playcademy 0.12.7 → 0.12.9
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 +116 -88
- 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";
|
|
@@ -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 {
|
|
@@ -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()],
|
|
@@ -5380,12 +5423,9 @@ function generateEntryCode(customRoutes, customRoutesDir) {
|
|
|
5380
5423
|
|
|
5381
5424
|
// src/lib/deploy/schema.ts
|
|
5382
5425
|
init_core();
|
|
5383
|
-
import { existsSync as existsSync9
|
|
5426
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5384
5427
|
import { createRequire } from "module";
|
|
5385
|
-
import {
|
|
5386
|
-
import { join as join8 } from "path";
|
|
5387
|
-
import { pathToFileURL } from "url";
|
|
5388
|
-
import * as esbuild from "esbuild";
|
|
5428
|
+
import { join as join9 } from "path";
|
|
5389
5429
|
|
|
5390
5430
|
// src/lib/init/prompts.ts
|
|
5391
5431
|
init_constants3();
|
|
@@ -5398,7 +5438,7 @@ init_src();
|
|
|
5398
5438
|
init_core();
|
|
5399
5439
|
init_loader2();
|
|
5400
5440
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5401
|
-
import { join as
|
|
5441
|
+
import { join as join8, resolve as resolve6 } from "path";
|
|
5402
5442
|
var sampleRouteTemplate = loadTemplateString("api/sample-route.ts");
|
|
5403
5443
|
var sampleRouteWithDbTemplate = loadTemplateString("api/sample-route-with-db.ts");
|
|
5404
5444
|
async function scaffoldApiDirectory(apiDirectory, sampleRouteTemplate2) {
|
|
@@ -5408,7 +5448,7 @@ async function scaffoldApiDirectory(apiDirectory, sampleRouteTemplate2) {
|
|
|
5408
5448
|
async () => {
|
|
5409
5449
|
if (!existsSync8(apiPath)) {
|
|
5410
5450
|
mkdirSync3(apiPath, { recursive: true });
|
|
5411
|
-
writeFileSync3(
|
|
5451
|
+
writeFileSync3(join8(apiPath, "hello.ts"), sampleRouteTemplate2, "utf-8");
|
|
5412
5452
|
}
|
|
5413
5453
|
},
|
|
5414
5454
|
"API directory scaffolded"
|
|
@@ -5640,7 +5680,7 @@ async function getSchemaInfo(previousSchemaSnapshot) {
|
|
|
5640
5680
|
if (!hasDatabaseSetup()) {
|
|
5641
5681
|
return null;
|
|
5642
5682
|
}
|
|
5643
|
-
const schemaPath =
|
|
5683
|
+
const schemaPath = join9(workspace, "db/schema/index.ts");
|
|
5644
5684
|
if (!existsSync9(schemaPath)) {
|
|
5645
5685
|
return null;
|
|
5646
5686
|
}
|
|
@@ -5648,21 +5688,7 @@ async function getSchemaInfo(previousSchemaSnapshot) {
|
|
|
5648
5688
|
const require2 = createRequire(import.meta.url);
|
|
5649
5689
|
const drizzleKitApi = require2("drizzle-kit/api");
|
|
5650
5690
|
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = drizzleKitApi;
|
|
5651
|
-
const
|
|
5652
|
-
const outFile = join8(tempDir, "schema.mjs");
|
|
5653
|
-
await esbuild.build({
|
|
5654
|
-
entryPoints: [schemaPath],
|
|
5655
|
-
outfile: outFile,
|
|
5656
|
-
bundle: true,
|
|
5657
|
-
platform: "node",
|
|
5658
|
-
format: "esm",
|
|
5659
|
-
target: "node20",
|
|
5660
|
-
sourcemap: false,
|
|
5661
|
-
minify: false
|
|
5662
|
-
});
|
|
5663
|
-
const schemaModule = await import(pathToFileURL(outFile).href);
|
|
5664
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
5665
|
-
const currentSchema = schemaModule && typeof schemaModule === "object" && "default" in schemaModule ? schemaModule.default : schemaModule;
|
|
5691
|
+
const currentSchema = await importTypescriptDefault(schemaPath);
|
|
5666
5692
|
const nextJson = await generateSQLiteDrizzleJson(currentSchema);
|
|
5667
5693
|
const prevJson = previousSchemaSnapshot ? previousSchemaSnapshot : await generateSQLiteDrizzleJson({});
|
|
5668
5694
|
const migrationStatements = await generateSQLiteMigration(prevJson, nextJson);
|
|
@@ -5704,7 +5730,7 @@ var CUSTOM_ROUTES_EXTENSIONS_WITH_DOT = [".ts", ".js", ".mjs", ".cjs"];
|
|
|
5704
5730
|
function getCustomRoutesDirectory(projectPath, config) {
|
|
5705
5731
|
const customRoutes = config?.integrations?.customRoutes;
|
|
5706
5732
|
const customRoutesDir = typeof customRoutes === "object" && customRoutes.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
5707
|
-
return
|
|
5733
|
+
return join10(projectPath, customRoutesDir);
|
|
5708
5734
|
}
|
|
5709
5735
|
function hasLocalCustomRoutes(projectPath, config) {
|
|
5710
5736
|
const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
|
|
@@ -5716,7 +5742,7 @@ async function getCustomRoutesHash(projectPath, config) {
|
|
|
5716
5742
|
}
|
|
5717
5743
|
async function getCustomRoutesSize(projectPath, config) {
|
|
5718
5744
|
const { stat: stat3, readdir } = await import("node:fs/promises");
|
|
5719
|
-
const { join:
|
|
5745
|
+
const { join: join20 } = await import("node:path");
|
|
5720
5746
|
const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
|
|
5721
5747
|
if (!existsSync10(customRoutesDir)) {
|
|
5722
5748
|
return null;
|
|
@@ -5725,7 +5751,7 @@ async function getCustomRoutesSize(projectPath, config) {
|
|
|
5725
5751
|
async function calculateDirSize(dir) {
|
|
5726
5752
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
5727
5753
|
for (const entry of entries) {
|
|
5728
|
-
const fullPath =
|
|
5754
|
+
const fullPath = join20(dir, entry.name);
|
|
5729
5755
|
if (entry.isDirectory()) {
|
|
5730
5756
|
await calculateDirSize(fullPath);
|
|
5731
5757
|
} else if (entry.isFile()) {
|
|
@@ -6095,10 +6121,10 @@ import { dim as dim4 } from "colorette";
|
|
|
6095
6121
|
init_file_loader();
|
|
6096
6122
|
init_constants2();
|
|
6097
6123
|
init_core();
|
|
6098
|
-
import { join as
|
|
6124
|
+
import { join as join11, relative as relative2 } from "path";
|
|
6099
6125
|
function findSingleBuildZip() {
|
|
6100
6126
|
const workspace = getWorkspace();
|
|
6101
|
-
const playcademyDir =
|
|
6127
|
+
const playcademyDir = join11(workspace, CLI_DIRECTORIES.WORKSPACE);
|
|
6102
6128
|
const zipFiles = findFilesByExtension(playcademyDir, "zip");
|
|
6103
6129
|
if (zipFiles.length === 1) {
|
|
6104
6130
|
return zipFiles[0] ? relative2(workspace, zipFiles[0]) : null;
|
|
@@ -6934,12 +6960,12 @@ init_constants2();
|
|
|
6934
6960
|
import { existsSync as existsSync12 } from "node:fs";
|
|
6935
6961
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
6936
6962
|
import { homedir as homedir2 } from "node:os";
|
|
6937
|
-
import { join as
|
|
6963
|
+
import { join as join12 } from "node:path";
|
|
6938
6964
|
function getGamesStorePath() {
|
|
6939
|
-
return
|
|
6965
|
+
return join12(homedir2(), CLI_USER_DIRECTORIES.CONFIG, CLI_FILES.GAMES_STORE);
|
|
6940
6966
|
}
|
|
6941
6967
|
async function ensureConfigDir() {
|
|
6942
|
-
const configDir =
|
|
6968
|
+
const configDir = join12(homedir2(), CLI_USER_DIRECTORIES.CONFIG);
|
|
6943
6969
|
await mkdir3(configDir, { recursive: true });
|
|
6944
6970
|
}
|
|
6945
6971
|
async function loadGameStore() {
|
|
@@ -7430,12 +7456,12 @@ async function saveDeploymentState(game, backendDeployment, context2) {
|
|
|
7430
7456
|
init_constants2();
|
|
7431
7457
|
init_core();
|
|
7432
7458
|
import { existsSync as existsSync14 } from "fs";
|
|
7433
|
-
import { join as
|
|
7459
|
+
import { join as join13 } from "path";
|
|
7434
7460
|
function hasCustomRoutes(config) {
|
|
7435
7461
|
const workspace = getWorkspace();
|
|
7436
7462
|
const customRoutesConfig = config?.integrations?.customRoutes;
|
|
7437
7463
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
7438
|
-
return existsSync14(
|
|
7464
|
+
return existsSync14(join13(workspace, customRoutesDir));
|
|
7439
7465
|
}
|
|
7440
7466
|
function needsBackend(config) {
|
|
7441
7467
|
return !!config?.integrations || hasCustomRoutes(config);
|
|
@@ -7486,13 +7512,13 @@ init_constants2();
|
|
|
7486
7512
|
init_core();
|
|
7487
7513
|
import { existsSync as existsSync15, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
7488
7514
|
import { mkdir as mkdir4, unlink, writeFile as writeFile4 } from "fs/promises";
|
|
7489
|
-
import { join as
|
|
7515
|
+
import { join as join14 } from "path";
|
|
7490
7516
|
function getDevServerPidPath() {
|
|
7491
|
-
return
|
|
7517
|
+
return join14(getWorkspace(), CLI_DIRECTORIES.WORKSPACE, CLI_FILES.DEV_SERVER_PID);
|
|
7492
7518
|
}
|
|
7493
7519
|
async function createDevServerPidFile() {
|
|
7494
7520
|
const pidPath = getDevServerPidFile();
|
|
7495
|
-
const pidDir =
|
|
7521
|
+
const pidDir = join14(getWorkspace(), CLI_DIRECTORIES.WORKSPACE);
|
|
7496
7522
|
await mkdir4(pidDir, { recursive: true });
|
|
7497
7523
|
await writeFile4(pidPath, process.pid.toString());
|
|
7498
7524
|
}
|
|
@@ -7532,7 +7558,7 @@ function getDevServerPidFile() {
|
|
|
7532
7558
|
// src/lib/dev/reload.ts
|
|
7533
7559
|
init_constants2();
|
|
7534
7560
|
init_core();
|
|
7535
|
-
import { join as
|
|
7561
|
+
import { join as join15, relative as relative3 } from "path";
|
|
7536
7562
|
import chokidar from "chokidar";
|
|
7537
7563
|
import { bold as bold4, cyan as cyan3, dim as dim6, green as green3 } from "colorette";
|
|
7538
7564
|
function formatTime() {
|
|
@@ -7549,9 +7575,9 @@ function startHotReload(onReload, options = {}) {
|
|
|
7549
7575
|
const customRoutesConfig = options.config?.integrations?.customRoutes;
|
|
7550
7576
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
7551
7577
|
const watchPaths = [
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7578
|
+
join15(workspace, customRoutesDir),
|
|
7579
|
+
join15(workspace, "playcademy.config.js"),
|
|
7580
|
+
join15(workspace, "playcademy.config.json")
|
|
7555
7581
|
];
|
|
7556
7582
|
const watcher = chokidar.watch(watchPaths, {
|
|
7557
7583
|
persistent: true,
|
|
@@ -7595,7 +7621,7 @@ init_src2();
|
|
|
7595
7621
|
init_constants2();
|
|
7596
7622
|
init_core();
|
|
7597
7623
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
7598
|
-
import { join as
|
|
7624
|
+
import { join as join16 } from "path";
|
|
7599
7625
|
import { Miniflare } from "miniflare";
|
|
7600
7626
|
async function startDevServer(options) {
|
|
7601
7627
|
const {
|
|
@@ -7609,7 +7635,7 @@ async function startDevServer(options) {
|
|
|
7609
7635
|
sourcemap: false,
|
|
7610
7636
|
minify: false
|
|
7611
7637
|
});
|
|
7612
|
-
const dbDir =
|
|
7638
|
+
const dbDir = join16(getWorkspace(), CLI_DIRECTORIES.DATABASE);
|
|
7613
7639
|
try {
|
|
7614
7640
|
await mkdir5(dbDir, { recursive: true });
|
|
7615
7641
|
} catch (error) {
|
|
@@ -8652,8 +8678,8 @@ async function runDbDiff() {
|
|
|
8652
8678
|
const deployedGameInfo = await getDeployedGame(workspace);
|
|
8653
8679
|
const previousSnapshot = deployedGameInfo?.schemaSnapshot;
|
|
8654
8680
|
const schemaInfo = await getSchemaInfo(previousSnapshot);
|
|
8681
|
+
logger.newLine();
|
|
8655
8682
|
if (!schemaInfo) {
|
|
8656
|
-
logger.newLine();
|
|
8657
8683
|
logger.success("No schema changes detected");
|
|
8658
8684
|
logger.newLine();
|
|
8659
8685
|
logger.remark("Nothing to do");
|
|
@@ -8757,13 +8783,13 @@ init_src();
|
|
|
8757
8783
|
init_constants2();
|
|
8758
8784
|
import { spawn } from "child_process";
|
|
8759
8785
|
import { existsSync as existsSync16, rmSync as rmSync2 } from "fs";
|
|
8760
|
-
import { join as
|
|
8786
|
+
import { join as join17 } from "path";
|
|
8761
8787
|
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
8762
8788
|
import { Miniflare as Miniflare2 } from "miniflare";
|
|
8763
8789
|
async function runDbReset() {
|
|
8764
8790
|
try {
|
|
8765
8791
|
const workspace = getWorkspace();
|
|
8766
|
-
const dbDir =
|
|
8792
|
+
const dbDir = join17(workspace, CLI_DIRECTORIES.DATABASE);
|
|
8767
8793
|
if (!existsSync16(dbDir)) {
|
|
8768
8794
|
logger.warn("No database found to reset");
|
|
8769
8795
|
logger.newLine();
|
|
@@ -8868,7 +8894,7 @@ async function runDbReset() {
|
|
|
8868
8894
|
init_package_manager();
|
|
8869
8895
|
import { execSync as execSync4 } from "child_process";
|
|
8870
8896
|
import { existsSync as existsSync17 } from "fs";
|
|
8871
|
-
import { join as
|
|
8897
|
+
import { join as join18 } from "path";
|
|
8872
8898
|
async function runDbSeed(options) {
|
|
8873
8899
|
const workspace = getWorkspace();
|
|
8874
8900
|
try {
|
|
@@ -8877,7 +8903,7 @@ async function runDbSeed(options) {
|
|
|
8877
8903
|
logger.newLine();
|
|
8878
8904
|
}
|
|
8879
8905
|
if (options.file) {
|
|
8880
|
-
const seedPath =
|
|
8906
|
+
const seedPath = join18(workspace, options.file);
|
|
8881
8907
|
if (!existsSync17(seedPath)) {
|
|
8882
8908
|
logger.error(`Seed file not found: ${options.file}`);
|
|
8883
8909
|
logger.newLine();
|
|
@@ -9454,7 +9480,7 @@ import { Command as Command26 } from "commander";
|
|
|
9454
9480
|
init_src();
|
|
9455
9481
|
init_constants2();
|
|
9456
9482
|
import { writeFileSync as writeFileSync8 } from "fs";
|
|
9457
|
-
import { join as
|
|
9483
|
+
import { join as join19 } from "path";
|
|
9458
9484
|
import { Command as Command25 } from "commander";
|
|
9459
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) => {
|
|
9460
9486
|
try {
|
|
@@ -9484,7 +9510,7 @@ var bundleCommand = new Command25("bundle").description("Bundle and inspect the
|
|
|
9484
9510
|
}),
|
|
9485
9511
|
(result) => `Bundled ${formatSize(result.code.length)}`
|
|
9486
9512
|
);
|
|
9487
|
-
const outputPath =
|
|
9513
|
+
const outputPath = join19(workspace, options.output);
|
|
9488
9514
|
writeFileSync8(outputPath, bundle.code, "utf-8");
|
|
9489
9515
|
logger.success(`Bundle saved to ${options.output}`);
|
|
9490
9516
|
logger.newLine();
|
|
@@ -9629,6 +9655,8 @@ export {
|
|
|
9629
9655
|
hashContent,
|
|
9630
9656
|
hashDirectory,
|
|
9631
9657
|
hashFile,
|
|
9658
|
+
importTypescriptDefault,
|
|
9659
|
+
importTypescriptFile,
|
|
9632
9660
|
integrationChangeDetectors,
|
|
9633
9661
|
isDevServerRunning,
|
|
9634
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,
|