@beechcms/cli 0.4.0-preview.12 → 0.4.0-preview.13
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/dist/index.js +681 -20
- package/package.json +2 -2
- package/src/commands/deploy.ts +122 -0
- package/src/commands/init.ts +400 -0
- package/src/commands/seed-create.ts +189 -0
- package/src/commands/seed-load.ts +137 -128
- package/src/commands/validate.ts +83 -0
- package/src/index.ts +10 -2
- package/src/lib/schema-diff.ts +64 -64
- package/src/lib/wrangler.ts +108 -108
- package/tsconfig.json +16 -16
- package/tsconfig.tsbuildinfo +1 -1
- 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,7 +1,7 @@
|
|
|
1
1
|
// src/commands/seed-load.ts
|
|
2
|
-
import
|
|
2
|
+
import pc2 from "picocolors";
|
|
3
3
|
import {
|
|
4
|
-
SEED_REGISTRY,
|
|
4
|
+
SEED_REGISTRY as SEED_REGISTRY2,
|
|
5
5
|
generateCreateTable,
|
|
6
6
|
generateDraftTable,
|
|
7
7
|
generateIndexes,
|
|
@@ -126,6 +126,68 @@ async function diffSeed(seed, options) {
|
|
|
126
126
|
return { slug: seed.slug, tableExists: true, columns };
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// src/commands/validate.ts
|
|
130
|
+
import pc from "picocolors";
|
|
131
|
+
import { SEED_REGISTRY } from "@beechcms/core";
|
|
132
|
+
function validateSeeds(registry) {
|
|
133
|
+
const result = [];
|
|
134
|
+
const slugsSeen = /* @__PURE__ */ new Set();
|
|
135
|
+
for (const seed of Object.values(registry)) {
|
|
136
|
+
const messages = [];
|
|
137
|
+
if (slugsSeen.has(seed.slug)) {
|
|
138
|
+
messages.push(`duplicate slug "${seed.slug}" \u2014 each seed must have a unique slug`);
|
|
139
|
+
}
|
|
140
|
+
slugsSeen.add(seed.slug);
|
|
141
|
+
const aliasesSeen = /* @__PURE__ */ new Set();
|
|
142
|
+
for (const branch of seed.branches) {
|
|
143
|
+
if (aliasesSeen.has(branch.alias)) {
|
|
144
|
+
messages.push(`duplicate branch alias "${branch.alias}"`);
|
|
145
|
+
}
|
|
146
|
+
aliasesSeen.add(branch.alias);
|
|
147
|
+
}
|
|
148
|
+
const allAliases = new Set(seed.branches.map((b) => b.alias));
|
|
149
|
+
if (!allAliases.has(seed.displayNameAlias)) {
|
|
150
|
+
messages.push(`displayNameAlias "${seed.displayNameAlias}" not found in branches`);
|
|
151
|
+
}
|
|
152
|
+
if (messages.length > 0) {
|
|
153
|
+
result.push({ slug: seed.slug, messages });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
async function validate(args) {
|
|
159
|
+
const registry = args.registry ?? SEED_REGISTRY;
|
|
160
|
+
if (Object.keys(registry).length === 0) {
|
|
161
|
+
console.warn(pc.yellow("\n Warning: SEED_REGISTRY is empty. Create a seeds.ts in your project root.\n"));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
console.log(pc.cyan("\n beech validate \u2014 checking seeds\n"));
|
|
165
|
+
const errors = validateSeeds(registry);
|
|
166
|
+
const errorMap = new Map(errors.map((e) => [e.slug, e.messages]));
|
|
167
|
+
let totalIssues = 0;
|
|
168
|
+
for (const seed of Object.values(registry)) {
|
|
169
|
+
const msgs = errorMap.get(seed.slug);
|
|
170
|
+
if (!msgs) {
|
|
171
|
+
console.log(pc.green(` \u2713 ${seed.slug}`));
|
|
172
|
+
} else {
|
|
173
|
+
totalIssues += msgs.length;
|
|
174
|
+
console.log(pc.red(` \u2717 ${seed.slug}`));
|
|
175
|
+
for (const msg of msgs) {
|
|
176
|
+
console.log(pc.red(` \u2192 ${msg}`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
console.log("");
|
|
181
|
+
if (totalIssues > 0) {
|
|
182
|
+
const s = totalIssues !== 1 ? "s" : "";
|
|
183
|
+
console.log(pc.red(` Found ${totalIssues} issue${s}. Fix the seeds above before loading.
|
|
184
|
+
`));
|
|
185
|
+
process.exit(1);
|
|
186
|
+
} else {
|
|
187
|
+
console.log(pc.green(" All seeds valid.\n"));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
129
191
|
// src/commands/seed-load.ts
|
|
130
192
|
function buildStatements(seed) {
|
|
131
193
|
const stmts = [generateCreateTable(seed), ...generateIndexes(seed)];
|
|
@@ -139,71 +201,80 @@ function buildStatements(seed) {
|
|
|
139
201
|
}
|
|
140
202
|
async function runDiff(options, registry) {
|
|
141
203
|
const seeds = Object.values(registry);
|
|
142
|
-
console.log(
|
|
204
|
+
console.log(pc2.cyan("\n Diffing schema\u2026\n"));
|
|
143
205
|
let allOk = true;
|
|
144
206
|
for (const seed of seeds) {
|
|
145
207
|
const result = await diffSeed(seed, options);
|
|
146
208
|
const tableName = `content_${seed.slug}`;
|
|
147
209
|
if (!result.tableExists) {
|
|
148
|
-
console.log(
|
|
210
|
+
console.log(pc2.red(` \u2717 ${tableName} \u2014 table missing`));
|
|
149
211
|
allOk = false;
|
|
150
212
|
continue;
|
|
151
213
|
}
|
|
152
214
|
const problems = result.columns.filter((c) => c.status !== "ok");
|
|
153
215
|
if (problems.length === 0) {
|
|
154
|
-
console.log(
|
|
216
|
+
console.log(pc2.green(` \u2713 ${tableName}`));
|
|
155
217
|
continue;
|
|
156
218
|
}
|
|
157
219
|
allOk = false;
|
|
158
|
-
console.log(
|
|
220
|
+
console.log(pc2.yellow(` \u26A0 ${tableName}`));
|
|
159
221
|
for (const col of problems) {
|
|
160
222
|
if (col.status === "missing") {
|
|
161
|
-
console.log(
|
|
223
|
+
console.log(pc2.red(` + missing column: ${col.name} ${col.expectedType}`));
|
|
162
224
|
} else if (col.status === "extra") {
|
|
163
|
-
console.log(
|
|
225
|
+
console.log(pc2.dim(` ~ orphaned column: "${col.name}" (${col.actualType}) \u2014 exists in DB but not in seeds.ts`));
|
|
164
226
|
} else if (col.status === "type_mismatch") {
|
|
165
|
-
console.log(
|
|
227
|
+
console.log(pc2.red(` \u2260 type mismatch: ${col.name} (expected ${col.expectedType}, got ${col.actualType})`));
|
|
166
228
|
}
|
|
167
229
|
}
|
|
168
230
|
}
|
|
169
231
|
console.log("");
|
|
170
232
|
if (allOk) {
|
|
171
|
-
console.log(
|
|
233
|
+
console.log(pc2.green(" Schema matches seeds. No action needed.\n"));
|
|
172
234
|
} else {
|
|
173
|
-
console.log(
|
|
235
|
+
console.log(pc2.yellow(" Run `beech seed:load` to apply missing tables/columns.\n"));
|
|
174
236
|
}
|
|
175
237
|
}
|
|
176
238
|
async function runLoad(options, dryRun, registry) {
|
|
177
239
|
const seeds = Object.values(registry);
|
|
178
240
|
if (dryRun) {
|
|
179
|
-
console.log(
|
|
241
|
+
console.log(pc2.cyan("\n -- dry-run: SQL that would be executed\n"));
|
|
180
242
|
for (const seed of seeds) {
|
|
181
243
|
const stmts = buildStatements(seed);
|
|
182
|
-
console.log(
|
|
244
|
+
console.log(pc2.dim(` -- content_${seed.slug}`));
|
|
183
245
|
for (const stmt of stmts) {
|
|
184
246
|
console.log(stmt + "\n");
|
|
185
247
|
}
|
|
186
248
|
}
|
|
187
249
|
return;
|
|
188
250
|
}
|
|
189
|
-
console.log(
|
|
251
|
+
console.log(pc2.cyan(`
|
|
190
252
|
Loading seeds into ${options.local ? "local" : "remote"} D1 (${options.db})\u2026
|
|
191
253
|
`));
|
|
192
254
|
for (const seed of seeds) {
|
|
193
255
|
const stmts = buildStatements(seed);
|
|
194
256
|
const sql = stmts.join("\n\n") + "\n";
|
|
195
|
-
process.stdout.write(` ${
|
|
257
|
+
process.stdout.write(` ${pc2.dim("\u2192")} content_${seed.slug}\u2026 `);
|
|
196
258
|
executeD1File(sql, options);
|
|
197
|
-
console.log(
|
|
259
|
+
console.log(pc2.green("done"));
|
|
198
260
|
}
|
|
199
|
-
console.log(
|
|
261
|
+
console.log(pc2.green("\n All seeds loaded.\n"));
|
|
200
262
|
}
|
|
201
263
|
async function seedLoad(args) {
|
|
202
|
-
const registry = args.registry ??
|
|
264
|
+
const registry = args.registry ?? SEED_REGISTRY2;
|
|
203
265
|
if (Object.keys(registry).length === 0) {
|
|
204
|
-
console.warn(
|
|
266
|
+
console.warn(pc2.yellow("\n Warning: SEED_REGISTRY is empty. Create a seeds.ts in your project root.\n"));
|
|
205
267
|
return;
|
|
206
268
|
}
|
|
269
|
+
const validationErrors = validateSeeds(registry);
|
|
270
|
+
if (validationErrors.length > 0) {
|
|
271
|
+
const total = validationErrors.reduce((n, e) => n + e.messages.length, 0);
|
|
272
|
+
const s = total !== 1 ? "s" : "";
|
|
273
|
+
console.log(pc2.yellow(`
|
|
274
|
+
\u26A0 Seed validation found ${total} issue${s}. Schema changes will still be applied.
|
|
275
|
+
`));
|
|
276
|
+
console.log(pc2.dim(' Run "npx beech validate" for details.\n'));
|
|
277
|
+
}
|
|
207
278
|
const configPath = findWranglerConfig();
|
|
208
279
|
const db = args.db ?? resolveDbName(configPath);
|
|
209
280
|
const options = {
|
|
@@ -217,6 +288,596 @@ async function seedLoad(args) {
|
|
|
217
288
|
await runLoad(options, args.dryRun, registry);
|
|
218
289
|
}
|
|
219
290
|
}
|
|
291
|
+
|
|
292
|
+
// src/commands/init.ts
|
|
293
|
+
import pc3 from "picocolors";
|
|
294
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
295
|
+
import { resolve as resolve2, basename } from "node:path";
|
|
296
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
297
|
+
var SYSTEM_TABLES = [
|
|
298
|
+
"users",
|
|
299
|
+
"refresh_tokens",
|
|
300
|
+
"password_reset_tokens",
|
|
301
|
+
"public_idempotency_keys",
|
|
302
|
+
"analytics",
|
|
303
|
+
"system_stats",
|
|
304
|
+
"activity_logs",
|
|
305
|
+
"notifications",
|
|
306
|
+
"media_objects",
|
|
307
|
+
"content_event_log"
|
|
308
|
+
];
|
|
309
|
+
var BASE_SCHEMA_SQL = `
|
|
310
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
311
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
312
|
+
email TEXT NOT NULL UNIQUE,
|
|
313
|
+
password_hash TEXT NOT NULL,
|
|
314
|
+
role TEXT NOT NULL DEFAULT 'editor' CHECK (role IN ('admin', 'editor')),
|
|
315
|
+
name TEXT,
|
|
316
|
+
avatar_url TEXT,
|
|
317
|
+
notification_prefs TEXT NOT NULL DEFAULT '{}',
|
|
318
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
|
322
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
323
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
324
|
+
token_hash TEXT NOT NULL,
|
|
325
|
+
expires_at INTEGER NOT NULL,
|
|
326
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
327
|
+
revoked_at INTEGER DEFAULT NULL
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
CREATE INDEX IF NOT EXISTS idx_refresh_user ON refresh_tokens(user_id);
|
|
331
|
+
CREATE INDEX IF NOT EXISTS idx_refresh_hash ON refresh_tokens(token_hash);
|
|
332
|
+
CREATE INDEX IF NOT EXISTS idx_refresh_expires ON refresh_tokens(expires_at);
|
|
333
|
+
|
|
334
|
+
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
|
335
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
336
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
337
|
+
token_hash TEXT NOT NULL,
|
|
338
|
+
expires_at INTEGER NOT NULL,
|
|
339
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
340
|
+
used_at INTEGER DEFAULT NULL
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
CREATE INDEX IF NOT EXISTS idx_prt_hash ON password_reset_tokens(token_hash);
|
|
344
|
+
CREATE INDEX IF NOT EXISTS idx_prt_user ON password_reset_tokens(user_id);
|
|
345
|
+
|
|
346
|
+
CREATE TABLE IF NOT EXISTS public_idempotency_keys (
|
|
347
|
+
idempotency_key TEXT NOT NULL PRIMARY KEY,
|
|
348
|
+
request_fingerprint TEXT NOT NULL,
|
|
349
|
+
response_status INTEGER NOT NULL,
|
|
350
|
+
response_body TEXT NOT NULL,
|
|
351
|
+
created_at INTEGER NOT NULL,
|
|
352
|
+
expires_at INTEGER NOT NULL
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
CREATE INDEX IF NOT EXISTS idx_idempotency_expires ON public_idempotency_keys(expires_at);
|
|
356
|
+
|
|
357
|
+
CREATE TABLE IF NOT EXISTS analytics (
|
|
358
|
+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
359
|
+
day_ts INTEGER NOT NULL,
|
|
360
|
+
metric TEXT NOT NULL,
|
|
361
|
+
seed TEXT NOT NULL DEFAULT '',
|
|
362
|
+
value INTEGER NOT NULL DEFAULT 0,
|
|
363
|
+
UNIQUE(day_ts, metric, seed)
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
CREATE INDEX IF NOT EXISTS idx_analytics_day ON analytics(day_ts);
|
|
367
|
+
CREATE INDEX IF NOT EXISTS idx_analytics_seed ON analytics(seed, day_ts);
|
|
368
|
+
|
|
369
|
+
CREATE TABLE IF NOT EXISTS system_stats (
|
|
370
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
371
|
+
value TEXT NOT NULL
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
INSERT OR IGNORE INTO system_stats (id, value) VALUES ('total_storage_bytes', '0');
|
|
375
|
+
|
|
376
|
+
CREATE TABLE IF NOT EXISTS activity_logs (
|
|
377
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
378
|
+
user_id TEXT NOT NULL,
|
|
379
|
+
user_email TEXT NOT NULL,
|
|
380
|
+
user_name TEXT,
|
|
381
|
+
action TEXT NOT NULL,
|
|
382
|
+
entity_type TEXT NOT NULL,
|
|
383
|
+
entity_id TEXT NOT NULL,
|
|
384
|
+
entity_slug TEXT,
|
|
385
|
+
details TEXT,
|
|
386
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
CREATE INDEX IF NOT EXISTS idx_activity_user ON activity_logs(user_id);
|
|
390
|
+
CREATE INDEX IF NOT EXISTS idx_activity_created ON activity_logs(created_at);
|
|
391
|
+
|
|
392
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
393
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
394
|
+
title TEXT NOT NULL,
|
|
395
|
+
message TEXT NOT NULL,
|
|
396
|
+
type TEXT NOT NULL DEFAULT 'info' CHECK (type IN ('info', 'warning', 'error')),
|
|
397
|
+
is_read INTEGER NOT NULL DEFAULT 0 CHECK (is_read IN (0, 1)),
|
|
398
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_created ON notifications(created_at);
|
|
402
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_unread ON notifications(is_read);
|
|
403
|
+
|
|
404
|
+
CREATE TABLE IF NOT EXISTS media_objects (
|
|
405
|
+
key TEXT NOT NULL PRIMARY KEY,
|
|
406
|
+
filename TEXT NOT NULL,
|
|
407
|
+
mime_type TEXT NOT NULL,
|
|
408
|
+
size_bytes INTEGER NOT NULL,
|
|
409
|
+
uploaded_by TEXT NOT NULL DEFAULT '',
|
|
410
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
CREATE INDEX IF NOT EXISTS idx_media_user ON media_objects(uploaded_by);
|
|
414
|
+
CREATE INDEX IF NOT EXISTS idx_media_created ON media_objects(created_at DESC);
|
|
415
|
+
|
|
416
|
+
CREATE TABLE IF NOT EXISTS content_event_log (
|
|
417
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
418
|
+
schema_slug TEXT NOT NULL,
|
|
419
|
+
entry_id TEXT NOT NULL,
|
|
420
|
+
action TEXT NOT NULL CHECK (action IN ('create', 'update', 'delete')),
|
|
421
|
+
user_id TEXT,
|
|
422
|
+
details TEXT,
|
|
423
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
CREATE INDEX IF NOT EXISTS idx_event_log_schema_slug ON content_event_log(schema_slug);
|
|
427
|
+
CREATE INDEX IF NOT EXISTS idx_event_log_created_at ON content_event_log(created_at DESC);
|
|
428
|
+
CREATE INDEX IF NOT EXISTS idx_event_log_entry_id ON content_event_log(entry_id);
|
|
429
|
+
`.trim();
|
|
430
|
+
var PLACEHOLDER_DB_IDS = [
|
|
431
|
+
"INCOLLA_QUI_IL_TUO_ID_D1",
|
|
432
|
+
"FILL_IN_YOUR_D1_DATABASE_ID",
|
|
433
|
+
"YOUR_D1_DATABASE_ID"
|
|
434
|
+
];
|
|
435
|
+
function checkWranglerAuth() {
|
|
436
|
+
const result = spawnSync2("npx", ["wrangler", "whoami", "--json"], {
|
|
437
|
+
encoding: "utf-8",
|
|
438
|
+
cwd: process.cwd(),
|
|
439
|
+
shell: true
|
|
440
|
+
});
|
|
441
|
+
return result.status === 0;
|
|
442
|
+
}
|
|
443
|
+
function checkWranglerPlaceholders(configPath) {
|
|
444
|
+
try {
|
|
445
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
446
|
+
const stripped = raw.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
447
|
+
const parsed = JSON.parse(stripped);
|
|
448
|
+
const bindings = parsed?.d1_databases ?? [];
|
|
449
|
+
const issues = [];
|
|
450
|
+
for (const b of bindings) {
|
|
451
|
+
const id = b.database_id ?? "";
|
|
452
|
+
if (!id || PLACEHOLDER_DB_IDS.includes(id)) {
|
|
453
|
+
issues.push(`d1_databases[0].database_id is "${id || "(empty)"}"`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return issues;
|
|
457
|
+
} catch {
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function echoApiKeys(configPath) {
|
|
462
|
+
if (!configPath) return;
|
|
463
|
+
try {
|
|
464
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
465
|
+
const stripped = raw.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
466
|
+
const parsed = JSON.parse(stripped);
|
|
467
|
+
const vars = parsed?.vars ?? {};
|
|
468
|
+
const readKey = vars["PUBLIC_READ_API_KEY"];
|
|
469
|
+
const writeKey = vars["PUBLIC_WRITE_API_KEY"];
|
|
470
|
+
if (!readKey && !writeKey) return;
|
|
471
|
+
console.log(pc3.dim(" API keys detected in wrangler.jsonc:\n"));
|
|
472
|
+
if (readKey) {
|
|
473
|
+
const masked = readKey.length > 8 ? readKey.slice(0, 4) + "****" + readKey.slice(-4) : "****";
|
|
474
|
+
console.log(pc3.dim(` PUBLIC_READ_API_KEY = ${masked}`));
|
|
475
|
+
}
|
|
476
|
+
if (writeKey) {
|
|
477
|
+
const masked = writeKey.length > 8 ? writeKey.slice(0, 4) + "****" + writeKey.slice(-4) : "****";
|
|
478
|
+
console.log(pc3.dim(` PUBLIC_WRITE_API_KEY = ${masked}`));
|
|
479
|
+
}
|
|
480
|
+
console.log(pc3.dim("\n Use these in your frontend as the X-API-Key header.\n"));
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function checkFiles(cwd, checkDevVars) {
|
|
485
|
+
let ok = true;
|
|
486
|
+
const workerExists = existsSync2(resolve2(cwd, "worker.ts")) || existsSync2(resolve2(cwd, "worker.js"));
|
|
487
|
+
if (!workerExists) {
|
|
488
|
+
console.log(pc3.red(" \u2717 worker.ts \u2014 missing (required)"));
|
|
489
|
+
ok = false;
|
|
490
|
+
} else {
|
|
491
|
+
console.log(pc3.green(" \u2713 worker.ts"));
|
|
492
|
+
}
|
|
493
|
+
const configPath = findWranglerConfig();
|
|
494
|
+
const configInCwd = configPath && (configPath === resolve2(cwd, "wrangler.jsonc") || configPath === resolve2(cwd, "wrangler.json") || configPath === resolve2(cwd, "wrangler.toml"));
|
|
495
|
+
if (!configInCwd) {
|
|
496
|
+
console.log(pc3.red(" \u2717 wrangler.jsonc \u2014 missing (required)"));
|
|
497
|
+
ok = false;
|
|
498
|
+
} else {
|
|
499
|
+
console.log(pc3.green(` \u2713 ${basename(configPath)}`));
|
|
500
|
+
}
|
|
501
|
+
const seedsExists = existsSync2(resolve2(cwd, "seeds.ts")) || existsSync2(resolve2(cwd, "seeds.js")) || existsSync2(resolve2(cwd, "seed.ts")) || existsSync2(resolve2(cwd, "seed.js"));
|
|
502
|
+
if (!seedsExists) {
|
|
503
|
+
console.log(pc3.yellow(" \u26A0 seeds.ts \u2014 missing (create it, then run beech seed:load)"));
|
|
504
|
+
} else {
|
|
505
|
+
console.log(pc3.green(" \u2713 seeds.ts"));
|
|
506
|
+
}
|
|
507
|
+
if (checkDevVars) {
|
|
508
|
+
if (!existsSync2(resolve2(cwd, ".dev.vars"))) {
|
|
509
|
+
console.log(pc3.dim(" \u25CB .dev.vars \u2014 not found (optional: only needed for production R2 credentials)"));
|
|
510
|
+
} else {
|
|
511
|
+
console.log(pc3.green(" \u2713 .dev.vars"));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return ok;
|
|
515
|
+
}
|
|
516
|
+
function printNextSteps(local) {
|
|
517
|
+
const localFlag = local ? " --local" : "";
|
|
518
|
+
console.log(pc3.dim(" Next steps:"));
|
|
519
|
+
console.log(pc3.cyan(` 1. npx beech seed:load${localFlag}`));
|
|
520
|
+
console.log(pc3.dim(" \u2192 create content tables from seeds.ts"));
|
|
521
|
+
console.log(pc3.cyan(" 2. npx wrangler dev"));
|
|
522
|
+
console.log(pc3.dim(" \u2192 start API + dashboard"));
|
|
523
|
+
console.log(pc3.dim(" 3. Open http://localhost:8789/admin\n"));
|
|
524
|
+
}
|
|
525
|
+
function getExistingTables(options) {
|
|
526
|
+
try {
|
|
527
|
+
const rows = queryD1(
|
|
528
|
+
`SELECT name FROM sqlite_master WHERE type='table'`,
|
|
529
|
+
options
|
|
530
|
+
);
|
|
531
|
+
return rows.map((r) => r.name);
|
|
532
|
+
} catch {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
async function init(args) {
|
|
537
|
+
const cwd = process.cwd();
|
|
538
|
+
console.log(pc3.cyan("\n beech init \u2014 project check\n"));
|
|
539
|
+
const filesOk = checkFiles(cwd, args.local);
|
|
540
|
+
if (!filesOk) {
|
|
541
|
+
console.log(pc3.red("\n \u2717 Required files missing. Fix the errors above before initialising the database.\n"));
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
console.log(pc3.green("\n All required files present.\n"));
|
|
545
|
+
if (!args.initDb) {
|
|
546
|
+
echoApiKeys(findWranglerConfig());
|
|
547
|
+
const localFlag = args.local ? " --local" : "";
|
|
548
|
+
console.log(pc3.dim(" Next steps:"));
|
|
549
|
+
console.log(pc3.dim(` 1. npx beech init --db${localFlag} # initialise D1 database`));
|
|
550
|
+
console.log(pc3.dim(` 2. npx beech seed:load${localFlag} # create content tables`));
|
|
551
|
+
console.log(pc3.dim(" 3. npx wrangler dev # start API + dashboard"));
|
|
552
|
+
console.log(pc3.dim(" 4. Open http://localhost:8789/admin\n"));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const configPath = findWranglerConfig();
|
|
556
|
+
if (configPath) {
|
|
557
|
+
const placeholders = checkWranglerPlaceholders(configPath);
|
|
558
|
+
if (placeholders.length > 0) {
|
|
559
|
+
console.log(pc3.yellow(" \u26A0 wrangler.jsonc contains placeholder values:\n"));
|
|
560
|
+
for (const issue of placeholders) {
|
|
561
|
+
console.log(pc3.yellow(` - ${issue}`));
|
|
562
|
+
}
|
|
563
|
+
console.log(pc3.dim("\n Update your D1 database_id in wrangler.jsonc,"));
|
|
564
|
+
console.log(pc3.dim(" or create a new database with:"));
|
|
565
|
+
console.log(pc3.cyan("\n npx wrangler d1 create my-project-db\n"));
|
|
566
|
+
console.log(pc3.dim(" Then retry: npx beech init --db\n"));
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (!args.local) {
|
|
571
|
+
const authed = checkWranglerAuth();
|
|
572
|
+
if (!authed) {
|
|
573
|
+
console.log(pc3.red(" \u2717 Not logged in to Cloudflare\n"));
|
|
574
|
+
console.log(pc3.dim(" BeechCMS needs access to your Cloudflare account to manage the D1 database.\n"));
|
|
575
|
+
console.log(pc3.cyan(" \u2192 Run: npx wrangler login"));
|
|
576
|
+
console.log(pc3.cyan(" \u2192 Then: npx beech init --db\n"));
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const db = args.db ?? resolveDbName(configPath);
|
|
581
|
+
const options = { db, local: args.local, configPath };
|
|
582
|
+
console.log(pc3.cyan(` Checking database "${db}" (${args.local ? "local" : "remote"})\u2026
|
|
583
|
+
`));
|
|
584
|
+
const existingTables = getExistingTables(options);
|
|
585
|
+
const missingTables = SYSTEM_TABLES.filter((t) => !existingTables?.includes(t));
|
|
586
|
+
if (!args.local) {
|
|
587
|
+
if (existingTables === null) {
|
|
588
|
+
console.log(pc3.red(" \u2717 Remote database unreachable\n"));
|
|
589
|
+
console.log(pc3.dim(" Most likely causes:"));
|
|
590
|
+
console.log(pc3.dim(" - Wrong database_id in wrangler.jsonc"));
|
|
591
|
+
console.log(pc3.dim(" - Worker not yet deployed\n"));
|
|
592
|
+
console.log(pc3.dim(" Fix the configuration and re-deploy:\n"));
|
|
593
|
+
console.log(pc3.cyan(" 1. Update d1_databases.database_id in wrangler.jsonc"));
|
|
594
|
+
console.log(pc3.cyan(" 2. npm run deploy\n"));
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
if (missingTables.length > 0) {
|
|
598
|
+
console.log(pc3.yellow(` \u26A0 Missing system tables: ${missingTables.join(", ")}
|
|
599
|
+
`));
|
|
600
|
+
console.log(pc3.dim(" Most likely causes:"));
|
|
601
|
+
console.log(pc3.dim(" - Wrong database_id in wrangler.jsonc"));
|
|
602
|
+
console.log(pc3.dim(" - Migrations did not run during deploy\n"));
|
|
603
|
+
console.log(pc3.dim(" Fix the configuration and re-deploy:\n"));
|
|
604
|
+
console.log(pc3.cyan(" 1. Update d1_databases.database_id in wrangler.jsonc"));
|
|
605
|
+
console.log(pc3.cyan(" 2. npm run deploy\n"));
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
for (const table of SYSTEM_TABLES) {
|
|
609
|
+
console.log(pc3.green(` \u2713 ${table}`));
|
|
610
|
+
}
|
|
611
|
+
console.log(pc3.green("\n All system tables present. Remote database is initialized.\n"));
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (existingTables === null) {
|
|
615
|
+
console.log(pc3.yellow(" Database unreachable or not yet created \u2014 applying base schema\u2026\n"));
|
|
616
|
+
} else if (missingTables.length === 0) {
|
|
617
|
+
console.log(pc3.green(" \u2713 All system tables present. Database already initialised.\n"));
|
|
618
|
+
printNextSteps(args.local);
|
|
619
|
+
return;
|
|
620
|
+
} else {
|
|
621
|
+
console.log(pc3.yellow(` Missing system tables: ${missingTables.join(", ")}`));
|
|
622
|
+
console.log(pc3.cyan("\n Applying base schema\u2026\n"));
|
|
623
|
+
}
|
|
624
|
+
executeD1File(BASE_SCHEMA_SQL, options);
|
|
625
|
+
console.log(pc3.green("\n \u2713 worker.ts"));
|
|
626
|
+
console.log(pc3.green(` \u2713 ${configPath ? basename(configPath) : "wrangler.jsonc"}`));
|
|
627
|
+
console.log(pc3.green(" \u2713 seeds.ts"));
|
|
628
|
+
console.log(pc3.green(" \u2713 Local D1 system tables ready\n"));
|
|
629
|
+
echoApiKeys(configPath);
|
|
630
|
+
printNextSteps(args.local);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/commands/seed-create.ts
|
|
634
|
+
import pc4 from "picocolors";
|
|
635
|
+
import { createInterface } from "node:readline/promises";
|
|
636
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
637
|
+
import { resolve as resolve3 } from "node:path";
|
|
638
|
+
var BRANCH_TYPES = ["text", "number", "boolean", "date", "richtext", "file", "tags"];
|
|
639
|
+
function slugify(str) {
|
|
640
|
+
return str.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
641
|
+
}
|
|
642
|
+
function toConstName(slug) {
|
|
643
|
+
return slug.replace(/-/g, "_").toUpperCase() + "_SEED";
|
|
644
|
+
}
|
|
645
|
+
function toLabel(alias) {
|
|
646
|
+
return alias.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
|
|
647
|
+
}
|
|
648
|
+
function generateSeedBlock(slug, label, labelPlural, branches) {
|
|
649
|
+
const displayAlias = branches.find((b) => b.type === "text")?.alias ?? branches[0]?.alias ?? "name";
|
|
650
|
+
const cName = toConstName(slug);
|
|
651
|
+
const branchLines = branches.map((b) => {
|
|
652
|
+
const parts = [
|
|
653
|
+
`alias: '${b.alias}'`,
|
|
654
|
+
`label: '${b.label}'`,
|
|
655
|
+
`type: '${b.type}'`
|
|
656
|
+
];
|
|
657
|
+
if (b.required) parts.push("requiredOnCreate: true");
|
|
658
|
+
return ` { ${parts.join(", ")} },`;
|
|
659
|
+
});
|
|
660
|
+
return [
|
|
661
|
+
"",
|
|
662
|
+
`export const ${cName} = defineSeed({`,
|
|
663
|
+
` slug: '${slug}',`,
|
|
664
|
+
` label: '${label}',`,
|
|
665
|
+
` labelPlural: '${labelPlural}',`,
|
|
666
|
+
` displayNameAlias: '${displayAlias}',`,
|
|
667
|
+
" branches: [",
|
|
668
|
+
...branchLines,
|
|
669
|
+
" ],",
|
|
670
|
+
" dashboard: {",
|
|
671
|
+
" icon: 'Folder',",
|
|
672
|
+
" group: 'Content',",
|
|
673
|
+
" },",
|
|
674
|
+
"})",
|
|
675
|
+
""
|
|
676
|
+
].join("\n");
|
|
677
|
+
}
|
|
678
|
+
function ensureDefineSeedImport(content) {
|
|
679
|
+
if (content.includes("defineSeed")) return content;
|
|
680
|
+
return `import { defineSeed } from '@beechcms/core'
|
|
681
|
+
` + content;
|
|
682
|
+
}
|
|
683
|
+
function tryInsertRegistryEntry(content, slug, cName) {
|
|
684
|
+
const match = content.match(/(SEED_REGISTRY[^{]*\{)([\s\S]*?)(\n\})/m);
|
|
685
|
+
if (!match) return null;
|
|
686
|
+
const [full, open, inner, close] = match;
|
|
687
|
+
const newEntry = `
|
|
688
|
+
${slug}: ${cName},`;
|
|
689
|
+
return content.replace(full, open + inner + newEntry + close);
|
|
690
|
+
}
|
|
691
|
+
function findSeedsFile() {
|
|
692
|
+
const cwd = process.cwd();
|
|
693
|
+
const searchDirs = [cwd, resolve3(cwd, "apps", "api")];
|
|
694
|
+
for (const dir of searchDirs) {
|
|
695
|
+
for (const name of ["seeds.ts", "seed.ts"]) {
|
|
696
|
+
const p = resolve3(dir, name);
|
|
697
|
+
if (existsSync3(p)) return p;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
async function seedCreate(_args) {
|
|
703
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
704
|
+
const ask = async (q, fallback = "") => {
|
|
705
|
+
const hint = fallback ? pc4.dim(` [${fallback}]`) : "";
|
|
706
|
+
const answer = await rl.question(` ${q}${hint}: `);
|
|
707
|
+
return answer.trim() || fallback;
|
|
708
|
+
};
|
|
709
|
+
const askYN = async (q, defaultYes = true) => {
|
|
710
|
+
const hint = defaultYes ? pc4.dim(" (Y/n)") : pc4.dim(" (y/N)");
|
|
711
|
+
const answer = (await rl.question(` ${q}${hint}: `)).trim().toLowerCase();
|
|
712
|
+
if (!answer) return defaultYes;
|
|
713
|
+
return answer === "y" || answer === "yes";
|
|
714
|
+
};
|
|
715
|
+
console.log(pc4.cyan("\n beech seed:create \u2014 new content type wizard\n"));
|
|
716
|
+
try {
|
|
717
|
+
const label = await ask('Content type name (singular, e.g. "Article")');
|
|
718
|
+
if (!label) {
|
|
719
|
+
rl.close();
|
|
720
|
+
console.log(pc4.red("\n \u2717 Name required.\n"));
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
const defaultSlug = slugify(label) + "s";
|
|
724
|
+
const slug = slugify(await ask("Slug (plural, used in URL + table name)", defaultSlug)) || defaultSlug;
|
|
725
|
+
const labelPlural = await ask("Plural label", label + "s") || label + "s";
|
|
726
|
+
const branches = [];
|
|
727
|
+
console.log(pc4.dim("\n Now define the fields. Press Enter to accept defaults.\n"));
|
|
728
|
+
let addMore = true;
|
|
729
|
+
while (addMore) {
|
|
730
|
+
console.log(pc4.dim(` \u2500\u2500\u2500 Field ${branches.length + 1} \u2500\u2500\u2500`));
|
|
731
|
+
const alias = await ask(' Alias (camelCase, e.g. "title", "publishedAt")');
|
|
732
|
+
if (!alias) {
|
|
733
|
+
console.log(pc4.yellow(" Alias required \u2014 skipping."));
|
|
734
|
+
addMore = await askYN("\n Add a field?");
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const fieldLabel = await ask(" Label", toLabel(alias)) || toLabel(alias);
|
|
738
|
+
const typeList = BRANCH_TYPES.join(" | ");
|
|
739
|
+
const rawType = (await ask(` Type (${typeList})`, "text")).toLowerCase();
|
|
740
|
+
const type = BRANCH_TYPES.includes(rawType) ? rawType : "text";
|
|
741
|
+
const required = await askYN(" Required on create?", false);
|
|
742
|
+
branches.push({ alias, label: fieldLabel, type, required });
|
|
743
|
+
addMore = await askYN("\n Add another field?");
|
|
744
|
+
}
|
|
745
|
+
rl.close();
|
|
746
|
+
if (branches.length === 0) {
|
|
747
|
+
console.log(pc4.yellow("\n No fields defined \u2014 seed not created.\n"));
|
|
748
|
+
process.exit(0);
|
|
749
|
+
}
|
|
750
|
+
const seedBlock = generateSeedBlock(slug, label, labelPlural, branches);
|
|
751
|
+
const cName = toConstName(slug);
|
|
752
|
+
const seedsPath = findSeedsFile();
|
|
753
|
+
if (!seedsPath) {
|
|
754
|
+
console.log(pc4.yellow("\n Could not find seeds.ts. Add this to your seeds file manually:\n"));
|
|
755
|
+
console.log(seedBlock);
|
|
756
|
+
process.exit(0);
|
|
757
|
+
}
|
|
758
|
+
let content = readFileSync3(seedsPath, "utf-8");
|
|
759
|
+
content = ensureDefineSeedImport(content);
|
|
760
|
+
const withSeed = content + seedBlock;
|
|
761
|
+
const withRegistry = tryInsertRegistryEntry(withSeed, slug, cName);
|
|
762
|
+
writeFileSync2(seedsPath, withRegistry ?? withSeed, "utf-8");
|
|
763
|
+
console.log(pc4.green(`
|
|
764
|
+
\u2713 Seed "${slug}" appended to ${seedsPath}
|
|
765
|
+
`));
|
|
766
|
+
if (!withRegistry) {
|
|
767
|
+
console.log(pc4.yellow(` \u26A0 Could not auto-update SEED_REGISTRY \u2014 add this entry manually:
|
|
768
|
+
`));
|
|
769
|
+
console.log(pc4.cyan(` ${slug}: ${cName},
|
|
770
|
+
`));
|
|
771
|
+
}
|
|
772
|
+
console.log(pc4.dim(" Next steps:"));
|
|
773
|
+
console.log(pc4.cyan(" npx beech seed:load --local"));
|
|
774
|
+
console.log(pc4.dim(" \u2192 create the new content table in your local D1 database\n"));
|
|
775
|
+
} catch (err) {
|
|
776
|
+
rl.close();
|
|
777
|
+
throw err;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/commands/deploy.ts
|
|
782
|
+
import pc5 from "picocolors";
|
|
783
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
784
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
785
|
+
function readWorkerName(configPath) {
|
|
786
|
+
if (!configPath) return null;
|
|
787
|
+
try {
|
|
788
|
+
const raw = readFileSync4(configPath, "utf-8");
|
|
789
|
+
const stripped = raw.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
790
|
+
const parsed = JSON.parse(stripped);
|
|
791
|
+
return parsed?.name ?? null;
|
|
792
|
+
} catch {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
function extractWorkerUrl(output) {
|
|
797
|
+
const match = output.match(/https:\/\/[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.workers\.dev\b/);
|
|
798
|
+
return match?.[0] ?? null;
|
|
799
|
+
}
|
|
800
|
+
async function checkAdmin(url) {
|
|
801
|
+
try {
|
|
802
|
+
const res = await fetch(`${url}/admin`, {
|
|
803
|
+
method: "HEAD",
|
|
804
|
+
redirect: "follow",
|
|
805
|
+
signal: AbortSignal.timeout(12e3)
|
|
806
|
+
});
|
|
807
|
+
return { ok: res.status < 500, status: res.status };
|
|
808
|
+
} catch {
|
|
809
|
+
return { ok: false, status: null };
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async function deploy(args) {
|
|
813
|
+
console.log(pc5.cyan("\n beech deploy\n"));
|
|
814
|
+
console.log(pc5.dim(" [1/3] Deploying Worker\u2026\n"));
|
|
815
|
+
const deployResult = spawnSync3("npm", ["run", "deploy"], {
|
|
816
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
817
|
+
encoding: "utf-8",
|
|
818
|
+
cwd: process.cwd(),
|
|
819
|
+
shell: true
|
|
820
|
+
});
|
|
821
|
+
const deployStdout = deployResult.stdout ?? "";
|
|
822
|
+
if (deployStdout) process.stdout.write(deployStdout);
|
|
823
|
+
if (deployResult.status !== 0) {
|
|
824
|
+
console.log(pc5.red("\n \u2717 Worker deploy failed\n"));
|
|
825
|
+
console.log(pc5.dim(" Check the output above for wrangler errors."));
|
|
826
|
+
console.log(pc5.dim(" Common causes:"));
|
|
827
|
+
console.log(pc5.dim(" - Not logged in to Cloudflare \u2192 npx wrangler login"));
|
|
828
|
+
console.log(pc5.dim(" - Wrong database_id in wrangler.jsonc\n"));
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
const deployedUrl = extractWorkerUrl(deployStdout);
|
|
832
|
+
console.log(pc5.green("\n \u2713 Worker deployed"));
|
|
833
|
+
if (args.skipSeed) {
|
|
834
|
+
console.log(pc5.dim("\n [2/3] Skipping seed:load (--skip-seed)"));
|
|
835
|
+
} else {
|
|
836
|
+
console.log(pc5.dim("\n [2/3] Syncing content schema to remote D1\u2026\n"));
|
|
837
|
+
const seedResult = spawnSync3("npx", ["beech", "seed:load"], {
|
|
838
|
+
stdio: "inherit",
|
|
839
|
+
cwd: process.cwd(),
|
|
840
|
+
shell: true
|
|
841
|
+
});
|
|
842
|
+
if (seedResult.status !== 0) {
|
|
843
|
+
console.log(pc5.yellow("\n \u26A0 seed:load failed \u2014 run manually: npx beech seed:load"));
|
|
844
|
+
} else {
|
|
845
|
+
console.log(pc5.green("\n \u2713 Content schema synced"));
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (args.skipCheck) {
|
|
849
|
+
console.log(pc5.dim("\n [3/3] Skipping admin check (--skip-check)\n"));
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const adminBase = deployedUrl ?? (() => {
|
|
853
|
+
const workerName = readWorkerName(findWranglerConfig());
|
|
854
|
+
return workerName ? `https://${workerName}.workers.dev` : null;
|
|
855
|
+
})();
|
|
856
|
+
if (!adminBase) {
|
|
857
|
+
console.log(pc5.dim("\n [3/3] Could not determine worker URL \u2014 skipping admin check\n"));
|
|
858
|
+
console.log(pc5.dim(" The deployed URL is printed by wrangler above. Open <url>/admin to verify.\n"));
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
console.log(pc5.dim(`
|
|
862
|
+
[3/3] Checking ${adminBase}/admin\u2026
|
|
863
|
+
`));
|
|
864
|
+
const { ok, status } = await checkAdmin(adminBase);
|
|
865
|
+
if (ok) {
|
|
866
|
+
console.log(pc5.green(` \u2713 Admin reachable at: ${adminBase}/admin
|
|
867
|
+
`));
|
|
868
|
+
} else {
|
|
869
|
+
const statusStr = status != null ? ` (HTTP ${status})` : "";
|
|
870
|
+
console.log(pc5.yellow(` \u26A0 Admin returned an error${statusStr} at: ${adminBase}/admin
|
|
871
|
+
`));
|
|
872
|
+
console.log(pc5.dim(" The database may not be fully initialized."));
|
|
873
|
+
console.log(pc5.cyan(" \u2192 Run: npx beech init --db --remote\n"));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
220
876
|
export {
|
|
221
|
-
|
|
877
|
+
deploy,
|
|
878
|
+
init,
|
|
879
|
+
seedCreate,
|
|
880
|
+
seedLoad,
|
|
881
|
+
validate,
|
|
882
|
+
validateSeeds
|
|
222
883
|
};
|