create-oke 0.2.8 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +11 -32
  2. package/package.json +2 -4
  3. package/src/cli.test.ts +29 -117
  4. package/src/cli.ts +16 -139
  5. package/src/docs-origin.ts +2 -3
  6. package/src/index.ts +1 -2
  7. package/src/scaffold.ts +13 -31
  8. package/src/templates.ts +12 -90
  9. package/src/transform.ts +1 -1
  10. package/template/.env.example +56 -0
  11. package/{templates/standard → template}/README.md +25 -12
  12. package/template/bun.lock +845 -0
  13. package/template/drizzle.config.ts +25 -0
  14. package/{templates/full → template}/oke.config.ts +11 -7
  15. package/{templates/minimal → template}/src/core.ts +1 -1
  16. package/template/src/schema.decl.ts +12 -0
  17. package/examples/linkly/.env.example +0 -15
  18. package/examples/linkly/drizzle.config.ts +0 -11
  19. package/examples/linkly/oke.config.ts +0 -12
  20. package/examples/linkly/package.json +0 -18
  21. package/examples/linkly/src/app.ts +0 -21
  22. package/examples/linkly/src/core.ts +0 -4
  23. package/examples/linkly/src/flows/analytics/index.ts +0 -39
  24. package/examples/linkly/src/flows/links/index.ts +0 -82
  25. package/examples/linkly/src/flows/links/shapes.ts +0 -22
  26. package/examples/linkly/src/flows/links/signals.ts +0 -14
  27. package/examples/linkly/src/gates.ts +0 -10
  28. package/examples/linkly/src/schema.decl.ts +0 -39
  29. package/examples/linkly/src/schema.generated.ts +0 -30
  30. package/examples/linkly/src/schema.ts +0 -2
  31. package/examples/linkly/tests/linkly.test.ts +0 -19
  32. package/examples/notes/.env.example +0 -12
  33. package/examples/notes/drizzle.config.ts +0 -11
  34. package/examples/notes/oke.config.ts +0 -14
  35. package/examples/notes/package.json +0 -19
  36. package/examples/notes/src/app.ts +0 -13
  37. package/examples/notes/src/core.ts +0 -4
  38. package/examples/notes/src/flows/notes/index.ts +0 -58
  39. package/examples/notes/src/schema.ts +0 -9
  40. package/examples/notes/tests/notes.test.ts +0 -10
  41. package/examples/provisions/.env.example +0 -19
  42. package/examples/provisions/drizzle.config.ts +0 -11
  43. package/examples/provisions/oke.config.ts +0 -19
  44. package/examples/provisions/package.json +0 -18
  45. package/examples/provisions/src/app.ts +0 -60
  46. package/examples/provisions/src/channels.ts +0 -15
  47. package/examples/provisions/src/core.ts +0 -4
  48. package/examples/provisions/src/flows/notifications/index.ts +0 -25
  49. package/examples/provisions/src/flows/orders/index.ts +0 -97
  50. package/examples/provisions/src/flows/orders/shapes.ts +0 -21
  51. package/examples/provisions/src/flows/orders/signals.ts +0 -19
  52. package/examples/provisions/src/flows/payments/index.ts +0 -43
  53. package/examples/provisions/src/flows/payments/shapes.ts +0 -3
  54. package/examples/provisions/src/flows/payments/stripe.ts +0 -18
  55. package/examples/provisions/src/gates.ts +0 -4
  56. package/examples/provisions/src/locales/ar.ts +0 -1
  57. package/examples/provisions/src/locales/en.ts +0 -1
  58. package/examples/provisions/src/plugins/audit-schema.ts +0 -2
  59. package/examples/provisions/src/plugins/audit.ts +0 -14
  60. package/examples/provisions/src/schema.ts +0 -16
  61. package/examples/provisions/src/vault.ts +0 -16
  62. package/examples/provisions/tests/orders.test.ts +0 -19
  63. package/examples/skyport/.env.example +0 -34
  64. package/examples/skyport/drizzle.config.ts +0 -11
  65. package/examples/skyport/evals/triage.jsonl +0 -1
  66. package/examples/skyport/oke.config.ts +0 -47
  67. package/examples/skyport/oke.images.lock +0 -4
  68. package/examples/skyport/package.json +0 -19
  69. package/examples/skyport/src/ai.ts +0 -25
  70. package/examples/skyport/src/app.ts +0 -46
  71. package/examples/skyport/src/channels.ts +0 -12
  72. package/examples/skyport/src/core.ts +0 -4
  73. package/examples/skyport/src/flows/bookings/index.ts +0 -73
  74. package/examples/skyport/src/flows/bookings/shapes.ts +0 -6
  75. package/examples/skyport/src/flows/bookings/signals.ts +0 -13
  76. package/examples/skyport/src/flows/notifications/index.ts +0 -16
  77. package/examples/skyport/src/flows/payments/index.ts +0 -14
  78. package/examples/skyport/src/flows/payments/shapes.ts +0 -5
  79. package/examples/skyport/src/flows/support/index.ts +0 -50
  80. package/examples/skyport/src/flows/users/elements.ts +0 -2
  81. package/examples/skyport/src/flows/users/index.ts +0 -12
  82. package/examples/skyport/src/flows/users/shapes.ts +0 -6
  83. package/examples/skyport/src/gates.ts +0 -10
  84. package/examples/skyport/src/journeys.ts +0 -9
  85. package/examples/skyport/src/locales/ar.ts +0 -4
  86. package/examples/skyport/src/locales/en.ts +0 -4
  87. package/examples/skyport/src/plugins/audit-schema.ts +0 -7
  88. package/examples/skyport/src/plugins/audit.ts +0 -14
  89. package/examples/skyport/src/plugins/panel.tsx +0 -4
  90. package/examples/skyport/src/schema.ts +0 -25
  91. package/examples/skyport/src/vault.ts +0 -24
  92. package/examples/skyport/tests/support.test.ts +0 -16
  93. package/src/sync-templates.ts +0 -77
  94. package/templates/full/.env.example +0 -24
  95. package/templates/full/README.md +0 -65
  96. package/templates/full/drizzle.config.ts +0 -14
  97. package/templates/full/package.json +0 -18
  98. package/templates/full/src/ai.ts +0 -11
  99. package/templates/full/src/app.ts +0 -34
  100. package/templates/full/src/channels.ts +0 -9
  101. package/templates/full/src/core.ts +0 -4
  102. package/templates/full/src/flows/main/index.ts +0 -45
  103. package/templates/full/src/flows/main/shapes.ts +0 -3
  104. package/templates/full/src/flows/main/signals.ts +0 -8
  105. package/templates/full/src/gates.ts +0 -4
  106. package/templates/full/src/locales/ar.ts +0 -3
  107. package/templates/full/src/locales/en.ts +0 -3
  108. package/templates/full/src/schema.ts +0 -8
  109. package/templates/full/src/vault.ts +0 -9
  110. package/templates/full/tests/full.test.ts +0 -11
  111. package/templates/hello/.env.example +0 -9
  112. package/templates/hello/README.md +0 -40
  113. package/templates/hello/oke.config.ts +0 -3
  114. package/templates/hello/package.json +0 -14
  115. package/templates/hello/src/app.ts +0 -10
  116. package/templates/hello/src/flows/hello/index.ts +0 -27
  117. package/templates/hello/tests/hello.test.ts +0 -10
  118. package/templates/minimal/.env.example +0 -13
  119. package/templates/minimal/README.md +0 -49
  120. package/templates/minimal/drizzle.config.ts +0 -14
  121. package/templates/minimal/oke.config.ts +0 -14
  122. package/templates/minimal/package.json +0 -18
  123. package/templates/minimal/src/app.ts +0 -13
  124. package/templates/minimal/src/flows/main/index.ts +0 -49
  125. package/templates/minimal/src/schema.ts +0 -8
  126. package/templates/minimal/tests/minimal.test.ts +0 -13
  127. package/templates/standard/.env.example +0 -21
  128. package/templates/standard/drizzle.config.ts +0 -14
  129. package/templates/standard/oke.config.ts +0 -51
  130. package/templates/standard/src/core.ts +0 -4
  131. package/templates/standard/src/schema.ts +0 -9
  132. /package/{templates/standard → template}/package.json +0 -0
  133. /package/{templates/standard → template}/src/app.ts +0 -0
  134. /package/{templates/standard → template}/src/channels.ts +0 -0
  135. /package/{templates/standard → template}/src/flows/main/index.ts +0 -0
  136. /package/{templates/standard → template}/src/flows/main/shapes.ts +0 -0
  137. /package/{templates/standard → template}/src/flows/main/signals.ts +0 -0
  138. /package/{templates/standard → template}/src/gates.ts +0 -0
  139. /package/{templates/standard → template}/src/locales/ar.ts +0 -0
  140. /package/{templates/standard → template}/src/locales/en.ts +0 -0
  141. /package/{templates/standard → template}/src/vault.ts +0 -0
  142. /package/{templates/standard → template}/tests/standard.test.ts +0 -0
