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 +12 -4
- package/dist/constants.d.ts +14 -3
- package/dist/constants.js +6 -2
- package/dist/db.d.ts +5 -0
- package/dist/db.js +80 -178
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2695 -426
- package/dist/templates/playcademy-env.d.ts.template +12 -2
- package/dist/utils.js +2524 -159
- package/dist/version.js +1 -1
- package/package.json +4 -2
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.
|
|
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.
|
|
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/
|
|
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/
|
|
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
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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 = "
|
|
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.
|
|
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 = "
|
|
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.
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
499
|
-
|
|
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
|
-
${
|
|
515
|
-
|
|
516
|
-
${createLogCaptureSource}
|
|
517
|
-
|
|
518
|
-
${buildSuccessResponseSource}
|
|
519
|
-
|
|
520
|
-
${buildErrorResponseSource}
|
|
521
|
-
|
|
522
|
-
${reconstructSecretsSource}
|
|
424
|
+
${parseD1ErrorJS}
|
|
523
425
|
|
|
524
|
-
${
|
|
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 */
|