create-oke 0.2.6 → 0.3.2

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 (126) hide show
  1. package/README.md +26 -35
  2. package/package.json +10 -12
  3. package/src/cli.test.ts +124 -144
  4. package/src/cli.ts +62 -160
  5. package/src/docs-origin.ts +2 -3
  6. package/src/index.ts +1 -2
  7. package/src/scaffold.ts +52 -32
  8. package/src/templates.ts +12 -91
  9. package/src/transform.ts +60 -10
  10. package/template/.env.example +56 -0
  11. package/template/README.md +77 -0
  12. package/template/bun.lock +845 -0
  13. package/template/drizzle.config.ts +25 -0
  14. package/{templates/standard → template}/oke.config.ts +22 -25
  15. package/{templates/standard → template}/package.json +3 -0
  16. package/{templates/minimal → template}/src/core.ts +1 -1
  17. package/template/src/schema.decl.ts +12 -0
  18. package/examples/linkly/oke.config.ts +0 -10
  19. package/examples/linkly/package.json +0 -15
  20. package/examples/linkly/src/app.ts +0 -21
  21. package/examples/linkly/src/core.ts +0 -4
  22. package/examples/linkly/src/flows/analytics/index.ts +0 -24
  23. package/examples/linkly/src/flows/links/index.ts +0 -59
  24. package/examples/linkly/src/flows/links/shapes.ts +0 -22
  25. package/examples/linkly/src/flows/links/signals.ts +0 -13
  26. package/examples/linkly/src/gates.ts +0 -8
  27. package/examples/linkly/src/schema.ts +0 -17
  28. package/examples/linkly/tests/linkly.test.ts +0 -19
  29. package/examples/notes/oke.config.ts +0 -14
  30. package/examples/notes/package.json +0 -16
  31. package/examples/notes/src/app.ts +0 -13
  32. package/examples/notes/src/core.ts +0 -4
  33. package/examples/notes/src/flows/notes/index.ts +0 -44
  34. package/examples/notes/src/schema.ts +0 -9
  35. package/examples/notes/tests/notes.test.ts +0 -10
  36. package/examples/provisions/oke.config.ts +0 -17
  37. package/examples/provisions/package.json +0 -15
  38. package/examples/provisions/src/app.ts +0 -60
  39. package/examples/provisions/src/channels.ts +0 -14
  40. package/examples/provisions/src/core.ts +0 -4
  41. package/examples/provisions/src/flows/notifications/index.ts +0 -19
  42. package/examples/provisions/src/flows/orders/index.ts +0 -70
  43. package/examples/provisions/src/flows/orders/shapes.ts +0 -21
  44. package/examples/provisions/src/flows/orders/signals.ts +0 -19
  45. package/examples/provisions/src/flows/payments/index.ts +0 -37
  46. package/examples/provisions/src/flows/payments/shapes.ts +0 -3
  47. package/examples/provisions/src/flows/payments/stripe.ts +0 -18
  48. package/examples/provisions/src/gates.ts +0 -4
  49. package/examples/provisions/src/locales/ar.ts +0 -1
  50. package/examples/provisions/src/locales/en.ts +0 -1
  51. package/examples/provisions/src/plugins/audit-schema.ts +0 -2
  52. package/examples/provisions/src/plugins/audit.ts +0 -14
  53. package/examples/provisions/src/schema.ts +0 -16
  54. package/examples/provisions/src/vault.ts +0 -16
  55. package/examples/provisions/tests/orders.test.ts +0 -18
  56. package/examples/skyport/evals/triage.jsonl +0 -1
  57. package/examples/skyport/oke.config.ts +0 -44
  58. package/examples/skyport/oke.images.lock +0 -4
  59. package/examples/skyport/package.json +0 -16
  60. package/examples/skyport/src/ai.ts +0 -25
  61. package/examples/skyport/src/app.ts +0 -57
  62. package/examples/skyport/src/channels.ts +0 -12
  63. package/examples/skyport/src/core.ts +0 -4
  64. package/examples/skyport/src/flows/bookings/index.ts +0 -50
  65. package/examples/skyport/src/flows/bookings/shapes.ts +0 -6
  66. package/examples/skyport/src/flows/bookings/signals.ts +0 -9
  67. package/examples/skyport/src/flows/notifications/index.ts +0 -16
  68. package/examples/skyport/src/flows/payments/index.ts +0 -14
  69. package/examples/skyport/src/flows/payments/shapes.ts +0 -5
  70. package/examples/skyport/src/flows/support/index.ts +0 -38
  71. package/examples/skyport/src/flows/users/elements.ts +0 -2
  72. package/examples/skyport/src/flows/users/index.ts +0 -12
  73. package/examples/skyport/src/flows/users/shapes.ts +0 -6
  74. package/examples/skyport/src/gates.ts +0 -10
  75. package/examples/skyport/src/journeys.ts +0 -9
  76. package/examples/skyport/src/locales/ar.ts +0 -4
  77. package/examples/skyport/src/locales/en.ts +0 -4
  78. package/examples/skyport/src/plugins/audit-schema.ts +0 -7
  79. package/examples/skyport/src/plugins/audit.ts +0 -14
  80. package/examples/skyport/src/plugins/panel.tsx +0 -4
  81. package/examples/skyport/src/schema.ts +0 -22
  82. package/examples/skyport/src/vault.ts +0 -24
  83. package/examples/skyport/tests/support.test.ts +0 -16
  84. package/src/sync-templates.ts +0 -86
  85. package/templates/full/README.md +0 -49
  86. package/templates/full/oke.config.ts +0 -68
  87. package/templates/full/package.json +0 -15
  88. package/templates/full/src/ai.ts +0 -11
  89. package/templates/full/src/app.ts +0 -34
  90. package/templates/full/src/channels.ts +0 -9
  91. package/templates/full/src/core.ts +0 -4
  92. package/templates/full/src/flows/main/index.ts +0 -45
  93. package/templates/full/src/flows/main/shapes.ts +0 -3
  94. package/templates/full/src/flows/main/signals.ts +0 -8
  95. package/templates/full/src/gates.ts +0 -4
  96. package/templates/full/src/locales/ar.ts +0 -3
  97. package/templates/full/src/locales/en.ts +0 -3
  98. package/templates/full/src/schema.ts +0 -8
  99. package/templates/full/src/vault.ts +0 -9
  100. package/templates/full/tests/full.test.ts +0 -11
  101. package/templates/hello/README.md +0 -38
  102. package/templates/hello/oke.config.ts +0 -3
  103. package/templates/hello/package.json +0 -14
  104. package/templates/hello/src/app.ts +0 -10
  105. package/templates/hello/src/flows/hello/index.ts +0 -27
  106. package/templates/hello/tests/hello.test.ts +0 -10
  107. package/templates/minimal/README.md +0 -41
  108. package/templates/minimal/oke.config.ts +0 -14
  109. package/templates/minimal/package.json +0 -15
  110. package/templates/minimal/src/app.ts +0 -13
  111. package/templates/minimal/src/flows/main/index.ts +0 -49
  112. package/templates/minimal/src/schema.ts +0 -8
  113. package/templates/minimal/tests/minimal.test.ts +0 -13
  114. package/templates/standard/README.md +0 -47
  115. package/templates/standard/src/core.ts +0 -4
  116. package/templates/standard/src/schema.ts +0 -9
  117. /package/{templates/standard → template}/src/app.ts +0 -0
  118. /package/{templates/standard → template}/src/channels.ts +0 -0
  119. /package/{templates/standard → template}/src/flows/main/index.ts +0 -0
  120. /package/{templates/standard → template}/src/flows/main/shapes.ts +0 -0
  121. /package/{templates/standard → template}/src/flows/main/signals.ts +0 -0
  122. /package/{templates/standard → template}/src/gates.ts +0 -0
  123. /package/{templates/standard → template}/src/locales/ar.ts +0 -0
  124. /package/{templates/standard → template}/src/locales/en.ts +0 -0
  125. /package/{templates/standard → template}/src/vault.ts +0 -0
  126. /package/{templates/standard → template}/tests/standard.test.ts +0 -0
