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/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";
|
|
@@ -39,6 +42,7 @@ import {
|
|
|
39
42
|
} from "./ai-setup/from-pref.ts";
|
|
40
43
|
import { askAiSetup } from "./ai-setup/prompts.ts";
|
|
41
44
|
import { askCustomizeFlow } from "./customize-flow.ts";
|
|
45
|
+
import { recommendedDefaults } from "./drivers-catalog.ts";
|
|
42
46
|
import { parseExtraLocales } from "./locales.ts";
|
|
43
47
|
import {
|
|
44
48
|
scaffold,
|
|
@@ -89,6 +93,11 @@ export type CliArgs = {
|
|
|
89
93
|
* `undefined` = ask (interactive) / off (`--yes`).
|
|
90
94
|
*/
|
|
91
95
|
readonly pgdog: boolean | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Proxy pin from CLI.
|
|
98
|
+
* `undefined` = ask (interactive) / none (`--yes`).
|
|
99
|
+
*/
|
|
100
|
+
readonly proxy: CreateProxyId | undefined;
|
|
92
101
|
readonly targetDir: string | undefined;
|
|
93
102
|
};
|
|
94
103
|
|
|
@@ -152,6 +161,8 @@ export type InteractiveAnswers = {
|
|
|
152
161
|
readonly locales: readonly string[];
|
|
153
162
|
/** Pin PgDog in front of Postgres. */
|
|
154
163
|
readonly pgdog: boolean;
|
|
164
|
+
/** Pin `images.proxy` (`none` = unset). */
|
|
165
|
+
readonly proxy: CreateProxyId;
|
|
155
166
|
};
|
|
156
167
|
|
|
157
168
|
/**
|
|
@@ -170,6 +181,8 @@ export type ScaffoldCallArgs = {
|
|
|
170
181
|
readonly locales?: readonly string[];
|
|
171
182
|
/** Pin PgDog in front of Postgres. */
|
|
172
183
|
readonly pgdog?: boolean;
|
|
184
|
+
/** Pin `images.proxy`. */
|
|
185
|
+
readonly proxy?: CreateProxyId;
|
|
173
186
|
};
|
|
174
187
|
|
|
175
188
|
/**
|
|
@@ -190,6 +203,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
190
203
|
let ai: AiCliMode = "prompt";
|
|
191
204
|
let locales: readonly string[] = [];
|
|
192
205
|
let pgdog: boolean | undefined;
|
|
206
|
+
let proxy: CreateProxyId | undefined;
|
|
193
207
|
let targetDir: string | undefined;
|
|
194
208
|
|
|
195
209
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -301,6 +315,26 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
301
315
|
pgdog = false;
|
|
302
316
|
continue;
|
|
303
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
|
+
}
|
|
304
338
|
if (a.startsWith("-")) {
|
|
305
339
|
throw new Error(`create-oke: unknown option ${a}`);
|
|
306
340
|
}
|
|
@@ -317,6 +351,11 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
317
351
|
if (argv.includes("--pgdog") && argv.includes("--no-pgdog")) {
|
|
318
352
|
throw new Error("create-oke: use either --pgdog or --no-pgdog, not both");
|
|
319
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
|
+
}
|
|
320
359
|
|
|
321
360
|
if (name !== undefined) {
|
|
322
361
|
targetDir = resolve(name);
|
|
@@ -335,6 +374,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
335
374
|
ai,
|
|
336
375
|
locales,
|
|
337
376
|
pgdog,
|
|
377
|
+
proxy,
|
|
338
378
|
targetDir,
|
|
339
379
|
};
|
|
340
380
|
}
|
|
@@ -353,9 +393,9 @@ export function formatCdPath(targetDir: string): string {
|
|
|
353
393
|
/**
|
|
354
394
|
* Post-scaffold next-steps block — shared by interactive and flag-driven paths.
|
|
355
395
|
*
|
|
356
|
-
* @param result - Successful scaffold result
|
|
396
|
+
* @param result - Successful scaffold result (only `label` + `targetDir` are read)
|
|
357
397
|
*/
|
|
358
|
-
export function nextStepsText(result: ScaffoldResult): string {
|
|
398
|
+
export function nextStepsText(result: Pick<ScaffoldResult, "label" | "targetDir">): string {
|
|
359
399
|
return `
|
|
360
400
|
Scaffolded ${result.label} → ${result.targetDir}
|
|
361
401
|
|
|
@@ -365,6 +405,7 @@ Next steps:
|
|
|
365
405
|
bun install
|
|
366
406
|
oke schema generate # system stubs → .oke/schema/oke.ts (also runs on db push)
|
|
367
407
|
oke dev # app :6530 · Console :6533 · MCP :6535
|
|
408
|
+
bun run web # Vite SPA — proxies /notes · /health to the app
|
|
368
409
|
|
|
369
410
|
Docs: ${docsUrl("/docs")}
|
|
370
411
|
`;
|
|
@@ -399,6 +440,8 @@ Options:
|
|
|
399
440
|
--locales <tags> Extra languages beyond English (e.g. ar or ar,fr)
|
|
400
441
|
--pgdog Pin PgDog pooling in front of Postgres
|
|
401
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)
|
|
402
445
|
-h, --help Show this help
|
|
403
446
|
|
|
404
447
|
Template:
|
|
@@ -406,9 +449,10 @@ ${templateLines}
|
|
|
406
449
|
|
|
407
450
|
On a TTY: pick standard|advanced, then recommended defaults, customize
|
|
408
451
|
(Docker-first facets; store.index with none; AI setup Recommended /
|
|
409
|
-
Customize / Off
|
|
410
|
-
|
|
411
|
-
|
|
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.
|
|
412
456
|
`;
|
|
413
457
|
}
|
|
414
458
|
|
|
@@ -453,6 +497,7 @@ export function scaffoldArgsFromCli(args: CliArgs): ScaffoldCallArgs {
|
|
|
453
497
|
aiApply: args.ai === "force" ? nonInteractiveAiApply("llama-cpp") : null,
|
|
454
498
|
locales: args.locales,
|
|
455
499
|
pgdog: args.pgdog ?? false,
|
|
500
|
+
proxy: args.proxy ?? "none",
|
|
456
501
|
};
|
|
457
502
|
}
|
|
458
503
|
|
|
@@ -503,6 +548,7 @@ export function scaffoldArgsFromAnswers(
|
|
|
503
548
|
aiApply: answers.aiApply,
|
|
504
549
|
locales: answers.locales,
|
|
505
550
|
pgdog: answers.pgdog,
|
|
551
|
+
proxy: answers.proxy,
|
|
506
552
|
};
|
|
507
553
|
}
|
|
508
554
|
|
|
@@ -519,6 +565,7 @@ export type AskInteractiveFn = (partial: {
|
|
|
519
565
|
readonly template?: TemplateId;
|
|
520
566
|
readonly locales?: readonly string[];
|
|
521
567
|
readonly pgdog?: boolean;
|
|
568
|
+
readonly proxy?: CreateProxyId;
|
|
522
569
|
}) => Promise<InteractiveAnswers | null>;
|
|
523
570
|
|
|
524
571
|
/** Injectable persistence seams for tests. */
|
|
@@ -528,6 +575,52 @@ export type CreateDefaultsIo = {
|
|
|
528
575
|
readonly write: (defaults: CreateDefaults) => void;
|
|
529
576
|
};
|
|
530
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Merge wizard locales / PgDog / proxy into a create-defaults document to persist.
|
|
580
|
+
*
|
|
581
|
+
* Prefer the in-session answers (customize / reuse), else same-template
|
|
582
|
+
* previous settings, else recommended pins for the template.
|
|
583
|
+
*
|
|
584
|
+
* @param input - Template, locales, pgdog, proxy, and optional session / previous docs
|
|
585
|
+
*/
|
|
586
|
+
export function withWizardExtras(input: {
|
|
587
|
+
readonly template: TemplateId;
|
|
588
|
+
readonly locales: readonly string[];
|
|
589
|
+
readonly pgdog: boolean;
|
|
590
|
+
readonly proxy: CreateProxyId;
|
|
591
|
+
readonly session: CreateDefaults | undefined;
|
|
592
|
+
readonly previous: CreateDefaults | null;
|
|
593
|
+
}): CreateDefaults {
|
|
594
|
+
const { template, locales, pgdog, proxy, session, previous } = input;
|
|
595
|
+
const base =
|
|
596
|
+
session ??
|
|
597
|
+
(previous?.template === template ? previous : null) ??
|
|
598
|
+
recommendedDefaults("docker-ready", template);
|
|
599
|
+
return {
|
|
600
|
+
...base,
|
|
601
|
+
template,
|
|
602
|
+
locales,
|
|
603
|
+
pgdog,
|
|
604
|
+
proxy,
|
|
605
|
+
updatedAt: new Date().toISOString(),
|
|
606
|
+
};
|
|
607
|
+
}
|
|
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
|
+
|
|
531
624
|
/**
|
|
532
625
|
* Collect interactive answers via `@clack/prompts`.
|
|
533
626
|
*
|
|
@@ -544,6 +637,7 @@ export async function askInteractiveAnswers(
|
|
|
544
637
|
readonly template?: TemplateId;
|
|
545
638
|
readonly locales?: readonly string[];
|
|
546
639
|
readonly pgdog?: boolean;
|
|
640
|
+
readonly proxy?: CreateProxyId;
|
|
547
641
|
} = {},
|
|
548
642
|
io: CreateDefaultsIo = {
|
|
549
643
|
path: createDefaultsPath(),
|
|
@@ -582,6 +676,7 @@ export async function askInteractiveAnswers(
|
|
|
582
676
|
let createDefaults: CreateDefaults | undefined;
|
|
583
677
|
let aiApply: AiSetupApplyInput | null = null;
|
|
584
678
|
let persistDefaults = false;
|
|
679
|
+
let reused = false;
|
|
585
680
|
|
|
586
681
|
for (;;) {
|
|
587
682
|
const previous = io.read();
|
|
@@ -608,6 +703,7 @@ export async function askInteractiveAnswers(
|
|
|
608
703
|
continue;
|
|
609
704
|
}
|
|
610
705
|
createDefaults = prev;
|
|
706
|
+
reused = true;
|
|
611
707
|
if (prev.ai.enabled && partial.ai !== "skip") {
|
|
612
708
|
aiApply = applyInputFromAiPref(prev.ai);
|
|
613
709
|
if (!aiApply?.chatModel && prev.ai.provider && prev.ai.provider !== "mock") {
|
|
@@ -644,36 +740,66 @@ export async function askInteractiveAnswers(
|
|
|
644
740
|
aiApply = picked;
|
|
645
741
|
}
|
|
646
742
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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;
|
|
660
783
|
} else {
|
|
661
|
-
const picked = await
|
|
784
|
+
const picked = await askProxy(createDefaults?.proxy ?? "none");
|
|
662
785
|
if (picked === null) return null;
|
|
663
|
-
|
|
786
|
+
proxy = picked;
|
|
664
787
|
}
|
|
665
788
|
}
|
|
666
789
|
|
|
790
|
+
const persisted = withWizardExtras({
|
|
791
|
+
template,
|
|
792
|
+
locales,
|
|
793
|
+
pgdog,
|
|
794
|
+
proxy,
|
|
795
|
+
session: createDefaults,
|
|
796
|
+
previous: io.read(),
|
|
797
|
+
});
|
|
667
798
|
if (createDefaults) {
|
|
668
|
-
createDefaults =
|
|
669
|
-
...createDefaults,
|
|
670
|
-
locales,
|
|
671
|
-
pgdog,
|
|
672
|
-
updatedAt: new Date().toISOString(),
|
|
673
|
-
};
|
|
799
|
+
createDefaults = persisted;
|
|
674
800
|
}
|
|
675
|
-
|
|
676
|
-
|
|
801
|
+
io.write(persisted);
|
|
802
|
+
if (persistDefaults) {
|
|
677
803
|
note(`Saved globally for next projects → ${io.path}`, "Defaults");
|
|
678
804
|
}
|
|
679
805
|
|
|
@@ -694,6 +820,7 @@ export async function askInteractiveAnswers(
|
|
|
694
820
|
aiApply,
|
|
695
821
|
locales,
|
|
696
822
|
pgdog,
|
|
823
|
+
proxy,
|
|
697
824
|
};
|
|
698
825
|
}
|
|
699
826
|
|
|
@@ -744,6 +871,27 @@ async function askPgDog(initial: boolean = false): Promise<boolean | null> {
|
|
|
744
871
|
return Boolean(value);
|
|
745
872
|
}
|
|
746
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
|
+
|
|
747
895
|
/**
|
|
748
896
|
* Provider passed to `oke ai setup` — prefer native Ollama when either env uses it.
|
|
749
897
|
*
|
|
@@ -871,8 +1019,9 @@ async function runInteractive(
|
|
|
871
1019
|
agentsMd: args.agentsMd,
|
|
872
1020
|
sqlDriver: args.sqlDriverExplicit ? args.sqlDriver : undefined,
|
|
873
1021
|
ai: args.ai,
|
|
874
|
-
locales: args.locales,
|
|
1022
|
+
...(args.locales.length > 0 ? { locales: args.locales } : {}),
|
|
875
1023
|
...(args.pgdog !== undefined ? { pgdog: args.pgdog } : {}),
|
|
1024
|
+
...(args.proxy !== undefined ? { proxy: args.proxy } : {}),
|
|
876
1025
|
});
|
|
877
1026
|
if (answers === null) {
|
|
878
1027
|
cancel("Cancelled.");
|
|
@@ -911,6 +1060,7 @@ async function runScaffold(
|
|
|
911
1060
|
aiApply,
|
|
912
1061
|
locales,
|
|
913
1062
|
pgdog,
|
|
1063
|
+
proxy,
|
|
914
1064
|
interactive,
|
|
915
1065
|
install,
|
|
916
1066
|
startDev,
|
|
@@ -947,6 +1097,7 @@ async function runScaffold(
|
|
|
947
1097
|
...(createDefaults !== undefined ? { createDefaults } : {}),
|
|
948
1098
|
...(locales !== undefined ? { locales } : {}),
|
|
949
1099
|
...(pgdog !== undefined ? { pgdog } : {}),
|
|
1100
|
+
...(proxy !== undefined ? { proxy } : {}),
|
|
950
1101
|
});
|
|
951
1102
|
if (spun) spun.stop("Scaffolded.");
|
|
952
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
|
}
|
|
@@ -48,13 +48,20 @@ describe("catalog labels", () => {
|
|
|
48
48
|
|
|
49
49
|
describe("recommendedAiApply", () => {
|
|
50
50
|
test("returns llama.cpp (openai-compatible) with curated ai/ model", () => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
const prev = process.env.OKE_AI_URL;
|
|
52
|
+
delete process.env.OKE_AI_URL;
|
|
53
|
+
try {
|
|
54
|
+
const apply = recommendedAiApply();
|
|
55
|
+
expect(apply.driver).toBe("openai-compatible");
|
|
56
|
+
expect(apply.chatModel).toBe("granite3.3:2b");
|
|
57
|
+
expect(apply.baseUrl).toContain("8080");
|
|
58
|
+
expect(apply.image).toContain("llama.cpp");
|
|
59
|
+
expect(apply.image).not.toContain("latest");
|
|
60
|
+
expect(apply.visionModel).toBeNull();
|
|
61
|
+
} finally {
|
|
62
|
+
if (prev !== undefined) process.env.OKE_AI_URL = prev;
|
|
63
|
+
else delete process.env.OKE_AI_URL;
|
|
64
|
+
}
|
|
58
65
|
});
|
|
59
66
|
});
|
|
60
67
|
|
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();
|