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.
Files changed (71) hide show
  1. package/README.md +32 -20
  2. package/examples/linkly/.env.example +15 -0
  3. package/examples/linkly/drizzle.config.ts +11 -0
  4. package/examples/linkly/oke.config.ts +6 -4
  5. package/examples/linkly/package.json +7 -4
  6. package/examples/linkly/src/flows/analytics/index.ts +29 -14
  7. package/examples/linkly/src/flows/links/index.ts +58 -35
  8. package/examples/linkly/src/flows/links/signals.ts +4 -3
  9. package/examples/linkly/src/gates.ts +4 -2
  10. package/examples/linkly/src/schema.decl.ts +39 -0
  11. package/examples/linkly/src/schema.generated.ts +30 -0
  12. package/examples/linkly/src/schema.ts +2 -17
  13. package/examples/linkly/tests/linkly.test.ts +9 -9
  14. package/examples/notes/.env.example +12 -0
  15. package/examples/notes/drizzle.config.ts +11 -0
  16. package/examples/notes/oke.config.ts +8 -1
  17. package/examples/notes/package.json +7 -4
  18. package/examples/notes/src/app.ts +1 -1
  19. package/examples/notes/src/flows/notes/index.ts +44 -30
  20. package/examples/notes/src/schema.ts +3 -3
  21. package/examples/notes/tests/notes.test.ts +1 -1
  22. package/examples/provisions/.env.example +19 -0
  23. package/examples/provisions/drizzle.config.ts +11 -0
  24. package/examples/provisions/oke.config.ts +10 -8
  25. package/examples/provisions/package.json +7 -4
  26. package/examples/provisions/src/app.ts +3 -3
  27. package/examples/provisions/src/channels.ts +4 -3
  28. package/examples/provisions/src/flows/notifications/index.ts +13 -7
  29. package/examples/provisions/src/flows/orders/index.ts +58 -31
  30. package/examples/provisions/src/flows/payments/index.ts +12 -6
  31. package/examples/provisions/src/schema.ts +7 -7
  32. package/examples/provisions/src/vault.ts +2 -2
  33. package/examples/provisions/tests/orders.test.ts +9 -8
  34. package/examples/skyport/.env.example +34 -0
  35. package/examples/skyport/drizzle.config.ts +11 -0
  36. package/examples/skyport/oke.config.ts +25 -22
  37. package/examples/skyport/package.json +8 -5
  38. package/examples/skyport/src/ai.ts +4 -4
  39. package/examples/skyport/src/app.ts +2 -13
  40. package/examples/skyport/src/flows/bookings/index.ts +45 -22
  41. package/examples/skyport/src/flows/bookings/shapes.ts +4 -4
  42. package/examples/skyport/src/flows/bookings/signals.ts +6 -2
  43. package/examples/skyport/src/flows/support/index.ts +37 -25
  44. package/examples/skyport/src/schema.ts +11 -8
  45. package/examples/skyport/src/vault.ts +1 -1
  46. package/examples/skyport/tests/support.test.ts +6 -6
  47. package/package.json +9 -9
  48. package/src/cli.test.ts +132 -64
  49. package/src/cli.ts +58 -33
  50. package/src/index.ts +0 -0
  51. package/src/scaffold.ts +53 -15
  52. package/src/sync-templates.ts +2 -11
  53. package/src/templates.ts +1 -2
  54. package/src/transform.ts +59 -9
  55. package/templates/full/.env.example +24 -0
  56. package/templates/full/README.md +22 -6
  57. package/templates/full/drizzle.config.ts +14 -0
  58. package/templates/full/oke.config.ts +38 -9
  59. package/templates/full/package.json +3 -0
  60. package/templates/hello/.env.example +9 -0
  61. package/templates/hello/README.md +6 -4
  62. package/templates/minimal/.env.example +13 -0
  63. package/templates/minimal/README.md +15 -7
  64. package/templates/minimal/drizzle.config.ts +14 -0
  65. package/templates/minimal/oke.config.ts +8 -1
  66. package/templates/minimal/package.json +3 -0
  67. package/templates/standard/.env.example +21 -0
  68. package/templates/standard/README.md +25 -8
  69. package/templates/standard/drizzle.config.ts +14 -0
  70. package/templates/standard/oke.config.ts +36 -8
  71. 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
- (id) => ` ${id.padEnd(12)}${EXAMPLE_NEW_IDEAS[id]}`,
253
- ).join("\n");
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
- partial: { readonly name?: string; readonly agentsMd?: boolean },
376
- ) => Promise<InteractiveAnswers | null>;
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: templateValue as TemplateId,
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()) {
@@ -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
@@ -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
- | Port | Try |
14
- |---|---|
15
- | `:6530` | `GET /` · `GET /health` |
16
- | `:6533` | Console |
17
- | `:6535` | MCP |
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
- ├── oke.config.ts
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
+ });