package/src/cli.ts CHANGED
@@ -1,25 +1,14 @@
1
1
  /**
2
- * `create-oke` CLI — clean templates by default; teaching examples opt-in.
2
+ * `create-oke` CLI — one standard starter.
3
3
  *
4
4
  * ```bash
5
- * bunx create-oke@latest <name> [--template hello|minimal|standard|full]
6
- * bunx create-oke@latest <name> --from-example notes|linkly|provisions|skyport
5
+ * bunx create-oke@latest <name> [--template standard]
7
6
  * bunx create-oke@latest <name> --yes
8
7
  * bunx create-oke@latest # interactive when stdin is a TTY
9
8
  * ```
10
9
  */
11
10
 
12
- import {
13
- cancel,
14
- confirm,
15
- intro,
16
- isCancel,
17
- note,
18
- outro,
19
- select,
20
- spinner,
21
- text,
22
- } from "@clack/prompts";
11
+ import { cancel, confirm, intro, isCancel, note, outro, spinner, text } from "@clack/prompts";
23
12
  import { basename, relative, resolve } from "node:path";
24
13
  import { existsSync, rmSync } from "node:fs";
25
14
  import { agentsMdContent } from "./agents-md.ts";
@@ -27,23 +16,23 @@ import { docsUrl } from "./docs-origin.ts";
27
16
  import { scaffold, type ScaffoldResult, type ScaffoldSource } from "./scaffold.ts";
