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.
- package/package.json +1 -1
- package/src/agents-md.ts +4 -1
- package/src/ai-setup/apply.ts +3 -5
- package/src/ai-setup/from-pref.ts +4 -4
- package/src/ai-setup/prompts.ts +1 -1
- package/src/cli.test.ts +86 -14
- package/src/cli.ts +145 -29
- package/src/create-defaults.test.ts +9 -0
- package/src/create-defaults.ts +27 -0
- 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 +2 -0
- package/templates/advanced/src/flows/main/index.ts +4 -4
- package/templates/advanced/src/flows/notes/index.ts +14 -14
- package/templates/advanced/src/flows/notes/shapes.ts +2 -3
- 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
|
};
|
|
@@ -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
|
@@ -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 =
|
|
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 =
|
|
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["']
|
|
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
|
-
|
|
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.
|
|
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 {
|