create-oke 0.2.6 → 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 (126) hide show
  1. package/README.md +26 -35
  2. package/package.json +10 -12
  3. package/src/cli.test.ts +124 -144
  4. package/src/cli.ts +62 -160
  5. package/src/docs-origin.ts +2 -3
  6. package/src/index.ts +1 -2
  7. package/src/scaffold.ts +52 -32
  8. package/src/templates.ts +12 -91
  9. package/src/transform.ts +60 -10
  10. package/template/.env.example +56 -0
  11. package/template/README.md +77 -0
  12. package/template/bun.lock +845 -0
  13. package/template/drizzle.config.ts +25 -0
  14. package/{templates/standard → template}/oke.config.ts +22 -25
  15. package/{templates/standard → template}/package.json +3 -0
  16. package/{templates/minimal → template}/src/core.ts +1 -1
  17. package/template/src/schema.decl.ts +12 -0
  18. package/examples/linkly/oke.config.ts +0 -10
  19. package/examples/linkly/package.json +0 -15
  20. package/examples/linkly/src/app.ts +0 -21
  21. package/examples/linkly/src/core.ts +0 -4
  22. package/examples/linkly/src/flows/analytics/index.ts +0 -24
  23. package/examples/linkly/src/flows/links/index.ts +0 -59
  24. package/examples/linkly/src/flows/links/shapes.ts +0 -22
  25. package/examples/linkly/src/flows/links/signals.ts +0 -13
  26. package/examples/linkly/src/gates.ts +0 -8
  27. package/examples/linkly/src/schema.ts +0 -17
  28. package/examples/linkly/tests/linkly.test.ts +0 -19
  29. package/examples/notes/oke.config.ts +0 -14
  30. package/examples/notes/package.json +0 -16
  31. package/examples/notes/src/app.ts +0 -13
  32. package/examples/notes/src/core.ts +0 -4
  33. package/examples/notes/src/flows/notes/index.ts +0 -44
  34. package/examples/notes/src/schema.ts +0 -9
  35. package/examples/notes/tests/notes.test.ts +0 -10
  36. package/examples/provisions/oke.config.ts +0 -17
  37. package/examples/provisions/package.json +0 -15
  38. package/examples/provisions/src/app.ts +0 -60
  39. package/examples/provisions/src/channels.ts +0 -14
  40. package/examples/provisions/src/core.ts +0 -4
  41. package/examples/provisions/src/flows/notifications/index.ts +0 -19
  42. package/examples/provisions/src/flows/orders/index.ts +0 -70
  43. package/examples/provisions/src/flows/orders/shapes.ts +0 -21
  44. package/examples/provisions/src/flows/orders/signals.ts +0 -19
  45. package/examples/provisions/src/flows/payments/index.ts +0 -37
  46. package/examples/provisions/src/flows/payments/shapes.ts +0 -3
  47. package/examples/provisions/src/flows/payments/stripe.ts +0 -18
  48. package/examples/provisions/src/gates.ts +0 -4
  49. package/examples/provisions/src/locales/ar.ts +0 -1
  50. package/examples/provisions/src/locales/en.ts +0 -1
  51. package/examples/provisions/src/plugins/audit-schema.ts +0 -2
  52. package/examples/provisions/src/plugins/audit.ts +0 -14
  53. package/examples/provisions/src/schema.ts +0 -16
  54. package/examples/provisions/src/vault.ts +0 -16
  55. package/examples/provisions/tests/orders.test.ts +0 -18
  56. package/examples/skyport/evals/triage.jsonl +0 -1
  57. package/examples/skyport/oke.config.ts +0 -44
  58. package/examples/skyport/oke.images.lock +0 -4
  59. package/examples/skyport/package.json +0 -16
  60. package/examples/skyport/src/ai.ts +0 -25
  61. package/examples/skyport/src/app.ts +0 -57
  62. package/examples/skyport/src/channels.ts +0 -12
  63. package/examples/skyport/src/core.ts +0 -4
  64. package/examples/skyport/src/flows/bookings/index.ts +0 -50
  65. package/examples/skyport/src/flows/bookings/shapes.ts +0 -6
  66. package/examples/skyport/src/flows/bookings/signals.ts +0 -9
  67. package/examples/skyport/src/flows/notifications/index.ts +0 -16
  68. package/examples/skyport/src/flows/payments/index.ts +0 -14
  69. package/examples/skyport/src/flows/payments/shapes.ts +0 -5
  70. package/examples/skyport/src/flows/support/index.ts +0 -38
  71. package/examples/skyport/src/flows/users/elements.ts +0 -2
  72. package/examples/skyport/src/flows/users/index.ts +0 -12
  73. package/examples/skyport/src/flows/users/shapes.ts +0 -6
  74. package/examples/skyport/src/gates.ts +0 -10
  75. package/examples/skyport/src/journeys.ts +0 -9
  76. package/examples/skyport/src/locales/ar.ts +0 -4
  77. package/examples/skyport/src/locales/en.ts +0 -4
  78. package/examples/skyport/src/plugins/audit-schema.ts +0 -7
  79. package/examples/skyport/src/plugins/audit.ts +0 -14
  80. package/examples/skyport/src/plugins/panel.tsx +0 -4
  81. package/examples/skyport/src/schema.ts +0 -22
  82. package/examples/skyport/src/vault.ts +0 -24
  83. package/examples/skyport/tests/support.test.ts +0 -16
  84. package/src/sync-templates.ts +0 -86
  85. package/templates/full/README.md +0 -49
  86. package/templates/full/oke.config.ts +0 -68
  87. package/templates/full/package.json +0 -15
  88. package/templates/full/src/ai.ts +0 -11
  89. package/templates/full/src/app.ts +0 -34
  90. package/templates/full/src/channels.ts +0 -9
  91. package/templates/full/src/core.ts +0 -4
  92. package/templates/full/src/flows/main/index.ts +0 -45
  93. package/templates/full/src/flows/main/shapes.ts +0 -3
  94. package/templates/full/src/flows/main/signals.ts +0 -8
  95. package/templates/full/src/gates.ts +0 -4
  96. package/templates/full/src/locales/ar.ts +0 -3
  97. package/templates/full/src/locales/en.ts +0 -3
  98. package/templates/full/src/schema.ts +0 -8
  99. package/templates/full/src/vault.ts +0 -9
  100. package/templates/full/tests/full.test.ts +0 -11
  101. package/templates/hello/README.md +0 -38
  102. package/templates/hello/oke.config.ts +0 -3
  103. package/templates/hello/package.json +0 -14
  104. package/templates/hello/src/app.ts +0 -10
  105. package/templates/hello/src/flows/hello/index.ts +0 -27
  106. package/templates/hello/tests/hello.test.ts +0 -10
  107. package/templates/minimal/README.md +0 -41
  108. package/templates/minimal/oke.config.ts +0 -14
  109. package/templates/minimal/package.json +0 -15
  110. package/templates/minimal/src/app.ts +0 -13
  111. package/templates/minimal/src/flows/main/index.ts +0 -49
  112. package/templates/minimal/src/schema.ts +0 -8
  113. package/templates/minimal/tests/minimal.test.ts +0 -13
  114. package/templates/standard/README.md +0 -47
  115. package/templates/standard/src/core.ts +0 -4
  116. package/templates/standard/src/schema.ts +0 -9
  117. /package/{templates/standard → template}/src/app.ts +0 -0
  118. /package/{templates/standard → template}/src/channels.ts +0 -0
  119. /package/{templates/standard → template}/src/flows/main/index.ts +0 -0
  120. /package/{templates/standard → template}/src/flows/main/shapes.ts +0 -0
  121. /package/{templates/standard → template}/src/flows/main/signals.ts +0 -0
  122. /package/{templates/standard → template}/src/gates.ts +0 -0
  123. /package/{templates/standard → template}/src/locales/ar.ts +0 -0
  124. /package/{templates/standard → template}/src/locales/en.ts +0 -0
  125. /package/{templates/standard → template}/src/vault.ts +0 -0
  126. /package/{templates/standard → template}/tests/standard.test.ts +0 -0
