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.
- package/package.json +1 -1
- package/src/agents-md.ts +4 -1
- package/src/ai-setup/apply.ts +64 -6
- package/src/ai-setup/from-pref.ts +4 -4
- package/src/ai-setup/prompts.ts +1 -1
- package/src/cli.test.ts +133 -11
- package/src/cli.ts +180 -29
- package/src/create-defaults.test.ts +9 -0
- package/src/create-defaults.ts +27 -0
- package/src/customize-flow.test.ts +14 -7
- package/src/customize-flow.ts +2 -1
- package/src/drivers-catalog.ts +18 -9
- package/src/local-okengine.test.ts +2 -0
- package/src/local-okengine.ts +3 -0
- package/src/scaffold.ts +17 -5
- package/src/transform.test.ts +6 -3
- package/src/transform.ts +24 -1
- package/templates/advanced/.env.example +13 -5
- package/templates/advanced/README.md +3 -0
- package/templates/advanced/oke.config.ts +6 -5
- package/templates/advanced/package.json +14 -3
- package/templates/advanced/src/app.ts +2 -1
- package/templates/advanced/src/core.ts +12 -0
- package/templates/advanced/src/db/seed/index.ts +3 -1
- package/templates/advanced/src/flows/main/index.ts +4 -4
- package/templates/advanced/src/flows/notes/index.ts +73 -26
- package/templates/advanced/src/flows/notes/shapes.ts +9 -4
- package/templates/advanced/tests/advanced.test.ts +3 -2
- package/templates/advanced/web/index.html +12 -0
- package/templates/advanced/web/src/App.css +78 -0
- package/templates/advanced/web/src/App.tsx +100 -0
- package/templates/advanced/web/src/client.ts +38 -0
- package/templates/advanced/web/src/index.css +18 -0
- package/templates/advanced/web/src/main.tsx +13 -0
- package/templates/advanced/web/src/vite-env.d.ts +17 -0
- package/templates/advanced/web/tsconfig.app.json +24 -0
- package/templates/advanced/web/tsconfig.json +4 -0
- package/templates/advanced/web/tsconfig.node.json +20 -0
- package/templates/advanced/web/vite.config.ts +32 -0
- package/templates/standard/.env.example +13 -5
- package/templates/standard/README.md +3 -0
- package/templates/standard/oke.config.ts +4 -3
- package/templates/standard/package.json +14 -3
- package/templates/standard/src/app.ts +2 -1
- package/templates/standard/src/core.ts +12 -0
- package/templates/standard/src/db/seed/index.ts +2 -0
- package/templates/standard/src/flows/main/index.ts +4 -4
- package/templates/standard/src/flows/notes/index.ts +12 -12
- package/templates/standard/src/flows/notes/shapes.ts +2 -3
- package/templates/standard/tests/standard.test.ts +3 -2
- package/templates/standard/web/index.html +12 -0
- package/templates/standard/web/src/App.css +78 -0
- package/templates/standard/web/src/App.tsx +100 -0
- package/templates/standard/web/src/client.ts +38 -0
- package/templates/standard/web/src/index.css +18 -0
- package/templates/standard/web/src/main.tsx +13 -0
- package/templates/standard/web/src/vite-env.d.ts +17 -0
- package/templates/standard/web/tsconfig.app.json +24 -0
- package/templates/standard/web/tsconfig.json +4 -0
- package/templates/standard/web/tsconfig.node.json +20 -0
- package/templates/standard/web/vite.config.ts +32 -0
package/package.json
CHANGED
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
|
|
package/src/ai-setup/apply.ts
CHANGED
|
@@ -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
|
|
74
|
-
//
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
104
|
+
image: SGLANG_IMAGE,
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
if (provider === "anthropic") {
|
package/src/ai-setup/prompts.ts
CHANGED
|
@@ -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.
|
|
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["']
|
|
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
|
-
|
|
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.
|
|
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 {
|