create-oke 0.11.1 → 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 (61) hide show
  1. package/package.json +1 -1
  2. package/src/agents-md.ts +4 -1
  3. package/src/ai-setup/apply.ts +64 -6
  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 +133 -11
  7. package/src/cli.ts +180 -29
  8. package/src/create-defaults.test.ts +9 -0
  9. package/src/create-defaults.ts +27 -0
  10. package/src/customize-flow.test.ts +14 -7
  11. package/src/customize-flow.ts +2 -1
  12. package/src/drivers-catalog.ts +18 -9
  13. package/src/local-okengine.test.ts +2 -0
  14. package/src/local-okengine.ts +3 -0
  15. package/src/scaffold.ts +17 -5
  16. package/src/transform.test.ts +6 -3
  17. package/src/transform.ts +24 -1
  18. package/templates/advanced/.env.example +13 -5
  19. package/templates/advanced/README.md +3 -0
  20. package/templates/advanced/oke.config.ts +6 -5
  21. package/templates/advanced/package.json +14 -3
  22. package/templates/advanced/src/app.ts +2 -1
  23. package/templates/advanced/src/core.ts +12 -0
  24. package/templates/advanced/src/db/seed/index.ts +3 -1
  25. package/templates/advanced/src/flows/main/index.ts +4 -4
  26. package/templates/advanced/src/flows/notes/index.ts +73 -26
  27. package/templates/advanced/src/flows/notes/shapes.ts +9 -4
  28. package/templates/advanced/tests/advanced.test.ts +3 -2
  29. package/templates/advanced/web/index.html +12 -0
  30. package/templates/advanced/web/src/App.css +78 -0
  31. package/templates/advanced/web/src/App.tsx +100 -0
  32. package/templates/advanced/web/src/client.ts +38 -0
  33. package/templates/advanced/web/src/index.css +18 -0
  34. package/templates/advanced/web/src/main.tsx +13 -0
  35. package/templates/advanced/web/src/vite-env.d.ts +17 -0
  36. package/templates/advanced/web/tsconfig.app.json +24 -0
  37. package/templates/advanced/web/tsconfig.json +4 -0
  38. package/templates/advanced/web/tsconfig.node.json +20 -0
  39. package/templates/advanced/web/vite.config.ts +32 -0
  40. package/templates/standard/.env.example +13 -5
  41. package/templates/standard/README.md +3 -0
  42. package/templates/standard/oke.config.ts +4 -3
  43. package/templates/standard/package.json +14 -3
  44. package/templates/standard/src/app.ts +2 -1
  45. package/templates/standard/src/core.ts +12 -0
  46. package/templates/standard/src/db/seed/index.ts +2 -0
  47. package/templates/standard/src/flows/main/index.ts +4 -4
  48. package/templates/standard/src/flows/notes/index.ts +12 -12
  49. package/templates/standard/src/flows/notes/shapes.ts +2 -3
  50. package/templates/standard/tests/standard.test.ts +3 -2
  51. package/templates/standard/web/index.html +12 -0
  52. package/templates/standard/web/src/App.css +78 -0
  53. package/templates/standard/web/src/App.tsx +100 -0
  54. package/templates/standard/web/src/client.ts +38 -0
  55. package/templates/standard/web/src/index.css +18 -0
  56. package/templates/standard/web/src/main.tsx +13 -0
  57. package/templates/standard/web/src/vite-env.d.ts +17 -0
  58. package/templates/standard/web/tsconfig.app.json +24 -0
  59. package/templates/standard/web/tsconfig.json +4 -0
  60. package/templates/standard/web/tsconfig.node.json +20 -0
  61. 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.1",
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
  };