28
17
  import {
29
18
  DEFAULT_TEMPLATE,
30
- EXAMPLE_NEW_IDEAS,
31
- EXAMPLES,
32
19
  TEMPLATE_PURPOSES,
33
20
  TEMPLATES,
34
- isExampleId,
35
21
  isTemplateId,
36
- type ExampleId,
37
22
  type TemplateId,
38
23
  } from "./templates.ts";
24
+ import { DEFAULT_SQL_DRIVER, SQL_DRIVERS, isSqlDriverId, type SqlDriverId } from "./transform.ts";
39
25
 
40
26
  /** Parsed CLI arguments. */
41
27
  export type CliArgs = {
42
28
  readonly name: string | undefined;
43
29
  readonly template: TemplateId;
44
- readonly fromExample: ExampleId | undefined;
45
30
  /** True when `--template` / `-t` was present on the argv. */
46
31
  readonly templateExplicit: boolean;
32
+ /** Store SQL driver (`sqlite` default). */
33
+ readonly sqlDriver: SqlDriverId;
34
+ /** True when `--sql` was present on the argv. */
35
+ readonly sqlDriverExplicit: boolean;
47
36
  readonly help: boolean;
48
37
  /** Skip all prompts; use defaults. */
49
38
  readonly yes: boolean;
@@ -57,27 +46,13 @@ export type CliArgs = {
57
46
  readonly targetDir: string | undefined;
58
47
  };
59
48
 
60
- /**
61
- * Sentinel choice for "Start from a worked example" in interactive answers.
62
- * Distinct from clack's internal select value (`__example__`).
63
- */
64
- export const FROM_EXAMPLE_CHOICE = "from-example" as const;
65
-
66
49
  /** Answers collected by the interactive ask step (no clack types). */
67
- export type InteractiveAnswers =
68
- | {
69
- readonly name: string;
70
- readonly choice: TemplateId;
71
- readonly installAndRun: boolean;
72
- readonly agentsMd: boolean;
73
- }
74
- | {
75
- readonly name: string;
76
- readonly choice: typeof FROM_EXAMPLE_CHOICE;
77
- readonly example: ExampleId;
78
- readonly installAndRun: boolean;
79
- readonly agentsMd: boolean;
80
- };
50
+ export type InteractiveAnswers = {
51
+ readonly name: string;
52
+ readonly choice: TemplateId;
53
+ readonly installAndRun: boolean;
54
+ readonly agentsMd: boolean;
55
+ };
81
56
 
82
57
  /**
83
58
  * Canonical scaffold invocation — shared by interactive and flag-driven paths.
@@ -87,6 +62,7 @@ export type ScaffoldCallArgs = {
87
62
  readonly targetDir: string;
88
63
  readonly source: ScaffoldSource;
89
64
  readonly agentsMd: boolean;
65
+ readonly sqlDriver: SqlDriverId;
90
66
  };
91
67
 
92
68
  /**
@@ -97,8 +73,9 @@ export type ScaffoldCallArgs = {
97
73
  export function parseArgs(argv: readonly string[]): CliArgs {
98
74
  let name: string | undefined;
99
75
  let template: TemplateId = DEFAULT_TEMPLATE;
100
- let fromExample: ExampleId | undefined;
101
76
  let templateExplicit = false;
77
+ let sqlDriver: SqlDriverId = DEFAULT_SQL_DRIVER;
78
+ let sqlDriverExplicit = false;
102
79
  let help = false;
103
80
  let yes = false;
104
81
  let install: boolean | undefined;
@@ -131,12 +108,28 @@ export function parseArgs(argv: readonly string[]): CliArgs {
131
108
  agentsMd = true;
132
109
  continue;
133
110
  }
111
+ if (a === "--sql") {
112
+ const next = argv[++i];
113
+ if (!next || !isSqlDriverId(next)) {
114
+ throw new Error(`create-oke: --sql must be one of ${SQL_DRIVERS.join("|")}`);
115
+ }
116
+ sqlDriver = next;
117
+ sqlDriverExplicit = true;
118
+ continue;
119
+ }
120
+ if (a.startsWith("--sql=")) {
121
+ const value = a.slice("--sql=".length);
122
+ if (!isSqlDriverId(value)) {
123
+ throw new Error(`create-oke: --sql must be one of ${SQL_DRIVERS.join("|")}`);
124
+ }
125
+ sqlDriver = value;
126
+ sqlDriverExplicit = true;
127
+ continue;
128
+ }
134
129
  if (a === "--template" || a === "-t") {
135
130
  const next = argv[++i];
136
131
  if (!next || !isTemplateId(next)) {
137
- throw new Error(
138
- `create-oke: --template must be one of ${TEMPLATES.join("|")}`,
139
- );
132
+ throw new Error(`create-oke: --template must be one of ${TEMPLATES.join("|")}`);
140
133
  }
141
134
  template = next;
142
135
  templateExplicit = true;
@@ -145,34 +138,12 @@ export function parseArgs(argv: readonly string[]): CliArgs {
145
138
  if (a.startsWith("--template=")) {
146
139
  const value = a.slice("--template=".length);
147
140
  if (!isTemplateId(value)) {
148
- throw new Error(
149
- `create-oke: --template must be one of ${TEMPLATES.join("|")}`,
150
- );
141
+ throw new Error(`create-oke: --template must be one of ${TEMPLATES.join("|")}`);
151
142
  }
152
143
  template = value;
153
144
  templateExplicit = true;
154
145
  continue;
155
146
  }
156
- if (a === "--from-example") {
157
- const next = argv[++i];
158
- if (!next || !isExampleId(next)) {
159
- throw new Error(
160
- `create-oke: --from-example must be one of ${EXAMPLES.join("|")}`,
161
- );
162
- }
163
- fromExample = next;
164
- continue;
165
- }
166
- if (a.startsWith("--from-example=")) {
167
- const value = a.slice("--from-example=".length);
168
- if (!isExampleId(value)) {
169
- throw new Error(
170
- `create-oke: --from-example must be one of ${EXAMPLES.join("|")}`,
171
- );
172
- }
173
- fromExample = value;
174
- continue;
175
- }
176
147
  if (a.startsWith("-")) {
177
148
  throw new Error(`create-oke: unknown option ${a}`);
178
149
  }
@@ -183,12 +154,6 @@ export function parseArgs(argv: readonly string[]): CliArgs {
183
154
  throw new Error(`create-oke: unexpected argument ${a}`);
184
155
  }
185
156
 
186
- if (templateExplicit && fromExample !== undefined) {
187
- throw new Error(
188
- "create-oke: use either --template or --from-example, not both",
189
- );
190
- }
191
-
192
157
  if (argv.includes("--install") && argv.includes("--no-install")) {
193
158
  throw new Error("create-oke: use either --install or --no-install, not both");
194
159
  }
@@ -200,8 +165,9 @@ export function parseArgs(argv: readonly string[]): CliArgs {
200
165
  return {
201
166
  name,
202
167
  template,
203
- fromExample,
204
168
  templateExplicit,
169
+ sqlDriver,
170
+ sqlDriverExplicit,
205
171
  help,
206
172
  yes,
207
173
  install,
@@ -248,21 +214,18 @@ export function helpText(): string {
248
214
  (id) =>
249
215
  ` ${id.padEnd(12)}${TEMPLATE_PURPOSES[id]}${id === DEFAULT_TEMPLATE ? " (default)" : ""}`,
250
216
  ).join("\n");
251
- const exampleLines = EXAMPLES.map(
252
- (id) => ` ${id.padEnd(12)}${EXAMPLE_NEW_IDEAS[id]}`,
253
- ).join("\n");
254
-
255
217
  return `create-oke — scaffold an okengine app
256
218
 
257
219
  Usage:
258
- bunx create-oke@latest <name> [--template hello|minimal|standard|full]
259
- bunx create-oke@latest <name> --from-example notes|linkly|provisions|skyport
220
+ bunx create-oke@latest <name> [--template standard]
260
221
  bunx create-oke@latest <name> --yes
261
222
  bunx create-oke@latest # interactive (TTY only)
262
223
 
263
224
  Options:
264
225
  -t, --template <id> Clean starter (default: ${DEFAULT_TEMPLATE})
265
- --from-example <id> Teaching example (non-interactive)
226
+ --sql <id> Opt-in Store SQL dialect: ${SQL_DRIVERS.join("|")}
227
+ Default keeps local sqlite · docker/prod postgres.
228
+ --sql postgres pins store.sql local/docker/prod to postgres.
266
229
  -y, --yes No prompts; defaults + bun install (no oke dev)
267
230
  --install Run bun install after scaffold
268
231
  --no-install Skip bun install
@@ -270,16 +233,11 @@ Options:
270
233
  --no-agents-md Skip AGENTS.md
271
234
  -h, --help Show this help
272
235
 
273
- Templates (clean starters from templates/):
236
+ Template:
274
237
  ${templateLines}
275
238
 
276
- --from-example (copies a teaching example, including its business logic and
277
- comments — most new projects want --template instead):
278
- ${exampleLines}
279
-
280
239
  No telemetry. Bun only. On a TTY, a project name alone still opens the wizard
281
- (confirm name, template, install). Non-TTY / --yes / --template /
282
- --from-example stay fully scriptable.
240
+ (confirm name and install). Non-TTY / --yes / --template stay fully scriptable.
283
241
  `;
284
242
  }
285
243
 
@@ -287,20 +245,16 @@ No telemetry. Bun only. On a TTY, a project name alone still opens the wizard
287
245
  * Whether the CLI should open the interactive Clack flow.
288
246
  *
289
247
  * TTY humans get the wizard even when a name is pre-filled. Config flags
290
- * (`--template`, `--from-example`) or `--yes` skip prompts for CI/agents.
248
+ * `--template` or `--yes` skips prompts for CI/agents.
291
249
  *
292
250
  * @param args - Parsed args
293
251
  * @param stdinIsTTY - `process.stdin.isTTY`
294
252
  */
295
- export function shouldPrompt(
296
- args: CliArgs,
297
- stdinIsTTY: boolean | undefined,
298
- ): boolean {
253
+ export function shouldPrompt(args: CliArgs, stdinIsTTY: boolean | undefined): boolean {
299
254
  if (!stdinIsTTY) return false;
300
255
  if (args.help) return false;
301
256
  if (args.yes) return false;
302
257
  if (args.templateExplicit) return false;
303
- if (args.fromExample !== undefined) return false;
304
258
  return true;
305
259
  }
306
260
 
@@ -310,9 +264,6 @@ export function shouldPrompt(
310
264
  * @param args - Parsed args
311
265
  */
312
266
  export function sourceFromArgs(args: CliArgs): ScaffoldSource {
313
- if (args.fromExample !== undefined) {
314
- return { kind: "example", id: args.fromExample };
315
- }
316
267
  return { kind: "template", id: args.template };
317
268
  }
318
269
 
@@ -332,6 +283,7 @@ export function scaffoldArgsFromCli(args: CliArgs): ScaffoldCallArgs {
332
283
  targetDir: args.targetDir,
333
284
  source: sourceFromArgs(args),
334
285
  agentsMd: args.agentsMd,
286
+ sqlDriver: args.sqlDriver,
335
287
  };
336
288
  }
337
289
 
@@ -339,28 +291,21 @@ export function scaffoldArgsFromCli(args: CliArgs): ScaffoldCallArgs {
339
291
  * Pure map from interactive answers → scaffold call args.
340
292
  *
341
293
  * Independent of clack — unit-tested against {@link scaffoldArgsFromCli}
342
- * for every template and every `--from-example` choice.
294
+ * for the standard template.
343
295
  *
344
296
  * @param answers - Collected interactive answers
345
297
  */
346
- export function scaffoldArgsFromAnswers(
347
- answers: InteractiveAnswers,
348
- ): ScaffoldCallArgs {
298
+ export function scaffoldArgsFromAnswers(answers: InteractiveAnswers): ScaffoldCallArgs {
349
299
  const targetDir = resolve(answers.name.trim());
350
300
  const name = basename(targetDir);
351
- if (answers.choice === FROM_EXAMPLE_CHOICE) {
352
- return {
353
- name,
354
- targetDir,
355
- source: { kind: "example", id: answers.example },
356
- agentsMd: answers.agentsMd,
357
- };
358
- }
301
+ // Interactive always keeps the dual-mode default (local sqlite ·
302
+ // docker/prod postgres). Opt into postgres-everywhere with `--sql postgres`.
359
303
  return {
360
304
  name,
361
305
  targetDir,
362
306
  source: { kind: "template", id: answers.choice },
363
307
  agentsMd: answers.agentsMd,
308
+ sqlDriver: DEFAULT_SQL_DRIVER,
364
309
  };
365
310
  }
366
311
 
@@ -371,9 +316,10 @@ export function scaffoldArgsFromAnswers(
371
316
  *
372
317
  * @param partial - Name already known (pre-filled in the prompt)
373
318
  */
374
- export type AskInteractiveFn = (
375
- partial: { readonly name?: string; readonly agentsMd?: boolean },
376
- ) => Promise<InteractiveAnswers | null>;
319
+ export type AskInteractiveFn = (partial: {
320
+ readonly name?: string;
321
+ readonly agentsMd?: boolean;
322
+ }) => Promise<InteractiveAnswers | null>;
377
323
 
378
324
  /**
379
325
  * Collect interactive answers via `@clack/prompts`.
@@ -396,52 +342,8 @@ export async function askInteractiveAnswers(
396
342
  if (isCancel(nameValue)) return null;
397
343
  const name = String(nameValue).trim();
398
344
 
399
- const templateValue = await select({
400
- message: "Template",
401
- options: [
402
- ...TEMPLATES.map((id) => ({
403
- value: id as string,
404
- label: id,
405
- hint: TEMPLATE_PURPOSES[id],
406
- })),
407
- {
408
- value: "__example__",
409
- label: "Start from a worked example",
410
- hint: "Teaching apps with business logic — most projects want a template",
411
- },
412
- ],
413
- initialValue: DEFAULT_TEMPLATE,
414
- });
415
- if (isCancel(templateValue)) return null;
416
-
417
345
  const agentsMd = partial.agentsMd ?? true;
418
346
 
419
- if (templateValue === "__example__") {
420
- const exampleValue = await select({
421
- message: "Example",
422
- options: EXAMPLES.map((id) => ({
423
- value: id,
424
- label: id,
425
- hint: EXAMPLE_NEW_IDEAS[id],
426
- })),
427
- });
428
- if (isCancel(exampleValue)) return null;
429
-
430
- const installAndRunValue = await confirm({
431
- message: "Install dependencies and start oke dev?",
432
- initialValue: true,
433
- });
434
- if (isCancel(installAndRunValue)) return null;
435
-
436
- return {
437
- name,
438
- choice: FROM_EXAMPLE_CHOICE,
439
- example: exampleValue as ExampleId,
440
- installAndRun: Boolean(installAndRunValue),
441
- agentsMd,
442
- };
443
- }
444
-
445
347
  const installAndRunValue = await confirm({
446
348
  message: "Install dependencies and start oke dev?",
447
349
  initialValue: true,
@@ -450,7 +352,7 @@ export async function askInteractiveAnswers(
450
352
 
451
353
  return {
452
354
  name,
453
- choice: templateValue as TemplateId,
355
+ choice: DEFAULT_TEMPLATE,
454
356
  installAndRun: Boolean(installAndRunValue),
455
357
  agentsMd,
456
358
  };
@@ -503,9 +405,7 @@ export async function run(
503
405
 
504
406
  if (args.name === undefined) {
505
407
  console.log(helpText());
506
- console.error(
507
- "create-oke: missing <name>. Pass a name, or run in a TTY for the wizard.",
508
- );
408
+ console.error("create-oke: missing <name>. Pass a name, or run in a TTY for the wizard.");
509
409
  console.error(" Example: bunx create-oke@latest my-app --yes");
510
410
  return 1;
511
411
  }
@@ -513,7 +413,7 @@ export async function run(
513
413
  if (args.yes) {
514
414
  const source = sourceFromArgs(args);
515
415
  console.log(
516
- `Using defaults: ${source.kind}=${source.id} agents-md=${args.agentsMd} install=${shouldInstall(args)}`,
416
+ `Using defaults: ${source.kind}=${source.id} sql=${args.sqlDriver} agents-md=${args.agentsMd} install=${shouldInstall(args)}`,
517
417
  );
518
418
  }
519
419
 
@@ -573,6 +473,7 @@ async function runScaffold(
573
473
  targetDir,
574
474
  source,
575
475
  agentsMd,
476
+ sqlDriver,
576
477
  interactive,
577
478
  install,
578
479
  startDev,
@@ -605,6 +506,7 @@ async function runScaffold(
605
506
  name,
606
507
  source,
607
508
  writeAgentsMd: agentsMd,
509
+ sqlDriver,
608
510
  });
609
511
  if (spun) spun.stop("Scaffolded.");
610
512
 
@@ -2,11 +2,10 @@
2
2
  * Canonical public docs origin for scaffolded AGENTS.md and next-steps.
3
3
  *
4
4
  * Keep in lockstep with `src/docs-origin.ts` in the okengine package.
5
- * Flip both when the custom domain is attached: `https://oke.omqkhafi.dev`.
6
5
  */
7
6
 
8
- /** Live docs site (Vercel). Planned cutover: `https://oke.omqkhafi.dev`. */
9
- export const DOCS_ORIGIN = "https://okengine.vercel.app" as const;
7
+ /** Live docs site. */
8
+ export const DOCS_ORIGIN = "https://oke.omqkhafi.dev" as const;
10
9
 
11
10
  /**
12
11
  * Absolute docs URL for a path under the handbook / site.
package/src/index.ts CHANGED
@@ -5,8 +5,7 @@
5
5
  * ```bash
6
6
  * bunx create-oke@latest my-app
7
7
  * bunx create-oke@latest my-app --yes
8
- * bunx create-oke@latest my-app --template hello
9
- * bunx create-oke@latest my-app --from-example notes
8
+ * bunx create-oke@latest my-app --template standard
10
9
  * ```
11
10
  *
12
11
  * @module
package/src/scaffold.ts CHANGED
@@ -13,26 +13,21 @@ import {
13
13
  writeFileSync,
14
14
  } from "node:fs";
15
15
  import { join, relative, resolve } from "node:path";
16
- import {
17
- resolveExampleDir,
18
- resolveLocalOkengineRoot,
19
- resolveTemplateDir,
20
- type ExampleId,
21
- type TemplateId,
22
- } from "./templates.ts";
16
+ import { resolveLocalOkengineRoot, resolveTemplateDir, type TemplateId } from "./templates.ts";
23
17
  import { agentsMdContent } from "./agents-md.ts";
24
18
  import {
19
+ DEFAULT_SQL_DRIVER,
25
20
  sanitizeProjectName,
26
21
  shouldSkipTemplatePath,
22
+ transformConfigForSqlDriver,
27
23
  transformPackageJson,
28
24
  resolveOkengineDependency,
29
25
  type ScaffoldPackageJson,
26
+ type SqlDriverId,
30
27
  } from "./transform.ts";
31
28
 
32
- /** Where the scaffold copies from. */
33
- export type ScaffoldSource =
34
- | { readonly kind: "template"; readonly id: TemplateId }
35
- | { readonly kind: "example"; readonly id: ExampleId };
29
+ /** The bundled starter copied by the scaffold. */
30
+ export type ScaffoldSource = { readonly kind: "template"; readonly id: TemplateId };
36
31
 
37
32
  /** Options for {@link scaffold}. */
38
33
  export type ScaffoldOptions = {
@@ -40,10 +35,15 @@ export type ScaffoldOptions = {
40
35
  readonly targetDir: string;
41
36
  /** npm package / folder name. */
42
37
  readonly name: string;
43
- /** Template or teaching-example source. */
38
+ /** Starter template source. */
44
39
  readonly source: ScaffoldSource;
45
40
  /** Write root `AGENTS.md` (default true). */
46
41
  readonly writeAgentsMd?: boolean;
42
+ /**
43
+ * Store SQL driver — pins `oke.config.ts` `store.sql` local/docker/prod
44
+ * when `postgres`. Default `sqlite` keeps the dual-mode config.
45
+ */
46
+ readonly sqlDriver?: SqlDriverId;
47
47
  };
48
48
 
49
49
  /** Result of a successful scaffold. */
@@ -54,39 +54,35 @@ export type ScaffoldResult = {
54
54
  /** Display label (`standard`, `notes`, …). */
55
55
  readonly label: string;
56
56
  readonly okengineDependency: string;
57
+ /** Store SQL driver applied to schema + config. */
58
+ readonly sqlDriver: SqlDriverId;
57
59
  /** Relative paths written (POSIX), sorted. */
58
60
  readonly files: readonly string[];
59
61
  };
60
62
 
61
63
  /**
62
- * Scaffold a new okengine project from a clean template or teaching example.
64
+ * Scaffold a new okengine project from the standard template.
63
65
  *
64
66
  * @param options - Name, source, destination
65
67
  */
66
68
  export function scaffold(options: ScaffoldOptions): ScaffoldResult {
67
69
  const name = sanitizeProjectName(options.name);
68
70
  const targetDir = resolve(options.targetDir);
69
- const sourceDir =
70
- options.source.kind === "template"
71
- ? resolveTemplateDir(options.source.id)
72
- : resolveExampleDir(options.source.id);
71
+ const sourceDir = resolveTemplateDir(options.source.id);
73
72
  const label = options.source.id;
73
+ const sqlDriver = options.sqlDriver ?? DEFAULT_SQL_DRIVER;
74
74
 
75
75
  if (existsSync(targetDir)) {
76
76
  const entries = readdirSync(targetDir);
77
77
  if (entries.length > 0) {
78
- throw new Error(
79
- `create-oke: target directory is not empty: ${targetDir}`,
80
- );
78
+ throw new Error(`create-oke: target directory is not empty: ${targetDir}`);
81
79
  }
82
80
  } else {
83
81
  mkdirSync(targetDir, { recursive: true });
84
82
  }
85
83
 
86
84
  try {
87
- const okengineDependency = resolveOkengineDependency(
88
- resolveLocalOkengineRoot(),
89
- );
85
+ const okengineDependency = resolveOkengineDependency(resolveLocalOkengineRoot());
90
86
  const written: string[] = [];
91
87
 
92
88
  copyTree(sourceDir, targetDir, "", written);
@@ -95,18 +91,25 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
95
91
  if (!existsSync(pkgPath)) {
96
92
  throw new Error(`create-oke: source "${label}" has no package.json`);
97
93
  }
98
- const sourcePkg = JSON.parse(
99
- readFileSync(pkgPath, "utf8"),
100
- ) as ScaffoldPackageJson;
94
+ const sourcePkg = JSON.parse(readFileSync(pkgPath, "utf8")) as ScaffoldPackageJson;
101
95
  const nextPkg = transformPackageJson(sourcePkg, name, okengineDependency);
102
96
  writeFileSync(pkgPath, `${JSON.stringify(nextPkg, null, 2)}\n`, "utf8");
103
97
 
98
+ applySqlDriverTransforms(targetDir, sqlDriver);
99
+
104
100
  if (options.writeAgentsMd !== false) {
105
101
  const agentsPath = join(targetDir, "AGENTS.md");
106
102
  writeFileSync(agentsPath, agentsMdContent(name), "utf8");
107
103
  if (!written.includes("AGENTS.md")) written.push("AGENTS.md");
108
104
  }
109
105
 
106
+ const envExample = join(targetDir, ".env.example");
107
+ const envLocal = join(targetDir, ".env.local");
108
+ if (existsSync(envExample) && !existsSync(envLocal)) {
109
+ cpSync(envExample, envLocal);
110
+ if (!written.includes(".env.local")) written.push(".env.local");
111
+ }
112
+
110
113
  written.sort();
111
114
  return {
112
115
  targetDir,
@@ -114,6 +117,7 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
114
117
  source: options.source,
115
118
  label,
116
119
  okengineDependency,
120
+ sqlDriver,
117
121
  files: written,
118
122
  };
119
123
  } catch (e) {
@@ -122,6 +126,27 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
122
126
  }
123
127
  }
124
128
 
129
+ /**
130
+ * For postgres, pin `store.sql` local/docker/prod in `oke.config.ts`.
131
+ *
132
+ * The default template ships abstract `src/schema.decl.ts` — dialect is
133
+ * emitted from the active `store.sql` driver, so there is no hand-written
134
+ * `sqliteTable`/`pgTable` source to rewrite. `sqlite` keeps the template
135
+ * dual-mode config (`local: sqlite` · `docker`/`prod: postgres`) untouched.
136
+ *
137
+ * @param targetDir - Scaffolded project root
138
+ * @param sqlDriver - Chosen store.sql driver
139
+ */
140
+ function applySqlDriverTransforms(targetDir: string, sqlDriver: SqlDriverId): void {
141
+ if (sqlDriver !== "postgres") return;
142
+
143
+ const configPath = join(targetDir, "oke.config.ts");
144
+ if (existsSync(configPath)) {
145
+ const next = transformConfigForSqlDriver(readFileSync(configPath, "utf8"), sqlDriver);
146
+ writeFileSync(configPath, next, "utf8");
147
+ }
148
+ }
149
+
125
150
  /**
126
151
  * Recursively copy template files, applying {@link shouldSkipTemplatePath}.
127
152
  *
@@ -130,12 +155,7 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
130
155
  * @param rel - Relative path under the roots
131
156
  * @param written - Accumulator for relative paths written
132
157
  */
133
- function copyTree(
134
- srcRoot: string,
135
- dstRoot: string,
136
- rel: string,
137
- written: string[],
138
- ): void {
158
+ function copyTree(srcRoot: string, dstRoot: string, rel: string, written: string[]): void {
139
159
  const src = rel ? join(srcRoot, rel) : srcRoot;
140
160
  const st = statSync(src);
141
161
  if (st.isDirectory()) {