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/cli.js +215 -10
- package/dist/constants/src/achievements.ts +107 -0
- package/dist/constants/src/character.ts +16 -0
- package/dist/constants/src/index.ts +3 -0
- package/dist/constants/src/timeback.ts +9 -0
- package/dist/constants/src/typescript.ts +19 -0
- package/dist/constants.js +99 -1
- package/dist/db.d.ts +68 -20
- package/dist/db.js +459 -43
- package/dist/edge-play/src/routes/integrations/timeback/end-activity.ts +1 -1
- package/dist/edge-play/src/types.ts +1 -1
- package/dist/index.d.ts +248 -16
- package/dist/index.js +957 -128
- package/dist/utils.d.ts +198 -8
- package/dist/utils.js +148 -34
- package/dist/version.js +2 -1
- package/package.json +3 -2
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
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
|
|
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
|
};
|