@@ -192,9 +190,27 @@ export function renderAiTs(input: AiSetupApplyInput): string {
192
190
  const lines = [
193
191
  `import { ai } from "okengine";`,
194
192
  ``,
193
+ `/** Cloud OpenAI-compatible binding (OpenAI / Groq / OpenRouter / …). */`,
195
194
  `export const smart = ai.model("smart", {`,
196
195
  ` provider: "${provider}",`,
197
- ` model: process.env.OKE_AI_MODEL ?? "${chat}",`,
196
+ ` model: process.env.OKE_AI_CLOUD_MODEL ?? process.env.OKE_AI_MODEL ?? "${chat}",`,
197
+ ` baseUrl: process.env.OPENAI_BASE_URL?.trim() || "https://api.openai.com/v1",`,
198
+ ` ...(process.env.OPENAI_API_KEY?.trim()`,
199
+ ` ? { apiKey: process.env.OPENAI_API_KEY.trim() }`,
200
+ ` : {}),`,
201
+ `});`,
202
+ ``,
203
+ `/** Local inference binding (docker llama.cpp / Ollama via \`OKE_AI_URL\`). */`,
204
+ `export const local = ai.model("local", {`,
205
+ ` provider: "${provider === "ollama" ? "ollama" : "openai-compatible"}",`,
206
+ ` model: process.env.OKE_AI_LOCAL_MODEL ?? "${chat}",`,
207
+ ` ...(process.env.OKE_AI_URL?.trim() ? { baseUrl: process.env.OKE_AI_URL.trim() } : {}),`,
208
+ `});`,
209
+ ``,
210
+ `/** Advanced Notes summarize — used by \`notes.summarize\` via \`fx.ask\`. */`,
211
+ `export const summarizeNote = smart.prompt("summarize-note", {`,
212
+ ` via: ["smart", "local"],`,
213
+ ` timeout: "30s",`,
198
214
  `});`,
199
215
  ];
200
216
 
@@ -249,6 +265,10 @@ function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
249
265
  if (existsSync(coreTsPath)) {
250
266
  const existing = readFileSync(coreTsPath, "utf8");
251
267
  if (hasAiModels(existing)) {
268
+ const withPrompt = ensureSummarizeNotePrompt(existing);
269
+ if (withPrompt !== existing) {
270
+ writeFileSync(coreTsPath, withPrompt, "utf8");
271
+ }
252
272
  return coreTsPath;
253
273
  }
254
274
  writeFileSync(coreTsPath, mergeAiIntoCore(existing, rendered), "utf8");
@@ -270,6 +290,44 @@ function hasAiModels(source: string): boolean {
270
290
  );
271
291
  }
272
292
 
293
+ /**
294
+ * Append the advanced Notes `summarize-note` prompt when a `smart` model
295
+ * exists but the prompt was never declared (common after older `--ai` runs).
296
+ *
297
+ * @param source - Existing `src/core.ts` (or AI sidecar) source
298
+ */
299
+ export function ensureSummarizeNotePrompt(source: string): string {
300
+ let next = source;
301
+ if (
302
+ !/\bai\.model\s*\(\s*["']local["']/.test(next) &&
303
+ /\bai\.model\s*\(\s*["']smart["']/.test(next)
304
+ ) {
305
+ next = `${next.trimEnd()}
306
+
307
+ /** Local inference binding (docker llama.cpp / Ollama via \`OKE_AI_URL\`). */
308
+ export const local = ai.model("local", {
309
+ provider: "openai-compatible",
310
+ model: process.env.OKE_AI_LOCAL_MODEL ?? "granite3.3:2b",
311
+ ...(process.env.OKE_AI_URL?.trim() ? { baseUrl: process.env.OKE_AI_URL.trim() } : {}),
312
+ });
313
+ `;
314
+ }
315
+ if (/summarize-note/.test(next) || /summarizeNote/.test(next)) {
316
+ return next;
317
+ }
318
+ if (!/\bai\.model\s*\(\s*["']smart["']/.test(next)) {
319
+ return next;
320
+ }
321
+ const prompt = `
322
+ /** Advanced Notes summarize — used by \`notes.summarize\` via \`fx.ask\`. */
323
+ export const summarizeNote = smart.prompt("summarize-note", {
324
+ via: ["smart", "local"],
325
+ timeout: "30s",
326
+ });
327
+ `;
328
+ return `${next.trimEnd()}\n${prompt}\n`;
329
+ }
330
+
273
331
  /**
274
332
  * Merge rendered AI module into an existing `src/core.ts`.
275
333
  *
@@ -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
@@ -27,6 +27,8 @@ import {
27
27
  shouldPrompt,
28
28
  sourceFromArgs,
29
29
  withBackOption,
30
+ withLocalesPgDog,
31
+ withWizardExtras,
30
32
  type InteractiveAnswers,
31
33
  } from "./cli.ts";
32
34
  import {
@@ -46,6 +48,62 @@ import {
46
48
  transformPackageJson,
47
49
  } from "./transform.ts";
48
50
 
51
+ describe("withWizardExtras / withLocalesPgDog", () => {
52
+ test("updates session defaults (reuse / customize)", () => {
53
+ const session = recommendedDefaults("docker-ready", "advanced");
54
+ const next = withWizardExtras({
55
+ template: "advanced",
56
+ locales: ["ar"],
57
+ pgdog: true,
58
+ proxy: "nginx",
59
+ session,
60
+ previous: null,
61
+ });
62
+ expect(next.template).toBe("advanced");
63
+ expect(next.locales).toEqual(["ar"]);
64
+ expect(next.pgdog).toBe(true);
65
+ expect(next.proxy).toBe("nginx");
66
+ expect(next.drivers.store.sql.dev).toBe(session.drivers.store.sql.dev);
67
+ });
68
+
69
+ test("updates previous settings when recommended has no session", () => {
70
+ const previous = {
71
+ ...recommendedDefaults("docker-ready", "advanced"),
72
+ locales: [] as const,
73
+ pgdog: false,
74
+ proxy: "none" as const,
75
+ };
76
+ const next = withLocalesPgDog({
77
+ template: "advanced",
78
+ locales: ["ar", "fr"],
79
+ pgdog: true,
80
+ proxy: "traefik",
81
+ session: undefined,
82
+ previous,
83
+ });
84
+ expect(next.locales).toEqual(["ar", "fr"]);
85
+ expect(next.pgdog).toBe(true);
86
+ expect(next.proxy).toBe("traefik");
87
+ expect(next.drivers).toEqual(previous.drivers);
88
+ });
89
+
90
+ test("falls back to recommended pins when nothing is saved", () => {
91
+ const next = withWizardExtras({
92
+ template: "standard",
93
+ locales: ["ar"],
94
+ pgdog: true,
95
+ proxy: "caddy",
96
+ session: undefined,
97
+ previous: null,
98
+ });
99
+ expect(next.template).toBe("standard");
100
+ expect(next.locales).toEqual(["ar"]);
101
+ expect(next.pgdog).toBe(true);
102
+ expect(next.proxy).toBe("caddy");
103
+ expect(next.profile).toBe("docker-ready");
104
+ });
105
+ });
106
+
49
107
  describe("defaultsBranchOptions", () => {
50
108
  test("hides reuse when no previous settings exist", () => {
51
109
  const values = defaultsBranchOptions(false).map((o) => o.value);
@@ -130,6 +188,15 @@ describe("parseArgs flags", () => {
130
188
  expect(() => parseArgs(["x", "--pgdog", "--no-pgdog"])).toThrow(/pgdog/);
131
189
  });
132
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
+
133
200
  test("rejects --install with --no-install", () => {
134
201
  expect(() => parseArgs(["x", "--install", "--no-install"])).toThrow(/install/);
135
202
  });
@@ -154,6 +221,7 @@ function recommendedAnswers(overrides: Partial<InteractiveAnswers> = {}): Intera
154
221
  aiApply: null,
155
222
  locales: [],
156
223
  pgdog: false,
224
+ proxy: "none",
157
225
  ...overrides,
158
226
  };
159
227
  }
@@ -349,6 +417,7 @@ describe("interactive branches", () => {
349
417
  ai: { enabled: false, provider: null, driver: null },
350
418
  locales: [],
351
419
  pgdog: false,
420
+ proxy: "none",
352
421
  });
353
422
 
354
423
  const dir = mkdtempSync(join(tmpdir(), "oke-custom-app-"));
@@ -448,6 +517,23 @@ describe("transformConfigForSqlDriver", () => {
448
517
  });
449
518
  });
450
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
+
451
537
  describe("shouldSkipTemplatePath", () => {
452
538
  test("skips node_modules, locks, and monorepo docker test", () => {
453
539
  expect(shouldSkipTemplatePath("node_modules/okengine/package.json")).toBe(true);
@@ -524,22 +610,32 @@ describe("scaffold structure", () => {
524
610
  expect(appTs).not.toMatch(/Object\.assign/);
525
611
  expect(appTs).not.toMatch(/env:\s*["']test["']/);
526
612
  expect(appTs).not.toMatch(/stores:\s*\[/);
527
- expect(appTs).toMatch(/oke\(\{\s*name:\s*["']notes["']\s*\}\)/);
613
+ expect(appTs).toMatch(/oke\(\{\s*name:\s*["']notes["']/);
528
614
  const pkg = JSON.parse(readFileSync(join(result.targetDir, "package.json"), "utf8")) as {
529
615
  name: string;
530
- dependencies: { okengine: string };
531
- 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 };
532
619
  devDependencies: {
533
620
  typescript?: string;
621
+ vite?: string;
534
622
  "@electric-sql/pglite"?: string;
535
623
  "@electric-sql/pglite-pgvector"?: string;
536
624
  };
537
625
  };
538
626
  expect(pkg.name).toBe(`app-${id}`);
539
627
  expect(pkg.dependencies.okengine).not.toMatch(/^file:\.\./);
540
- 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");
541
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");
542
637
  expect(pkg.devDependencies.typescript).toBeTruthy();
638
+ expect(pkg.devDependencies.vite).toBe("^8.2.0");
543
639
  expect(pkg.devDependencies["@electric-sql/pglite"]).toBe("^0.5.4");
544
640
  expect(pkg.devDependencies["@electric-sql/pglite-pgvector"]).toBe("^0.0.5");
545
641
  const drizzle = readFileSync(join(result.targetDir, "drizzle.config.ts"), "utf8");
@@ -582,6 +678,7 @@ describe("scaffold structure", () => {
582
678
  }
583
679
  const notes = readFileSync(join(result.targetDir, "src/flows/notes/index.ts"), "utf8");
584
680
  expect(notes).toContain('flow("notes.create"');
681
+ expect(notes).toContain("fx.json.withQuery");
585
682
  expect(notes).not.toContain('flow("notes.digest"');
586
683
  expect(existsSync(join(result.targetDir, "src/locales/ar.ts"))).toBe(false);
587
684
  expect(readFileSync(join(result.targetDir, "oke.config.ts"), "utf8")).toContain(
@@ -665,6 +762,37 @@ describe("scaffold structure", () => {
665
762
  }
666
763
  });
667
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
+
668
796
  test("--sql postgres pins store.sql (abstract schema stays dialect-agnostic)", async () => {
669
797
  const dir = mkdtempSync(join(tmpdir(), "create-oke-sql-pg-"));
670
798
  try {
@@ -799,16 +927,10 @@ describe("non-TTY CLI", () => {
799
927
  expect(existsSync(join(target, "AGENTS.md"))).toBe(true);
800
928
  const result = {
801
929
  targetDir: target,
802
- name: "flag-app",
803
- source: { kind: "template" as const, id: "standard" as const },
804
930
  label: "standard",
805
- okengineDependency: "x",
806
- files: [],
807
- sqlDriver: "postgres" as const,
808
- locales: [],
809
- pgdog: false,
810
931
  };
811
932
  expect(nextStepsText(result)).toContain("oke dev");
933
+ expect(nextStepsText(result)).toContain("bun run web");
812
934
  expect(nextStepsText(result)).toContain("bun install");
813
935
  expect(nextStepsText(result)).toContain("oke.omqkhafi.dev");
814
936
  } finally {