package/README.md CHANGED
@@ -8,9 +8,8 @@ cd my-app
8
8
  oke dev
9
9
  ```
10
10
 
11
- On a TTY, create-oke opens a Clack wizard: confirm name, pick a template, and
12
- optionally install + start `oke dev`. Pass `--yes` or `--template` /
13
- `--from-example` for non-interactive (CI / agents).
11
+ On a TTY, create-oke opens a Clack wizard to confirm the name and optionally
12
+ install dependencies and start `oke dev`. Pass `--yes` for non-interactive use.
14
13
 
15
14
  | Port | What |
16
15
  | ------- | ------------------- |
@@ -18,49 +17,29 @@ optionally install + start `oke dev`. Pass `--yes` or `--template` /
18
17
  | `:6533` | Console |
19
18
  | `:6535` | MCP (live Manifest) |
20
19
 
21
- Default template is **standard** (full recommended file layout, empty scaffolding).
22
- Pick another with `--template`, or start from a teaching example with `--from-example`:
20
+ The single bundled template is **standard**: the full recommended file layout
21
+ with empty scaffolding.
23
22
 
24
23
  ```bash
25
- bunx create-oke@latest my-app --template hello
26
- bunx create-oke@latest my-app --template minimal
27
- bunx create-oke@latest my-app --template full
24
+ bunx create-oke@latest my-app --template standard
28
25
  bunx create-oke@latest my-app --sql postgres
29
26
  bunx create-oke@latest my-app --yes
