playcademy 0.14.32 → 0.14.33

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/db.d.ts CHANGED
@@ -30,9 +30,17 @@ import { Miniflare } from 'miniflare';
30
30
  declare function getDevDbPath(): string;
31
31
 
32
32
  /**
33
- * Seed Worker Bundler
34
- *
35
- * Bundles user's seed file into standalone Cloudflare Worker
33
+ * Environment context passed to seed functions
34
+ */
35
+ interface SeedContext {
36
+ env: {
37
+ DB: unknown;
38
+ BUCKET?: unknown;
39
+ secrets?: Record<string, string>;
40
+ };
41
+ }
42
+ /**
43
+ * Result of bundling a seed file into a Cloudflare Worker
36
44
  */
37
45
  interface SeedWorkerBundle {
38
46
  /** Bundled worker code */
@@ -40,13 +48,29 @@ interface SeedWorkerBundle {
40
48
  /** Size in bytes */
41
49
  size: number;
42
50
  }
51
+
43
52
  /**
44
- * Bundle seed file into a Cloudflare Worker
53
+ * Seed Worker Bundler
54
+ *
55
+ * Bundles user's seed file into standalone Cloudflare Worker
56
+ * with enhanced logging capture and structured error reporting.
57
+ */
58
+
59
+ /**
60
+ * Generate the worker entry code that wraps the user's seed function.
61
+ *
62
+ * The generated worker:
63
+ * - Imports the user's seed function from the specified path
64
+ * - Captures console output (log/warn/error/info) with timestamps
65
+ * - Parses D1/SQLite errors into structured details
66
+ * - Returns success/error as JSON with logs and timing
45
67
  *
46
- * Creates a minimal worker that:
47
- * 1. Imports user's seed function
48
- * 2. Wraps it in fetch handler
49
- * 3. Returns success/error as JSON
68
+ * @param seedFilePath - Absolute path to user's seed file
69
+ * @returns Worker entry code as a string
70
+ */
71
+ declare function createSeedWorkerEntry(seedFilePath: string): Promise<string>;
72
+ /**
73
+ * Bundle seed file into a Cloudflare Worker
50
74
  *
51
75
  * @param seedFilePath - Absolute path to seed file
52
76
  * @param projectPath - Project root (for resolving imports)
@@ -75,17 +99,6 @@ declare function resetDatabase(workspace: string, mf: Miniflare, options?: {
75
99
  debug?: boolean;
76
100
  }): Promise<void>;
77
101
 
78
- /**
79
- * Environment context passed to seed functions
80
- */
81
- interface SeedContext {
82
- env: {
83
- DB: unknown;
84
- BUCKET?: unknown;
85
- secrets?: Record<string, string>;
86
- };
87
- }
88
-
89
102
  /**
90
103
  * Database Seed Utilities
91
104
  *
@@ -110,5 +123,40 @@ declare function getBucket(mf: Miniflare): Promise<unknown | null>;
110
123
  */
111
124
  declare function executeSeedFile(seedFilePath: string, mf: Miniflare, envSecrets?: Record<string, string>): Promise<void>;
112
125
 
113
- export { bundleSeedWorker, executeSeedFile, getBucket, getDevDbPath as getPath, importSeedModule, resetDatabase };
126
+ /** Log entry captured from seed worker console output */
127
+ interface SeedLogEntry {
128
+ /** Log level (log, warn, error, info) */
129
+ level: 'log' | 'warn' | 'error' | 'info';
130
+ /** Log message content */
131
+ message: string;
132
+ /** Milliseconds since seed execution started */
133
+ timestamp: number;
134
+ }
135
+
136
+ /**
137
+ * Seed Output Utilities
138
+ *
139
+ * Functions for displaying seed execution results and handling errors.
140
+ */
141
+
142
+ /**
143
+ * Display captured logs from seed execution.
144
+ * Uses a vertical line to indicate output is from another source (the seed file).
145
+ *
146
+ * @param logs - Captured log entries
147
+ * @param status - 'success' for green bar, 'error' for red bar
148
+ */
149
+ declare function displaySeedLogs(logs: SeedLogEntry[], status?: 'success' | 'error'): void;
150
+ /**
151
+ * Handle seed-specific errors with helpful messages.
152
+ *
153
+ * For seed execution errors, displays captured logs with error styling.
154
+ * For infrastructure errors (network, auth, etc.), shows admonitions.
155
+ *
156
+ * Returns true if the error was handled (caller should show simple failure message).
157
+ * Returns false if not handled (caller should show the error message).
158
+ */
159
+ declare function handleSeedError(error: unknown): boolean;
160
+
161
+ export { bundleSeedWorker, createSeedWorkerEntry, displaySeedLogs, executeSeedFile, getBucket, getDevDbPath as getPath, handleSeedError, importSeedModule, resetDatabase };
114
162
  export type { SeedWorkerBundle };
package/dist/db.js CHANGED
@@ -89,7 +89,7 @@ var package_default = {
89
89
  sort: "bunx sort-package-json **/package.json",
90
90
  test: "bun test",
91
91
  "test:unit": "bun scripts/test-unit.ts",
92
- "test:integration": "bun scripts/test-integration.ts",
92
+ "test:e2e": "bun scripts/test-e2e.ts",
93
93
  "test:watch": "bun test --watch",
94
94
  "docker:relay": "bun run scripts/docker-relay.ts",
95
95
  "debug:leaderboard-notifications": "bunx sst shell -- bun scripts/debug/leaderboard-notifications.ts",
@@ -216,6 +216,104 @@ var CLI_FILES = {
216
216
  INITIAL_DATABASE: "initial.sqlite"
217
217
  };
218
218
 
219
+ // ../constants/src/achievements.ts
220
+ var ACHIEVEMENT_IDS = {
221
+ PLAY_ANY_GAME_DAILY: "play_any_game_daily",
222
+ DAILY_CHEST_OPEN: "daily_chest_open",
223
+ LEADERBOARD_TOP3_DAILY: "leaderboard_top3_daily",
224
+ LEADERBOARD_TOP3_WEEKLY: "leaderboard_top3_weekly",
225
+ FIRST_SCORE_ANY_GAME: "first_score_any_game",
226
+ PERSONAL_BEST_ANY_GAME: "personal_best_any_game"
227
+ };
228
+ var ACHIEVEMENT_DEFINITIONS = [
229
+ {
230
+ id: ACHIEVEMENT_IDS.PLAY_ANY_GAME_DAILY,
231
+ title: "Play any game",
232
+ description: "Play any arcade game for at least 60 seconds in a single session.",
233
+ scope: "daily",
234
+ rewardCredits: 10,
235
+ limit: 1,
236
+ completionType: "time_played_session",
237
+ completionConfig: { minSeconds: 60 },
238
+ target: { anyArcadeGame: true },
239
+ active: true
240
+ },
241
+ {
242
+ id: ACHIEVEMENT_IDS.DAILY_CHEST_OPEN,
243
+ title: "Opened the daily chest",
244
+ description: "Find the chest on the map and open it to claim your reward.",
245
+ scope: "daily",
246
+ rewardCredits: 10,
247
+ limit: 1,
248
+ completionType: "interaction",
249
+ completionConfig: { triggerId: "bunny_chest" },
250
+ target: { map: "arcade" },
251
+ active: true
252
+ },
253
+ {
254
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_DAILY,
255
+ title: "Daily Champion",
256
+ description: "Finish in the top 3 of any game leaderboard for the day.",
257
+ scope: "daily",
258
+ rewardCredits: 25,
259
+ // Base reward, overridden by rankRewards
260
+ limit: 1,
261
+ completionType: "leaderboard_rank",
262
+ completionConfig: {
263
+ rankRewards: [50, 30, 15]
264
+ // [1st place, 2nd place, 3rd place]
265
+ },
266
+ target: { anyArcadeGame: true },
267
+ active: true
268
+ },
269
+ {
270
+ id: ACHIEVEMENT_IDS.LEADERBOARD_TOP3_WEEKLY,
271
+ title: "Weekly Legend",
272
+ description: "Finish in the top 3 of any game leaderboard for the week.",
273
+ scope: "weekly",
274
+ rewardCredits: 50,
275
+ // Base reward, overridden by rankRewards
276
+ limit: 1,
277
+ completionType: "leaderboard_rank",
278
+ completionConfig: {
279
+ rankRewards: [100, 60, 30]
280
+ // [1st place, 2nd place, 3rd place]
281
+ },
282
+ target: { anyArcadeGame: true },
283
+ active: true
284
+ },
285
+ {
286
+ id: ACHIEVEMENT_IDS.FIRST_SCORE_ANY_GAME,
287
+ title: "First Score",
288
+ description: "Submit your first score in any arcade game.",
289
+ scope: "game",
290
+ // Scoped per game, no time limit
291
+ rewardCredits: 5,
292
+ limit: 1,
293
+ completionType: "first_score",
294
+ completionConfig: {},
295
+ target: { anyArcadeGame: true },
296
+ active: true
297
+ },
298
+ {
299
+ id: ACHIEVEMENT_IDS.PERSONAL_BEST_ANY_GAME,
300
+ title: "Personal Best",
301
+ description: "Beat your personal best score in any arcade game.",
302
+ scope: "daily",
303
+ rewardCredits: 5,
304
+ limit: 3,
305
+ // Can earn 3 times per day
306
+ completionType: "personal_best",
307
+ completionConfig: {},
308
+ target: { anyArcadeGame: true },
309
+ active: true
310
+ }
311
+ ];
312
+
313
+ // ../constants/src/typescript.ts
314
+ var TSC_PACKAGE = "@typescript/native-preview";
315
+ var USE_NATIVE_TSC = TSC_PACKAGE.includes("native-preview");
316
+
219
317
  // ../constants/src/overworld.ts
220
318
  var ITEM_SLUGS = {
221
319
  /** Primary platform currency */
@@ -278,29 +376,196 @@ function getDevDbPath() {
278
376
  return initialDbPath;
279
377
  }
280
378
 
379
+ // src/lib/core/transpile.ts
380
+ import * as esbuild from "esbuild";
381
+ async function transpileTypeScript(code) {
382
+ const result = await esbuild.transform(code, {
383
+ loader: "ts",
384
+ format: "esm",
385
+ target: "esnext"
386
+ });
387
+ return result.code;
388
+ }
389
+
390
+ // src/lib/db/seed-runtime/parse-d1-error.ts
391
+ function parseD1Error(error) {
392
+ if (!(error instanceof Error)) return void 0;
393
+ const details = {};
394
+ const msg = error.message || "";
395
+ if (msg.includes("SQLITE_CONSTRAINT")) {
396
+ details.code = "CONSTRAINT_VIOLATION";
397
+ const uniqueMatch = msg.match(/UNIQUE constraint failed: (\w+)\.(\w+)/);
398
+ if (uniqueMatch) {
399
+ details.table = uniqueMatch[1];
400
+ details.constraint = uniqueMatch[2];
401
+ details.constraintType = "UNIQUE";
402
+ }
403
+ if (msg.includes("FOREIGN KEY constraint failed")) {
404
+ details.constraintType = "FOREIGN_KEY";
405
+ }
406
+ const notNullMatch = msg.match(/NOT NULL constraint failed: (\w+)\.(\w+)/);
407
+ if (notNullMatch) {
408
+ details.table = notNullMatch[1];
409
+ details.constraint = notNullMatch[2];
410
+ details.constraintType = "NOT_NULL";
411
+ }
412
+ }
413
+ if (msg.includes("SQLITE_ERROR") || msg.includes("syntax error")) {
414
+ details.code = "SQL_ERROR";
415
+ const tableMatch = msg.match(/no such table: (\w+)/);
416
+ if (tableMatch) {
417
+ details.table = tableMatch[1];
418
+ details.errorType = "TABLE_NOT_FOUND";
419
+ }
420
+ const syntaxMatch = msg.match(/near "([^"]+)": syntax error/);
421
+ if (syntaxMatch) {
422
+ details.nearToken = syntaxMatch[1];
423
+ details.errorType = "SYNTAX_ERROR";
424
+ }
425
+ }
426
+ if (msg.includes("SQLITE_BUSY") || msg.includes("database is locked")) {
427
+ details.code = "DATABASE_BUSY";
428
+ }
429
+ return Object.keys(details).length > 0 ? details : void 0;
430
+ }
431
+ async function getParseD1ErrorSource() {
432
+ return transpileTypeScript(parseD1Error.toString());
433
+ }
434
+
435
+ // src/lib/db/seed-runtime/runtime.ts
436
+ function createLogCapture(startTime) {
437
+ const logs = [];
438
+ const originalConsole = {
439
+ // eslint-disable-next-line no-console
440
+ log: console.log.bind(console),
441
+ // eslint-disable-next-line no-console
442
+ warn: console.warn.bind(console),
443
+ // eslint-disable-next-line no-console
444
+ error: console.error.bind(console),
445
+ // eslint-disable-next-line no-console
446
+ info: console.info.bind(console)
447
+ };
448
+ const isSilent = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.LOG_SILENT === "true";
449
+ const captureLog = (level, args) => {
450
+ const message = args.map((arg) => {
451
+ if (typeof arg === "object") {
452
+ try {
453
+ return JSON.stringify(arg);
454
+ } catch {
455
+ return String(arg);
456
+ }
457
+ }
458
+ return String(arg);
459
+ }).join(" ");
460
+ logs.push({
461
+ level,
462
+ message,
463
+ timestamp: Date.now() - startTime
464
+ });
465
+ if (!isSilent) {
466
+ originalConsole[level](...args);
467
+ }
468
+ };
469
+ console.log = (...args) => captureLog("log", args);
470
+ console.warn = (...args) => captureLog("warn", args);
471
+ console.error = (...args) => captureLog("error", args);
472
+ console.info = (...args) => captureLog("info", args);
473
+ return {
474
+ logs,
475
+ restore: () => {
476
+ console.log = originalConsole.log;
477
+ console.warn = originalConsole.warn;
478
+ console.error = originalConsole.error;
479
+ console.info = originalConsole.info;
480
+ }
481
+ };
482
+ }
483
+ function buildSuccessResponse(logs, duration) {
484
+ return {
485
+ success: true,
486
+ logs,
487
+ duration
488
+ };
489
+ }
490
+ function buildErrorResponse(error, logs, duration, details) {
491
+ logs.push({
492
+ level: "error",
493
+ message: error instanceof Error ? error.message : String(error),
494
+ timestamp: duration
495
+ });
496
+ return {
497
+ success: false,
498
+ error: error instanceof Error ? error.message : String(error),
499
+ stack: error instanceof Error ? error.stack : void 0,
500
+ details,
501
+ logs,
502
+ duration
503
+ };
504
+ }
505
+ function createSeedFetchHandler(seedFn, parseD1ErrorFn) {
506
+ return async function fetch(req, env, ctx) {
507
+ const startTime = Date.now();
508
+ const { logs, restore } = createLogCapture(startTime);
509
+ try {
510
+ const c = { env, ctx, req };
511
+ await seedFn(c);
512
+ const duration = Date.now() - startTime;
513
+ restore();
514
+ return Response.json(buildSuccessResponse(logs, duration));
515
+ } catch (error) {
516
+ const duration = Date.now() - startTime;
517
+ const details = parseD1ErrorFn(error);
518
+ restore();
519
+ return Response.json(buildErrorResponse(error, logs, duration, details));
520
+ }
521
+ };
522
+ }
523
+ async function getCreateLogCaptureSource() {
524
+ return transpileTypeScript(createLogCapture.toString());
525
+ }
526
+ async function getBuildSuccessResponseSource() {
527
+ return transpileTypeScript(buildSuccessResponse.toString());
528
+ }
529
+ async function getBuildErrorResponseSource() {
530
+ return transpileTypeScript(buildErrorResponse.toString());
531
+ }
532
+ async function getCreateSeedFetchHandlerSource() {
533
+ return transpileTypeScript(createSeedFetchHandler.toString());
534
+ }
535
+
281
536
  // src/lib/db/bundle-seed.ts
537
+ async function createSeedWorkerEntry(seedFilePath) {
538
+ const [
539
+ parseD1ErrorSource,
540
+ createLogCaptureSource,
541
+ buildSuccessResponseSource,
542
+ buildErrorResponseSource,
543
+ createSeedFetchHandlerSource
544
+ ] = await Promise.all([
545
+ getParseD1ErrorSource(),
546
+ getCreateLogCaptureSource(),
547
+ getBuildSuccessResponseSource(),
548
+ getBuildErrorResponseSource(),
549
+ getCreateSeedFetchHandlerSource()
550
+ ]);
551
+ return `import { seed } from '${seedFilePath}'
552
+
553
+ ${parseD1ErrorSource}
554
+
555
+ ${createLogCaptureSource}
556
+
557
+ ${buildSuccessResponseSource}
558
+
559
+ ${buildErrorResponseSource}
560
+
561
+ ${createSeedFetchHandlerSource}
562
+
563
+ export default { fetch: createSeedFetchHandler(seed, parseD1Error) }
564
+ `;
565
+ }
282
566
  async function bundleSeedWorker(seedFilePath, projectPath) {
283
- const esbuild2 = await import("esbuild");
284
- const entryCode = `
285
- import { seed } from '${seedFilePath}'
286
-
287
- export default {
288
- async fetch(req, env, ctx) {
289
- try {
290
- // Create Hono-like context
291
- const c = { env, ctx, req }
292
- await seed(c)
293
- return Response.json({ success: true })
294
- } catch (error) {
295
- return Response.json({
296
- success: false,
297
- error: error instanceof Error ? error.message : String(error),
298
- stack: error instanceof Error ? error.stack : undefined,
299
- }, 500)
300
- }
301
- }
302
- }
303
- `;
567
+ const esbuild3 = await import("esbuild");
568
+ const entryCode = await createSeedWorkerEntry(seedFilePath);
304
569
  const buildConfig = {
305
570
  stdin: {
306
571
  contents: entryCode,
@@ -318,7 +583,7 @@ async function bundleSeedWorker(seedFilePath, projectPath) {
318
583
  sourcemap: false,
319
584
  logLevel: "error"
320
585
  };
321
- const result = await esbuild2.build(buildConfig);
586
+ const result = await esbuild3.build(buildConfig);
322
587
  if (!result.outputFiles?.[0]) {
323
588
  throw new Error("Seed worker bundling failed: no output");
324
589
  }
@@ -689,17 +954,11 @@ function displayApiError(error, indent) {
689
954
  return;
690
955
  }
691
956
  const statusCode = errorInfo.status;
692
- let displayMessage = errorInfo.statusText;
957
+ let displayMessage = errorInfo.message;
693
958
  displayMessage = removeStatusPrefix(displayMessage, statusCode);
694
959
  const { cleanMessage, json: embeddedJson } = extractEmbeddedJson(displayMessage);
695
960
  displayMessage = cleanMessageSuffix(cleanMessage);
696
- let errorCode;
697
- if (error.details && typeof error.details === "object") {
698
- const details = error.details;
699
- if ("code" in details && typeof details.code === "string") {
700
- errorCode = details.code;
701
- }
702
- }
961
+ const errorCode = error.code;
703
962
  let errorHeader = "API Error";
704
963
  if (errorCode) {
705
964
  errorHeader += ` ${redBright(`[${errorCode}]`)}`;
@@ -958,6 +1217,9 @@ var logger = {
958
1217
  }
959
1218
  };
960
1219
 
1220
+ // src/lib/core/errors.ts
1221
+ import { ApiError as ApiError2 } from "@playcademy/sdk/internal";
1222
+
961
1223
  // src/lib/secrets/env.ts
962
1224
  init_file_loader();
963
1225
 
@@ -974,12 +1236,12 @@ import { mkdtempSync, rmSync } from "fs";
974
1236
  import { tmpdir } from "os";
975
1237
  import { join as join8 } from "path";
976
1238
  import { pathToFileURL } from "url";
977
- import * as esbuild from "esbuild";
1239
+ import * as esbuild2 from "esbuild";
978
1240
  async function importTypescriptFile(filePath, bundleOptions) {
979
1241
  const tempDir = mkdtempSync(join8(tmpdir(), "playcademy-import-"));
980
1242
  const outFile = join8(tempDir, "bundle.mjs");
981
1243
  try {
982
- await esbuild.build({
1244
+ await esbuild2.build({
983
1245
  entryPoints: [filePath],
984
1246
  outfile: outFile,
985
1247
  bundle: true,
@@ -1024,6 +1286,96 @@ async function resetDatabase(workspace, mf, options = { debug: false }) {
1024
1286
  );
1025
1287
  }
1026
1288
 
1289
+ // src/lib/db/seed.ts
1290
+ import { dim as dim4 } from "colorette";
1291
+
1292
+ // src/lib/db/seed-output.ts
1293
+ import { green as green2, red as red2, yellow as yellow2 } from "colorette";
1294
+ function getLogPrefix(level) {
1295
+ switch (level) {
1296
+ case "warn":
1297
+ return yellow2("\u26A0") + " ";
1298
+ default:
1299
+ return "";
1300
+ }
1301
+ }
1302
+ function displaySeedLogs(logs, status = "success") {
1303
+ const barColor = status === "error" ? red2 : green2;
1304
+ const bar = barColor("\u2502");
1305
+ let trimmedLogs = logs;
1306
+ while (trimmedLogs.length > 0 && !trimmedLogs[trimmedLogs.length - 1]?.message.trim()) {
1307
+ trimmedLogs = trimmedLogs.slice(0, -1);
1308
+ }
1309
+ for (const log of trimmedLogs) {
1310
+ const prefix = getLogPrefix(log.level);
1311
+ logger.raw(` ${bar} ${prefix}${log.message}`);
1312
+ }
1313
+ }
1314
+ function handleSeedError(error) {
1315
+ let seedLogs;
1316
+ if (error && typeof error === "object" && "details" in error) {
1317
+ const details = error.details;
1318
+ if (details && typeof details === "object" && "logs" in details) {
1319
+ const logs = details.logs;
1320
+ if (Array.isArray(logs)) {
1321
+ seedLogs = logs;
1322
+ }
1323
+ }
1324
+ }
1325
+ if (seedLogs && seedLogs.length > 0) {
1326
+ displaySeedLogs(seedLogs, "error");
1327
+ return true;
1328
+ }
1329
+ if (!(error instanceof Error)) {
1330
+ return false;
1331
+ }
1332
+ const message = error.message;
1333
+ const errorName = error.name;
1334
+ if (errorName === "TimeoutError" || message.includes("timed out")) {
1335
+ logger.admonition("warning", "Seed Timed Out", [
1336
+ "Your seed operation took too long to complete.",
1337
+ "",
1338
+ "Possible causes:",
1339
+ "\u2022 Inserting too many records at once",
1340
+ "\u2022 Complex queries or missing indexes",
1341
+ "\u2022 Database under heavy load",
1342
+ "",
1343
+ "Try breaking your seed into smaller batches."
1344
+ ]);
1345
+ return true;
1346
+ }
1347
+ if (errorName === "ServiceUnavailableError" || message.includes("worker failed") || message.includes("Cloudflare")) {
1348
+ logger.admonition("warning", "Service Unavailable", [
1349
+ "Unable to deploy the seed worker to Cloudflare.",
1350
+ "",
1351
+ "This could be a temporary issue. Please:",
1352
+ "1. Wait a moment and try again",
1353
+ "2. Check status.cloudflare.com for outages",
1354
+ "3. Verify your deployment is working with `playcademy deploy`"
1355
+ ]);
1356
+ return true;
1357
+ }
1358
+ if (message.includes("ECONNREFUSED") || message.includes("fetch failed") || message.includes("network")) {
1359
+ logger.admonition("warning", "Network Error", [
1360
+ "Unable to connect to the Playcademy API.",
1361
+ "",
1362
+ "Please check your internet connection and try again."
1363
+ ]);
1364
+ return true;
1365
+ }
1366
+ if (errorName === "UnauthorizedError" || errorName === "AccessDeniedError" || message.includes("Unauthorized") || message.includes("Access denied")) {
1367
+ logger.admonition("warning", "Authentication Error", [
1368
+ "You do not have permission to seed this database.",
1369
+ "",
1370
+ "Make sure you:",
1371
+ "\u2022 Are logged in: `playcademy login`",
1372
+ "\u2022 Own this project + have developer access"
1373
+ ]);
1374
+ return true;
1375
+ }
1376
+ return false;
1377
+ }
1378
+
1027
1379
  // src/lib/db/seed.ts
1028
1380
  async function importSeedModule(seedPath) {
1029
1381
  return await importTypescriptFile(seedPath);
@@ -1035,6 +1387,44 @@ async function getBucket(mf) {
1035
1387
  return null;
1036
1388
  }
1037
1389
  }
1390
+ function captureConsole() {
1391
+ const logs = [];
1392
+ const startTime = Date.now();
1393
+ const original = {
1394
+ log: console.log.bind(console),
1395
+ warn: console.warn.bind(console),
1396
+ error: console.error.bind(console),
1397
+ info: console.info.bind(console)
1398
+ };
1399
+ const capture = (level) => {
1400
+ return (...args) => {
1401
+ const message = args.map((arg) => {
1402
+ if (typeof arg === "object") {
1403
+ try {
1404
+ return JSON.stringify(arg);
1405
+ } catch {
1406
+ return String(arg);
1407
+ }
1408
+ }
1409
+ return String(arg);
1410
+ }).filter((arg) => arg !== void 0 && arg !== null && arg !== "").join(" ");
1411
+ logs.push({ level, message, timestamp: Date.now() - startTime });
1412
+ };
1413
+ };
1414
+ console.log = capture("log");
1415
+ console.warn = capture("warn");
1416
+ console.error = capture("error");
1417
+ console.info = capture("info");
1418
+ return {
1419
+ logs,
1420
+ restore: () => {
1421
+ console.log = original.log;
1422
+ console.warn = original.warn;
1423
+ console.error = original.error;
1424
+ console.info = original.info;
1425
+ }
1426
+ };
1427
+ }
1038
1428
  async function executeSeedFile(seedFilePath, mf, envSecrets = {}) {
1039
1429
  const d1 = await mf.getD1Database(CLOUDFLARE_BINDINGS.DB);
1040
1430
  const seedModule = await importSeedModule(seedFilePath);
@@ -1050,23 +1440,49 @@ async function executeSeedFile(seedFilePath, mf, envSecrets = {}) {
1050
1440
  }
1051
1441
  const bucket = await getBucket(mf);
1052
1442
  const hasSecrets = Object.keys(envSecrets).length > 0;
1053
- await runStep(
1054
- "Seeding database...",
1055
- async () => {
1056
- const env = { DB: d1 };
1057
- if (bucket) env.BUCKET = bucket;
1058
- if (hasSecrets) env.secrets = envSecrets;
1059
- return seedModule.seed?.({ env });
1060
- },
1061
- "Database seeded successfully!"
1062
- );
1443
+ const env = { DB: d1 };
1444
+ if (bucket) env.BUCKET = bucket;
1445
+ if (hasSecrets) env.secrets = envSecrets;
1446
+ const { logs, restore } = captureConsole();
1447
+ const startTime = Date.now();
1448
+ let seedError = null;
1449
+ try {
1450
+ await seedModule.seed?.({ env });
1451
+ } catch (error) {
1452
+ seedError = error;
1453
+ } finally {
1454
+ restore();
1455
+ }
1456
+ const duration = Date.now() - startTime;
1457
+ const hasLogs = logs.length > 0;
1458
+ if (hasLogs) {
1459
+ logger.newLine();
1460
+ displaySeedLogs(logs, seedError ? "error" : "success");
1461
+ }
1462
+ if (seedError) {
1463
+ logger.newLine();
1464
+ const handled = handleSeedError(seedError);
1465
+ if (!handled && !hasLogs) {
1466
+ const errorMessage = seedError instanceof Error ? seedError.message : String(seedError);
1467
+ logger.error(`Seeding failed: ${errorMessage} ${dim4(`[${duration}ms]`)}`);
1468
+ } else {
1469
+ logger.error(`Seeding failed ${dim4(`[${duration}ms]`)}`);
1470
+ }
1471
+ logger.newLine();
1472
+ process.exit(1);
1473
+ }
1474
+ logger.newLine();
1475
+ logger.success(`Database seeded ${dim4(`[${duration}ms]`)}`);
1063
1476
  logger.newLine();
1064
1477
  }
1065
1478
  export {
1066
1479
  bundleSeedWorker,
1480
+ createSeedWorkerEntry,
1481
+ displaySeedLogs,
1067
1482
  executeSeedFile,
1068
1483
  getBucket,
1069
1484
  getDevDbPath as getPath,
1485
+ handleSeedError,
1070
1486
  importSeedModule,
1071
1487
  resetDatabase
1072
1488
  };