@beechcms/cli 0.6.0-preview.2 → 0.6.0-preview.4

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 (57) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-lint.log +1 -0
  3. package/coverage/coverage-summary.json +3 -0
  4. package/dist/index.js +563 -230
  5. package/package.json +12 -11
  6. package/src/commands/deploy.ts +126 -126
  7. package/src/commands/generate-types.ts +65 -0
  8. package/src/commands/init.ts +599 -599
  9. package/src/commands/onboard.ts +32 -32
  10. package/src/commands/reset.ts +157 -0
  11. package/src/commands/schema-diff.ts +78 -0
  12. package/src/commands/seed-create.ts +192 -192
  13. package/src/commands/seed-load.ts +206 -235
  14. package/src/commands/update.ts +54 -54
  15. package/src/commands/validate.ts +80 -80
  16. package/src/index.ts +24 -17
  17. package/src/lib/migration-writer.ts +106 -0
  18. package/src/lib/schema-diff.ts +175 -150
  19. package/src/lib/wrangler.ts +129 -129
  20. package/src/test/generate-types.test.ts +58 -0
  21. package/src/test/reset.test.ts +128 -0
  22. package/src/test/schema-diff.test.ts +232 -0
  23. package/src/test/seed-load.test.ts +158 -0
  24. package/src/test/validate.test.ts +261 -0
  25. package/tsconfig.json +16 -16
  26. package/tsconfig.tsbuildinfo +1 -1
  27. package/vitest.config.ts +33 -33
  28. package/coverage/base.css +0 -224
  29. package/coverage/block-navigation.js +0 -87
  30. package/coverage/favicon.png +0 -0
  31. package/coverage/index.html +0 -116
  32. package/coverage/lcov-report/base.css +0 -224
  33. package/coverage/lcov-report/block-navigation.js +0 -87
  34. package/coverage/lcov-report/favicon.png +0 -0
  35. package/coverage/lcov-report/index.html +0 -116
  36. package/coverage/lcov-report/prettify.css +0 -1
  37. package/coverage/lcov-report/prettify.js +0 -2
  38. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  39. package/coverage/lcov-report/sorter.js +0 -210
  40. package/coverage/lcov-report/validate.ts.html +0 -325
  41. package/coverage/lcov.info +0 -80
  42. package/coverage/prettify.css +0 -1
  43. package/coverage/prettify.js +0 -2
  44. package/coverage/sort-arrow-sprite.png +0 -0
  45. package/coverage/sorter.js +0 -210
  46. package/coverage/validate.ts.html +0 -325
  47. package/dist/commands/seed-load.d.ts +0 -8
  48. package/dist/commands/seed-load.d.ts.map +0 -1
  49. package/dist/commands/seed-load.js +0 -89
  50. package/dist/index.d.ts +0 -3
  51. package/dist/index.d.ts.map +0 -1
  52. package/dist/lib/schema-diff.d.ts +0 -15
  53. package/dist/lib/schema-diff.d.ts.map +0 -1
  54. package/dist/lib/schema-diff.js +0 -37
  55. package/dist/lib/wrangler.d.ts +0 -17
  56. package/dist/lib/wrangler.d.ts.map +0 -1
  57. package/dist/lib/wrangler.js +0 -65
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/commands/seed-load.ts
2
- import pc2 from "picocolors";
2
+ import pc3 from "picocolors";
3
3
  import {
4
4
  SEED_REGISTRY as SEED_REGISTRY2,
5
5
  generateCreateTable,
@@ -107,7 +107,46 @@ function resolveDbName(configPath) {
107
107
  }
108
108
 
109
109
  // src/lib/schema-diff.ts
110
+ import pc from "picocolors";
110
111
  import { getExpectedColumns } from "@beechcms/core";
112
+ function isSeedClean(diff) {
113
+ return diff.tableExists && diff.columns.every((c) => c.status === "ok");
114
+ }
115
+ function renderSeedDiff(diff) {
116
+ const table = `content_${diff.slug}`;
117
+ if (!diff.tableExists) {
118
+ console.log(pc.red(` \u2717 ${table} \u2014 table missing`));
119
+ return;
120
+ }
121
+ const problems = diff.columns.filter((c) => c.status !== "ok");
122
+ if (problems.length === 0) {
123
+ console.log(pc.green(` \u2713 ${table}`));
124
+ return;
125
+ }
126
+ console.log(pc.yellow(` \u26A0 ${table}`));
127
+ for (const col of problems) {
128
+ switch (col.status) {
129
+ case "missing":
130
+ console.log(pc.red(` + missing column: ${col.name} ${col.expectedType}`));
131
+ break;
132
+ case "extra":
133
+ console.log(pc.dim(` ~ orphaned column: "${col.name}" (${col.actualType}) \u2014 in DB, not in seeds.ts`));
134
+ break;
135
+ case "type_mismatch":
136
+ console.log(pc.red(` \u2260 type mismatch: ${col.name} (expected ${col.expectedType}, got ${col.actualType})`));
137
+ break;
138
+ case "fk_missing":
139
+ console.log(pc.red(` \u292C missing FK: ${col.name} \u2192 content_${col.expectedTarget}(id)`));
140
+ break;
141
+ case "fk_mismatch":
142
+ console.log(pc.yellow(` \u292C FK mismatch: ${col.name} expected ${col.expected}, got ${col.actual}`));
143
+ break;
144
+ case "index_missing":
145
+ console.log(pc.yellow(` \u2298 missing index on ${col.name}`));
146
+ break;
147
+ }
148
+ }
149
+ }
111
150
  async function diffSeed(seed, options) {
112
151
  const tableName = `content_${seed.slug}`;
113
152
  const expected = getExpectedColumns(seed);
@@ -185,7 +224,7 @@ async function diffSeed(seed, options) {
185
224
  }
186
225
 
187
226
  // src/commands/validate.ts
188
- import pc from "picocolors";
227
+ import pc2 from "picocolors";
189
228
  import { SEED_REGISTRY, validateSeedDefinitions } from "@beechcms/core";
190
229
  function validateSeeds(registry) {
191
230
  return validateSeedDefinitions(Object.values(registry));
@@ -193,17 +232,17 @@ function validateSeeds(registry) {
193
232
  async function validate(args) {
194
233
  const registry = args.registry ?? SEED_REGISTRY;
195
234
  if (Object.keys(registry).length === 0) {
196
- console.warn(pc.yellow("\n Warning: SEED_REGISTRY is empty. Create a seeds.ts in your project root.\n"));
235
+ console.warn(pc2.yellow("\n Warning: SEED_REGISTRY is empty. Create a seeds.ts in your project root.\n"));
197
236
  return;
198
237
  }
199
- console.log(pc.cyan("\n beech validate \u2014 checking seeds\n"));
238
+ console.log(pc2.cyan("\n beech validate \u2014 checking seeds\n"));
200
239
  const errors = validateSeeds(registry);
201
240
  const fatalErrors = errors.filter((e) => e.fatal);
202
241
  const warnings = errors.filter((e) => !e.fatal);
203
242
  for (const e of fatalErrors) {
204
- console.log(pc.red(` \u2717 ${e.slug} (fatal)`));
243
+ console.log(pc2.red(` \u2717 ${e.slug} (fatal)`));
205
244
  for (const msg of e.messages) {
206
- console.log(pc.red(` \u2192 ${msg}`));
245
+ console.log(pc2.red(` \u2192 ${msg}`));
207
246
  }
208
247
  }
209
248
  const warningMap = new Map(warnings.map((e) => [e.slug, e.messages]));
@@ -213,12 +252,12 @@ async function validate(args) {
213
252
  if (!msgs) {
214
253
  if (!allWarningSlugsSeen.has(seed.slug)) {
215
254
  const hasFatal = fatalErrors.some((e) => e.slug === seed.slug);
216
- if (!hasFatal) console.log(pc.green(` \u2713 ${seed.slug}`));
255
+ if (!hasFatal) console.log(pc2.green(` \u2713 ${seed.slug}`));
217
256
  }
218
257
  } else {
219
- console.log(pc.yellow(` \u26A0 ${seed.slug}`));
258
+ console.log(pc2.yellow(` \u26A0 ${seed.slug}`));
220
259
  for (const msg of msgs) {
221
- console.log(pc.yellow(` \u2192 ${msg}`));
260
+ console.log(pc2.yellow(` \u2192 ${msg}`));
222
261
  }
223
262
  }
224
263
  }
@@ -227,15 +266,15 @@ async function validate(args) {
227
266
  const totalWarnings = warnings.reduce((n, e) => n + e.messages.length, 0);
228
267
  if (totalFatal > 0) {
229
268
  const s = totalFatal !== 1 ? "s" : "";
230
- console.log(pc.red(` Found ${totalFatal} fatal error${s}. Fix before loading.
269
+ console.log(pc2.red(` Found ${totalFatal} fatal error${s}. Fix before loading.
231
270
  `));
232
271
  process.exit(1);
233
272
  } else if (totalWarnings > 0) {
234
273
  const s = totalWarnings !== 1 ? "s" : "";
235
- console.log(pc.yellow(` Found ${totalWarnings} warning${s}. Review seeds above.
274
+ console.log(pc2.yellow(` Found ${totalWarnings} warning${s}. Review seeds above.
236
275
  `));
237
276
  } else {
238
- console.log(pc.green(" All seeds valid.\n"));
277
+ console.log(pc2.green(" All seeds valid.\n"));
239
278
  }
240
279
  }
241
280
 
@@ -267,102 +306,76 @@ function buildStatements(seed) {
267
306
  }
268
307
  async function runDiff(options, registry) {
269
308
  const seeds = sortSeedsByDependencies(Object.values(registry));
270
- console.log(pc2.cyan("\n Diffing schema\u2026\n"));
309
+ console.log(pc3.cyan("\n Diffing schema\u2026\n"));
271
310
  let allOk = true;
272
311
  for (const seed of seeds) {
273
312
  const result = await diffSeed(seed, options);
274
- const tableName = `content_${seed.slug}`;
275
- if (!result.tableExists) {
276
- console.log(pc2.red(` \u2717 ${tableName} \u2014 table missing`));
277
- allOk = false;
278
- continue;
279
- }
280
- const problems = result.columns.filter((c) => c.status !== "ok");
281
- if (problems.length === 0) {
282
- console.log(pc2.green(` \u2713 ${tableName}`));
283
- continue;
284
- }
285
- allOk = false;
286
- console.log(pc2.yellow(` \u26A0 ${tableName}`));
287
- for (const col of problems) {
288
- if (col.status === "missing") {
289
- console.log(pc2.red(` + missing column: ${col.name} ${col.expectedType}`));
290
- } else if (col.status === "extra") {
291
- console.log(pc2.dim(` ~ orphaned column: "${col.name}" (${col.actualType}) \u2014 exists in DB but not in seeds.ts`));
292
- } else if (col.status === "type_mismatch") {
293
- console.log(pc2.red(` \u2260 type mismatch: ${col.name} (expected ${col.expectedType}, got ${col.actualType})`));
294
- } else if (col.status === "fk_missing") {
295
- console.log(pc2.red(` \u292C missing FK: ${col.name} \u2192 content_${col.expectedTarget}(id)`));
296
- } else if (col.status === "fk_mismatch") {
297
- console.log(pc2.yellow(` \u292C FK mismatch: ${col.name} expected ${col.expected}, got ${col.actual}`));
298
- } else if (col.status === "index_missing") {
299
- console.log(pc2.yellow(` \u2298 missing index on ${col.name}`));
300
- }
301
- }
313
+ renderSeedDiff(result);
314
+ if (!isSeedClean(result)) allOk = false;
302
315
  }
303
316
  console.log("");
304
317
  if (allOk) {
305
- console.log(pc2.green(" Schema matches seeds. No action needed.\n"));
318
+ console.log(pc3.green(" Schema matches seeds. No action needed.\n"));
306
319
  } else {
307
- console.log(pc2.yellow(" Run `beech seed:load` to apply missing tables/columns.\n"));
320
+ console.log(pc3.yellow(" Run `beech seed:load` to apply missing tables/columns.\n"));
308
321
  }
309
322
  }
310
323
  async function runLoad(options, dryRun, registry) {
311
324
  const seeds = sortSeedsByDependencies(Object.values(registry));
312
325
  if (dryRun) {
313
- console.log(pc2.cyan("\n -- dry-run: SQL that would be executed\n"));
326
+ console.log(pc3.cyan("\n -- dry-run: SQL that would be executed\n"));
314
327
  for (const seed of seeds) {
315
328
  const stmts = buildStatements(seed);
316
- console.log(pc2.dim(` -- content_${seed.slug}`));
329
+ console.log(pc3.dim(` -- content_${seed.slug}`));
317
330
  for (const stmt of stmts) {
318
331
  console.log(stmt + "\n");
319
332
  }
320
- console.log(pc2.dim(` -- register ${seed.slug} in seeds table`));
333
+ console.log(pc3.dim(` -- register ${seed.slug} in seeds table`));
321
334
  console.log(buildSeedRegistrationSql(seed) + "\n");
322
335
  }
323
- console.log(pc2.dim(" -- bump registry_version"));
336
+ console.log(pc3.dim(" -- bump registry_version"));
324
337
  console.log(SEED_META_BUMP_SQL + "\n");
325
338
  return;
326
339
  }
327
- console.log(pc2.cyan(`
340
+ console.log(pc3.cyan(`
328
341
  Loading seeds into ${options.local ? "local" : "remote"} D1 (${options.db})\u2026
329
342
  `));
330
343
  for (const seed of seeds) {
331
344
  const stmts = [...buildStatements(seed), buildSeedRegistrationSql(seed)];
332
345
  const sql = stmts.join("\n\n") + "\n";
333
- process.stdout.write(` ${pc2.dim("\u2192")} content_${seed.slug}\u2026 `);
346
+ process.stdout.write(` ${pc3.dim("\u2192")} content_${seed.slug}\u2026 `);
334
347
  const ok = executeD1File(sql, options);
335
348
  if (!ok) {
336
- console.log(pc2.red("failed"));
337
- console.log(pc2.red(`
349
+ console.log(pc3.red("failed"));
350
+ console.log(pc3.red(`
338
351
  \u2717 Failed to apply schema for content_${seed.slug}
339
352
  `));
340
- console.log(pc2.dim(" wrangler reported an error above."));
341
- console.log(pc2.dim(` Most likely causes:`));
342
- console.log(pc2.dim(` - Database "${options.db}" not found or wrong database_id`));
353
+ console.log(pc3.dim(" wrangler reported an error above."));
354
+ console.log(pc3.dim(` Most likely causes:`));
355
+ console.log(pc3.dim(` - Database "${options.db}" not found or wrong database_id`));
343
356
  if (!options.local) {
344
- console.log(pc2.dim(" - Not logged in to Cloudflare"));
345
- console.log(pc2.cyan("\n \u2192 Run: npx wrangler login"));
346
- console.log(pc2.cyan(" \u2192 Then: npx beech seed:load\n"));
357
+ console.log(pc3.dim(" - Not logged in to Cloudflare"));
358
+ console.log(pc3.cyan("\n \u2192 Run: npx wrangler login"));
359
+ console.log(pc3.cyan(" \u2192 Then: npx beech seed:load\n"));
347
360
  } else {
348
- console.log(pc2.cyan("\n \u2192 Run: npx beech init --db --local # re-initialise local DB"));
349
- console.log(pc2.cyan(" \u2192 Then: npx beech seed:load --local\n"));
361
+ console.log(pc3.cyan("\n \u2192 Run: npx beech init --db --local # re-initialise local DB"));
362
+ console.log(pc3.cyan(" \u2192 Then: npx beech seed:load --local\n"));
350
363
  }
351
364
  process.exit(1);
352
365
  }
353
- console.log(pc2.green("done"));
366
+ console.log(pc3.green("done"));
354
367
  }
355
368
  executeD1File(SEED_META_BUMP_SQL, options);
356
- console.log(pc2.green("\n All seeds loaded.\n"));
357
- console.log(pc2.dim(" Definitions registered in the database."));
358
- console.log(pc2.dim(" seed.ts is no longer required at runtime \u2014 you may keep it for code-first edits or delete it.\n"));
369
+ console.log(pc3.green("\n All seeds loaded.\n"));
370
+ console.log(pc3.dim(" Definitions registered in the database."));
371
+ console.log(pc3.dim(" seed.ts is no longer required at runtime \u2014 you may keep it for code-first edits or delete it.\n"));
359
372
  }
360
373
  async function seedLoad(args) {
361
374
  const registry = args.registry ?? SEED_REGISTRY2;
362
375
  if (Object.keys(registry).length === 0) {
363
- console.log(pc2.yellow("\n \u2717 No seeds found\n"));
364
- console.log(pc2.dim(" Create a seeds.ts file in your project root with at least one content type."));
365
- console.log(pc2.cyan("\n \u2192 Run: npx beech seed:create\n"));
376
+ console.log(pc3.yellow("\n \u2717 No seeds found\n"));
377
+ console.log(pc3.dim(" Create a seeds.ts file in your project root with at least one content type."));
378
+ console.log(pc3.cyan("\n \u2192 Run: npx beech seed:create\n"));
366
379
  return;
367
380
  }
368
381
  const validationErrors = validateSeeds(registry);
@@ -371,13 +384,13 @@ async function seedLoad(args) {
371
384
  if (fatalErrors.length > 0) {
372
385
  const total = fatalErrors.reduce((n, e) => n + e.messages.length, 0);
373
386
  const s = total !== 1 ? "s" : "";
374
- console.log(pc2.red(`
387
+ console.log(pc3.red(`
375
388
  \u2717 Seed validation found ${total} fatal error${s}. Cannot load schema.
376
389
  `));
377
390
  for (const e of fatalErrors) {
378
- console.log(pc2.red(` \u2717 ${e.slug}`));
391
+ console.log(pc3.red(` \u2717 ${e.slug}`));
379
392
  for (const msg of e.messages) {
380
- console.log(pc2.red(` \u2192 ${msg}`));
393
+ console.log(pc3.red(` \u2192 ${msg}`));
381
394
  }
382
395
  }
383
396
  console.log("");
@@ -386,10 +399,10 @@ async function seedLoad(args) {
386
399
  if (warnings.length > 0) {
387
400
  const total = warnings.reduce((n, e) => n + e.messages.length, 0);
388
401
  const s = total !== 1 ? "s" : "";
389
- console.log(pc2.yellow(`
402
+ console.log(pc3.yellow(`
390
403
  \u26A0 Seed validation found ${total} issue${s}. Schema changes will still be applied.
391
404
  `));
392
- console.log(pc2.dim(' Run "npx beech validate" for details.\n'));
405
+ console.log(pc3.dim(' Run "npx beech validate" for details.\n'));
393
406
  }
394
407
  const configPath = findWranglerConfig();
395
408
  const db = args.db ?? resolveDbName(configPath);
@@ -405,17 +418,17 @@ async function seedLoad(args) {
405
418
  options
406
419
  );
407
420
  if (rows.length < 2) {
408
- console.log(pc2.red("\n \u2717 System tables not found (seeds, seed_meta)\n"));
409
- console.log(pc2.dim(" Run `beech init --db` first to initialise the database."));
421
+ console.log(pc3.red("\n \u2717 System tables not found (seeds, seed_meta)\n"));
422
+ console.log(pc3.dim(" Run `beech init --db` first to initialise the database."));
410
423
  const flag = args.local ? " --local" : "";
411
- console.log(pc2.cyan(`
424
+ console.log(pc3.cyan(`
412
425
  \u2192 Run: npx beech init --db${flag}
413
426
  `));
414
427
  process.exit(1);
415
428
  }
416
429
  } catch {
417
- console.log(pc2.red("\n \u2717 Could not query the database\n"));
418
- console.log(pc2.dim(" Run `beech init --db` first to initialise the database."));
430
+ console.log(pc3.red("\n \u2717 Could not query the database\n"));
431
+ console.log(pc3.dim(" Run `beech init --db` first to initialise the database."));
419
432
  process.exit(1);
420
433
  }
421
434
  }
@@ -427,7 +440,7 @@ async function seedLoad(args) {
427
440
  }
428
441
 
429
442
  // src/commands/init.ts
430
- import pc3 from "picocolors";
443
+ import pc4 from "picocolors";
431
444
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
432
445
  import { createInterface } from "node:readline/promises";
433
446
  import { resolve as resolve2, basename } from "node:path";
@@ -701,10 +714,10 @@ function patchWranglerConfig(configPath, dbId) {
701
714
  }
702
715
  }
703
716
  function printManualDbInstructions() {
704
- console.log(pc3.dim("\n Update your D1 database_id in wrangler.jsonc,"));
705
- console.log(pc3.dim(" or create a new database with:"));
706
- console.log(pc3.cyan("\n \u2192 Run: npx wrangler d1 create my-project-db"));
707
- console.log(pc3.cyan(" \u2192 Then: npx beech init --db\n"));
717
+ console.log(pc4.dim("\n Update your D1 database_id in wrangler.jsonc,"));
718
+ console.log(pc4.dim(" or create a new database with:"));
719
+ console.log(pc4.cyan("\n \u2192 Run: npx wrangler d1 create my-project-db"));
720
+ console.log(pc4.cyan(" \u2192 Then: npx beech init --db\n"));
708
721
  }
709
722
  function echoApiKeys(configPath) {
710
723
  if (!configPath) return;
@@ -716,16 +729,16 @@ function echoApiKeys(configPath) {
716
729
  const readKey = vars["PUBLIC_READ_API_KEY"];
717
730
  const writeKey = vars["PUBLIC_WRITE_API_KEY"];
718
731
  if (!readKey && !writeKey) return;
719
- console.log(pc3.dim(" API keys detected in wrangler.jsonc:\n"));
732
+ console.log(pc4.dim(" API keys detected in wrangler.jsonc:\n"));
720
733
  if (readKey) {
721
734
  const masked = readKey.length > 8 ? readKey.slice(0, 4) + "****" + readKey.slice(-4) : "****";
722
- console.log(pc3.dim(` PUBLIC_READ_API_KEY = ${masked}`));
735
+ console.log(pc4.dim(` PUBLIC_READ_API_KEY = ${masked}`));
723
736
  }
724
737
  if (writeKey) {
725
738
  const masked = writeKey.length > 8 ? writeKey.slice(0, 4) + "****" + writeKey.slice(-4) : "****";
726
- console.log(pc3.dim(` PUBLIC_WRITE_API_KEY = ${masked}`));
739
+ console.log(pc4.dim(` PUBLIC_WRITE_API_KEY = ${masked}`));
727
740
  }
728
- console.log(pc3.dim("\n Use these in your frontend as the X-API-Key header.\n"));
741
+ console.log(pc4.dim("\n Use these in your frontend as the X-API-Key header.\n"));
729
742
  } catch {
730
743
  }
731
744
  }
@@ -733,42 +746,42 @@ function checkFiles(cwd, checkDevVars) {
733
746
  let ok = true;
734
747
  const workerExists = existsSync2(resolve2(cwd, "worker.ts")) || existsSync2(resolve2(cwd, "worker.js"));
735
748
  if (!workerExists) {
736
- console.log(pc3.red(" \u2717 worker.ts \u2014 missing (required)"));
749
+ console.log(pc4.red(" \u2717 worker.ts \u2014 missing (required)"));
737
750
  ok = false;
738
751
  } else {
739
- console.log(pc3.green(" \u2713 worker.ts"));
752
+ console.log(pc4.green(" \u2713 worker.ts"));
740
753
  }
741
754
  const configPath = findWranglerConfig();
742
755
  const configInCwd = configPath && (configPath === resolve2(cwd, "wrangler.jsonc") || configPath === resolve2(cwd, "wrangler.json") || configPath === resolve2(cwd, "wrangler.toml"));
743
756
  if (!configInCwd) {
744
- console.log(pc3.red(" \u2717 wrangler.jsonc \u2014 missing (required)"));
757
+ console.log(pc4.red(" \u2717 wrangler.jsonc \u2014 missing (required)"));
745
758
  ok = false;
746
759
  } else {
747
- console.log(pc3.green(` \u2713 ${basename(configPath)}`));
760
+ console.log(pc4.green(` \u2713 ${basename(configPath)}`));
748
761
  }
749
762
  const seedsExists = existsSync2(resolve2(cwd, "seeds.ts")) || existsSync2(resolve2(cwd, "seeds.js")) || existsSync2(resolve2(cwd, "seed.ts")) || existsSync2(resolve2(cwd, "seed.js"));
750
763
  if (!seedsExists) {
751
- console.log(pc3.yellow(" \u26A0 seeds.ts \u2014 not found (optional: needed only for the one-time code \u2192 DB load; after `beech seed:load`, the DB is canonical)"));
764
+ console.log(pc4.yellow(" \u26A0 seeds.ts \u2014 not found (optional: needed only for the one-time code \u2192 DB load; after `beech seed:load`, the DB is canonical)"));
752
765
  } else {
753
- console.log(pc3.green(" \u2713 seeds.ts"));
766
+ console.log(pc4.green(" \u2713 seeds.ts"));
754
767
  }
755
768
  if (checkDevVars) {
756
769
  if (!existsSync2(resolve2(cwd, ".dev.vars"))) {
757
- console.log(pc3.dim(" \u25CB .dev.vars \u2014 not found (optional: only needed for production R2 credentials)"));
770
+ console.log(pc4.dim(" \u25CB .dev.vars \u2014 not found (optional: only needed for production R2 credentials)"));
758
771
  } else {
759
- console.log(pc3.green(" \u2713 .dev.vars"));
772
+ console.log(pc4.green(" \u2713 .dev.vars"));
760
773
  }
761
774
  }
762
775
  return ok;
763
776
  }
764
777
  function printNextSteps(local) {
765
778
  const localFlag = local ? " --local" : "";
766
- console.log(pc3.dim(" Next steps:"));
767
- console.log(pc3.cyan(` 1. npx beech seed:load${localFlag}`));
768
- console.log(pc3.dim(" \u2192 create content tables and register seed definitions in D1"));
769
- console.log(pc3.cyan(" 2. npx wrangler dev"));
770
- console.log(pc3.dim(" \u2192 start API + dashboard"));
771
- console.log(pc3.dim(" 3. Open http://localhost:8789/admin\n"));
779
+ console.log(pc4.dim(" Next steps:"));
780
+ console.log(pc4.cyan(` 1. npx beech seed:load${localFlag}`));
781
+ console.log(pc4.dim(" \u2192 create content tables and register seed definitions in D1"));
782
+ console.log(pc4.cyan(" 2. npx wrangler dev"));
783
+ console.log(pc4.dim(" \u2192 start API + dashboard"));
784
+ console.log(pc4.dim(" 3. Open http://localhost:8789/admin\n"));
772
785
  }
773
786
  function getExistingTables(options) {
774
787
  try {
@@ -783,39 +796,39 @@ function getExistingTables(options) {
783
796
  }
784
797
  async function init(args) {
785
798
  const cwd = process.cwd();
786
- console.log(pc3.cyan("\n beech init \u2014 project check\n"));
799
+ console.log(pc4.cyan("\n beech init \u2014 project check\n"));
787
800
  const filesOk = checkFiles(cwd, args.local);
788
801
  if (!filesOk) {
789
- console.log(pc3.red("\n \u2717 Required files missing\n"));
790
- console.log(pc3.dim(" Fix the errors above before initialising the database."));
791
- console.log(pc3.cyan("\n \u2192 See: https://beechcms.dev/docs/getting-started\n"));
802
+ console.log(pc4.red("\n \u2717 Required files missing\n"));
803
+ console.log(pc4.dim(" Fix the errors above before initialising the database."));
804
+ console.log(pc4.cyan("\n \u2192 See: https://beechcms.dev/docs/getting-started\n"));
792
805
  process.exit(1);
793
806
  }
794
- console.log(pc3.green("\n All required files present.\n"));
807
+ console.log(pc4.green("\n All required files present.\n"));
795
808
  if (!args.initDb) {
796
809
  echoApiKeys(findWranglerConfig());
797
810
  const localFlag = args.local ? " --local" : "";
798
- console.log(pc3.dim(" Next steps:"));
799
- console.log(pc3.dim(` 1. npx beech init --db${localFlag} # initialise D1 database`));
800
- console.log(pc3.dim(` 2. npx beech seed:load${localFlag} # create content tables`));
801
- console.log(pc3.dim(" 3. npx wrangler dev # start API + dashboard"));
802
- console.log(pc3.dim(" 4. Open http://localhost:8789/admin\n"));
811
+ console.log(pc4.dim(" Next steps:"));
812
+ console.log(pc4.dim(` 1. npx beech init --db${localFlag} # initialise D1 database`));
813
+ console.log(pc4.dim(` 2. npx beech seed:load${localFlag} # create content tables`));
814
+ console.log(pc4.dim(" 3. npx wrangler dev # start API + dashboard"));
815
+ console.log(pc4.dim(" 4. Open http://localhost:8789/admin\n"));
803
816
  return;
804
817
  }
805
818
  const configPath = findWranglerConfig();
806
819
  if (configPath) {
807
820
  const placeholders = checkWranglerPlaceholders(configPath);
808
821
  if (placeholders.length > 0) {
809
- console.log(pc3.yellow(" \u26A0 wrangler.jsonc contains placeholder values:\n"));
822
+ console.log(pc4.yellow(" \u26A0 wrangler.jsonc contains placeholder values:\n"));
810
823
  for (const issue of placeholders) {
811
- console.log(pc3.yellow(` - ${issue}`));
824
+ console.log(pc4.yellow(` - ${issue}`));
812
825
  }
813
826
  if (process.stdin.isTTY && !args.nonInteractive) {
814
827
  const rl = createInterface({ input: process.stdin, output: process.stdout });
815
828
  let autoCreate = false;
816
829
  try {
817
830
  const answer = (await rl.question(
818
- pc3.cyan("\n \u2192 Create a new D1 database (and R2 bucket) on Cloudflare automatically? (Y/n): ")
831
+ pc4.cyan("\n \u2192 Create a new D1 database (and R2 bucket) on Cloudflare automatically? (Y/n): ")
819
832
  )).trim().toLowerCase();
820
833
  autoCreate = !answer || answer === "y" || answer === "yes";
821
834
  } finally {
@@ -824,44 +837,44 @@ async function init(args) {
824
837
  if (autoCreate) {
825
838
  const authed = checkWranglerAuth();
826
839
  if (!authed) {
827
- console.log(pc3.red("\n \u2717 Not logged in to Cloudflare\n"));
828
- console.log(pc3.dim(" BeechCMS needs access to your Cloudflare account to create the database."));
829
- console.log(pc3.cyan("\n \u2192 Run: npx wrangler login"));
830
- console.log(pc3.cyan(" \u2192 Then: npx beech init --db\n"));
840
+ console.log(pc4.red("\n \u2717 Not logged in to Cloudflare\n"));
841
+ console.log(pc4.dim(" BeechCMS needs access to your Cloudflare account to create the database."));
842
+ console.log(pc4.cyan("\n \u2192 Run: npx wrangler login"));
843
+ console.log(pc4.cyan(" \u2192 Then: npx beech init --db\n"));
831
844
  process.exit(1);
832
845
  }
833
846
  const projectName = readProjectName(configPath);
834
847
  const dbName = `${projectName}-db`;
835
848
  const bucketName = readBucketName(configPath) || `${projectName}-media`;
836
- console.log(pc3.dim(`
849
+ console.log(pc4.dim(`
837
850
  Creating D1 database "${dbName}"\u2026`));
838
851
  const dbId = createD1Database(dbName);
839
852
  if (!dbId) {
840
- console.log(pc3.red("\n \u2717 Failed to create D1 database\n"));
841
- console.log(pc3.dim(" Create it manually and retry:"));
842
- console.log(pc3.cyan(`
853
+ console.log(pc4.red("\n \u2717 Failed to create D1 database\n"));
854
+ console.log(pc4.dim(" Create it manually and retry:"));
855
+ console.log(pc4.cyan(`
843
856
  \u2192 Run: npx wrangler d1 create ${dbName}`));
844
- console.log(pc3.cyan(" \u2192 Then: npx beech init --db\n"));
857
+ console.log(pc4.cyan(" \u2192 Then: npx beech init --db\n"));
845
858
  process.exit(1);
846
859
  }
847
- console.log(pc3.green(` \u2713 D1 database created (id: ${dbId})`));
848
- console.log(pc3.dim(`
860
+ console.log(pc4.green(` \u2713 D1 database created (id: ${dbId})`));
861
+ console.log(pc4.dim(`
849
862
  Creating R2 bucket "${bucketName}"\u2026`));
850
863
  const r2Ok = createR2Bucket(bucketName);
851
864
  if (r2Ok) {
852
- console.log(pc3.green(` \u2713 R2 bucket "${bucketName}" created`));
865
+ console.log(pc4.green(` \u2713 R2 bucket "${bucketName}" created`));
853
866
  } else {
854
- console.log(pc3.yellow(` \u26A0 R2 bucket creation failed (may already exist \u2014 continuing)`));
867
+ console.log(pc4.yellow(` \u26A0 R2 bucket creation failed (may already exist \u2014 continuing)`));
855
868
  }
856
- console.log(pc3.dim("\n Updating wrangler.jsonc\u2026"));
869
+ console.log(pc4.dim("\n Updating wrangler.jsonc\u2026"));
857
870
  const patched = patchWranglerConfig(configPath, dbId);
858
871
  if (patched) {
859
- console.log(pc3.green(" \u2713 wrangler.jsonc updated\n"));
872
+ console.log(pc4.green(" \u2713 wrangler.jsonc updated\n"));
860
873
  } else {
861
- console.log(pc3.yellow(` \u26A0 Could not update wrangler.jsonc automatically
874
+ console.log(pc4.yellow(` \u26A0 Could not update wrangler.jsonc automatically
862
875
  `));
863
- console.log(pc3.dim(` Set database_id = "${dbId}" in wrangler.jsonc manually, then retry:`));
864
- console.log(pc3.cyan(" \u2192 Run: npx beech init --db\n"));
876
+ console.log(pc4.dim(` Set database_id = "${dbId}" in wrangler.jsonc manually, then retry:`));
877
+ console.log(pc4.cyan(" \u2192 Run: npx beech init --db\n"));
865
878
  process.exit(1);
866
879
  }
867
880
  } else {
@@ -869,11 +882,11 @@ async function init(args) {
869
882
  process.exit(1);
870
883
  }
871
884
  } else if (args.nonInteractive && args.local) {
872
- console.log(pc3.dim(" --yes: proceeding with local mode (no remote resources needed)\n"));
885
+ console.log(pc4.dim(" --yes: proceeding with local mode (no remote resources needed)\n"));
873
886
  } else {
874
887
  if (args.nonInteractive) {
875
- console.log(pc3.red("\n \u2717 Cannot proceed non-interactively: remote database has a placeholder database_id\n"));
876
- console.log(pc3.dim(" Set a real database_id in wrangler.jsonc, then retry."));
888
+ console.log(pc4.red("\n \u2717 Cannot proceed non-interactively: remote database has a placeholder database_id\n"));
889
+ console.log(pc4.dim(" Set a real database_id in wrangler.jsonc, then retry."));
877
890
  process.exit(1);
878
891
  }
879
892
  printManualDbInstructions();
@@ -884,72 +897,72 @@ async function init(args) {
884
897
  if (!args.local) {
885
898
  const authed = checkWranglerAuth();
886
899
  if (!authed) {
887
- console.log(pc3.red(" \u2717 Not logged in to Cloudflare\n"));
888
- console.log(pc3.dim(" BeechCMS needs access to your Cloudflare account to manage the D1 database."));
889
- console.log(pc3.cyan("\n \u2192 Run: npx wrangler login"));
890
- console.log(pc3.cyan(" \u2192 Then: npx beech init --db\n"));
900
+ console.log(pc4.red(" \u2717 Not logged in to Cloudflare\n"));
901
+ console.log(pc4.dim(" BeechCMS needs access to your Cloudflare account to manage the D1 database."));
902
+ console.log(pc4.cyan("\n \u2192 Run: npx wrangler login"));
903
+ console.log(pc4.cyan(" \u2192 Then: npx beech init --db\n"));
891
904
  process.exit(1);
892
905
  }
893
906
  }
894
907
  const db = args.db ?? resolveDbName(configPath);
895
908
  const options = { db, local: args.local, configPath };
896
- console.log(pc3.cyan(` Checking database "${db}" (${args.local ? "local" : "remote"})\u2026
909
+ console.log(pc4.cyan(` Checking database "${db}" (${args.local ? "local" : "remote"})\u2026
897
910
  `));
898
911
  const existingTables = getExistingTables(options);
899
912
  const missingTables = SYSTEM_TABLES.filter((t) => !existingTables?.includes(t));
900
913
  if (!args.local) {
901
914
  if (existingTables === null) {
902
- console.log(pc3.red(" \u2717 Remote database unreachable\n"));
903
- console.log(pc3.dim(" Most likely causes:"));
904
- console.log(pc3.dim(" - Wrong database_id in wrangler.jsonc"));
905
- console.log(pc3.dim(" - Worker not yet deployed"));
906
- console.log(pc3.cyan("\n \u2192 Fix: Update d1_databases.database_id in wrangler.jsonc"));
907
- console.log(pc3.cyan(" \u2192 Then: npm run deploy\n"));
915
+ console.log(pc4.red(" \u2717 Remote database unreachable\n"));
916
+ console.log(pc4.dim(" Most likely causes:"));
917
+ console.log(pc4.dim(" - Wrong database_id in wrangler.jsonc"));
918
+ console.log(pc4.dim(" - Worker not yet deployed"));
919
+ console.log(pc4.cyan("\n \u2192 Fix: Update d1_databases.database_id in wrangler.jsonc"));
920
+ console.log(pc4.cyan(" \u2192 Then: npm run deploy\n"));
908
921
  process.exit(1);
909
922
  }
910
923
  if (missingTables.length > 0) {
911
- console.log(pc3.yellow(` \u26A0 Missing system tables: ${missingTables.join(", ")}
924
+ console.log(pc4.yellow(` \u26A0 Missing system tables: ${missingTables.join(", ")}
912
925
  `));
913
- console.log(pc3.dim(" Most likely causes:"));
914
- console.log(pc3.dim(" - Wrong database_id in wrangler.jsonc"));
915
- console.log(pc3.dim(" - Migrations did not run during deploy"));
916
- console.log(pc3.cyan("\n \u2192 Fix: Update d1_databases.database_id in wrangler.jsonc"));
917
- console.log(pc3.cyan(" \u2192 Then: npm run deploy\n"));
926
+ console.log(pc4.dim(" Most likely causes:"));
927
+ console.log(pc4.dim(" - Wrong database_id in wrangler.jsonc"));
928
+ console.log(pc4.dim(" - Migrations did not run during deploy"));
929
+ console.log(pc4.cyan("\n \u2192 Fix: Update d1_databases.database_id in wrangler.jsonc"));
930
+ console.log(pc4.cyan(" \u2192 Then: npm run deploy\n"));
918
931
  process.exit(1);
919
932
  }
920
933
  for (const table of SYSTEM_TABLES) {
921
- console.log(pc3.green(` \u2713 ${table}`));
934
+ console.log(pc4.green(` \u2713 ${table}`));
922
935
  }
923
- console.log(pc3.green("\n All system tables present. Remote database is initialized.\n"));
936
+ console.log(pc4.green("\n All system tables present. Remote database is initialized.\n"));
924
937
  return;
925
938
  }
926
939
  if (existingTables === null) {
927
- console.log(pc3.yellow(" Database unreachable or not yet created \u2014 applying base schema\u2026\n"));
940
+ console.log(pc4.yellow(" Database unreachable or not yet created \u2014 applying base schema\u2026\n"));
928
941
  } else if (missingTables.length === 0) {
929
- console.log(pc3.green(" \u2713 All system tables present. Database already initialised.\n"));
942
+ console.log(pc4.green(" \u2713 All system tables present. Database already initialised.\n"));
930
943
  printNextSteps(args.local);
931
944
  return;
932
945
  } else {
933
- console.log(pc3.yellow(` Missing system tables: ${missingTables.join(", ")}`));
934
- console.log(pc3.cyan("\n Applying base schema\u2026\n"));
946
+ console.log(pc4.yellow(` Missing system tables: ${missingTables.join(", ")}`));
947
+ console.log(pc4.cyan("\n Applying base schema\u2026\n"));
935
948
  }
936
949
  const ok = executeD1File(BASE_SCHEMA_SQL, options);
937
950
  if (!ok) {
938
- console.log(pc3.red("\n \u2717 Database initialisation failed\n"));
939
- console.log(pc3.dim(" wrangler reported an error above."));
940
- console.log(pc3.cyan("\n \u2192 Run: npx beech init --db --local\n"));
951
+ console.log(pc4.red("\n \u2717 Database initialisation failed\n"));
952
+ console.log(pc4.dim(" wrangler reported an error above."));
953
+ console.log(pc4.cyan("\n \u2192 Run: npx beech init --db --local\n"));
941
954
  process.exit(1);
942
955
  }
943
- console.log(pc3.green("\n \u2713 worker.ts"));
944
- console.log(pc3.green(` \u2713 ${configPath ? basename(configPath) : "wrangler.jsonc"}`));
945
- console.log(pc3.green(" \u2713 seeds.ts"));
946
- console.log(pc3.green(" \u2713 Local D1 system tables ready\n"));
956
+ console.log(pc4.green("\n \u2713 worker.ts"));
957
+ console.log(pc4.green(` \u2713 ${configPath ? basename(configPath) : "wrangler.jsonc"}`));
958
+ console.log(pc4.green(" \u2713 seeds.ts"));
959
+ console.log(pc4.green(" \u2713 Local D1 system tables ready\n"));
947
960
  echoApiKeys(configPath);
948
961
  printNextSteps(args.local);
949
962
  }
950
963
 
951
964
  // src/commands/seed-create.ts
952
- import pc4 from "picocolors";
965
+ import pc5 from "picocolors";
953
966
  import { createInterface as createInterface2 } from "node:readline/promises";
954
967
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
955
968
  import { resolve as resolve3 } from "node:path";
@@ -1020,35 +1033,35 @@ function findSeedsFile() {
1020
1033
  async function seedCreate(_args) {
1021
1034
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
1022
1035
  const ask = async (q, fallback = "") => {
1023
- const hint = fallback ? pc4.dim(` [${fallback}]`) : "";
1036
+ const hint = fallback ? pc5.dim(` [${fallback}]`) : "";
1024
1037
  const answer = await rl.question(` ${q}${hint}: `);
1025
1038
  return answer.trim() || fallback;
1026
1039
  };
1027
1040
  const askYN = async (q, defaultYes = true) => {
1028
- const hint = defaultYes ? pc4.dim(" (Y/n)") : pc4.dim(" (y/N)");
1041
+ const hint = defaultYes ? pc5.dim(" (Y/n)") : pc5.dim(" (y/N)");
1029
1042
  const answer = (await rl.question(` ${q}${hint}: `)).trim().toLowerCase();
1030
1043
  if (!answer) return defaultYes;
1031
1044
  return answer === "y" || answer === "yes";
1032
1045
  };
1033
- console.log(pc4.cyan("\n beech seed:create \u2014 new content type wizard\n"));
1046
+ console.log(pc5.cyan("\n beech seed:create \u2014 new content type wizard\n"));
1034
1047
  try {
1035
1048
  const label = await ask('Content type name (singular, e.g. "Article")');
1036
1049
  if (!label) {
1037
1050
  rl.close();
1038
- console.log(pc4.red("\n \u2717 Name required.\n"));
1051
+ console.log(pc5.red("\n \u2717 Name required.\n"));
1039
1052
  process.exit(1);
1040
1053
  }
1041
1054
  const defaultSlug = slugify(label) + "s";
1042
1055
  const slug = slugify(await ask("Slug (plural, used in URL + table name)", defaultSlug)) || defaultSlug;
1043
1056
  const labelPlural = await ask("Plural label", label + "s") || label + "s";
1044
1057
  const branches = [];
1045
- console.log(pc4.dim("\n Now define the fields. Press Enter to accept defaults.\n"));
1058
+ console.log(pc5.dim("\n Now define the fields. Press Enter to accept defaults.\n"));
1046
1059
  let addMore = true;
1047
1060
  while (addMore) {
1048
- console.log(pc4.dim(` \u2500\u2500\u2500 Field ${branches.length + 1} \u2500\u2500\u2500`));
1061
+ console.log(pc5.dim(` \u2500\u2500\u2500 Field ${branches.length + 1} \u2500\u2500\u2500`));
1049
1062
  const alias = await ask(' Alias (camelCase, e.g. "title", "publishedAt")');
1050
1063
  if (!alias) {
1051
- console.log(pc4.yellow(" Alias required \u2014 skipping."));
1064
+ console.log(pc5.yellow(" Alias required \u2014 skipping."));
1052
1065
  addMore = await askYN("\n Add a field?");
1053
1066
  continue;
1054
1067
  }
@@ -1062,14 +1075,14 @@ async function seedCreate(_args) {
1062
1075
  }
1063
1076
  rl.close();
1064
1077
  if (branches.length === 0) {
1065
- console.log(pc4.yellow("\n No fields defined \u2014 seed not created.\n"));
1078
+ console.log(pc5.yellow("\n No fields defined \u2014 seed not created.\n"));
1066
1079
  process.exit(0);
1067
1080
  }
1068
1081
  const seedBlock = generateSeedBlock(slug, label, labelPlural, branches);
1069
1082
  const cName = toConstName(slug);
1070
1083
  const seedsPath = findSeedsFile();
1071
1084
  if (!seedsPath) {
1072
- console.log(pc4.yellow("\n Could not find seeds.ts. Add this to your seeds file manually:\n"));
1085
+ console.log(pc5.yellow("\n Could not find seeds.ts. Add this to your seeds file manually:\n"));
1073
1086
  console.log(seedBlock);
1074
1087
  process.exit(0);
1075
1088
  }
@@ -1078,18 +1091,18 @@ async function seedCreate(_args) {
1078
1091
  const withSeed = content + seedBlock;
1079
1092
  const withRegistry = tryInsertRegistryEntry(withSeed, slug, cName);
1080
1093
  writeFileSync3(seedsPath, withRegistry ?? withSeed, "utf-8");
1081
- console.log(pc4.green(`
1094
+ console.log(pc5.green(`
1082
1095
  \u2713 Seed "${slug}" appended to ${seedsPath}
1083
1096
  `));
1084
1097
  if (!withRegistry) {
1085
- console.log(pc4.yellow(` \u26A0 Could not auto-update SEED_REGISTRY \u2014 add this entry manually:
1098
+ console.log(pc5.yellow(` \u26A0 Could not auto-update SEED_REGISTRY \u2014 add this entry manually:
1086
1099
  `));
1087
- console.log(pc4.cyan(` ${slug}: ${cName},
1100
+ console.log(pc5.cyan(` ${slug}: ${cName},
1088
1101
  `));
1089
1102
  }
1090
- console.log(pc4.dim(" Next steps:"));
1091
- console.log(pc4.cyan(" npx beech seed:load --local"));
1092
- console.log(pc4.dim(" \u2192 create the new content table in your local D1 database\n"));
1103
+ console.log(pc5.dim(" Next steps:"));
1104
+ console.log(pc5.cyan(" npx beech seed:load --local"));
1105
+ console.log(pc5.dim(" \u2192 create the new content table in your local D1 database\n"));
1093
1106
  } catch (err) {
1094
1107
  rl.close();
1095
1108
  throw err;
@@ -1097,7 +1110,7 @@ async function seedCreate(_args) {
1097
1110
  }
1098
1111
 
1099
1112
  // src/commands/deploy.ts
1100
- import pc5 from "picocolors";
1113
+ import pc6 from "picocolors";
1101
1114
  import { spawnSync as spawnSync3 } from "node:child_process";
1102
1115
  import { readFileSync as readFileSync4 } from "node:fs";
1103
1116
  function readWorkerName(configPath) {
@@ -1128,8 +1141,8 @@ async function checkAdmin(url) {
1128
1141
  }
1129
1142
  }
1130
1143
  async function deploy(args) {
1131
- console.log(pc5.cyan("\n beech deploy\n"));
1132
- console.log(pc5.dim(" [1/3] Deploying Worker\u2026\n"));
1144
+ console.log(pc6.cyan("\n beech deploy\n"));
1145
+ console.log(pc6.dim(" [1/3] Deploying Worker\u2026\n"));
1133
1146
  const deployResult = spawnSync3("npm", ["run", "deploy"], {
1134
1147
  stdio: ["inherit", "pipe", "inherit"],
1135
1148
  encoding: "utf-8",
@@ -1139,33 +1152,33 @@ async function deploy(args) {
1139
1152
  const deployStdout = deployResult.stdout ?? "";
1140
1153
  if (deployStdout) process.stdout.write(deployStdout);
1141
1154
  if (deployResult.status !== 0) {
1142
- console.log(pc5.red("\n \u2717 Worker deploy failed\n"));
1143
- console.log(pc5.dim(" Check the wrangler output above for details."));
1144
- console.log(pc5.cyan("\n \u2192 Run: npx wrangler login # if not authenticated"));
1145
- console.log(pc5.cyan(" \u2192 Or: Update wrangler.jsonc # if database_id is wrong\n"));
1155
+ console.log(pc6.red("\n \u2717 Worker deploy failed\n"));
1156
+ console.log(pc6.dim(" Check the wrangler output above for details."));
1157
+ console.log(pc6.cyan("\n \u2192 Run: npx wrangler login # if not authenticated"));
1158
+ console.log(pc6.cyan(" \u2192 Or: Update wrangler.jsonc # if database_id is wrong\n"));
1146
1159
  process.exit(1);
1147
1160
  }
1148
1161
  const deployedUrl = extractWorkerUrl(deployStdout);
1149
- console.log(pc5.green("\n \u2713 Worker deployed"));
1162
+ console.log(pc6.green("\n \u2713 Worker deployed"));
1150
1163
  if (args.skipSeed) {
1151
- console.log(pc5.dim("\n [2/3] Skipping seed:load (--skip-seed)"));
1164
+ console.log(pc6.dim("\n [2/3] Skipping seed:load (--skip-seed)"));
1152
1165
  } else {
1153
- console.log(pc5.dim("\n [2/3] Syncing content schema to remote D1\u2026\n"));
1166
+ console.log(pc6.dim("\n [2/3] Syncing content schema to remote D1\u2026\n"));
1154
1167
  const seedResult = spawnSync3("npx", ["beech", "seed:load"], {
1155
1168
  stdio: "inherit",
1156
1169
  cwd: process.cwd(),
1157
1170
  shell: true
1158
1171
  });
1159
1172
  if (seedResult.status !== 0) {
1160
- console.log(pc5.yellow("\n \u26A0 seed:load failed\n"));
1161
- console.log(pc5.dim(" Sync the remote content schema manually:"));
1162
- console.log(pc5.cyan(" \u2192 Run: npx beech seed:load\n"));
1173
+ console.log(pc6.yellow("\n \u26A0 seed:load failed\n"));
1174
+ console.log(pc6.dim(" Sync the remote content schema manually:"));
1175
+ console.log(pc6.cyan(" \u2192 Run: npx beech seed:load\n"));
1163
1176
  } else {
1164
- console.log(pc5.green("\n \u2713 Content schema synced"));
1177
+ console.log(pc6.green("\n \u2713 Content schema synced"));
1165
1178
  }
1166
1179
  }
1167
1180
  if (args.skipCheck) {
1168
- console.log(pc5.dim("\n [3/3] Skipping admin check (--skip-check)\n"));
1181
+ console.log(pc6.dim("\n [3/3] Skipping admin check (--skip-check)\n"));
1169
1182
  return;
1170
1183
  }
1171
1184
  const adminBase = deployedUrl ?? (() => {
@@ -1173,85 +1186,405 @@ async function deploy(args) {
1173
1186
  return workerName ? `https://${workerName}.workers.dev` : null;
1174
1187
  })();
1175
1188
  if (!adminBase) {
1176
- console.log(pc5.dim("\n [3/3] Could not determine worker URL \u2014 skipping admin check\n"));
1177
- console.log(pc5.dim(" The deployed URL is printed by wrangler above. Open <url>/admin to verify.\n"));
1189
+ console.log(pc6.dim("\n [3/3] Could not determine worker URL \u2014 skipping admin check\n"));
1190
+ console.log(pc6.dim(" The deployed URL is printed by wrangler above. Open <url>/admin to verify.\n"));
1178
1191
  return;
1179
1192
  }
1180
- console.log(pc5.dim(`
1193
+ console.log(pc6.dim(`
1181
1194
  [3/3] Checking ${adminBase}/admin\u2026
1182
1195
  `));
1183
1196
  const { ok, status } = await checkAdmin(adminBase);
1184
1197
  if (ok) {
1185
- console.log(pc5.green(` \u2713 Admin reachable at: ${adminBase}/admin
1198
+ console.log(pc6.green(` \u2713 Admin reachable at: ${adminBase}/admin
1186
1199
  `));
1187
1200
  } else {
1188
1201
  const statusStr = status != null ? ` (HTTP ${status})` : "";
1189
- console.log(pc5.yellow(` \u26A0 Admin returned an error${statusStr} at: ${adminBase}/admin
1202
+ console.log(pc6.yellow(` \u26A0 Admin returned an error${statusStr} at: ${adminBase}/admin
1190
1203
  `));
1191
- console.log(pc5.dim(" The database may not be fully initialized."));
1192
- console.log(pc5.cyan(" \u2192 Run: npx beech init --db --remote\n"));
1204
+ console.log(pc6.dim(" The database may not be fully initialized."));
1205
+ console.log(pc6.cyan(" \u2192 Run: npx beech init --db --remote\n"));
1193
1206
  }
1194
1207
  }
1195
1208
 
1196
1209
  // src/commands/onboard.ts
1197
- import pc6 from "picocolors";
1210
+ import pc7 from "picocolors";
1198
1211
  async function onboard(args) {
1199
- console.log(pc6.cyan("\n beech onboard \u2014 full provisioning\n"));
1212
+ console.log(pc7.cyan("\n beech onboard \u2014 full provisioning\n"));
1200
1213
  await init({ initDb: true, local: args.local, db: args.db, nonInteractive: args.yes });
1201
1214
  await seedLoad({ dryRun: false, diff: false, local: args.local, db: args.db, registry: args.registry ?? null });
1202
- console.log(pc6.cyan("\n Provisioning complete.\n"));
1203
- console.log(pc6.dim(" Next steps:"));
1204
- console.log(pc6.cyan(" 1. npx wrangler dev"));
1205
- console.log(pc6.dim(" \u2192 start API + dashboard"));
1206
- console.log(pc6.dim(" 2. Open http://localhost:8789/admin"));
1207
- console.log(pc6.dim(" \u2192 complete setup wizard to create admin user\n"));
1215
+ console.log(pc7.cyan("\n Provisioning complete.\n"));
1216
+ console.log(pc7.dim(" Next steps:"));
1217
+ console.log(pc7.cyan(" 1. npx wrangler dev"));
1218
+ console.log(pc7.dim(" \u2192 start API + dashboard"));
1219
+ console.log(pc7.dim(" 2. Open http://localhost:8789/admin"));
1220
+ console.log(pc7.dim(" \u2192 complete setup wizard to create admin user\n"));
1208
1221
  }
1209
1222
 
1210
1223
  // src/commands/update.ts
1211
- import pc7 from "picocolors";
1224
+ import pc8 from "picocolors";
1212
1225
  import { spawnSync as spawnSync4 } from "node:child_process";
1213
1226
  async function update(_args) {
1214
- console.log(pc7.cyan("\n beech update\n"));
1215
- console.log(pc7.dim(" [1/2] Installing latest BeechCMS packages\u2026\n"));
1227
+ console.log(pc8.cyan("\n beech update\n"));
1228
+ console.log(pc8.dim(" [1/2] Installing latest BeechCMS packages\u2026\n"));
1216
1229
  const installResult = spawnSync4(
1217
1230
  "npm",
1218
1231
  ["install", "@beechcms/api@latest", "@beechcms/core@latest"],
1219
1232
  { stdio: "inherit", cwd: process.cwd(), shell: true }
1220
1233
  );
1221
1234
  if (installResult.status !== 0) {
1222
- console.log(pc7.red("\n \u2717 npm install failed\n"));
1223
- console.log(pc7.dim(" Check the output above for details."));
1224
- console.log(pc7.dim(" You may need to resolve version conflicts manually."));
1225
- console.log(pc7.cyan("\n \u2192 Try: npm install --legacy-peer-deps\n"));
1235
+ console.log(pc8.red("\n \u2717 npm install failed\n"));
1236
+ console.log(pc8.dim(" Check the output above for details."));
1237
+ console.log(pc8.dim(" You may need to resolve version conflicts manually."));
1238
+ console.log(pc8.cyan("\n \u2192 Try: npm install --legacy-peer-deps\n"));
1226
1239
  process.exit(1);
1227
1240
  }
1228
- console.log(pc7.green("\n \u2713 Packages updated"));
1229
- console.log(pc7.dim("\n [2/2] Applying system migrations to local database\u2026\n"));
1241
+ console.log(pc8.green("\n \u2713 Packages updated"));
1242
+ console.log(pc8.dim("\n [2/2] Applying system migrations to local database\u2026\n"));
1230
1243
  const initResult = spawnSync4(
1231
1244
  "npx",
1232
1245
  ["beech", "init", "--db", "--local"],
1233
1246
  { stdio: "inherit", cwd: process.cwd(), shell: true }
1234
1247
  );
1235
1248
  if (initResult.status !== 0) {
1236
- console.log(pc7.yellow("\n \u26A0 Local DB update failed\n"));
1237
- console.log(pc7.dim(" Apply system migrations manually:"));
1238
- console.log(pc7.cyan(" \u2192 Run: npx beech init --db --local\n"));
1249
+ console.log(pc8.yellow("\n \u26A0 Local DB update failed\n"));
1250
+ console.log(pc8.dim(" Apply system migrations manually:"));
1251
+ console.log(pc8.cyan(" \u2192 Run: npx beech init --db --local\n"));
1239
1252
  } else {
1240
- console.log(pc7.green("\n \u2713 Local database updated"));
1253
+ console.log(pc8.green("\n \u2713 Local database updated"));
1254
+ }
1255
+ console.log(pc8.dim("\n Local update complete.\n"));
1256
+ console.log(pc8.dim(" Next steps:"));
1257
+ console.log(pc8.cyan(" 1. npx beech seed:load --local"));
1258
+ console.log(pc8.dim(" \u2192 sync content schema to local DB"));
1259
+ console.log(pc8.cyan(" 2. npm run deploy"));
1260
+ console.log(pc8.dim(" \u2192 deploy updated API + dashboard"));
1261
+ console.log(pc8.cyan(" 3. npx beech seed:load"));
1262
+ console.log(pc8.dim(" \u2192 sync remote schema\n"));
1263
+ }
1264
+
1265
+ // src/commands/reset.ts
1266
+ import pc9 from "picocolors";
1267
+ import { spawnSync as spawnSync5 } from "node:child_process";
1268
+ import { existsSync as existsSync4, readFileSync as readFileSync5, rmSync as rmSync2 } from "node:fs";
1269
+ import { resolve as resolve4 } from "node:path";
1270
+ import { createInterface as createInterface3 } from "node:readline/promises";
1271
+ function isDockerInstalled() {
1272
+ try {
1273
+ const result = spawnSync5("docker", ["--version"], { stdio: "ignore", shell: true });
1274
+ return result.status === 0;
1275
+ } catch {
1276
+ return false;
1277
+ }
1278
+ }
1279
+ function isDockerRunning() {
1280
+ try {
1281
+ const result = spawnSync5("docker", ["info"], { stdio: "ignore", shell: true });
1282
+ return result.status === 0;
1283
+ } catch {
1284
+ return false;
1285
+ }
1286
+ }
1287
+ async function reset(args) {
1288
+ console.log(pc9.cyan("\n beech reset \u2014 cleanup environments\n"));
1289
+ let resetDb = args.db || args.all;
1290
+ let resetDocker = args.docker || args.all;
1291
+ if (!args.db && !args.docker && !args.all) {
1292
+ if (process.stdin.isTTY) {
1293
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
1294
+ try {
1295
+ const answer = (await rl.question(
1296
+ pc9.cyan(" \u2192 No options provided. Would you like to reset everything (DB & Docker)? (y/N): ")
1297
+ )).trim().toLowerCase();
1298
+ if (answer === "y" || answer === "yes") {
1299
+ resetDb = true;
1300
+ resetDocker = true;
1301
+ } else {
1302
+ console.log(pc9.dim("\n Reset cancelled. Use --db, --docker, or --all.\n"));
1303
+ return;
1304
+ }
1305
+ } finally {
1306
+ rl.close();
1307
+ }
1308
+ } else {
1309
+ console.log(pc9.red("\n \u2717 Error: Please specify what to reset using --db, --docker, or --all.\n"));
1310
+ process.exit(1);
1311
+ }
1312
+ }
1313
+ const cwd = process.cwd();
1314
+ if (resetDocker) {
1315
+ if (!isDockerInstalled()) {
1316
+ console.log(pc9.red(" \u2717 Docker is not installed or not found in your PATH."));
1317
+ console.log(pc9.dim(" Please install Docker to reset Docker containers and volumes.\n"));
1318
+ if (!args.all) {
1319
+ process.exit(1);
1320
+ }
1321
+ } else if (!isDockerRunning()) {
1322
+ console.log(pc9.yellow(" \u26A0 Docker is installed, but the Docker daemon is NOT running."));
1323
+ console.log(pc9.dim(" Please start Docker Desktop or your Docker daemon to reset containers.\n"));
1324
+ if (!args.all) {
1325
+ process.exit(1);
1326
+ }
1327
+ } else {
1328
+ console.log(pc9.dim(" Resetting Docker containers and volumes\u2026\n"));
1329
+ const result = spawnSync5("docker", ["compose", "down", "-v"], {
1330
+ stdio: "inherit",
1331
+ cwd,
1332
+ shell: true
1333
+ });
1334
+ if (result.status === 0) {
1335
+ console.log(pc9.green("\n \u2713 Docker containers stopped and volumes removed."));
1336
+ } else {
1337
+ console.log(pc9.red("\n \u2717 Docker reset failed."));
1338
+ }
1339
+ }
1340
+ }
1341
+ if (resetDb) {
1342
+ console.log(pc9.dim("\n Resetting local database\u2026\n"));
1343
+ let dbResetSuccess = false;
1344
+ const apiDir = resolve4(cwd, "apps", "api");
1345
+ if (existsSync4(resolve4(apiDir, "package.json"))) {
1346
+ const result = spawnSync5("npm", ["run", "db:reset:local"], {
1347
+ stdio: "inherit",
1348
+ cwd: apiDir,
1349
+ shell: true
1350
+ });
1351
+ dbResetSuccess = result.status === 0;
1352
+ } else if (existsSync4(resolve4(cwd, "package.json"))) {
1353
+ const pkg = JSON.parse(readFileSync5(resolve4(cwd, "package.json"), "utf-8"));
1354
+ if (pkg.scripts?.["db:reset:local"]) {
1355
+ const result = spawnSync5("npm", ["run", "db:reset:local"], {
1356
+ stdio: "inherit",
1357
+ cwd,
1358
+ shell: true
1359
+ });
1360
+ dbResetSuccess = result.status === 0;
1361
+ } else {
1362
+ const wranglerStateDir = resolve4(cwd, ".wrangler/state");
1363
+ if (existsSync4(wranglerStateDir)) {
1364
+ console.log(pc9.dim(" Removing .wrangler/state\u2026"));
1365
+ rmSync2(wranglerStateDir, { recursive: true, force: true });
1366
+ }
1367
+ if (existsSync4(resolve4(cwd, "scripts", "bootstrap-d1.mjs"))) {
1368
+ const result = spawnSync5("node", ["scripts/bootstrap-d1.mjs"], {
1369
+ stdio: "inherit",
1370
+ cwd,
1371
+ shell: true
1372
+ });
1373
+ dbResetSuccess = result.status === 0;
1374
+ } else {
1375
+ console.log(pc9.yellow(" \u26A0 Could not find database reset script."));
1376
+ const initResult = spawnSync5("npx", ["beech", "init", "--db", "--local"], {
1377
+ stdio: "inherit",
1378
+ cwd,
1379
+ shell: true
1380
+ });
1381
+ dbResetSuccess = initResult.status === 0;
1382
+ }
1383
+ }
1384
+ } else {
1385
+ const wranglerStateDir = resolve4(cwd, ".wrangler/state");
1386
+ if (existsSync4(wranglerStateDir)) {
1387
+ console.log(pc9.dim(" Removing .wrangler/state\u2026"));
1388
+ rmSync2(wranglerStateDir, { recursive: true, force: true });
1389
+ }
1390
+ dbResetSuccess = true;
1391
+ }
1392
+ if (dbResetSuccess) {
1393
+ console.log(pc9.green("\n \u2713 Local database reset completed."));
1394
+ } else {
1395
+ console.log(pc9.red("\n \u2717 Database reset failed."));
1396
+ }
1397
+ }
1398
+ console.log(pc9.dim("\n Reset process finished.\n"));
1399
+ }
1400
+
1401
+ // src/commands/generate-types.ts
1402
+ import { writeFileSync as writeFileSync4, mkdirSync } from "node:fs";
1403
+ import { dirname, resolve as resolve5 } from "node:path";
1404
+ import pc10 from "picocolors";
1405
+ import { generateSeedTypes } from "@beechcms/core";
1406
+ function loadSeedsFromD1(db) {
1407
+ const configPath = findWranglerConfig();
1408
+ const options = { db, local: false, configPath };
1409
+ const rows = queryD1(
1410
+ `SELECT slug, definition FROM seeds WHERE status = 'active';`,
1411
+ options
1412
+ );
1413
+ return rows.map((r) => JSON.parse(r.definition));
1414
+ }
1415
+ async function generateTypes(args) {
1416
+ let seeds;
1417
+ if (args.local) {
1418
+ const registry = args.registry ?? {};
1419
+ if (Object.keys(registry).length === 0) {
1420
+ console.log(pc10.red("\n \u2717 No seeds found (seeds.ts empty or missing).\n"));
1421
+ process.exit(1);
1422
+ }
1423
+ seeds = Object.values(registry);
1424
+ } else {
1425
+ const db = args.db ?? resolveDbName(findWranglerConfig());
1426
+ seeds = loadSeedsFromD1(db);
1427
+ if (seeds.length === 0) {
1428
+ console.log(pc10.red(`
1429
+ \u2717 No active seeds in D1 (${db}). Run \`beech seed:load\` first.
1430
+ `));
1431
+ process.exit(1);
1432
+ }
1433
+ }
1434
+ const code = generateSeedTypes(seeds);
1435
+ const outPath = resolve5(process.cwd(), args.out);
1436
+ mkdirSync(dirname(outPath), { recursive: true });
1437
+ writeFileSync4(outPath, code, "utf-8");
1438
+ console.log(pc10.green(`
1439
+ \u2713 Generated ${seeds.length} interface(s) \u2192 ${args.out}
1440
+ `));
1441
+ }
1442
+
1443
+ // src/commands/schema-diff.ts
1444
+ import pc11 from "picocolors";
1445
+ import { resolve as resolve6 } from "node:path";
1446
+ import { SEED_REGISTRY as SEED_REGISTRY3, sortSeedsByDependencies as sortSeedsByDependencies2 } from "@beechcms/core";
1447
+
1448
+ // src/lib/migration-writer.ts
1449
+ import { readdirSync, writeFileSync as writeFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "node:fs";
1450
+ import { join as join2 } from "node:path";
1451
+ import {
1452
+ generateAddColumn,
1453
+ generateIndexes as generateIndexes2,
1454
+ planCreateSeed
1455
+ } from "@beechcms/core";
1456
+ var DESTRUCTIVE = /* @__PURE__ */ new Set([
1457
+ "extra",
1458
+ "type_mismatch",
1459
+ "fk_mismatch"
1460
+ ]);
1461
+ function nextMigrationIndex(migrationsDir) {
1462
+ if (!existsSync5(migrationsDir)) return "0000";
1463
+ let max = -1;
1464
+ for (const f of readdirSync(migrationsDir)) {
1465
+ const m = /^(\d{4})_/.exec(f);
1466
+ if (m) max = Math.max(max, Number(m[1]));
1467
+ }
1468
+ return String(max + 1).padStart(4, "0");
1469
+ }
1470
+ function buildMigrationSql(diffs, registry) {
1471
+ const lines = [];
1472
+ let additiveCount = 0;
1473
+ const destructiveSlugs = [];
1474
+ for (const diff of diffs) {
1475
+ const seed = registry[diff.slug];
1476
+ if (!seed) continue;
1477
+ if (!diff.tableExists) {
1478
+ lines.push(`-- ${diff.slug}: create table from scratch`);
1479
+ for (const stmt of planCreateSeed(seed)) {
1480
+ lines.push(stmt);
1481
+ additiveCount++;
1482
+ }
1483
+ lines.push("");
1484
+ continue;
1485
+ }
1486
+ const missing = diff.columns.filter((c) => c.status === "missing");
1487
+ const idxMissing = diff.columns.filter((c) => c.status === "index_missing");
1488
+ const destructive = diff.columns.filter((c) => DESTRUCTIVE.has(c.status));
1489
+ if (missing.length || idxMissing.length) {
1490
+ lines.push(`-- ${diff.slug}: additive changes`);
1491
+ for (const col of missing) {
1492
+ const branch = seed.branches.find((b) => b.alias === col.name);
1493
+ if (branch) {
1494
+ lines.push(generateAddColumn(seed, branch));
1495
+ additiveCount++;
1496
+ }
1497
+ }
1498
+ if (idxMissing.length) {
1499
+ for (const stmt of generateIndexes2(seed)) {
1500
+ lines.push(stmt);
1501
+ additiveCount++;
1502
+ }
1503
+ }
1504
+ lines.push("");
1505
+ }
1506
+ if (destructive.length) {
1507
+ destructiveSlugs.push(diff.slug);
1508
+ lines.push(`-- \u26A0 ${diff.slug}: DESTRUCTIVE drift NOT auto-migrated \u2014 review manually:`);
1509
+ for (const col of destructive) {
1510
+ lines.push(`-- ${col.status}: ${col.name}` + (col.actualType ? ` (db: ${col.actualType})` : ""));
1511
+ }
1512
+ lines.push("");
1513
+ }
1514
+ }
1515
+ return { sql: lines.join("\n").trimEnd() + "\n", additiveCount, destructiveSlugs };
1516
+ }
1517
+ function writeMigrationFile(migrationsDir, index, name, sql) {
1518
+ if (!existsSync5(migrationsDir)) mkdirSync2(migrationsDir, { recursive: true });
1519
+ const safe = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "schema_sync";
1520
+ const file = join2(migrationsDir, `${index}_${safe}.sql`);
1521
+ writeFileSync5(file, sql, "utf-8");
1522
+ return file;
1523
+ }
1524
+
1525
+ // src/commands/schema-diff.ts
1526
+ function resolveMigrationsDir(override) {
1527
+ if (override) return resolve6(process.cwd(), override);
1528
+ return resolve6(process.cwd(), "apps", "api", "migrations");
1529
+ }
1530
+ async function schemaDiff(args) {
1531
+ const registry = args.registry ?? SEED_REGISTRY3;
1532
+ if (Object.keys(registry).length === 0) {
1533
+ console.log(pc11.yellow("\n \u2717 No seeds found \u2014 nothing to diff.\n"));
1534
+ return;
1535
+ }
1536
+ const configPath = findWranglerConfig();
1537
+ const options = { db: args.db ?? resolveDbName(configPath), local: args.local, configPath };
1538
+ const seeds = sortSeedsByDependencies2(Object.values(registry));
1539
+ console.log(pc11.cyan(`
1540
+ Diffing schema vs ${args.local ? "local" : "remote"} D1 (${options.db})\u2026
1541
+ `));
1542
+ const diffs = [];
1543
+ let clean = true;
1544
+ for (const seed of seeds) {
1545
+ const d = await diffSeed(seed, options);
1546
+ diffs.push(d);
1547
+ renderSeedDiff(d);
1548
+ if (!isSeedClean(d)) clean = false;
1549
+ }
1550
+ if (clean) {
1551
+ console.log(pc11.green("\n Schema matches seeds. No migration needed.\n"));
1552
+ return;
1553
+ }
1554
+ const plan = buildMigrationSql(diffs, registry);
1555
+ if (!args.write) {
1556
+ console.log(pc11.dim("\n -- proposed additive migration (preview):\n"));
1557
+ console.log(plan.sql);
1558
+ if (plan.destructiveSlugs.length) {
1559
+ console.log(pc11.yellow(`
1560
+ \u26A0 Destructive drift in: ${plan.destructiveSlugs.join(", ")} \u2014 not auto-migrated.`));
1561
+ }
1562
+ console.log(pc11.cyan("\n \u2192 Re-run with --write to save the migration file.\n"));
1563
+ return;
1564
+ }
1565
+ if (plan.additiveCount === 0) {
1566
+ console.log(pc11.yellow("\n \u26A0 Only destructive drift detected \u2014 no additive migration written."));
1567
+ console.log(pc11.dim(" Author a reviewed migration by hand for renames/drops/type changes.\n"));
1568
+ return;
1569
+ }
1570
+ const dir = resolveMigrationsDir(args.migrationsDir);
1571
+ const index = nextMigrationIndex(dir);
1572
+ const file = writeMigrationFile(dir, index, args.name ?? "schema_sync", plan.sql);
1573
+ console.log(pc11.green(`
1574
+ \u2713 Wrote ${file} (${plan.additiveCount} statement(s)).`));
1575
+ console.log(pc11.dim(" Review, commit, then `wrangler d1 migrations apply --remote` in CI.\n"));
1576
+ if (plan.destructiveSlugs.length) {
1577
+ console.log(pc11.yellow(` \u26A0 Destructive drift in ${plan.destructiveSlugs.join(", ")} was NOT included.
1578
+ `));
1241
1579
  }
1242
- console.log(pc7.dim("\n Local update complete.\n"));
1243
- console.log(pc7.dim(" Next steps:"));
1244
- console.log(pc7.cyan(" 1. npx beech seed:load --local"));
1245
- console.log(pc7.dim(" \u2192 sync content schema to local DB"));
1246
- console.log(pc7.cyan(" 2. npm run deploy"));
1247
- console.log(pc7.dim(" \u2192 deploy updated API + dashboard"));
1248
- console.log(pc7.cyan(" 3. npx beech seed:load"));
1249
- console.log(pc7.dim(" \u2192 sync remote schema\n"));
1250
1580
  }
1251
1581
  export {
1252
1582
  deploy,
1583
+ generateTypes,
1253
1584
  init,
1254
1585
  onboard,
1586
+ reset,
1587
+ schemaDiff,
1255
1588
  seedCreate,
1256
1589
  seedLoad,
1257
1590
  update,