package/README.md CHANGED
@@ -8,52 +8,43 @@ 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
- | Port | What |
16
- |---|---|
17
- | `:6530` | your app |
18
- | `:6533` | Console |
19
- | `:6535` | MCP |
14
+ | Port | What |
15
+ | ------- | ------------------- |
16
+ | `:6530` | your app |
17
+ | `:6533` | Console |
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
25
+ bunx create-oke@latest my-app --sql postgres
28
26
  bunx create-oke@latest my-app --yes
29
- bunx create-oke@latest my-notes --from-example notes
30
27
  ```
31
28
 
32
- | Template | Purpose |
33
- |---|---|
34
- | `hello` | Fastest possible "it works" one flow, no Store |
35
- | `minimal` | Smallest shape you'd actually ship — Store + 1–2 flows |
36
- | `standard` | Full recommended file layout, empty scaffolding (default) |
37
- | `full` | Every element present and wired, no business logic |
29
+ `--sql sqlite|postgres` is opt-in. The default keeps the dual-mode 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.
35
+
36
+ Store-bearing scaffolds ship `drizzle.config.ts` and `.env.example`. Local
37
+ `oke dev` auto-runs `oke db push` on schema change; use `oke mode docker` /
38
+ `oke dev --docker` for compose infra.
38
39
 
39
40
  Every scaffold writes an `AGENTS.md` so coding agents know the OKE contract
40
41
  (`--no-agents-md` to skip).
41
42
 
42
- `--from-example` copies a teaching example (including its business logic and
43
- comments) — most new projects want `--template` instead.
44
-
45
- | Example | Adds |
46
- |---|---|
47
- | `notes` | `oke` · `flow` · `http` · `store.sql` · typed client |
48
- | `linkly` | `signal` · `clock` · `gate` |
49
- | `provisions` | durable flows · `vault` · `channel` · plugins |
50
- | `skyport` | `ai` · multi-tenancy · distributed topology |
51
-
52
43
  ## Registries
53
44
 
54
- | Registry | Package |
55
- |---|---|
56
- | npm (CLI) | [`create-oke`](https://www.npmjs.com/package/create-oke) — use this with `bunx` |
57
- | JSR | [`@omqkhafi/create-oke`](https://jsr.io/@omqkhafi/create-oke) — same sources; prefer npm for the binary |
45
+ | Registry | Package |
46
+ | --------- | ------------------------------------------------------------------------------------------------------- |
47
+ | npm (CLI) | [`create-oke`](https://www.npmjs.com/package/create-oke) — use this with `bunx` |
48
+ | JSR | [`@omqkhafi/create-oke`](https://jsr.io/@omqkhafi/create-oke) — same sources; prefer npm for the binary |
58
49
 
59
- 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,10 +1,8 @@
1
1
  {
2
2
  "name": "create-oke",
3
- "version": "0.2.6",
3
+ "version": "0.3.2",
4
4
  "description": "Scaffold an okengine app — bunx create-oke@latest <name>",
5
5
  "license": "MIT",
6
- "type": "module",
7
- "sideEffects": false,
8
6
  "repository": {
9
7
  "type": "git",
10
8
  "url": "git+https://github.com/omqkhafi/okengine.git",
@@ -13,28 +11,28 @@
13
11
  "bin": {
14
12
  "create-oke": "src/index.ts"
15
13
  },
16
- "exports": {
17
- ".": "./src/index.ts"
18
- },
19
14
  "files": [
20
15
  "src",
21
- "templates",
22
- "examples"
16
+ "template"
23
17
  ],
18
+ "type": "module",
19
+ "sideEffects": false,
20
+ "exports": {
21
+ ".": "./src/index.ts"
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"
29
27
  },
30
- "engines": {
31
- "bun": ">=1.3"
32
- },
33
28
  "dependencies": {
34
29
  "@clack/prompts": "^1.7.0"
35
30
  },
36
31
  "devDependencies": {
37
32
  "@types/bun": "latest",
38
33
  "typescript": "^7.0.2"
34
+ },
35
+ "engines": {
36
+ "bun": ">=1.3"
39
37
  }
40
38
  }
package/src/cli.test.ts CHANGED
@@ -3,17 +3,10 @@
3
3
  */
4
4
 
5
5
  import { describe, expect, test } from "bun:test";
6
- import {
7
- existsSync,
8
- mkdtempSync,
9
- readFileSync,
10
- readdirSync,
11
- rmSync,
12
- } from "node:fs";
6
+ import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
13
7
  import { tmpdir } from "node:os";
14
8
  import { join } from "node:path";
15
9
  import {
16
- FROM_EXAMPLE_CHOICE,
17
10
  formatCdPath,
18
11
  nextStepsText,
19
12
  parseArgs,
@@ -25,18 +18,13 @@ import {
25
18
  type InteractiveAnswers,
26
19
  } from "./cli.ts";
27
20
  import { listTemplateFiles, scaffold } from "./scaffold.ts";
28
- import {
29
- DEFAULT_TEMPLATE,
30
- EXAMPLES,
31
- TEMPLATES,
32
- packageRoot,
33
- resolveExampleDir,
34
- resolveTemplateDir,
35
- } from "./templates.ts";
21
+ import { DEFAULT_TEMPLATE, TEMPLATES, packageRoot, resolveTemplateDir } from "./templates.ts";
36
22
  import {
37
23
  sanitizeProjectName,
38
24
  shouldSkipTemplatePath,
25
+ transformConfigForSqlDriver,
39
26
  transformPackageJson,
27
+ transformSchemaForSqlDriver,
40
28
  } from "./transform.ts";
41
29
 
42
30
  describe("parseArgs", () => {
@@ -45,31 +33,27 @@ describe("parseArgs", () => {
45
33
  expect(a.name).toBe("my-app");
46
34
  expect(a.template).toBe("standard");
47
35
  expect(a.templateExplicit).toBe(false);
48
- expect(a.fromExample).toBeUndefined();
49
36
  });
50
37
 
51
38
  test("accepts --template and -t", () => {
52
- expect(parseArgs(["x", "--template", "hello"]).template).toBe("hello");
53
- expect(parseArgs(["x", "-t", "full"]).template).toBe("full");
54
- expect(parseArgs(["x", "--template=minimal"]).template).toBe("minimal");
55
- expect(parseArgs(["x", "--template", "hello"]).templateExplicit).toBe(true);
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);
56
43
  });
57
44
 
58
- test("accepts --from-example", () => {
59
- expect(parseArgs(["x", "--from-example", "notes"]).fromExample).toBe(
60
- "notes",
61
- );
62
- expect(parseArgs(["x", "--from-example=skyport"]).fromExample).toBe(
63
- "skyport",
64
- );
45
+ test("accepts --sql sqlite|postgres", () => {
46
+ expect(parseArgs(["x"]).sqlDriver).toBe("sqlite");
47
+ expect(parseArgs(["x"]).sqlDriverExplicit).toBe(false);
48
+ expect(parseArgs(["x", "--sql", "postgres"]).sqlDriver).toBe("postgres");
49
+ expect(parseArgs(["x", "--sql=sqlite"]).sqlDriver).toBe("sqlite");
50
+ expect(parseArgs(["x", "--sql", "postgres"]).sqlDriverExplicit).toBe(true);
65
51
  });
66
52
 
67
- test("rejects unknown template / example / 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/);
70
- expect(() =>
71
- parseArgs(["x", "--template", "hello", "--from-example", "notes"]),
72
- ).toThrow(/either/);
55
+ expect(() => parseArgs(["x", "--unknown"])).toThrow(/unknown option/);
56
+ expect(() => parseArgs(["x", "--sql", "mysql"])).toThrow(/sql/);
73
57
  });
74
58
  });
75
59
 
@@ -86,13 +70,8 @@ describe("shouldPrompt", () => {
86
70
  expect(shouldPrompt(parseArgs([]), false)).toBe(false);
87
71
  expect(shouldPrompt(parseArgs(["my-app"]), false)).toBe(false);
88
72
  expect(shouldPrompt(parseArgs(["my-app", "--yes"]), true)).toBe(false);
89
- expect(shouldPrompt(parseArgs(["my-app", "--template", "hello"]), true)).toBe(
90
- false,
91
- );
92
- expect(shouldPrompt(parseArgs(["--template", "hello"]), true)).toBe(false);
93
- expect(shouldPrompt(parseArgs(["--from-example", "notes"]), true)).toBe(
94
- false,
95
- );
73
+ expect(shouldPrompt(parseArgs(["my-app", "--template", "standard"]), true)).toBe(false);
74
+ expect(shouldPrompt(parseArgs(["--template", "standard"]), true)).toBe(false);
96
75
  expect(shouldPrompt(parseArgs(["--help"]), true)).toBe(false);
97
76
  });
98
77
  });
@@ -108,17 +87,12 @@ describe("parseArgs flags", () => {
108
87
  });
109
88
 
110
89
  test("rejects --install with --no-install", () => {
111
- expect(() => parseArgs(["x", "--install", "--no-install"])).toThrow(
112
- /install/,
113
- );
90
+ expect(() => parseArgs(["x", "--install", "--no-install"])).toThrow(/install/);
114
91
  });
115
92
  });
116
93
 
117
94
  describe("sourceFromArgs", () => {
118
- test("prefers example when set", () => {
119
- expect(sourceFromArgs(parseArgs(["x", "--from-example", "linkly"]))).toEqual(
120
- { kind: "example", id: "linkly" },
121
- );
95
+ test("uses the standard template", () => {
122
96
  expect(sourceFromArgs(parseArgs(["x"]))).toEqual({
123
97
  kind: "template",
124
98
  id: DEFAULT_TEMPLATE,
@@ -136,34 +110,29 @@ describe("scaffoldArgsFromAnswers ≡ flag-driven", () => {
136
110
  agentsMd: true,
137
111
  };
138
112
  const fromAnswers = scaffoldArgsFromAnswers(answers);
139
- const fromFlags = scaffoldArgsFromCli(
140
- parseArgs(["x", "--template", id]),
141
- );
113
+ const fromFlags = scaffoldArgsFromCli(parseArgs(["x", "--template", id]));
142
114
  expect(fromAnswers).toEqual(fromFlags);
143
115
  expect(fromAnswers.source).toEqual({ kind: "template", id });
116
+ expect(fromAnswers.sqlDriver).toBe("sqlite");
144
117
  }
145
118
  });
146
119
 
147
- test("each --from-example path matches interactive from-example choice", () => {
148
- for (const id of EXAMPLES) {
149
- const answers: InteractiveAnswers = {
150
- name: "x",
151
- choice: FROM_EXAMPLE_CHOICE,
152
- example: id,
153
- installAndRun: false,
154
- agentsMd: true,
155
- };
156
- const fromAnswers = scaffoldArgsFromAnswers(answers);
157
- const fromFlags = scaffoldArgsFromCli(
158
- parseArgs(["x", "--from-example", id]),
159
- );
160
- expect(fromAnswers).toEqual(fromFlags);
161
- expect(fromAnswers.source).toEqual({ kind: "example", id });
162
- }
120
+ test("interactive never opts into --sql postgres (flag-only)", () => {
121
+ const answers: InteractiveAnswers = {
122
+ name: "x",
123
+ choice: "standard",
124
+ installAndRun: false,
125
+ agentsMd: true,
126
+ };
127
+ expect(scaffoldArgsFromAnswers(answers).sqlDriver).toBe("sqlite");
128
+ expect(
129
+ scaffoldArgsFromCli(parseArgs(["x", "--template", "standard", "--sql", "postgres"]))
130
+ .sqlDriver,
131
+ ).toBe("postgres");
163
132
  });
164
133
 
165
- test("8 paths cover every template and every example", () => {
166
- expect(TEMPLATES.length + EXAMPLES.length).toBe(8);
134
+ test("only standard is available", () => {
135
+ expect(TEMPLATES).toEqual(["standard"]);
167
136
  });
168
137
  });
169
138
 
@@ -171,7 +140,7 @@ describe("transformPackageJson", () => {
171
140
  test("rewrites name and okengine file:../.. to installable ref", () => {
172
141
  const next = transformPackageJson(
173
142
  {
174
- name: "@oke/template-hello",
143
+ name: "@oke/template-standard",
175
144
  private: true,
176
145
  dependencies: {
177
146
  okengine: "file:../..",
@@ -189,11 +158,51 @@ describe("transformPackageJson", () => {
189
158
  });
190
159
  });
191
160
 
161
+ describe("transformSchemaForSqlDriver", () => {
162
+ const sqliteSchema = `import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
163
+ export const pings = sqliteTable("pings", {});
164
+ `;
165
+
166
+ test("postgres swaps dialect imports and table helper", () => {
167
+ const next = transformSchemaForSqlDriver(sqliteSchema, "postgres");
168
+ expect(next).toContain('from "drizzle-orm/pg-core"');
169
+ expect(next).toContain("pgTable(");
170
+ expect(next).not.toContain("sqliteTable");
171
+ expect(next).not.toContain("sqlite-core");
172
+ });
173
+
174
+ test("sqlite is idempotent on template sources", () => {
175
+ expect(transformSchemaForSqlDriver(sqliteSchema, "sqlite")).toBe(sqliteSchema);
176
+ });
177
+ });
178
+
179
+ describe("transformConfigForSqlDriver", () => {
180
+ const config = `export default defineConfig({
181
+ drivers: {
182
+ store: {
183
+ sql: {
184
+ local: "sqlite",
185
+ docker: "postgres",
186
+ test: "memory",
187
+ prod: "postgres",
188
+ },
189
+ },
190
+ },
191
+ });
192
+ `;
193
+
194
+ test("postgres pins local/docker/prod and leaves test memory", () => {
195
+ const next = transformConfigForSqlDriver(config, "postgres");
196
+ expect(next).toContain('local: "postgres"');
197
+ expect(next).toContain('docker: "postgres"');
198
+ expect(next).toContain('prod: "postgres"');
199
+ expect(next).toContain('test: "memory"');
200
+ });
201
+ });
202
+
192
203
  describe("shouldSkipTemplatePath", () => {
193
204
  test("skips node_modules, locks, and monorepo docker test", () => {
194
- expect(shouldSkipTemplatePath("node_modules/okengine/package.json")).toBe(
195
- true,
196
- );
205
+ expect(shouldSkipTemplatePath("node_modules/okengine/package.json")).toBe(true);
197
206
  expect(shouldSkipTemplatePath("bun.lock")).toBe(true);
198
207
  expect(shouldSkipTemplatePath("tests/docker.test.ts")).toBe(true);
199
208
  expect(shouldSkipTemplatePath("tests/support.test.ts")).toBe(false);
@@ -225,23 +234,23 @@ describe("scaffold structure", () => {
225
234
  name: `app-${id}`,
226
235
  source: { kind: "template", id },
227
236
  });
228
- expect([...result.files].sort()).toEqual(
229
- [...expected, "AGENTS.md"].sort(),
230
- );
237
+ const extras = ["AGENTS.md"];
238
+ if (expected.includes(".env.example")) extras.push(".env.local");
239
+ expect([...result.files].sort()).toEqual([...expected, ...extras].sort());
231
240
  expect(result.files).toContain(".gitignore");
232
241
  expect(result.files).toContain("README.md");
233
- expect(
234
- readFileSync(join(result.targetDir, "AGENTS.md"), "utf8"),
235
- ).toMatch(/one law|on\(Trigger\)/i);
242
+ expect(result.sqlDriver).toBe("sqlite");
243
+ expect(readFileSync(join(result.targetDir, "AGENTS.md"), "utf8")).toMatch(
244
+ /one law|on\(Trigger\)/i,
245
+ );
236
246
  const readme = readFileSync(join(result.targetDir, "README.md"), "utf8");
237
247
  expect(readme).toMatch(/oke dev/);
238
248
  expect(readme).toMatch(new RegExp(`^# ${id}`, "m"));
