create-oke 0.11.2 → 0.13.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/src/cli.ts
CHANGED
|
@@ -25,10 +25,13 @@ import { existsSync, rmSync } from "node:fs";
|
|
|
25
25
|
import { agentsMdContent } from "./agents-md.ts";
|
|
26
26
|
import {
|
|
27
27
|
createDefaultsPath,
|
|
28
|
+
isCreateProxyId,
|
|
28
29
|
readCreateDefaults,
|
|
29
30
|
writeCreateDefaults,
|
|
30
31
|
type CreateDefaults,
|
|
32
|
+
type CreateProxyId,
|
|
31
33
|
type EnvDriverPins,
|
|
34
|
+
CREATE_PROXY_IDS,
|
|
32
35
|
} from "./create-defaults.ts";
|
|
33
36
|
import { docsUrl } from "./docs-origin.ts";
|
|
34
37
|
import { applyAiSetup, type AiSetupApplyInput } from "./ai-setup/apply.ts";
|
|
@@ -90,6 +93,11 @@ export type CliArgs = {
|
|
|
90
93
|
* `undefined` = ask (interactive) / off (`--yes`).
|
|
91
94
|
*/
|
|
92
95
|
readonly pgdog: boolean | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Proxy pin from CLI.
|
|
98
|
+
* `undefined` = ask (interactive) / none (`--yes`).
|
|
99
|
+
*/
|
|
100
|
+
readonly proxy: CreateProxyId | undefined;
|
|
93
101
|
readonly targetDir: string | undefined;
|
|
94
102
|
};
|
|
95
103
|
|
|
@@ -153,6 +161,8 @@ export type InteractiveAnswers = {
|
|
|
153
161
|
readonly locales: readonly string[];
|
|
154
162
|
/** Pin PgDog in front of Postgres. */
|
|
155
163
|
readonly pgdog: boolean;
|
|
164
|
+
/** Pin `images.proxy` (`none` = unset). */
|
|
165
|
+
readonly proxy: CreateProxyId;
|
|
156
166
|
};
|
|
157
167
|
|
|
158
168
|
/**
|
|
@@ -171,6 +181,8 @@ export type ScaffoldCallArgs = {
|
|
|
171
181
|
readonly locales?: readonly string[];
|
|
172
182
|
/** Pin PgDog in front of Postgres. */
|
|
173
183
|
readonly pgdog?: boolean;
|
|
184
|
+
/** Pin `images.proxy`. */
|
|
185
|
+
readonly proxy?: CreateProxyId;
|
|
174
186
|
};
|
|
175
187
|
|
|
176
188
|
/**
|
|
@@ -191,6 +203,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
191
203
|
let ai: AiCliMode = "prompt";
|
|
192
204
|
let locales: readonly string[] = [];
|
|
193
205
|
let pgdog: boolean | undefined;
|
|
206
|
+
let proxy: CreateProxyId | undefined;
|
|
194
207
|
let targetDir: string | undefined;
|
|
195
208
|
|
|
196
209
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -302,6 +315,26 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
302
315
|
pgdog = false;
|
|
303
316
|
continue;
|
|
304
317
|
}
|
|
318
|
+
if (a === "--proxy") {
|
|
319
|
+
const next = argv[++i];
|
|
320
|
+
if (!next || !isCreateProxyId(next)) {
|
|
321
|
+
throw new Error(`create-oke: --proxy must be one of ${CREATE_PROXY_IDS.join("|")}`);
|
|
322
|
+
}
|
|
323
|
+
proxy = next;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (a.startsWith("--proxy=")) {
|
|
327
|
+
const value = a.slice("--proxy=".length);
|
|
328
|
+
if (!isCreateProxyId(value)) {
|
|
329
|
+
throw new Error(`create-oke: --proxy must be one of ${CREATE_PROXY_IDS.join("|")}`);
|
|
330
|
+
}
|
|
331
|
+
proxy = value;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (a === "--no-proxy") {
|
|
335
|
+
proxy = "none";
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
305
338
|
if (a.startsWith("-")) {
|
|
306
339
|
throw new Error(`create-oke: unknown option ${a}`);
|
|
307
340
|
}
|
|
@@ -318,6 +351,11 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
318
351
|
if (argv.includes("--pgdog") && argv.includes("--no-pgdog")) {
|
|
319
352
|
throw new Error("create-oke: use either --pgdog or --no-pgdog, not both");
|
|
320
353
|
}
|
|
354
|
+
if (argv.includes("--proxy") || argv.some((x) => x.startsWith("--proxy="))) {
|
|
355
|
+
if (argv.includes("--no-proxy")) {
|
|
356
|
+
throw new Error("create-oke: use either --proxy or --no-proxy, not both");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
321
359
|
|
|
322
360
|
if (name !== undefined) {
|
|
323
361
|
targetDir = resolve(name);
|
|
@@ -336,6 +374,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
336
374
|
ai,
|
|
337
375
|
locales,
|
|
338
376
|
pgdog,
|
|
377
|
+
proxy,
|
|
339
378
|
targetDir,
|
|
340
379
|
};
|
|
341
380
|
}
|
|
@@ -354,9 +393,9 @@ export function formatCdPath(targetDir: string): string {
|
|
|
354
393
|
/**
|
|
355
394
|
* Post-scaffold next-steps block — shared by interactive and flag-driven paths.
|
|
356
395
|
*
|
|
357
|
-
* @param result - Successful scaffold result
|
|
396
|
+
* @param result - Successful scaffold result (only `label` + `targetDir` are read)
|
|
358
397
|
*/
|
|
359
|
-
export function nextStepsText(result: ScaffoldResult): string {
|
|
398
|
+
export function nextStepsText(result: Pick<ScaffoldResult, "label" | "targetDir">): string {
|
|
360
399
|
return `
|
|
361
400
|
Scaffolded ${result.label} → ${result.targetDir}
|
|
362
401
|
|
|
@@ -366,6 +405,7 @@ Next steps:
|
|
|
366
405
|
bun install
|
|
367
406
|
oke schema generate # system stubs → .oke/schema/oke.ts (also runs on db push)
|
|
368
407
|
oke dev # app :6530 · Console :6533 · MCP :6535
|
|
408
|
+
bun run web # Vite SPA — proxies /notes · /health to the app
|
|
369
409
|
|
|
370
410
|
Docs: ${docsUrl("/docs")}
|
|
371
411
|
`;
|
|
@@ -400,6 +440,8 @@ Options:
|
|
|
400
440
|
--locales <tags> Extra languages beyond English (e.g. ar or ar,fr)
|
|
401
441
|
--pgdog Pin PgDog pooling in front of Postgres
|
|
402
442
|
--no-pgdog Skip PgDog (default for --yes)
|
|
443
|
+
--proxy <id> Pin reverse proxy: ${CREATE_PROXY_IDS.join("|")}
|
|
444
|
+
--no-proxy Skip proxy (default for --yes)
|
|
403
445
|
-h, --help Show this help
|
|
404
446
|
|
|
405
447
|
Template:
|
|
@@ -407,10 +449,10 @@ ${templateLines}
|
|
|
407
449
|
|
|
408
450
|
On a TTY: pick standard|advanced, then recommended defaults, customize
|
|
409
451
|
(Docker-first facets; store.index with none; AI setup Recommended /
|
|
410
|
-
Customize / Off), optional
|
|
411
|
-
saved for that template.
|
|
412
|
-
~/.oke/create-defaults.json
|
|
413
|
-
Non-TTY / --yes stay English-only / no PgDog
|
|
452
|
+
Customize / Off), optional locales + PgDog + proxy, or reuse when
|
|
453
|
+
saved for that template. Reuse applies saved locales / PgDog / proxy
|
|
454
|
+
without re-asking. Answers write to ~/.oke/create-defaults.json.
|
|
455
|
+
Non-TTY / --yes stay English-only / no PgDog / no proxy unless flagged.
|
|
414
456
|
`;
|
|
415
457
|
}
|
|
416
458
|
|
|
@@ -455,6 +497,7 @@ export function scaffoldArgsFromCli(args: CliArgs): ScaffoldCallArgs {
|
|
|
455
497
|
aiApply: args.ai === "force" ? nonInteractiveAiApply("llama-cpp") : null,
|
|
456
498
|
locales: args.locales,
|
|
457
499
|
pgdog: args.pgdog ?? false,
|
|
500
|
+
proxy: args.proxy ?? "none",
|
|
458
501
|
};
|
|
459
502
|
}
|
|
460
503
|
|
|
@@ -505,6 +548,7 @@ export function scaffoldArgsFromAnswers(
|
|
|
505
548
|
aiApply: answers.aiApply,
|
|
506
549
|
locales: answers.locales,
|
|
507
550
|
pgdog: answers.pgdog,
|
|
551
|
+
proxy: answers.proxy,
|
|
508
552
|
};
|
|
509
553
|
}
|
|
510
554
|
|
|
@@ -521,6 +565,7 @@ export type AskInteractiveFn = (partial: {
|
|
|
521
565
|
readonly template?: TemplateId;
|
|
522
566
|
readonly locales?: readonly string[];
|
|
523
567
|
readonly pgdog?: boolean;
|
|
568
|
+
readonly proxy?: CreateProxyId;
|
|
524
569
|
}) => Promise<InteractiveAnswers | null>;
|
|
525
570
|
|
|
526
571
|
/** Injectable persistence seams for tests. */
|
|
@@ -531,21 +576,22 @@ export type CreateDefaultsIo = {
|
|
|
531
576
|
};
|
|
532
577
|
|
|
533
578
|
/**
|
|
534
|
-
* Merge wizard locales / PgDog into a create-defaults document to persist.
|
|
579
|
+
* Merge wizard locales / PgDog / proxy into a create-defaults document to persist.
|
|
535
580
|
*
|
|
536
581
|
* Prefer the in-session answers (customize / reuse), else same-template
|
|
537
582
|
* previous settings, else recommended pins for the template.
|
|
538
583
|
*
|
|
539
|
-
* @param input - Template, locales, pgdog, and optional session / previous docs
|
|
584
|
+
* @param input - Template, locales, pgdog, proxy, and optional session / previous docs
|
|
540
585
|
*/
|
|
541
|
-
export function
|
|
586
|
+
export function withWizardExtras(input: {
|
|
542
587
|
readonly template: TemplateId;
|
|
543
588
|
readonly locales: readonly string[];
|
|
544
589
|
readonly pgdog: boolean;
|
|
590
|
+
readonly proxy: CreateProxyId;
|
|
545
591
|
readonly session: CreateDefaults | undefined;
|
|
546
592
|
readonly previous: CreateDefaults | null;
|
|
547
593
|
}): CreateDefaults {
|
|
548
|
-
const { template, locales, pgdog, session, previous } = input;
|
|
594
|
+
const { template, locales, pgdog, proxy, session, previous } = input;
|
|
549
595
|
const base =
|
|
550
596
|
session ??
|
|
551
597
|
(previous?.template === template ? previous : null) ??
|
|
@@ -555,10 +601,26 @@ export function withLocalesPgDog(input: {
|
|
|
555
601
|
template,
|
|
556
602
|
locales,
|
|
557
603
|
pgdog,
|
|
604
|
+
proxy,
|
|
558
605
|
updatedAt: new Date().toISOString(),
|
|
559
606
|
};
|
|
560
607
|
}
|
|
561
608
|
|
|
609
|
+
/** @deprecated Prefer {@link withWizardExtras}. */
|
|
610
|
+
export function withLocalesPgDog(input: {
|
|
611
|
+
readonly template: TemplateId;
|
|
612
|
+
readonly locales: readonly string[];
|
|
613
|
+
readonly pgdog: boolean;
|
|
614
|
+
readonly proxy?: CreateProxyId;
|
|
615
|
+
readonly session: CreateDefaults | undefined;
|
|
616
|
+
readonly previous: CreateDefaults | null;
|
|
617
|
+
}): CreateDefaults {
|
|
618
|
+
return withWizardExtras({
|
|
619
|
+
...input,
|
|
620
|
+
proxy: input.proxy ?? input.session?.proxy ?? input.previous?.proxy ?? "none",
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
562
624
|
/**
|
|
563
625
|
* Collect interactive answers via `@clack/prompts`.
|
|
564
626
|
*
|
|
@@ -575,6 +637,7 @@ export async function askInteractiveAnswers(
|
|
|
575
637
|
readonly template?: TemplateId;
|
|
576
638
|
readonly locales?: readonly string[];
|
|
577
639
|
readonly pgdog?: boolean;
|
|
640
|
+
readonly proxy?: CreateProxyId;
|
|
578
641
|
} = {},
|
|
579
642
|
io: CreateDefaultsIo = {
|
|
580
643
|
path: createDefaultsPath(),
|
|
@@ -613,6 +676,7 @@ export async function askInteractiveAnswers(
|
|
|
613
676
|
let createDefaults: CreateDefaults | undefined;
|
|
614
677
|
let aiApply: AiSetupApplyInput | null = null;
|
|
615
678
|
let persistDefaults = false;
|
|
679
|
+
let reused = false;
|
|
616
680
|
|
|
617
681
|
for (;;) {
|
|
618
682
|
const previous = io.read();
|
|
@@ -639,6 +703,7 @@ export async function askInteractiveAnswers(
|
|
|
639
703
|
continue;
|
|
640
704
|
}
|
|
641
705
|
createDefaults = prev;
|
|
706
|
+
reused = true;
|
|
642
707
|
if (prev.ai.enabled && partial.ai !== "skip") {
|
|
643
708
|
aiApply = applyInputFromAiPref(prev.ai);
|
|
644
709
|
if (!aiApply?.chatModel && prev.ai.provider && prev.ai.provider !== "mock") {
|
|
@@ -675,32 +740,58 @@ export async function askInteractiveAnswers(
|
|
|
675
740
|
aiApply = picked;
|
|
676
741
|
}
|
|
677
742
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
743
|
+
// Reuse applies saved locales / PgDog / proxy without re-asking (CLI flags still win).
|
|
744
|
+
let locales: readonly string[];
|
|
745
|
+
let pgdog: boolean;
|
|
746
|
+
let proxy: CreateProxyId;
|
|
747
|
+
|
|
748
|
+
if (reused && createDefaults) {
|
|
749
|
+
locales =
|
|
750
|
+
partial.locales !== undefined && partial.locales.length > 0
|
|
751
|
+
? partial.locales
|
|
752
|
+
: createDefaults.locales;
|
|
753
|
+
pgdog = partial.pgdog !== undefined ? partial.pgdog : createDefaults.pgdog;
|
|
754
|
+
proxy = partial.proxy !== undefined ? partial.proxy : createDefaults.proxy;
|
|
755
|
+
} else {
|
|
756
|
+
if (partial.locales !== undefined && partial.locales.length > 0) {
|
|
757
|
+
locales = partial.locales;
|
|
758
|
+
} else {
|
|
759
|
+
const pickedLocales = await askExtraLocales(createDefaults?.locales ?? []);
|
|
760
|
+
if (pickedLocales === null) return null;
|
|
761
|
+
locales = pickedLocales;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const usesPostgres = createDefaults
|
|
765
|
+
? createDefaults.drivers.store.sql.dev === "postgres" ||
|
|
766
|
+
createDefaults.drivers.store.sql.prod === "postgres" ||
|
|
767
|
+
createDefaults.drivers.store.sql.dev === "pgvector" ||
|
|
768
|
+
createDefaults.drivers.store.sql.prod === "pgvector"
|
|
769
|
+
: true;
|
|
770
|
+
pgdog = false;
|
|
771
|
+
if (usesPostgres) {
|
|
772
|
+
if (partial.pgdog !== undefined) {
|
|
773
|
+
pgdog = partial.pgdog;
|
|
774
|
+
} else {
|
|
775
|
+
const picked = await askPgDog(createDefaults?.pgdog ?? false);
|
|
776
|
+
if (picked === null) return null;
|
|
777
|
+
pgdog = picked;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (partial.proxy !== undefined) {
|
|
782
|
+
proxy = partial.proxy;
|
|
691
783
|
} else {
|
|
692
|
-
const picked = await
|
|
784
|
+
const picked = await askProxy(createDefaults?.proxy ?? "none");
|
|
693
785
|
if (picked === null) return null;
|
|
694
|
-
|
|
786
|
+
proxy = picked;
|
|
695
787
|
}
|
|
696
788
|
}
|
|
697
789
|
|
|
698
|
-
|
|
699
|
-
// (and recommended) keep the last answers in ~/.oke/create-defaults.json.
|
|
700
|
-
const persisted = withLocalesPgDog({
|
|
790
|
+
const persisted = withWizardExtras({
|
|
701
791
|
template,
|
|
702
792
|
locales,
|
|
703
793
|
pgdog,
|
|
794
|
+
proxy,
|
|
704
795
|
session: createDefaults,
|
|
705
796
|
previous: io.read(),
|
|
706
797
|
});
|
|
@@ -729,6 +820,7 @@ export async function askInteractiveAnswers(
|
|
|
729
820
|
aiApply,
|
|
730
821
|
locales,
|
|
731
822
|
pgdog,
|
|
823
|
+
proxy,
|
|
732
824
|
};
|
|
733
825
|
}
|
|
734
826
|
|
|
@@ -779,6 +871,27 @@ async function askPgDog(initial: boolean = false): Promise<boolean | null> {
|
|
|
779
871
|
return Boolean(value);
|
|
780
872
|
}
|
|
781
873
|
|
|
874
|
+
/**
|
|
875
|
+
* Ask which reverse proxy to pin (or none).
|
|
876
|
+
*
|
|
877
|
+
* @param initial - Prefill from reused create-defaults
|
|
878
|
+
* @returns Proxy id, or `null` on cancel
|
|
879
|
+
*/
|
|
880
|
+
async function askProxy(initial: CreateProxyId = "none"): Promise<CreateProxyId | null> {
|
|
881
|
+
const value = await select({
|
|
882
|
+
message: "Add a reverse proxy in front of the app?",
|
|
883
|
+
options: [
|
|
884
|
+
{ value: "none", label: "No", hint: "app publishes :6530" },
|
|
885
|
+
{ value: "caddy", label: "Caddy", hint: "automatic HTTPS · Caddyfile" },
|
|
886
|
+
{ value: "traefik", label: "Traefik", hint: "Docker labels · --scale app=N" },
|
|
887
|
+
{ value: "nginx", label: "nginx", hint: "static nginx.conf reverse proxy" },
|
|
888
|
+
],
|
|
889
|
+
initialValue: initial,
|
|
890
|
+
});
|
|
891
|
+
if (isCancel(value)) return null;
|
|
892
|
+
return value as CreateProxyId;
|
|
893
|
+
}
|
|
894
|
+
|
|
782
895
|
/**
|
|
783
896
|
* Provider passed to `oke ai setup` — prefer native Ollama when either env uses it.
|
|
784
897
|
*
|
|
@@ -906,8 +1019,9 @@ async function runInteractive(
|
|
|
906
1019
|
agentsMd: args.agentsMd,
|
|
907
1020
|
sqlDriver: args.sqlDriverExplicit ? args.sqlDriver : undefined,
|
|
908
1021
|
ai: args.ai,
|
|
909
|
-
locales: args.locales,
|
|
1022
|
+
...(args.locales.length > 0 ? { locales: args.locales } : {}),
|
|
910
1023
|
...(args.pgdog !== undefined ? { pgdog: args.pgdog } : {}),
|
|
1024
|
+
...(args.proxy !== undefined ? { proxy: args.proxy } : {}),
|
|
911
1025
|
});
|
|
912
1026
|
if (answers === null) {
|
|
913
1027
|
cancel("Cancelled.");
|
|
@@ -946,6 +1060,7 @@ async function runScaffold(
|
|
|
946
1060
|
aiApply,
|
|
947
1061
|
locales,
|
|
948
1062
|
pgdog,
|
|
1063
|
+
proxy,
|
|
949
1064
|
interactive,
|
|
950
1065
|
install,
|
|
951
1066
|
startDev,
|
|
@@ -982,6 +1097,7 @@ async function runScaffold(
|
|
|
982
1097
|
...(createDefaults !== undefined ? { createDefaults } : {}),
|
|
983
1098
|
...(locales !== undefined ? { locales } : {}),
|
|
984
1099
|
...(pgdog !== undefined ? { pgdog } : {}),
|
|
1100
|
+
...(proxy !== undefined ? { proxy } : {}),
|
|
985
1101
|
});
|
|
986
1102
|
if (spun) spun.stop("Scaffolded.");
|
|
987
1103
|
|
|
@@ -45,6 +45,13 @@ describe("parseCreateDefaults", () => {
|
|
|
45
45
|
const parsed = parseCreateDefaults(rest);
|
|
46
46
|
expect(parsed?.template).toBe("standard");
|
|
47
47
|
});
|
|
48
|
+
|
|
49
|
+
test("older files without proxy default to none", () => {
|
|
50
|
+
const d = recommendedDefaults("docker-ready", "standard");
|
|
51
|
+
const { proxy: _p, ...rest } = d;
|
|
52
|
+
const parsed = parseCreateDefaults(rest);
|
|
53
|
+
expect(parsed?.proxy).toBe("none");
|
|
54
|
+
});
|
|
48
55
|
});
|
|
49
56
|
|
|
50
57
|
describe("read/write round-trip", () => {
|
|
@@ -71,6 +78,7 @@ describe("read/write round-trip", () => {
|
|
|
71
78
|
ai: { enabled: true, provider: "ollama", driver: "ollama" },
|
|
72
79
|
locales: ["ar"],
|
|
73
80
|
pgdog: true,
|
|
81
|
+
proxy: "caddy",
|
|
74
82
|
});
|
|
75
83
|
writeCreateDefaults(original, path);
|
|
76
84
|
const loaded = readCreateDefaults(path);
|
|
@@ -82,6 +90,7 @@ describe("read/write round-trip", () => {
|
|
|
82
90
|
expect(loaded!.profile).toBe("docker-ready");
|
|
83
91
|
expect(loaded!.locales).toEqual(["ar"]);
|
|
84
92
|
expect(loaded!.pgdog).toBe(true);
|
|
93
|
+
expect(loaded!.proxy).toBe("caddy");
|
|
85
94
|
} finally {
|
|
86
95
|
rmSync(home, { recursive: true, force: true });
|
|
87
96
|
}
|
package/src/create-defaults.ts
CHANGED
|
@@ -13,6 +13,9 @@ import { isTemplateId, type TemplateId } from "./templates.ts";
|
|
|
13
13
|
/** Create profile — Docker Compose is always the `dev` runtime. */
|
|
14
14
|
export type CreateProfile = "docker-ready";
|
|
15
15
|
|
|
16
|
+
/** Opt-in reverse proxy pinned into `images.proxy`. */
|
|
17
|
+
export type CreateProxyId = "none" | "caddy" | "traefik" | "nginx";
|
|
18
|
+
|
|
16
19
|
/** Env columns written into `oke.config.ts` driver maps. */
|
|
17
20
|
export type EnvDriverPins = {
|
|
18
21
|
readonly dev: string;
|
|
@@ -71,12 +74,20 @@ export type CreateDefaults = {
|
|
|
71
74
|
* Default false — opt in via the wizard or `--pgdog`.
|
|
72
75
|
*/
|
|
73
76
|
readonly pgdog: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Pin `images.proxy` (Caddy / Traefik / nginx). `none` leaves the role unset.
|
|
79
|
+
* Default `none` — opt in via the wizard or `--proxy`.
|
|
80
|
+
*/
|
|
81
|
+
readonly proxy: CreateProxyId;
|
|
74
82
|
readonly updatedAt: string;
|
|
75
83
|
};
|
|
76
84
|
|
|
77
85
|
/** Relative path under the user home directory. */
|
|
78
86
|
export const CREATE_DEFAULTS_RELATIVE = ".oke/create-defaults.json";
|
|
79
87
|
|
|
88
|
+
/** Valid proxy wizard / CLI values. */
|
|
89
|
+
export const CREATE_PROXY_IDS = ["none", "caddy", "traefik", "nginx"] as const;
|
|
90
|
+
|
|
80
91
|
/**
|
|
81
92
|
* Absolute path to the create-defaults file.
|
|
82
93
|
*
|
|
@@ -130,6 +141,15 @@ export function toCreateDefaults(
|
|
|
130
141
|
};
|
|
131
142
|
}
|
|
132
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Whether `value` is a known {@link CreateProxyId}.
|
|
146
|
+
*
|
|
147
|
+
* @param value - Candidate
|
|
148
|
+
*/
|
|
149
|
+
export function isCreateProxyId(value: string): value is CreateProxyId {
|
|
150
|
+
return (CREATE_PROXY_IDS as readonly string[]).includes(value);
|
|
151
|
+
}
|
|
152
|
+
|
|
133
153
|
/**
|
|
134
154
|
* Validate a parsed JSON value as {@link CreateDefaults}.
|
|
135
155
|
*
|
|
@@ -213,6 +233,12 @@ export function parseCreateDefaults(raw: unknown): CreateDefaults | null {
|
|
|
213
233
|
if (typeof o.pgdog !== "boolean") return null;
|
|
214
234
|
pgdog = o.pgdog;
|
|
215
235
|
}
|
|
236
|
+
// Older files omit proxy — treat as none (opt-in).
|
|
237
|
+
let proxy: CreateProxyId = "none";
|
|
238
|
+
if (o.proxy !== undefined && o.proxy !== null) {
|
|
239
|
+
if (typeof o.proxy !== "string" || !isCreateProxyId(o.proxy)) return null;
|
|
240
|
+
proxy = o.proxy;
|
|
241
|
+
}
|
|
216
242
|
return {
|
|
217
243
|
version: 1,
|
|
218
244
|
template,
|
|
@@ -237,6 +263,7 @@ export function parseCreateDefaults(raw: unknown): CreateDefaults | null {
|
|
|
237
263
|
},
|
|
238
264
|
locales,
|
|
239
265
|
pgdog,
|
|
266
|
+
proxy,
|
|
240
267
|
updatedAt: o.updatedAt,
|
|
241
268
|
};
|
|
242
269
|
}
|
package/src/customize-flow.ts
CHANGED
|
@@ -240,9 +240,10 @@ export async function askCustomizeFlow(
|
|
|
240
240
|
profile: "docker-ready",
|
|
241
241
|
drivers: { ...drivers, ai: aiPins },
|
|
242
242
|
ai: aiPref,
|
|
243
|
-
// Locales / PgDog are asked once in the main wizard (not per customize pass).
|
|
243
|
+
// Locales / PgDog / proxy are asked once in the main wizard (not per customize pass).
|
|
244
244
|
locales: [],
|
|
245
245
|
pgdog: false,
|
|
246
|
+
proxy: "none",
|
|
246
247
|
});
|
|
247
248
|
}
|
|
248
249
|
|
package/src/drivers-catalog.ts
CHANGED
|
@@ -89,7 +89,7 @@ export const JOURNAL_CHOICES: readonly DriverChoice[] = [
|
|
|
89
89
|
* Vault backends. `vault` is okengine's own encrypted-at-rest store: it lives
|
|
90
90
|
* in the app's Postgres, so it needs no extra container, and it is the only
|
|
91
91
|
* backend with `oke vault init` / `seal` / `audit`. `managed` reads from a
|
|
92
|
-
* provider secret store (
|
|
92
|
+
* provider secret store (AWS, Azure, GCP, Doppler, 1Password).
|
|
93
93
|
*/
|
|
94
94
|
export const VAULT_CHOICES: readonly DriverChoice[] = [
|
|
95
95
|
{ value: "env", label: "env (dotenv layers only)" },
|
|
@@ -112,13 +112,13 @@ export const EMAIL_CHOICES: readonly DriverChoice[] = [
|
|
|
112
112
|
* Pinned local-inference images — must match `src/docker/recipes/*` pins.
|
|
113
113
|
* Never `latest` (GGUF-parser CVE floors: llama.cpp ≥ b8146, Ollama ≥ 0.17.1).
|
|
114
114
|
*/
|
|
115
|
-
export const LLAMA_CPP_IMAGE = "ghcr.io/ggml-org/llama.cpp:server-
|
|
115
|
+
export const LLAMA_CPP_IMAGE = "ghcr.io/ggml-org/llama.cpp:server-b10450";
|
|
116
116
|
/** @see LLAMA_CPP_IMAGE */
|
|
117
|
-
export const OLLAMA_IMAGE = "ollama/ollama:0.32.
|
|
117
|
+
export const OLLAMA_IMAGE = "ollama/ollama:0.32.13";
|
|
118
118
|
/** @see LLAMA_CPP_IMAGE */
|
|
119
|
-
export const VLLM_IMAGE = "vllm/vllm-openai:v0.
|
|
119
|
+
export const VLLM_IMAGE = "vllm/vllm-openai:v0.27.1";
|
|
120
120
|
/** @see LLAMA_CPP_IMAGE */
|
|
121
|
-
export const SGLANG_IMAGE = "lmsysorg/sglang:v0.5.
|
|
121
|
+
export const SGLANG_IMAGE = "lmsysorg/sglang:v0.5.17-runtime";
|
|
122
122
|
|
|
123
123
|
/** AI menu providers → protocol driver. */
|
|
124
124
|
export const AI_PROVIDERS = [
|
|
@@ -191,6 +191,7 @@ export function recommendedDefaults(
|
|
|
191
191
|
ai: { enabled: false, provider: null, driver: null },
|
|
192
192
|
locales: [],
|
|
193
193
|
pgdog: false,
|
|
194
|
+
proxy: "none",
|
|
194
195
|
updatedAt: new Date().toISOString(),
|
|
195
196
|
};
|
|
196
197
|
}
|
|
@@ -229,10 +230,18 @@ export function aiDriverForProvider(provider: string): string {
|
|
|
229
230
|
/** Default image pins keyed by role (standard template). */
|
|
230
231
|
export const DEFAULT_IMAGES: Readonly<Record<string, string>> = {
|
|
231
232
|
"store.sql": "postgres:18-alpine",
|
|
232
|
-
pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.
|
|
233
|
+
pgdog: "ghcr.io/pgdogdev/pgdog:v0.1.53",
|
|
233
234
|
"store.kv": "redis:8-alpine",
|
|
234
|
-
"store.files": "rustfs/rustfs:1.0.0-
|
|
235
|
-
"channel.email": "axllent/mailpit:v1.
|
|
236
|
-
"store.index": "getmeili/meilisearch:v1.
|
|
235
|
+
"store.files": "rustfs/rustfs:1.0.0-rc.2",
|
|
236
|
+
"channel.email": "axllent/mailpit:v1.30.7",
|
|
237
|
+
"store.index": "getmeili/meilisearch:v1.53",
|
|
237
238
|
ai: LLAMA_CPP_IMAGE,
|
|
239
|
+
proxy: "caddy:2-alpine",
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/** Default `images.proxy` pins by wizard id (excluding `none`). */
|
|
243
|
+
export const PROXY_IMAGES: Readonly<Record<"caddy" | "traefik" | "nginx", string>> = {
|
|
244
|
+
caddy: "caddy:2-alpine",
|
|
245
|
+
traefik: "traefik:v3.7",
|
|
246
|
+
nginx: "nginx:1.31-alpine",
|
|
238
247
|
};
|
|
@@ -29,6 +29,7 @@ describe("materializeLocalOkengineDependency", () => {
|
|
|
29
29
|
workspaces?: unknown;
|
|
30
30
|
peerDependencies?: Record<string, string>;
|
|
31
31
|
exports?: Record<string, string>;
|
|
32
|
+
trustedDependencies?: readonly string[];
|
|
32
33
|
};
|
|
33
34
|
expect(pkg.name).toBe("okengine");
|
|
34
35
|
expect(pkg.devDependencies).toBeUndefined();
|
|
@@ -40,6 +41,7 @@ describe("materializeLocalOkengineDependency", () => {
|
|
|
40
41
|
expect(pkg.dependencies?.["drizzle-kit"]).toBe("1.0.0-rc.4");
|
|
41
42
|
expect(pkg.dependencies?.["zod"]).toBeDefined();
|
|
42
43
|
expect(pkg.exports?.["./config"]).toBe("./src/config/index.ts");
|
|
44
|
+
expect(pkg.trustedDependencies).toContain("@duckdb/node-api");
|
|
43
45
|
|
|
44
46
|
// Real copies — Bun file: install drops directory symlinks.
|
|
45
47
|
const src = join(stage, "src");
|
package/src/local-okengine.ts
CHANGED
|
@@ -33,6 +33,7 @@ type ConsumerOkenginePackageJson = {
|
|
|
33
33
|
readonly peerDependencies?: Record<string, string>;
|
|
34
34
|
readonly peerDependenciesMeta?: Record<string, unknown>;
|
|
35
35
|
readonly engines?: unknown;
|
|
36
|
+
readonly trustedDependencies?: readonly string[];
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -92,6 +93,8 @@ export function materializeLocalOkengineDependency(localOkengineRoot: string): s
|
|
|
92
93
|
...(raw["exports"] !== undefined ? { exports: raw["exports"] } : {}),
|
|
93
94
|
...(Object.keys(stageDependencies).length > 0 ? { dependencies: stageDependencies } : {}),
|
|
94
95
|
...(raw["engines"] !== undefined ? { engines: raw["engines"] } : {}),
|
|
96
|
+
// Bun blocks native postinstalls unless listed — DuckDB runs queries need it.
|
|
97
|
+
trustedDependencies: ["@duckdb/node-api"],
|
|
95
98
|
};
|
|
96
99
|
|
|
97
100
|
const stage = localOkengineStageDir();
|
package/src/scaffold.ts
CHANGED
|
@@ -16,13 +16,14 @@ import { basename, join, relative, resolve } from "node:path";
|
|
|
16
16
|
import { pathToFileURL } from "node:url";
|
|
17
17
|
import { resolveLocalOkengineRoot, resolveTemplateDir, type TemplateId } from "./templates.ts";
|
|
18
18
|
import { agentsMdContent } from "./agents-md.ts";
|
|
19
|
-
import type { CreateDefaults } from "./create-defaults.ts";
|
|
19
|
+
import type { CreateDefaults, CreateProxyId } from "./create-defaults.ts";
|
|
20
20
|
import { DEFAULT_IMAGES, TEMPLATE_DEV } from "./drivers-catalog.ts";
|
|
21
21
|
import { applyLocalesToProject } from "./locales.ts";
|
|
22
22
|
import {
|
|
23
23
|
DEFAULT_SQL_DRIVER,
|
|
24
24
|
applyCreateAnswers,
|
|
25
25
|
applyPgDogToConfig,
|
|
26
|
+
applyProxyToConfig,
|
|
26
27
|
extractImages,
|
|
27
28
|
sanitizeProjectName,
|
|
28
29
|
shouldSkipTemplatePath,
|
|
@@ -64,6 +65,11 @@ export type ScaffoldOptions = {
|
|
|
64
65
|
* {@link CreateDefaults.pgdog} or `false`.
|
|
65
66
|
*/
|
|
66
67
|
readonly pgdog?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Pin `images.proxy`. When omitted, uses {@link CreateDefaults.proxy} or
|
|
70
|
+
* `none`.
|
|
71
|
+
*/
|
|
72
|
+
readonly proxy?: CreateProxyId;
|
|
67
73
|
};
|
|
68
74
|
|
|
69
75
|
/** Result of a successful scaffold. */
|
|
@@ -82,6 +88,8 @@ export type ScaffoldResult = {
|
|
|
82
88
|
readonly locales: readonly string[];
|
|
83
89
|
/** Whether `images.pgdog` was pinned. */
|
|
84
90
|
readonly pgdog: boolean;
|
|
91
|
+
/** Proxy wizard id applied (`none` = unset). */
|
|
92
|
+
readonly proxy: CreateProxyId;
|
|
85
93
|
/** Relative paths written (POSIX), sorted. */
|
|
86
94
|
readonly files: readonly string[];
|
|
87
95
|
};
|
|
@@ -178,10 +186,13 @@ export async function scaffold(options: ScaffoldOptions): Promise<ScaffoldResult
|
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
const pgdog = options.pgdog ?? createDefaults?.pgdog ?? false;
|
|
189
|
+
const proxy = options.proxy ?? createDefaults?.proxy ?? "none";
|
|
181
190
|
const configPath = join(targetDir, "oke.config.ts");
|
|
182
191
|
if (existsSync(configPath)) {
|
|
192
|
+
let next = readFileSync(configPath, "utf8");
|
|
193
|
+
next = applyPgDogToConfig(next, pgdog);
|
|
194
|
+
next = applyProxyToConfig(next, proxy);
|
|
183
195
|
const prev = readFileSync(configPath, "utf8");
|
|
184
|
-
const next = applyPgDogToConfig(prev, pgdog);
|
|
185
196
|
if (next !== prev) writeFileSync(configPath, next, "utf8");
|
|
186
197
|
}
|
|
187
198
|
|
|
@@ -204,6 +215,7 @@ export async function scaffold(options: ScaffoldOptions): Promise<ScaffoldResult
|
|
|
204
215
|
...(createDefaults !== undefined ? { createDefaults } : {}),
|
|
205
216
|
locales,
|
|
206
217
|
pgdog,
|
|
218
|
+
proxy,
|
|
207
219
|
files: written,
|
|
208
220
|
};
|
|
209
221
|
} catch (e) {
|
|
@@ -393,7 +405,7 @@ services:
|
|
|
393
405
|
ports:
|
|
394
406
|
- "6530:6530"
|
|
395
407
|
env_file:
|
|
396
|
-
-
|
|
408
|
+
- ../.env.local
|
|
397
409
|
depends_on:
|
|
398
410
|
store-sql:
|
|
399
411
|
condition: service_healthy
|
|
@@ -427,7 +439,7 @@ services:
|
|
|
427
439
|
networks:
|
|
428
440
|
- oke
|
|
429
441
|
env_file:
|
|
430
|
-
-
|
|
442
|
+
- ../.env.local
|
|
431
443
|
environment:
|
|
432
444
|
POSTGRES_USER: \${OKE_STORE_SQL_USER}
|
|
433
445
|
POSTGRES_PASSWORD: \${OKE_STORE_SQL_PASSWORD}
|
|
@@ -453,7 +465,7 @@ services:
|
|
|
453
465
|
networks:
|
|
454
466
|
- oke
|
|
455
467
|
env_file:
|
|
456
|
-
-
|
|
468
|
+
- ../.env.local
|
|
457
469
|
command:
|
|
458
470
|
- sh
|
|
459
471
|
- -c
|