create-oke 0.11.2 → 0.12.0

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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/agents-md.ts +4 -1
  3. package/src/ai-setup/apply.ts +3 -5
  4. package/src/ai-setup/from-pref.ts +4 -4
  5. package/src/ai-setup/prompts.ts +1 -1
  6. package/src/cli.test.ts +86 -14
  7. package/src/cli.ts +145 -29
  8. package/src/create-defaults.test.ts +9 -0
  9. package/src/create-defaults.ts +27 -0
  10. package/src/customize-flow.ts +2 -1
  11. package/src/drivers-catalog.ts +18 -9
  12. package/src/local-okengine.test.ts +2 -0
  13. package/src/local-okengine.ts +3 -0
  14. package/src/scaffold.ts +17 -5
  15. package/src/transform.test.ts +6 -3
  16. package/src/transform.ts +24 -1
  17. package/templates/advanced/.env.example +13 -5
  18. package/templates/advanced/README.md +3 -0
  19. package/templates/advanced/oke.config.ts +6 -5
  20. package/templates/advanced/package.json +14 -3
  21. package/templates/advanced/src/app.ts +2 -1
  22. package/templates/advanced/src/core.ts +12 -0
  23. package/templates/advanced/src/db/seed/index.ts +2 -0
  24. package/templates/advanced/src/flows/main/index.ts +4 -4
  25. package/templates/advanced/src/flows/notes/index.ts +14 -14
  26. package/templates/advanced/src/flows/notes/shapes.ts +2 -3
  27. package/templates/advanced/web/index.html +12 -0
  28. package/templates/advanced/web/src/App.css +78 -0
  29. package/templates/advanced/web/src/App.tsx +100 -0
  30. package/templates/advanced/web/src/client.ts +38 -0
  31. package/templates/advanced/web/src/index.css +18 -0
  32. package/templates/advanced/web/src/main.tsx +13 -0
  33. package/templates/advanced/web/src/vite-env.d.ts +17 -0
  34. package/templates/advanced/web/tsconfig.app.json +24 -0
  35. package/templates/advanced/web/tsconfig.json +4 -0
  36. package/templates/advanced/web/tsconfig.node.json +20 -0
  37. package/templates/advanced/web/vite.config.ts +32 -0
  38. package/templates/standard/.env.example +13 -5
  39. package/templates/standard/README.md +3 -0
  40. package/templates/standard/oke.config.ts +4 -3
  41. package/templates/standard/package.json +14 -3
  42. package/templates/standard/src/app.ts +2 -1
  43. package/templates/standard/src/core.ts +12 -0
  44. package/templates/standard/src/db/seed/index.ts +2 -0
  45. package/templates/standard/src/flows/main/index.ts +4 -4
  46. package/templates/standard/src/flows/notes/index.ts +12 -12
  47. package/templates/standard/src/flows/notes/shapes.ts +2 -3
  48. package/templates/standard/tests/standard.test.ts +3 -2
  49. package/templates/standard/web/index.html +12 -0
  50. package/templates/standard/web/src/App.css +78 -0
  51. package/templates/standard/web/src/App.tsx +100 -0
  52. package/templates/standard/web/src/client.ts +38 -0
  53. package/templates/standard/web/src/index.css +18 -0
  54. package/templates/standard/web/src/main.tsx +13 -0
  55. package/templates/standard/web/src/vite-env.d.ts +17 -0
  56. package/templates/standard/web/tsconfig.app.json +24 -0
  57. package/templates/standard/web/tsconfig.json +4 -0
  58. package/templates/standard/web/tsconfig.node.json +20 -0
  59. package/templates/standard/web/vite.config.ts +32 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-oke",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Scaffold an okengine app — bunx create-oke@latest <name>",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/agents-md.ts CHANGED