239
- expect(
240
- readFileSync(join(result.targetDir, ".gitignore"), "utf8"),
241
- ).toMatch(/node_modules/);
242
- const pkg = JSON.parse(
243
- readFileSync(join(result.targetDir, "package.json"), "utf8"),
244
- ) as { name: string; dependencies: { okengine: string } };
249
+ expect(readFileSync(join(result.targetDir, ".gitignore"), "utf8")).toMatch(/node_modules/);
250
+ const pkg = JSON.parse(readFileSync(join(result.targetDir, "package.json"), "utf8")) as {
251
+ name: string;
252
+ dependencies: { okengine: string };
253
+ };
245
254
  expect(pkg.name).toBe(`app-${id}`);
246
255
  expect(pkg.dependencies.okengine).not.toBe("file:../..");
247
256
  } finally {
@@ -250,37 +259,6 @@ describe("scaffold structure", () => {
250
259
  }
251
260
  });
252
261
 
253
- test("hello has exactly one flow and no Store", () => {
254
- const dir = mkdtempSync(join(tmpdir(), "create-oke-hello-assert-"));
255
- try {
256
- const result = scaffold({
257
- targetDir: join(dir, "hello"),
258
- name: "hello-app",
259
- source: { kind: "template", id: "hello" },
260
- });
261
- const flowFiles = result.files.filter(
262
- (f) => f.startsWith("src/flows/") && f.endsWith(".ts"),
263
- );
264
- expect(flowFiles).toEqual(["src/flows/hello/index.ts"]);
265
- const flowSrc = readFileSync(
266
- join(result.targetDir, "src/flows/hello/index.ts"),
267
- "utf8",
268
- );
269
- expect(flowSrc).toMatch(/export const hello/);
270
- expect(flowSrc).not.toMatch(/\bstore\b/);
271
- expect(result.files.some((f) => f.includes("core.ts"))).toBe(false);
272
- expect(result.files.some((f) => f.includes("schema.ts"))).toBe(false);
273
- const all = result.files
274
- .filter((f) => f.endsWith(".ts"))
275
- .map((f) => readFileSync(join(result.targetDir, f), "utf8"))
276
- .join("\n");
277
- expect(all).not.toMatch(/store\.sql/);
278
- expect(all).not.toMatch(/\bbookings\b|\borders\b|\blinks\b|\bnotes\b/);
279
- } finally {
280
- rmSync(dir, { recursive: true, force: true });
281
- }
282
- });
283
-
284
262
  test("standard has full four-applications layout and no business domain", () => {
285
263
  const dir = mkdtempSync(join(tmpdir(), "create-oke-standard-assert-"));
286
264
  try {
@@ -298,7 +276,7 @@ describe("scaffold structure", () => {
298
276
  "src/flows/main/shapes.ts",
299
277
  "src/flows/main/signals.ts",
300
278
  "src/core.ts",
301
- "src/schema.ts",
279
+ "src/schema.decl.ts",
302
280
  "src/app.ts",
303
281
  ]) {
304
282
  expect(result.files).toContain(path);
@@ -313,20 +291,29 @@ describe("scaffold structure", () => {
313
291
  }
314
292
  });
315
293
 
316
- test("--from-example notes matches today's example tree", () => {
317
- const dir = mkdtempSync(join(tmpdir(), "create-oke-from-example-"));
294
+ test("--sql postgres pins store.sql (abstract schema stays dialect-agnostic)", () => {
295
+ const dir = mkdtempSync(join(tmpdir(), "create-oke-sql-pg-"));
318
296
  try {
319
- const exampleDir = resolveExampleDir("notes");
320
- const expected = listTemplateFiles(exampleDir);
321
297
  const result = scaffold({
322
- targetDir: join(dir, "notes"),
323
- name: "from-notes",
324
- source: { kind: "example", id: "notes" },
298
+ targetDir: join(dir, "pg-app"),
299
+ name: "pg-app",
300
+ source: { kind: "template", id: "standard" },
301
+ sqlDriver: "postgres",
325
302
  });
326
- expect([...result.files].sort()).toEqual(
327
- [...expected, "AGENTS.md"].sort(),
328
- );
329
- expect(result.files).toContain("src/flows/notes/index.ts");
303
+ expect(result.sqlDriver).toBe("postgres");
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");
312
+ const config = readFileSync(join(result.targetDir, "oke.config.ts"), "utf8");
313
+ expect(config).toMatch(/sql:\s*\{[\s\S]*local:\s*"postgres"/);
314
+ expect(config).toMatch(/sql:\s*\{[\s\S]*docker:\s*"postgres"/);
315
+ expect(config).toMatch(/sql:\s*\{[\s\S]*prod:\s*"postgres"/);
316
+ expect(config).toMatch(/sql:\s*\{[\s\S]*test:\s*"memory"/);
330
317
  } finally {
331
318
  rmSync(dir, { recursive: true, force: true });
332
319
  }
@@ -336,9 +323,9 @@ describe("scaffold structure", () => {
336
323
  const dir = mkdtempSync(join(tmpdir(), "create-oke-no-agents-"));
337
324
  try {
338
325
  const result = scaffold({
339
- targetDir: join(dir, "hello"),
326
+ targetDir: join(dir, "standard"),
340
327
  name: "no-agents",
341
- source: { kind: "template", id: "hello" },
328
+ source: { kind: "template", id: "standard" },
342
329
  writeAgentsMd: false,
343
330
  });
344
331
  expect(result.files).not.toContain("AGENTS.md");
@@ -376,34 +363,27 @@ describe("non-TTY CLI", () => {
376
363
  const root = mkdtempSync(join(tmpdir(), "create-oke-flag-"));
377
364
  const target = join(root, "flag-app");
378
365
  try {
379
- const code = await run(
380
- [target, "--template", "hello", "--no-install"],
381
- { stdinIsTTY: false, runPostScaffold: false },
382
- );
366
+ const code = await run([target, "--template", "standard", "--no-install"], {
367
+ stdinIsTTY: false,
368
+ runPostScaffold: false,
369
+ });
383
370
  expect(code).toBe(0);
384
371
  expect(readdirSync(target).length).toBeGreaterThan(0);
385
372
  expect(existsSync(join(target, "AGENTS.md"))).toBe(true);
386
373
  const result = {
387
374
  targetDir: target,
388
375
  name: "flag-app",
389
- source: { kind: "template" as const, id: "hello" as const },
390
- label: "hello",
376
+ source: { kind: "template" as const, id: "standard" as const },
377
+ label: "standard",
391
378
  okengineDependency: "x",
392
379
  files: [],
380
+ sqlDriver: "sqlite" as const,
393
381
  };
394
382
  expect(nextStepsText(result)).toContain("oke dev");
395
383
  expect(nextStepsText(result)).toContain("bun install");
396
- expect(nextStepsText(result)).toContain("okengine.vercel.app");
384
+ expect(nextStepsText(result)).toContain("oke.omqkhafi.dev");
397
385
  } finally {
398
386
  rmSync(root, { recursive: true, force: true });
399
387
  }
400
388
  });
401
389
  });
402
-
403
- describe("examples catalogue", () => {
404
- test("all four teaching examples resolve", () => {
405
- for (const id of EXAMPLES) {
406
- expect(resolveExampleDir(id)).toBeTruthy();
407
- }
408
- });
409
- });