30
- bunx create-oke@latest my-notes --from-example notes
31
27
  ```
32
28
 
33
29
  `--sql sqlite|postgres` is opt-in. The default keeps the dual-mode Store
34
- config (`local: sqlite` · `docker`/`prod: postgres`) and a `sqliteTable`
35
- schema. Pass `--sql postgres` to rewrite `src/schema.ts` to `pgTable` /
36
- `drizzle-orm/pg-core` and pin `oke.config.ts` `store.sql` local/docker/prod to
37
- `postgres`. The interactive wizard does not ask same dual-mode default
38
- (skipped entirely for `hello`, which has no Store).
30
+ config (`local: sqlite` · `docker`/`prod: postgres`) and abstract
31
+ `src/schema.decl.ts` (dialect emitted from the active driver). Pass
32
+ `--sql postgres` to pin `oke.config.ts` `store.sql` local/docker/prod to
33
+ `postgres`. The interactive wizard does not ask and keeps the same dual-mode
34
+ default.
39
35
 
40
36
  Store-bearing scaffolds ship `drizzle.config.ts` and `.env.example`. Local
41
37
  `oke dev` auto-runs `oke db push` on schema change; use `oke mode docker` /
42
38
  `oke dev --docker` for compose infra.
43
39
 
44
- | Template | Purpose |
45
- | ---------- | --------------------------------------------------------- |
46
- | `hello` | Fastest possible "it works" — one flow, no Store |
47
- | `minimal` | Smallest shape you'd actually ship — Store + 1–2 flows |
48
- | `standard` | Full recommended file layout, empty scaffolding (default) |
49
- | `full` | Every element present and wired, no business logic |
50
-
51
40
  Every scaffold writes an `AGENTS.md` so coding agents know the OKE contract
52
41
  (`--no-agents-md` to skip).
53
42
 
54
- `--from-example` copies a teaching example (including its business logic and
55
- comments) — most new projects want `--template` instead.
56
-
57
- | Example | Adds |
58
- | ------------ | ---------------------------------------------------- |
59
- | `notes` | `oke` · `flow` · `http` · `store.sql` · typed client |
60
- | `linkly` | `signal` · `clock` · `gate` · abstract `schema.decl` |
61
- | `provisions` | durable flows · `vault` · `channel` · plugins |
62
- | `skyport` | `ai` · multi-tenancy · distributed topology |
63
-
64
43
  ## Registries
65
44
 
66
45
  | Registry | Package |
@@ -68,4 +47,4 @@ comments) — most new projects want `--template` instead.
68
47
  | npm (CLI) | [`create-oke`](https://www.npmjs.com/package/create-oke) — use this with `bunx` |
69
48
  | JSR | [`@omqkhafi/create-oke`](https://jsr.io/@omqkhafi/create-oke) — same sources; prefer npm for the binary |
70
49
 
71
- Requires **Bun ≥ 1.3**. Docs: [okengine.vercel.app](https://okengine.vercel.app) · [handbook](https://okengine.vercel.app/docs) · [unified theory](https://github.com/omqkhafi/okengine/blob/main/docs/spec/unified-theory.md).
50
+ Requires **Bun ≥ 1.3**. Docs: [oke.omqkhafi.dev](https://oke.omqkhafi.dev) · [handbook](https://oke.omqkhafi.dev/docs).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-oke",
3
- "version": "0.2.8",
3
+ "version": "0.3.2",
4
4
  "description": "Scaffold an okengine app — bunx create-oke@latest <name>",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,8 +13,7 @@
13
13
  },
14
14
  "files": [
15
15
  "src",
16
- "templates",
17
- "examples"
16
+ "template"
18
17
  ],
19
18
  "type": "module",
20
19
  "sideEffects": false,
@@ -22,7 +21,6 @@
22
21
  ".": "./src/index.ts"
23
22
  },
24
23
  "scripts": {
25
- "prepack": "bun ./src/sync-templates.ts",
26
24
  "typecheck": "tsc --noEmit",
27
25
  "test": "bun test",
28
26
  "test:integration": "CREATE_OKE_INTEGRATION=1 bun test tests/scaffold.integration.test.ts tests/dev.integration.test.ts"
package/src/cli.test.ts CHANGED
@@ -7,7 +7,6 @@ import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node
7
7
  import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import {
10
- FROM_EXAMPLE_CHOICE,
11
10
  formatCdPath,
12
11
  nextStepsText,
13
12
  parseArgs,
@@ -19,14 +18,7 @@ import {
19
18
  type InteractiveAnswers,
20
19
  } from "./cli.ts";
21
20
  import { listTemplateFiles, scaffold } from "./scaffold.ts";
22
- import {
23
- DEFAULT_TEMPLATE,
24
- EXAMPLES,
25
- TEMPLATES,
26
- packageRoot,
27
- resolveExampleDir,
28
- resolveTemplateDir,
29
- } from "./templates.ts";
21
+ import { DEFAULT_TEMPLATE, TEMPLATES, packageRoot, resolveTemplateDir } from "./templates.ts";
30
22
  import {
31
23
  sanitizeProjectName,
32
24
  shouldSkipTemplatePath,
@@ -41,19 +33,13 @@ describe("parseArgs", () => {
41
33
  expect(a.name).toBe("my-app");
42
34
  expect(a.template).toBe("standard");
43
35
  expect(a.templateExplicit).toBe(false);
44
- expect(a.fromExample).toBeUndefined();
45
36
  });
46
37
 
47
38
  test("accepts --template and -t", () => {
48
- expect(parseArgs(["x", "--template", "hello"]).template).toBe("hello");
49
- expect(parseArgs(["x", "-t", "full"]).template).toBe("full");
50
- expect(parseArgs(["x", "--template=minimal"]).template).toBe("minimal");
51
- expect(parseArgs(["x", "--template", "hello"]).templateExplicit).toBe(true);
52
- });
53
-
54
- test("accepts --from-example", () => {
55
- expect(parseArgs(["x", "--from-example", "notes"]).fromExample).toBe("notes");
56
- expect(parseArgs(["x", "--from-example=skyport"]).fromExample).toBe("skyport");
39
+ expect(parseArgs(["x", "--template", "standard"]).template).toBe("standard");
40
+ expect(parseArgs(["x", "-t", "standard"]).template).toBe("standard");
41
+ expect(parseArgs(["x", "--template=standard"]).template).toBe("standard");
42
+ expect(parseArgs(["x", "--template", "standard"]).templateExplicit).toBe(true);
57
43
  });
58
44
 
59
45
  test("accepts --sql sqlite|postgres", () => {
@@ -64,13 +50,10 @@ describe("parseArgs", () => {
64
50
  expect(parseArgs(["x", "--sql", "postgres"]).sqlDriverExplicit).toBe(true);
65
51
  });
66
52
 
67
- test("rejects unknown template / example / sql / both", () => {
53
+ test("rejects unknown template, option, and sql", () => {
68
54
  expect(() => parseArgs(["x", "--template", "nope"])).toThrow(/template/);
69
- expect(() => parseArgs(["x", "--from-example", "nope"])).toThrow(/example/);
55
+ expect(() => parseArgs(["x", "--unknown"])).toThrow(/unknown option/);
70
56
  expect(() => parseArgs(["x", "--sql", "mysql"])).toThrow(/sql/);
71
- expect(() => parseArgs(["x", "--template", "hello", "--from-example", "notes"])).toThrow(
72
- /either/,
73
- );
74
57
  });
75
58
  });
76
59
 
@@ -87,9 +70,8 @@ describe("shouldPrompt", () => {
87
70
  expect(shouldPrompt(parseArgs([]), false)).toBe(false);
88
71
  expect(shouldPrompt(parseArgs(["my-app"]), false)).toBe(false);
89
72
  expect(shouldPrompt(parseArgs(["my-app", "--yes"]), true)).toBe(false);
90
- expect(shouldPrompt(parseArgs(["my-app", "--template", "hello"]), true)).toBe(false);
91
- expect(shouldPrompt(parseArgs(["--template", "hello"]), true)).toBe(false);
92
- expect(shouldPrompt(parseArgs(["--from-example", "notes"]), true)).toBe(false);
73
+ expect(shouldPrompt(parseArgs(["my-app", "--template", "standard"]), true)).toBe(false);
74
+ expect(shouldPrompt(parseArgs(["--template", "standard"]), true)).toBe(false);
93
75
  expect(shouldPrompt(parseArgs(["--help"]), true)).toBe(false);
94
76
  });
95
77
  });
@@ -110,11 +92,7 @@ describe("parseArgs flags", () => {
110
92
  });
111
93
 
112
94
  describe("sourceFromArgs", () => {
113
- test("prefers example when set", () => {
114
- expect(sourceFromArgs(parseArgs(["x", "--from-example", "linkly"]))).toEqual({
115
- kind: "example",
116
- id: "linkly",
117
- });
95
+ test("uses the standard template", () => {
118
96
  expect(sourceFromArgs(parseArgs(["x"]))).toEqual({
119
97
  kind: "template",
120
98
  id: DEFAULT_TEMPLATE,
@@ -139,23 +117,6 @@ describe("scaffoldArgsFromAnswers ≡ flag-driven", () => {
139
117
  }
140
118
  });
141
119
 
142
- test("each --from-example path matches interactive from-example choice", () => {
143
- for (const id of EXAMPLES) {
144
- const answers: InteractiveAnswers = {
145
- name: "x",
146
- choice: FROM_EXAMPLE_CHOICE,
147
- example: id,
148
- installAndRun: false,
149
- agentsMd: true,
150
- };
151
- const fromAnswers = scaffoldArgsFromAnswers(answers);
152
- const fromFlags = scaffoldArgsFromCli(parseArgs(["x", "--from-example", id]));
153
- expect(fromAnswers).toEqual(fromFlags);
154
- expect(fromAnswers.source).toEqual({ kind: "example", id });
155
- expect(fromAnswers.sqlDriver).toBe("sqlite");
156
- }
157
- });
158
-
159
120
  test("interactive never opts into --sql postgres (flag-only)", () => {
160
121
  const answers: InteractiveAnswers = {
161
122
  name: "x",
@@ -170,8 +131,8 @@ describe("scaffoldArgsFromAnswers ≡ flag-driven", () => {
170
131
  ).toBe("postgres");
171
132
  });
172
133
 
173
- test("8 paths cover every template and every example", () => {
174
- expect(TEMPLATES.length + EXAMPLES.length).toBe(8);
134
+ test("only standard is available", () => {
135
+ expect(TEMPLATES).toEqual(["standard"]);
175
136
  });
176
137
  });
177
138
 
@@ -179,7 +140,7 @@ describe("transformPackageJson", () => {
179
140
  test("rewrites name and okengine file:../.. to installable ref", () => {
180
141
  const next = transformPackageJson(
181
142
  {
182
- name: "@oke/template-hello",
143
+ name: "@oke/template-standard",
183
144
  private: true,
184
145
  dependencies: {
185
146
  okengine: "file:../..",
@@ -298,32 +259,6 @@ describe("scaffold structure", () => {
298
259
  }
299
260
  });
300
261
 
301
- test("hello has exactly one flow and no Store", () => {
302
- const dir = mkdtempSync(join(tmpdir(), "create-oke-hello-assert-"));
303
- try {
304
- const result = scaffold({
305
- targetDir: join(dir, "hello"),
306
- name: "hello-app",
307
- source: { kind: "template", id: "hello" },
308
- });
309
- const flowFiles = result.files.filter((f) => f.startsWith("src/flows/") && f.endsWith(".ts"));
310
- expect(flowFiles).toEqual(["src/flows/hello/index.ts"]);
311
- const flowSrc = readFileSync(join(result.targetDir, "src/flows/hello/index.ts"), "utf8");
312
- expect(flowSrc).toMatch(/export const hello/);
313
- expect(flowSrc).not.toMatch(/\bstore\b/);
314
- expect(result.files.some((f) => f.includes("core.ts"))).toBe(false);
315
- expect(result.files.some((f) => f.includes("schema.ts"))).toBe(false);
316
- const all = result.files
317
- .filter((f) => f.endsWith(".ts"))
318
- .map((f) => readFileSync(join(result.targetDir, f), "utf8"))
319
- .join("\n");
320
- expect(all).not.toMatch(/store\.sql/);
321
- expect(all).not.toMatch(/\bbookings\b|\borders\b|\blinks\b|\bnotes\b/);
322
- } finally {
323
- rmSync(dir, { recursive: true, force: true });
324
- }
325
- });
326
-
327
262
  test("standard has full four-applications layout and no business domain", () => {
328
263
  const dir = mkdtempSync(join(tmpdir(), "create-oke-standard-assert-"));
329
264
  try {
@@ -341,7 +276,7 @@ describe("scaffold structure", () => {
341
276
  "src/flows/main/shapes.ts",
342
277
  "src/flows/main/signals.ts",
343
278
  "src/core.ts",
344
- "src/schema.ts",
279
+ "src/schema.decl.ts",
345
280
  "src/app.ts",
346
281
  ]) {
347
282
  expect(result.files).toContain(path);
@@ -356,26 +291,7 @@ describe("scaffold structure", () => {
356
291
  }
357
292
  });
358
293
 
359
- test("--from-example notes matches today's example tree", () => {
360
- const dir = mkdtempSync(join(tmpdir(), "create-oke-from-example-"));
361
- try {
362
- const exampleDir = resolveExampleDir("notes");
363
- const expected = listTemplateFiles(exampleDir);
364
- const result = scaffold({
365
- targetDir: join(dir, "notes"),
366
- name: "from-notes",
367
- source: { kind: "example", id: "notes" },
368
- });
369
- const extras = ["AGENTS.md"];
370
- if (expected.includes(".env.example")) extras.push(".env.local");
371
- expect([...result.files].sort()).toEqual([...expected, ...extras].sort());
372
- expect(result.files).toContain("src/flows/notes/index.ts");
373
- } finally {
374
- rmSync(dir, { recursive: true, force: true });
375
- }
376
- });
377
-
378
- test("--sql postgres rewrites schema dialect and pins store.sql", () => {
294
+ test("--sql postgres pins store.sql (abstract schema stays dialect-agnostic)", () => {
379
295
  const dir = mkdtempSync(join(tmpdir(), "create-oke-sql-pg-"));
380
296
  try {
381
297
  const result = scaffold({
@@ -385,10 +301,14 @@ describe("scaffold structure", () => {
385
301
  sqlDriver: "postgres",
386
302
  });
387
303
  expect(result.sqlDriver).toBe("postgres");
388
- const schema = readFileSync(join(result.targetDir, "src/schema.ts"), "utf8");
389
- expect(schema).toContain('from "drizzle-orm/pg-core"');
390
- expect(schema).toContain("pgTable(");
391
- expect(schema).not.toContain("sqliteTable");
304
+ // Abstract decl is dialect-agnostic — emit picks pgTable at sync time.
305
+ const decl = readFileSync(join(result.targetDir, "src/schema.decl.ts"), "utf8");
306
+ expect(decl).toContain("store.schema.table(");
307
+ expect(decl).not.toContain("sqliteTable");
308
+ expect(decl).not.toContain("pgTable");
309
+ const drizzle = readFileSync(join(result.targetDir, "drizzle.config.ts"), "utf8");
310
+ expect(drizzle).toContain("OKE_DRIZZLE_DIALECT");
311
+ expect(drizzle).toContain("schema.generated.ts");
392
312
  const config = readFileSync(join(result.targetDir, "oke.config.ts"), "utf8");
393
313
  expect(config).toMatch(/sql:\s*\{[\s\S]*local:\s*"postgres"/);
394
314
  expect(config).toMatch(/sql:\s*\{[\s\S]*docker:\s*"postgres"/);
@@ -403,9 +323,9 @@ describe("scaffold structure", () => {
403
323
  const dir = mkdtempSync(join(tmpdir(), "create-oke-no-agents-"));
404
324
  try {
405
325
  const result = scaffold({
406
- targetDir: join(dir, "hello"),
326
+ targetDir: join(dir, "standard"),
407
327
  name: "no-agents",
408
- source: { kind: "template", id: "hello" },
328
+ source: { kind: "template", id: "standard" },
409
329
  writeAgentsMd: false,
410
330
  });
411
331
  expect(result.files).not.toContain("AGENTS.md");
@@ -443,7 +363,7 @@ describe("non-TTY CLI", () => {
443
363
  const root = mkdtempSync(join(tmpdir(), "create-oke-flag-"));
444
364
  const target = join(root, "flag-app");
445
365
  try {
446
- const code = await run([target, "--template", "hello", "--no-install"], {
366
+ const code = await run([target, "--template", "standard", "--no-install"], {
447
367
  stdinIsTTY: false,
448
368
  runPostScaffold: false,
449
369
  });
@@ -453,25 +373,17 @@ describe("non-TTY CLI", () => {
453
373
  const result = {
454
374
  targetDir: target,
455
375
  name: "flag-app",
456
- source: { kind: "template" as const, id: "hello" as const },
457
- label: "hello",
376
+ source: { kind: "template" as const, id: "standard" as const },
377
+ label: "standard",
458
378
  okengineDependency: "x",
459
379
  files: [],
460
380
  sqlDriver: "sqlite" as const,
461
381
  };
462
382
  expect(nextStepsText(result)).toContain("oke dev");
463
383
  expect(nextStepsText(result)).toContain("bun install");
464
- expect(nextStepsText(result)).toContain("okengine.vercel.app");
384
+ expect(nextStepsText(result)).toContain("oke.omqkhafi.dev");
465
385
  } finally {
466
386
  rmSync(root, { recursive: true, force: true });
467
387
  }
468
388
  });
469
389
  });
470
-
471
- describe("examples catalogue", () => {
472
- test("all four teaching examples resolve", () => {
473
- for (const id of EXAMPLES) {
474
- expect(resolveExampleDir(id)).toBeTruthy();
475
- }
476
- });
477
- });
package/src/cli.ts CHANGED
@@ -1,25 +1,14 @@
1
1
  /**
2
- * `create-oke` CLI — clean templates by default; teaching examples opt-in.
2
+ * `create-oke` CLI — one standard starter.
3
3
  *
4
4
  * ```bash
