create-oke 0.2.3 → 0.2.8
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 +32 -20
- package/examples/linkly/.env.example +15 -0
- package/examples/linkly/drizzle.config.ts +11 -0
- package/examples/linkly/oke.config.ts +6 -4
- package/examples/linkly/package.json +7 -4
- package/examples/linkly/src/flows/analytics/index.ts +29 -14
- package/examples/linkly/src/flows/links/index.ts +58 -35
- package/examples/linkly/src/flows/links/signals.ts +4 -3
- package/examples/linkly/src/gates.ts +4 -2
- package/examples/linkly/src/schema.decl.ts +39 -0
- package/examples/linkly/src/schema.generated.ts +30 -0
- package/examples/linkly/src/schema.ts +2 -17
- package/examples/linkly/tests/linkly.test.ts +9 -9
- package/examples/notes/.env.example +12 -0
- package/examples/notes/drizzle.config.ts +11 -0
- package/examples/notes/oke.config.ts +8 -1
- package/examples/notes/package.json +7 -4
- package/examples/notes/src/app.ts +1 -1
- package/examples/notes/src/flows/notes/index.ts +44 -30
- package/examples/notes/src/schema.ts +3 -3
- package/examples/notes/tests/notes.test.ts +1 -1
- package/examples/provisions/.env.example +19 -0
- package/examples/provisions/drizzle.config.ts +11 -0
- package/examples/provisions/oke.config.ts +10 -8
- package/examples/provisions/package.json +7 -4
- package/examples/provisions/src/app.ts +3 -3
- package/examples/provisions/src/channels.ts +4 -3
- package/examples/provisions/src/flows/notifications/index.ts +13 -7
- package/examples/provisions/src/flows/orders/index.ts +58 -31
- package/examples/provisions/src/flows/payments/index.ts +12 -6
- package/examples/provisions/src/schema.ts +7 -7
- package/examples/provisions/src/vault.ts +2 -2
- package/examples/provisions/tests/orders.test.ts +9 -8
- package/examples/skyport/.env.example +34 -0
- package/examples/skyport/drizzle.config.ts +11 -0
- package/examples/skyport/oke.config.ts +25 -22
- package/examples/skyport/package.json +8 -5
- package/examples/skyport/src/ai.ts +4 -4
- package/examples/skyport/src/app.ts +2 -13
- package/examples/skyport/src/flows/bookings/index.ts +45 -22
- package/examples/skyport/src/flows/bookings/shapes.ts +4 -4
- package/examples/skyport/src/flows/bookings/signals.ts +6 -2
- package/examples/skyport/src/flows/support/index.ts +37 -25
- package/examples/skyport/src/schema.ts +11 -8
- package/examples/skyport/src/vault.ts +1 -1
- package/examples/skyport/tests/support.test.ts +6 -6
- package/package.json +9 -9
- package/src/cli.test.ts +132 -64
- package/src/cli.ts +58 -33
- package/src/index.ts +0 -0
- package/src/scaffold.ts +53 -15
- package/src/sync-templates.ts +2 -11
- package/src/templates.ts +1 -2
- package/src/transform.ts +59 -9
- package/templates/full/.env.example +24 -0
- package/templates/full/README.md +22 -6
- package/templates/full/drizzle.config.ts +14 -0
- package/templates/full/oke.config.ts +38 -9
- package/templates/full/package.json +3 -0
- package/templates/hello/.env.example +9 -0
- package/templates/hello/README.md +6 -4
- package/templates/minimal/.env.example +13 -0
- package/templates/minimal/README.md +15 -7
- package/templates/minimal/drizzle.config.ts +14 -0
- package/templates/minimal/oke.config.ts +8 -1
- package/templates/minimal/package.json +3 -0
- package/templates/standard/.env.example +21 -0
- package/templates/standard/README.md +25 -8
- package/templates/standard/drizzle.config.ts +14 -0
- package/templates/standard/oke.config.ts +36 -8
- package/templates/standard/package.json +3 -0
package/src/cli.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
type ExampleId,
|
|
37
37
|
type TemplateId,
|
|
38
38
|
} from "./templates.ts";
|
|
39
|
+
import { DEFAULT_SQL_DRIVER, SQL_DRIVERS, isSqlDriverId, type SqlDriverId } from "./transform.ts";
|
|
39
40
|
|
|
40
41
|
/** Parsed CLI arguments. */
|
|
41
42
|
export type CliArgs = {
|
|
@@ -44,6 +45,10 @@ export type CliArgs = {
|
|
|
44
45
|
readonly fromExample: ExampleId | undefined;
|
|
45
46
|
/** True when `--template` / `-t` was present on the argv. */
|
|
46
47
|
readonly templateExplicit: boolean;
|
|
48
|
+
/** Store SQL driver (`sqlite` default). */
|
|
49
|
+
readonly sqlDriver: SqlDriverId;
|
|
50
|
+
/** True when `--sql` was present on the argv. */
|
|
51
|
+
readonly sqlDriverExplicit: boolean;
|
|
47
52
|
readonly help: boolean;
|
|
48
53
|
/** Skip all prompts; use defaults. */
|
|
49
54
|
readonly yes: boolean;
|
|
@@ -87,6 +92,7 @@ export type ScaffoldCallArgs = {
|
|
|
87
92
|
readonly targetDir: string;
|
|
88
93
|
readonly source: ScaffoldSource;
|
|
89
94
|
readonly agentsMd: boolean;
|
|
95
|
+
readonly sqlDriver: SqlDriverId;
|
|
90
96
|
};
|
|
91
97
|
|
|
92
98
|
/**
|
|
@@ -99,6 +105,8 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
99
105
|
let template: TemplateId = DEFAULT_TEMPLATE;
|
|
100
106
|
let fromExample: ExampleId | undefined;
|
|
101
107
|
let templateExplicit = false;
|
|
108
|
+
let sqlDriver: SqlDriverId = DEFAULT_SQL_DRIVER;
|
|
109
|
+
let sqlDriverExplicit = false;
|
|
102
110
|
let help = false;
|
|
103
111
|
let yes = false;
|
|
104
112
|
let install: boolean | undefined;
|
|
@@ -131,12 +139,28 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
131
139
|
agentsMd = true;
|
|
132
140
|
continue;
|
|
133
141
|
}
|
|
142
|
+
if (a === "--sql") {
|
|
143
|
+
const next = argv[++i];
|
|
144
|
+
if (!next || !isSqlDriverId(next)) {
|
|
145
|
+
throw new Error(`create-oke: --sql must be one of ${SQL_DRIVERS.join("|")}`);
|
|
146
|
+
}
|
|
147
|
+
sqlDriver = next;
|
|
148
|
+
sqlDriverExplicit = true;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (a.startsWith("--sql=")) {
|
|
152
|
+
const value = a.slice("--sql=".length);
|
|
153
|
+
if (!isSqlDriverId(value)) {
|
|
154
|
+
throw new Error(`create-oke: --sql must be one of ${SQL_DRIVERS.join("|")}`);
|
|
155
|
+
}
|
|
156
|
+
sqlDriver = value;
|
|
157
|
+
sqlDriverExplicit = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
134
160
|
if (a === "--template" || a === "-t") {
|
|
135
161
|
const next = argv[++i];
|
|
136
162
|
if (!next || !isTemplateId(next)) {
|
|
137
|
-
throw new Error(
|
|
138
|
-
`create-oke: --template must be one of ${TEMPLATES.join("|")}`,
|
|
139
|
-
);
|
|
163
|
+
throw new Error(`create-oke: --template must be one of ${TEMPLATES.join("|")}`);
|
|
140
164
|
}
|
|
141
165
|
template = next;
|
|
142
166
|
templateExplicit = true;
|
|
@@ -145,9 +169,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
145
169
|
if (a.startsWith("--template=")) {
|
|
146
170
|
const value = a.slice("--template=".length);
|
|
147
171
|
if (!isTemplateId(value)) {
|
|
148
|
-
throw new Error(
|
|
149
|
-
`create-oke: --template must be one of ${TEMPLATES.join("|")}`,
|
|
150
|
-
);
|
|
172
|
+
throw new Error(`create-oke: --template must be one of ${TEMPLATES.join("|")}`);
|
|
151
173
|
}
|
|
152
174
|
template = value;
|
|
153
175
|
templateExplicit = true;
|
|
@@ -156,9 +178,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
156
178
|
if (a === "--from-example") {
|
|
157
179
|
const next = argv[++i];
|
|
158
180
|
if (!next || !isExampleId(next)) {
|
|
159
|
-
throw new Error(
|
|
160
|
-
`create-oke: --from-example must be one of ${EXAMPLES.join("|")}`,
|
|
161
|
-
);
|
|
181
|
+
throw new Error(`create-oke: --from-example must be one of ${EXAMPLES.join("|")}`);
|
|
162
182
|
}
|
|
163
183
|
fromExample = next;
|
|
164
184
|
continue;
|
|
@@ -166,9 +186,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
166
186
|
if (a.startsWith("--from-example=")) {
|
|
167
187
|
const value = a.slice("--from-example=".length);
|
|
168
188
|
if (!isExampleId(value)) {
|
|
169
|
-
throw new Error(
|
|
170
|
-
`create-oke: --from-example must be one of ${EXAMPLES.join("|")}`,
|
|
171
|
-
);
|
|
189
|
+
throw new Error(`create-oke: --from-example must be one of ${EXAMPLES.join("|")}`);
|
|
172
190
|
}
|
|
173
191
|
fromExample = value;
|
|
174
192
|
continue;
|
|
@@ -184,9 +202,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
184
202
|
}
|
|
185
203
|
|
|
186
204
|
if (templateExplicit && fromExample !== undefined) {
|
|
187
|
-
throw new Error(
|
|
188
|
-
"create-oke: use either --template or --from-example, not both",
|
|
189
|
-
);
|
|
205
|
+
throw new Error("create-oke: use either --template or --from-example, not both");
|
|
190
206
|
}
|
|
191
207
|
|
|
192
208
|
if (argv.includes("--install") && argv.includes("--no-install")) {
|
|
@@ -202,6 +218,8 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
202
218
|
template,
|
|
203
219
|
fromExample,
|
|
204
220
|
templateExplicit,
|
|
221
|
+
sqlDriver,
|
|
222
|
+
sqlDriverExplicit,
|
|
205
223
|
help,
|
|
206
224
|
yes,
|
|
207
225
|
install,
|
|
@@ -248,9 +266,9 @@ export function helpText(): string {
|
|
|
248
266
|
(id) =>
|
|
249
267
|
` ${id.padEnd(12)}${TEMPLATE_PURPOSES[id]}${id === DEFAULT_TEMPLATE ? " (default)" : ""}`,
|
|
250
268
|
).join("\n");
|
|
251
|
-
const exampleLines = EXAMPLES.map(
|
|
252
|
-
|
|
253
|
-
)
|
|
269
|
+
const exampleLines = EXAMPLES.map((id) => ` ${id.padEnd(12)}${EXAMPLE_NEW_IDEAS[id]}`).join(
|
|
270
|
+
"\n",
|
|
271
|
+
);
|
|
254
272
|
|
|
255
273
|
return `create-oke — scaffold an okengine app
|
|
256
274
|
|
|
@@ -263,6 +281,10 @@ Usage:
|
|
|
263
281
|
Options:
|
|
264
282
|
-t, --template <id> Clean starter (default: ${DEFAULT_TEMPLATE})
|
|
265
283
|
--from-example <id> Teaching example (non-interactive)
|
|
284
|
+
--sql <id> Opt-in Store SQL dialect: ${SQL_DRIVERS.join("|")}
|
|
285
|
+
Default keeps local sqlite · docker/prod postgres.
|
|
286
|
+
--sql postgres rewrites src/schema.ts to pgTable and
|
|
287
|
+
pins store.sql local/docker/prod to postgres.
|
|
266
288
|
-y, --yes No prompts; defaults + bun install (no oke dev)
|
|
267
289
|
--install Run bun install after scaffold
|
|
268
290
|
--no-install Skip bun install
|
|
@@ -292,10 +314,7 @@ No telemetry. Bun only. On a TTY, a project name alone still opens the wizard
|
|
|
292
314
|
* @param args - Parsed args
|
|
293
315
|
* @param stdinIsTTY - `process.stdin.isTTY`
|
|
294
316
|
*/
|
|
295
|
-
export function shouldPrompt(
|
|
296
|
-
args: CliArgs,
|
|
297
|
-
stdinIsTTY: boolean | undefined,
|
|
298
|
-
): boolean {
|
|
317
|
+
export function shouldPrompt(args: CliArgs, stdinIsTTY: boolean | undefined): boolean {
|
|
299
318
|
if (!stdinIsTTY) return false;
|
|
300
319
|
if (args.help) return false;
|
|
301
320
|
if (args.yes) return false;
|
|
@@ -332,6 +351,7 @@ export function scaffoldArgsFromCli(args: CliArgs): ScaffoldCallArgs {
|
|
|
332
351
|
targetDir: args.targetDir,
|
|
333
352
|
source: sourceFromArgs(args),
|
|
334
353
|
agentsMd: args.agentsMd,
|
|
354
|
+
sqlDriver: args.sqlDriver,
|
|
335
355
|
};
|
|
336
356
|
}
|
|
337
357
|
|
|
@@ -343,17 +363,18 @@ export function scaffoldArgsFromCli(args: CliArgs): ScaffoldCallArgs {
|
|
|
343
363
|
*
|
|
344
364
|
* @param answers - Collected interactive answers
|
|
345
365
|
*/
|
|
346
|
-
export function scaffoldArgsFromAnswers(
|
|
347
|
-
answers: InteractiveAnswers,
|
|
348
|
-
): ScaffoldCallArgs {
|
|
366
|
+
export function scaffoldArgsFromAnswers(answers: InteractiveAnswers): ScaffoldCallArgs {
|
|
349
367
|
const targetDir = resolve(answers.name.trim());
|
|
350
368
|
const name = basename(targetDir);
|
|
369
|
+
// Interactive always keeps the dual-mode default (local sqlite ·
|
|
370
|
+
// docker/prod postgres). Opt into postgres-everywhere with `--sql postgres`.
|
|
351
371
|
if (answers.choice === FROM_EXAMPLE_CHOICE) {
|
|
352
372
|
return {
|
|
353
373
|
name,
|
|
354
374
|
targetDir,
|
|
355
375
|
source: { kind: "example", id: answers.example },
|
|
356
376
|
agentsMd: answers.agentsMd,
|
|
377
|
+
sqlDriver: DEFAULT_SQL_DRIVER,
|
|
357
378
|
};
|
|
358
379
|
}
|
|
359
380
|
return {
|
|
@@ -361,6 +382,7 @@ export function scaffoldArgsFromAnswers(
|
|
|
361
382
|
targetDir,
|
|
362
383
|
source: { kind: "template", id: answers.choice },
|
|
363
384
|
agentsMd: answers.agentsMd,
|
|
385
|
+
sqlDriver: DEFAULT_SQL_DRIVER,
|
|
364
386
|
};
|
|
365
387
|
}
|
|
366
388
|
|
|
@@ -371,9 +393,10 @@ export function scaffoldArgsFromAnswers(
|
|
|
371
393
|
*
|
|
372
394
|
* @param partial - Name already known (pre-filled in the prompt)
|
|
373
395
|
*/
|
|
374
|
-
export type AskInteractiveFn = (
|
|
375
|
-
|
|
376
|
-
|
|
396
|
+
export type AskInteractiveFn = (partial: {
|
|
397
|
+
readonly name?: string;
|
|
398
|
+
readonly agentsMd?: boolean;
|
|
399
|
+
}) => Promise<InteractiveAnswers | null>;
|
|
377
400
|
|
|
378
401
|
/**
|
|
379
402
|
* Collect interactive answers via `@clack/prompts`.
|
|
@@ -442,6 +465,8 @@ export async function askInteractiveAnswers(
|
|
|
442
465
|
};
|
|
443
466
|
}
|
|
444
467
|
|
|
468
|
+
const templateId = templateValue as TemplateId;
|
|
469
|
+
|
|
445
470
|
const installAndRunValue = await confirm({
|
|
446
471
|
message: "Install dependencies and start oke dev?",
|
|
447
472
|
initialValue: true,
|
|
@@ -450,7 +475,7 @@ export async function askInteractiveAnswers(
|
|
|
450
475
|
|
|
451
476
|
return {
|
|
452
477
|
name,
|
|
453
|
-
choice:
|
|
478
|
+
choice: templateId,
|
|
454
479
|
installAndRun: Boolean(installAndRunValue),
|
|
455
480
|
agentsMd,
|
|
456
481
|
};
|
|
@@ -503,9 +528,7 @@ export async function run(
|
|
|
503
528
|
|
|
504
529
|
if (args.name === undefined) {
|
|
505
530
|
console.log(helpText());
|
|
506
|
-
console.error(
|
|
507
|
-
"create-oke: missing <name>. Pass a name, or run in a TTY for the wizard.",
|
|
508
|
-
);
|
|
531
|
+
console.error("create-oke: missing <name>. Pass a name, or run in a TTY for the wizard.");
|
|
509
532
|
console.error(" Example: bunx create-oke@latest my-app --yes");
|
|
510
533
|
return 1;
|
|
511
534
|
}
|
|
@@ -513,7 +536,7 @@ export async function run(
|
|
|
513
536
|
if (args.yes) {
|
|
514
537
|
const source = sourceFromArgs(args);
|
|
515
538
|
console.log(
|
|
516
|
-
`Using defaults: ${source.kind}=${source.id} agents-md=${args.agentsMd} install=${shouldInstall(args)}`,
|
|
539
|
+
`Using defaults: ${source.kind}=${source.id} sql=${args.sqlDriver} agents-md=${args.agentsMd} install=${shouldInstall(args)}`,
|
|
517
540
|
);
|
|
518
541
|
}
|
|
519
542
|
|
|
@@ -573,6 +596,7 @@ async function runScaffold(
|
|
|
573
596
|
targetDir,
|
|
574
597
|
source,
|
|
575
598
|
agentsMd,
|
|
599
|
+
sqlDriver,
|
|
576
600
|
interactive,
|
|
577
601
|
install,
|
|
578
602
|
startDev,
|
|
@@ -605,6 +629,7 @@ async function runScaffold(
|
|
|
605
629
|
name,
|
|
606
630
|
source,
|
|
607
631
|
writeAgentsMd: agentsMd,
|
|
632
|
+
sqlDriver,
|
|
608
633
|
});
|
|
609
634
|
if (spun) spun.stop("Scaffolded.");
|
|
610
635
|
|
package/src/index.ts
CHANGED
|
File without changes
|
package/src/scaffold.ts
CHANGED
|
@@ -22,11 +22,15 @@ import {
|
|
|
22
22
|
} from "./templates.ts";
|
|
23
23
|
import { agentsMdContent } from "./agents-md.ts";
|
|
24
24
|
import {
|
|
25
|
+
DEFAULT_SQL_DRIVER,
|
|
25
26
|
sanitizeProjectName,
|
|
26
27
|
shouldSkipTemplatePath,
|
|
28
|
+
transformConfigForSqlDriver,
|
|
27
29
|
transformPackageJson,
|
|
30
|
+
transformSchemaForSqlDriver,
|
|
28
31
|
resolveOkengineDependency,
|
|
29
32
|
type ScaffoldPackageJson,
|
|
33
|
+
type SqlDriverId,
|
|
30
34
|
} from "./transform.ts";
|
|
31
35
|
|
|
32
36
|
/** Where the scaffold copies from. */
|
|
@@ -44,6 +48,12 @@ export type ScaffoldOptions = {
|
|
|
44
48
|
readonly source: ScaffoldSource;
|
|
45
49
|
/** Write root `AGENTS.md` (default true). */
|
|
46
50
|
readonly writeAgentsMd?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Store SQL driver — rewrites `src/schema.ts` dialect and pins
|
|
53
|
+
* `oke.config.ts` `store.sql` local/docker/prod when `postgres`.
|
|
54
|
+
* Default `sqlite`.
|
|
55
|
+
*/
|
|
56
|
+
readonly sqlDriver?: SqlDriverId;
|
|
47
57
|
};
|
|
48
58
|
|
|
49
59
|
/** Result of a successful scaffold. */
|
|
@@ -54,6 +64,8 @@ export type ScaffoldResult = {
|
|
|
54
64
|
/** Display label (`standard`, `notes`, …). */
|
|
55
65
|
readonly label: string;
|
|
56
66
|
readonly okengineDependency: string;
|
|
67
|
+
/** Store SQL driver applied to schema + config. */
|
|
68
|
+
readonly sqlDriver: SqlDriverId;
|
|
57
69
|
/** Relative paths written (POSIX), sorted. */
|
|
58
70
|
readonly files: readonly string[];
|
|
59
71
|
};
|
|
@@ -71,22 +83,19 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
|
|
|
71
83
|
? resolveTemplateDir(options.source.id)
|
|
72
84
|
: resolveExampleDir(options.source.id);
|
|
73
85
|
const label = options.source.id;
|
|
86
|
+
const sqlDriver = options.sqlDriver ?? DEFAULT_SQL_DRIVER;
|
|
74
87
|
|
|
75
88
|
if (existsSync(targetDir)) {
|
|
76
89
|
const entries = readdirSync(targetDir);
|
|
77
90
|
if (entries.length > 0) {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`create-oke: target directory is not empty: ${targetDir}`,
|
|
80
|
-
);
|
|
91
|
+
throw new Error(`create-oke: target directory is not empty: ${targetDir}`);
|
|
81
92
|
}
|
|
82
93
|
} else {
|
|
83
94
|
mkdirSync(targetDir, { recursive: true });
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
try {
|
|
87
|
-
const okengineDependency = resolveOkengineDependency(
|
|
88
|
-
resolveLocalOkengineRoot(),
|
|
89
|
-
);
|
|
98
|
+
const okengineDependency = resolveOkengineDependency(resolveLocalOkengineRoot());
|
|
90
99
|
const written: string[] = [];
|
|
91
100
|
|
|
92
101
|
copyTree(sourceDir, targetDir, "", written);
|
|
@@ -95,18 +104,25 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
|
|
|
95
104
|
if (!existsSync(pkgPath)) {
|
|
96
105
|
throw new Error(`create-oke: source "${label}" has no package.json`);
|
|
97
106
|
}
|
|
98
|
-
const sourcePkg = JSON.parse(
|
|
99
|
-
readFileSync(pkgPath, "utf8"),
|
|
100
|
-
) as ScaffoldPackageJson;
|
|
107
|
+
const sourcePkg = JSON.parse(readFileSync(pkgPath, "utf8")) as ScaffoldPackageJson;
|
|
101
108
|
const nextPkg = transformPackageJson(sourcePkg, name, okengineDependency);
|
|
102
109
|
writeFileSync(pkgPath, `${JSON.stringify(nextPkg, null, 2)}\n`, "utf8");
|
|
103
110
|
|
|
111
|
+
applySqlDriverTransforms(targetDir, sqlDriver);
|
|
112
|
+
|
|
104
113
|
if (options.writeAgentsMd !== false) {
|
|
105
114
|
const agentsPath = join(targetDir, "AGENTS.md");
|
|
106
115
|
writeFileSync(agentsPath, agentsMdContent(name), "utf8");
|
|
107
116
|
if (!written.includes("AGENTS.md")) written.push("AGENTS.md");
|
|
108
117
|
}
|
|
109
118
|
|
|
119
|
+
const envExample = join(targetDir, ".env.example");
|
|
120
|
+
const envLocal = join(targetDir, ".env.local");
|
|
121
|
+
if (existsSync(envExample) && !existsSync(envLocal)) {
|
|
122
|
+
cpSync(envExample, envLocal);
|
|
123
|
+
if (!written.includes(".env.local")) written.push(".env.local");
|
|
124
|
+
}
|
|
125
|
+
|
|
110
126
|
written.sort();
|
|
111
127
|
return {
|
|
112
128
|
targetDir,
|
|
@@ -114,6 +130,7 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
|
|
|
114
130
|
source: options.source,
|
|
115
131
|
label,
|
|
116
132
|
okengineDependency,
|
|
133
|
+
sqlDriver,
|
|
117
134
|
files: written,
|
|
118
135
|
};
|
|
119
136
|
} catch (e) {
|
|
@@ -122,6 +139,32 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
|
|
|
122
139
|
}
|
|
123
140
|
}
|
|
124
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Rewrite schema dialect + (for postgres) `store.sql` pins.
|
|
144
|
+
*
|
|
145
|
+
* `sqlite` keeps the template dual-mode config (`local: sqlite` ·
|
|
146
|
+
* `docker`/`prod: postgres`) — only the Drizzle dialect is ensured.
|
|
147
|
+
* `postgres` writes `pgTable` and pins local/docker/prod to postgres.
|
|
148
|
+
*
|
|
149
|
+
* @param targetDir - Scaffolded project root
|
|
150
|
+
* @param sqlDriver - Chosen store.sql driver
|
|
151
|
+
*/
|
|
152
|
+
function applySqlDriverTransforms(targetDir: string, sqlDriver: SqlDriverId): void {
|
|
153
|
+
const schemaPath = join(targetDir, "src/schema.ts");
|
|
154
|
+
if (existsSync(schemaPath)) {
|
|
155
|
+
const next = transformSchemaForSqlDriver(readFileSync(schemaPath, "utf8"), sqlDriver);
|
|
156
|
+
writeFileSync(schemaPath, next, "utf8");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (sqlDriver !== "postgres") return;
|
|
160
|
+
|
|
161
|
+
const configPath = join(targetDir, "oke.config.ts");
|
|
162
|
+
if (existsSync(configPath)) {
|
|
163
|
+
const next = transformConfigForSqlDriver(readFileSync(configPath, "utf8"), sqlDriver);
|
|
164
|
+
writeFileSync(configPath, next, "utf8");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
125
168
|
/**
|
|
126
169
|
* Recursively copy template files, applying {@link shouldSkipTemplatePath}.
|
|
127
170
|
*
|
|
@@ -130,12 +173,7 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
|
|
|
130
173
|
* @param rel - Relative path under the roots
|
|
131
174
|
* @param written - Accumulator for relative paths written
|
|
132
175
|
*/
|
|
133
|
-
function copyTree(
|
|
134
|
-
srcRoot: string,
|
|
135
|
-
dstRoot: string,
|
|
136
|
-
rel: string,
|
|
137
|
-
written: string[],
|
|
138
|
-
): void {
|
|
176
|
+
function copyTree(srcRoot: string, dstRoot: string, rel: string, written: string[]): void {
|
|
139
177
|
const src = rel ? join(srcRoot, rel) : srcRoot;
|
|
140
178
|
const st = statSync(src);
|
|
141
179
|
if (st.isDirectory()) {
|
package/src/sync-templates.ts
CHANGED
|
@@ -7,14 +7,7 @@
|
|
|
7
7
|
* monorepo-only docker.test.ts).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
cpSync,
|
|
12
|
-
existsSync,
|
|
13
|
-
mkdirSync,
|
|
14
|
-
readdirSync,
|
|
15
|
-
rmSync,
|
|
16
|
-
statSync,
|
|
17
|
-
} from "node:fs";
|
|
10
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
18
11
|
import { join, relative, resolve } from "node:path";
|
|
19
12
|
import { EXAMPLES, packageRoot, TEMPLATES } from "./templates.ts";
|
|
20
13
|
import { shouldSkipTemplatePath } from "./transform.ts";
|
|
@@ -26,9 +19,7 @@ const outTemplates = join(pkg, "templates");
|
|
|
26
19
|
const outExamples = join(pkg, "examples");
|
|
27
20
|
|
|
28
21
|
if (!existsSync(templatesRoot)) {
|
|
29
|
-
console.error(
|
|
30
|
-
`create-oke prepack: templates/ not found at ${templatesRoot}`,
|
|
31
|
-
);
|
|
22
|
+
console.error(`create-oke prepack: templates/ not found at ${templatesRoot}`);
|
|
32
23
|
process.exit(1);
|
|
33
24
|
}
|
|
34
25
|
if (!existsSync(examplesRoot)) {
|
package/src/templates.ts
CHANGED
|
@@ -38,8 +38,7 @@ export type ExampleId = (typeof EXAMPLES)[number];
|
|
|
38
38
|
* `--from-example` help and the interactive example select.
|
|
39
39
|
*/
|
|
40
40
|
export const EXAMPLE_NEW_IDEAS: Readonly<Record<ExampleId, string>> = {
|
|
41
|
-
notes:
|
|
42
|
-
"`oke`, `on`, `flow`, `http`, `store.sql`, `fx`, typed errors, the typed client.",
|
|
41
|
+
notes: "`oke`, `on`, `flow`, `http`, `store.sql`, `fx`, typed errors, the typed client.",
|
|
43
42
|
linkly:
|
|
44
43
|
"`signal` and its three delivery physics · `clock` · `gate` · triggers beyond HTTP · transactional emit · cross-unit decoupling.",
|
|
45
44
|
provisions:
|
package/src/transform.ts
CHANGED
|
@@ -8,12 +8,31 @@
|
|
|
8
8
|
* (absolute `file:<okengine-root>` in the monorepo; registry version otherwise)
|
|
9
9
|
* 3. Drop monorepo-only files that import paths outside the source tree
|
|
10
10
|
* (today: `tests/docker.test.ts`)
|
|
11
|
+
* 4. Optional `--sql` / wizard choice → Drizzle dialect + `store.sql` pins
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import { readFileSync } from "node:fs";
|
|
14
15
|
import { join } from "node:path";
|
|
15
16
|
import { packageRoot } from "./templates.ts";
|
|
16
17
|
|
|
18
|
+
/** SQL store drivers selectable at scaffold time. */
|
|
19
|
+
export const SQL_DRIVERS = ["sqlite", "postgres"] as const;
|
|
20
|
+
|
|
21
|
+
/** A known store.sql driver id for create-oke. */
|
|
22
|
+
export type SqlDriverId = (typeof SQL_DRIVERS)[number];
|
|
23
|
+
|
|
24
|
+
/** Default when `--sql` / wizard choice is omitted (matches template sources). */
|
|
25
|
+
export const DEFAULT_SQL_DRIVER: SqlDriverId = "sqlite";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether `value` is a known {@link SqlDriverId}.
|
|
29
|
+
*
|
|
30
|
+
* @param value - Candidate string
|
|
31
|
+
*/
|
|
32
|
+
export function isSqlDriverId(value: string): value is SqlDriverId {
|
|
33
|
+
return (SQL_DRIVERS as readonly string[]).includes(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
/** Shape of an example / scaffolded `package.json`. */
|
|
18
37
|
export type ScaffoldPackageJson = {
|
|
19
38
|
name?: string;
|
|
@@ -33,9 +52,7 @@ export type ScaffoldPackageJson = {
|
|
|
33
52
|
*
|
|
34
53
|
* @param localOkengineRoot - Absolute path when available
|
|
35
54
|
*/
|
|
36
|
-
export function resolveOkengineDependency(
|
|
37
|
-
localOkengineRoot: string | null,
|
|
38
|
-
): string {
|
|
55
|
+
export function resolveOkengineDependency(localOkengineRoot: string | null): string {
|
|
39
56
|
if (localOkengineRoot) return `file:${localOkengineRoot}`;
|
|
40
57
|
const pkgPath = join(packageRoot(), "package.json");
|
|
41
58
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { version: string };
|
|
@@ -94,6 +111,43 @@ export function shouldSkipTemplatePath(relativePath: string): boolean {
|
|
|
94
111
|
return false;
|
|
95
112
|
}
|
|
96
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Rewrite a Drizzle schema file to the dialect for `driver`.
|
|
116
|
+
*
|
|
117
|
+
* Templates ship as `sqliteTable` / `drizzle-orm/sqlite-core`. Choosing
|
|
118
|
+
* `postgres` swaps to `pgTable` / `pg-core` (and the reverse is idempotent).
|
|
119
|
+
*
|
|
120
|
+
* @param source - Schema TypeScript source
|
|
121
|
+
* @param driver - Target store.sql driver
|
|
122
|
+
*/
|
|
123
|
+
export function transformSchemaForSqlDriver(source: string, driver: SqlDriverId): string {
|
|
124
|
+
if (driver === "postgres") {
|
|
125
|
+
return source
|
|
126
|
+
.replaceAll("drizzle-orm/sqlite-core", "drizzle-orm/pg-core")
|
|
127
|
+
.replaceAll("sqliteTable", "pgTable");
|
|
128
|
+
}
|
|
129
|
+
return source
|
|
130
|
+
.replaceAll("drizzle-orm/pg-core", "drizzle-orm/sqlite-core")
|
|
131
|
+
.replaceAll("pgTable", "sqliteTable");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Pin `store.sql` `local` / `docker` / `prod` in `oke.config.ts` to `driver`.
|
|
136
|
+
*
|
|
137
|
+
* Leaves `test: "memory"` (and every other facet) untouched.
|
|
138
|
+
*
|
|
139
|
+
* @param source - Config TypeScript source
|
|
140
|
+
* @param driver - Target store.sql driver
|
|
141
|
+
*/
|
|
142
|
+
export function transformConfigForSqlDriver(source: string, driver: SqlDriverId): string {
|
|
143
|
+
return source.replace(/sql:\s*\{[\s\S]*?\n\s*\}/, (block) =>
|
|
144
|
+
block
|
|
145
|
+
.replace(/local:\s*"(?:sqlite|postgres)"/, `local: "${driver}"`)
|
|
146
|
+
.replace(/docker:\s*"(?:sqlite|postgres)"/, `docker: "${driver}"`)
|
|
147
|
+
.replace(/prod:\s*"(?:sqlite|postgres)"/, `prod: "${driver}"`),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
97
151
|
/**
|
|
98
152
|
* Sanitize a directory / package name for npm.
|
|
99
153
|
*
|
|
@@ -106,13 +160,9 @@ export function sanitizeProjectName(raw: string): string {
|
|
|
106
160
|
}
|
|
107
161
|
// Allow scoped names; otherwise lowercase npm-safe slug.
|
|
108
162
|
if (trimmed.startsWith("@")) {
|
|
109
|
-
const m = /^(@[a-z0-9-~][a-z0-9-._~]*\/[a-z0-9-~][a-z0-9-._~]*)$/.exec(
|
|
110
|
-
trimmed,
|
|
111
|
-
);
|
|
163
|
+
const m = /^(@[a-z0-9-~][a-z0-9-._~]*\/[a-z0-9-~][a-z0-9-._~]*)$/.exec(trimmed);
|
|
112
164
|
if (!m) {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`create-oke: invalid scoped package name "${trimmed}"`,
|
|
115
|
-
);
|
|
165
|
+
throw new Error(`create-oke: invalid scoped package name "${trimmed}"`);
|
|
116
166
|
}
|
|
117
167
|
return trimmed;
|
|
118
168
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Environment template — copy to `.env.local` for local overrides.
|
|
2
|
+
# create-oke copies this file to `.env.local` when you scaffold a new project.
|
|
3
|
+
#
|
|
4
|
+
# Resolution order (first hit wins):
|
|
5
|
+
# process.env → .env.local → docker/.env.docker → vault driver → dev fallback
|
|
6
|
+
#
|
|
7
|
+
# --- Vault (app secrets) ---
|
|
8
|
+
|
|
9
|
+
# APP_SECRET — signs sessions and internal tokens; generate with `openssl rand -hex 32`
|
|
10
|
+
# Local scaffold default (also set as vault `dev:` fallback in src/vault.ts).
|
|
11
|
+
APP_SECRET=dev-only-secret
|
|
12
|
+
|
|
13
|
+
# --- Infrastructure (optional) ---
|
|
14
|
+
# Commented — this app pins postgres, redis, and smtp (Mailpit) in oke.config.ts.
|
|
15
|
+
# Run `oke dev --docker` to write role-accurate keys into `docker/.env.docker`.
|
|
16
|
+
|
|
17
|
+
# DATABASE_URL — primary Postgres connection; from your host or `docker/.env.docker` after `oke dev --docker`
|
|
18
|
+
# DATABASE_URL=postgres://user:password@127.0.0.1:5432/oke
|
|
19
|
+
|
|
20
|
+
# REDIS_URL — Redis/Valkey connection for store.kv; from your host or `docker/.env.docker` after `oke dev --docker`
|
|
21
|
+
# REDIS_URL=redis://:password@127.0.0.1:6379
|
|
22
|
+
|
|
23
|
+
# SMTP_URL — outbound email (Mailpit in docker dev); from your provider or `docker/.env.docker` after `oke dev --docker`
|
|
24
|
+
# SMTP_URL=smtp://127.0.0.1:1025
|
package/templates/full/README.md
CHANGED
|
@@ -8,19 +8,25 @@ complete surface ready to fill in.
|
|
|
8
8
|
```bash
|
|
9
9
|
bun install
|
|
10
10
|
oke dev # app :6530 · Console :6533 · MCP :6535
|
|
11
|
+
# or: oke mode docker && oke dev # postgres · redis · Mailpit · RustFS · sops
|
|
11
12
|
```
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
|
17
|
-
|
|
|
14
|
+
Local `oke dev` auto-runs `oke db push` when `schema.ts` changes (opt out:
|
|
15
|
+
`--no-db-push`). Docker/prod never auto-apply DDL — use `oke db migrate`.
|
|
16
|
+
|
|
17
|
+
| Port | Try |
|
|
18
|
+
| ------- | ------------------------- |
|
|
19
|
+
| `:6530` | `GET /` · `GET /health` |
|
|
20
|
+
| `:6533` | Console |
|
|
21
|
+
| `:6535` | MCP |
|
|
18
22
|
|
|
19
23
|
## What’s in this template
|
|
20
24
|
|
|
21
25
|
```text
|
|
22
26
|
full/
|
|
23
|
-
├──
|
|
27
|
+
├── .env.example # copied to .env.local by create-oke
|
|
28
|
+
├── drizzle.config.ts # oke db push|generate|migrate
|
|
29
|
+
├── oke.config.ts # local: sqlite · docker/prod: postgres
|
|
24
30
|
├── src/
|
|
25
31
|
│ ├── app.ts # gates, secrets, signals, stores, channel, ai
|
|
26
32
|
│ ├── core.ts / schema.ts # Store
|
|
@@ -44,6 +50,16 @@ full/
|
|
|
44
50
|
Replace stubs with real models, prompts, channels, and domain flows. Prefer
|
|
45
51
|
`standard` if you do not need AI / clock / vault on day one.
|
|
46
52
|
|
|
53
|
+
## Docker mode (`oke dev --docker`)
|
|
54
|
+
|
|
55
|
+
Closest to production protocols: Postgres, Redis, SMTP (Mailpit), S3 (RustFS),
|
|
56
|
+
vault `sops`/age. Compose credentials land in `docker/.env.docker`.
|
|
57
|
+
|
|
58
|
+
| Surface | URL |
|
|
59
|
+
| -------------- | ------------------------------------------------ |
|
|
60
|
+
| Mailpit UI | [http://127.0.0.1:8025](http://127.0.0.1:8025) |
|
|
61
|
+
| RustFS console | [http://127.0.0.1:9001](http://127.0.0.1:9001) |
|
|
62
|
+
|
|
47
63
|
## Agent contract
|
|
48
64
|
|
|
49
65
|
See [`AGENTS.md`](./AGENTS.md). Handbook: [okengine.vercel.app/docs](https://okengine.vercel.app/docs)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from "drizzle-kit";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Domain schema sync for `oke db push|generate|migrate`.
|
|
5
|
+
* Versioned prod migrations land in `./drizzle` — never mix with `.oke/`.
|
|
6
|
+
*/
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
dialect: "sqlite",
|
|
9
|
+
schema: "./src/schema.ts",
|
|
10
|
+
out: "./drizzle",
|
|
11
|
+
dbCredentials: {
|
|
12
|
+
url: process.env.OKE_SQLITE_URL ?? "file:.oke/app.sqlite",
|
|
13
|
+
},
|
|
14
|
+
});
|