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 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 join3 } from "path";
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 = join3(dbDir, "miniflare-D1DatabaseObject");
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 join3(miniflareDir, sqliteFiles[0]);
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 = join3(DB_DIRECTORY, INITIAL_DB_NAME);
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 join9 } from "node:path";
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 join4 } from "path";
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 = join4(workspace, ".gitignore");
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 = join4(workspace, "db");
4620
- const schemaDir = join4(dbDir, "schema");
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 = join4(schemaDir, "users.ts");
4670
+ const usersSchemaPath = join5(schemaDir, "users.ts");
4628
4671
  writeFileSync2(usersSchemaPath, dbSchemaUsersTemplate);
4629
- const scoresSchemaPath = join4(schemaDir, "scores.ts");
4672
+ const scoresSchemaPath = join5(schemaDir, "scores.ts");
4630
4673
  writeFileSync2(scoresSchemaPath, dbSchemaScoresTemplate);
4631
- const schemaIndexPath = join4(schemaDir, "index.ts");
4674
+ const schemaIndexPath = join5(schemaDir, "index.ts");
4632
4675
  writeFileSync2(schemaIndexPath, dbSchemaIndexTemplate);
4633
- const dbIndexPath = join4(dbDir, "index.ts");
4676
+ const dbIndexPath = join5(dbDir, "index.ts");
4634
4677
  writeFileSync2(dbIndexPath, dbIndexTemplate);
4635
- const dbTypesPath = join4(dbDir, "types.ts");
4678
+ const dbTypesPath = join5(dbDir, "types.ts");
4636
4679
  writeFileSync2(dbTypesPath, dbTypesTemplate);
4637
- const drizzleConfigPath = join4(workspace, "drizzle.config.ts");
4680
+ const drizzleConfigPath = join5(workspace, "drizzle.config.ts");
4638
4681
  writeFileSync2(drizzleConfigPath, drizzleConfigTemplate);
4639
- const playcademyDir = join4(workspace, CLI_DIRECTORIES.WORKSPACE);
4682
+ const playcademyDir = join5(workspace, CLI_DIRECTORIES.WORKSPACE);
4640
4683
  if (!existsSync6(playcademyDir)) {
4641
4684
  mkdirSync2(playcademyDir, { recursive: true });
4642
4685
  }