@@ -78,9 +78,10 @@ Mnemonic: O·K·E = 6·5·3.
78
78
  \`\`\`bash
79
79
  bun install
80
80
  oke dev # or: bun run dev
81
+ bun run web # Vite SPA (proxies Flows to the app)
81
82
  \`\`\`
82
83
 
83
- App \`:6530\` · Console \`:6533\` · MCP \`:6535\`.
84
+ App \`:6530\` · Console \`:6533\` · MCP \`:6535\`. Vite web is \`bun run web\`.
84
85
 
85
86
  ## Common mistakes
86
87
 
@@ -90,6 +91,8 @@ App \`:6530\` · Console \`:6533\` · MCP \`:6535\`.
90
91
  - ✅ New capability = new **driver** on an existing element, or a new Flow
91
92
  - ❌ Untyped HTTP handlers that skip \`on\` / \`flow\` / contracts
92
93
  - ✅ \`on(http.get("/…"), flow(name, { in, out, do }))\`
94
+ - ❌ Returning \`{ items, count }\` from a list \`do\` (nests the pager inside \`data\`)
95
+ - ✅ \`out: z.array(Item)\` + \`fx.json.withQuery(rows, input)\` — or any other \`out\` you declare
93
96
 
94
97
  ## Learn more
95
98
 
@@ -70,10 +70,8 @@ export function applyAiSetup(
70
70
  : existsSync(envExample)
71
71
  ? readFileSync(envExample, "utf8")
72
72
  : "";
73
- // Driver / URL / model stay commented so docker mode can hydrate from
74
- // `docker/.env.docker` without being shadowed. The chosen model is still
75
- // written into `.env.local` as a hint (and into `.env.docker` when present);
76
- // `loadExistingStackControls` seeds commented `OKE_AI_MODEL` on first boot.
73
+ // Driver / URL / model stay commented so `oke dev --docker` can write
74
+ // the live compose URL into `.env.local` without a leftover pin winning.
77
75
  env = upsertEnv(env, "OKE_AI_DRIVER", input.driver, { comment: true });
78
76
  if (input.baseUrl) env = upsertEnv(env, "OKE_AI_URL", input.baseUrl, { comment: true });
79
77
  if (input.chatModel) {
@@ -146,7 +144,7 @@ export function upsertImage(source: string, key: string, image: string): string
146
144
  export type UpsertEnvOptions = {
147
145
  /**
148
146
  * When true, write `# KEY=value` (opt-in override). Used for AI stack keys
149
- * so they do not shadow `docker/.env.docker`.
147
+ * so `oke dev --docker` can write the live compose URL into `.env.local`.
150
148
  */
151
149
  readonly comment?: boolean;
152
150
  };
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { CreateAiPref } from "../create-defaults.ts";
6
- import { LLAMA_CPP_IMAGE } from "../drivers-catalog.ts";
6
+ import { LLAMA_CPP_IMAGE, OLLAMA_IMAGE, SGLANG_IMAGE, VLLM_IMAGE } from "../drivers-catalog.ts";
7
7
  import type { AiSetupApplyInput } from "./apply.ts";
8
8
 
