playcademy 0.12.6 → 0.12.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db.js +3 -0
- package/dist/index.js +129 -84
- package/dist/utils.d.ts +42 -1
- package/dist/utils.js +73 -36
- package/package.json +1 -1
package/dist/db.js
CHANGED
|
@@ -629,6 +629,9 @@ import { dirname, resolve } from "path";
|
|
|
629
629
|
import { fileURLToPath } from "url";
|
|
630
630
|
var currentDir = dirname(fileURLToPath(import.meta.url));
|
|
631
631
|
|
|
632
|
+
// src/lib/core/import.ts
|
|
633
|
+
import * as esbuild from "esbuild";
|
|
634
|
+
|
|
632
635
|
// src/lib/db/reset.ts
|
|
633
636
|
function resetDatabase() {
|
|
634
637
|
const dbPath = getDevDbPath();
|
package/dist/index.js
CHANGED
|
@@ -3610,6 +3610,46 @@ var init_errors = __esm({
|
|
|
3610
3610
|
}
|
|
3611
3611
|
});
|
|
3612
3612
|
|
|
3613
|
+
// src/lib/core/import.ts
|
|
3614
|
+
import { mkdtempSync, rmSync } from "fs";
|
|
3615
|
+
import { tmpdir } from "os";
|
|
3616
|
+
import { join as join3 } from "path";
|
|
3617
|
+
import { pathToFileURL } from "url";
|
|
3618
|
+
import * as esbuild from "esbuild";
|
|
3619
|
+
async function importTypescriptFile(filePath, bundleOptions) {
|
|
3620
|
+
const tempDir = mkdtempSync(join3(tmpdir(), "playcademy-import-"));
|
|
3621
|
+
const outFile = join3(tempDir, "bundle.mjs");
|
|
3622
|
+
try {
|
|
3623
|
+
await esbuild.build({
|
|
3624
|
+
entryPoints: [filePath],
|
|
3625
|
+
outfile: outFile,
|
|
3626
|
+
bundle: true,
|
|
3627
|
+
platform: "node",
|
|
3628
|
+
format: "esm",
|
|
3629
|
+
target: "node20",
|
|
3630
|
+
sourcemap: false,
|
|
3631
|
+
minify: false,
|
|
3632
|
+
...bundleOptions
|
|
3633
|
+
});
|
|
3634
|
+
const module = await import(pathToFileURL(outFile).href);
|
|
3635
|
+
return module;
|
|
3636
|
+
} finally {
|
|
3637
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
async function importTypescriptDefault(filePath, bundleOptions) {
|
|
3641
|
+
const module = await importTypescriptFile(filePath, bundleOptions);
|
|
3642
|
+
if (module && typeof module === "object" && "default" in module) {
|
|
3643
|
+
return module.default;
|
|
3644
|
+
}
|
|
3645
|
+
return module;
|
|
3646
|
+
}
|
|
3647
|
+
var init_import = __esm({
|
|
3648
|
+
"src/lib/core/import.ts"() {
|
|
3649
|
+
"use strict";
|
|
3650
|
+
}
|
|
3651
|
+
});
|
|
3652
|
+
|
|
3613
3653
|
// src/lib/core/index.ts
|
|
3614
3654
|
var core_exports = {};
|
|
3615
3655
|
__export(core_exports, {
|
|
@@ -3628,6 +3668,8 @@ __export(core_exports, {
|
|
|
3628
3668
|
getWebBaseUrl: () => getWebBaseUrl,
|
|
3629
3669
|
getWorkspace: () => getWorkspace,
|
|
3630
3670
|
hasPackageJson: () => hasPackageJson,
|
|
3671
|
+
importTypescriptDefault: () => importTypescriptDefault,
|
|
3672
|
+
importTypescriptFile: () => importTypescriptFile,
|
|
3631
3673
|
logAndExit: () => logAndExit,
|
|
3632
3674
|
logger: () => logger,
|
|
3633
3675
|
normalizeEnvironment: () => normalizeEnvironment,
|
|
@@ -3642,6 +3684,7 @@ var init_core = __esm({
|
|
|
3642
3684
|
init_context();
|
|
3643
3685
|
init_errors();
|
|
3644
3686
|
init_game();
|
|
3687
|
+
init_import();
|
|
3645
3688
|
init_logger();
|
|
3646
3689
|
}
|
|
3647
3690
|
});
|
|
@@ -4476,7 +4519,7 @@ init_core();
|
|
|
4476
4519
|
// src/lib/db/path.ts
|
|
4477
4520
|
init_constants2();
|
|
4478
4521
|
import { copyFileSync, existsSync as existsSync5, mkdirSync, readdirSync as readdirSync2, unlinkSync } from "fs";
|
|
4479
|
-
import { join as
|
|
4522
|
+
import { join as join4 } from "path";
|
|
4480
4523
|
import Database from "better-sqlite3";
|
|
4481
4524
|
var DB_DIRECTORY = CLI_DIRECTORIES.DATABASE;
|
|
4482
4525
|
var INITIAL_DB_NAME = CLI_FILES.INITIAL_DATABASE;
|
|
@@ -4490,11 +4533,11 @@ var createEmptyDatabase = (path2) => {
|
|
|
4490
4533
|
db.close();
|
|
4491
4534
|
};
|
|
4492
4535
|
var findMiniflareDatabase = (dbDir) => {
|
|
4493
|
-
const miniflareDir =
|
|
4536
|
+
const miniflareDir = join4(dbDir, "miniflare-D1DatabaseObject");
|
|
4494
4537
|
if (!existsSync5(miniflareDir)) return null;
|
|
4495
4538
|
const sqliteFiles = readdirSync2(miniflareDir).filter((file) => file.endsWith(".sqlite"));
|
|
4496
4539
|
if (sqliteFiles.length === 0) return null;
|
|
4497
|
-
return
|
|
4540
|
+
return join4(miniflareDir, sqliteFiles[0]);
|
|
4498
4541
|
};
|
|
4499
4542
|
var migrateInitialDbToTarget = (initialPath, targetPath) => {
|
|
4500
4543
|
if (!existsSync5(initialPath)) return;
|
|
@@ -4502,7 +4545,7 @@ var migrateInitialDbToTarget = (initialPath, targetPath) => {
|
|
|
4502
4545
|
unlinkSync(initialPath);
|
|
4503
4546
|
};
|
|
4504
4547
|
function getDevDbPath() {
|
|
4505
|
-
const initialDbPath =
|
|
4548
|
+
const initialDbPath = join4(DB_DIRECTORY, INITIAL_DB_NAME);
|
|
4506
4549
|
ensureDirectoryExists(DB_DIRECTORY);
|
|
4507
4550
|
const miniflareDbPath = findMiniflareDatabase(DB_DIRECTORY);
|
|
4508
4551
|
if (miniflareDbPath) {
|
|
@@ -4562,7 +4605,7 @@ init_src();
|
|
|
4562
4605
|
init_constants2();
|
|
4563
4606
|
init_core();
|
|
4564
4607
|
import { existsSync as existsSync10 } from "node:fs";
|
|
4565
|
-
import { join as
|
|
4608
|
+
import { join as join10 } from "node:path";
|
|
4566
4609
|
|
|
4567
4610
|
// src/lib/init/database.ts
|
|
4568
4611
|
init_log();
|
|
@@ -4572,7 +4615,7 @@ init_core();
|
|
|
4572
4615
|
init_logger();
|
|
4573
4616
|
init_loader2();
|
|
4574
4617
|
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4575
|
-
import { join as
|
|
4618
|
+
import { join as join5 } from "path";
|
|
4576
4619
|
var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
|
|
4577
4620
|
var dbSchemaUsersTemplate = loadTemplateString("database/db-schema-users.ts");
|
|
4578
4621
|
var dbSchemaScoresTemplate = loadTemplateString("database/db-schema-scores.ts");
|
|
@@ -4586,7 +4629,7 @@ function normalizeGitignoreEntry(entry) {
|
|
|
4586
4629
|
return entry.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
4587
4630
|
}
|
|
4588
4631
|
function ensureGitignoreEntries(workspace) {
|
|
4589
|
-
const gitignorePath =
|
|
4632
|
+
const gitignorePath = join5(workspace, ".gitignore");
|
|
4590
4633
|
if (!existsSync6(gitignorePath)) {
|
|
4591
4634
|
writeFileSync2(gitignorePath, rootGitignoreTemplate);
|
|
4592
4635
|
return;
|
|
@@ -4616,31 +4659,31 @@ async function scaffoldDatabaseSetup(options) {
|
|
|
4616
4659
|
await runStep(
|
|
4617
4660
|
"Configuring database...",
|
|
4618
4661
|
async () => {
|
|
4619
|
-
const dbDir =
|
|
4620
|
-
const schemaDir =
|
|
4662
|
+
const dbDir = join5(workspace, "db");
|
|
4663
|
+
const schemaDir = join5(dbDir, "schema");
|
|
4621
4664
|
if (!existsSync6(dbDir)) {
|
|
4622
4665
|
mkdirSync2(dbDir, { recursive: true });
|
|
4623
4666
|
}
|
|
4624
4667
|
if (!existsSync6(schemaDir)) {
|
|
4625
4668
|
mkdirSync2(schemaDir, { recursive: true });
|
|
4626
4669
|
}
|
|
4627
|
-
const usersSchemaPath =
|
|
4670
|
+
const usersSchemaPath = join5(schemaDir, "users.ts");
|
|
4628
4671
|
writeFileSync2(usersSchemaPath, dbSchemaUsersTemplate);
|
|
4629
|
-
const scoresSchemaPath =
|
|
4672
|
+
const scoresSchemaPath = join5(schemaDir, "scores.ts");
|
|
4630
4673
|
writeFileSync2(scoresSchemaPath, dbSchemaScoresTemplate);
|
|
4631
|
-
const schemaIndexPath =
|
|
4674
|
+
const schemaIndexPath = join5(schemaDir, "index.ts");
|
|
4632
4675
|
writeFileSync2(schemaIndexPath, dbSchemaIndexTemplate);
|
|
4633
|
-
const dbIndexPath =
|
|
4676
|
+
const dbIndexPath = join5(dbDir, "index.ts");
|
|
4634
4677
|
writeFileSync2(dbIndexPath, dbIndexTemplate);
|
|
4635
|
-
const dbTypesPath =
|
|
4678
|
+
const dbTypesPath = join5(dbDir, "types.ts");
|
|
4636
4679
|
writeFileSync2(dbTypesPath, dbTypesTemplate);
|
|
4637
|
-
const drizzleConfigPath =
|
|
4680
|
+
const drizzleConfigPath = join5(workspace, "drizzle.config.ts");
|
|
4638
4681
|
writeFileSync2(drizzleConfigPath, drizzleConfigTemplate);
|
|
4639
|
-
const playcademyDir =
|
|
4682
|
+
const playcademyDir = join5(workspace, CLI_DIRECTORIES.WORKSPACE);
|
|
4640
4683
|
if (!existsSync6(playcademyDir)) {
|
|
4641
4684
|
mkdirSync2(playcademyDir, { recursive: true });
|
|
4642
4685
|
}
|
|
4643
|
-
const playcademyGitignorePath =
|
|
4686
|
+
const playcademyGitignorePath = join5(playcademyDir, ".gitignore");
|
|
4644
4687
|
writeFileSync2(playcademyGitignorePath, playcademyGitignoreTemplate);
|
|
4645
4688
|
ensureGitignoreEntries(workspace);
|
|
4646
4689
|
packagesUpdated = await setupPackageJson(workspace, options.gameName);
|
|
@@ -4650,7 +4693,7 @@ async function scaffoldDatabaseSetup(options) {
|
|
|
4650
4693
|
return packagesUpdated;
|
|
4651
4694
|
}
|
|
4652
4695
|
async function setupPackageJson(workspace, gameName) {
|
|
4653
|
-
const pkgPath =
|
|
4696
|
+
const pkgPath = join5(workspace, "package.json");
|
|
4654
4697
|
const dbDeps = {
|
|
4655
4698
|
"drizzle-orm": "^0.42.0",
|
|
4656
4699
|
"better-sqlite3": "^12.0.0"
|
|
@@ -4690,8 +4733,8 @@ async function setupPackageJson(workspace, gameName) {
|
|
|
4690
4733
|
}
|
|
4691
4734
|
function hasDatabaseSetup() {
|
|
4692
4735
|
const workspace = getWorkspace();
|
|
4693
|
-
const drizzleConfigPath =
|
|
4694
|
-
const drizzleConfigJsPath =
|
|
4736
|
+
const drizzleConfigPath = join5(workspace, "drizzle.config.ts");
|
|
4737
|
+
const drizzleConfigJsPath = join5(workspace, "drizzle.config.js");
|
|
4695
4738
|
return existsSync6(drizzleConfigPath) || existsSync6(drizzleConfigJsPath);
|
|
4696
4739
|
}
|
|
4697
4740
|
|
|
@@ -4825,7 +4868,7 @@ var integrationChangeDetectors = {
|
|
|
4825
4868
|
|
|
4826
4869
|
// src/lib/deploy/bundle.ts
|
|
4827
4870
|
import { existsSync as existsSync7 } from "fs";
|
|
4828
|
-
import { join as
|
|
4871
|
+
import { join as join7 } from "path";
|
|
4829
4872
|
|
|
4830
4873
|
// ../edge-play/src/entry.ts
|
|
4831
4874
|
var entry_default = "/**\n * Game Backend Entry Point\n *\n * This file is the main entry point for deployed game backends.\n * It creates a Hono app and registers all enabled integration routes.\n *\n * Bundled with esbuild and deployed to Cloudflare Workers (or AWS Lambda).\n * Config is injected at build time via esbuild's `define` option.\n */\n\nimport { Hono } from 'hono'\nimport { cors } from 'hono/cors'\n\nimport { PlaycademyClient } from '@playcademy/sdk/server'\n\nimport { ENV_VARS } from './constants'\nimport { registerBuiltinRoutes } from './register-routes'\n\nimport type { PlaycademyConfig } from '@playcademy/sdk/server'\nimport type { HonoEnv } from './types'\n\n/**\n * Config injected at build time by esbuild\n *\n * The `declare const` tells TypeScript \"this exists at runtime, trust me.\"\n * During bundling, esbuild's `define` option does literal text replacement:\n *\n * Example bundling:\n * Source: if (PLAYCADEMY_CONFIG.integrations.timeback) { ... }\n * Define: { 'PLAYCADEMY_CONFIG': JSON.stringify({ integrations: { timeback: {...} } }) }\n * Output: if ({\"integrations\":{\"timeback\":{...}}}.integrations.timeback) { ... }\n *\n * This enables tree-shaking: if timeback is not configured, those code paths are removed.\n * The bundled Worker only includes the routes that are actually enabled.\n */\ndeclare const PLAYCADEMY_CONFIG: PlaycademyConfig & {\n customRoutes?: Array<{ path: string; file: string }>\n}\n\n// XXX: Polyfill process global for SDK compatibility\n// SDK code may reference process.env without importing it\n// @ts-expect-error - Adding global for Worker environment\nglobalThis.process = {\n env: {}, // Populated per-request from Worker env bindings\n cwd: () => '/',\n}\n\nconst app = new Hono<HonoEnv>()\n\n// TODO: Harden CORS in production - restrict to trusted origins:\n// - Game's assetBundleBase (for hosted games)\n// - Game's externalUrl (for external games)\n// - Platform frontend domains (hub.playcademy.com, hub.dev.playcademy.net)\n// This would require passing game metadata through env bindings during deployment\napp.use(\n '*',\n cors({\n origin: '*', // Permissive for now\n allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Authorization'],\n }),\n)\n\nlet sdkPromise: Promise<PlaycademyClient> | null = null\n\napp.use('*', async (c, next) => {\n // Populate process.env from Worker bindings for SDK compatibility\n globalThis.process.env = {\n [ENV_VARS.PLAYCADEMY_API_KEY]: c.env.PLAYCADEMY_API_KEY,\n [ENV_VARS.GAME_ID]: c.env.GAME_ID,\n [ENV_VARS.PLAYCADEMY_BASE_URL]: c.env.PLAYCADEMY_BASE_URL,\n }\n\n // Set config for all routes\n c.set('config', PLAYCADEMY_CONFIG)\n c.set('customRoutes', PLAYCADEMY_CONFIG.customRoutes || [])\n\n await next()\n})\n\n// Initialize SDK lazily on first request\napp.use('*', async (c, next) => {\n if (!sdkPromise) {\n sdkPromise = PlaycademyClient.init({\n apiKey: c.env[ENV_VARS.PLAYCADEMY_API_KEY],\n gameId: c.env[ENV_VARS.GAME_ID],\n baseUrl: c.env[ENV_VARS.PLAYCADEMY_BASE_URL],\n config: PLAYCADEMY_CONFIG,\n })\n }\n\n c.set('sdk', await sdkPromise)\n await next()\n})\n\n/**\n * Register built-in integration routes based on enabled integrations\n *\n * This function conditionally imports and registers routes like:\n * - POST /api/integrations/timeback/end-activity (if timeback enabled)\n * - GET /api/health (always included)\n *\n * Uses dynamic imports for tree-shaking: if an integration is not enabled,\n * its route code is completely removed from the bundle.\n */\nawait registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)\n\nexport default app\n";
|
|
@@ -5051,8 +5094,8 @@ init_constants2();
|
|
|
5051
5094
|
function textLoaderPlugin() {
|
|
5052
5095
|
return {
|
|
5053
5096
|
name: "text-loader",
|
|
5054
|
-
setup(
|
|
5055
|
-
|
|
5097
|
+
setup(build2) {
|
|
5098
|
+
build2.onLoad({ filter: /edge-play\/src\/entry\.ts$/ }, async (args) => {
|
|
5056
5099
|
const fs2 = await import("fs/promises");
|
|
5057
5100
|
const text5 = await fs2.readFile(args.path, "utf8");
|
|
5058
5101
|
return {
|
|
@@ -5060,7 +5103,7 @@ function textLoaderPlugin() {
|
|
|
5060
5103
|
loader: "js"
|
|
5061
5104
|
};
|
|
5062
5105
|
});
|
|
5063
|
-
|
|
5106
|
+
build2.onLoad({ filter: /edge-play\/src\/routes\/root\.html$/ }, async (args) => {
|
|
5064
5107
|
const fs2 = await import("fs/promises");
|
|
5065
5108
|
const text5 = await fs2.readFile(args.path, "utf8");
|
|
5066
5109
|
return {
|
|
@@ -5068,7 +5111,7 @@ function textLoaderPlugin() {
|
|
|
5068
5111
|
loader: "js"
|
|
5069
5112
|
};
|
|
5070
5113
|
});
|
|
5071
|
-
|
|
5114
|
+
build2.onLoad({ filter: /templates\/sample-route\.ts$/ }, async (args) => {
|
|
5072
5115
|
const fs2 = await import("fs/promises");
|
|
5073
5116
|
const text5 = await fs2.readFile(args.path, "utf8");
|
|
5074
5117
|
return {
|
|
@@ -5087,8 +5130,8 @@ init_core();
|
|
|
5087
5130
|
init_file_loader();
|
|
5088
5131
|
init_core();
|
|
5089
5132
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
5090
|
-
import { tmpdir } from "os";
|
|
5091
|
-
import { join as
|
|
5133
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
5134
|
+
import { join as join6, relative } from "path";
|
|
5092
5135
|
|
|
5093
5136
|
// src/lib/deploy/hash.ts
|
|
5094
5137
|
init_file_loader();
|
|
@@ -5136,7 +5179,7 @@ async function discoverRoutes(apiDir) {
|
|
|
5136
5179
|
const routes = await Promise.all(
|
|
5137
5180
|
files.map(async (file) => {
|
|
5138
5181
|
const routePath = filePathToRoutePath(file);
|
|
5139
|
-
const absolutePath =
|
|
5182
|
+
const absolutePath = join6(apiDir, file);
|
|
5140
5183
|
const relativePath = relative(getWorkspace(), absolutePath);
|
|
5141
5184
|
const methods = await detectExportedMethods(absolutePath);
|
|
5142
5185
|
return {
|
|
@@ -5181,8 +5224,8 @@ async function transpileRoute(filePath) {
|
|
|
5181
5224
|
if (isBun() || !filePath.endsWith(".ts")) {
|
|
5182
5225
|
return filePath;
|
|
5183
5226
|
}
|
|
5184
|
-
const
|
|
5185
|
-
const result = await
|
|
5227
|
+
const esbuild2 = await import("esbuild");
|
|
5228
|
+
const result = await esbuild2.build({
|
|
5186
5229
|
entryPoints: [filePath],
|
|
5187
5230
|
write: false,
|
|
5188
5231
|
format: "esm",
|
|
@@ -5196,10 +5239,10 @@ async function transpileRoute(filePath) {
|
|
|
5196
5239
|
if (!result.outputFiles?.[0]) {
|
|
5197
5240
|
throw new Error("Transpilation failed: no output");
|
|
5198
5241
|
}
|
|
5199
|
-
const tempDir =
|
|
5242
|
+
const tempDir = join6(tmpdir2(), "playcademy-dev");
|
|
5200
5243
|
await mkdir2(tempDir, { recursive: true });
|
|
5201
5244
|
const hash = hashContent(filePath).slice(0, 12);
|
|
5202
|
-
const jsPath =
|
|
5245
|
+
const jsPath = join6(tempDir, `${hash}.mjs`);
|
|
5203
5246
|
await writeFile2(jsPath, result.outputFiles[0].text);
|
|
5204
5247
|
return jsPath;
|
|
5205
5248
|
}
|
|
@@ -5227,7 +5270,7 @@ async function discoverCustomRoutes(config) {
|
|
|
5227
5270
|
const workspace = getWorkspace();
|
|
5228
5271
|
const customRoutesConfig = config.integrations?.customRoutes;
|
|
5229
5272
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
5230
|
-
const customRoutes = await discoverRoutes(
|
|
5273
|
+
const customRoutes = await discoverRoutes(join7(workspace, customRoutesDir));
|
|
5231
5274
|
const customRouteData = customRoutes.map((r) => ({
|
|
5232
5275
|
path: r.path,
|
|
5233
5276
|
file: r.file,
|
|
@@ -5239,15 +5282,15 @@ async function discoverCustomRoutes(config) {
|
|
|
5239
5282
|
function resolveEmbeddedSourcePaths() {
|
|
5240
5283
|
const workspace = getWorkspace();
|
|
5241
5284
|
const distDir = new URL(".", import.meta.url).pathname;
|
|
5242
|
-
const embeddedEdgeSrc =
|
|
5285
|
+
const embeddedEdgeSrc = join7(distDir, "edge-play", "src");
|
|
5243
5286
|
const isBuiltPackage = existsSync7(embeddedEdgeSrc);
|
|
5244
5287
|
const monorepoRoot = getMonorepoRoot();
|
|
5245
|
-
const monorepoEdgeSrc =
|
|
5288
|
+
const monorepoEdgeSrc = join7(monorepoRoot, "packages/edge-play/src");
|
|
5246
5289
|
const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
|
|
5247
|
-
const cliPackageRoot = isBuiltPackage ?
|
|
5248
|
-
const cliNodeModules = isBuiltPackage ?
|
|
5249
|
-
const workspaceNodeModules =
|
|
5250
|
-
const constantsEntry = isBuiltPackage ?
|
|
5290
|
+
const cliPackageRoot = isBuiltPackage ? join7(distDir, "../../..") : join7(monorepoRoot, "packages/cli");
|
|
5291
|
+
const cliNodeModules = isBuiltPackage ? join7(cliPackageRoot, "node_modules") : monorepoRoot;
|
|
5292
|
+
const workspaceNodeModules = join7(workspace, "node_modules");
|
|
5293
|
+
const constantsEntry = isBuiltPackage ? join7(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join7(monorepoRoot, "packages", "constants", "src", "index.ts");
|
|
5251
5294
|
return {
|
|
5252
5295
|
isBuiltPackage,
|
|
5253
5296
|
edgePlaySrc,
|
|
@@ -5307,16 +5350,16 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
|
|
|
5307
5350
|
// │ Example: import * as route from '@game-api/hello.ts' │
|
|
5308
5351
|
// │ Resolves to: /user-project/server/api/hello.ts │
|
|
5309
5352
|
// └─────────────────────────────────────────────────────────────────┘
|
|
5310
|
-
"@game-api":
|
|
5353
|
+
"@game-api": join7(workspace, customRoutesDir),
|
|
5311
5354
|
// ┌─ Node.js polyfills for Cloudflare Workers ──────────────────────┐
|
|
5312
5355
|
// │ Workers don't have fs, path, os, etc. Redirect to polyfills │
|
|
5313
5356
|
// │ that throw helpful errors if user code tries to use them. │
|
|
5314
5357
|
// └─────────────────────────────────────────────────────────────────┘
|
|
5315
|
-
fs:
|
|
5316
|
-
"fs/promises":
|
|
5317
|
-
path:
|
|
5318
|
-
os:
|
|
5319
|
-
process:
|
|
5358
|
+
fs: join7(edgePlaySrc, "polyfills.js"),
|
|
5359
|
+
"fs/promises": join7(edgePlaySrc, "polyfills.js"),
|
|
5360
|
+
path: join7(edgePlaySrc, "polyfills.js"),
|
|
5361
|
+
os: join7(edgePlaySrc, "polyfills.js"),
|
|
5362
|
+
process: join7(edgePlaySrc, "polyfills.js")
|
|
5320
5363
|
},
|
|
5321
5364
|
// ──── Build Plugins ────
|
|
5322
5365
|
plugins: [textLoaderPlugin()],
|
|
@@ -5327,7 +5370,7 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
|
|
|
5327
5370
|
};
|
|
5328
5371
|
}
|
|
5329
5372
|
async function bundleBackend(config, options = {}) {
|
|
5330
|
-
const
|
|
5373
|
+
const esbuild2 = await import("esbuild");
|
|
5331
5374
|
const { customRouteData, customRoutesDir } = await discoverCustomRoutes(config);
|
|
5332
5375
|
const bundleConfig = {
|
|
5333
5376
|
...config,
|
|
@@ -5342,7 +5385,7 @@ async function bundleBackend(config, options = {}) {
|
|
|
5342
5385
|
customRoutesDir,
|
|
5343
5386
|
options
|
|
5344
5387
|
);
|
|
5345
|
-
const result = await
|
|
5388
|
+
const result = await esbuild2.build(buildConfig);
|
|
5346
5389
|
if (!result.outputFiles?.[0]) {
|
|
5347
5390
|
throw new Error("Backend bundling failed: no output");
|
|
5348
5391
|
}
|
|
@@ -5382,7 +5425,7 @@ function generateEntryCode(customRoutes, customRoutesDir) {
|
|
|
5382
5425
|
init_core();
|
|
5383
5426
|
import { existsSync as existsSync9 } from "fs";
|
|
5384
5427
|
import { createRequire } from "module";
|
|
5385
|
-
import { join as
|
|
5428
|
+
import { join as join9 } from "path";
|
|
5386
5429
|
|
|
5387
5430
|
// src/lib/init/prompts.ts
|
|
5388
5431
|
init_constants3();
|
|
@@ -5395,7 +5438,7 @@ init_src();
|
|
|
5395
5438
|
init_core();
|
|
5396
5439
|
init_loader2();
|
|
5397
5440
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5398
|
-
import { join as
|
|
5441
|
+
import { join as join8, resolve as resolve6 } from "path";
|
|
5399
5442
|
var sampleRouteTemplate = loadTemplateString("api/sample-route.ts");
|
|
5400
5443
|
var sampleRouteWithDbTemplate = loadTemplateString("api/sample-route-with-db.ts");
|
|
5401
5444
|
async function scaffoldApiDirectory(apiDirectory, sampleRouteTemplate2) {
|
|
@@ -5405,7 +5448,7 @@ async function scaffoldApiDirectory(apiDirectory, sampleRouteTemplate2) {
|
|
|
5405
5448
|
async () => {
|
|
5406
5449
|
if (!existsSync8(apiPath)) {
|
|
5407
5450
|
mkdirSync3(apiPath, { recursive: true });
|
|
5408
|
-
writeFileSync3(
|
|
5451
|
+
writeFileSync3(join8(apiPath, "hello.ts"), sampleRouteTemplate2, "utf-8");
|
|
5409
5452
|
}
|
|
5410
5453
|
},
|
|
5411
5454
|
"API directory scaffolded"
|
|
@@ -5637,7 +5680,7 @@ async function getSchemaInfo(previousSchemaSnapshot) {
|
|
|
5637
5680
|
if (!hasDatabaseSetup()) {
|
|
5638
5681
|
return null;
|
|
5639
5682
|
}
|
|
5640
|
-
const schemaPath =
|
|
5683
|
+
const schemaPath = join9(workspace, "db/schema/index.ts");
|
|
5641
5684
|
if (!existsSync9(schemaPath)) {
|
|
5642
5685
|
return null;
|
|
5643
5686
|
}
|
|
@@ -5645,8 +5688,7 @@ async function getSchemaInfo(previousSchemaSnapshot) {
|
|
|
5645
5688
|
const require2 = createRequire(import.meta.url);
|
|
5646
5689
|
const drizzleKitApi = require2("drizzle-kit/api");
|
|
5647
5690
|
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = drizzleKitApi;
|
|
5648
|
-
const
|
|
5649
|
-
const currentSchema = schemaModule.default || schemaModule;
|
|
5691
|
+
const currentSchema = await importTypescriptDefault(schemaPath);
|
|
5650
5692
|
const nextJson = await generateSQLiteDrizzleJson(currentSchema);
|
|
5651
5693
|
const prevJson = previousSchemaSnapshot ? previousSchemaSnapshot : await generateSQLiteDrizzleJson({});
|
|
5652
5694
|
const migrationStatements = await generateSQLiteMigration(prevJson, nextJson);
|
|
@@ -5688,7 +5730,7 @@ var CUSTOM_ROUTES_EXTENSIONS_WITH_DOT = [".ts", ".js", ".mjs", ".cjs"];
|
|
|
5688
5730
|
function getCustomRoutesDirectory(projectPath, config) {
|
|
5689
5731
|
const customRoutes = config?.integrations?.customRoutes;
|
|
5690
5732
|
const customRoutesDir = typeof customRoutes === "object" && customRoutes.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
5691
|
-
return
|
|
5733
|
+
return join10(projectPath, customRoutesDir);
|
|
5692
5734
|
}
|
|
5693
5735
|
function hasLocalCustomRoutes(projectPath, config) {
|
|
5694
5736
|
const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
|
|
@@ -5700,7 +5742,7 @@ async function getCustomRoutesHash(projectPath, config) {
|
|
|
5700
5742
|
}
|
|
5701
5743
|
async function getCustomRoutesSize(projectPath, config) {
|
|
5702
5744
|
const { stat: stat3, readdir } = await import("node:fs/promises");
|
|
5703
|
-
const { join:
|
|
5745
|
+
const { join: join20 } = await import("node:path");
|
|
5704
5746
|
const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
|
|
5705
5747
|
if (!existsSync10(customRoutesDir)) {
|
|
5706
5748
|
return null;
|
|
@@ -5709,7 +5751,7 @@ async function getCustomRoutesSize(projectPath, config) {
|
|
|
5709
5751
|
async function calculateDirSize(dir) {
|
|
5710
5752
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
5711
5753
|
for (const entry of entries) {
|
|
5712
|
-
const fullPath =
|
|
5754
|
+
const fullPath = join20(dir, entry.name);
|
|
5713
5755
|
if (entry.isDirectory()) {
|
|
5714
5756
|
await calculateDirSize(fullPath);
|
|
5715
5757
|
} else if (entry.isFile()) {
|
|
@@ -5932,14 +5974,14 @@ function formatDelta(bytes) {
|
|
|
5932
5974
|
return `${arrow} ${value.toFixed(2)} ${unit}`;
|
|
5933
5975
|
}
|
|
5934
5976
|
function displayDeploymentDiff(options) {
|
|
5935
|
-
const { diff, noChanges, build, backend, integrations } = options;
|
|
5977
|
+
const { diff, noChanges, build: build2, backend, integrations } = options;
|
|
5936
5978
|
if (noChanges) {
|
|
5937
5979
|
logger.remark("No changes detected");
|
|
5938
5980
|
logger.newLine();
|
|
5939
5981
|
return;
|
|
5940
5982
|
}
|
|
5941
5983
|
const hasConfigChanges = Object.keys(diff).length > 0;
|
|
5942
|
-
const buildChanged =
|
|
5984
|
+
const buildChanged = build2?.changed === true;
|
|
5943
5985
|
const backendChanged = backend?.changed === true;
|
|
5944
5986
|
const customRoutesChanged = backend?.customRoutesChanged === true;
|
|
5945
5987
|
const forceBackend = backend?.forced;
|
|
@@ -5985,8 +6027,8 @@ function displayDeploymentDiff(options) {
|
|
|
5985
6027
|
if (buildChanged) {
|
|
5986
6028
|
logger.bold("Frontend", 1);
|
|
5987
6029
|
let buildInfo;
|
|
5988
|
-
const previousSize =
|
|
5989
|
-
const currentSize =
|
|
6030
|
+
const previousSize = build2?.previousSize;
|
|
6031
|
+
const currentSize = build2?.currentSize;
|
|
5990
6032
|
if (previousSize !== void 0 && currentSize !== void 0) {
|
|
5991
6033
|
const oldSize = formatSize(previousSize);
|
|
5992
6034
|
const newSize = formatSize(currentSize);
|
|
@@ -6079,10 +6121,10 @@ import { dim as dim4 } from "colorette";
|
|
|
6079
6121
|
init_file_loader();
|
|
6080
6122
|
init_constants2();
|
|
6081
6123
|
init_core();
|
|
6082
|
-
import { join as
|
|
6124
|
+
import { join as join11, relative as relative2 } from "path";
|
|
6083
6125
|
function findSingleBuildZip() {
|
|
6084
6126
|
const workspace = getWorkspace();
|
|
6085
|
-
const playcademyDir =
|
|
6127
|
+
const playcademyDir = join11(workspace, CLI_DIRECTORIES.WORKSPACE);
|
|
6086
6128
|
const zipFiles = findFilesByExtension(playcademyDir, "zip");
|
|
6087
6129
|
if (zipFiles.length === 1) {
|
|
6088
6130
|
return zipFiles[0] ? relative2(workspace, zipFiles[0]) : null;
|
|
@@ -6918,12 +6960,12 @@ init_constants2();
|
|
|
6918
6960
|
import { existsSync as existsSync12 } from "node:fs";
|
|
6919
6961
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
6920
6962
|
import { homedir as homedir2 } from "node:os";
|
|
6921
|
-
import { join as
|
|
6963
|
+
import { join as join12 } from "node:path";
|
|
6922
6964
|
function getGamesStorePath() {
|
|
6923
|
-
return
|
|
6965
|
+
return join12(homedir2(), CLI_USER_DIRECTORIES.CONFIG, CLI_FILES.GAMES_STORE);
|
|
6924
6966
|
}
|
|
6925
6967
|
async function ensureConfigDir() {
|
|
6926
|
-
const configDir =
|
|
6968
|
+
const configDir = join12(homedir2(), CLI_USER_DIRECTORIES.CONFIG);
|
|
6927
6969
|
await mkdir3(configDir, { recursive: true });
|
|
6928
6970
|
}
|
|
6929
6971
|
async function loadGameStore() {
|
|
@@ -7414,12 +7456,12 @@ async function saveDeploymentState(game, backendDeployment, context2) {
|
|
|
7414
7456
|
init_constants2();
|
|
7415
7457
|
init_core();
|
|
7416
7458
|
import { existsSync as existsSync14 } from "fs";
|
|
7417
|
-
import { join as
|
|
7459
|
+
import { join as join13 } from "path";
|
|
7418
7460
|
function hasCustomRoutes(config) {
|
|
7419
7461
|
const workspace = getWorkspace();
|
|
7420
7462
|
const customRoutesConfig = config?.integrations?.customRoutes;
|
|
7421
7463
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
7422
|
-
return existsSync14(
|
|
7464
|
+
return existsSync14(join13(workspace, customRoutesDir));
|
|
7423
7465
|
}
|
|
7424
7466
|
function needsBackend(config) {
|
|
7425
7467
|
return !!config?.integrations || hasCustomRoutes(config);
|
|
@@ -7470,13 +7512,13 @@ init_constants2();
|
|
|
7470
7512
|
init_core();
|
|
7471
7513
|
import { existsSync as existsSync15, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
7472
7514
|
import { mkdir as mkdir4, unlink, writeFile as writeFile4 } from "fs/promises";
|
|
7473
|
-
import { join as
|
|
7515
|
+
import { join as join14 } from "path";
|
|
7474
7516
|
function getDevServerPidPath() {
|
|
7475
|
-
return
|
|
7517
|
+
return join14(getWorkspace(), CLI_DIRECTORIES.WORKSPACE, CLI_FILES.DEV_SERVER_PID);
|
|
7476
7518
|
}
|
|
7477
7519
|
async function createDevServerPidFile() {
|
|
7478
7520
|
const pidPath = getDevServerPidFile();
|
|
7479
|
-
const pidDir =
|
|
7521
|
+
const pidDir = join14(getWorkspace(), CLI_DIRECTORIES.WORKSPACE);
|
|
7480
7522
|
await mkdir4(pidDir, { recursive: true });
|
|
7481
7523
|
await writeFile4(pidPath, process.pid.toString());
|
|
7482
7524
|
}
|
|
@@ -7516,7 +7558,7 @@ function getDevServerPidFile() {
|
|
|
7516
7558
|
// src/lib/dev/reload.ts
|
|
7517
7559
|
init_constants2();
|
|
7518
7560
|
init_core();
|
|
7519
|
-
import { join as
|
|
7561
|
+
import { join as join15, relative as relative3 } from "path";
|
|
7520
7562
|
import chokidar from "chokidar";
|
|
7521
7563
|
import { bold as bold4, cyan as cyan3, dim as dim6, green as green3 } from "colorette";
|
|
7522
7564
|
function formatTime() {
|
|
@@ -7533,9 +7575,9 @@ function startHotReload(onReload, options = {}) {
|
|
|
7533
7575
|
const customRoutesConfig = options.config?.integrations?.customRoutes;
|
|
7534
7576
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
7535
7577
|
const watchPaths = [
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7578
|
+
join15(workspace, customRoutesDir),
|
|
7579
|
+
join15(workspace, "playcademy.config.js"),
|
|
7580
|
+
join15(workspace, "playcademy.config.json")
|
|
7539
7581
|
];
|
|
7540
7582
|
const watcher = chokidar.watch(watchPaths, {
|
|
7541
7583
|
persistent: true,
|
|
@@ -7579,7 +7621,7 @@ init_src2();
|
|
|
7579
7621
|
init_constants2();
|
|
7580
7622
|
init_core();
|
|
7581
7623
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
7582
|
-
import { join as
|
|
7624
|
+
import { join as join16 } from "path";
|
|
7583
7625
|
import { Miniflare } from "miniflare";
|
|
7584
7626
|
async function startDevServer(options) {
|
|
7585
7627
|
const {
|
|
@@ -7593,7 +7635,7 @@ async function startDevServer(options) {
|
|
|
7593
7635
|
sourcemap: false,
|
|
7594
7636
|
minify: false
|
|
7595
7637
|
});
|
|
7596
|
-
const dbDir =
|
|
7638
|
+
const dbDir = join16(getWorkspace(), CLI_DIRECTORIES.DATABASE);
|
|
7597
7639
|
try {
|
|
7598
7640
|
await mkdir5(dbDir, { recursive: true });
|
|
7599
7641
|
} catch (error) {
|
|
@@ -8637,6 +8679,7 @@ async function runDbDiff() {
|
|
|
8637
8679
|
const previousSnapshot = deployedGameInfo?.schemaSnapshot;
|
|
8638
8680
|
const schemaInfo = await getSchemaInfo(previousSnapshot);
|
|
8639
8681
|
if (!schemaInfo) {
|
|
8682
|
+
logger.newLine();
|
|
8640
8683
|
logger.success("No schema changes detected");
|
|
8641
8684
|
logger.newLine();
|
|
8642
8685
|
logger.remark("Nothing to do");
|
|
@@ -8739,14 +8782,14 @@ init_src2();
|
|
|
8739
8782
|
init_src();
|
|
8740
8783
|
init_constants2();
|
|
8741
8784
|
import { spawn } from "child_process";
|
|
8742
|
-
import { existsSync as existsSync16, rmSync } from "fs";
|
|
8743
|
-
import { join as
|
|
8785
|
+
import { existsSync as existsSync16, rmSync as rmSync2 } from "fs";
|
|
8786
|
+
import { join as join17 } from "path";
|
|
8744
8787
|
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
8745
8788
|
import { Miniflare as Miniflare2 } from "miniflare";
|
|
8746
8789
|
async function runDbReset() {
|
|
8747
8790
|
try {
|
|
8748
8791
|
const workspace = getWorkspace();
|
|
8749
|
-
const dbDir =
|
|
8792
|
+
const dbDir = join17(workspace, CLI_DIRECTORIES.DATABASE);
|
|
8750
8793
|
if (!existsSync16(dbDir)) {
|
|
8751
8794
|
logger.warn("No database found to reset");
|
|
8752
8795
|
logger.newLine();
|
|
@@ -8776,7 +8819,7 @@ async function runDbReset() {
|
|
|
8776
8819
|
runStep(
|
|
8777
8820
|
"Deleting database...",
|
|
8778
8821
|
async () => {
|
|
8779
|
-
|
|
8822
|
+
rmSync2(dbDir, { recursive: true, force: true });
|
|
8780
8823
|
},
|
|
8781
8824
|
"Database deleted"
|
|
8782
8825
|
);
|
|
@@ -8851,7 +8894,7 @@ async function runDbReset() {
|
|
|
8851
8894
|
init_package_manager();
|
|
8852
8895
|
import { execSync as execSync4 } from "child_process";
|
|
8853
8896
|
import { existsSync as existsSync17 } from "fs";
|
|
8854
|
-
import { join as
|
|
8897
|
+
import { join as join18 } from "path";
|
|
8855
8898
|
async function runDbSeed(options) {
|
|
8856
8899
|
const workspace = getWorkspace();
|
|
8857
8900
|
try {
|
|
@@ -8860,7 +8903,7 @@ async function runDbSeed(options) {
|
|
|
8860
8903
|
logger.newLine();
|
|
8861
8904
|
}
|
|
8862
8905
|
if (options.file) {
|
|
8863
|
-
const seedPath =
|
|
8906
|
+
const seedPath = join18(workspace, options.file);
|
|
8864
8907
|
if (!existsSync17(seedPath)) {
|
|
8865
8908
|
logger.error(`Seed file not found: ${options.file}`);
|
|
8866
8909
|
logger.newLine();
|
|
@@ -9437,7 +9480,7 @@ import { Command as Command26 } from "commander";
|
|
|
9437
9480
|
init_src();
|
|
9438
9481
|
init_constants2();
|
|
9439
9482
|
import { writeFileSync as writeFileSync8 } from "fs";
|
|
9440
|
-
import { join as
|
|
9483
|
+
import { join as join19 } from "path";
|
|
9441
9484
|
import { Command as Command25 } from "commander";
|
|
9442
9485
|
var bundleCommand = new Command25("bundle").description("Bundle and inspect the game backend worker code (for debugging)").option("-o, --output <path>", "Output file path", CLI_DEFAULT_OUTPUTS.WORKER_BUNDLE).option("--minify", "Minify the output").option("--sourcemap", "Include source maps").action(async (options) => {
|
|
9443
9486
|
try {
|
|
@@ -9467,7 +9510,7 @@ var bundleCommand = new Command25("bundle").description("Bundle and inspect the
|
|
|
9467
9510
|
}),
|
|
9468
9511
|
(result) => `Bundled ${formatSize(result.code.length)}`
|
|
9469
9512
|
);
|
|
9470
|
-
const outputPath =
|
|
9513
|
+
const outputPath = join19(workspace, options.output);
|
|
9471
9514
|
writeFileSync8(outputPath, bundle.code, "utf-8");
|
|
9472
9515
|
logger.success(`Bundle saved to ${options.output}`);
|
|
9473
9516
|
logger.newLine();
|
|
@@ -9612,6 +9655,8 @@ export {
|
|
|
9612
9655
|
hashContent,
|
|
9613
9656
|
hashDirectory,
|
|
9614
9657
|
hashFile,
|
|
9658
|
+
importTypescriptDefault,
|
|
9659
|
+
importTypescriptFile,
|
|
9615
9660
|
integrationChangeDetectors,
|
|
9616
9661
|
isDevServerRunning,
|
|
9617
9662
|
listProfiles,
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { OrganizationConfig, CourseConfig, ComponentConfig, ResourceConfig, ComponentResourceConfig } from '@playcademy/timeback/types';
|
|
2
2
|
import { Miniflare } from 'miniflare';
|
|
3
3
|
import * as chokidar from 'chokidar';
|
|
4
|
+
import * as esbuild from 'esbuild';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @fileoverview Server SDK Type Definitions
|
|
@@ -125,4 +126,44 @@ interface HotReloadOptions {
|
|
|
125
126
|
*/
|
|
126
127
|
declare function startHotReload(onReload: () => Promise<void>, options?: HotReloadOptions): chokidar.FSWatcher;
|
|
127
128
|
|
|
128
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Import utilities for Node-safe TypeScript file loading
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Import a TypeScript file in a Node-safe way
|
|
135
|
+
*
|
|
136
|
+
* This utility compiles TypeScript files to ESM using esbuild and imports them dynamically.
|
|
137
|
+
* This is necessary because Node.js doesn't support importing .ts files directly.
|
|
138
|
+
*
|
|
139
|
+
* @param filePath - Absolute path to the TypeScript file to import
|
|
140
|
+
* @param bundleOptions - Optional esbuild options to customize the build
|
|
141
|
+
* @returns The imported module
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* const schema = await importTypescriptFile('/path/to/schema.ts')
|
|
146
|
+
* const exported = schema.default || schema
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
declare function importTypescriptFile(filePath: string, bundleOptions?: Partial<esbuild.BuildOptions>): Promise<unknown>;
|
|
150
|
+
/**
|
|
151
|
+
* Import a TypeScript file and extract its default export
|
|
152
|
+
*
|
|
153
|
+
* Convenience wrapper around importTypescriptFile that automatically
|
|
154
|
+
* extracts the default export if available, otherwise returns the module itself.
|
|
155
|
+
*
|
|
156
|
+
* @param filePath - Absolute path to the TypeScript file to import
|
|
157
|
+
* @param bundleOptions - Optional esbuild options to customize the build
|
|
158
|
+
* @returns The default export or the entire module
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const schema = await importTypescriptDefault('/path/to/schema.ts')
|
|
163
|
+
* // If schema.ts has `export default`, returns that
|
|
164
|
+
* // Otherwise returns the entire module object
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
declare function importTypescriptDefault(filePath: string, bundleOptions?: Partial<esbuild.BuildOptions>): Promise<unknown>;
|
|
168
|
+
|
|
169
|
+
export { findConfigPath as findPlaycademyConfigPath, importTypescriptDefault, importTypescriptFile, loadConfig as loadPlaycademyConfig, startDevServer as startPlaycademyDevServer, startHotReload as startPlaycademyHotReload, validateConfig as validatePlaycademyConfig };
|
package/dist/utils.js
CHANGED
|
@@ -564,7 +564,7 @@ function processConfigVariables(config) {
|
|
|
564
564
|
|
|
565
565
|
// src/lib/dev/server.ts
|
|
566
566
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
567
|
-
import { join as
|
|
567
|
+
import { join as join5 } from "path";
|
|
568
568
|
import { Miniflare } from "miniflare";
|
|
569
569
|
|
|
570
570
|
// src/lib/core/client.ts
|
|
@@ -816,9 +816,44 @@ import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
|
816
816
|
import { fileURLToPath } from "url";
|
|
817
817
|
var currentDir = dirname3(fileURLToPath(import.meta.url));
|
|
818
818
|
|
|
819
|
+
// src/lib/core/import.ts
|
|
820
|
+
import { mkdtempSync, rmSync } from "fs";
|
|
821
|
+
import { tmpdir } from "os";
|
|
822
|
+
import { join } from "path";
|
|
823
|
+
import { pathToFileURL } from "url";
|
|
824
|
+
import * as esbuild from "esbuild";
|
|
825
|
+
async function importTypescriptFile(filePath, bundleOptions) {
|
|
826
|
+
const tempDir = mkdtempSync(join(tmpdir(), "playcademy-import-"));
|
|
827
|
+
const outFile = join(tempDir, "bundle.mjs");
|
|
828
|
+
try {
|
|
829
|
+
await esbuild.build({
|
|
830
|
+
entryPoints: [filePath],
|
|
831
|
+
outfile: outFile,
|
|
832
|
+
bundle: true,
|
|
833
|
+
platform: "node",
|
|
834
|
+
format: "esm",
|
|
835
|
+
target: "node20",
|
|
836
|
+
sourcemap: false,
|
|
837
|
+
minify: false,
|
|
838
|
+
...bundleOptions
|
|
839
|
+
});
|
|
840
|
+
const module = await import(pathToFileURL(outFile).href);
|
|
841
|
+
return module;
|
|
842
|
+
} finally {
|
|
843
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
async function importTypescriptDefault(filePath, bundleOptions) {
|
|
847
|
+
const module = await importTypescriptFile(filePath, bundleOptions);
|
|
848
|
+
if (module && typeof module === "object" && "default" in module) {
|
|
849
|
+
return module.default;
|
|
850
|
+
}
|
|
851
|
+
return module;
|
|
852
|
+
}
|
|
853
|
+
|
|
819
854
|
// src/lib/deploy/bundle.ts
|
|
820
855
|
import { existsSync as existsSync2 } from "fs";
|
|
821
|
-
import { join as
|
|
856
|
+
import { join as join3 } from "path";
|
|
822
857
|
|
|
823
858
|
// ../edge-play/src/entry.ts
|
|
824
859
|
var entry_default = "/**\n * Game Backend Entry Point\n *\n * This file is the main entry point for deployed game backends.\n * It creates a Hono app and registers all enabled integration routes.\n *\n * Bundled with esbuild and deployed to Cloudflare Workers (or AWS Lambda).\n * Config is injected at build time via esbuild's `define` option.\n */\n\nimport { Hono } from 'hono'\nimport { cors } from 'hono/cors'\n\nimport { PlaycademyClient } from '@playcademy/sdk/server'\n\nimport { ENV_VARS } from './constants'\nimport { registerBuiltinRoutes } from './register-routes'\n\nimport type { PlaycademyConfig } from '@playcademy/sdk/server'\nimport type { HonoEnv } from './types'\n\n/**\n * Config injected at build time by esbuild\n *\n * The `declare const` tells TypeScript \"this exists at runtime, trust me.\"\n * During bundling, esbuild's `define` option does literal text replacement:\n *\n * Example bundling:\n * Source: if (PLAYCADEMY_CONFIG.integrations.timeback) { ... }\n * Define: { 'PLAYCADEMY_CONFIG': JSON.stringify({ integrations: { timeback: {...} } }) }\n * Output: if ({\"integrations\":{\"timeback\":{...}}}.integrations.timeback) { ... }\n *\n * This enables tree-shaking: if timeback is not configured, those code paths are removed.\n * The bundled Worker only includes the routes that are actually enabled.\n */\ndeclare const PLAYCADEMY_CONFIG: PlaycademyConfig & {\n customRoutes?: Array<{ path: string; file: string }>\n}\n\n// XXX: Polyfill process global for SDK compatibility\n// SDK code may reference process.env without importing it\n// @ts-expect-error - Adding global for Worker environment\nglobalThis.process = {\n env: {}, // Populated per-request from Worker env bindings\n cwd: () => '/',\n}\n\nconst app = new Hono<HonoEnv>()\n\n// TODO: Harden CORS in production - restrict to trusted origins:\n// - Game's assetBundleBase (for hosted games)\n// - Game's externalUrl (for external games)\n// - Platform frontend domains (hub.playcademy.com, hub.dev.playcademy.net)\n// This would require passing game metadata through env bindings during deployment\napp.use(\n '*',\n cors({\n origin: '*', // Permissive for now\n allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Authorization'],\n }),\n)\n\nlet sdkPromise: Promise<PlaycademyClient> | null = null\n\napp.use('*', async (c, next) => {\n // Populate process.env from Worker bindings for SDK compatibility\n globalThis.process.env = {\n [ENV_VARS.PLAYCADEMY_API_KEY]: c.env.PLAYCADEMY_API_KEY,\n [ENV_VARS.GAME_ID]: c.env.GAME_ID,\n [ENV_VARS.PLAYCADEMY_BASE_URL]: c.env.PLAYCADEMY_BASE_URL,\n }\n\n // Set config for all routes\n c.set('config', PLAYCADEMY_CONFIG)\n c.set('customRoutes', PLAYCADEMY_CONFIG.customRoutes || [])\n\n await next()\n})\n\n// Initialize SDK lazily on first request\napp.use('*', async (c, next) => {\n if (!sdkPromise) {\n sdkPromise = PlaycademyClient.init({\n apiKey: c.env[ENV_VARS.PLAYCADEMY_API_KEY],\n gameId: c.env[ENV_VARS.GAME_ID],\n baseUrl: c.env[ENV_VARS.PLAYCADEMY_BASE_URL],\n config: PLAYCADEMY_CONFIG,\n })\n }\n\n c.set('sdk', await sdkPromise)\n await next()\n})\n\n/**\n * Register built-in integration routes based on enabled integrations\n *\n * This function conditionally imports and registers routes like:\n * - POST /api/integrations/timeback/end-activity (if timeback enabled)\n * - GET /api/health (always included)\n *\n * Uses dynamic imports for tree-shaking: if an integration is not enabled,\n * its route code is completely removed from the bundle.\n */\nawait registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)\n\nexport default app\n";
|
|
@@ -1041,8 +1076,8 @@ function getMonorepoRoot() {
|
|
|
1041
1076
|
function textLoaderPlugin() {
|
|
1042
1077
|
return {
|
|
1043
1078
|
name: "text-loader",
|
|
1044
|
-
setup(
|
|
1045
|
-
|
|
1079
|
+
setup(build2) {
|
|
1080
|
+
build2.onLoad({ filter: /edge-play\/src\/entry\.ts$/ }, async (args) => {
|
|
1046
1081
|
const fs2 = await import("fs/promises");
|
|
1047
1082
|
const text = await fs2.readFile(args.path, "utf8");
|
|
1048
1083
|
return {
|
|
@@ -1050,7 +1085,7 @@ function textLoaderPlugin() {
|
|
|
1050
1085
|
loader: "js"
|
|
1051
1086
|
};
|
|
1052
1087
|
});
|
|
1053
|
-
|
|
1088
|
+
build2.onLoad({ filter: /edge-play\/src\/routes\/root\.html$/ }, async (args) => {
|
|
1054
1089
|
const fs2 = await import("fs/promises");
|
|
1055
1090
|
const text = await fs2.readFile(args.path, "utf8");
|
|
1056
1091
|
return {
|
|
@@ -1058,7 +1093,7 @@ function textLoaderPlugin() {
|
|
|
1058
1093
|
loader: "js"
|
|
1059
1094
|
};
|
|
1060
1095
|
});
|
|
1061
|
-
|
|
1096
|
+
build2.onLoad({ filter: /templates\/sample-route\.ts$/ }, async (args) => {
|
|
1062
1097
|
const fs2 = await import("fs/promises");
|
|
1063
1098
|
const text = await fs2.readFile(args.path, "utf8");
|
|
1064
1099
|
return {
|
|
@@ -1073,8 +1108,8 @@ function textLoaderPlugin() {
|
|
|
1073
1108
|
// src/lib/dev/routes.ts
|
|
1074
1109
|
init_file_loader();
|
|
1075
1110
|
import { mkdir, writeFile } from "fs/promises";
|
|
1076
|
-
import { tmpdir } from "os";
|
|
1077
|
-
import { join, relative } from "path";
|
|
1111
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1112
|
+
import { join as join2, relative } from "path";
|
|
1078
1113
|
|
|
1079
1114
|
// src/lib/deploy/hash.ts
|
|
1080
1115
|
import { createHash } from "crypto";
|
|
@@ -1093,7 +1128,7 @@ async function discoverRoutes(apiDir) {
|
|
|
1093
1128
|
const routes = await Promise.all(
|
|
1094
1129
|
files.map(async (file) => {
|
|
1095
1130
|
const routePath = filePathToRoutePath(file);
|
|
1096
|
-
const absolutePath =
|
|
1131
|
+
const absolutePath = join2(apiDir, file);
|
|
1097
1132
|
const relativePath = relative(getWorkspace(), absolutePath);
|
|
1098
1133
|
const methods = await detectExportedMethods(absolutePath);
|
|
1099
1134
|
return {
|
|
@@ -1138,8 +1173,8 @@ async function transpileRoute(filePath) {
|
|
|
1138
1173
|
if (isBun() || !filePath.endsWith(".ts")) {
|
|
1139
1174
|
return filePath;
|
|
1140
1175
|
}
|
|
1141
|
-
const
|
|
1142
|
-
const result = await
|
|
1176
|
+
const esbuild2 = await import("esbuild");
|
|
1177
|
+
const result = await esbuild2.build({
|
|
1143
1178
|
entryPoints: [filePath],
|
|
1144
1179
|
write: false,
|
|
1145
1180
|
format: "esm",
|
|
@@ -1153,10 +1188,10 @@ async function transpileRoute(filePath) {
|
|
|
1153
1188
|
if (!result.outputFiles?.[0]) {
|
|
1154
1189
|
throw new Error("Transpilation failed: no output");
|
|
1155
1190
|
}
|
|
1156
|
-
const tempDir =
|
|
1191
|
+
const tempDir = join2(tmpdir2(), "playcademy-dev");
|
|
1157
1192
|
await mkdir(tempDir, { recursive: true });
|
|
1158
1193
|
const hash = hashContent(filePath).slice(0, 12);
|
|
1159
|
-
const jsPath =
|
|
1194
|
+
const jsPath = join2(tempDir, `${hash}.mjs`);
|
|
1160
1195
|
await writeFile(jsPath, result.outputFiles[0].text);
|
|
1161
1196
|
return jsPath;
|
|
1162
1197
|
}
|
|
@@ -1167,7 +1202,7 @@ async function discoverCustomRoutes(config) {
|
|
|
1167
1202
|
const workspace = getWorkspace();
|
|
1168
1203
|
const customRoutesConfig = config.integrations?.customRoutes;
|
|
1169
1204
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
1170
|
-
const customRoutes = await discoverRoutes(
|
|
1205
|
+
const customRoutes = await discoverRoutes(join3(workspace, customRoutesDir));
|
|
1171
1206
|
const customRouteData = customRoutes.map((r) => ({
|
|
1172
1207
|
path: r.path,
|
|
1173
1208
|
file: r.file,
|
|
@@ -1179,15 +1214,15 @@ async function discoverCustomRoutes(config) {
|
|
|
1179
1214
|
function resolveEmbeddedSourcePaths() {
|
|
1180
1215
|
const workspace = getWorkspace();
|
|
1181
1216
|
const distDir = new URL(".", import.meta.url).pathname;
|
|
1182
|
-
const embeddedEdgeSrc =
|
|
1217
|
+
const embeddedEdgeSrc = join3(distDir, "edge-play", "src");
|
|
1183
1218
|
const isBuiltPackage = existsSync2(embeddedEdgeSrc);
|
|
1184
1219
|
const monorepoRoot = getMonorepoRoot();
|
|
1185
|
-
const monorepoEdgeSrc =
|
|
1220
|
+
const monorepoEdgeSrc = join3(monorepoRoot, "packages/edge-play/src");
|
|
1186
1221
|
const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
|
|
1187
|
-
const cliPackageRoot = isBuiltPackage ?
|
|
1188
|
-
const cliNodeModules = isBuiltPackage ?
|
|
1189
|
-
const workspaceNodeModules =
|
|
1190
|
-
const constantsEntry = isBuiltPackage ?
|
|
1222
|
+
const cliPackageRoot = isBuiltPackage ? join3(distDir, "../../..") : join3(monorepoRoot, "packages/cli");
|
|
1223
|
+
const cliNodeModules = isBuiltPackage ? join3(cliPackageRoot, "node_modules") : monorepoRoot;
|
|
1224
|
+
const workspaceNodeModules = join3(workspace, "node_modules");
|
|
1225
|
+
const constantsEntry = isBuiltPackage ? join3(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join3(monorepoRoot, "packages", "constants", "src", "index.ts");
|
|
1191
1226
|
return {
|
|
1192
1227
|
isBuiltPackage,
|
|
1193
1228
|
edgePlaySrc,
|
|
@@ -1247,16 +1282,16 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
|
|
|
1247
1282
|
// │ Example: import * as route from '@game-api/hello.ts' │
|
|
1248
1283
|
// │ Resolves to: /user-project/server/api/hello.ts │
|
|
1249
1284
|
// └─────────────────────────────────────────────────────────────────┘
|
|
1250
|
-
"@game-api":
|
|
1285
|
+
"@game-api": join3(workspace, customRoutesDir),
|
|
1251
1286
|
// ┌─ Node.js polyfills for Cloudflare Workers ──────────────────────┐
|
|
1252
1287
|
// │ Workers don't have fs, path, os, etc. Redirect to polyfills │
|
|
1253
1288
|
// │ that throw helpful errors if user code tries to use them. │
|
|
1254
1289
|
// └─────────────────────────────────────────────────────────────────┘
|
|
1255
|
-
fs:
|
|
1256
|
-
"fs/promises":
|
|
1257
|
-
path:
|
|
1258
|
-
os:
|
|
1259
|
-
process:
|
|
1290
|
+
fs: join3(edgePlaySrc, "polyfills.js"),
|
|
1291
|
+
"fs/promises": join3(edgePlaySrc, "polyfills.js"),
|
|
1292
|
+
path: join3(edgePlaySrc, "polyfills.js"),
|
|
1293
|
+
os: join3(edgePlaySrc, "polyfills.js"),
|
|
1294
|
+
process: join3(edgePlaySrc, "polyfills.js")
|
|
1260
1295
|
},
|
|
1261
1296
|
// ──── Build Plugins ────
|
|
1262
1297
|
plugins: [textLoaderPlugin()],
|
|
@@ -1267,7 +1302,7 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
|
|
|
1267
1302
|
};
|
|
1268
1303
|
}
|
|
1269
1304
|
async function bundleBackend(config, options = {}) {
|
|
1270
|
-
const
|
|
1305
|
+
const esbuild2 = await import("esbuild");
|
|
1271
1306
|
const { customRouteData, customRoutesDir } = await discoverCustomRoutes(config);
|
|
1272
1307
|
const bundleConfig = {
|
|
1273
1308
|
...config,
|
|
@@ -1282,7 +1317,7 @@ async function bundleBackend(config, options = {}) {
|
|
|
1282
1317
|
customRoutesDir,
|
|
1283
1318
|
options
|
|
1284
1319
|
);
|
|
1285
|
-
const result = await
|
|
1320
|
+
const result = await esbuild2.build(buildConfig);
|
|
1286
1321
|
if (!result.outputFiles?.[0]) {
|
|
1287
1322
|
throw new Error("Backend bundling failed: no output");
|
|
1288
1323
|
}
|
|
@@ -1320,13 +1355,13 @@ function generateEntryCode(customRoutes, customRoutesDir) {
|
|
|
1320
1355
|
|
|
1321
1356
|
// src/lib/dev/pid.ts
|
|
1322
1357
|
import { mkdir as mkdir2, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
1323
|
-
import { join as
|
|
1358
|
+
import { join as join4 } from "path";
|
|
1324
1359
|
function getDevServerPidPath() {
|
|
1325
|
-
return
|
|
1360
|
+
return join4(getWorkspace(), CLI_DIRECTORIES.WORKSPACE, CLI_FILES.DEV_SERVER_PID);
|
|
1326
1361
|
}
|
|
1327
1362
|
async function createDevServerPidFile() {
|
|
1328
1363
|
const pidPath = getDevServerPidFile();
|
|
1329
|
-
const pidDir =
|
|
1364
|
+
const pidDir = join4(getWorkspace(), CLI_DIRECTORIES.WORKSPACE);
|
|
1330
1365
|
await mkdir2(pidDir, { recursive: true });
|
|
1331
1366
|
await writeFile2(pidPath, process.pid.toString());
|
|
1332
1367
|
}
|
|
@@ -1347,7 +1382,7 @@ async function startDevServer(options) {
|
|
|
1347
1382
|
sourcemap: false,
|
|
1348
1383
|
minify: false
|
|
1349
1384
|
});
|
|
1350
|
-
const dbDir =
|
|
1385
|
+
const dbDir = join5(getWorkspace(), CLI_DIRECTORIES.DATABASE);
|
|
1351
1386
|
try {
|
|
1352
1387
|
await mkdir3(dbDir, { recursive: true });
|
|
1353
1388
|
} catch (error) {
|
|
@@ -1378,7 +1413,7 @@ async function startDevServer(options) {
|
|
|
1378
1413
|
}
|
|
1379
1414
|
|
|
1380
1415
|
// src/lib/dev/reload.ts
|
|
1381
|
-
import { join as
|
|
1416
|
+
import { join as join6, relative as relative2 } from "path";
|
|
1382
1417
|
import chokidar from "chokidar";
|
|
1383
1418
|
import { bold as bold3, cyan as cyan2, dim as dim3, green as green2 } from "colorette";
|
|
1384
1419
|
function formatTime() {
|
|
@@ -1395,9 +1430,9 @@ function startHotReload(onReload, options = {}) {
|
|
|
1395
1430
|
const customRoutesConfig = options.config?.integrations?.customRoutes;
|
|
1396
1431
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
1397
1432
|
const watchPaths = [
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1433
|
+
join6(workspace, customRoutesDir),
|
|
1434
|
+
join6(workspace, "playcademy.config.js"),
|
|
1435
|
+
join6(workspace, "playcademy.config.json")
|
|
1401
1436
|
];
|
|
1402
1437
|
const watcher = chokidar.watch(watchPaths, {
|
|
1403
1438
|
persistent: true,
|
|
@@ -1437,6 +1472,8 @@ function startHotReload(onReload, options = {}) {
|
|
|
1437
1472
|
}
|
|
1438
1473
|
export {
|
|
1439
1474
|
findConfigPath as findPlaycademyConfigPath,
|
|
1475
|
+
importTypescriptDefault,
|
|
1476
|
+
importTypescriptFile,
|
|
1440
1477
|
loadConfig as loadPlaycademyConfig,
|
|
1441
1478
|
startDevServer as startPlaycademyDevServer,
|
|
1442
1479
|
startHotReload as startPlaycademyHotReload,
|