5
- * bunx create-oke@latest <name> [--template hello|minimal|standard|full]
6
- * bunx create-oke@latest <name> --from-example notes|linkly|provisions|skyport
5
+ * bunx create-oke@latest <name> [--template standard]
7
6
  * bunx create-oke@latest <name> --yes
8
7
  * bunx create-oke@latest # interactive when stdin is a TTY
9
8
  * ```
10
9
  */
11
10
 
12
- import {
13
- cancel,
14
- confirm,
15
- intro,
16
- isCancel,
17
- note,
18
- outro,
19
- select,
20
- spinner,
21
- text,
22
- } from "@clack/prompts";
11
+ import { cancel, confirm, intro, isCancel, note, outro, spinner, text } from "@clack/prompts";
23
12
  import { basename, relative, resolve } from "node:path";
24
13
  import { existsSync, rmSync } from "node:fs";
25
14
  import { agentsMdContent } from "./agents-md.ts";
@@ -27,13 +16,9 @@ import { docsUrl } from "./docs-origin.ts";
27
16
  import { scaffold, type ScaffoldResult, type ScaffoldSource } from "./scaffold.ts";
28
17
  import {
29
18
  DEFAULT_TEMPLATE,
30
- EXAMPLE_NEW_IDEAS,
31
- EXAMPLES,
32
19
  TEMPLATE_PURPOSES,
33
20
  TEMPLATES,
34
- isExampleId,
35
21
  isTemplateId,
36
- type ExampleId,
37
22
  type TemplateId,
38
23
  } from "./templates.ts";
39
24
  import { DEFAULT_SQL_DRIVER, SQL_DRIVERS, isSqlDriverId, type SqlDriverId } from "./transform.ts";
@@ -42,7 +27,6 @@ import { DEFAULT_SQL_DRIVER, SQL_DRIVERS, isSqlDriverId, type SqlDriverId } from
42
27
  export type CliArgs = {
43
28
  readonly name: string | undefined;
44
29
  readonly template: TemplateId;
45
- readonly fromExample: ExampleId | undefined;
46
30
  /** True when `--template` / `-t` was present on the argv. */
47
31
  readonly templateExplicit: boolean;
48
32
  /** Store SQL driver (`sqlite` default). */
@@ -62,27 +46,13 @@ export type CliArgs = {
62
46
  readonly targetDir: string | undefined;
63
47
  };
64
48
 
65
- /**
66
- * Sentinel choice for "Start from a worked example" in interactive answers.
67
- * Distinct from clack's internal select value (`__example__`).
68
- */
69
- export const FROM_EXAMPLE_CHOICE = "from-example" as const;
70
-
71
49
  /** Answers collected by the interactive ask step (no clack types). */
72
- export type InteractiveAnswers =
73
- | {
74
- readonly name: string;
75
- readonly choice: TemplateId;
76
- readonly installAndRun: boolean;
77
- readonly agentsMd: boolean;
78
- }
79
- | {
80
- readonly name: string;
81
- readonly choice: typeof FROM_EXAMPLE_CHOICE;
82
- readonly example: ExampleId;
83
- readonly installAndRun: boolean;
84
- readonly agentsMd: boolean;
85
- };
50
+ export type InteractiveAnswers = {
51
+ readonly name: string;
52
+ readonly choice: TemplateId;
53
+ readonly installAndRun: boolean;
54
+ readonly agentsMd: boolean;
55
+ };
86
56
 
87
57
  /**
88
58
  * Canonical scaffold invocation — shared by interactive and flag-driven paths.
@@ -103,7 +73,6 @@ export type ScaffoldCallArgs = {
103
73
  export function parseArgs(argv: readonly string[]): CliArgs {
104
74
  let name: string | undefined;
105
75
  let template: TemplateId = DEFAULT_TEMPLATE;
106
- let fromExample: ExampleId | undefined;
107
76
  let templateExplicit = false;
108
77
  let sqlDriver: SqlDriverId = DEFAULT_SQL_DRIVER;
109
78
  let sqlDriverExplicit = false;
@@ -175,22 +144,6 @@ export function parseArgs(argv: readonly string[]): CliArgs {
175
144
  templateExplicit = true;
176
145
  continue;
177
146
  }
178
- if (a === "--from-example") {
179
- const next = argv[++i];
180
- if (!next || !isExampleId(next)) {
181
- throw new Error(`create-oke: --from-example must be one of ${EXAMPLES.join("|")}`);
182
- }
183
- fromExample = next;
184
- continue;
185
- }
186
- if (a.startsWith("--from-example=")) {
187
- const value = a.slice("--from-example=".length);
188
- if (!isExampleId(value)) {
189
- throw new Error(`create-oke: --from-example must be one of ${EXAMPLES.join("|")}`);
190
- }
191
- fromExample = value;
192
- continue;
193
- }
194
147
  if (a.startsWith("-")) {
195
148
  throw new Error(`create-oke: unknown option ${a}`);
196
149
  }
@@ -201,10 +154,6 @@ export function parseArgs(argv: readonly string[]): CliArgs {
201
154
  throw new Error(`create-oke: unexpected argument ${a}`);
202
155
  }
203
156
 
204
- if (templateExplicit && fromExample !== undefined) {
205
- throw new Error("create-oke: use either --template or --from-example, not both");
206
- }
207
-
208
157
  if (argv.includes("--install") && argv.includes("--no-install")) {
209
158
  throw new Error("create-oke: use either --install or --no-install, not both");
210
159
  }
@@ -216,7 +165,6 @@ export function parseArgs(argv: readonly string[]): CliArgs {
216
165
  return {
217
166
  name,
218
167
  template,
219
- fromExample,
220
168
  templateExplicit,
221
169
  sqlDriver,
222
170
  sqlDriverExplicit,
@@ -266,25 +214,18 @@ export function helpText(): string {
266
214
  (id) =>
267
215
  ` ${id.padEnd(12)}${TEMPLATE_PURPOSES[id]}${id === DEFAULT_TEMPLATE ? " (default)" : ""}`,
268
216
  ).join("\n");
269
- const exampleLines = EXAMPLES.map((id) => ` ${id.padEnd(12)}${EXAMPLE_NEW_IDEAS[id]}`).join(
270
- "\n",
271
- );
272
-
273
217
  return `create-oke — scaffold an okengine app