4643
- const playcademyGitignorePath = join4(playcademyDir, ".gitignore");
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 = join4(workspace, "package.json");
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 = join4(workspace, "drizzle.config.ts");
4694
- const drizzleConfigJsPath = join4(workspace, "drizzle.config.js");
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 join6 } from "path";
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(build) {
5055
- build.onLoad({ filter: /edge-play\/src\/entry\.ts$/ }, async (args) => {
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
- build.onLoad({ filter: /edge-play\/src\/routes\/root\.html$/ }, async (args) => {
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
- build.onLoad({ filter: /templates\/sample-route\.ts$/ }, async (args) => {
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 join5, relative } from "path";
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 = join5(apiDir, file);
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 esbuild = await import("esbuild");
5185
- const result = await esbuild.build({
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 = join5(tmpdir(), "playcademy-dev");
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 = join5(tempDir, `${hash}.mjs`);
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(join6(workspace, customRoutesDir));
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 = join6(distDir, "edge-play", "src");
5285
+ const embeddedEdgeSrc = join7(distDir, "edge-play", "src");
5243
5286
  const isBuiltPackage = existsSync7(embeddedEdgeSrc);
5244
5287
  const monorepoRoot = getMonorepoRoot();
5245
- const monorepoEdgeSrc = join6(monorepoRoot, "packages/edge-play/src");
5288
+ const monorepoEdgeSrc = join7(monorepoRoot, "packages/edge-play/src");
5246
5289
  const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
5247
- const cliPackageRoot = isBuiltPackage ? join6(distDir, "../../..") : join6(monorepoRoot, "packages/cli");
5248
- const cliNodeModules = isBuiltPackage ? join6(cliPackageRoot, "node_modules") : monorepoRoot;
5249
- const workspaceNodeModules = join6(workspace, "node_modules");
5250
- const constantsEntry = isBuiltPackage ? join6(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join6(monorepoRoot, "packages", "constants", "src", "index.ts");
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": join6(workspace, customRoutesDir),
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: join6(edgePlaySrc, "polyfills.js"),
5316
- "fs/promises": join6(edgePlaySrc, "polyfills.js"),
5317
- path: join6(edgePlaySrc, "polyfills.js"),
5318
- os: join6(edgePlaySrc, "polyfills.js"),
5319
- process: join6(edgePlaySrc, "polyfills.js")
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 esbuild = await import("esbuild");
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 esbuild.build(buildConfig);
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 join8 } from "path";
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 join7, resolve as resolve6 } from "path";
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(join7(apiPath, "hello.ts"), sampleRouteTemplate2, "utf-8");
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 = join8(workspace, "db/schema/index.ts");
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 schemaModule = await import(schemaPath);
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 join9(projectPath, customRoutesDir);
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: join19 } = await import("node:path");
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 = join19(dir, entry.name);
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 = build?.changed === true;
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 = build?.previousSize;
5989
- const currentSize = build?.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 join10, relative as relative2 } from "path";
6124
+ import { join as join11, relative as relative2 } from "path";
6083
6125
  function findSingleBuildZip() {
6084
6126
  const workspace = getWorkspace();
6085
- const playcademyDir = join10(workspace, CLI_DIRECTORIES.WORKSPACE);
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 join11 } from "node:path";
6963
+ import { join as join12 } from "node:path";
6922
6964
  function getGamesStorePath() {
6923
- return join11(homedir2(), CLI_USER_DIRECTORIES.CONFIG, CLI_FILES.GAMES_STORE);
6965
+ return join12(homedir2(), CLI_USER_DIRECTORIES.CONFIG, CLI_FILES.GAMES_STORE);
6924
6966
  }
6925
6967
  async function ensureConfigDir() {
6926
- const configDir = join11(homedir2(), CLI_USER_DIRECTORIES.CONFIG);
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 join12 } from "path";
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(join12(workspace, customRoutesDir));
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 join13 } from "path";
7515
+ import { join as join14 } from "path";
7474
7516
  function getDevServerPidPath() {
7475
- return join13(getWorkspace(), CLI_DIRECTORIES.WORKSPACE, CLI_FILES.DEV_SERVER_PID);
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 = join13(getWorkspace(), CLI_DIRECTORIES.WORKSPACE);
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 join14, relative as relative3 } from "path";
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
- join14(workspace, customRoutesDir),
7537
- join14(workspace, "playcademy.config.js"),
7538
- join14(workspace, "playcademy.config.json")
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 join15 } from "path";
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 = join15(getWorkspace(), CLI_DIRECTORIES.DATABASE);
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 join16 } from "path";
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 = join16(workspace, CLI_DIRECTORIES.DATABASE);
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
- rmSync(dbDir, { recursive: true, force: true });
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 join17 } from "path";
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 = join17(workspace, options.file);
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 join18 } from "path";
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 = join18(workspace, options.output);
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
- export { findConfigPath as findPlaycademyConfigPath, loadConfig as loadPlaycademyConfig, startDevServer as startPlaycademyDevServer, startHotReload as startPlaycademyHotReload, validateConfig as validatePlaycademyConfig };
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 join4 } from "path";
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 join2 } from "path";
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(build) {
1045
- build.onLoad({ filter: /edge-play\/src\/entry\.ts$/ }, async (args) => {
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
- build.onLoad({ filter: /edge-play\/src\/routes\/root\.html$/ }, async (args) => {
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
- build.onLoad({ filter: /templates\/sample-route\.ts$/ }, async (args) => {
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 = join(apiDir, file);
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 esbuild = await import("esbuild");
1142
- const result = await esbuild.build({
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 = join(tmpdir(), "playcademy-dev");
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 = join(tempDir, `${hash}.mjs`);
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(join2(workspace, customRoutesDir));
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 = join2(distDir, "edge-play", "src");
1217
+ const embeddedEdgeSrc = join3(distDir, "edge-play", "src");
1183
1218
  const isBuiltPackage = existsSync2(embeddedEdgeSrc);
1184
1219
  const monorepoRoot = getMonorepoRoot();
1185
- const monorepoEdgeSrc = join2(monorepoRoot, "packages/edge-play/src");
1220
+ const monorepoEdgeSrc = join3(monorepoRoot, "packages/edge-play/src");
1186
1221
  const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
1187
- const cliPackageRoot = isBuiltPackage ? join2(distDir, "../../..") : join2(monorepoRoot, "packages/cli");
1188
- const cliNodeModules = isBuiltPackage ? join2(cliPackageRoot, "node_modules") : monorepoRoot;
1189
- const workspaceNodeModules = join2(workspace, "node_modules");
1190
- const constantsEntry = isBuiltPackage ? join2(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join2(monorepoRoot, "packages", "constants", "src", "index.ts");
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": join2(workspace, customRoutesDir),
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: join2(edgePlaySrc, "polyfills.js"),
1256
- "fs/promises": join2(edgePlaySrc, "polyfills.js"),
1257
- path: join2(edgePlaySrc, "polyfills.js"),
1258
- os: join2(edgePlaySrc, "polyfills.js"),
1259
- process: join2(edgePlaySrc, "polyfills.js")
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 esbuild = await import("esbuild");
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 esbuild.build(buildConfig);
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 join3 } from "path";
1358
+ import { join as join4 } from "path";
1324
1359
  function getDevServerPidPath() {
1325
- return join3(getWorkspace(), CLI_DIRECTORIES.WORKSPACE, CLI_FILES.DEV_SERVER_PID);
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 = join3(getWorkspace(), CLI_DIRECTORIES.WORKSPACE);
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 = join4(getWorkspace(), CLI_DIRECTORIES.DATABASE);
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 join5, relative as relative2 } from "path";
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
- join5(workspace, customRoutesDir),
1399
- join5(workspace, "playcademy.config.js"),
1400
- join5(workspace, "playcademy.config.json")
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playcademy",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "type": "module",
5
5
  "module": "./dist/index.js",
6
6
  "main": "./dist/index.js",