@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.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-lint.log +1 -0
- package/coverage/coverage-summary.json +3 -0
- package/dist/index.js +563 -230
- package/package.json +12 -11
- package/src/commands/deploy.ts +126 -126
- package/src/commands/generate-types.ts +65 -0
- package/src/commands/init.ts +599 -599
- package/src/commands/onboard.ts +32 -32
- package/src/commands/reset.ts +157 -0
- package/src/commands/schema-diff.ts +78 -0
- package/src/commands/seed-create.ts +192 -192
- package/src/commands/seed-load.ts +206 -235
- package/src/commands/update.ts +54 -54
- package/src/commands/validate.ts +80 -80
- package/src/index.ts +24 -17
- package/src/lib/migration-writer.ts +106 -0
- package/src/lib/schema-diff.ts +175 -150
- package/src/lib/wrangler.ts +129 -129
- package/src/test/generate-types.test.ts +58 -0
- package/src/test/reset.test.ts +128 -0
- package/src/test/schema-diff.test.ts +232 -0
- package/src/test/seed-load.test.ts +158 -0
- package/src/test/validate.test.ts +261 -0
- package/tsconfig.json +16 -16
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +33 -33
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -116
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -116
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/validate.ts.html +0 -325
- package/coverage/lcov.info +0 -80
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/validate.ts.html +0 -325
- package/dist/commands/seed-load.d.ts +0 -8
- package/dist/commands/seed-load.d.ts.map +0 -1
- package/dist/commands/seed-load.js +0 -89
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/schema-diff.d.ts +0 -15
- package/dist/lib/schema-diff.d.ts.map +0 -1
- package/dist/lib/schema-diff.js +0 -37
- package/dist/lib/wrangler.d.ts +0 -17
- package/dist/lib/wrangler.d.ts.map +0 -1
- 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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
243
|
+
console.log(pc2.red(` \u2717 ${e.slug} (fatal)`));
|
|
205
244
|
for (const msg of e.messages) {
|
|
206
|
-
console.log(
|
|
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(
|
|
255
|
+
if (!hasFatal) console.log(pc2.green(` \u2713 ${seed.slug}`));
|
|
217
256
|
}
|
|
218
257
|
} else {
|
|
219
|
-
console.log(
|
|
258
|
+
console.log(pc2.yellow(` \u26A0 ${seed.slug}`));
|
|
220
259
|
for (const msg of msgs) {
|
|
221
|
-
console.log(
|
|
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(
|
|
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(
|
|
274
|
+
console.log(pc2.yellow(` Found ${totalWarnings} warning${s}. Review seeds above.
|
|
236
275
|
`));
|
|
237
276
|
} else {
|
|
238
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
275
|
-
if (!result
|
|
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(
|
|
318
|
+
console.log(pc3.green(" Schema matches seeds. No action needed.\n"));
|
|
306
319
|
} else {
|
|
307
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
333
|
+
console.log(pc3.dim(` -- register ${seed.slug} in seeds table`));
|
|
321
334
|
console.log(buildSeedRegistrationSql(seed) + "\n");
|
|
322
335
|
}
|
|
323
|
-
console.log(
|
|
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(
|
|
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(` ${
|
|
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(
|
|
337
|
-
console.log(
|
|
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(
|
|
341
|
-
console.log(
|
|
342
|
-
console.log(
|
|
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(
|
|
345
|
-
console.log(
|
|
346
|
-
console.log(
|
|
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(
|
|
349
|
-
console.log(
|
|
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(
|
|
366
|
+
console.log(pc3.green("done"));
|
|
354
367
|
}
|
|
355
368
|
executeD1File(SEED_META_BUMP_SQL, options);
|
|
356
|
-
console.log(
|
|
357
|
-
console.log(
|
|
358
|
-
console.log(
|
|
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(
|
|
364
|
-
console.log(
|
|
365
|
-
console.log(
|
|
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(
|
|
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(
|
|
391
|
+
console.log(pc3.red(` \u2717 ${e.slug}`));
|
|
379
392
|
for (const msg of e.messages) {
|
|
380
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
409
|
-
console.log(
|
|
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(
|
|
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(
|
|
418
|
-
console.log(
|
|
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
|
|
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(
|
|
705
|
-
console.log(
|
|
706
|
-
console.log(
|
|
707
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
739
|
+
console.log(pc4.dim(` PUBLIC_WRITE_API_KEY = ${masked}`));
|
|
727
740
|
}
|
|
728
|
-
console.log(
|
|
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(
|
|
749
|
+
console.log(pc4.red(" \u2717 worker.ts \u2014 missing (required)"));
|
|
737
750
|
ok = false;
|
|
738
751
|
} else {
|
|
739
|
-
console.log(
|
|
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(
|
|
757
|
+
console.log(pc4.red(" \u2717 wrangler.jsonc \u2014 missing (required)"));
|
|
745
758
|
ok = false;
|
|
746
759
|
} else {
|
|
747
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
770
|
+
console.log(pc4.dim(" \u25CB .dev.vars \u2014 not found (optional: only needed for production R2 credentials)"));
|
|
758
771
|
} else {
|
|
759
|
-
console.log(
|
|
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(
|
|
767
|
-
console.log(
|
|
768
|
-
console.log(
|
|
769
|
-
console.log(
|
|
770
|
-
console.log(
|
|
771
|
-
console.log(
|
|
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(
|
|
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(
|
|
790
|
-
console.log(
|
|
791
|
-
console.log(
|
|
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(
|
|
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(
|
|
799
|
-
console.log(
|
|
800
|
-
console.log(
|
|
801
|
-
console.log(
|
|
802
|
-
console.log(
|
|
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(
|
|
822
|
+
console.log(pc4.yellow(" \u26A0 wrangler.jsonc contains placeholder values:\n"));
|
|
810
823
|
for (const issue of placeholders) {
|
|
811
|
-
console.log(
|
|
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
|
-
|
|
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(
|
|
828
|
-
console.log(
|
|
829
|
-
console.log(
|
|
830
|
-
console.log(
|
|
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(
|
|
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(
|
|
841
|
-
console.log(
|
|
842
|
-
console.log(
|
|
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(
|
|
857
|
+
console.log(pc4.cyan(" \u2192 Then: npx beech init --db\n"));
|
|
845
858
|
process.exit(1);
|
|
846
859
|
}
|
|
847
|
-
console.log(
|
|
848
|
-
console.log(
|
|
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(
|
|
865
|
+
console.log(pc4.green(` \u2713 R2 bucket "${bucketName}" created`));
|
|
853
866
|
} else {
|
|
854
|
-
console.log(
|
|
867
|
+
console.log(pc4.yellow(` \u26A0 R2 bucket creation failed (may already exist \u2014 continuing)`));
|
|
855
868
|
}
|
|
856
|
-
console.log(
|
|
869
|
+
console.log(pc4.dim("\n Updating wrangler.jsonc\u2026"));
|
|
857
870
|
const patched = patchWranglerConfig(configPath, dbId);
|
|
858
871
|
if (patched) {
|
|
859
|
-
console.log(
|
|
872
|
+
console.log(pc4.green(" \u2713 wrangler.jsonc updated\n"));
|
|
860
873
|
} else {
|
|
861
|
-
console.log(
|
|
874
|
+
console.log(pc4.yellow(` \u26A0 Could not update wrangler.jsonc automatically
|
|
862
875
|
`));
|
|
863
|
-
console.log(
|
|
864
|
-
console.log(
|
|
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(
|
|
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(
|
|
876
|
-
console.log(
|
|
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(
|
|
888
|
-
console.log(
|
|
889
|
-
console.log(
|
|
890
|
-
console.log(
|
|
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(
|
|
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(
|
|
903
|
-
console.log(
|
|
904
|
-
console.log(
|
|
905
|
-
console.log(
|
|
906
|
-
console.log(
|
|
907
|
-
console.log(
|
|
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(
|
|
924
|
+
console.log(pc4.yellow(` \u26A0 Missing system tables: ${missingTables.join(", ")}
|
|
912
925
|
`));
|
|
913
|
-
console.log(
|
|
914
|
-
console.log(
|
|
915
|
-
console.log(
|
|
916
|
-
console.log(
|
|
917
|
-
console.log(
|
|
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(
|
|
934
|
+
console.log(pc4.green(` \u2713 ${table}`));
|
|
922
935
|
}
|
|
923
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
934
|
-
console.log(
|
|
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(
|
|
939
|
-
console.log(
|
|
940
|
-
console.log(
|
|
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(
|
|
944
|
-
console.log(
|
|
945
|
-
console.log(
|
|
946
|
-
console.log(
|
|
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
|
|
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 ?
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1094
|
+
console.log(pc5.green(`
|
|
1082
1095
|
\u2713 Seed "${slug}" appended to ${seedsPath}
|
|
1083
1096
|
`));
|
|
1084
1097
|
if (!withRegistry) {
|
|
1085
|
-
console.log(
|
|
1098
|
+
console.log(pc5.yellow(` \u26A0 Could not auto-update SEED_REGISTRY \u2014 add this entry manually:
|
|
1086
1099
|
`));
|
|
1087
|
-
console.log(
|
|
1100
|
+
console.log(pc5.cyan(` ${slug}: ${cName},
|
|
1088
1101
|
`));
|
|
1089
1102
|
}
|
|
1090
|
-
console.log(
|
|
1091
|
-
console.log(
|
|
1092
|
-
console.log(
|
|
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
|
|
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(
|
|
1132
|
-
console.log(
|
|
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(
|
|
1143
|
-
console.log(
|
|
1144
|
-
console.log(
|
|
1145
|
-
console.log(
|
|
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(
|
|
1162
|
+
console.log(pc6.green("\n \u2713 Worker deployed"));
|
|
1150
1163
|
if (args.skipSeed) {
|
|
1151
|
-
console.log(
|
|
1164
|
+
console.log(pc6.dim("\n [2/3] Skipping seed:load (--skip-seed)"));
|
|
1152
1165
|
} else {
|
|
1153
|
-
console.log(
|
|
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(
|
|
1161
|
-
console.log(
|
|
1162
|
-
console.log(
|
|
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(
|
|
1177
|
+
console.log(pc6.green("\n \u2713 Content schema synced"));
|
|
1165
1178
|
}
|
|
1166
1179
|
}
|
|
1167
1180
|
if (args.skipCheck) {
|
|
1168
|
-
console.log(
|
|
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(
|
|
1177
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
1202
|
+
console.log(pc6.yellow(` \u26A0 Admin returned an error${statusStr} at: ${adminBase}/admin
|
|
1190
1203
|
`));
|
|
1191
|
-
console.log(
|
|
1192
|
-
console.log(
|
|
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
|
|
1210
|
+
import pc7 from "picocolors";
|
|
1198
1211
|
async function onboard(args) {
|
|
1199
|
-
console.log(
|
|
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(
|
|
1203
|
-
console.log(
|
|
1204
|
-
console.log(
|
|
1205
|
-
console.log(
|
|
1206
|
-
console.log(
|
|
1207
|
-
console.log(
|
|
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
|
|
1224
|
+
import pc8 from "picocolors";
|
|
1212
1225
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
1213
1226
|
async function update(_args) {
|
|
1214
|
-
console.log(
|
|
1215
|
-
console.log(
|
|
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(
|
|
1223
|
-
console.log(
|
|
1224
|
-
console.log(
|
|
1225
|
-
console.log(
|
|
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(
|
|
1229
|
-
console.log(
|
|
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(
|
|
1237
|
-
console.log(
|
|
1238
|
-
console.log(
|
|
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(
|
|
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,
|