274
218
 
275
219
  Usage:
276
- bunx create-oke@latest <name> [--template hello|minimal|standard|full]
277
- bunx create-oke@latest <name> --from-example notes|linkly|provisions|skyport
220
+ bunx create-oke@latest <name> [--template standard]
278
221
  bunx create-oke@latest <name> --yes
279
222
  bunx create-oke@latest # interactive (TTY only)
280
223
 
281
224
  Options:
282
225
  -t, --template <id> Clean starter (default: ${DEFAULT_TEMPLATE})
283
- --from-example <id> Teaching example (non-interactive)
284
226
  --sql <id> Opt-in Store SQL dialect: ${SQL_DRIVERS.join("|")}
285
227
  Default keeps local sqlite · docker/prod postgres.
286
- --sql postgres rewrites src/schema.ts to pgTable and
287
- pins store.sql local/docker/prod to postgres.
228
+ --sql postgres pins store.sql local/docker/prod to postgres.
288
229
  -y, --yes No prompts; defaults + bun install (no oke dev)
289
230
  --install Run bun install after scaffold
290
231
  --no-install Skip bun install
@@ -292,16 +233,11 @@ Options:
292
233
  --no-agents-md Skip AGENTS.md
293
234
  -h, --help Show this help
294
235
 
295
- Templates (clean starters from templates/):
236
+ Template:
296
237
  ${templateLines}