9
9
  /**
@@ -81,7 +81,7 @@ export function nonInteractiveAiApply(provider: string): AiSetupApplyInput {
81
81
  chatModel: "gemma4:e4b",
82
82
  visionModel: "qwen3-vl:4b",
83
83
  embedModel: "nomic-embed-text",
84
- image: "ollama/ollama:0.32.6",
84
+ image: OLLAMA_IMAGE,
85
85
  };
86
86
  }
87
87
  if (provider === "vllm") {
@@ -91,7 +91,7 @@ export function nonInteractiveAiApply(provider: string): AiSetupApplyInput {
91
91
  chatModel: "Qwen/Qwen3-0.6B",
92
92
  visionModel: null,
93
93
  embedModel: null,
94
- image: "vllm/vllm-openai:v0.26.0",
94
+ image: VLLM_IMAGE,
95
95
  };
96
96
  }
97
97
  if (provider === "sglang") {
@@ -101,7 +101,7 @@ export function nonInteractiveAiApply(provider: string): AiSetupApplyInput {
101
101
  chatModel: "Qwen/Qwen3-0.6B",
102
102
  visionModel: null,
103
103
  embedModel: null,
104
- image: "lmsysorg/sglang:v0.5.16-runtime",
104
+ image: SGLANG_IMAGE,
105
105
  };
106
106
  }
107
107
  if (provider === "anthropic") {
@@ -250,7 +250,7 @@ async function askSelfHostedGpuPath(
250
250
  ): Promise<AiSetupApplyInput | null> {
251
251
  const port = provider === "vllm" ? 8000 : 30000;
252
252
  const image =
253
- provider === "vllm" ? "vllm/vllm-openai:v0.26.0" : "lmsysorg/sglang:v0.5.16-runtime";
253
+ provider === "vllm" ? "vllm/vllm-openai:v0.27.1" : "lmsysorg/sglang:v0.5.17-runtime";
254
254
  const model = await text({
255
255
  message: "Hugging Face model id",
256
256
  placeholder: "Qwen/Qwen3-0.6B",
package/src/cli.test.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  sourceFromArgs,
29
29
  withBackOption,
30
30
  withLocalesPgDog,
31
+ withWizardExtras,
31
32
  type InteractiveAnswers,
32
33
  } from "./cli.ts";
33
34
  import {
@@ -47,19 +48,21 @@ import {
47
48
  transformPackageJson,
48
49
  } from "./transform.ts";
49
50
 
50
- describe("withLocalesPgDog", () => {
51
+ describe("withWizardExtras / withLocalesPgDog", () => {
51
52
  test("updates session defaults (reuse / customize)", () => {
52
53
  const session = recommendedDefaults("docker-ready", "advanced");
53
- const next = withLocalesPgDog({
54
+ const next = withWizardExtras({
54
55
  template: "advanced",
55
56
  locales: ["ar"],
56
57
  pgdog: true,
58
+ proxy: "nginx",
57
59
  session,
58
60
  previous: null,
59
61
  });
60
62
  expect(next.template).toBe("advanced");
61
63
  expect(next.locales).toEqual(["ar"]);
62
64
  expect(next.pgdog).toBe(true);
65
+ expect(next.proxy).toBe("nginx");
63
66
  expect(next.drivers.store.sql.dev).toBe(session.drivers.store.sql.dev);
64
67
  });
65
68
 
@@ -68,30 +71,35 @@ describe("withLocalesPgDog", () => {
68
71
  ...recommendedDefaults("docker-ready", "advanced"),
69
72
  locales: [] as const,
70
73
  pgdog: false,
74
+ proxy: "none" as const,
71
75
  };
72
76
  const next = withLocalesPgDog({
73
77
  template: "advanced",
74
78
  locales: ["ar", "fr"],
75
79
  pgdog: true,
80
+ proxy: "traefik",
76
81
  session: undefined,
77
82
  previous,
78
83
  });
79
84
  expect(next.locales).toEqual(["ar", "fr"]);
80
85
  expect(next.pgdog).toBe(true);
86
+ expect(next.proxy).toBe("traefik");
81
87
  expect(next.drivers).toEqual(previous.drivers);
82
88
  });
83
89
 
84
90
  test("falls back to recommended pins when nothing is saved", () => {
85
- const next = withLocalesPgDog({
91
+ const next = withWizardExtras({
86
92
  template: "standard",
87
93
  locales: ["ar"],
88
94
  pgdog: true,
95
+ proxy: "caddy",
89
96
  session: undefined,
90
97
  previous: null,
91
98
  });
92
99
  expect(next.template).toBe("standard");
93
100
  expect(next.locales).toEqual(["ar"]);
94
101
  expect(next.pgdog).toBe(true);
102
+ expect(next.proxy).toBe("caddy");
95
103
  expect(next.profile).toBe("docker-ready");
96
104
  });
97
105
  });
@@ -180,6 +188,15 @@ describe("parseArgs flags", () => {
180
188
  expect(() => parseArgs(["x", "--pgdog", "--no-pgdog"])).toThrow(/pgdog/);
181
189
  });
182
190
 
191
+ test("accepts --proxy / --no-proxy", () => {
192
+ expect(parseArgs(["x", "--proxy", "caddy"]).proxy).toBe("caddy");
193
+ expect(parseArgs(["x", "--proxy=nginx"]).proxy).toBe("nginx");
194
+ expect(parseArgs(["x", "--no-proxy"]).proxy).toBe("none");
195
+ expect(parseArgs(["x"]).proxy).toBeUndefined();
196
+ expect(() => parseArgs(["x", "--proxy", "nope"])).toThrow(/proxy/);
197
+ expect(() => parseArgs(["x", "--proxy", "caddy", "--no-proxy"])).toThrow(/proxy/);
198
+ });
199
+
183
200
  test("rejects --install with --no-install", () => {
184
201
  expect(() => parseArgs(["x", "--install", "--no-install"])).toThrow(/install/);
185
202
  });
@@ -204,6 +221,7 @@ function recommendedAnswers(overrides: Partial<InteractiveAnswers> = {}): Intera
204
221
  aiApply: null,
205
222
  locales: [],
206
223
  pgdog: false,
224
+ proxy: "none",
207
225
  ...overrides,
208
226
  };
209
227
  }
@@ -399,6 +417,7 @@ describe("interactive branches", () => {
399
417
  ai: { enabled: false, provider: null, driver: null },
400
418
  locales: [],
401
419
  pgdog: false,
420
+ proxy: "none",
402
421
  });
403
422
 
404
423
  const dir = mkdtempSync(join(tmpdir(), "oke-custom-app-"));
@@ -498,6 +517,23 @@ describe("transformConfigForSqlDriver", () => {
498
517
  });
499
518
  });
500
519
 
520
+ describe("template Vite web", () => {
521
+ test("proxies API paths and never steals GET / from the SPA", () => {
522
+ for (const id of TEMPLATES) {
523
+ const config = readFileSync(join(resolveTemplateDir(id), "web/vite.config.ts"), "utf8");
524
+ expect(config).toContain('"/health"');
525
+ expect(config).toContain('"/notes"');
526
+ expect(config).toContain('"/_oke"');
527
+ expect(config).toContain("127.0.0.1:6530");
528
+ expect(config).not.toMatch(/proxy:\s*\{[^}]*["']\/["']/);
529
+ expect(config).not.toContain("cors: true");
530
+ expect(config).not.toContain("5173");
531
+ expect(config).toContain('from "vite"');
532
+ expect(config).toContain("@vitejs/plugin-react");
533
+ }
534
+ });
535
+ });
536
+
501
537
  describe("shouldSkipTemplatePath", () => {
502
538
  test("skips node_modules, locks, and monorepo docker test", () => {
503
539
  expect(shouldSkipTemplatePath("node_modules/okengine/package.json")).toBe(true);
@@ -574,22 +610,32 @@ describe("scaffold structure", () => {
574
610
  expect(appTs).not.toMatch(/Object\.assign/);
575
611
  expect(appTs).not.toMatch(/env:\s*["']test["']/);
576
612
  expect(appTs).not.toMatch(/stores:\s*\[/);
577
- expect(appTs).toMatch(/oke\(\{\s*name:\s*["']notes["']\s*\}\)/);
613
+ expect(appTs).toMatch(/oke\(\{\s*name:\s*["']notes["']/);
578
614
  const pkg = JSON.parse(readFileSync(join(result.targetDir, "package.json"), "utf8")) as {
579
615
  name: string;
580
- dependencies: { okengine: string };
581
- scripts: { typecheck?: string; test?: string };
616
+ dependencies: { okengine: string; "@duckdb/node-api"?: string };
617
+ trustedDependencies?: readonly string[];
618
+ scripts: { typecheck?: string; test?: string; web?: string; "web:build"?: string };
582
619
  devDependencies: {
583
620
  typescript?: string;
621
+ vite?: string;
584
622
  "@electric-sql/pglite"?: string;
585
623
  "@electric-sql/pglite-pgvector"?: string;
586
624
  };
587
625
  };
588
626
  expect(pkg.name).toBe(`app-${id}`);
589
627
  expect(pkg.dependencies.okengine).not.toMatch(/^file:\.\./);
590
- expect(pkg.scripts.typecheck).toBe("tsc --noEmit");
628
+ expect(pkg.dependencies["@duckdb/node-api"]).toBe("^1.5.5-r.2");
629
+ expect(pkg.trustedDependencies).toContain("@duckdb/node-api");
630
+ expect(pkg.scripts.typecheck).toContain("tsc --noEmit");
631
+ expect(pkg.scripts.typecheck).toContain("tsc -b -p web/tsconfig.json");
632
+ expect(pkg.scripts.web).toContain("vite --config web/vite.config.ts");
633
+ expect(pkg.scripts["web:build"]).toContain("tsc -b -p web/tsconfig.json");
591
634
  expect(pkg.scripts.test).toBe("oke test");
635
+ expect(result.files).toContain("web/vite.config.ts");
636
+ expect(result.files).toContain("web/src/client.ts");
592
637
  expect(pkg.devDependencies.typescript).toBeTruthy();
638
+ expect(pkg.devDependencies.vite).toBe("^8.2.0");
593
639
  expect(pkg.devDependencies["@electric-sql/pglite"]).toBe("^0.5.4");
594
640
  expect(pkg.devDependencies["@electric-sql/pglite-pgvector"]).toBe("^0.0.5");
595
641
  const drizzle = readFileSync(join(result.targetDir, "drizzle.config.ts"), "utf8");
@@ -632,6 +678,7 @@ describe("scaffold structure", () => {
632
678
  }
633
679
  const notes = readFileSync(join(result.targetDir, "src/flows/notes/index.ts"), "utf8");
634
680
  expect(notes).toContain('flow("notes.create"');
681
+ expect(notes).toContain("fx.json.withQuery");
635
682
  expect(notes).not.toContain('flow("notes.digest"');
636
683
  expect(existsSync(join(result.targetDir, "src/locales/ar.ts"))).toBe(false);
637
684
  expect(readFileSync(join(result.targetDir, "oke.config.ts"), "utf8")).toContain(
@@ -715,6 +762,37 @@ describe("scaffold structure", () => {
715
762
  }
716
763
  });
717
764
 
765
+ test("proxy opt-in pins images.proxy and emits companion config", async () => {
766
+ const dir = mkdtempSync(join(tmpdir(), "create-oke-proxy-"));
767
+ try {
768
+ const caddy = await scaffold({
769
+ targetDir: join(dir, "with-caddy"),
770
+ name: "with-caddy",
771
+ source: { kind: "template", id: "standard" },
772
+ proxy: "caddy",
773
+ });
774
+ expect(caddy.proxy).toBe("caddy");
775
+ expect(readFileSync(join(caddy.targetDir, "oke.config.ts"), "utf8")).toMatch(
776
+ /proxy:\s*"caddy:2-alpine"/,
777
+ );
778
+ expect(existsSync(join(caddy.targetDir, "docker/Caddyfile"))).toBe(true);
779
+
780
+ const nginx = await scaffold({
781
+ targetDir: join(dir, "with-nginx"),
782
+ name: "with-nginx",
783
+ source: { kind: "template", id: "standard" },
784
+ proxy: "nginx",
785
+ });
786
+ expect(nginx.proxy).toBe("nginx");
787
+ expect(readFileSync(join(nginx.targetDir, "oke.config.ts"), "utf8")).toMatch(
788
+ /proxy:\s*"nginx:1\.31-alpine"/,
789
+ );
790
+ expect(existsSync(join(nginx.targetDir, "docker/nginx.conf"))).toBe(true);
791
+ } finally {
792
+ rmSync(dir, { recursive: true, force: true });
793
+ }
794
+ });
795
+
718
796
  test("--sql postgres pins store.sql (abstract schema stays dialect-agnostic)", async () => {
719
797
  const dir = mkdtempSync(join(tmpdir(), "create-oke-sql-pg-"));
720
798
  try {
@@ -849,16 +927,10 @@ describe("non-TTY CLI", () => {
849
927
  expect(existsSync(join(target, "AGENTS.md"))).toBe(true);
850
928
  const result = {
851
929
  targetDir: target,
852
- name: "flag-app",
853
- source: { kind: "template" as const, id: "standard" as const },
854
930
  label: "standard",
855
- okengineDependency: "x",
856
- files: [],
857
- sqlDriver: "postgres" as const,
858
- locales: [],
859
- pgdog: false,
860
931
  };
861
932
  expect(nextStepsText(result)).toContain("oke dev");
933
+ expect(nextStepsText(result)).toContain("bun run web");
862
934
  expect(nextStepsText(result)).toContain("bun install");
863
935
  expect(nextStepsText(result)).toContain("oke.omqkhafi.dev");
864
936
  } finally {