playcademy 0.13.9 → 0.13.11
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 +928 -210
- 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 +205 -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,96 @@ 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
|
+
dependencies: {
|
|
1562
|
+
hono: "latest"
|
|
1563
|
+
},
|
|
1564
|
+
devDependencies: {
|
|
1565
|
+
"@cloudflare/workers-types": workersTypesVersion
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
writeFileSync3(playcademyPkgPath, JSON.stringify(playcademyPkg, null, 4) + "\n");
|
|
1569
|
+
const pm = detectPackageManager(workspace);
|
|
1570
|
+
const installCmd = getInstallCommand(pm);
|
|
1571
|
+
execSync2(installCmd, {
|
|
1572
|
+
cwd: playcademyDir,
|
|
1573
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
1574
|
+
});
|
|
1575
|
+
const bindings = [];
|
|
1576
|
+
if (hasKV) {
|
|
1577
|
+
bindings.push(" KV: KVNamespace");
|
|
1578
|
+
}
|
|
1579
|
+
if (hasDB) {
|
|
1580
|
+
bindings.push(" DB: D1Database");
|
|
1581
|
+
}
|
|
1582
|
+
if (hasBucket) {
|
|
1583
|
+
bindings.push(" BUCKET: R2Bucket");
|
|
1584
|
+
}
|
|
1585
|
+
const bindingsStr = bindings.length > 0 ? "\n" + bindings.join("\n") : "";
|
|
1586
|
+
const envContent = playcademyEnvTemplate.replace("{{BINDINGS}}", bindingsStr);
|
|
1587
|
+
const envPath = join8(workspace, "playcademy-env.d.ts");
|
|
1588
|
+
writeFileSync3(envPath, envContent);
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
logger.warn(
|
|
1591
|
+
`Failed to generate TypeScript types: ${error instanceof Error ? error.message : String(error)}`
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1449
1594
|
}
|
|
1450
1595
|
|
|
1451
1596
|
// src/lib/init/scaffold.ts
|
|
1452
1597
|
var sampleCustomRouteTemplate = loadTemplateString("api/sample-custom.ts");
|
|
1453
1598
|
var sampleDatabaseRouteTemplate = loadTemplateString("api/sample-database.ts");
|
|
1454
1599
|
var sampleKvRouteTemplate = loadTemplateString("api/sample-kv.ts");
|
|
1600
|
+
var sampleBucketRouteTemplate = loadTemplateString("api/sample-bucket.ts");
|
|
1455
1601
|
var playcademyGitignoreTemplate = loadTemplateString("playcademy-gitignore");
|
|
1456
1602
|
|
|
1457
1603
|
// src/lib/init/display.ts
|
|
1458
1604
|
import { blueBright } from "colorette";
|
|
1459
1605
|
|
|
1460
|
-
// src/lib/init/
|
|
1461
|
-
|
|
1462
|
-
return !!config.integrations?.kv;
|
|
1463
|
-
}
|
|
1606
|
+
// src/lib/init/gitignore.ts
|
|
1607
|
+
var rootGitignoreTemplate = loadTemplateString("gitignore");
|
|
1464
1608
|
|
|
1465
1609
|
// src/lib/dev/server.ts
|
|
1466
1610
|
var FilteredLog = class extends Log {
|
|
@@ -1470,14 +1614,10 @@ var FilteredLog = class extends Log {
|
|
|
1470
1614
|
}
|
|
1471
1615
|
};
|
|
1472
1616
|
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;
|
|
1617
|
+
const { port: preferredPort, config: providedConfig, platformUrl, logger: logger2 = true } = options;
|
|
1479
1618
|
const port = await findAvailablePort(preferredPort);
|
|
1480
1619
|
const config = providedConfig ?? await loadConfig();
|
|
1620
|
+
await ensurePlaycademyTypes();
|
|
1481
1621
|
const hasSandboxTimebackCreds = !!process.env.TIMEBACK_API_CLIENT_ID;
|
|
1482
1622
|
const devConfig = config.integrations?.timeback && !hasSandboxTimebackCreds ? { ...config, integrations: { ...config.integrations, timeback: void 0 } } : config;
|
|
1483
1623
|
const bundle = await bundleBackend(devConfig, {
|
|
@@ -1488,7 +1628,11 @@ async function startDevServer(options) {
|
|
|
1488
1628
|
const dbDir = hasDatabase ? await ensureDatabaseDirectory() : void 0;
|
|
1489
1629
|
const hasKV = hasKVSetup(config);
|
|
1490
1630
|
const kvDir = hasKV ? await ensureKvDirectory() : void 0;
|
|
1631
|
+
const hasBucket = hasBucketSetup(config);
|
|
1632
|
+
const bucketDir = hasBucket ? await ensureBucketDirectory() : void 0;
|
|
1491
1633
|
const log2 = logger2 ? new FilteredLog(LogLevel.INFO) : new Log(LogLevel.NONE);
|
|
1634
|
+
const sandboxInfo = readServerInfo("sandbox", getWorkspace());
|
|
1635
|
+
const baseUrl = platformUrl ?? sandboxInfo?.url ?? process.env.PLAYCADEMY_BASE_URL ?? `http://localhost:${DEFAULT_PORTS.SANDBOX}`;
|
|
1492
1636
|
const mf = new Miniflare({
|
|
1493
1637
|
port,
|
|
1494
1638
|
log: log2,
|
|
@@ -1502,12 +1646,14 @@ async function startDevServer(options) {
|
|
|
1502
1646
|
bindings: {
|
|
1503
1647
|
PLAYCADEMY_API_KEY: process.env.PLAYCADEMY_API_KEY || "dev-api-key",
|
|
1504
1648
|
GAME_ID: CORE_GAME_UUIDS.PLAYGROUND,
|
|
1505
|
-
PLAYCADEMY_BASE_URL:
|
|
1649
|
+
PLAYCADEMY_BASE_URL: baseUrl
|
|
1506
1650
|
},
|
|
1507
1651
|
d1Databases: hasDatabase ? ["DB"] : [],
|
|
1508
1652
|
d1Persist: dbDir,
|
|
1509
1653
|
kvNamespaces: hasKV ? ["KV"] : [],
|
|
1510
1654
|
kvPersist: kvDir,
|
|
1655
|
+
r2Buckets: hasBucket ? ["BUCKET"] : [],
|
|
1656
|
+
r2Persist: bucketDir,
|
|
1511
1657
|
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
1512
1658
|
});
|
|
1513
1659
|
if (hasDatabase) {
|
|
@@ -1517,7 +1663,7 @@ async function startDevServer(options) {
|
|
|
1517
1663
|
return { server: mf, port };
|
|
1518
1664
|
}
|
|
1519
1665
|
async function ensureDatabaseDirectory() {
|
|
1520
|
-
const dbDir =
|
|
1666
|
+
const dbDir = join9(getWorkspace(), CLI_DIRECTORIES.DATABASE);
|
|
1521
1667
|
try {
|
|
1522
1668
|
await mkdir2(dbDir, { recursive: true });
|
|
1523
1669
|
} catch (error) {
|
|
@@ -1526,7 +1672,7 @@ async function ensureDatabaseDirectory() {
|
|
|
1526
1672
|
return dbDir;
|
|
1527
1673
|
}
|
|
1528
1674
|
async function ensureKvDirectory() {
|
|
1529
|
-
const kvDir =
|
|
1675
|
+
const kvDir = join9(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
1530
1676
|
try {
|
|
1531
1677
|
await mkdir2(kvDir, { recursive: true });
|
|
1532
1678
|
} catch (error) {
|
|
@@ -1534,6 +1680,15 @@ async function ensureKvDirectory() {
|
|
|
1534
1680
|
}
|
|
1535
1681
|
return kvDir;
|
|
1536
1682
|
}
|
|
1683
|
+
async function ensureBucketDirectory() {
|
|
1684
|
+
const bucketDir = join9(getWorkspace(), CLI_DIRECTORIES.BUCKET);
|
|
1685
|
+
try {
|
|
1686
|
+
await mkdir2(bucketDir, { recursive: true });
|
|
1687
|
+
} catch (error) {
|
|
1688
|
+
throw new Error(`Failed to create bucket directory: ${getErrorMessage(error)}`);
|
|
1689
|
+
}
|
|
1690
|
+
return bucketDir;
|
|
1691
|
+
}
|
|
1537
1692
|
async function initializeDatabase(mf) {
|
|
1538
1693
|
const d1 = await mf.getD1Database("DB");
|
|
1539
1694
|
await d1.exec("SELECT 1");
|
|
@@ -1549,7 +1704,7 @@ async function writeBackendServerInfo(port) {
|
|
|
1549
1704
|
}
|
|
1550
1705
|
|
|
1551
1706
|
// src/lib/dev/reload.ts
|
|
1552
|
-
import { join as
|
|
1707
|
+
import { join as join10, relative as relative2 } from "path";
|
|
1553
1708
|
import chokidar from "chokidar";
|
|
1554
1709
|
import { bold as bold4, cyan as cyan3, dim as dim3, green as green2 } from "colorette";
|
|
1555
1710
|
function formatTime() {
|
|
@@ -1566,9 +1721,9 @@ function startHotReload(onReload, options = {}) {
|
|
|
1566
1721
|
const customRoutesConfig = options.config?.integrations?.customRoutes;
|
|
1567
1722
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
1568
1723
|
const watchPaths = [
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1724
|
+
join10(workspace, customRoutesDir),
|
|
1725
|
+
join10(workspace, "playcademy.config.js"),
|
|
1726
|
+
join10(workspace, "playcademy.config.json")
|
|
1572
1727
|
];
|
|
1573
1728
|
const watcher = chokidar.watch(watchPaths, {
|
|
1574
1729
|
persistent: true,
|