playcademy 0.18.0 → 0.18.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 +1 -1
- package/dist/index.d.ts +24 -12
- package/dist/index.js +465 -255
- package/dist/utils.js +558 -351
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/constants/src/achievements.ts +0 -107
- package/dist/constants/src/auth.ts +0 -13
- package/dist/constants/src/character.ts +0 -16
- package/dist/constants/src/domains.ts +0 -50
- package/dist/constants/src/env-vars.ts +0 -20
- package/dist/constants/src/index.ts +0 -18
- package/dist/constants/src/overworld.ts +0 -330
- package/dist/constants/src/system.ts +0 -10
- package/dist/constants/src/timeback.ts +0 -118
- package/dist/constants/src/typescript.ts +0 -21
- package/dist/constants/src/workers.ts +0 -36
- package/dist/edge-play/src/constants.ts +0 -27
- package/dist/edge-play/src/entry/middleware.ts +0 -247
- package/dist/edge-play/src/entry/queue.test.ts +0 -279
- package/dist/edge-play/src/entry/queue.ts +0 -107
- package/dist/edge-play/src/entry/session.ts +0 -45
- package/dist/edge-play/src/entry/setup.ts +0 -78
- package/dist/edge-play/src/entry/types.ts +0 -30
- package/dist/edge-play/src/entry.ts +0 -94
- package/dist/edge-play/src/html.d.ts +0 -5
- package/dist/edge-play/src/index.ts +0 -4
- package/dist/edge-play/src/lib/errors.ts +0 -51
- package/dist/edge-play/src/lib/index.ts +0 -3
- package/dist/edge-play/src/lib/self-dispatch.test.ts +0 -244
- package/dist/edge-play/src/lib/self-dispatch.ts +0 -41
- package/dist/edge-play/src/lib/validation.test.ts +0 -190
- package/dist/edge-play/src/lib/validation.ts +0 -64
- package/dist/edge-play/src/polyfills.js +0 -54
- package/dist/edge-play/src/register-routes.ts +0 -59
- package/dist/edge-play/src/routes/health.ts +0 -104
- package/dist/edge-play/src/routes/index.ts +0 -66
- package/dist/edge-play/src/routes/integrations/timeback/end-activity.ts +0 -181
- package/dist/edge-play/src/routes/integrations/timeback/get-xp.ts +0 -159
- package/dist/edge-play/src/routes/root.html +0 -253
- package/dist/edge-play/src/routes/root.ts +0 -22
- package/dist/edge-play/src/stub-entry.ts +0 -161
- package/dist/edge-play/src/types.ts +0 -124
package/dist/utils.js
CHANGED
|
@@ -1529,13 +1529,10 @@ function buildQueueImportStatements(queueHandlers) {
|
|
|
1529
1529
|
if (queueHandlers.length === 0) {
|
|
1530
1530
|
return "// No queue handlers";
|
|
1531
1531
|
}
|
|
1532
|
-
return
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
return `import queueHandler${index} from '@game-server/${normalizedFile}'`;
|
|
1537
|
-
})
|
|
1538
|
-
].join("\n");
|
|
1532
|
+
return queueHandlers.map((handler, index) => {
|
|
1533
|
+
const normalizedFile = handler.file.replace(/^server\//, "").replace(/\\/g, "/");
|
|
1534
|
+
return `import queueHandler${index} from '@game-server/${normalizedFile}'`;
|
|
1535
|
+
}).join("\n");
|
|
1539
1536
|
}
|
|
1540
1537
|
function buildQueueHandlerCode(queueHandlers) {
|
|
1541
1538
|
if (queueHandlers.length === 0) {
|
|
@@ -1950,8 +1947,8 @@ function processConfigVariables(config) {
|
|
|
1950
1947
|
}
|
|
1951
1948
|
|
|
1952
1949
|
// src/lib/dev/server.ts
|
|
1953
|
-
import { mkdir as
|
|
1954
|
-
import { join as
|
|
1950
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
1951
|
+
import { join as join20 } from "path";
|
|
1955
1952
|
import { Log, LogLevel, Miniflare } from "miniflare";
|
|
1956
1953
|
|
|
1957
1954
|
// ../edge-play/src/constants.ts
|
|
@@ -2027,14 +2024,14 @@ function readServerInfo(type, projectRoot) {
|
|
|
2027
2024
|
return servers[0] || null;
|
|
2028
2025
|
}
|
|
2029
2026
|
async function isPortInUse(port) {
|
|
2030
|
-
return new Promise((
|
|
2027
|
+
return new Promise((resolve6) => {
|
|
2031
2028
|
const server = createServer();
|
|
2032
2029
|
server.once("error", () => {
|
|
2033
|
-
|
|
2030
|
+
resolve6(true);
|
|
2034
2031
|
});
|
|
2035
2032
|
server.once("listening", () => {
|
|
2036
2033
|
server.close();
|
|
2037
|
-
|
|
2034
|
+
resolve6(false);
|
|
2038
2035
|
});
|
|
2039
2036
|
server.listen(port);
|
|
2040
2037
|
});
|
|
@@ -2048,12 +2045,150 @@ async function waitForPort(port, timeoutMs = 5e3) {
|
|
|
2048
2045
|
Stop the other server or specify a different port with --port <number>.`
|
|
2049
2046
|
);
|
|
2050
2047
|
}
|
|
2051
|
-
await new Promise((
|
|
2048
|
+
await new Promise((resolve6) => setTimeout(resolve6, 100));
|
|
2052
2049
|
}
|
|
2053
2050
|
}
|
|
2054
2051
|
|
|
2055
2052
|
// src/lib/deploy/bundle.ts
|
|
2056
|
-
import {
|
|
2053
|
+
import { join as join14, relative as relative3 } from "node:path";
|
|
2054
|
+
|
|
2055
|
+
// src/lib/dev/routes.ts
|
|
2056
|
+
init_file_loader();
|
|
2057
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
2058
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2059
|
+
import { join as join11, relative as relative2 } from "path";
|
|
2060
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2061
|
+
|
|
2062
|
+
// src/lib/deploy/hash.ts
|
|
2063
|
+
init_file_loader();
|
|
2064
|
+
import { createHash } from "crypto";
|
|
2065
|
+
import { createReadStream } from "fs";
|
|
2066
|
+
import { resolve as resolve4 } from "path";
|
|
2067
|
+
async function hashFile(filePath) {
|
|
2068
|
+
return new Promise((resolveHash, reject) => {
|
|
2069
|
+
const hash = createHash("sha256");
|
|
2070
|
+
const stream = createReadStream(filePath);
|
|
2071
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
2072
|
+
stream.on("end", () => resolveHash(hash.digest("hex")));
|
|
2073
|
+
stream.on("error", reject);
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
async function hashDirectory(dirPath, extensions) {
|
|
2077
|
+
try {
|
|
2078
|
+
const files = scanDirectory(dirPath, { extensions, allowMissing: true });
|
|
2079
|
+
if (files.length === 0) {
|
|
2080
|
+
return null;
|
|
2081
|
+
}
|
|
2082
|
+
files.sort();
|
|
2083
|
+
const combinedHash = createHash("sha256");
|
|
2084
|
+
for (const file of files) {
|
|
2085
|
+
const filePath = resolve4(dirPath, file);
|
|
2086
|
+
const fileHash = await hashFile(filePath);
|
|
2087
|
+
combinedHash.update(`${file}:${fileHash}`);
|
|
2088
|
+
}
|
|
2089
|
+
return combinedHash.digest("hex");
|
|
2090
|
+
} catch {
|
|
2091
|
+
return null;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
function hashContent(content) {
|
|
2095
|
+
const contentStr = typeof content === "string" ? content : JSON.stringify(content);
|
|
2096
|
+
return createHash("sha256").update(contentStr).digest("hex");
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// src/lib/dev/routes.ts
|
|
2100
|
+
async function discoverRoutes(apiDir) {
|
|
2101
|
+
const files = scanDirectory(apiDir, {
|
|
2102
|
+
extensions: ["ts", "js"],
|
|
2103
|
+
allowMissing: true
|
|
2104
|
+
});
|
|
2105
|
+
const routes = await Promise.all(
|
|
2106
|
+
files.map(async (file) => {
|
|
2107
|
+
const routePath = filePathToRoutePath(file);
|
|
2108
|
+
const absolutePath = join11(apiDir, file);
|
|
2109
|
+
const relativePath = relative2(getWorkspace(), absolutePath).replace(/\\/g, "/");
|
|
2110
|
+
const methods = await detectExportedMethods(absolutePath);
|
|
2111
|
+
return {
|
|
2112
|
+
path: routePath,
|
|
2113
|
+
file: relativePath,
|
|
2114
|
+
absolutePath,
|
|
2115
|
+
methods
|
|
2116
|
+
};
|
|
2117
|
+
})
|
|
2118
|
+
);
|
|
2119
|
+
return routes;
|
|
2120
|
+
}
|
|
2121
|
+
async function detectExportedMethods(filePath) {
|
|
2122
|
+
try {
|
|
2123
|
+
const importPath = await transpileRoute(filePath);
|
|
2124
|
+
const specifier = pathToFileURL2(importPath).href;
|
|
2125
|
+
const module = await import(specifier);
|
|
2126
|
+
const methods = [];
|
|
2127
|
+
if (module.GET) {
|
|
2128
|
+
methods.push("GET");
|
|
2129
|
+
}
|
|
2130
|
+
if (module.POST) {
|
|
2131
|
+
methods.push("POST");
|
|
2132
|
+
}
|
|
2133
|
+
if (module.PUT) {
|
|
2134
|
+
methods.push("PUT");
|
|
2135
|
+
}
|
|
2136
|
+
if (module.PATCH) {
|
|
2137
|
+
methods.push("PATCH");
|
|
2138
|
+
}
|
|
2139
|
+
if (module.DELETE) {
|
|
2140
|
+
methods.push("DELETE");
|
|
2141
|
+
}
|
|
2142
|
+
return methods;
|
|
2143
|
+
} catch {
|
|
2144
|
+
return [];
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
function filePathToRoutePath(filePath) {
|
|
2148
|
+
let routePath = filePath.replace(/\.(ts|js)$/, "");
|
|
2149
|
+
if (routePath === "index" || routePath.endsWith("/index")) {
|
|
2150
|
+
routePath = routePath.replace(/\/?index$/, "");
|
|
2151
|
+
}
|
|
2152
|
+
let urlPath = `/${routePath.replace(/\\/g, "/")}`;
|
|
2153
|
+
urlPath = urlPath.replace(/\[\.\.\.([^\]]+)\]/g, ":$1{.*}");
|
|
2154
|
+
urlPath = urlPath.replace(/\[([^\]]+)\]/g, ":$1");
|
|
2155
|
+
urlPath = urlPath === "/" ? "/api" : `/api${urlPath}`;
|
|
2156
|
+
return urlPath;
|
|
2157
|
+
}
|
|
2158
|
+
function isBun() {
|
|
2159
|
+
return typeof Bun !== "undefined";
|
|
2160
|
+
}
|
|
2161
|
+
async function transpileRoute(filePath) {
|
|
2162
|
+
if (isBun() || !filePath.endsWith(".ts")) {
|
|
2163
|
+
return filePath;
|
|
2164
|
+
}
|
|
2165
|
+
const esbuild3 = await import("esbuild");
|
|
2166
|
+
const result = await esbuild3.build({
|
|
2167
|
+
entryPoints: [filePath],
|
|
2168
|
+
write: false,
|
|
2169
|
+
format: "esm",
|
|
2170
|
+
platform: "node",
|
|
2171
|
+
bundle: true,
|
|
2172
|
+
// Bundle dependencies so route is self-contained
|
|
2173
|
+
target: "node18",
|
|
2174
|
+
external: ["hono"]
|
|
2175
|
+
// Don't bundle Hono (provided by dev server)
|
|
2176
|
+
});
|
|
2177
|
+
if (!result.outputFiles?.[0]) {
|
|
2178
|
+
throw new Error("Transpilation failed: no output");
|
|
2179
|
+
}
|
|
2180
|
+
const tempDir = join11(tmpdir2(), "playcademy-dev");
|
|
2181
|
+
await mkdir(tempDir, { recursive: true });
|
|
2182
|
+
const hash = hashContent(filePath).slice(0, 12);
|
|
2183
|
+
const jsPath = join11(tempDir, `${hash}.mjs`);
|
|
2184
|
+
await writeFile(jsPath, result.outputFiles[0].text);
|
|
2185
|
+
return jsPath;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// src/lib/deploy/backend-runtime.ts
|
|
2189
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
2190
|
+
import { copyFile, mkdir as mkdir2, readFile as readFile2, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
2191
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
2057
2192
|
import { join as join13 } from "node:path";
|
|
2058
2193
|
|
|
2059
2194
|
// ../utils/src/path.ts
|
|
@@ -2309,193 +2444,266 @@ function getMonorepoRoot() {
|
|
|
2309
2444
|
var isCompiledBinary = typeof IS_COMPILED_BINARY !== "undefined" && IS_COMPILED_BINARY;
|
|
2310
2445
|
|
|
2311
2446
|
// src/lib/build/plugins.ts
|
|
2447
|
+
var TEXT_LOADER_PATTERNS = [
|
|
2448
|
+
/edge-play\/src\/stub-entry\.ts$/,
|
|
2449
|
+
/edge-play\/src\/routes\/root\.html$/,
|
|
2450
|
+
/templates\/sample-route\.ts$/
|
|
2451
|
+
];
|
|
2312
2452
|
function textLoaderPlugin() {
|
|
2313
2453
|
return {
|
|
2314
2454
|
name: "text-loader",
|
|
2315
2455
|
setup(build2) {
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
const text = await fs2.readFile(args.path, "utf8");
|
|
2327
|
-
return {
|
|
2328
|
-
contents: `export default ${JSON.stringify(text)}`,
|
|
2329
|
-
loader: "js"
|
|
2330
|
-
};
|
|
2331
|
-
});
|
|
2332
|
-
build2.onLoad({ filter: /edge-play\/src\/routes\/root\.html$/ }, async (args) => {
|
|
2333
|
-
const fs2 = await import("fs/promises");
|
|
2334
|
-
const text = await fs2.readFile(args.path, "utf8");
|
|
2335
|
-
return {
|
|
2336
|
-
contents: `export default ${JSON.stringify(text)}`,
|
|
2337
|
-
loader: "js"
|
|
2338
|
-
};
|
|
2339
|
-
});
|
|
2340
|
-
build2.onLoad({ filter: /templates\/sample-route\.ts$/ }, async (args) => {
|
|
2341
|
-
const fs2 = await import("fs/promises");
|
|
2342
|
-
const text = await fs2.readFile(args.path, "utf8");
|
|
2343
|
-
return {
|
|
2344
|
-
contents: `export default ${JSON.stringify(text)}`,
|
|
2345
|
-
loader: "js"
|
|
2346
|
-
};
|
|
2347
|
-
});
|
|
2456
|
+
for (const filter of TEXT_LOADER_PATTERNS) {
|
|
2457
|
+
build2.onLoad({ filter }, async (args) => {
|
|
2458
|
+
const fs2 = await import("fs/promises");
|
|
2459
|
+
const text = await fs2.readFile(args.path, "utf8");
|
|
2460
|
+
return {
|
|
2461
|
+
contents: `export default ${JSON.stringify(text)}`,
|
|
2462
|
+
loader: "js"
|
|
2463
|
+
};
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2348
2466
|
}
|
|
2349
2467
|
};
|
|
2350
2468
|
}
|
|
2351
2469
|
|
|
2352
|
-
// src/lib/
|
|
2353
|
-
|
|
2354
|
-
import {
|
|
2355
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
2356
|
-
import { join as join11, relative as relative2 } from "path";
|
|
2357
|
-
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2470
|
+
// src/lib/build/embed.ts
|
|
2471
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2472
|
+
import { dirname as dirname4, join as join12 } from "node:path";
|
|
2358
2473
|
|
|
2359
|
-
// src/
|
|
2360
|
-
|
|
2361
|
-
init_file_loader();
|
|
2362
|
-
function hashContent(content) {
|
|
2363
|
-
const contentStr = typeof content === "string" ? content : JSON.stringify(content);
|
|
2364
|
-
return createHash("sha256").update(contentStr).digest("hex");
|
|
2365
|
-
}
|
|
2474
|
+
// src/version.ts
|
|
2475
|
+
var cliVersion = false ? "0.0.0-dev" : "0.18.2";
|
|
2366
2476
|
|
|
2367
|
-
// src/lib/
|
|
2368
|
-
|
|
2369
|
-
const
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
files.map(async (file) => {
|
|
2375
|
-
const routePath = filePathToRoutePath(file);
|
|
2376
|
-
const absolutePath = join11(apiDir, file);
|
|
2377
|
-
const relativePath = relative2(getWorkspace(), absolutePath).replace(/\\/g, "/");
|
|
2378
|
-
const methods = await detectExportedMethods(absolutePath);
|
|
2379
|
-
return {
|
|
2380
|
-
path: routePath,
|
|
2381
|
-
file: relativePath,
|
|
2382
|
-
absolutePath,
|
|
2383
|
-
methods
|
|
2384
|
-
};
|
|
2385
|
-
})
|
|
2386
|
-
);
|
|
2387
|
-
return routes;
|
|
2388
|
-
}
|
|
2389
|
-
async function detectExportedMethods(filePath) {
|
|
2390
|
-
try {
|
|
2391
|
-
const importPath = await transpileRoute(filePath);
|
|
2392
|
-
const specifier = pathToFileURL2(importPath).href;
|
|
2393
|
-
const module = await import(specifier);
|
|
2394
|
-
const methods = [];
|
|
2395
|
-
if (module.GET) {
|
|
2396
|
-
methods.push("GET");
|
|
2397
|
-
}
|
|
2398
|
-
if (module.POST) {
|
|
2399
|
-
methods.push("POST");
|
|
2477
|
+
// src/lib/build/embed.ts
|
|
2478
|
+
function writeFileTree(baseDir, files) {
|
|
2479
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
2480
|
+
const fullPath = join12(baseDir, relativePath);
|
|
2481
|
+
const dir = dirname4(fullPath);
|
|
2482
|
+
if (!existsSync7(dir)) {
|
|
2483
|
+
mkdirSync2(dir, { recursive: true });
|
|
2400
2484
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
2485
|
+
writeFileSync3(fullPath, content);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
function createBundleExtractor(options) {
|
|
2489
|
+
const { name, cacheSubdir, getBundle } = options;
|
|
2490
|
+
const marker = `.${name}.ok`;
|
|
2491
|
+
let cachedDir = null;
|
|
2492
|
+
return () => {
|
|
2493
|
+
if (cachedDir) {
|
|
2494
|
+
return cachedDir;
|
|
2403
2495
|
}
|
|
2404
|
-
|
|
2405
|
-
|
|
2496
|
+
const bundle = getBundle();
|
|
2497
|
+
if (!bundle) {
|
|
2498
|
+
throw new Error(`${name}: embedded bundle is null (not available in dev mode)`);
|
|
2406
2499
|
}
|
|
2407
|
-
|
|
2408
|
-
|
|
2500
|
+
const dir = join12(cacheVersionDir(cliVersion), cacheSubdir);
|
|
2501
|
+
const markerPath = join12(dir, marker);
|
|
2502
|
+
if (!existsSync7(markerPath)) {
|
|
2503
|
+
mkdirSync2(dir, { recursive: true });
|
|
2504
|
+
writeFileTree(dir, bundle);
|
|
2505
|
+
writeFileSync3(markerPath, `${cliVersion}
|
|
2506
|
+
`, "utf8");
|
|
2409
2507
|
}
|
|
2410
|
-
|
|
2508
|
+
cachedDir = dir;
|
|
2509
|
+
return dir;
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
// src/lib/deploy/embedded-runtime.ts
|
|
2514
|
+
var EMBEDDED_BACKEND_RUNTIME = null;
|
|
2515
|
+
|
|
2516
|
+
// src/lib/deploy/extract-runtime.ts
|
|
2517
|
+
var extractEmbeddedBackendRuntime = createBundleExtractor({
|
|
2518
|
+
name: "embedded-backend-runtime",
|
|
2519
|
+
cacheSubdir: "backend-runtime",
|
|
2520
|
+
getBundle: () => EMBEDDED_BACKEND_RUNTIME
|
|
2521
|
+
});
|
|
2522
|
+
|
|
2523
|
+
// src/lib/deploy/backend-runtime.ts
|
|
2524
|
+
var BACKEND_RUNTIME_ENTRY = "index.js";
|
|
2525
|
+
var BACKEND_RUNTIME_POLYFILLS = "polyfills.js";
|
|
2526
|
+
var BACKEND_RUNTIME_MANIFEST = "manifest.json";
|
|
2527
|
+
var DEV_RUNTIME_CACHE_DIR = "backend-runtime-dev";
|
|
2528
|
+
function createNodePolyfillAliases(polyfillsPath) {
|
|
2529
|
+
return {
|
|
2530
|
+
fs: polyfillsPath,
|
|
2531
|
+
"fs/promises": polyfillsPath,
|
|
2532
|
+
path: polyfillsPath,
|
|
2533
|
+
os: polyfillsPath,
|
|
2534
|
+
process: polyfillsPath,
|
|
2535
|
+
"node:fs": polyfillsPath,
|
|
2536
|
+
"node:fs/promises": polyfillsPath,
|
|
2537
|
+
"node:path": polyfillsPath,
|
|
2538
|
+
"node:os": polyfillsPath,
|
|
2539
|
+
"node:process": polyfillsPath
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
var WORKSPACE_SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
2543
|
+
var RUNTIME_SOURCE_DIRS = [
|
|
2544
|
+
"packages/edge-play/src",
|
|
2545
|
+
"packages/sdk/src",
|
|
2546
|
+
"packages/constants/src",
|
|
2547
|
+
"packages/timeback/src",
|
|
2548
|
+
"packages/types/src",
|
|
2549
|
+
"packages/utils/src",
|
|
2550
|
+
"packages/cloudflare/src",
|
|
2551
|
+
"packages/logger/src"
|
|
2552
|
+
];
|
|
2553
|
+
async function getPackageVersion2(monorepoRoot, packageName) {
|
|
2554
|
+
try {
|
|
2555
|
+
const packageJson = JSON.parse(
|
|
2556
|
+
await readFile2(join13(monorepoRoot, "packages", packageName, "package.json"), "utf8")
|
|
2557
|
+
);
|
|
2558
|
+
return packageJson.version ?? "0.0.0-dev";
|
|
2411
2559
|
} catch {
|
|
2412
|
-
return
|
|
2560
|
+
return "0.0.0-dev";
|
|
2413
2561
|
}
|
|
2414
2562
|
}
|
|
2415
|
-
function
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
urlPath = urlPath === "/" ? "/api" : `/api${urlPath}`;
|
|
2424
|
-
return urlPath;
|
|
2563
|
+
async function computeRuntimeInputFingerprint(monorepoRoot, sdkVersion) {
|
|
2564
|
+
const entries = await Promise.all(
|
|
2565
|
+
RUNTIME_SOURCE_DIRS.map(async (dir) => [
|
|
2566
|
+
dir,
|
|
2567
|
+
await hashDirectory(join13(monorepoRoot, dir), ["ts", "js", "json", "html"])
|
|
2568
|
+
])
|
|
2569
|
+
);
|
|
2570
|
+
return hashContent({ ...Object.fromEntries(entries), sdkVersion });
|
|
2425
2571
|
}
|
|
2426
|
-
function
|
|
2427
|
-
|
|
2572
|
+
async function getRuntimeBuildMetadata(monorepoRoot) {
|
|
2573
|
+
const [cliVersion2, sdkVersion] = await Promise.all([
|
|
2574
|
+
getPackageVersion2(monorepoRoot, "cli"),
|
|
2575
|
+
getPackageVersion2(monorepoRoot, "sdk")
|
|
2576
|
+
]);
|
|
2577
|
+
const inputFingerprint = await computeRuntimeInputFingerprint(monorepoRoot, sdkVersion);
|
|
2578
|
+
return {
|
|
2579
|
+
cliVersion: cliVersion2,
|
|
2580
|
+
sdkVersion,
|
|
2581
|
+
runtimeBuildId: inputFingerprint.slice(0, 12),
|
|
2582
|
+
inputFingerprint,
|
|
2583
|
+
entry: BACKEND_RUNTIME_ENTRY,
|
|
2584
|
+
polyfills: BACKEND_RUNTIME_POLYFILLS
|
|
2585
|
+
};
|
|
2428
2586
|
}
|
|
2429
|
-
|
|
2430
|
-
if (
|
|
2431
|
-
return
|
|
2587
|
+
function resolveWorkspaceSourceImport(monorepoRoot, specifier) {
|
|
2588
|
+
if (!specifier.startsWith("@playcademy/")) {
|
|
2589
|
+
return null;
|
|
2590
|
+
}
|
|
2591
|
+
const pathParts = specifier.slice("@playcademy/".length).split("/");
|
|
2592
|
+
const packageName = pathParts.shift();
|
|
2593
|
+
if (!packageName) {
|
|
2594
|
+
return null;
|
|
2595
|
+
}
|
|
2596
|
+
const basePath = join13(monorepoRoot, "packages", packageName, "src");
|
|
2597
|
+
const targetPath = pathParts.length > 0 ? join13(basePath, ...pathParts) : join13(basePath, "index");
|
|
2598
|
+
for (const extension of WORKSPACE_SOURCE_EXTENSIONS) {
|
|
2599
|
+
const withExtension = `${targetPath}${extension}`;
|
|
2600
|
+
if (existsSync8(withExtension)) {
|
|
2601
|
+
return withExtension;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
for (const extension of WORKSPACE_SOURCE_EXTENSIONS) {
|
|
2605
|
+
const indexPath = join13(targetPath, `index${extension}`);
|
|
2606
|
+
if (existsSync8(indexPath)) {
|
|
2607
|
+
return indexPath;
|
|
2608
|
+
}
|
|
2432
2609
|
}
|
|
2610
|
+
return null;
|
|
2611
|
+
}
|
|
2612
|
+
function createWorkspaceSourceResolverPlugin(monorepoRoot) {
|
|
2613
|
+
return {
|
|
2614
|
+
name: "playcademy-workspace-source-resolver",
|
|
2615
|
+
setup(build2) {
|
|
2616
|
+
build2.onResolve({ filter: /^@playcademy\// }, (args) => {
|
|
2617
|
+
const resolved = resolveWorkspaceSourceImport(monorepoRoot, args.path);
|
|
2618
|
+
return resolved ? { path: resolved } : null;
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
async function writeRuntimeManifest(outdir, manifest) {
|
|
2624
|
+
await writeFile2(
|
|
2625
|
+
join13(outdir, BACKEND_RUNTIME_MANIFEST),
|
|
2626
|
+
`${JSON.stringify(manifest, null, 2)}
|
|
2627
|
+
`,
|
|
2628
|
+
"utf8"
|
|
2629
|
+
);
|
|
2630
|
+
}
|
|
2631
|
+
async function buildBackendRuntime(outdir) {
|
|
2632
|
+
const monorepoRoot = getMonorepoRoot();
|
|
2633
|
+
const manifest = await getRuntimeBuildMetadata(monorepoRoot);
|
|
2634
|
+
const runtimeEntry = join13(monorepoRoot, "packages", "edge-play", "src", "entry", "index.ts");
|
|
2635
|
+
const polyfillsEntry = join13(monorepoRoot, "packages", "edge-play", "src", "polyfills.js");
|
|
2433
2636
|
const esbuild3 = await import("esbuild");
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2637
|
+
await rm(outdir, { recursive: true, force: true });
|
|
2638
|
+
await mkdir2(outdir, { recursive: true });
|
|
2639
|
+
await esbuild3.build({
|
|
2640
|
+
entryPoints: [runtimeEntry],
|
|
2641
|
+
outfile: join13(outdir, BACKEND_RUNTIME_ENTRY),
|
|
2439
2642
|
bundle: true,
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2643
|
+
format: "esm",
|
|
2644
|
+
platform: "browser",
|
|
2645
|
+
target: "es2022",
|
|
2646
|
+
write: true,
|
|
2647
|
+
sourcemap: false,
|
|
2648
|
+
minify: false,
|
|
2649
|
+
logLevel: "error",
|
|
2650
|
+
alias: createNodePolyfillAliases(polyfillsEntry),
|
|
2651
|
+
plugins: [textLoaderPlugin(), createWorkspaceSourceResolverPlugin(monorepoRoot)],
|
|
2652
|
+
define: {
|
|
2653
|
+
PLAYCADEMY_RUNTIME_CLI_VERSION: JSON.stringify(manifest.cliVersion),
|
|
2654
|
+
PLAYCADEMY_RUNTIME_SDK_VERSION: JSON.stringify(manifest.sdkVersion),
|
|
2655
|
+
PLAYCADEMY_RUNTIME_BUILD_ID: JSON.stringify(manifest.runtimeBuildId)
|
|
2656
|
+
}
|
|
2444
2657
|
});
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
const tempDir = join11(tmpdir2(), "playcademy-dev");
|
|
2449
|
-
await mkdir(tempDir, { recursive: true });
|
|
2450
|
-
const hash = hashContent(filePath).slice(0, 12);
|
|
2451
|
-
const jsPath = join11(tempDir, `${hash}.mjs`);
|
|
2452
|
-
await writeFile(jsPath, result.outputFiles[0].text);
|
|
2453
|
-
return jsPath;
|
|
2658
|
+
await copyFile(polyfillsEntry, join13(outdir, BACKEND_RUNTIME_POLYFILLS));
|
|
2659
|
+
await writeRuntimeManifest(outdir, manifest);
|
|
2660
|
+
return manifest;
|
|
2454
2661
|
}
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
// src/lib/deploy/extract-sources.ts
|
|
2467
|
-
var EXTRACTION_MARKER = ".embedded-sources.ok";
|
|
2468
|
-
function writeTree(baseDir, files) {
|
|
2469
|
-
for (const [relativePath, content] of Object.entries(files)) {
|
|
2470
|
-
const fullPath = join12(baseDir, relativePath);
|
|
2471
|
-
const dir = dirname4(fullPath);
|
|
2472
|
-
if (!existsSync7(dir)) {
|
|
2473
|
-
mkdirSync2(dir, { recursive: true });
|
|
2474
|
-
}
|
|
2475
|
-
writeFileSync3(fullPath, content);
|
|
2662
|
+
async function ensureDevBackendRuntime() {
|
|
2663
|
+
const monorepoRoot = getMonorepoRoot();
|
|
2664
|
+
const manifest = await getRuntimeBuildMetadata(monorepoRoot);
|
|
2665
|
+
const runtimeDir = join13(
|
|
2666
|
+
tmpdir3(),
|
|
2667
|
+
DEV_RUNTIME_CACHE_DIR,
|
|
2668
|
+
manifest.cliVersion,
|
|
2669
|
+
manifest.inputFingerprint
|
|
2670
|
+
);
|
|
2671
|
+
if (!existsSync8(join13(runtimeDir, BACKEND_RUNTIME_MANIFEST))) {
|
|
2672
|
+
await buildBackendRuntime(runtimeDir);
|
|
2476
2673
|
}
|
|
2674
|
+
return runtimeDir;
|
|
2477
2675
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2676
|
+
async function readRuntimeManifest(runtimeDir) {
|
|
2677
|
+
const content = await readFile2(join13(runtimeDir, BACKEND_RUNTIME_MANIFEST), "utf8");
|
|
2678
|
+
return JSON.parse(content);
|
|
2679
|
+
}
|
|
2680
|
+
async function resolveBackendRuntimePaths() {
|
|
2681
|
+
const workspace = getWorkspace();
|
|
2682
|
+
const workspaceNodeModules = join13(workspace, "node_modules");
|
|
2683
|
+
if (isCompiledBinary) {
|
|
2684
|
+
const runtimeDir2 = extractEmbeddedBackendRuntime();
|
|
2685
|
+
const manifest2 = await readRuntimeManifest(runtimeDir2);
|
|
2686
|
+
const cliDepsRoot = join13(workspace, ".playcademy", "node_modules");
|
|
2687
|
+
return {
|
|
2688
|
+
runtimeDir: runtimeDir2,
|
|
2689
|
+
runtimeEntry: join13(runtimeDir2, manifest2.entry),
|
|
2690
|
+
polyfillsEntry: join13(runtimeDir2, manifest2.polyfills),
|
|
2691
|
+
workspaceNodeModules,
|
|
2692
|
+
cliDepsRoot,
|
|
2693
|
+
manifest: manifest2
|
|
2694
|
+
};
|
|
2496
2695
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2696
|
+
const monorepoRoot = getMonorepoRoot();
|
|
2697
|
+
const runtimeDir = await ensureDevBackendRuntime();
|
|
2698
|
+
const manifest = await readRuntimeManifest(runtimeDir);
|
|
2699
|
+
return {
|
|
2700
|
+
runtimeDir,
|
|
2701
|
+
runtimeEntry: join13(runtimeDir, manifest.entry),
|
|
2702
|
+
polyfillsEntry: join13(runtimeDir, manifest.polyfills),
|
|
2703
|
+
workspaceNodeModules,
|
|
2704
|
+
cliDepsRoot: monorepoRoot,
|
|
2705
|
+
manifest
|
|
2706
|
+
};
|
|
2499
2707
|
}
|
|
2500
2708
|
|
|
2501
2709
|
// src/lib/deploy/bundle.ts
|
|
@@ -2503,153 +2711,134 @@ async function discoverCustomRoutes(config) {
|
|
|
2503
2711
|
const workspace = getWorkspace();
|
|
2504
2712
|
const customRoutesConfig = config.integrations?.customRoutes;
|
|
2505
2713
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
2506
|
-
const customRoutes = await discoverRoutes(
|
|
2714
|
+
const customRoutes = await discoverRoutes(join14(workspace, customRoutesDir));
|
|
2507
2715
|
const customRouteData = customRoutes.map((r) => ({
|
|
2508
2716
|
path: r.path,
|
|
2509
2717
|
file: r.file,
|
|
2510
|
-
//
|
|
2718
|
+
// relative path (e.g., 'server/api/test.ts'), not absolute
|
|
2511
2719
|
methods: r.methods
|
|
2512
2720
|
}));
|
|
2513
2721
|
return { customRouteData, customRoutesDir };
|
|
2514
2722
|
}
|
|
2515
|
-
function
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
const { edgePlaySrc: edgePlaySrc2, constantsSrc } = extractEmbeddedSources();
|
|
2519
|
-
const constantsEntry2 = join13(constantsSrc, "index.ts");
|
|
2520
|
-
const workspaceNodeModules2 = join13(workspace, "node_modules");
|
|
2521
|
-
const playcademyNodeModules = join13(workspace, ".playcademy", "node_modules");
|
|
2522
|
-
return {
|
|
2523
|
-
isBuiltPackage: true,
|
|
2524
|
-
edgePlaySrc: edgePlaySrc2,
|
|
2525
|
-
constantsEntry: constantsEntry2,
|
|
2526
|
-
workspaceNodeModules: workspaceNodeModules2,
|
|
2527
|
-
cliDepsRoot: playcademyNodeModules
|
|
2528
|
-
};
|
|
2723
|
+
function getForbiddenImportMessage(specifier) {
|
|
2724
|
+
if (specifier === "@playcademy/sdk/server") {
|
|
2725
|
+
return "Use `c.get('sdk')` instead of importing the server SDK directly.";
|
|
2529
2726
|
}
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
const cliPackageRoot = isBuiltPackage ? join13(distDir, "../../..") : join13(monorepoRoot, "packages/cli");
|
|
2537
|
-
const cliDepsRoot = isBuiltPackage ? join13(cliPackageRoot, "node_modules") : monorepoRoot;
|
|
2538
|
-
const workspaceNodeModules = join13(workspace, "node_modules");
|
|
2539
|
-
const constantsEntry = isBuiltPackage ? join13(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join13(monorepoRoot, "packages", "constants", "src", "index.ts");
|
|
2727
|
+
if (specifier.startsWith("@playcademy/edge-play")) {
|
|
2728
|
+
return "Built-in backend runtime helpers are internal to the CLI-owned runtime.";
|
|
2729
|
+
}
|
|
2730
|
+
return "CLI-owned backend runtime packages are not available to game backend code.";
|
|
2731
|
+
}
|
|
2732
|
+
function createForbiddenRuntimeImportsPlugin(workspace) {
|
|
2540
2733
|
return {
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2734
|
+
name: "forbidden-backend-runtime-imports",
|
|
2735
|
+
setup(build2) {
|
|
2736
|
+
build2.onResolve(
|
|
2737
|
+
{
|
|
2738
|
+
filter: /^@playcademy\/(?:sdk\/server|edge-play(?:\/.*)?|constants(?:\/.*)?)$/
|
|
2739
|
+
},
|
|
2740
|
+
(args) => {
|
|
2741
|
+
if (!args.importer) {
|
|
2742
|
+
return null;
|
|
2743
|
+
}
|
|
2744
|
+
const importerPath = relative3(workspace, args.importer).replace(/\\/g, "/");
|
|
2745
|
+
return {
|
|
2746
|
+
errors: [
|
|
2747
|
+
{
|
|
2748
|
+
text: `Unsupported backend import "${args.path}" in ${importerPath}. ${getForbiddenImportMessage(args.path)} Use \`Context\`, \`c.env\`, \`c.get('sdk')\`, and \`c.get('playcademyUser')\` instead.`
|
|
2749
|
+
}
|
|
2750
|
+
]
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
);
|
|
2754
|
+
}
|
|
2546
2755
|
};
|
|
2547
2756
|
}
|
|
2548
2757
|
function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, options) {
|
|
2549
2758
|
const workspace = getWorkspace();
|
|
2550
|
-
const {
|
|
2759
|
+
const { runtimeEntry, polyfillsEntry, workspaceNodeModules, cliDepsRoot } = paths;
|
|
2551
2760
|
return {
|
|
2552
|
-
// ──── Input
|
|
2761
|
+
// ──── Input: generated entry code, not a real file ────
|
|
2762
|
+
// resolveDir = workspace so that bare specifiers in the generated code
|
|
2763
|
+
// (e.g. '@game-api/save.ts') resolve relative to the user's project root.
|
|
2553
2764
|
stdin: {
|
|
2554
2765
|
contents: entryCode,
|
|
2555
|
-
|
|
2556
|
-
resolveDir: edgePlaySrc,
|
|
2557
|
-
// Resolve relative imports from edge-play/src
|
|
2766
|
+
resolveDir: workspace,
|
|
2558
2767
|
loader: "ts"
|
|
2559
|
-
// Treat input as TypeScript
|
|
2560
2768
|
},
|
|
2561
|
-
// ──── Output
|
|
2769
|
+
// ──── Output configuration ────
|
|
2562
2770
|
bundle: true,
|
|
2563
|
-
// Bundle all dependencies into single file
|
|
2564
2771
|
format: "esm",
|
|
2565
|
-
//
|
|
2772
|
+
// Cloudflare Workers require ES modules
|
|
2566
2773
|
platform: "browser",
|
|
2567
|
-
// Workers
|
|
2774
|
+
// Workers run in a browser-like environment, not Node.js
|
|
2568
2775
|
target: "es2022",
|
|
2569
|
-
// Modern JavaScript for Workers runtime
|
|
2570
2776
|
write: false,
|
|
2571
|
-
// Return code as string
|
|
2777
|
+
// Return code as a string; the caller writes/uploads it
|
|
2572
2778
|
sourcemap: options.sourcemap ? "inline" : false,
|
|
2573
2779
|
minify: options.minify || false,
|
|
2574
|
-
logLevel: "
|
|
2575
|
-
//
|
|
2576
|
-
// ──── Module
|
|
2577
|
-
//
|
|
2578
|
-
//
|
|
2579
|
-
//
|
|
2580
|
-
//
|
|
2780
|
+
logLevel: "silent",
|
|
2781
|
+
// errors propagate as thrown exceptions; we don't want esbuild double-printing to stderr
|
|
2782
|
+
// ──── Module resolution ────
|
|
2783
|
+
// esbuild searches nodePaths in order for bare specifiers.
|
|
2784
|
+
// workspaceNodeModules: game project's own deps (hono, etc. if user installed them)
|
|
2785
|
+
// cliDepsRoot: CLI-managed deps (.playcademy/node_modules in binary mode,
|
|
2786
|
+
// monorepo root in dev mode) — where hono, @hono/node-server live
|
|
2581
2787
|
nodePaths: [workspaceNodeModules, cliDepsRoot],
|
|
2582
|
-
// ──── Build-time
|
|
2583
|
-
//
|
|
2584
|
-
//
|
|
2788
|
+
// ──── Build-time constants ────
|
|
2789
|
+
// PLAYCADEMY_CONFIG is accessed as a global in the generated entry code and in
|
|
2790
|
+
// the sealed runtime itself (passed to createBaseApp). It carries integrations
|
|
2791
|
+
// config, route metadata, queue metadata, etc.
|
|
2585
2792
|
define: {
|
|
2586
2793
|
PLAYCADEMY_CONFIG: JSON.stringify(bundleConfig)
|
|
2587
2794
|
},
|
|
2588
|
-
// ──── Import Aliases ────
|
|
2589
2795
|
alias: {
|
|
2590
|
-
// ┌─
|
|
2591
|
-
// │
|
|
2592
|
-
// │
|
|
2593
|
-
//
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
//
|
|
2597
|
-
// │
|
|
2598
|
-
//
|
|
2599
|
-
|
|
2600
|
-
//
|
|
2601
|
-
|
|
2602
|
-
//
|
|
2603
|
-
// │
|
|
2604
|
-
//
|
|
2605
|
-
|
|
2606
|
-
//
|
|
2607
|
-
|
|
2608
|
-
//
|
|
2609
|
-
// │
|
|
2610
|
-
//
|
|
2611
|
-
|
|
2612
|
-
//
|
|
2613
|
-
|
|
2614
|
-
// │ that throw helpful errors if user code tries to use them. │
|
|
2615
|
-
// └─────────────────────────────────────────────────────────────────┘
|
|
2616
|
-
fs: join13(edgePlaySrc, "polyfills.js"),
|
|
2617
|
-
"fs/promises": join13(edgePlaySrc, "polyfills.js"),
|
|
2618
|
-
path: join13(edgePlaySrc, "polyfills.js"),
|
|
2619
|
-
os: join13(edgePlaySrc, "polyfills.js"),
|
|
2620
|
-
process: join13(edgePlaySrc, "polyfills.js")
|
|
2796
|
+
// ┌─ Sealed backend runtime ────────────────────────────────────────────┐
|
|
2797
|
+
// │ The pre-bundled artifact: edge-play + sdk/server + all internal │
|
|
2798
|
+
// │ @playcademy/* deps, zero external imports remaining. This alias │
|
|
2799
|
+
// │ is what enforces version isolation — game node_modules are ignored. │
|
|
2800
|
+
// └─────────────────────────────────────────────────────────────────────┘
|
|
2801
|
+
"@playcademy/backend-runtime": runtimeEntry,
|
|
2802
|
+
// ┌─ Game's custom route files ─────────────────────────────────────────┐
|
|
2803
|
+
// │ @game-api is a virtual module pointing to the game's API directory. │
|
|
2804
|
+
// │ Example: import * as route from '@game-api/save.ts' │
|
|
2805
|
+
// │ Resolves to: /user-project/server/api/save.ts │
|
|
2806
|
+
// └─────────────────────────────────────────────────────────────────────┘
|
|
2807
|
+
"@game-api": join14(workspace, customRoutesDir),
|
|
2808
|
+
// ┌─ Game's server library directory ───────────────────────────────────┐
|
|
2809
|
+
// │ @game-server is a virtual module for server-side utilities. │
|
|
2810
|
+
// │ Example: import { getAuth } from '@game-server/lib/auth' │
|
|
2811
|
+
// │ Resolves to: /user-project/server/lib/auth.ts │
|
|
2812
|
+
// └─────────────────────────────────────────────────────────────────────┘
|
|
2813
|
+
"@game-server": join14(workspace, "server"),
|
|
2814
|
+
// ┌─ Node.js polyfills for Cloudflare Workers ──────────────────────────┐
|
|
2815
|
+
// │ Workers don't have fs, path, os, process, etc. Redirect them to a │
|
|
2816
|
+
// │ polyfills stub bundled into the sealed runtime that throws a clear │
|
|
2817
|
+
// │ error if user code actually calls them at runtime. │
|
|
2818
|
+
// └─────────────────────────────────────────────────────────────────────┘
|
|
2819
|
+
...createNodePolyfillAliases(polyfillsEntry)
|
|
2621
2820
|
},
|
|
2622
|
-
|
|
2623
|
-
plugins: [textLoaderPlugin()],
|
|
2624
|
-
// Support Bun's 'with { type: "text" }' imports
|
|
2625
|
-
// ──── External Dependencies ────
|
|
2821
|
+
plugins: [createForbiddenRuntimeImportsPlugin(workspace)],
|
|
2626
2822
|
external: []
|
|
2627
|
-
// Bundle everything
|
|
2823
|
+
// Bundle everything — no externals allowed in Workers
|
|
2628
2824
|
};
|
|
2629
2825
|
}
|
|
2630
|
-
function loadEntryTemplate(paths) {
|
|
2631
|
-
const entryPath = paths ? join13(paths.edgePlaySrc, "entry.ts") : join13(getMonorepoRoot(), "packages", "edge-play", "src", "entry.ts");
|
|
2632
|
-
return readFileSync3(entryPath, "utf8");
|
|
2633
|
-
}
|
|
2634
2826
|
async function bundleBackend(config, options = {}) {
|
|
2635
2827
|
const esbuild3 = await import("esbuild");
|
|
2636
2828
|
const { customRouteData, customRoutesDir } = await discoverCustomRoutes(config);
|
|
2637
2829
|
const queueHandlers = discoverQueueHandlers();
|
|
2638
2830
|
validateQueueHandlersConfig(config, queueHandlers);
|
|
2639
|
-
const paths =
|
|
2640
|
-
const entryTemplate = loadEntryTemplate(paths);
|
|
2831
|
+
const paths = await resolveBackendRuntimePaths();
|
|
2641
2832
|
const bundleConfig = {
|
|
2642
2833
|
...config,
|
|
2643
2834
|
__routeMetadata: customRouteData,
|
|
2644
2835
|
__queueMetadata: queueHandlers
|
|
2645
2836
|
};
|
|
2646
|
-
const hasAuth = Boolean(config.integrations?.auth);
|
|
2647
2837
|
const entryCode = generateEntryCode(
|
|
2648
2838
|
customRouteData,
|
|
2649
2839
|
customRoutesDir,
|
|
2650
2840
|
queueHandlers,
|
|
2651
|
-
|
|
2652
|
-
entryTemplate
|
|
2841
|
+
Boolean(config.integrations?.auth)
|
|
2653
2842
|
);
|
|
2654
2843
|
const buildConfig = createEsbuildConfig(
|
|
2655
2844
|
entryCode,
|
|
@@ -2662,9 +2851,8 @@ async function bundleBackend(config, options = {}) {
|
|
|
2662
2851
|
if (!result.outputFiles?.[0]) {
|
|
2663
2852
|
throw new Error("Backend bundling failed: no output");
|
|
2664
2853
|
}
|
|
2665
|
-
const code = result.outputFiles[0].text;
|
|
2666
2854
|
return {
|
|
2667
|
-
code,
|
|
2855
|
+
code: result.outputFiles[0].text,
|
|
2668
2856
|
config,
|
|
2669
2857
|
customRoutes: customRouteData
|
|
2670
2858
|
};
|
|
@@ -2672,50 +2860,69 @@ async function bundleBackend(config, options = {}) {
|
|
|
2672
2860
|
function buildCustomRouteImportStatements(customRoutes, customRoutesDir) {
|
|
2673
2861
|
const normalizedRoutesDir = customRoutesDir.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
2674
2862
|
const customRoutesPrefix = normalizedRoutesDir.endsWith("/") ? normalizedRoutesDir : `${normalizedRoutesDir}/`;
|
|
2675
|
-
return customRoutes.map((route,
|
|
2863
|
+
return customRoutes.map((route, index) => {
|
|
2676
2864
|
const normalizedFile = route.file.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
2677
2865
|
const importPath = normalizedFile.startsWith(customRoutesPrefix) ? normalizedFile.slice(customRoutesPrefix.length) : normalizedFile;
|
|
2678
|
-
return `import * as customRoute${
|
|
2866
|
+
return `import * as customRoute${index} from '@game-api/${importPath}'`;
|
|
2679
2867
|
}).join("\n");
|
|
2680
2868
|
}
|
|
2681
|
-
function
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
);
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
const
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
"import { getAuth } from '@game-server/lib/auth'"
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2869
|
+
function buildCustomRouteModuleCode(customRoutes) {
|
|
2870
|
+
if (customRoutes.length === 0) {
|
|
2871
|
+
return "const __customRoutes = []";
|
|
2872
|
+
}
|
|
2873
|
+
return [
|
|
2874
|
+
"const __customRoutes = [",
|
|
2875
|
+
...customRoutes.map(
|
|
2876
|
+
(route, index) => ` { path: ${JSON.stringify(route.path)}, module: customRoute${index} },`
|
|
2877
|
+
),
|
|
2878
|
+
"]"
|
|
2879
|
+
].join("\n");
|
|
2880
|
+
}
|
|
2881
|
+
function generateEntryCode(customRoutes, customRoutesDir, queueHandlers, hasAuth = false) {
|
|
2882
|
+
const runtimeImports = [
|
|
2883
|
+
"createBaseApp",
|
|
2884
|
+
"registerBuiltinRoutes",
|
|
2885
|
+
"registerCustomRouteModules",
|
|
2886
|
+
"registerTerminalHandlers"
|
|
2887
|
+
];
|
|
2888
|
+
if (hasAuth) {
|
|
2889
|
+
runtimeImports.push("createSessionMiddleware");
|
|
2890
|
+
}
|
|
2891
|
+
if (queueHandlers.length > 0) {
|
|
2892
|
+
runtimeImports.push("createQueueRouter", "registerQueueIngressRoute");
|
|
2893
|
+
}
|
|
2894
|
+
const importBlocks = [
|
|
2895
|
+
`import { ${runtimeImports.join(", ")} } from '@playcademy/backend-runtime'`
|
|
2896
|
+
];
|
|
2897
|
+
if (hasAuth) {
|
|
2898
|
+
importBlocks.push("import { getAuth } from '@game-server/lib/auth'");
|
|
2899
|
+
}
|
|
2900
|
+
if (customRoutes.length > 0) {
|
|
2901
|
+
importBlocks.push(buildCustomRouteImportStatements(customRoutes, customRoutesDir));
|
|
2902
|
+
}
|
|
2903
|
+
if (queueHandlers.length > 0) {
|
|
2904
|
+
importBlocks.push(buildQueueImportStatements(queueHandlers));
|
|
2905
|
+
}
|
|
2906
|
+
const body = [
|
|
2907
|
+
"// PLAYCADEMY_CONFIG is injected at bundle time via esbuild define",
|
|
2908
|
+
"const app = createBaseApp(PLAYCADEMY_CONFIG)",
|
|
2909
|
+
""
|
|
2910
|
+
];
|
|
2911
|
+
if (hasAuth) {
|
|
2912
|
+
body.push("app.use('*', createSessionMiddleware(getAuth))", "");
|
|
2913
|
+
}
|
|
2914
|
+
body.push(
|
|
2915
|
+
"await registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)",
|
|
2916
|
+
"",
|
|
2917
|
+
buildCustomRouteModuleCode(customRoutes),
|
|
2918
|
+
"registerCustomRouteModules(app, __customRoutes)",
|
|
2919
|
+
"",
|
|
2920
|
+
buildQueueHandlerCode(queueHandlers),
|
|
2921
|
+
"registerTerminalHandlers(app)",
|
|
2922
|
+
"",
|
|
2923
|
+
buildWorkerExportCode(queueHandlers)
|
|
2717
2924
|
);
|
|
2718
|
-
return
|
|
2925
|
+
return [...importBlocks, "", ...body].join("\n");
|
|
2719
2926
|
}
|
|
2720
2927
|
|
|
2721
2928
|
// src/lib/init/prompts.ts
|
|
@@ -2729,8 +2936,8 @@ var authSchemaTemplate = loadTemplateString("auth/auth-schema.ts");
|
|
|
2729
2936
|
var protectedRouteTemplate = loadTemplateString("api/sample-protected.ts");
|
|
2730
2937
|
|
|
2731
2938
|
// src/lib/init/database.ts
|
|
2732
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as
|
|
2733
|
-
import { join as
|
|
2939
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2940
|
+
import { join as join15 } from "node:path";
|
|
2734
2941
|
var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
|
|
2735
2942
|
var dbSchemaExampleTemplate = loadTemplateString("database/db-schema-example.ts");
|
|
2736
2943
|
var dbSchemaIndexTemplate = loadTemplateString("database/db-schema-index.ts");
|
|
@@ -2740,7 +2947,7 @@ var dbSeedTemplate = loadTemplateString("database/db-seed.ts");
|
|
|
2740
2947
|
var packageTemplate = loadTemplateString("database/package.json");
|
|
2741
2948
|
function hasDatabaseSetup() {
|
|
2742
2949
|
const workspace = getWorkspace();
|
|
2743
|
-
return DRIZZLE_CONFIG_FILES.some((filename) => existsSync9(
|
|
2950
|
+
return DRIZZLE_CONFIG_FILES.some((filename) => existsSync9(join15(workspace, filename)));
|
|
2744
2951
|
}
|
|
2745
2952
|
|
|
2746
2953
|
// src/lib/init/scaffold.ts
|
|
@@ -2762,17 +2969,17 @@ function hasBucketSetup(config) {
|
|
|
2762
2969
|
|
|
2763
2970
|
// src/lib/init/types.ts
|
|
2764
2971
|
import { execSync as execSync2 } from "node:child_process";
|
|
2765
|
-
import { existsSync as existsSync12, readFileSync as
|
|
2766
|
-
import { dirname as dirname5, join as
|
|
2972
|
+
import { existsSync as existsSync12, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2973
|
+
import { dirname as dirname5, join as join18, resolve as resolve5 } from "node:path";
|
|
2767
2974
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2768
2975
|
|
|
2769
2976
|
// src/lib/deploy/backend.ts
|
|
2770
2977
|
import { existsSync as existsSync10 } from "node:fs";
|
|
2771
|
-
import { join as
|
|
2978
|
+
import { join as join16 } from "node:path";
|
|
2772
2979
|
function getCustomRoutesDirectory(projectPath, config) {
|
|
2773
2980
|
const customRoutes = config?.integrations?.customRoutes;
|
|
2774
2981
|
const customRoutesDir = typeof customRoutes === "object" && customRoutes.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
2775
|
-
return
|
|
2982
|
+
return join16(projectPath, customRoutesDir);
|
|
2776
2983
|
}
|
|
2777
2984
|
function hasLocalCustomRoutes(projectPath, config) {
|
|
2778
2985
|
const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
|
|
@@ -2817,8 +3024,8 @@ function validateIntegrationSecrets(config, envSecrets) {
|
|
|
2817
3024
|
|
|
2818
3025
|
// src/lib/init/tsconfig.ts
|
|
2819
3026
|
init_file_loader();
|
|
2820
|
-
import { existsSync as existsSync11, readFileSync as
|
|
2821
|
-
import { join as
|
|
3027
|
+
import { existsSync as existsSync11, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
3028
|
+
import { join as join17 } from "path";
|
|
2822
3029
|
function hasAllRequiredIncludes(config) {
|
|
2823
3030
|
if (!config.include) {
|
|
2824
3031
|
return false;
|
|
@@ -2891,7 +3098,7 @@ function addToIncludeArrayPreservingComments(content) {
|
|
|
2891
3098
|
}
|
|
2892
3099
|
async function ensureTsconfigIncludes(workspace) {
|
|
2893
3100
|
for (const filename of TSCONFIG_FILES) {
|
|
2894
|
-
const configPath =
|
|
3101
|
+
const configPath = join17(workspace, filename);
|
|
2895
3102
|
if (existsSync11(configPath)) {
|
|
2896
3103
|
try {
|
|
2897
3104
|
const config = await loadFile(configPath, {
|
|
@@ -2903,7 +3110,7 @@ async function ensureTsconfigIncludes(workspace) {
|
|
|
2903
3110
|
return filename;
|
|
2904
3111
|
}
|
|
2905
3112
|
try {
|
|
2906
|
-
const rawContent =
|
|
3113
|
+
const rawContent = readFileSync4(configPath, "utf8");
|
|
2907
3114
|
const updatedContent = addToIncludeArrayPreservingComments(rawContent);
|
|
2908
3115
|
if (updatedContent && updatedContent !== rawContent) {
|
|
2909
3116
|
writeFileSync5(configPath, updatedContent);
|
|
@@ -2943,14 +3150,14 @@ function getDepVersions() {
|
|
|
2943
3150
|
return { honoVersion: "^4.9.9", workersTypesVersion: "^4.20251011.0" };
|
|
2944
3151
|
}
|
|
2945
3152
|
try {
|
|
2946
|
-
const pkgPath =
|
|
3153
|
+
const pkgPath = resolve5(
|
|
2947
3154
|
dirname5(fileURLToPath2(import.meta.url)),
|
|
2948
3155
|
"..",
|
|
2949
3156
|
"..",
|
|
2950
3157
|
"..",
|
|
2951
3158
|
"package.json"
|
|
2952
3159
|
);
|
|
2953
|
-
const pkg = JSON.parse(
|
|
3160
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
2954
3161
|
return {
|
|
2955
3162
|
honoVersion: pkg.dependencies?.hono ?? "latest",
|
|
2956
3163
|
workersTypesVersion: pkg.devDependencies?.["@cloudflare/workers-types"] ?? "latest"
|
|
@@ -2960,8 +3167,8 @@ function getDepVersions() {
|
|
|
2960
3167
|
}
|
|
2961
3168
|
}
|
|
2962
3169
|
async function setupPlaycademyDependencies(workspace) {
|
|
2963
|
-
const playcademyDir =
|
|
2964
|
-
const playcademyPkgPath =
|
|
3170
|
+
const playcademyDir = join18(workspace, CLI_DIRECTORIES.WORKSPACE);
|
|
3171
|
+
const playcademyPkgPath = join18(playcademyDir, "package.json");
|
|
2965
3172
|
const { honoVersion, workersTypesVersion } = getDepVersions();
|
|
2966
3173
|
const playcademyPkg = {
|
|
2967
3174
|
private: true,
|
|
@@ -3057,14 +3264,14 @@ async function ensurePlaycademyTypes(options = {}) {
|
|
|
3057
3264
|
const bindingsStr = generateBindingsTypeString(features, queueBindings);
|
|
3058
3265
|
const secretsStr = await generateSecretsTypeString(workspace, verbose);
|
|
3059
3266
|
const hasAuth = Boolean(config.integrations?.auth);
|
|
3060
|
-
const hasAuthFile = existsSync12(
|
|
3267
|
+
const hasAuthFile = existsSync12(join18(workspace, "server/lib/auth.ts"));
|
|
3061
3268
|
const authVariablesString = generateAuthVariablesString(hasAuth, hasAuthFile);
|
|
3062
3269
|
let envContent = playcademyEnvTemplate.replace("{{BINDINGS}}", bindingsStr);
|
|
3063
3270
|
envContent = envContent.replace("{{SECRETS}}", secretsStr);
|
|
3064
3271
|
envContent = envContent.replace("{{AUTH_IMPORT}}", authVariablesString.authImport);
|
|
3065
3272
|
envContent = envContent.replace("{{VARIABLES}}", authVariablesString.variables);
|
|
3066
3273
|
envContent = envContent.replace("{{CONTEXT_VARS}}", authVariablesString.contextVars);
|
|
3067
|
-
const envPath =
|
|
3274
|
+
const envPath = join18(workspace, "playcademy-env.d.ts");
|
|
3068
3275
|
writeFileSync6(envPath, envContent);
|
|
3069
3276
|
if (verbose) {
|
|
3070
3277
|
logger.success(`Generated <playcademy-env.d.ts>`);
|
|
@@ -3099,12 +3306,12 @@ import { blue as blue2, cyan as cyan3, green as green3, magenta, red as red3, ye
|
|
|
3099
3306
|
|
|
3100
3307
|
// src/lib/init/engine/hooks.ts
|
|
3101
3308
|
import { existsSync as existsSync13, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
3102
|
-
import { rename, rm } from "fs/promises";
|
|
3103
|
-
import { dirname as dirname6, join as
|
|
3309
|
+
import { rename, rm as rm2 } from "fs/promises";
|
|
3310
|
+
import { dirname as dirname6, join as join19 } from "path";
|
|
3104
3311
|
import JSZip from "jszip";
|
|
3105
3312
|
async function godotAfterFetch({ destDir }) {
|
|
3106
|
-
const addonsDir =
|
|
3107
|
-
const targetDir =
|
|
3313
|
+
const addonsDir = join19(destDir, "addons");
|
|
3314
|
+
const targetDir = join19(addonsDir, "playcademy");
|
|
3108
3315
|
let zipData;
|
|
3109
3316
|
await runStep(
|
|
3110
3317
|
"Downloading Playcademy Addon for Godot",
|
|
@@ -3132,7 +3339,7 @@ async function godotAfterFetch({ destDir }) {
|
|
|
3132
3339
|
if (file.dir) {
|
|
3133
3340
|
return;
|
|
3134
3341
|
}
|
|
3135
|
-
const extractPath =
|
|
3342
|
+
const extractPath = join19(destDir, relativePath);
|
|
3136
3343
|
extractPromises.push(
|
|
3137
3344
|
file.async("nodebuffer").then((content) => {
|
|
3138
3345
|
const dir = dirname6(extractPath);
|
|
@@ -3144,14 +3351,14 @@ async function godotAfterFetch({ destDir }) {
|
|
|
3144
3351
|
);
|
|
3145
3352
|
});
|
|
3146
3353
|
await Promise.all(extractPromises);
|
|
3147
|
-
const extractedRoot =
|
|
3354
|
+
const extractedRoot = join19(destDir, "playcademy", "addons", "playcademy");
|
|
3148
3355
|
if (existsSync13(extractedRoot)) {
|
|
3149
3356
|
if (existsSync13(targetDir)) {
|
|
3150
|
-
await
|
|
3357
|
+
await rm2(targetDir, { recursive: true, force: true });
|
|
3151
3358
|
}
|
|
3152
3359
|
await rename(extractedRoot, targetDir);
|
|
3153
|
-
const extractedParent =
|
|
3154
|
-
await
|
|
3360
|
+
const extractedParent = join19(destDir, "playcademy");
|
|
3361
|
+
await rm2(extractedParent, { recursive: true, force: true });
|
|
3155
3362
|
}
|
|
3156
3363
|
},
|
|
3157
3364
|
"Playcademy Addon extracted",
|
|
@@ -3363,9 +3570,9 @@ async function startDevServer(options) {
|
|
|
3363
3570
|
return { server: mf, port };
|
|
3364
3571
|
}
|
|
3365
3572
|
async function ensureDatabaseDirectory() {
|
|
3366
|
-
const dbDir =
|
|
3573
|
+
const dbDir = join20(getWorkspace(), CLI_DIRECTORIES.DATABASE);
|
|
3367
3574
|
try {
|
|
3368
|
-
await
|
|
3575
|
+
await mkdir3(dbDir, { recursive: true });
|
|
3369
3576
|
} catch (error) {
|
|
3370
3577
|
throw new Error(`Failed to create database directory: ${getErrorMessage(error)}`, {
|
|
3371
3578
|
cause: error
|
|
@@ -3374,9 +3581,9 @@ async function ensureDatabaseDirectory() {
|
|
|
3374
3581
|
return dbDir;
|
|
3375
3582
|
}
|
|
3376
3583
|
async function ensureKvDirectory() {
|
|
3377
|
-
const kvDir =
|
|
3584
|
+
const kvDir = join20(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
3378
3585
|
try {
|
|
3379
|
-
await
|
|
3586
|
+
await mkdir3(kvDir, { recursive: true });
|
|
3380
3587
|
} catch (error) {
|
|
3381
3588
|
throw new Error(`Failed to create KV directory: ${getErrorMessage(error)}`, {
|
|
3382
3589
|
cause: error
|
|
@@ -3385,9 +3592,9 @@ async function ensureKvDirectory() {
|
|
|
3385
3592
|
return kvDir;
|
|
3386
3593
|
}
|
|
3387
3594
|
async function ensureBucketDirectory() {
|
|
3388
|
-
const bucketDir =
|
|
3595
|
+
const bucketDir = join20(getWorkspace(), CLI_DIRECTORIES.BUCKET);
|
|
3389
3596
|
try {
|
|
3390
|
-
await
|
|
3597
|
+
await mkdir3(bucketDir, { recursive: true });
|
|
3391
3598
|
} catch (error) {
|
|
3392
3599
|
throw new Error(`Failed to create bucket directory: ${getErrorMessage(error)}`, {
|
|
3393
3600
|
cause: error
|
|
@@ -3410,7 +3617,7 @@ async function writeBackendServerInfo(port) {
|
|
|
3410
3617
|
}
|
|
3411
3618
|
|
|
3412
3619
|
// src/lib/dev/reload.ts
|
|
3413
|
-
import { join as
|
|
3620
|
+
import { join as join21, relative as relative4 } from "path";
|
|
3414
3621
|
import chokidar from "chokidar";
|
|
3415
3622
|
import { bold as bold6, cyan as cyan5, dim as dim4, green as green4 } from "colorette";
|
|
3416
3623
|
function formatTime() {
|
|
@@ -3427,9 +3634,9 @@ function startHotReload(onReload, options = {}) {
|
|
|
3427
3634
|
const customRoutesConfig = options.config?.integrations?.customRoutes;
|
|
3428
3635
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
3429
3636
|
const watchPaths = [
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3637
|
+
join21(workspace, customRoutesDir),
|
|
3638
|
+
join21(workspace, "playcademy.config.js"),
|
|
3639
|
+
join21(workspace, "playcademy.config.json")
|
|
3433
3640
|
];
|
|
3434
3641
|
const watcher = chokidar.watch(watchPaths, {
|
|
3435
3642
|
persistent: true,
|
|
@@ -3441,7 +3648,7 @@ function startHotReload(onReload, options = {}) {
|
|
|
3441
3648
|
});
|
|
3442
3649
|
const logSuccess = options.onSuccess || ((changedPath, eventType) => {
|
|
3443
3650
|
if (changedPath) {
|
|
3444
|
-
const relativePath =
|
|
3651
|
+
const relativePath = relative4(workspace, changedPath);
|
|
3445
3652
|
const timestamp = dim4(formatTime());
|
|
3446
3653
|
const brand = bold6(cyan5("[playcademy]"));
|
|
3447
3654
|
const event = eventType === "changed" ? green4("reload") : green4(eventType || "reload");
|