create-oke 0.10.2 → 0.11.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/README.md +2 -2
- package/package.json +2 -2
- package/src/ai-setup/apply.ts +123 -31
- package/src/ai-setup/catalog.ts +3 -3
- package/src/ai-setup/from-pref.ts +1 -1
- package/src/ai-setup/prompts.ts +1 -1
- package/src/cli.test.ts +183 -142
- package/src/cli.ts +190 -32
- package/src/create-defaults.test.ts +21 -15
- package/src/create-defaults.ts +33 -13
- package/src/customize-flow.test.ts +30 -35
- package/src/customize-flow.ts +68 -229
- package/src/drivers-catalog.ts +42 -48
- package/src/local-okengine.test.ts +50 -41
- package/src/local-okengine.ts +28 -14
- package/src/locales.test.ts +128 -0
- package/src/locales.ts +321 -0
- package/src/scaffold.ts +277 -33
- package/src/templates.ts +2 -8
- package/src/transform.test.ts +102 -38
- package/src/transform.ts +378 -132
- package/templates/advanced/.env.example +23 -3
- package/templates/advanced/README.md +1 -1
- package/templates/advanced/drizzle.config.ts +10 -13
- package/templates/advanced/oke.config.ts +21 -62
- package/templates/advanced/package.json +3 -1
- package/templates/advanced/src/app.ts +3 -10
- package/templates/advanced/src/core.ts +58 -0
- package/templates/advanced/src/db/schema.decl.ts +1 -1
- package/templates/advanced/src/db/seed/index.ts +2 -2
- package/templates/advanced/src/flows/notes/index.ts +3 -15
- package/templates/advanced/src/locales/index.ts +6 -0
- package/templates/advanced/tests/advanced.test.ts +1 -1
- package/templates/advanced/tsconfig.json +3 -0
- package/templates/standard/.env.example +23 -3
- package/templates/standard/README.md +1 -1
- package/templates/standard/drizzle.config.ts +10 -13
- package/templates/standard/oke.config.ts +18 -60
- package/templates/standard/package.json +3 -1
- package/templates/standard/src/app.ts +3 -10
- package/templates/standard/src/core.ts +55 -0
- package/templates/standard/src/db/schema.decl.ts +1 -1
- package/templates/standard/src/db/seed/index.ts +2 -2
- package/templates/standard/src/flows/notes/index.ts +3 -12
- package/templates/standard/src/locales/index.ts +6 -0
- package/templates/standard/tests/standard.test.ts +1 -1
- package/templates/standard/tsconfig.json +3 -0
- package/templates/advanced/src/core/channels.ts +0 -13
- package/templates/advanced/src/core/gates.ts +0 -12
- package/templates/advanced/src/core/index.ts +0 -12
- package/templates/advanced/src/core/store.ts +0 -8
- package/templates/advanced/src/core/vault.ts +0 -7
- package/templates/advanced/src/locales/ar.ts +0 -18
- package/templates/standard/src/core/channels.ts +0 -13
- package/templates/standard/src/core/gates.ts +0 -12
- package/templates/standard/src/core/index.ts +0 -12
- package/templates/standard/src/core/store.ts +0 -5
- package/templates/standard/src/core/vault.ts +0 -7
- package/templates/standard/src/locales/ar.ts +0 -18
package/src/cli.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
} from "./ai-setup/from-pref.ts";
|
|
40
40
|
import { askAiSetup } from "./ai-setup/prompts.ts";
|
|
41
41
|
import { askCustomizeFlow } from "./customize-flow.ts";
|
|
42
|
+
import { parseExtraLocales } from "./locales.ts";
|
|
42
43
|
import {
|
|
43
44
|
scaffold,
|
|
44
45
|
targetDirectoryBlockReason,
|
|
@@ -65,7 +66,7 @@ export type CliArgs = {
|
|
|
65
66
|
readonly template: TemplateId;
|
|
66
67
|
/** True when `--template` / `-t` was present on the argv. */
|
|
67
68
|
readonly templateExplicit: boolean;
|
|
68
|
-
/** Store SQL driver (`
|
|
69
|
+
/** Store SQL driver (`postgres` default). */
|
|
69
70
|
readonly sqlDriver: SqlDriverId;
|
|
70
71
|
/** True when `--sql` was present on the argv. */
|
|
71
72
|
readonly sqlDriverExplicit: boolean;
|
|
@@ -81,6 +82,13 @@ export type CliArgs = {
|
|
|
81
82
|
readonly agentsMd: boolean;
|
|
82
83
|
/** AI setup after install. */
|
|
83
84
|
readonly ai: AiCliMode;
|
|
85
|
+
/** Extra locales beyond English (`--locales ar,fr`). */
|
|
86
|
+
readonly locales: readonly string[];
|
|
87
|
+
/**
|
|
88
|
+
* PgDog pooling flag from CLI.
|
|
89
|
+
* `undefined` = ask (interactive) / off (`--yes`).
|
|
90
|
+
*/
|
|
91
|
+
readonly pgdog: boolean | undefined;
|
|
84
92
|
readonly targetDir: string | undefined;
|
|
85
93
|
};
|
|
86
94
|
|
|
@@ -111,8 +119,8 @@ export function defaultsBranchOptions(
|
|
|
111
119
|
label: "Yes, use recommended defaults",
|
|
112
120
|
hint:
|
|
113
121
|
template === "advanced"
|
|
114
|
-
? "Notes ·
|
|
115
|
-
: "Notes ·
|
|
122
|
+
? "Notes · Docker-first pins · store.index"
|
|
123
|
+
: "Notes · Docker-first pins (postgres · redis · s3)",
|
|
116
124
|
},
|
|
117
125
|
];
|
|
118
126
|
if (hasPreviousForTemplate) {
|
|
@@ -125,10 +133,7 @@ export function defaultsBranchOptions(
|
|
|
125
133
|
options.push({
|
|
126
134
|
value: "customize",
|
|
127
135
|
label: "No, customize settings",
|
|
128
|
-
hint:
|
|
129
|
-
template === "standard"
|
|
130
|
-
? "local|docker · SQL · optional AI"
|
|
131
|
-
: "local|docker · all facets · optional AI",
|
|
136
|
+
hint: template === "standard" ? "dev/prod SQL · optional AI" : "dev/prod facets · optional AI",
|
|
132
137
|
});
|
|
133
138
|
return options;
|
|
134
139
|
}
|
|
@@ -143,6 +148,10 @@ export type InteractiveAnswers = {
|
|
|
143
148
|
readonly createDefaults: CreateDefaults | undefined;
|
|
144
149
|
/** Model / env apply payload — written after scaffold, before install. */
|
|
145
150
|
readonly aiApply: AiSetupApplyInput | null;
|
|
151
|
+
/** Extra locales beyond English (empty = English only). */
|
|
152
|
+
readonly locales: readonly string[];
|
|
153
|
+
/** Pin PgDog in front of Postgres. */
|
|
154
|
+
readonly pgdog: boolean;
|
|
146
155
|
};
|
|
147
156
|
|
|
148
157
|
/**
|
|
@@ -155,8 +164,12 @@ export type ScaffoldCallArgs = {
|
|
|
155
164
|
readonly agentsMd: boolean;
|
|
156
165
|
readonly sqlDriver: SqlDriverId;
|
|
157
166
|
readonly createDefaults?: CreateDefaults;
|
|
158
|
-
/** Apply `src/core
|
|
167
|
+
/** Apply AI models in `src/core.ts` + `.env.local` after scaffold (before install). */
|
|
159
168
|
readonly aiApply?: AiSetupApplyInput | null;
|
|
169
|
+
/** Extra locales beyond English. */
|
|
170
|
+
readonly locales?: readonly string[];
|
|
171
|
+
/** Pin PgDog in front of Postgres. */
|
|
172
|
+
readonly pgdog?: boolean;
|
|
160
173
|
};
|
|
161
174
|
|
|
162
175
|
/**
|
|
@@ -175,6 +188,8 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
175
188
|
let install: boolean | undefined;
|
|
176
189
|
let agentsMd = true;
|
|
177
190
|
let ai: AiCliMode = "prompt";
|
|
191
|
+
let locales: readonly string[] = [];
|
|
192
|
+
let pgdog: boolean | undefined;
|
|
178
193
|
let targetDir: string | undefined;
|
|
179
194
|
|
|
180
195
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -259,6 +274,33 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
259
274
|
templateExplicit = true;
|
|
260
275
|
continue;
|
|
261
276
|
}
|
|
277
|
+
if (a === "--locales") {
|
|
278
|
+
const next = argv[++i];
|
|
279
|
+
if (!next) throw new Error("create-oke: --locales requires a value (e.g. ar or ar,fr)");
|
|
280
|
+
const parsed = parseExtraLocales(next);
|
|
281
|
+
if (parsed === null) {
|
|
282
|
+
throw new Error("create-oke: --locales must be BCP-47 tags like ar or ar,fr");
|
|
283
|
+
}
|
|
284
|
+
locales = parsed;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (a.startsWith("--locales=")) {
|
|
288
|
+
const value = a.slice("--locales=".length);
|
|
289
|
+
const parsed = parseExtraLocales(value);
|
|
290
|
+
if (parsed === null) {
|
|
291
|
+
throw new Error("create-oke: --locales must be BCP-47 tags like ar or ar,fr");
|
|
292
|
+
}
|
|
293
|
+
locales = parsed;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (a === "--pgdog") {
|
|
297
|
+
pgdog = true;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (a === "--no-pgdog") {
|
|
301
|
+
pgdog = false;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
262
304
|
if (a.startsWith("-")) {
|
|
263
305
|
throw new Error(`create-oke: unknown option ${a}`);
|
|
264
306
|
}
|
|
@@ -272,6 +314,9 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
272
314
|
if (argv.includes("--install") && argv.includes("--no-install")) {
|
|
273
315
|
throw new Error("create-oke: use either --install or --no-install, not both");
|
|
274
316
|
}
|
|
317
|
+
if (argv.includes("--pgdog") && argv.includes("--no-pgdog")) {
|
|
318
|
+
throw new Error("create-oke: use either --pgdog or --no-pgdog, not both");
|
|
319
|
+
}
|
|
275
320
|
|
|
276
321
|
if (name !== undefined) {
|
|
277
322
|
targetDir = resolve(name);
|
|
@@ -288,6 +333,8 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
288
333
|
install,
|
|
289
334
|
agentsMd,
|
|
290
335
|
ai,
|
|
336
|
+
locales,
|
|
337
|
+
pgdog,
|
|
291
338
|
targetDir,
|
|
292
339
|
};
|
|
293
340
|
}
|
|
@@ -316,7 +363,8 @@ Next steps:
|
|
|
316
363
|
|
|
317
364
|
cd ${formatCdPath(result.targetDir)}
|
|
318
365
|
bun install
|
|
319
|
-
oke
|
|
366
|
+
oke schema generate # system stubs → .oke/schema/oke.ts (also runs on db push)
|
|
367
|
+
oke dev # app :6530 · Console :6533 · MCP :6535
|
|
320
368
|
|
|
321
369
|
Docs: ${docsUrl("/docs")}
|
|
322
370
|
`;
|
|
@@ -339,9 +387,8 @@ Usage:
|
|
|
339
387
|
|
|
340
388
|
Options:
|
|
341
389
|
-t, --template <id> Clean starter (default: ${DEFAULT_TEMPLATE})
|
|
342
|
-
--sql <id>
|
|
343
|
-
|
|
344
|
-
--sql postgres pins store.sql local/docker/prod to postgres.
|
|
390
|
+
--sql <id> Store SQL dialect: ${SQL_DRIVERS.join("|")} (default)
|
|
391
|
+
Pins store.sql dev/prod (test stays pglite).
|
|
345
392
|
-y, --yes No prompts; defaults + bun install (no oke dev)
|
|
346
393
|
--install Run bun install after scaffold
|
|
347
394
|
--no-install Skip bun install
|
|
@@ -349,15 +396,19 @@ Options:
|
|
|
349
396
|
--no-agents-md Skip AGENTS.md
|
|
350
397
|
--ai Configure AI in the wizard (models before install)
|
|
351
398
|
--no-ai, --ai skip Skip AI configuration
|
|
399
|
+
--locales <tags> Extra languages beyond English (e.g. ar or ar,fr)
|
|
400
|
+
--pgdog Pin PgDog pooling in front of Postgres
|
|
401
|
+
--no-pgdog Skip PgDog (default for --yes)
|
|
352
402
|
-h, --help Show this help
|
|
353
403
|
|
|
354
404
|
Template:
|
|
355
405
|
${templateLines}
|
|
356
406
|
|
|
357
407
|
On a TTY: pick standard|advanced, then recommended defaults, customize
|
|
358
|
-
(
|
|
359
|
-
Customize / Off;
|
|
360
|
-
or reuse when saved for that template.
|
|
408
|
+
(Docker-first facets; store.index with none; AI setup Recommended /
|
|
409
|
+
Customize / Off; saved to ~/.oke/create-defaults.json),
|
|
410
|
+
optional extra locales + PgDog pooling, or reuse when saved for that template.
|
|
411
|
+
Non-TTY / --yes stay English-only / no PgDog unless --locales / --pgdog.
|
|
361
412
|
`;
|
|
362
413
|
}
|
|
363
414
|
|
|
@@ -400,6 +451,8 @@ export function scaffoldArgsFromCli(args: CliArgs): ScaffoldCallArgs {
|
|
|
400
451
|
agentsMd: args.agentsMd,
|
|
401
452
|
sqlDriver: args.sqlDriver,
|
|
402
453
|
aiApply: args.ai === "force" ? nonInteractiveAiApply("llama-cpp") : null,
|
|
454
|
+
locales: args.locales,
|
|
455
|
+
pgdog: args.pgdog ?? false,
|
|
403
456
|
};
|
|
404
457
|
}
|
|
405
458
|
|
|
@@ -426,8 +479,7 @@ export function scaffoldArgsFromAnswers(
|
|
|
426
479
|
store: {
|
|
427
480
|
...createDefaults.drivers.store,
|
|
428
481
|
sql: {
|
|
429
|
-
|
|
430
|
-
docker: "postgres",
|
|
482
|
+
dev: "postgres",
|
|
431
483
|
test: createDefaults.drivers.store.sql.test,
|
|
432
484
|
prod: "postgres",
|
|
433
485
|
},
|
|
@@ -437,7 +489,7 @@ export function scaffoldArgsFromAnswers(
|
|
|
437
489
|
sqlDriver = "postgres";
|
|
438
490
|
} else if (!createDefaults && sqlDriverOverride) {
|
|
439
491
|
sqlDriver = sqlDriverOverride;
|
|
440
|
-
} else if (createDefaults?.drivers.store.sql.
|
|
492
|
+
} else if (createDefaults?.drivers.store.sql.dev === "postgres") {
|
|
441
493
|
sqlDriver = "postgres";
|
|
442
494
|
}
|
|
443
495
|
|
|
@@ -449,6 +501,8 @@ export function scaffoldArgsFromAnswers(
|
|
|
449
501
|
sqlDriver,
|
|
450
502
|
...(createDefaults !== undefined ? { createDefaults } : {}),
|
|
451
503
|
aiApply: answers.aiApply,
|
|
504
|
+
locales: answers.locales,
|
|
505
|
+
pgdog: answers.pgdog,
|
|
452
506
|
};
|
|
453
507
|
}
|
|
454
508
|
|
|
@@ -463,6 +517,8 @@ export type AskInteractiveFn = (partial: {
|
|
|
463
517
|
readonly sqlDriver?: SqlDriverId;
|
|
464
518
|
readonly ai?: AiCliMode;
|
|
465
519
|
readonly template?: TemplateId;
|
|
520
|
+
readonly locales?: readonly string[];
|
|
521
|
+
readonly pgdog?: boolean;
|
|
466
522
|
}) => Promise<InteractiveAnswers | null>;
|
|
467
523
|
|
|
468
524
|
/** Injectable persistence seams for tests. */
|
|
@@ -486,6 +542,8 @@ export async function askInteractiveAnswers(
|
|
|
486
542
|
readonly sqlDriver?: SqlDriverId;
|
|
487
543
|
readonly ai?: AiCliMode;
|
|
488
544
|
readonly template?: TemplateId;
|
|
545
|
+
readonly locales?: readonly string[];
|
|
546
|
+
readonly pgdog?: boolean;
|
|
489
547
|
} = {},
|
|
490
548
|
io: CreateDefaultsIo = {
|
|
491
549
|
path: createDefaultsPath(),
|
|
@@ -523,6 +581,7 @@ export async function askInteractiveAnswers(
|
|
|
523
581
|
|
|
524
582
|
let createDefaults: CreateDefaults | undefined;
|
|
525
583
|
let aiApply: AiSetupApplyInput | null = null;
|
|
584
|
+
let persistDefaults = false;
|
|
526
585
|
|
|
527
586
|
for (;;) {
|
|
528
587
|
const previous = io.read();
|
|
@@ -570,8 +629,7 @@ export async function askInteractiveAnswers(
|
|
|
570
629
|
if (customized === null) return null;
|
|
571
630
|
if (customized === WIZARD_BACK) continue;
|
|
572
631
|
createDefaults = customized;
|
|
573
|
-
|
|
574
|
-
note(`Saved globally for next projects → ${io.path}`, "Defaults");
|
|
632
|
+
persistDefaults = true;
|
|
575
633
|
if (customized.ai.enabled && partial.ai !== "skip") {
|
|
576
634
|
aiApply = applyInputFromAiPref(customized.ai);
|
|
577
635
|
}
|
|
@@ -586,6 +644,39 @@ export async function askInteractiveAnswers(
|
|
|
586
644
|
aiApply = picked;
|
|
587
645
|
}
|
|
588
646
|
|
|
647
|
+
const locales = await askExtraLocales(partial.locales ?? createDefaults?.locales ?? []);
|
|
648
|
+
if (locales === null) return null;
|
|
649
|
+
|
|
650
|
+
const usesPostgres = createDefaults
|
|
651
|
+
? createDefaults.drivers.store.sql.dev === "postgres" ||
|
|
652
|
+
createDefaults.drivers.store.sql.prod === "postgres" ||
|
|
653
|
+
createDefaults.drivers.store.sql.dev === "pgvector" ||
|
|
654
|
+
createDefaults.drivers.store.sql.prod === "pgvector"
|
|
655
|
+
: true;
|
|
656
|
+
let pgdog = false;
|
|
657
|
+
if (usesPostgres) {
|
|
658
|
+
if (partial.pgdog !== undefined) {
|
|
659
|
+
pgdog = partial.pgdog;
|
|
660
|
+
} else {
|
|
661
|
+
const picked = await askPgDog(createDefaults?.pgdog ?? false);
|
|
662
|
+
if (picked === null) return null;
|
|
663
|
+
pgdog = picked;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (createDefaults) {
|
|
668
|
+
createDefaults = {
|
|
669
|
+
...createDefaults,
|
|
670
|
+
locales,
|
|
671
|
+
pgdog,
|
|
672
|
+
updatedAt: new Date().toISOString(),
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
if (persistDefaults && createDefaults) {
|
|
676
|
+
io.write(createDefaults);
|
|
677
|
+
note(`Saved globally for next projects → ${io.path}`, "Defaults");
|
|
678
|
+
}
|
|
679
|
+
|
|
589
680
|
const agentsMd = partial.agentsMd ?? true;
|
|
590
681
|
|
|
591
682
|
const installAndRunValue = await confirm({
|
|
@@ -601,25 +692,77 @@ export async function askInteractiveAnswers(
|
|
|
601
692
|
agentsMd,
|
|
602
693
|
createDefaults,
|
|
603
694
|
aiApply,
|
|
695
|
+
locales,
|
|
696
|
+
pgdog,
|
|
604
697
|
};
|
|
605
698
|
}
|
|
606
699
|
|
|
700
|
+
/**
|
|
701
|
+
* Ask whether to add languages beyond English, then collect BCP-47 tags.
|
|
702
|
+
*
|
|
703
|
+
* @param initialExtra - Prefill from reused create-defaults
|
|
704
|
+
* @returns Extra locale tags, or `null` on cancel
|
|
705
|
+
*/
|
|
706
|
+
async function askExtraLocales(
|
|
707
|
+
initialExtra: readonly string[] = [],
|
|
708
|
+
): Promise<readonly string[] | null> {
|
|
709
|
+
const addMore = await confirm({
|
|
710
|
+
message: "Add more languages besides English?",
|
|
711
|
+
initialValue: initialExtra.length > 0,
|
|
712
|
+
});
|
|
713
|
+
if (isCancel(addMore)) return null;
|
|
714
|
+
if (!addMore) return [];
|
|
715
|
+
|
|
716
|
+
const raw = await text({
|
|
717
|
+
message: "Language codes (comma-separated)",
|
|
718
|
+
placeholder: "ar",
|
|
719
|
+
initialValue: initialExtra.length > 0 ? initialExtra.join(",") : "ar",
|
|
720
|
+
validate: (value) => {
|
|
721
|
+
if (!value?.trim()) return "Enter at least one code (e.g. ar or ar,fr)";
|
|
722
|
+
if (parseExtraLocales(value) === null) {
|
|
723
|
+
return "Use BCP-47 tags like ar or ar,fr";
|
|
724
|
+
}
|
|
725
|
+
return undefined;
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
if (isCancel(raw)) return null;
|
|
729
|
+
return parseExtraLocales(String(raw)) ?? [];
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Ask whether to put PgDog in front of Postgres.
|
|
734
|
+
*
|
|
735
|
+
* @param initial - Prefill from reused create-defaults
|
|
736
|
+
* @returns `true` to pin PgDog, or `null` on cancel
|
|
737
|
+
*/
|
|
738
|
+
async function askPgDog(initial: boolean = false): Promise<boolean | null> {
|
|
739
|
+
const value = await confirm({
|
|
740
|
+
message: "Add PgDog connection pooling in front of Postgres?",
|
|
741
|
+
initialValue: initial,
|
|
742
|
+
});
|
|
743
|
+
if (isCancel(value)) return null;
|
|
744
|
+
return Boolean(value);
|
|
745
|
+
}
|
|
746
|
+
|
|
607
747
|
/**
|
|
608
748
|
* Provider passed to `oke ai setup` — prefer native Ollama when either env uses it.
|
|
609
749
|
*
|
|
610
|
-
* @param
|
|
750
|
+
* @param menuProvider - Menu id chosen in the wizard
|
|
611
751
|
* @param pins - Resolved driver pins
|
|
612
752
|
*/
|
|
613
|
-
export function aiSetupProviderFor(
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
if (
|
|
618
|
-
if (
|
|
619
|
-
|
|
620
|
-
if (pins.
|
|
753
|
+
export function aiSetupProviderFor(menuProvider: string, pins: EnvDriverPins): string {
|
|
754
|
+
if (menuProvider === "ollama" || pins.dev === "ollama" || pins.prod === "ollama") {
|
|
755
|
+
return "ollama";
|
|
756
|
+
}
|
|
757
|
+
if (menuProvider === "llama-cpp") return "llama-cpp";
|
|
758
|
+
if (menuProvider === "vllm" || menuProvider === "sglang") return menuProvider;
|
|
759
|
+
if (menuProvider === "mock") {
|
|
760
|
+
if (pins.dev === "anthropic" || pins.prod === "anthropic") return "anthropic";
|
|
761
|
+
if (pins.dev === "openai-compatible" || pins.prod === "openai-compatible") {
|
|
762
|
+
return "llama-cpp";
|
|
763
|
+
}
|
|
621
764
|
}
|
|
622
|
-
return
|
|
765
|
+
return menuProvider;
|
|
623
766
|
}
|
|
624
767
|
|
|
625
768
|
/**
|
|
@@ -728,6 +871,8 @@ async function runInteractive(
|
|
|
728
871
|
agentsMd: args.agentsMd,
|
|
729
872
|
sqlDriver: args.sqlDriverExplicit ? args.sqlDriver : undefined,
|
|
730
873
|
ai: args.ai,
|
|
874
|
+
locales: args.locales,
|
|
875
|
+
...(args.pgdog !== undefined ? { pgdog: args.pgdog } : {}),
|
|
731
876
|
});
|
|
732
877
|
if (answers === null) {
|
|
733
878
|
cancel("Cancelled.");
|
|
@@ -764,6 +909,8 @@ async function runScaffold(
|
|
|
764
909
|
sqlDriver,
|
|
765
910
|
createDefaults,
|
|
766
911
|
aiApply,
|
|
912
|
+
locales,
|
|
913
|
+
pgdog,
|
|
767
914
|
interactive,
|
|
768
915
|
install,
|
|
769
916
|
startDev,
|
|
@@ -791,17 +938,19 @@ async function runScaffold(
|
|
|
791
938
|
spun = spinner();
|
|
792
939
|
spun.start("Scaffolding…");
|
|
793
940
|
}
|
|
794
|
-
const result = scaffold({
|
|
941
|
+
const result = await scaffold({
|
|
795
942
|
targetDir,
|
|
796
943
|
name,
|
|
797
944
|
source,
|
|
798
945
|
writeAgentsMd: agentsMd,
|
|
799
946
|
sqlDriver,
|
|
800
947
|
...(createDefaults !== undefined ? { createDefaults } : {}),
|
|
948
|
+
...(locales !== undefined ? { locales } : {}),
|
|
949
|
+
...(pgdog !== undefined ? { pgdog } : {}),
|
|
801
950
|
});
|
|
802
951
|
if (spun) spun.stop("Scaffolded.");
|
|
803
952
|
|
|
804
|
-
// Models were chosen in the wizard — write env + src/core
|
|
953
|
+
// Models were chosen in the wizard — write env + AI models in src/core.ts before install
|
|
805
954
|
// so `--no-install` still gets a complete AI project. Preserve per-env
|
|
806
955
|
// `drivers.ai` pins from customize; flatten only when pins were never written.
|
|
807
956
|
if (runPostScaffold && aiApply) {
|
|
@@ -829,6 +978,15 @@ async function runScaffold(
|
|
|
829
978
|
return 1;
|
|
830
979
|
}
|
|
831
980
|
if (interactive) log.success("Installed.");
|
|
981
|
+
// System stubs under `.oke/schema/` — refresh after install so published
|
|
982
|
+
// create-oke (no monorepo import) still gets them; re-run after plugins.
|
|
983
|
+
const schemaOk = await runCommand(["bunx", "oke", "schema", "generate"], targetDir, {
|
|
984
|
+
inherit: false,
|
|
985
|
+
});
|
|
986
|
+
if (interactive) {
|
|
987
|
+
if (schemaOk) log.success("System schema generated (.oke/schema/oke.ts).");
|
|
988
|
+
else log.warn("oke schema generate skipped — run it after plugins / auth tables.");
|
|
989
|
+
}
|
|
832
990
|
}
|
|
833
991
|
|
|
834
992
|
const message = nextStepsText(result);
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
toCreateDefaults,
|
|
14
14
|
writeCreateDefaults,
|
|
15
15
|
} from "./create-defaults.ts";
|
|
16
|
-
import {
|
|
16
|
+
import { pinsDockerReady, recommendedDefaults } from "./drivers-catalog.ts";
|
|
17
17
|
|
|
18
18
|
describe("createDefaultsPath", () => {
|
|
19
19
|
test("joins ~/.oke/create-defaults.json", () => {
|
|
@@ -26,19 +26,21 @@ describe("parseCreateDefaults", () => {
|
|
|
26
26
|
expect(parseCreateDefaults(null)).toBeNull();
|
|
27
27
|
expect(parseCreateDefaults({ version: 2 })).toBeNull();
|
|
28
28
|
expect(parseCreateDefaults({ version: 1, profile: "nope" })).toBeNull();
|
|
29
|
+
expect(parseCreateDefaults({ version: 1, profile: "local-only" })).toBeNull();
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
test("accepts recommended shape", () => {
|
|
32
|
-
const d = recommendedDefaults("
|
|
33
|
+
const d = recommendedDefaults("docker-ready", "standard");
|
|
33
34
|
const parsed = parseCreateDefaults(d);
|
|
34
|
-
expect(parsed?.profile).toBe("
|
|
35
|
+
expect(parsed?.profile).toBe("docker-ready");
|
|
35
36
|
expect(parsed?.template).toBe("standard");
|
|
36
|
-
expect(parsed?.drivers.store.sql.
|
|
37
|
+
expect(parsed?.drivers.store.sql.dev).toBe("postgres");
|
|
38
|
+
expect(parsed?.drivers.store.sql.test).toBe("pglite");
|
|
37
39
|
expect(parsed?.ai.enabled).toBe(false);
|
|
38
40
|
});
|
|
39
41
|
|
|
40
42
|
test("older files without template default to standard", () => {
|
|
41
|
-
const d = recommendedDefaults("
|
|
43
|
+
const d = recommendedDefaults("docker-ready", "standard");
|
|
42
44
|
const { template: _t, ...rest } = d;
|
|
43
45
|
const parsed = parseCreateDefaults(rest);
|
|
44
46
|
expect(parsed?.template).toBe("standard");
|
|
@@ -55,27 +57,31 @@ describe("read/write round-trip", () => {
|
|
|
55
57
|
profile: "docker-ready",
|
|
56
58
|
drivers: {
|
|
57
59
|
store: {
|
|
58
|
-
sql:
|
|
59
|
-
kv:
|
|
60
|
-
files:
|
|
60
|
+
sql: pinsDockerReady("postgres", "pglite"),
|
|
61
|
+
kv: pinsDockerReady("redis", "memory"),
|
|
62
|
+
files: pinsDockerReady("s3", "memory"),
|
|
61
63
|
index: null,
|
|
62
64
|
},
|
|
63
|
-
signal:
|
|
64
|
-
clock:
|
|
65
|
-
vault:
|
|
66
|
-
channel: { email:
|
|
67
|
-
ai:
|
|
65
|
+
signal: pinsDockerReady("redis", "memory"),
|
|
66
|
+
clock: pinsDockerReady("file", "frozen"),
|
|
67
|
+
vault: pinsDockerReady("vault", "memory"),
|
|
68
|
+
channel: { email: pinsDockerReady("smtp", "console") },
|
|
69
|
+
ai: pinsDockerReady("ollama", "mock"),
|
|
68
70
|
},
|
|
69
71
|
ai: { enabled: true, provider: "ollama", driver: "ollama" },
|
|
72
|
+
locales: ["ar"],
|
|
73
|
+
pgdog: true,
|
|
70
74
|
});
|
|
71
75
|
writeCreateDefaults(original, path);
|
|
72
76
|
const loaded = readCreateDefaults(path);
|
|
73
77
|
expect(loaded).not.toBeNull();
|
|
74
78
|
expect(loaded!.template).toBe("advanced");
|
|
75
|
-
expect(loaded!.drivers.store.sql.
|
|
76
|
-
expect(loaded!.drivers.ai?.
|
|
79
|
+
expect(loaded!.drivers.store.sql.dev).toBe("postgres");
|
|
80
|
+
expect(loaded!.drivers.ai?.dev).toBe("ollama");
|
|
77
81
|
expect(loaded!.ai.provider).toBe("ollama");
|
|
78
82
|
expect(loaded!.profile).toBe("docker-ready");
|
|
83
|
+
expect(loaded!.locales).toEqual(["ar"]);
|
|
84
|
+
expect(loaded!.pgdog).toBe(true);
|
|
79
85
|
} finally {
|
|
80
86
|
rmSync(home, { recursive: true, force: true });
|
|
81
87
|
}
|
package/src/create-defaults.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User-global create-oke defaults — `~/.oke/create-defaults.json`.
|
|
3
3
|
*
|
|
4
|
-
* Project-local `.oke/`
|
|
4
|
+
* Project-local `.oke/` stays under the app cwd.
|
|
5
5
|
* This file is the first home-directory preference for the scaffold wizard.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -10,13 +10,12 @@ import { homedir } from "node:os";
|
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
import { isTemplateId, type TemplateId } from "./templates.ts";
|
|
12
12
|
|
|
13
|
-
/** Create profile
|
|
14
|
-
export type CreateProfile = "
|
|
13
|
+
/** Create profile — Docker Compose is always the `dev` runtime. */
|
|
14
|
+
export type CreateProfile = "docker-ready";
|
|
15
15
|
|
|
16
16
|
/** Env columns written into `oke.config.ts` driver maps. */
|
|
17
17
|
export type EnvDriverPins = {
|
|
18
|
-
readonly
|
|
19
|
-
readonly docker: string;
|
|
18
|
+
readonly dev: string;
|
|
20
19
|
readonly test: string;
|
|
21
20
|
readonly prod: string;
|
|
22
21
|
};
|
|
@@ -62,6 +61,16 @@ export type CreateDefaults = {
|
|
|
62
61
|
readonly ai: EnvDriverPins | null;
|
|
63
62
|
};
|
|
64
63
|
readonly ai: CreateAiPref;
|
|
64
|
+
/**
|
|
65
|
+
* Extra locales beyond English (`en` is always the default).
|
|
66
|
+
* Empty = English-only project.
|
|
67
|
+
*/
|
|
68
|
+
readonly locales: readonly string[];
|
|
69
|
+
/**
|
|
70
|
+
* Pin `images.pgdog` so Compose puts PgDog in front of Postgres.
|
|
71
|
+
* Default false — opt in via the wizard or `--pgdog`.
|
|
72
|
+
*/
|
|
73
|
+
readonly pgdog: boolean;
|
|
65
74
|
readonly updatedAt: string;
|
|
66
75
|
};
|
|
67
76
|
|
|
@@ -130,7 +139,8 @@ export function parseCreateDefaults(raw: unknown): CreateDefaults | null {
|
|
|
130
139
|
if (!raw || typeof raw !== "object") return null;
|
|
131
140
|
const o = raw as Record<string, unknown>;
|
|
132
141
|
if (o.version !== 1) return null;
|
|
133
|
-
|
|
142
|
+
// Reject deprecated local-only / unknown profiles (Docker-first only).
|
|
143
|
+
if (o.profile !== "docker-ready") return null;
|
|
134
144
|
// Older files omit template — treat as standard so reuse stays usable.
|
|
135
145
|
const templateRaw = o.template === undefined || o.template === null ? "standard" : o.template;
|
|
136
146
|
if (typeof templateRaw !== "string" || !isTemplateId(templateRaw)) return null;
|
|
@@ -190,6 +200,19 @@ export function parseCreateDefaults(raw: unknown): CreateDefaults | null {
|
|
|
190
200
|
return null;
|
|
191
201
|
}
|
|
192
202
|
if (typeof o.updatedAt !== "string") return null;
|
|
203
|
+
let locales: readonly string[] = [];
|
|
204
|
+
if (o.locales !== undefined && o.locales !== null) {
|
|
205
|
+
if (!Array.isArray(o.locales) || !o.locales.every((x) => typeof x === "string")) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
locales = o.locales.filter((tag) => tag !== "en");
|
|
209
|
+
}
|
|
210
|
+
// Older files omit pgdog — treat as off (opt-in).
|
|
211
|
+
let pgdog = false;
|
|
212
|
+
if (o.pgdog !== undefined && o.pgdog !== null) {
|
|
213
|
+
if (typeof o.pgdog !== "boolean") return null;
|
|
214
|
+
pgdog = o.pgdog;
|
|
215
|
+
}
|
|
193
216
|
return {
|
|
194
217
|
version: 1,
|
|
195
218
|
template,
|
|
@@ -212,6 +235,8 @@ export function parseCreateDefaults(raw: unknown): CreateDefaults | null {
|
|
|
212
235
|
...(baseUrl !== undefined ? { baseUrl } : {}),
|
|
213
236
|
...(apiKeyEnv !== undefined ? { apiKeyEnv } : {}),
|
|
214
237
|
},
|
|
238
|
+
locales,
|
|
239
|
+
pgdog,
|
|
215
240
|
updatedAt: o.updatedAt,
|
|
216
241
|
};
|
|
217
242
|
}
|
|
@@ -234,13 +259,8 @@ function optionalStringOrNull(value: unknown): string | null | undefined | false
|
|
|
234
259
|
function parsePins(raw: unknown): EnvDriverPins | null {
|
|
235
260
|
if (!raw || typeof raw !== "object") return null;
|
|
236
261
|
const o = raw as Record<string, unknown>;
|
|
237
|
-
if (
|
|
238
|
-
typeof o.local !== "string" ||
|
|
239
|
-
typeof o.docker !== "string" ||
|
|
240
|
-
typeof o.test !== "string" ||
|
|
241
|
-
typeof o.prod !== "string"
|
|
242
|
-
) {
|
|
262
|
+
if (typeof o.dev !== "string" || typeof o.test !== "string" || typeof o.prod !== "string") {
|
|
243
263
|
return null;
|
|
244
264
|
}
|
|
245
|
-
return {
|
|
265
|
+
return { dev: o.dev, test: o.test, prod: o.prod };
|
|
246
266
|
}
|