playcademy 0.18.9 → 0.18.10-beta.2

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 CHANGED
@@ -1051,6 +1051,7 @@ import { join as join3 } from "node:path";
1051
1051
  import { join as join2 } from "node:path";
1052
1052
  var SERVER_ROOT_DIRECTORY = "server";
1053
1053
  var SERVER_LIB_DIRECTORY = join2(SERVER_ROOT_DIRECTORY, "lib");
1054
+ var HOOKS_FILE = "hooks";
1054
1055
 
1055
1056
  // src/constants/api.ts
1056
1057
  var DEFAULT_API_ROUTES_DIRECTORY = join3(SERVER_ROOT_DIRECTORY, "api");
@@ -1064,7 +1065,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
1064
1065
  // ../better-auth/package.json
1065
1066
  var package_default = {
1066
1067
  name: "@playcademy/better-auth",
1067
- version: "0.0.4",
1068
+ version: "0.0.5-beta.2",
1068
1069
  type: "module",
1069
1070
  exports: {
1070
1071
  "./server": {
@@ -3015,7 +3016,7 @@ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as re
3015
3016
  import { join as join13 } from "node:path";
3016
3017
 
3017
3018
  // src/version.ts
3018
- var cliVersion = false ? "0.0.0-dev" : "0.18.9";
3019
+ var cliVersion = false ? "0.0.0-dev" : "0.18.10-beta.2";
3019
3020
 
3020
3021
  // src/lib/init/database.ts
3021
3022
  var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
@@ -3415,9 +3416,10 @@ import { existsSync as existsSync15, readFileSync as readFileSync9, writeFileSyn
3415
3416
  import { dirname as dirname5, join as join17, resolve as resolve7 } from "node:path";
3416
3417
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3417
3418
 
3418
- // src/lib/deploy/backend.ts
3419
+ // src/lib/bundle/backend.ts
3419
3420
  import { existsSync as existsSync13 } from "node:fs";
3420
3421
  import { join as join15 } from "node:path";
3422
+ var HOOKS_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
3421
3423
  function getCustomRoutesDirectory(projectPath, config) {
3422
3424
  const customRoutes = config?.integrations?.customRoutes;
3423
3425
  const customRoutesDir = typeof customRoutes === "object" && customRoutes.directory || DEFAULT_API_ROUTES_DIRECTORY;
@@ -3427,8 +3429,13 @@ function hasLocalCustomRoutes(projectPath, config) {
3427
3429
  const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
3428
3430
  return existsSync13(customRoutesDir);
3429
3431
  }
3432
+ function hasHooksModule(workspace) {
3433
+ return HOOKS_EXTENSIONS.some(
3434
+ (ext) => existsSync13(join15(workspace, SERVER_ROOT_DIRECTORY, `${HOOKS_FILE}${ext}`))
3435
+ );
3436
+ }
3430
3437
 
3431
- // src/lib/deploy/hash.ts
3438
+ // src/lib/bundle/hash.ts
3432
3439
  init_file_loader();
3433
3440
 
3434
3441
  // src/lib/secrets/diff.ts
@@ -3565,6 +3572,7 @@ function detectBackendFeatures(workspace, config) {
3565
3572
  hasBucket: hasBucketSetup(config),
3566
3573
  hasRoutes: hasLocalCustomRoutes(workspace, config),
3567
3574
  hasQueues: hasQueueSetup(config),
3575
+ hasHooks: hasHooksModule(workspace),
3568
3576
  hasSecrets: hasEnvFile(workspace)
3569
3577
  };
3570
3578
  }
@@ -70,9 +70,16 @@ declare const BUCKET_ALWAYS_SKIP: readonly [".git", ".DS_Store", ".gitignore", "
70
70
  */
71
71
  /**
72
72
  * Cloudflare Workers compatibility date
73
- * Update when adopting new Cloudflare features or breaking changes
73
+ * Must stay on or after 2024-09-23 so nodejs_compat uses the modern v2 path.
74
74
  */
75
- declare const CLOUDFLARE_COMPATIBILITY_DATE = "2024-01-01";
75
+ declare const CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
76
+ /**
77
+ * Compatibility flags enabled for Playcademy backend workers.
78
+ *
79
+ * Keep local dev and deployed backends aligned so libraries like Better Auth
80
+ * and Sentry see the same Worker runtime capabilities.
81
+ */
82
+ declare const BACKEND_COMPATIBILITY_FLAGS: readonly ["nodejs_compat"];
76
83
  /**
77
84
  * Cloudflare binding names for local development (Miniflare)
78
85
  */
@@ -320,6 +327,10 @@ declare const SERVER_ROOT_DIRECTORY = "server";
320
327
  * Server library/utilities directory (fixed location)
321
328
  */
322
329
  declare const SERVER_LIB_DIRECTORY: string;
330
+ /**
331
+ * Hooks file basename (without extension) inside server/
332
+ */
333
+ declare const HOOKS_FILE = "hooks";
323
334
  /**
324
335
  * Public assets directory (fixed location)
325
336
  */
@@ -340,4 +351,4 @@ declare const CONFIG_FILE_NAMES: string[];
340
351
  */
341
352
  declare const DOCS_URL = "https://docs.dev.playcademy.net/platform";
342
353
 
343
- export { AUTH_API_SUBDIRECTORY, AUTH_CONFIG_FILE, AUTH_PROVIDER_NAMES, BETTER_AUTH_VERSION, BUCKET_ALWAYS_SKIP, CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_BINDINGS, CLOUDFLARE_COMPATIBILITY_DATE, CONFIG_FILE_NAMES, DB_FILES, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_TEMPLATE_REF, DOCS_URL, DRIZZLE_CONFIG_FILES, ENV_EXAMPLE_FILE, ENV_FILES, GODOT_ADDON_URL, GODOT_BUILD_DIRECTORIES, GODOT_BUILD_OUTPUTS, GODOT_EXECUTABLE_PATTERNS, GODOT_EXPORT_PRESETS_FILE, GODOT_PROJECT_FILE, GODOT_WEB_PLATFORM, MINIFLARE_D1_DIRECTORY, OAUTH_CALLBACK_URL_PATTERN, PLACEHOLDER_APP_URL, PLAYCADEMY_AUTH_VERSION, PUBLIC_DIRECTORY, QUEUE_NAME_PREFIX, SAMPLE_API_SUBDIRECTORY, SAMPLE_BUCKET_FILENAME, SAMPLE_CUSTOM_FILENAME, SAMPLE_DATABASE_FILENAME, SAMPLE_KV_FILENAME, SCHEMA_SUBDIRECTORY, SELF_WORKER_NAME_BINDING, SERVER_LIB_DIRECTORY, SERVER_ROOT_DIRECTORY, SSO_AUTH_TIMEOUT_MS, TEMPLATE_RENAME_FILES, TEMPLATE_REPOS, TSCONFIG_APP_JSON, TSCONFIG_FILES, TSCONFIG_JSON, TSCONFIG_REQUIRED_INCLUDES, WORKSPACE_NAME, cacheVersionDir, nativeBinaryPath };
354
+ export { AUTH_API_SUBDIRECTORY, AUTH_CONFIG_FILE, AUTH_PROVIDER_NAMES, BACKEND_COMPATIBILITY_FLAGS, BETTER_AUTH_VERSION, BUCKET_ALWAYS_SKIP, CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_BINDINGS, CLOUDFLARE_COMPATIBILITY_DATE, CONFIG_FILE_NAMES, DB_FILES, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_TEMPLATE_REF, DOCS_URL, DRIZZLE_CONFIG_FILES, ENV_EXAMPLE_FILE, ENV_FILES, GODOT_ADDON_URL, GODOT_BUILD_DIRECTORIES, GODOT_BUILD_OUTPUTS, GODOT_EXECUTABLE_PATTERNS, GODOT_EXPORT_PRESETS_FILE, GODOT_PROJECT_FILE, GODOT_WEB_PLATFORM, HOOKS_FILE, MINIFLARE_D1_DIRECTORY, OAUTH_CALLBACK_URL_PATTERN, PLACEHOLDER_APP_URL, PLAYCADEMY_AUTH_VERSION, PUBLIC_DIRECTORY, QUEUE_NAME_PREFIX, SAMPLE_API_SUBDIRECTORY, SAMPLE_BUCKET_FILENAME, SAMPLE_CUSTOM_FILENAME, SAMPLE_DATABASE_FILENAME, SAMPLE_KV_FILENAME, SCHEMA_SUBDIRECTORY, SELF_WORKER_NAME_BINDING, SERVER_LIB_DIRECTORY, SERVER_ROOT_DIRECTORY, SSO_AUTH_TIMEOUT_MS, TEMPLATE_RENAME_FILES, TEMPLATE_REPOS, TSCONFIG_APP_JSON, TSCONFIG_FILES, TSCONFIG_JSON, TSCONFIG_REQUIRED_INCLUDES, WORKSPACE_NAME, cacheVersionDir, nativeBinaryPath };
package/dist/constants.js CHANGED
@@ -5,6 +5,7 @@ import { join as join2 } from "node:path";
5
5
  import { join } from "node:path";
6
6
  var SERVER_ROOT_DIRECTORY = "server";
7
7
  var SERVER_LIB_DIRECTORY = join(SERVER_ROOT_DIRECTORY, "lib");
8
+ var HOOKS_FILE = "hooks";
8
9
  var PUBLIC_DIRECTORY = "public";
9
10
 
10
11
  // src/constants/api.ts
@@ -19,7 +20,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
19
20
  // ../better-auth/package.json
20
21
  var package_default = {
21
22
  name: "@playcademy/better-auth",
22
- version: "0.0.4",
23
+ version: "0.0.5-beta.2",
23
24
  type: "module",
24
25
  exports: {
25
26
  "./server": {
@@ -100,7 +101,8 @@ var TSCONFIG_REQUIRED_INCLUDES = [
100
101
  var BUCKET_ALWAYS_SKIP = [".git", ".DS_Store", ".gitignore", ...ENV_FILES];
101
102
 
102
103
  // src/constants/cloudflare.ts
103
- var CLOUDFLARE_COMPATIBILITY_DATE = "2024-01-01";
104
+ var CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
105
+ var BACKEND_COMPATIBILITY_FLAGS = ["nodejs_compat"];
104
106
  var CLOUDFLARE_BINDINGS = {
105
107
  /** R2 bucket binding name */
106
108
  BUCKET: "BUCKET",
@@ -388,6 +390,7 @@ export {
388
390
  AUTH_API_SUBDIRECTORY,
389
391
  AUTH_CONFIG_FILE,
390
392
  AUTH_PROVIDER_NAMES,
393
+ BACKEND_COMPATIBILITY_FLAGS,
391
394
  BETTER_AUTH_VERSION,
392
395
  BUCKET_ALWAYS_SKIP,
393
396
  CALLBACK_PATH,
@@ -416,6 +419,7 @@ export {
416
419
  GODOT_EXPORT_PRESETS_FILE,
417
420
  GODOT_PROJECT_FILE,
418
421
  GODOT_WEB_PLATFORM,
422
+ HOOKS_FILE,
419
423
  MINIFLARE_D1_DIRECTORY,
420
424
  OAUTH_CALLBACK_URL_PATTERN,
421
425
  PLACEHOLDER_APP_URL,
package/dist/db.d.ts CHANGED
@@ -54,6 +54,11 @@ interface SeedWorkerBundle {
54
54
  *
55
55
  * Bundles user's seed file into standalone Cloudflare Worker
56
56
  * with enhanced logging capture and structured error reporting.
57
+ *
58
+ * Runtime helper source is imported via `?raw`, then transpiled from
59
+ * TypeScript to JavaScript before injection into the worker template.
60
+ * This avoids Function.prototype.toString(), which breaks when the CLI
61
+ * binary is compiled with minification.
57
62
  */
58
63
 
59
64
  /**
package/dist/db.js CHANGED
@@ -36,7 +36,7 @@ var DEFAULT_API_ROUTES_DIRECTORY = join2(SERVER_ROOT_DIRECTORY, "api");
36
36
  // ../better-auth/package.json
37
37
  var package_default = {
38
38
  name: "@playcademy/better-auth",
39
- version: "0.0.4",
39
+ version: "0.0.5-beta.2",
40
40
  type: "module",
41
41
  exports: {
42
42
  "./server": {
@@ -331,197 +331,99 @@ async function transpileTypeScript(code) {
331
331
  return result.code;
332
332
  }
333
333
 
334
- // src/lib/db/seed-runtime/parse-d1-error.ts
335
- function parseD1Error(error) {
336
- if (!(error instanceof Error)) {
337
- return void 0;
338
- }
339
- const details = {};
340
- const msg = error.message || "";
341
- if (msg.includes("SQLITE_CONSTRAINT")) {
342
- details.code = "CONSTRAINT_VIOLATION";
343
- const uniqueMatch = msg.match(/UNIQUE constraint failed: (\w+)\.(\w+)/);
344
- if (uniqueMatch) {
345
- details.table = uniqueMatch[1];
346
- details.constraint = uniqueMatch[2];
347
- details.constraintType = "UNIQUE";
348
- }
349
- if (msg.includes("FOREIGN KEY constraint failed")) {
350
- details.constraintType = "FOREIGN_KEY";
351
- }
352
- const notNullMatch = msg.match(/NOT NULL constraint failed: (\w+)\.(\w+)/);
353
- if (notNullMatch) {
354
- details.table = notNullMatch[1];
355
- details.constraint = notNullMatch[2];
356
- details.constraintType = "NOT_NULL";
357
- }
358
- }
359
- if (msg.includes("SQLITE_ERROR") || msg.includes("syntax error")) {
360
- details.code = "SQL_ERROR";
361
- const tableMatch = msg.match(/no such table: (\w+)/);
362
- if (tableMatch) {
363
- details.table = tableMatch[1];
364
- details.errorType = "TABLE_NOT_FOUND";
334
+ // playcademy-raw:/home/runner/work/playcademy/playcademy/packages/cli/src/lib/db/seed-runtime/parse-d1-error.ts
335
+ var parse_d1_error_default = `/**
336
+ * Parse D1/SQLite errors into structured details.
337
+ *
338
+ * This function runs inside the seed worker at runtime.
339
+ * The source file is embedded as text and transpiled to JS before injection
340
+ * into the worker template \u2014 see bundle-seed.ts.
341
+ *
342
+ * @param error - The error to parse
343
+ * @returns Structured error details, or undefined if not a D1 error
344
+ */
345
+ // oxlint-disable-next-line @typescript-eslint/no-explicit-any
346
+ export function parseD1Error(error: unknown): Record<string, any> | undefined {
347
+ if (!(error instanceof Error)) {
348
+ return undefined
365
349
  }
366
- const syntaxMatch = msg.match(/near "([^"]+)": syntax error/);
367
- if (syntaxMatch) {
368
- details.nearToken = syntaxMatch[1];
369
- details.errorType = "SYNTAX_ERROR";
370
- }
371
- }
372
- if (msg.includes("SQLITE_BUSY") || msg.includes("database is locked")) {
373
- details.code = "DATABASE_BUSY";
374
- }
375
- return Object.keys(details).length > 0 ? details : void 0;
376
- }
377
- async function getParseD1ErrorSource() {
378
- return transpileTypeScript(parseD1Error.toString());
379
- }
380
350
 
381
- // src/lib/db/seed-runtime/runtime.ts
382
- function createLogCapture(startTime) {
383
- const logs = [];
384
- const originalConsole = {
385
- log: console.log.bind(console),
386
- warn: console.warn.bind(console),
387
- error: console.error.bind(console),
388
- info: console.info.bind(console)
389
- };
390
- const isSilent = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.LOG_SILENT === "true";
391
- function captureLog(level, args) {
392
- const message = args.map((arg) => {
393
- if (typeof arg === "object") {
394
- try {
395
- return JSON.stringify(arg);
396
- } catch {
397
- return String(arg);
351
+ // oxlint-disable-next-line @typescript-eslint/no-explicit-any
352
+ const details: Record<string, any> = {}
353
+ const msg = error.message || ''
354
+
355
+ // SQLITE_CONSTRAINT errors (unique, foreign key, not null, etc.)
356
+ if (msg.includes('SQLITE_CONSTRAINT')) {
357
+ details.code = 'CONSTRAINT_VIOLATION'
358
+
359
+ // UNIQUE constraint failed: table.column
360
+ const uniqueMatch = msg.match(/UNIQUE constraint failed: (\\w+)\\.(\\w+)/)
361
+
362
+ if (uniqueMatch) {
363
+ details.table = uniqueMatch[1]
364
+ details.constraint = uniqueMatch[2]
365
+ details.constraintType = 'UNIQUE'
366
+ }
367
+
368
+ // FOREIGN KEY constraint failed
369
+ if (msg.includes('FOREIGN KEY constraint failed')) {
370
+ details.constraintType = 'FOREIGN_KEY'
371
+ }
372
+
373
+ // NOT NULL constraint failed: table.column
374
+ const notNullMatch = msg.match(/NOT NULL constraint failed: (\\w+)\\.(\\w+)/)
375
+
376
+ if (notNullMatch) {
377
+ details.table = notNullMatch[1]
378
+ details.constraint = notNullMatch[2]
379
+ details.constraintType = 'NOT_NULL'
398
380
  }
399
- }
400
- return String(arg);
401
- }).join(" ");
402
- logs.push({
403
- level,
404
- message,
405
- timestamp: Date.now() - startTime
406
- });
407
- if (!isSilent) {
408
- originalConsole[level](...args);
409
- }
410
- }
411
- console.log = (...args) => captureLog("log", args);
412
- console.warn = (...args) => captureLog("warn", args);
413
- console.error = (...args) => captureLog("error", args);
414
- console.info = (...args) => captureLog("info", args);
415
- return {
416
- logs,
417
- restore: () => {
418
- console.log = originalConsole.log;
419
- console.warn = originalConsole.warn;
420
- console.error = originalConsole.error;
421
- console.info = originalConsole.info;
422
381
  }
423
- };
424
- }
425
- function buildSuccessResponse(logs, duration) {
426
- return {
427
- success: true,
428
- logs,
429
- duration
430
- };
431
- }
432
- function buildErrorResponse(error, logs, duration, details) {
433
- logs.push({
434
- level: "error",
435
- message: error instanceof Error ? error.message : String(error),
436
- timestamp: duration
437
- });
438
- return {
439
- success: false,
440
- error: error instanceof Error ? error.message : String(error),
441
- stack: error instanceof Error ? error.stack : void 0,
442
- details,
443
- logs,
444
- duration
445
- };
446
- }
447
- function reconstructSecrets(env) {
448
- const secrets = {};
449
- const prefix = "secrets_";
450
- for (const key of Object.keys(env)) {
451
- if (key.startsWith(prefix)) {
452
- const secretKey = key.slice(prefix.length);
453
- secrets[secretKey] = env[key];
382
+
383
+ // SQLITE_ERROR (syntax errors, unknown tables, etc.)
384
+ if (msg.includes('SQLITE_ERROR') || msg.includes('syntax error')) {
385
+ details.code = 'SQL_ERROR'
386
+
387
+ // no such table: tablename
388
+ const tableMatch = msg.match(/no such table: (\\w+)/)
389
+
390
+ if (tableMatch) {
391
+ details.table = tableMatch[1]
392
+ details.errorType = 'TABLE_NOT_FOUND'
393
+ }
394
+
395
+ // near "X": syntax error
396
+ const syntaxMatch = msg.match(/near "([^"]+)": syntax error/)
397
+
398
+ if (syntaxMatch) {
399
+ details.nearToken = syntaxMatch[1]
400
+ details.errorType = 'SYNTAX_ERROR'
401
+ }
454
402
  }
455
- }
456
- return secrets;
457
- }
458
- function createSeedFetchHandler(seedFn, parseD1ErrorFn) {
459
- return async function fetch(req, env, ctx) {
460
- const startTime = Date.now();
461
- const { logs, restore } = createLogCapture(startTime);
462
- try {
463
- const envRecord = env;
464
- const secrets = reconstructSecrets(envRecord);
465
- const enrichedEnv = { ...envRecord, secrets };
466
- const c = { env: enrichedEnv, ctx, req };
467
- await seedFn(c);
468
- const duration = Date.now() - startTime;
469
- restore();
470
- return Response.json(buildSuccessResponse(logs, duration));
471
- } catch (error) {
472
- const duration = Date.now() - startTime;
473
- const details = parseD1ErrorFn(error);
474
- restore();
475
- return Response.json(buildErrorResponse(error, logs, duration, details));
403
+
404
+ // SQLITE_BUSY (database locked)
405
+ if (msg.includes('SQLITE_BUSY') || msg.includes('database is locked')) {
406
+ details.code = 'DATABASE_BUSY'
476
407
  }
477
- };
478
- }
479
- async function getCreateLogCaptureSource() {
480
- return transpileTypeScript(createLogCapture.toString());
481
- }
482
- async function getBuildSuccessResponseSource() {
483
- return transpileTypeScript(buildSuccessResponse.toString());
484
- }
485
- async function getBuildErrorResponseSource() {
486
- return transpileTypeScript(buildErrorResponse.toString());
487
- }
488
- async function getReconstructSecretsSource() {
489
- return transpileTypeScript(reconstructSecrets.toString());
490
- }
491
- async function getCreateSeedFetchHandlerSource() {
492
- return transpileTypeScript(createSeedFetchHandler.toString());
408
+
409
+ return Object.keys(details).length > 0 ? details : undefined
493
410
  }
411
+ `;
412
+
413
+ // playcademy-raw:/home/runner/work/playcademy/playcademy/packages/cli/src/lib/db/seed-runtime/runtime.ts
414
+ var runtime_default = "/**\n * Seed Worker Runtime Helpers\n *\n * These functions run inside the seed worker at runtime.\n * The source file is embedded as text and transpiled to JS before injection\n * into the worker template \u2014 see bundle-seed.ts.\n */\n\nimport type { SeedErrorDetails, SeedLogEntry } from '@playcademy/types'\n\n/**\n * Log capture state returned by createLogCapture\n */\nexport interface LogCaptureState {\n logs: SeedLogEntry[]\n restore: () => void\n}\n\n/**\n * Create a log capture system that intercepts console methods.\n *\n * @param startTime - The start time for calculating timestamps\n * @returns Object with logs array and restore function\n */\nexport function createLogCapture(startTime: number): LogCaptureState {\n const logs: SeedLogEntry[] = []\n\n const originalConsole = {\n log: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n info: console.info.bind(console),\n }\n\n const isSilent =\n typeof process !== 'undefined' &&\n typeof process.env !== 'undefined' &&\n process.env.LOG_SILENT === 'true'\n\n function captureLog(level: SeedLogEntry['level'], args: unknown[]): void {\n const message = args\n .map(arg => {\n if (typeof arg === 'object') {\n try {\n return JSON.stringify(arg)\n } catch {\n return String(arg)\n }\n }\n\n return String(arg)\n })\n .join(' ')\n\n logs.push({\n level,\n message,\n timestamp: Date.now() - startTime,\n })\n\n // Also call original for Cloudflare logs (unless silenced for tests)\n if (!isSilent) {\n originalConsole[level](...args)\n }\n }\n\n // Override console methods\n console.log = (...args: unknown[]) => captureLog('log', args)\n console.warn = (...args: unknown[]) => captureLog('warn', args)\n console.error = (...args: unknown[]) => captureLog('error', args)\n console.info = (...args: unknown[]) => captureLog('info', args)\n\n return {\n logs,\n restore: () => {\n console.log = originalConsole.log\n console.warn = originalConsole.warn\n console.error = originalConsole.error\n console.info = originalConsole.info\n },\n }\n}\n\n/**\n * Build a success response for the seed worker\n */\nexport function buildSuccessResponse(logs: SeedLogEntry[], duration: number) {\n return {\n success: true as const,\n logs,\n duration,\n }\n}\n\n/**\n * Build an error response for the seed worker\n */\nexport function buildErrorResponse(\n error: unknown,\n logs: SeedLogEntry[],\n duration: number,\n details?: SeedErrorDetails,\n) {\n // Add error to logs\n logs.push({\n level: 'error',\n message: error instanceof Error ? error.message : String(error),\n timestamp: duration,\n })\n\n return {\n success: false as const,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n details,\n logs,\n duration,\n }\n}\n\n/** Seed function type - matches what users export from their seed file */\ntype SeedFunction = (c: { env: unknown; ctx: unknown; req: unknown }) => Promise<void>\n\n/**\n * Reconstruct secrets from flat env bindings (secrets_KEY) into a secrets object.\n *\n * Cloudflare bindings are flat (secrets_KEY_NAME), but we expose them\n * as c.env.secrets.KEY_NAME for better developer ergonomics.\n *\n * NOTE: This function mirrors @playcademy/edge-play/entry/setup.ts:reconstructSecrets\n * It must remain self-contained here because it's transpiled and injected into\n * the seed worker (not imported at runtime).\n *\n * @see SECRETS_PREFIX in @playcademy/constants/workers\n * @see prefixSecrets in @playcademy/edge-play/entry/setup\n *\n * @param env - The raw Cloudflare Worker environment bindings\n * @returns Object with secrets keyed by their original names (without prefix)\n */\nexport function reconstructSecrets(env: Record<string, unknown>): Record<string, string> {\n const secrets: Record<string, string> = {}\n const prefix = 'secrets_' // MUST match SECRETS_PREFIX from @playcademy/constants\n\n for (const key of Object.keys(env)) {\n if (key.startsWith(prefix)) {\n const secretKey = key.slice(prefix.length)\n\n secrets[secretKey] = env[key] as string\n }\n }\n\n return secrets\n}\n\n/**\n * Create the fetch handler for the seed worker.\n *\n * This is a factory that takes the user's seed function and returns\n * a Cloudflare Worker fetch handler that:\n * 1. Sets up console capture\n * 2. Reconstructs secrets from env bindings\n * 3. Calls the seed function\n * 4. Returns success/error JSON response with logs\n *\n * @param seedFn - The user's seed function\n * @param parseD1ErrorFn - The D1 error parser function\n */\nexport function createSeedFetchHandler(\n seedFn: SeedFunction,\n parseD1ErrorFn: (error: unknown) => Record<string, unknown> | undefined,\n) {\n return async function fetch(req: Request, env: unknown, ctx: unknown): Promise<Response> {\n const startTime = Date.now()\n const { logs, restore } = createLogCapture(startTime)\n\n try {\n const envRecord = env as Record<string, unknown>\n const secrets = reconstructSecrets(envRecord)\n\n const enrichedEnv = { ...envRecord, secrets }\n const c = { env: enrichedEnv, ctx, req }\n\n await seedFn(c)\n\n const duration = Date.now() - startTime\n\n restore()\n\n return Response.json(buildSuccessResponse(logs, duration))\n } catch (error) {\n const duration = Date.now() - startTime\n const details = parseD1ErrorFn(error)\n\n restore()\n\n // Return 200 with error body - the request was processed successfully,\n // the seed just failed. This prevents the seed service from retrying\n // actual execution errors thinking the worker isn't ready.\n return Response.json(buildErrorResponse(error, logs, duration, details))\n }\n }\n}\n";
494
415
 
495
416
  // src/lib/db/bundle-seed.ts
496
417
  async function createSeedWorkerEntry(seedFilePath) {
497
- const [
498
- parseD1ErrorSource,
499
- createLogCaptureSource,
500
- buildSuccessResponseSource,
501
- buildErrorResponseSource,
502
- reconstructSecretsSource,
503
- createSeedFetchHandlerSource
504
- ] = await Promise.all([
505
- getParseD1ErrorSource(),
506
- getCreateLogCaptureSource(),
507
- getBuildSuccessResponseSource(),
508
- getBuildErrorResponseSource(),
509
- getReconstructSecretsSource(),
510
- getCreateSeedFetchHandlerSource()
418
+ const [parseD1ErrorJS, runtimeJS] = await Promise.all([
419
+ transpileTypeScript(parse_d1_error_default),
420
+ transpileTypeScript(runtime_default)
511
421
  ]);
512
422
  return `import { seed } from '${seedFilePath}'
513
423
 
514
- ${parseD1ErrorSource}
515
-
516
- ${createLogCaptureSource}
517
-
518
- ${buildSuccessResponseSource}
519
-
520
- ${buildErrorResponseSource}
521
-
522
- ${reconstructSecretsSource}
424
+ ${parseD1ErrorJS}
523
425
 
524
- ${createSeedFetchHandlerSource}
426
+ ${runtimeJS}
525
427
 
526
428
  export default { fetch: createSeedFetchHandler(seed, parseD1Error) }
527
429
  `;
package/dist/index.d.ts CHANGED
@@ -186,6 +186,8 @@ interface BackendFeatures {
186
186
  hasRoutes: boolean;
187
187
  /** Queue handlers/config exist */
188
188
  hasQueues: boolean;
189
+ /** server/hooks.ts exists */
190
+ hasHooks: boolean;
189
191
  /** .env files exist */
190
192
  hasSecrets: boolean;
191
193
  }
@@ -613,6 +615,8 @@ interface BackendDeploymentBundle {
613
615
  bindings?: BackendResourceBindings;
614
616
  /** Optional schema information for database setup */
615
617
  schema?: SchemaInfo;
618
+ /** Optional Cloudflare Worker compatibility flags */
619
+ compatibilityFlags?: string[];
616
620
  }
617
621
 
618
622
  /**
@@ -1199,7 +1203,6 @@ interface BackendRuntimeManifest {
1199
1203
  runtimeBuildId: string;
1200
1204
  inputFingerprint: string;
1201
1205
  entry: string;
1202
- polyfills: string;
1203
1206
  }
1204
1207
  /**
1205
1208
  * Resolved runtime artifact paths for backend bundling.
@@ -1209,8 +1212,6 @@ interface BackendRuntimePaths {
1209
1212
  runtimeDir: string;
1210
1213
  /** Runtime entry point used by the final worker bundler */
1211
1214
  runtimeEntry: string;
1212
- /** Polyfills entry used for user backend code */
1213
- polyfillsEntry: string;
1214
1215
  /** User's workspace node_modules */
1215
1216
  workspaceNodeModules: string;
1216
1217
  /** Resolution root for CLI dependencies — monorepo root in dev, .playcademy/node_modules in compiled binary */