playcademy 0.13.9 → 0.13.10
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/constants.d.ts +2 -0
- package/dist/constants.js +3 -1
- package/dist/db.js +3 -1
- package/dist/edge-play/src/types.ts +10 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +926 -211
- package/dist/templates/api/sample-bucket.ts.template +178 -0
- package/dist/templates/api/sample-custom.ts.template +0 -2
- package/dist/templates/api/sample-database.ts.template +5 -3
- package/dist/templates/api/sample-kv.ts.template +0 -2
- package/dist/templates/api/sample-route-with-db.ts.template +0 -2
- package/dist/templates/api/sample-route.ts.template +0 -1
- package/dist/templates/gitignore.template +3 -3
- package/dist/templates/playcademy-env.d.ts.template +16 -0
- package/dist/templates/playcademy-gitignore.template +4 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +202 -50
- package/package.json +1 -1
package/dist/utils.js
CHANGED
|
@@ -301,7 +301,17 @@ var CLI_DIRECTORIES = {
|
|
|
301
301
|
/** Database directory within workspace */
|
|
302
302
|
DATABASE: join(WORKSPACE_NAME, "db"),
|
|
303
303
|
/** KV storage directory within workspace */
|
|
304
|
-
KV: join(WORKSPACE_NAME, "kv")
|
|
304
|
+
KV: join(WORKSPACE_NAME, "kv"),
|
|
305
|
+
/** Bucket storage directory within workspace */
|
|
306
|
+
BUCKET: join(WORKSPACE_NAME, "bucket")
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// src/constants/ports.ts
|
|
310
|
+
var DEFAULT_PORTS = {
|
|
311
|
+
/** Sandbox server (mock platform API) */
|
|
312
|
+
SANDBOX: 4321,
|
|
313
|
+
/** Backend dev server (game backend with HMR) */
|
|
314
|
+
BACKEND: 8788
|
|
305
315
|
};
|
|
306
316
|
|
|
307
317
|
// src/constants/timeback.ts
|
|
@@ -558,7 +568,7 @@ function processConfigVariables(config) {
|
|
|
558
568
|
|
|
559
569
|
// src/lib/dev/server.ts
|
|
560
570
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
561
|
-
import { join as
|
|
571
|
+
import { join as join9 } from "path";
|
|
562
572
|
import { Log, LogLevel, Miniflare } from "miniflare";
|
|
563
573
|
|
|
564
574
|
// ../utils/src/port.ts
|
|
@@ -623,10 +633,75 @@ function writeServerInfo(type, info) {
|
|
|
623
633
|
registry[key] = info;
|
|
624
634
|
writeRegistry(registry);
|
|
625
635
|
}
|
|
636
|
+
function readServerInfo(type, projectRoot) {
|
|
637
|
+
const registry = readRegistry();
|
|
638
|
+
const servers = Object.entries(registry).filter(([key]) => key.startsWith(`${type}-`)).map(([, info]) => info);
|
|
639
|
+
if (servers.length === 0) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
if (projectRoot) {
|
|
643
|
+
const match = servers.find((s) => s.projectRoot === projectRoot);
|
|
644
|
+
return match || null;
|
|
645
|
+
}
|
|
646
|
+
return servers[0] || null;
|
|
647
|
+
}
|
|
626
648
|
|
|
627
649
|
// src/lib/core/client.ts
|
|
628
650
|
import { PlaycademyClient } from "@playcademy/sdk";
|
|
629
651
|
|
|
652
|
+
// ../utils/src/package-manager.ts
|
|
653
|
+
import { execSync } from "child_process";
|
|
654
|
+
import { existsSync as existsSync3 } from "fs";
|
|
655
|
+
import { join as join3 } from "path";
|
|
656
|
+
function isCommandAvailable(command) {
|
|
657
|
+
try {
|
|
658
|
+
execSync(`command -v ${command}`, { stdio: "ignore" });
|
|
659
|
+
return true;
|
|
660
|
+
} catch {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function detectPackageManager(cwd = process.cwd()) {
|
|
665
|
+
if (existsSync3(join3(cwd, "bun.lock")) || existsSync3(join3(cwd, "bun.lockb"))) {
|
|
666
|
+
return "bun";
|
|
667
|
+
}
|
|
668
|
+
if (existsSync3(join3(cwd, "pnpm-lock.yaml"))) {
|
|
669
|
+
return "pnpm";
|
|
670
|
+
}
|
|
671
|
+
if (existsSync3(join3(cwd, "yarn.lock"))) {
|
|
672
|
+
return "yarn";
|
|
673
|
+
}
|
|
674
|
+
if (existsSync3(join3(cwd, "package-lock.json"))) {
|
|
675
|
+
return "npm";
|
|
676
|
+
}
|
|
677
|
+
return detectByCommandAvailability();
|
|
678
|
+
}
|
|
679
|
+
function detectByCommandAvailability() {
|
|
680
|
+
if (isCommandAvailable("bun")) {
|
|
681
|
+
return "bun";
|
|
682
|
+
}
|
|
683
|
+
if (isCommandAvailable("pnpm")) {
|
|
684
|
+
return "pnpm";
|
|
685
|
+
}
|
|
686
|
+
if (isCommandAvailable("yarn")) {
|
|
687
|
+
return "yarn";
|
|
688
|
+
}
|
|
689
|
+
return "npm";
|
|
690
|
+
}
|
|
691
|
+
function getInstallCommand(pm) {
|
|
692
|
+
switch (pm) {
|
|
693
|
+
case "bun":
|
|
694
|
+
return "bun install";
|
|
695
|
+
case "pnpm":
|
|
696
|
+
return "pnpm install";
|
|
697
|
+
case "yarn":
|
|
698
|
+
return "yarn install";
|
|
699
|
+
case "npm":
|
|
700
|
+
default:
|
|
701
|
+
return "npm install";
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
630
705
|
// src/lib/core/context.ts
|
|
631
706
|
var context = {};
|
|
632
707
|
function getWorkspace() {
|
|
@@ -869,7 +944,7 @@ var CROSS_MARK = String.fromCodePoint(10006);
|
|
|
869
944
|
init_package_json();
|
|
870
945
|
|
|
871
946
|
// src/lib/templates/loader.ts
|
|
872
|
-
import { existsSync as
|
|
947
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
873
948
|
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
874
949
|
import { fileURLToPath } from "url";
|
|
875
950
|
var currentDir = dirname3(fileURLToPath(import.meta.url));
|
|
@@ -882,7 +957,7 @@ function loadTemplateString(filename) {
|
|
|
882
957
|
resolve3(currentDir, "templates", name)
|
|
883
958
|
]);
|
|
884
959
|
for (const candidate of candidatePaths) {
|
|
885
|
-
if (
|
|
960
|
+
if (existsSync4(candidate)) {
|
|
886
961
|
return readFileSync2(candidate, "utf-8");
|
|
887
962
|
}
|
|
888
963
|
}
|
|
@@ -892,12 +967,12 @@ function loadTemplateString(filename) {
|
|
|
892
967
|
// src/lib/core/import.ts
|
|
893
968
|
import { mkdtempSync, rmSync } from "fs";
|
|
894
969
|
import { tmpdir } from "os";
|
|
895
|
-
import { join as
|
|
970
|
+
import { join as join4 } from "path";
|
|
896
971
|
import { pathToFileURL } from "url";
|
|
897
972
|
import * as esbuild from "esbuild";
|
|
898
973
|
async function importTypescriptFile(filePath, bundleOptions) {
|
|
899
|
-
const tempDir = mkdtempSync(
|
|
900
|
-
const outFile =
|
|
974
|
+
const tempDir = mkdtempSync(join4(tmpdir(), "playcademy-import-"));
|
|
975
|
+
const outFile = join4(tempDir, "bundle.mjs");
|
|
901
976
|
try {
|
|
902
977
|
await esbuild.build({
|
|
903
978
|
entryPoints: [filePath],
|
|
@@ -925,8 +1000,8 @@ async function importTypescriptDefault(filePath, bundleOptions) {
|
|
|
925
1000
|
}
|
|
926
1001
|
|
|
927
1002
|
// src/lib/deploy/bundle.ts
|
|
928
|
-
import { existsSync as
|
|
929
|
-
import { join as
|
|
1003
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1004
|
+
import { join as join6 } from "path";
|
|
930
1005
|
|
|
931
1006
|
// ../edge-play/src/entry.ts
|
|
932
1007
|
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";
|
|
@@ -1182,7 +1257,7 @@ function textLoaderPlugin() {
|
|
|
1182
1257
|
init_file_loader();
|
|
1183
1258
|
import { mkdir, writeFile } from "fs/promises";
|
|
1184
1259
|
import { tmpdir as tmpdir2 } from "os";
|
|
1185
|
-
import { join as
|
|
1260
|
+
import { join as join5, relative } from "path";
|
|
1186
1261
|
|
|
1187
1262
|
// src/lib/deploy/hash.ts
|
|
1188
1263
|
import { createHash } from "crypto";
|
|
@@ -1201,7 +1276,7 @@ async function discoverRoutes(apiDir) {
|
|
|
1201
1276
|
const routes = await Promise.all(
|
|
1202
1277
|
files.map(async (file) => {
|
|
1203
1278
|
const routePath = filePathToRoutePath(file);
|
|
1204
|
-
const absolutePath =
|
|
1279
|
+
const absolutePath = join5(apiDir, file);
|
|
1205
1280
|
const relativePath = relative(getWorkspace(), absolutePath);
|
|
1206
1281
|
const methods = await detectExportedMethods(absolutePath);
|
|
1207
1282
|
return {
|
|
@@ -1261,10 +1336,10 @@ async function transpileRoute(filePath) {
|
|
|
1261
1336
|
if (!result.outputFiles?.[0]) {
|
|
1262
1337
|
throw new Error("Transpilation failed: no output");
|
|
1263
1338
|
}
|
|
1264
|
-
const tempDir =
|
|
1339
|
+
const tempDir = join5(tmpdir2(), "playcademy-dev");
|
|
1265
1340
|
await mkdir(tempDir, { recursive: true });
|
|
1266
1341
|
const hash = hashContent(filePath).slice(0, 12);
|
|
1267
|
-
const jsPath =
|
|
1342
|
+
const jsPath = join5(tempDir, `${hash}.mjs`);
|
|
1268
1343
|
await writeFile(jsPath, result.outputFiles[0].text);
|
|
1269
1344
|
return jsPath;
|
|
1270
1345
|
}
|
|
@@ -1275,7 +1350,7 @@ async function discoverCustomRoutes(config) {
|
|
|
1275
1350
|
const workspace = getWorkspace();
|
|
1276
1351
|
const customRoutesConfig = config.integrations?.customRoutes;
|
|
1277
1352
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
1278
|
-
const customRoutes = await discoverRoutes(
|
|
1353
|
+
const customRoutes = await discoverRoutes(join6(workspace, customRoutesDir));
|
|
1279
1354
|
const customRouteData = customRoutes.map((r) => ({
|
|
1280
1355
|
path: r.path,
|
|
1281
1356
|
file: r.file,
|
|
@@ -1287,15 +1362,15 @@ async function discoverCustomRoutes(config) {
|
|
|
1287
1362
|
function resolveEmbeddedSourcePaths() {
|
|
1288
1363
|
const workspace = getWorkspace();
|
|
1289
1364
|
const distDir = new URL(".", import.meta.url).pathname;
|
|
1290
|
-
const embeddedEdgeSrc =
|
|
1291
|
-
const isBuiltPackage =
|
|
1365
|
+
const embeddedEdgeSrc = join6(distDir, "edge-play", "src");
|
|
1366
|
+
const isBuiltPackage = existsSync5(embeddedEdgeSrc);
|
|
1292
1367
|
const monorepoRoot = getMonorepoRoot();
|
|
1293
|
-
const monorepoEdgeSrc =
|
|
1368
|
+
const monorepoEdgeSrc = join6(monorepoRoot, "packages/edge-play/src");
|
|
1294
1369
|
const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
|
|
1295
|
-
const cliPackageRoot = isBuiltPackage ?
|
|
1296
|
-
const cliNodeModules = isBuiltPackage ?
|
|
1297
|
-
const workspaceNodeModules =
|
|
1298
|
-
const constantsEntry = isBuiltPackage ?
|
|
1370
|
+
const cliPackageRoot = isBuiltPackage ? join6(distDir, "../../..") : join6(monorepoRoot, "packages/cli");
|
|
1371
|
+
const cliNodeModules = isBuiltPackage ? join6(cliPackageRoot, "node_modules") : monorepoRoot;
|
|
1372
|
+
const workspaceNodeModules = join6(workspace, "node_modules");
|
|
1373
|
+
const constantsEntry = isBuiltPackage ? join6(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join6(monorepoRoot, "packages", "constants", "src", "index.ts");
|
|
1299
1374
|
return {
|
|
1300
1375
|
isBuiltPackage,
|
|
1301
1376
|
edgePlaySrc,
|
|
@@ -1355,16 +1430,16 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
|
|
|
1355
1430
|
// │ Example: import * as route from '@game-api/hello.ts' │
|
|
1356
1431
|
// │ Resolves to: /user-project/server/api/hello.ts │
|
|
1357
1432
|
// └─────────────────────────────────────────────────────────────────┘
|
|
1358
|
-
"@game-api":
|
|
1433
|
+
"@game-api": join6(workspace, customRoutesDir),
|
|
1359
1434
|
// ┌─ Node.js polyfills for Cloudflare Workers ──────────────────────┐
|
|
1360
1435
|
// │ Workers don't have fs, path, os, etc. Redirect to polyfills │
|
|
1361
1436
|
// │ that throw helpful errors if user code tries to use them. │
|
|
1362
1437
|
// └─────────────────────────────────────────────────────────────────┘
|
|
1363
|
-
fs:
|
|
1364
|
-
"fs/promises":
|
|
1365
|
-
path:
|
|
1366
|
-
os:
|
|
1367
|
-
process:
|
|
1438
|
+
fs: join6(edgePlaySrc, "polyfills.js"),
|
|
1439
|
+
"fs/promises": join6(edgePlaySrc, "polyfills.js"),
|
|
1440
|
+
path: join6(edgePlaySrc, "polyfills.js"),
|
|
1441
|
+
os: join6(edgePlaySrc, "polyfills.js"),
|
|
1442
|
+
process: join6(edgePlaySrc, "polyfills.js")
|
|
1368
1443
|
},
|
|
1369
1444
|
// ──── Build Plugins ────
|
|
1370
1445
|
plugins: [textLoaderPlugin()],
|
|
@@ -1431,8 +1506,8 @@ import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
|
1431
1506
|
import { bold as bold3, cyan as cyan2 } from "colorette";
|
|
1432
1507
|
|
|
1433
1508
|
// src/lib/init/database.ts
|
|
1434
|
-
import { existsSync as
|
|
1435
|
-
import { join as
|
|
1509
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1510
|
+
import { join as join7 } from "path";
|
|
1436
1511
|
var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
|
|
1437
1512
|
var dbSchemaUsersTemplate = loadTemplateString("database/db-schema-users.ts");
|
|
1438
1513
|
var dbSchemaScoresTemplate = loadTemplateString("database/db-schema-scores.ts");
|
|
@@ -1440,27 +1515,93 @@ var dbSchemaIndexTemplate = loadTemplateString("database/db-schema-index.ts");
|
|
|
1440
1515
|
var dbIndexTemplate = loadTemplateString("database/db-index.ts");
|
|
1441
1516
|
var dbTypesTemplate = loadTemplateString("database/db-types.ts");
|
|
1442
1517
|
var packageTemplate = loadTemplateString("database/package.json");
|
|
1443
|
-
var rootGitignoreTemplate = loadTemplateString("gitignore");
|
|
1444
1518
|
function hasDatabaseSetup() {
|
|
1445
1519
|
const workspace = getWorkspace();
|
|
1446
|
-
const drizzleConfigPath =
|
|
1447
|
-
const drizzleConfigJsPath =
|
|
1448
|
-
return
|
|
1520
|
+
const drizzleConfigPath = join7(workspace, "drizzle.config.ts");
|
|
1521
|
+
const drizzleConfigJsPath = join7(workspace, "drizzle.config.js");
|
|
1522
|
+
return existsSync6(drizzleConfigPath) || existsSync6(drizzleConfigJsPath);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// src/lib/init/types.ts
|
|
1526
|
+
init_file_loader();
|
|
1527
|
+
import { execSync as execSync2 } from "child_process";
|
|
1528
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
1529
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
1530
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1531
|
+
|
|
1532
|
+
// src/lib/init/bucket.ts
|
|
1533
|
+
function hasBucketSetup(config) {
|
|
1534
|
+
return !!config.integrations?.bucket;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// src/lib/init/kv.ts
|
|
1538
|
+
function hasKVSetup(config) {
|
|
1539
|
+
return !!config.integrations?.kv;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/lib/init/types.ts
|
|
1543
|
+
var playcademyEnvTemplate = loadTemplateString("playcademy-env.d.ts");
|
|
1544
|
+
async function ensurePlaycademyTypes() {
|
|
1545
|
+
try {
|
|
1546
|
+
const workspace = getWorkspace();
|
|
1547
|
+
const config = await loadConfig();
|
|
1548
|
+
const hasDB = hasDatabaseSetup();
|
|
1549
|
+
const hasKV = hasKVSetup(config);
|
|
1550
|
+
const hasBucket = hasBucketSetup(config);
|
|
1551
|
+
if (!hasDB && !hasKV && !hasBucket) {
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
const playcademyDir = join8(workspace, CLI_DIRECTORIES.WORKSPACE);
|
|
1555
|
+
const playcademyPkgPath = join8(playcademyDir, "package.json");
|
|
1556
|
+
const __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
1557
|
+
const cliPkg = await loadPackageJson({ cwd: __dirname, searchUp: true, required: true });
|
|
1558
|
+
const workersTypesVersion = cliPkg?.devDependencies?.["@cloudflare/workers-types"] || "latest";
|
|
1559
|
+
const playcademyPkg = {
|
|
1560
|
+
private: true,
|
|
1561
|
+
devDependencies: {
|
|
1562
|
+
"@cloudflare/workers-types": workersTypesVersion
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
writeFileSync3(playcademyPkgPath, JSON.stringify(playcademyPkg, null, 4) + "\n");
|
|
1566
|
+
const pm = detectPackageManager(workspace);
|
|
1567
|
+
const installCmd = getInstallCommand(pm);
|
|
1568
|
+
execSync2(installCmd, {
|
|
1569
|
+
cwd: playcademyDir,
|
|
1570
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
1571
|
+
});
|
|
1572
|
+
const bindings = [];
|
|
1573
|
+
if (hasKV) {
|
|
1574
|
+
bindings.push(" KV: KVNamespace");
|
|
1575
|
+
}
|
|
1576
|
+
if (hasDB) {
|
|
1577
|
+
bindings.push(" DB: D1Database");
|
|
1578
|
+
}
|
|
1579
|
+
if (hasBucket) {
|
|
1580
|
+
bindings.push(" BUCKET: R2Bucket");
|
|
1581
|
+
}
|
|
1582
|
+
const bindingsStr = bindings.length > 0 ? "\n" + bindings.join("\n") : "";
|
|
1583
|
+
const envContent = playcademyEnvTemplate.replace("{{BINDINGS}}", bindingsStr);
|
|
1584
|
+
const envPath = join8(workspace, "playcademy-env.d.ts");
|
|
1585
|
+
writeFileSync3(envPath, envContent);
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
logger.warn(
|
|
1588
|
+
`Failed to generate TypeScript types: ${error instanceof Error ? error.message : String(error)}`
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1449
1591
|
}
|
|
1450
1592
|
|
|
1451
1593
|
// src/lib/init/scaffold.ts
|
|
1452
1594
|
var sampleCustomRouteTemplate = loadTemplateString("api/sample-custom.ts");
|
|
1453
1595
|
var sampleDatabaseRouteTemplate = loadTemplateString("api/sample-database.ts");
|
|
1454
1596
|
var sampleKvRouteTemplate = loadTemplateString("api/sample-kv.ts");
|
|
1597
|
+
var sampleBucketRouteTemplate = loadTemplateString("api/sample-bucket.ts");
|
|
1455
1598
|
var playcademyGitignoreTemplate = loadTemplateString("playcademy-gitignore");
|
|
1456
1599
|
|
|
1457
1600
|
// src/lib/init/display.ts
|
|
1458
1601
|
import { blueBright } from "colorette";
|
|
1459
1602
|
|
|
1460
|
-
// src/lib/init/
|
|
1461
|
-
|
|
1462
|
-
return !!config.integrations?.kv;
|
|
1463
|
-
}
|
|
1603
|
+
// src/lib/init/gitignore.ts
|
|
1604
|
+
var rootGitignoreTemplate = loadTemplateString("gitignore");
|
|
1464
1605
|
|
|
1465
1606
|
// src/lib/dev/server.ts
|
|
1466
1607
|
var FilteredLog = class extends Log {
|
|
@@ -1470,14 +1611,10 @@ var FilteredLog = class extends Log {
|
|
|
1470
1611
|
}
|
|
1471
1612
|
};
|
|
1472
1613
|
async function startDevServer(options) {
|
|
1473
|
-
const {
|
|
1474
|
-
port: preferredPort,
|
|
1475
|
-
config: providedConfig,
|
|
1476
|
-
platformUrl = process.env.PLAYCADEMY_BASE_URL || "http://localhost:5174",
|
|
1477
|
-
logger: logger2 = true
|
|
1478
|
-
} = options;
|
|
1614
|
+
const { port: preferredPort, config: providedConfig, platformUrl, logger: logger2 = true } = options;
|
|
1479
1615
|
const port = await findAvailablePort(preferredPort);
|
|
1480
1616
|
const config = providedConfig ?? await loadConfig();
|
|
1617
|
+
await ensurePlaycademyTypes();
|
|
1481
1618
|
const hasSandboxTimebackCreds = !!process.env.TIMEBACK_API_CLIENT_ID;
|
|
1482
1619
|
const devConfig = config.integrations?.timeback && !hasSandboxTimebackCreds ? { ...config, integrations: { ...config.integrations, timeback: void 0 } } : config;
|
|
1483
1620
|
const bundle = await bundleBackend(devConfig, {
|
|
@@ -1488,7 +1625,11 @@ async function startDevServer(options) {
|
|
|
1488
1625
|
const dbDir = hasDatabase ? await ensureDatabaseDirectory() : void 0;
|
|
1489
1626
|
const hasKV = hasKVSetup(config);
|
|
1490
1627
|
const kvDir = hasKV ? await ensureKvDirectory() : void 0;
|
|
1628
|
+
const hasBucket = hasBucketSetup(config);
|
|
1629
|
+
const bucketDir = hasBucket ? await ensureBucketDirectory() : void 0;
|
|
1491
1630
|
const log2 = logger2 ? new FilteredLog(LogLevel.INFO) : new Log(LogLevel.NONE);
|
|
1631
|
+
const sandboxInfo = readServerInfo("sandbox", getWorkspace());
|
|
1632
|
+
const baseUrl = platformUrl ?? sandboxInfo?.url ?? process.env.PLAYCADEMY_BASE_URL ?? `http://localhost:${DEFAULT_PORTS.SANDBOX}`;
|
|
1492
1633
|
const mf = new Miniflare({
|
|
1493
1634
|
port,
|
|
1494
1635
|
log: log2,
|
|
@@ -1502,12 +1643,14 @@ async function startDevServer(options) {
|
|
|
1502
1643
|
bindings: {
|
|
1503
1644
|
PLAYCADEMY_API_KEY: process.env.PLAYCADEMY_API_KEY || "dev-api-key",
|
|
1504
1645
|
GAME_ID: CORE_GAME_UUIDS.PLAYGROUND,
|
|
1505
|
-
PLAYCADEMY_BASE_URL:
|
|
1646
|
+
PLAYCADEMY_BASE_URL: baseUrl
|
|
1506
1647
|
},
|
|
1507
1648
|
d1Databases: hasDatabase ? ["DB"] : [],
|
|
1508
1649
|
d1Persist: dbDir,
|
|
1509
1650
|
kvNamespaces: hasKV ? ["KV"] : [],
|
|
1510
1651
|
kvPersist: kvDir,
|
|
1652
|
+
r2Buckets: hasBucket ? ["BUCKET"] : [],
|
|
1653
|
+
r2Persist: bucketDir,
|
|
1511
1654
|
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
1512
1655
|
});
|
|
1513
1656
|
if (hasDatabase) {
|
|
@@ -1517,7 +1660,7 @@ async function startDevServer(options) {
|
|
|
1517
1660
|
return { server: mf, port };
|
|
1518
1661
|
}
|
|
1519
1662
|
async function ensureDatabaseDirectory() {
|
|
1520
|
-
const dbDir =
|
|
1663
|
+
const dbDir = join9(getWorkspace(), CLI_DIRECTORIES.DATABASE);
|
|
1521
1664
|
try {
|
|
1522
1665
|
await mkdir2(dbDir, { recursive: true });
|
|
1523
1666
|
} catch (error) {
|
|
@@ -1526,7 +1669,7 @@ async function ensureDatabaseDirectory() {
|
|
|
1526
1669
|
return dbDir;
|
|
1527
1670
|
}
|
|
1528
1671
|
async function ensureKvDirectory() {
|
|
1529
|
-
const kvDir =
|
|
1672
|
+
const kvDir = join9(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
1530
1673
|
try {
|
|
1531
1674
|
await mkdir2(kvDir, { recursive: true });
|
|
1532
1675
|
} catch (error) {
|
|
@@ -1534,6 +1677,15 @@ async function ensureKvDirectory() {
|
|
|
1534
1677
|
}
|
|
1535
1678
|
return kvDir;
|
|
1536
1679
|
}
|
|
1680
|
+
async function ensureBucketDirectory() {
|
|
1681
|
+
const bucketDir = join9(getWorkspace(), CLI_DIRECTORIES.BUCKET);
|
|
1682
|
+
try {
|
|
1683
|
+
await mkdir2(bucketDir, { recursive: true });
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
throw new Error(`Failed to create bucket directory: ${getErrorMessage(error)}`);
|
|
1686
|
+
}
|
|
1687
|
+
return bucketDir;
|
|
1688
|
+
}
|
|
1537
1689
|
async function initializeDatabase(mf) {
|
|
1538
1690
|
const d1 = await mf.getD1Database("DB");
|
|
1539
1691
|
await d1.exec("SELECT 1");
|
|
@@ -1549,7 +1701,7 @@ async function writeBackendServerInfo(port) {
|
|
|
1549
1701
|
}
|
|
1550
1702
|
|
|
1551
1703
|
// src/lib/dev/reload.ts
|
|
1552
|
-
import { join as
|
|
1704
|
+
import { join as join10, relative as relative2 } from "path";
|
|
1553
1705
|
import chokidar from "chokidar";
|
|
1554
1706
|
import { bold as bold4, cyan as cyan3, dim as dim3, green as green2 } from "colorette";
|
|
1555
1707
|
function formatTime() {
|
|
@@ -1566,9 +1718,9 @@ function startHotReload(onReload, options = {}) {
|
|
|
1566
1718
|
const customRoutesConfig = options.config?.integrations?.customRoutes;
|
|
1567
1719
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
1568
1720
|
const watchPaths = [
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1721
|
+
join10(workspace, customRoutesDir),
|
|
1722
|
+
join10(workspace, "playcademy.config.js"),
|
|
1723
|
+
join10(workspace, "playcademy.config.json")
|
|
1572
1724
|
];
|
|
1573
1725
|
const watcher = chokidar.watch(watchPaths, {
|
|
1574
1726
|
persistent: true,
|