297
238
 
298
- --from-example (copies a teaching example, including its business logic and
299
- comments — most new projects want --template instead):
300
- ${exampleLines}
301
-
302
239
  No telemetry. Bun only. On a TTY, a project name alone still opens the wizard
303
- (confirm name, template, install). Non-TTY / --yes / --template /
304
- --from-example stay fully scriptable.
240
+ (confirm name and install). Non-TTY / --yes / --template stay fully scriptable.
305
241
  `;
306
242
  }
307
243
 
@@ -309,7 +245,7 @@ No telemetry. Bun only. On a TTY, a project name alone still opens the wizard
309
245
  * Whether the CLI should open the interactive Clack flow.
310
246
  *
311
247
  * TTY humans get the wizard even when a name is pre-filled. Config flags
312
- * (`--template`, `--from-example`) or `--yes` skip prompts for CI/agents.
248
+ * `--template` or `--yes` skips prompts for CI/agents.
313
249
  *
314
250
  * @param args - Parsed args
315
251
  * @param stdinIsTTY - `process.stdin.isTTY`
@@ -319,7 +255,6 @@ export function shouldPrompt(args: CliArgs, stdinIsTTY: boolean | undefined): bo
319
255
  if (args.help) return false;
320
256
  if (args.yes) return false;
321
257
  if (args.templateExplicit) return false;
322
- if (args.fromExample !== undefined) return false;
323
258
  return true;
324
259
  }
325
260
 
@@ -329,9 +264,6 @@ export function shouldPrompt(args: CliArgs, stdinIsTTY: boolean | undefined): bo
329
264
  * @param args - Parsed args
330
265
  */
331
266
  export function sourceFromArgs(args: CliArgs): ScaffoldSource {
332
- if (args.fromExample !== undefined) {
333
- return { kind: "example", id: args.fromExample };
334
- }
335
267
  return { kind: "template", id: args.template };
336
268
  }
337
269
 
@@ -359,7 +291,7 @@ export function scaffoldArgsFromCli(args: CliArgs): ScaffoldCallArgs {
359
291
  * Pure map from interactive answers → scaffold call args.
360
292
  *
361
293
  * Independent of clack — unit-tested against {@link scaffoldArgsFromCli}
362
- * for every template and every `--from-example` choice.
294
+ * for the standard template.
363
295
  *
364
296
  * @param answers - Collected interactive answers
365
297
  */
@@ -368,15 +300,6 @@ export function scaffoldArgsFromAnswers(answers: InteractiveAnswers): ScaffoldCa
368
300
  const name = basename(targetDir);
369
301
  // Interactive always keeps the dual-mode default (local sqlite ·
370
302
  // docker/prod postgres). Opt into postgres-everywhere with `--sql postgres`.
371
- if (answers.choice === FROM_EXAMPLE_CHOICE) {
372
- return {
373
- name,
374
- targetDir,
375
- source: { kind: "example", id: answers.example },
376
- agentsMd: answers.agentsMd,
377
- sqlDriver: DEFAULT_SQL_DRIVER,
378
- };
379
- }
380
303
  return {
381
304
  name,
382
305
  targetDir,
@@ -419,54 +342,8 @@ export async function askInteractiveAnswers(
419
342
  if (isCancel(nameValue)) return null;
420
343
  const name = String(nameValue).trim();
421
344
 
422
- const templateValue = await select({
423
- message: "Template",
424
- options: [
425
- ...TEMPLATES.map((id) => ({
426
- value: id as string,
427
- label: id,
428
- hint: TEMPLATE_PURPOSES[id],
429
- })),
430
- {
431
- value: "__example__",
432
- label: "Start from a worked example",
433
- hint: "Teaching apps with business logic — most projects want a template",
434
- },
435
- ],
436
- initialValue: DEFAULT_TEMPLATE,
437
- });
438
- if (isCancel(templateValue)) return null;
439
-
440
345
  const agentsMd = partial.agentsMd ?? true;
441
346
 
442
- if (templateValue === "__example__") {
443
- const exampleValue = await select({
444
- message: "Example",
445
- options: EXAMPLES.map((id) => ({
446
- value: id,
447
- label: id,
448
- hint: EXAMPLE_NEW_IDEAS[id],
449
- })),
450
- });
451
- if (isCancel(exampleValue)) return null;
452
-
453
- const installAndRunValue = await confirm({
454
- message: "Install dependencies and start oke dev?",
455
- initialValue: true,
456
- });
457
- if (isCancel(installAndRunValue)) return null;
458
-
459
- return {
460
- name,
461
- choice: FROM_EXAMPLE_CHOICE,
462
- example: exampleValue as ExampleId,
463
- installAndRun: Boolean(installAndRunValue),
464
- agentsMd,
465
- };
466
- }
467
-
468
- const templateId = templateValue as TemplateId;
469
-
470
347
  const installAndRunValue = await confirm({
471
348
  message: "Install dependencies and start oke dev?",
472
349
  initialValue: true,
@@ -475,7 +352,7 @@ export async function askInteractiveAnswers(
475
352
 
476
353
  return {
477
354
  name,
478
- choice: templateId,
355
+ choice: DEFAULT_TEMPLATE,
479
356
  installAndRun: Boolean(installAndRunValue),
480
357
  agentsMd,
481
358
  };
@@ -2,11 +2,10 @@
2
2
  * Canonical public docs origin for scaffolded AGENTS.md and next-steps.
3
3
  *
4
4
  * Keep in lockstep with `src/docs-origin.ts` in the okengine package.
5
- * Flip both when the custom domain is attached: `https://oke.omqkhafi.dev`.
6
5
  */
7
6
 
8
- /** Live docs site (Vercel). Planned cutover: `https://oke.omqkhafi.dev`. */
9
- export const DOCS_ORIGIN = "https://okengine.vercel.app" as const;
7
+ /** Live docs site. */
8
+ export const DOCS_ORIGIN = "https://oke.omqkhafi.dev" as const;
10
9
 
11
10
  /**
12
11
  * Absolute docs URL for a path under the handbook / site.