playcademy 0.13.19 → 0.13.21

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/index.js CHANGED
@@ -117,7 +117,8 @@ async function loadFile(filename, options = {}) {
117
117
  required = false,
118
118
  searchUp = false,
119
119
  maxLevels = 3,
120
- parseJson = false
120
+ parseJson = false,
121
+ stripComments = false
121
122
  } = options;
122
123
  let fileResult;
123
124
  if (searchUp) {
@@ -146,8 +147,11 @@ async function loadFile(filename, options = {}) {
146
147
  return null;
147
148
  }
148
149
  try {
149
- const content = await readFile(fileResult.path, "utf-8");
150
+ let content = await readFile(fileResult.path, "utf-8");
150
151
  if (parseJson) {
152
+ if (stripComments) {
153
+ content = stripJsonComments(content);
154
+ }
151
155
  return JSON.parse(content);
152
156
  }
153
157
  return content;
@@ -212,6 +216,11 @@ function getCurrentDirectoryName(fallback = "unknown-directory") {
212
216
  function getFileExtension(path2) {
213
217
  return path2.split(".").pop()?.toLowerCase();
214
218
  }
219
+ function stripJsonComments(jsonc) {
220
+ let result = jsonc.replace(/\/\*[\s\S]*?\*\//g, "");
221
+ result = result.replace(/\/\/.*/g, "");
222
+ return result;
223
+ }
215
224
  async function loadModule(filename, options = {}) {
216
225
  const { cwd = process.cwd(), required = false, searchUp = false, maxLevels = 3 } = options;
217
226
  let fileResult;
@@ -2099,6 +2108,28 @@ var init_api = __esm({
2099
2108
  }
2100
2109
  });
2101
2110
 
2111
+ // src/constants/config.ts
2112
+ var ENV_FILES, TSCONFIG_FILES;
2113
+ var init_config = __esm({
2114
+ "src/constants/config.ts"() {
2115
+ "use strict";
2116
+ ENV_FILES = [
2117
+ ".env",
2118
+ // Loaded first
2119
+ ".env.development",
2120
+ // Overrides .env
2121
+ ".env.local"
2122
+ // Overrides all (highest priority)
2123
+ ];
2124
+ TSCONFIG_FILES = [
2125
+ "tsconfig.app.json",
2126
+ // Modern tooling (try first)
2127
+ "tsconfig.json"
2128
+ // Standard (fallback)
2129
+ ];
2130
+ }
2131
+ });
2132
+
2102
2133
  // src/constants/database.ts
2103
2134
  var init_database = __esm({
2104
2135
  "src/constants/database.ts"() {
@@ -2311,6 +2342,7 @@ var init_constants = __esm({
2311
2342
  "src/constants/index.ts"() {
2312
2343
  "use strict";
2313
2344
  init_api();
2345
+ init_config();
2314
2346
  init_database();
2315
2347
  init_http_server();
2316
2348
  init_paths();
@@ -2364,6 +2396,7 @@ var init_context = __esm({
2364
2396
  // src/lib/core/logger.ts
2365
2397
  import {
2366
2398
  blue,
2399
+ blueBright,
2367
2400
  bold as bold2,
2368
2401
  cyan,
2369
2402
  dim as dim2,
@@ -2376,8 +2409,10 @@ import {
2376
2409
  } from "colorette";
2377
2410
  import { colorize } from "json-colorizer";
2378
2411
  function customTransform(text5) {
2379
- const highlightCode = (text6) => text6.replace(/`([^`]+)`/g, (_, code) => greenBright(code));
2380
- return highlightCode(text5);
2412
+ let result = text5;
2413
+ result = result.replace(/`([^`]+)`/g, (_, code) => greenBright(code));
2414
+ result = result.replace(/<([^>]+)>/g, (_, path2) => blueBright(path2));
2415
+ return result;
2381
2416
  }
2382
2417
  function formatTable(data, title) {
2383
2418
  if (data.length === 0) return;
@@ -2545,6 +2580,10 @@ var init_logger = __esm({
2545
2580
  const spaces = " ".repeat(indent);
2546
2581
  console.log(`${spaces}${text5}`);
2547
2582
  },
2583
+ customRaw: (text5, indent = 0) => {
2584
+ const spaces = " ".repeat(indent);
2585
+ console.log(`${spaces}${customTransform(text5)}`);
2586
+ },
2548
2587
  /**
2549
2588
  * Display a configuration error with helpful suggestions
2550
2589
  */
@@ -2625,7 +2664,7 @@ function getProfileName() {
2625
2664
  function getWebBaseUrl(apiBaseUrl) {
2626
2665
  return apiBaseUrl.replace(/\/api\/?$/, "");
2627
2666
  }
2628
- var init_config = __esm({
2667
+ var init_config2 = __esm({
2629
2668
  "src/lib/core/config.ts"() {
2630
2669
  "use strict";
2631
2670
  init_constants2();
@@ -2757,7 +2796,7 @@ var init_storage = __esm({
2757
2796
  "src/lib/auth/storage.ts"() {
2758
2797
  "use strict";
2759
2798
  init_constants2();
2760
- init_config();
2799
+ init_config2();
2761
2800
  }
2762
2801
  });
2763
2802
 
@@ -3154,7 +3193,7 @@ var init_game = __esm({
3154
3193
  "use strict";
3155
3194
  init_src();
3156
3195
  init_slug();
3157
- init_config2();
3196
+ init_config3();
3158
3197
  }
3159
3198
  });
3160
3199
 
@@ -3573,7 +3612,7 @@ __export(config_exports, {
3573
3612
  updateConfigFile: () => updateConfigFile,
3574
3613
  validateConfig: () => validateConfig
3575
3614
  });
3576
- var init_config2 = __esm({
3615
+ var init_config3 = __esm({
3577
3616
  "src/lib/config/index.ts"() {
3578
3617
  "use strict";
3579
3618
  init_loader();
@@ -3617,7 +3656,7 @@ var init_client = __esm({
3617
3656
  "use strict";
3618
3657
  init_storage();
3619
3658
  init_logger();
3620
- init_config();
3659
+ init_config2();
3621
3660
  }
3622
3661
  });
3623
3662
 
@@ -3715,7 +3754,7 @@ var init_core = __esm({
3715
3754
  "src/lib/core/index.ts"() {
3716
3755
  "use strict";
3717
3756
  init_client();
3718
- init_config();
3757
+ init_config2();
3719
3758
  init_context();
3720
3759
  init_errors();
3721
3760
  init_game();
@@ -3751,7 +3790,7 @@ import { program } from "commander";
3751
3790
 
3752
3791
  // src/commands/init/index.ts
3753
3792
  import { execSync as execSync4 } from "child_process";
3754
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync8 } from "fs";
3793
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync9 } from "fs";
3755
3794
  import { resolve as resolve10 } from "path";
3756
3795
 
3757
3796
  // ../../node_modules/@inquirer/core/dist/esm/lib/errors.js
@@ -4548,7 +4587,7 @@ function getCallbackUrl() {
4548
4587
  }
4549
4588
 
4550
4589
  // src/lib/index.ts
4551
- init_config2();
4590
+ init_config3();
4552
4591
  init_core();
4553
4592
 
4554
4593
  // src/lib/db/path.ts
@@ -4639,8 +4678,8 @@ function resetDatabase() {
4639
4678
  init_src();
4640
4679
  init_constants2();
4641
4680
  init_core();
4642
- import { existsSync as existsSync11 } from "node:fs";
4643
- import { join as join13 } from "node:path";
4681
+ import { existsSync as existsSync13 } from "node:fs";
4682
+ import { join as join15 } from "node:path";
4644
4683
 
4645
4684
  // src/lib/init/bucket.ts
4646
4685
  function hasBucketSetup(config) {
@@ -4805,7 +4844,7 @@ var timebackChangeDetector = {
4805
4844
  return;
4806
4845
  }
4807
4846
  const currentConfig = await client.timeback.management.getConfig(gameId);
4808
- const { deriveTimebackConfig: deriveTimebackConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
4847
+ const { deriveTimebackConfig: deriveTimebackConfig2 } = await Promise.resolve().then(() => (init_config3(), config_exports));
4809
4848
  const newConfig = await deriveTimebackConfig2(config);
4810
4849
  const changes = this.detectChanges(currentConfig, newConfig);
4811
4850
  if (changes.length > 0) {
@@ -4877,7 +4916,7 @@ import { existsSync as existsSync7 } from "fs";
4877
4916
  import { join as join8 } from "path";
4878
4917
 
4879
4918
  // ../edge-play/src/entry.ts
4880
- 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";
4919
+ 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'\n\nimport { registerCors, registerEnvSetup, registerSdkInit } from './entry/middleware'\nimport { setupProcessGlobal } from './entry/setup'\nimport { registerBuiltinRoutes } from './register-routes'\n\nimport type { RuntimeConfig } from './entry/types'\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: RuntimeConfig\n\n// Setup process global polyfill for SDK compatibility\nsetupProcessGlobal()\n\n// Create Hono app\nconst app = new Hono<HonoEnv>()\n\n// Register middleware\nregisterCors(app)\nregisterEnvSetup(app, PLAYCADEMY_CONFIG)\nregisterSdkInit(app, PLAYCADEMY_CONFIG)\n\n// Register built-in integration routes based on enabled integrations\n// This function conditionally imports and registers routes like:\n// - GET /api (always included)\n// - GET /api/health (always included)\n// - POST /api/integrations/timeback/end-activity (if timeback enabled)\n//\n// Uses dynamic imports for tree-shaking: if an integration is not enabled,\n// its route code is completely removed from the bundle.\nawait registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)\n\nexport default app\n";
4881
4920
 
4882
4921
  // ../utils/src/path.ts
4883
4922
  import fs from "node:fs";
@@ -5219,6 +5258,7 @@ function filePathToRoutePath(filePath) {
5219
5258
  routePath = routePath.replace(/\/?index$/, "");
5220
5259
  }
5221
5260
  let urlPath = "/" + routePath.replace(/\\/g, "/");
5261
+ urlPath = urlPath.replace(/\[\.\.\.([^\]]+)\]/g, ":$1{.*}");
5222
5262
  urlPath = urlPath.replace(/\[([^\]]+)\]/g, ":$1");
5223
5263
  urlPath = urlPath === "/" ? "/api" : `/api${urlPath}`;
5224
5264
  return urlPath;
@@ -5380,7 +5420,7 @@ async function bundleBackend(config, options = {}) {
5380
5420
  const { customRouteData, customRoutesDir } = await discoverCustomRoutes(config);
5381
5421
  const bundleConfig = {
5382
5422
  ...config,
5383
- customRoutes: customRouteData
5423
+ __routeMetadata: customRouteData
5384
5424
  };
5385
5425
  const entryCode = generateEntryCode(customRouteData, customRoutesDir);
5386
5426
  const paths = resolveEmbeddedSourcePaths();
@@ -5429,9 +5469,9 @@ function generateEntryCode(customRoutes, customRoutesDir) {
5429
5469
 
5430
5470
  // src/lib/deploy/schema.ts
5431
5471
  init_core();
5432
- import { existsSync as existsSync10 } from "fs";
5472
+ import { existsSync as existsSync12 } from "fs";
5433
5473
  import { createRequire } from "module";
5434
- import { join as join12 } from "path";
5474
+ import { join as join14 } from "path";
5435
5475
 
5436
5476
  // src/lib/init/prompts.ts
5437
5477
  init_constants3();
@@ -5670,7 +5710,6 @@ async function selectConfigFormat(hasPackageJson2) {
5670
5710
  init_package_manager();
5671
5711
  init_context();
5672
5712
  init_logger();
5673
- import { blueBright } from "colorette";
5674
5713
  function displayConfigSuccess(timebackConfig) {
5675
5714
  logger.success("Playcademy configuration created successfully!");
5676
5715
  logger.newLine();
@@ -5698,9 +5737,9 @@ function displaySuccessMessage(context2) {
5698
5737
  logger.newLine();
5699
5738
  const nextSteps = [];
5700
5739
  const pm = getPackageManager();
5701
- nextSteps.push(`1. Review your config file: ${blueBright(context2.configFileName)}`);
5740
+ nextSteps.push(`1. Review your config file: <${context2.configFileName}>`);
5702
5741
  if (context2.hasDatabase) {
5703
- nextSteps.push(`3. Review schema: ${blueBright(`db/schema/index.ts`)}`);
5742
+ nextSteps.push(`3. Review schema: <db/schema/index.ts>`);
5704
5743
  nextSteps.push("4. Start dev server: `playcademy dev`");
5705
5744
  nextSteps.push(`5. Push your schema: \`${getRunCommand(pm, "db:push")}\``);
5706
5745
  }
@@ -5712,7 +5751,7 @@ function displaySuccessMessage(context2) {
5712
5751
  }
5713
5752
  if (context2.apiDirectory) {
5714
5753
  const stepNum = context2.hasDatabase ? "7" : "3";
5715
- nextSteps.push(`${stepNum}. Customize API routes: ${blueBright(`${context2.apiDirectory}`)}`);
5754
+ nextSteps.push(`${stepNum}. Customize API routes: <${context2.apiDirectory}>`);
5716
5755
  }
5717
5756
  if (context2.timebackConfig) {
5718
5757
  const stepNum = nextSteps.length + 1;
@@ -5727,61 +5766,256 @@ function displaySuccessMessage(context2) {
5727
5766
  // src/lib/init/types.ts
5728
5767
  init_file_loader();
5729
5768
  init_package_manager();
5769
+ init_string();
5730
5770
  init_constants2();
5731
5771
  init_loader();
5732
5772
  init_core();
5733
- init_loader2();
5734
5773
  import { execSync as execSync3 } from "child_process";
5735
- import { writeFileSync as writeFileSync4 } from "fs";
5736
- import { dirname as dirname4, join as join10 } from "path";
5774
+ import { writeFileSync as writeFileSync5 } from "fs";
5775
+ import { dirname as dirname4, join as join12 } from "path";
5737
5776
  import { fileURLToPath as fileURLToPath2 } from "url";
5777
+
5778
+ // src/lib/secrets/env.ts
5779
+ init_file_loader();
5780
+ init_constants2();
5781
+ import { existsSync as existsSync9 } from "fs";
5782
+ import { join as join10 } from "path";
5783
+ function parseEnvFile(contents) {
5784
+ const secrets = {};
5785
+ for (const line of contents.split("\n")) {
5786
+ const trimmed = line.trim();
5787
+ if (!trimmed || trimmed.startsWith("#")) {
5788
+ continue;
5789
+ }
5790
+ const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
5791
+ if (match) {
5792
+ const [, key, value] = match;
5793
+ if (!key || !value) {
5794
+ continue;
5795
+ }
5796
+ const unquoted = value.replace(/^["']|["']$/g, "");
5797
+ secrets[key] = unquoted;
5798
+ }
5799
+ }
5800
+ return secrets;
5801
+ }
5802
+ async function readEnvFile(workspace) {
5803
+ let secrets = {};
5804
+ for (const filename of ENV_FILES) {
5805
+ try {
5806
+ const contents = await loadFile(filename, { cwd: workspace, searchUp: false });
5807
+ if (contents) {
5808
+ const fileSecrets = parseEnvFile(contents);
5809
+ secrets = { ...secrets, ...fileSecrets };
5810
+ }
5811
+ } catch {
5812
+ continue;
5813
+ }
5814
+ }
5815
+ return secrets;
5816
+ }
5817
+ function getLoadedEnvFiles(workspace) {
5818
+ return ENV_FILES.filter((filename) => existsSync9(join10(workspace, filename)));
5819
+ }
5820
+ function hasEnvFile(workspace) {
5821
+ return ENV_FILES.some((filename) => existsSync9(join10(workspace, filename)));
5822
+ }
5823
+
5824
+ // src/lib/init/types.ts
5825
+ init_loader2();
5826
+
5827
+ // src/lib/init/tsconfig.ts
5828
+ init_file_loader();
5829
+ init_constants2();
5830
+ import { existsSync as existsSync10, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
5831
+ import { join as join11 } from "path";
5832
+ function hasPlaycademyEnv(config) {
5833
+ return config.include?.includes("playcademy-env.d.ts") ?? false;
5834
+ }
5835
+ function addPlaycademyEnv(config) {
5836
+ if (!config.include) {
5837
+ config.include = [];
5838
+ }
5839
+ config.include.push("playcademy-env.d.ts");
5840
+ }
5841
+ function updateExistingIncludeArray(content) {
5842
+ const includeRegex = /"include"\s*:\s*\[([^\]]*)\]/;
5843
+ const match = content.match(includeRegex);
5844
+ if (!match || !match[1]) {
5845
+ return null;
5846
+ }
5847
+ const fullMatch = match[0];
5848
+ const arrayContents = match[1];
5849
+ if (arrayContents.includes("playcademy-env.d.ts")) {
5850
+ return content;
5851
+ }
5852
+ const trimmedContents = arrayContents.trim();
5853
+ const newEntry = '"playcademy-env.d.ts"';
5854
+ let newArrayContents;
5855
+ if (trimmedContents === "") {
5856
+ newArrayContents = newEntry;
5857
+ } else if (arrayContents.includes("\n")) {
5858
+ newArrayContents = `${arrayContents},
5859
+ ${newEntry}
5860
+ `;
5861
+ } else {
5862
+ newArrayContents = `${trimmedContents}, ${newEntry}`;
5863
+ }
5864
+ const newIncludeArray = `"include": [${newArrayContents}]`;
5865
+ return content.replace(fullMatch, newIncludeArray);
5866
+ }
5867
+ function addNewIncludeProperty(content) {
5868
+ const closingBraceMatch = content.match(/\n(\s*)\}(\s*)$/);
5869
+ if (!closingBraceMatch || closingBraceMatch.index === void 0) {
5870
+ return null;
5871
+ }
5872
+ const propertyMatch = content.match(/\n(\s+)"/);
5873
+ const propertyIndent = propertyMatch ? propertyMatch[1] : " ";
5874
+ const insertPosition = closingBraceMatch.index;
5875
+ const beforeClosing = content.slice(0, insertPosition).trim();
5876
+ const needsComma = beforeClosing.endsWith("]") || beforeClosing.endsWith("}") || beforeClosing.endsWith('"');
5877
+ const comma = needsComma ? "," : "";
5878
+ const includeEntry = `${comma}
5879
+ ${propertyIndent}"include": ["playcademy-env.d.ts"]`;
5880
+ const updatedContent = content.slice(0, insertPosition) + includeEntry + content.slice(insertPosition);
5881
+ return updatedContent;
5882
+ }
5883
+ function addToIncludeArrayPreservingComments(content) {
5884
+ return updateExistingIncludeArray(content) || addNewIncludeProperty(content);
5885
+ }
5886
+ async function ensureTsconfigIncludes(workspace) {
5887
+ for (const filename of TSCONFIG_FILES) {
5888
+ const configPath = join11(workspace, filename);
5889
+ if (!existsSync10(configPath)) {
5890
+ continue;
5891
+ }
5892
+ try {
5893
+ const config = await loadFile(configPath, {
5894
+ parseJson: true,
5895
+ stripComments: true
5896
+ });
5897
+ if (!config) continue;
5898
+ if (config.references && filename !== "tsconfig.json") {
5899
+ continue;
5900
+ }
5901
+ if (hasPlaycademyEnv(config)) {
5902
+ return filename;
5903
+ }
5904
+ try {
5905
+ const rawContent = readFileSync4(configPath, "utf-8");
5906
+ const updatedContent = addToIncludeArrayPreservingComments(rawContent);
5907
+ if (updatedContent && updatedContent !== rawContent) {
5908
+ writeFileSync4(configPath, updatedContent);
5909
+ return filename;
5910
+ }
5911
+ } catch {
5912
+ }
5913
+ addPlaycademyEnv(config);
5914
+ writeFileSync4(configPath, JSON.stringify(config, null, 4) + "\n");
5915
+ return filename;
5916
+ } catch {
5917
+ continue;
5918
+ }
5919
+ }
5920
+ return null;
5921
+ }
5922
+
5923
+ // src/lib/init/types.ts
5738
5924
  var playcademyEnvTemplate = loadTemplateString("playcademy-env.d.ts");
5739
- async function ensurePlaycademyTypes() {
5925
+ function detectBackendFeatures(workspace, config) {
5926
+ return {
5927
+ hasDB: hasDatabaseSetup(),
5928
+ hasKV: hasKVSetup(config),
5929
+ hasBucket: hasBucketSetup(config),
5930
+ hasRoutes: hasLocalCustomRoutes(workspace, config),
5931
+ hasSecrets: hasEnvFile(workspace)
5932
+ };
5933
+ }
5934
+ function hasAnyBackend(features) {
5935
+ return Object.values(features).some(Boolean);
5936
+ }
5937
+ async function setupPlaycademyDependencies(workspace) {
5938
+ const playcademyDir = join12(workspace, CLI_DIRECTORIES.WORKSPACE);
5939
+ const playcademyPkgPath = join12(playcademyDir, "package.json");
5940
+ const __dirname2 = dirname4(fileURLToPath2(import.meta.url));
5941
+ const cliPkg = await loadPackageJson({ cwd: __dirname2, searchUp: true, required: true });
5942
+ const workersTypesVersion = cliPkg?.devDependencies?.["@cloudflare/workers-types"] || "latest";
5943
+ const honoVersion = cliPkg?.dependencies?.hono || "latest";
5944
+ const playcademyPkg = {
5945
+ private: true,
5946
+ dependencies: { hono: honoVersion },
5947
+ devDependencies: { "@cloudflare/workers-types": workersTypesVersion }
5948
+ };
5949
+ writeFileSync5(playcademyPkgPath, JSON.stringify(playcademyPkg, null, 4) + "\n");
5950
+ const pm = detectPackageManager(workspace);
5951
+ const installCmd = getInstallCommand(pm);
5952
+ execSync3(installCmd, {
5953
+ cwd: playcademyDir,
5954
+ stdio: ["ignore", "ignore", "ignore"]
5955
+ });
5956
+ }
5957
+ function generateBindingsTypeString(features) {
5958
+ const bindings = [];
5959
+ if (features.hasKV) bindings.push(" KV: KVNamespace");
5960
+ if (features.hasDB) bindings.push(" DB: D1Database");
5961
+ if (features.hasBucket) bindings.push(" BUCKET: R2Bucket");
5962
+ return bindings.length > 0 ? "\n" + bindings.join("\n") : "";
5963
+ }
5964
+ async function generateSecretsTypeString(workspace, verbose = false) {
5965
+ try {
5966
+ const envSecrets = await readEnvFile(workspace);
5967
+ const secretKeys = Object.keys(envSecrets);
5968
+ if (secretKeys.length === 0) {
5969
+ if (verbose) logger.success("No secrets in <.env> files");
5970
+ return "\n secrets: Record<string, string>";
5971
+ }
5972
+ if (verbose) {
5973
+ const loadedFiles = getLoadedEnvFiles(workspace);
5974
+ const fileList = loadedFiles.map((f) => `<${f}>`).join(", ");
5975
+ logger.success(
5976
+ `Loaded ${secretKeys.length} ${pluralize(secretKeys.length, "secret")} from ${fileList}`
5977
+ );
5978
+ }
5979
+ const secretTypes = secretKeys.map((key) => ` ${key}: string`).join("\n");
5980
+ return `
5981
+ secrets: {
5982
+ ${secretTypes}
5983
+ [key: string]: string
5984
+ }`;
5985
+ } catch {
5986
+ return "\n secrets: Record<string, string>";
5987
+ }
5988
+ }
5989
+ async function ensurePlaycademyTypes(options = {}) {
5990
+ const { verbose = false } = options;
5740
5991
  try {
5741
5992
  const workspace = getWorkspace();
5742
5993
  const config = await loadConfig();
5743
- const hasDB = hasDatabaseSetup();
5744
- const hasKV = hasKVSetup(config);
5745
- const hasBucket = hasBucketSetup(config);
5746
- if (!hasDB && !hasKV && !hasBucket) {
5994
+ const features = detectBackendFeatures(workspace, config);
5995
+ if (!hasAnyBackend(features)) {
5996
+ if (verbose) {
5997
+ logger.dim("No backend functionality detected");
5998
+ }
5747
5999
  return;
5748
6000
  }
5749
- const playcademyDir = join10(workspace, CLI_DIRECTORIES.WORKSPACE);
5750
- const playcademyPkgPath = join10(playcademyDir, "package.json");
5751
- const __dirname2 = dirname4(fileURLToPath2(import.meta.url));
5752
- const cliPkg = await loadPackageJson({ cwd: __dirname2, searchUp: true, required: true });
5753
- const workersTypesVersion = cliPkg?.devDependencies?.["@cloudflare/workers-types"] || "latest";
5754
- const honoVersion = cliPkg?.dependencies?.hono || "latest";
5755
- const playcademyPkg = {
5756
- private: true,
5757
- dependencies: {
5758
- hono: honoVersion
5759
- },
5760
- devDependencies: {
5761
- "@cloudflare/workers-types": workersTypesVersion
5762
- }
5763
- };
5764
- writeFileSync4(playcademyPkgPath, JSON.stringify(playcademyPkg, null, 4) + "\n");
5765
- const pm = detectPackageManager(workspace);
5766
- const installCmd = getInstallCommand(pm);
5767
- execSync3(installCmd, {
5768
- cwd: playcademyDir,
5769
- stdio: ["ignore", "ignore", "ignore"]
5770
- });
5771
- const bindings = [];
5772
- if (hasKV) {
5773
- bindings.push(" KV: KVNamespace");
6001
+ await setupPlaycademyDependencies(workspace);
6002
+ if (verbose) {
6003
+ logger.success(`Installed packages in <${CLI_DIRECTORIES.WORKSPACE}>`);
5774
6004
  }
5775
- if (hasDB) {
5776
- bindings.push(" DB: D1Database");
6005
+ const bindingsStr = generateBindingsTypeString(features);
6006
+ const secretsStr = await generateSecretsTypeString(workspace, verbose);
6007
+ let envContent = playcademyEnvTemplate.replace("{{BINDINGS}}", bindingsStr);
6008
+ envContent = envContent.replace("{{SECRETS}}", secretsStr);
6009
+ const envPath = join12(workspace, "playcademy-env.d.ts");
6010
+ writeFileSync5(envPath, envContent);
6011
+ if (verbose) {
6012
+ logger.success(`Generated <playcademy-env.d.ts>`);
5777
6013
  }
5778
- if (hasBucket) {
5779
- bindings.push(" BUCKET: R2Bucket");
6014
+ const tsConfigFilename = await ensureTsconfigIncludes(workspace);
6015
+ if (verbose) {
6016
+ logger.success(`Updated <${tsConfigFilename}>`);
5780
6017
  }
5781
- const bindingsStr = bindings.length > 0 ? "\n" + bindings.join("\n") : "";
5782
- const envContent = playcademyEnvTemplate.replace("{{BINDINGS}}", bindingsStr);
5783
- const envPath = join10(workspace, "playcademy-env.d.ts");
5784
- writeFileSync4(envPath, envContent);
6018
+ logger.newLine();
5785
6019
  } catch (error) {
5786
6020
  logger.warn(
5787
6021
  `Failed to generate TypeScript types: ${error instanceof Error ? error.message : String(error)}`
@@ -5792,19 +6026,19 @@ async function ensurePlaycademyTypes() {
5792
6026
  // src/lib/init/gitignore.ts
5793
6027
  init_core();
5794
6028
  init_loader2();
5795
- import { existsSync as existsSync9, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
5796
- import { join as join11 } from "path";
6029
+ import { existsSync as existsSync11, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
6030
+ import { join as join13 } from "path";
5797
6031
  var rootGitignoreTemplate = loadTemplateString("gitignore");
5798
6032
  function normalizeGitignoreEntry(entry) {
5799
6033
  return entry.replace(/^\/+/, "").replace(/\/+$/, "");
5800
6034
  }
5801
6035
  function ensureRootGitignore(workspace = getWorkspace()) {
5802
- const gitignorePath = join11(workspace, ".gitignore");
5803
- if (!existsSync9(gitignorePath)) {
5804
- writeFileSync5(gitignorePath, rootGitignoreTemplate);
6036
+ const gitignorePath = join13(workspace, ".gitignore");
6037
+ if (!existsSync11(gitignorePath)) {
6038
+ writeFileSync6(gitignorePath, rootGitignoreTemplate);
5805
6039
  return;
5806
6040
  }
5807
- const existingContent = readFileSync4(gitignorePath, "utf-8");
6041
+ const existingContent = readFileSync5(gitignorePath, "utf-8");
5808
6042
  const existingNormalized = new Set(
5809
6043
  existingContent.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map(normalizeGitignoreEntry)
5810
6044
  );
@@ -5821,7 +6055,7 @@ function ensureRootGitignore(workspace = getWorkspace()) {
5821
6055
  const prefix = hasTrailingNewline ? "\n" : "\n\n";
5822
6056
  const header = "# Generated by Playcademy\n";
5823
6057
  const newEntries = entriesToAdd.join("\n");
5824
- writeFileSync5(gitignorePath, existingContent + prefix + header + newEntries + "\n");
6058
+ writeFileSync6(gitignorePath, existingContent + prefix + header + newEntries + "\n");
5825
6059
  }
5826
6060
 
5827
6061
  // src/lib/deploy/schema.ts
@@ -5839,8 +6073,8 @@ async function getSchemaInfo(previousSchemaSnapshot) {
5839
6073
  if (!hasDatabaseSetup()) {
5840
6074
  return null;
5841
6075
  }
5842
- const schemaPath = join12(workspace, "db/schema/index.ts");
5843
- if (!existsSync10(schemaPath)) {
6076
+ const schemaPath = join14(workspace, "db/schema/index.ts");
6077
+ if (!existsSync12(schemaPath)) {
5844
6078
  return null;
5845
6079
  }
5846
6080
  try {
@@ -5874,6 +6108,42 @@ async function getSchemaStatementCount(previousSchemaSnapshot) {
5874
6108
  return schemaInfo.sql.split(";").filter((stmt) => stmt.trim()).length;
5875
6109
  }
5876
6110
 
6111
+ // src/lib/deploy/secrets.ts
6112
+ init_core();
6113
+ init_logger();
6114
+ function compareSecrets(current, previous) {
6115
+ const currentKeys = Object.keys(current);
6116
+ const changes = [];
6117
+ for (const key of currentKeys) {
6118
+ if (!previous.includes(key)) {
6119
+ changes.push({ key, type: "added" });
6120
+ }
6121
+ }
6122
+ for (const key of previous) {
6123
+ if (!currentKeys.includes(key)) {
6124
+ changes.push({ key, type: "removed" });
6125
+ }
6126
+ }
6127
+ return {
6128
+ hasChanges: changes.length > 0,
6129
+ changes
6130
+ };
6131
+ }
6132
+ async function fetchSecretsForDeployment(slug) {
6133
+ try {
6134
+ const client = await requireAuthenticatedClient();
6135
+ const secrets = await client.dev.games.secrets.get(slug);
6136
+ const keys = Object.keys(secrets);
6137
+ return { secrets, keys };
6138
+ } catch (error) {
6139
+ logger.warn(
6140
+ `Could not fetch secrets: ${error instanceof Error ? error.message : String(error)}`
6141
+ );
6142
+ logger.debug(`[Secrets] Error details: ${error}`);
6143
+ return { secrets: {}, keys: [] };
6144
+ }
6145
+ }
6146
+
5877
6147
  // src/lib/deploy/utils.ts
5878
6148
  init_src2();
5879
6149
  function getDeploymentId(gameSlug) {
@@ -5887,11 +6157,11 @@ var CUSTOM_ROUTES_EXTENSIONS_WITH_DOT = [".ts", ".js", ".mjs", ".cjs"];
5887
6157
  function getCustomRoutesDirectory(projectPath, config) {
5888
6158
  const customRoutes = config?.integrations?.customRoutes;
5889
6159
  const customRoutesDir = typeof customRoutes === "object" && customRoutes.directory || DEFAULT_API_ROUTES_DIRECTORY;
5890
- return join13(projectPath, customRoutesDir);
6160
+ return join15(projectPath, customRoutesDir);
5891
6161
  }
5892
6162
  function hasLocalCustomRoutes(projectPath, config) {
5893
6163
  const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
5894
- return existsSync11(customRoutesDir);
6164
+ return existsSync13(customRoutesDir);
5895
6165
  }
5896
6166
  async function getCustomRoutesHash(projectPath, config) {
5897
6167
  const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
@@ -5899,16 +6169,16 @@ async function getCustomRoutesHash(projectPath, config) {
5899
6169
  }
5900
6170
  async function getCustomRoutesSize(projectPath, config) {
5901
6171
  const { stat: stat3, readdir } = await import("node:fs/promises");
5902
- const { join: join35 } = await import("node:path");
6172
+ const { join: join37 } = await import("node:path");
5903
6173
  const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
5904
- if (!existsSync11(customRoutesDir)) {
6174
+ if (!existsSync13(customRoutesDir)) {
5905
6175
  return null;
5906
6176
  }
5907
6177
  let totalSize = 0;
5908
6178
  async function calculateDirSize(dir) {
5909
6179
  const entries = await readdir(dir, { withFileTypes: true });
5910
6180
  for (const entry of entries) {
5911
- const fullPath = join35(dir, entry.name);
6181
+ const fullPath = join37(dir, entry.name);
5912
6182
  if (entry.isDirectory()) {
5913
6183
  await calculateDirSize(fullPath);
5914
6184
  } else if (entry.isFile()) {
@@ -5958,7 +6228,7 @@ function getBackendBindings(config, slug) {
5958
6228
  }
5959
6229
  return Object.keys(bindings).length > 0 ? bindings : void 0;
5960
6230
  }
5961
- async function deployGameBackend(client, slug, config, projectPath, previousCustomRoutesHash, fullConfig, forceBackend, previousIntegrationKeys, previousSchemaSnapshot, debug) {
6231
+ async function deployGameBackend(client, slug, config, projectPath, previousCustomRoutesHash, fullConfig, forceBackend, previousIntegrationKeys, previousSchemaSnapshot, debug, previousDeployedSecrets) {
5962
6232
  try {
5963
6233
  if (fullConfig === void 0 || fullConfig === null) {
5964
6234
  return null;
@@ -5967,11 +6237,29 @@ async function deployGameBackend(client, slug, config, projectPath, previousCust
5967
6237
  const currentIntegrationKeys = getIntegrationKeys(fullConfig?.integrations);
5968
6238
  const hasIntegrations = currentIntegrationKeys.length > 0;
5969
6239
  const currentIntegrationsHash = hasIntegrations ? hashContent(fullConfig.integrations) : null;
5970
- if (!hasCustomRoutes2 && !hasIntegrations) {
6240
+ const { secrets, keys: currentSecretKeys } = await fetchSecretsForDeployment(slug);
6241
+ const hasSecrets = currentSecretKeys.length > 0;
6242
+ if (!hasCustomRoutes2 && !hasIntegrations && !hasSecrets) {
5971
6243
  return null;
5972
6244
  }
5973
6245
  const currentCustomRoutesHash = hasCustomRoutes2 ? await getCustomRoutesHash(projectPath, fullConfig) : null;
5974
6246
  const currentCustomRoutesSize = hasCustomRoutes2 ? await getCustomRoutesSize(projectPath, fullConfig) : null;
6247
+ const secretsComparison = compareSecrets(secrets, previousDeployedSecrets || []);
6248
+ const secretsChanged = secretsComparison.hasChanges;
6249
+ if (debug) {
6250
+ logger.highlight("Debug: Secrets Info");
6251
+ logger.data("Current secret keys", currentSecretKeys.join(", ") || "none", 1);
6252
+ logger.data(
6253
+ "Previous secret keys",
6254
+ (previousDeployedSecrets || []).join(", ") || "none",
6255
+ 1
6256
+ );
6257
+ logger.data("Secrets changed", String(secretsChanged), 1);
6258
+ if (secretsChanged) {
6259
+ logger.data("Changes", JSON.stringify(secretsComparison.changes), 1);
6260
+ }
6261
+ logger.newLine();
6262
+ }
5975
6263
  const customRoutesChanged = currentCustomRoutesHash !== previousCustomRoutesHash;
5976
6264
  const integrationsComparison = compareIntegrationKeys(
5977
6265
  previousIntegrationKeys,
@@ -5979,7 +6267,7 @@ async function deployGameBackend(client, slug, config, projectPath, previousCust
5979
6267
  );
5980
6268
  const integrationsChanged = integrationsComparison.hasChanges;
5981
6269
  const schemaChanged = await hasSchemaChanged(previousSchemaSnapshot);
5982
- const nothingChanged = !customRoutesChanged && !integrationsChanged && !schemaChanged;
6270
+ const nothingChanged = !customRoutesChanged && !integrationsChanged && !schemaChanged && !secretsChanged;
5983
6271
  if (!forceBackend && nothingChanged) {
5984
6272
  return null;
5985
6273
  }
@@ -6002,7 +6290,8 @@ async function deployGameBackend(client, slug, config, projectPath, previousCust
6002
6290
  async () => client.dev.games.deploy.backend(slug, {
6003
6291
  ...bundle,
6004
6292
  bindings,
6005
- schema: schemaInfo || void 0
6293
+ schema: schemaInfo || void 0,
6294
+ secrets: Object.keys(secrets).length > 0 ? secrets : void 0
6006
6295
  }),
6007
6296
  "Backend deployed"
6008
6297
  );
@@ -6013,7 +6302,8 @@ async function deployGameBackend(client, slug, config, projectPath, previousCust
6013
6302
  integrationKeys: currentIntegrationKeys,
6014
6303
  integrationsHash: currentIntegrationsHash,
6015
6304
  backendSize: bundle.code.length,
6016
- schemaSnapshot: schemaInfo?.hash ? JSON.parse(schemaInfo.hash) : void 0
6305
+ schemaSnapshot: schemaInfo?.hash ? JSON.parse(schemaInfo.hash) : void 0,
6306
+ deployedSecrets: currentSecretKeys
6017
6307
  };
6018
6308
  } catch (error) {
6019
6309
  logger.error("Backend deployment failed");
@@ -6211,7 +6501,7 @@ function displayDeploymentDiff(options) {
6211
6501
  }
6212
6502
  if (backendChanged || customRoutesChanged || forceBackend || schemaStatementCount && schemaStatementCount > 0) {
6213
6503
  logger.bold("Backend", 1);
6214
- if (customRoutesChanged || backend?.currentCustomRoutesSize !== void 0) {
6504
+ if (customRoutesChanged) {
6215
6505
  const previousSize = backend?.previousCustomRoutesSize;
6216
6506
  const currentSize = backend?.currentCustomRoutesSize;
6217
6507
  let routesInfo;
@@ -6226,7 +6516,7 @@ function displayDeploymentDiff(options) {
6226
6516
  } else {
6227
6517
  routesInfo = green2("Updated");
6228
6518
  }
6229
- logger.data("Routes (src)", routesInfo, 2);
6519
+ logger.data("Routes", routesInfo, 2);
6230
6520
  }
6231
6521
  if (backendChanged || forceBackend) {
6232
6522
  const previousSize = backend?.previousBundleSize;
@@ -6245,7 +6535,7 @@ function displayDeploymentDiff(options) {
6245
6535
  } else {
6246
6536
  bundleInfo = green2("Updated");
6247
6537
  }
6248
- logger.data("Bundle (dep)", bundleInfo, 2);
6538
+ logger.data("Bundle", bundleInfo, 2);
6249
6539
  }
6250
6540
  if (schemaStatementCount && schemaStatementCount > 0) {
6251
6541
  const schemaInfo = green2(
@@ -6272,6 +6562,25 @@ function displayDeploymentDiff(options) {
6272
6562
  logger.newLine();
6273
6563
  }
6274
6564
  }
6565
+ const previousSecretKeys = options.secrets?.previousKeys;
6566
+ const currentSecretKeys = options.secrets?.currentKeys;
6567
+ if (previousSecretKeys !== void 0 && currentSecretKeys !== void 0) {
6568
+ const added = currentSecretKeys.filter((k) => !previousSecretKeys.includes(k));
6569
+ const removed = previousSecretKeys.filter((k) => !currentSecretKeys.includes(k));
6570
+ const hasSecretsChanges = added.length > 0 || removed.length > 0;
6571
+ if (hasSecretsChanges) {
6572
+ logger.bold("Secrets", 1);
6573
+ if (added.length > 0) {
6574
+ const addedList = added.map((k) => green2(k)).join(", ");
6575
+ logger.data("Added", addedList, 2);
6576
+ }
6577
+ if (removed.length > 0) {
6578
+ const removedList = removed.map((k) => red2(k)).join(", ");
6579
+ logger.data("Removed", removedList, 2);
6580
+ }
6581
+ logger.newLine();
6582
+ }
6583
+ }
6275
6584
  }
6276
6585
 
6277
6586
  // src/lib/deploy/interaction.ts
@@ -6287,10 +6596,10 @@ import { dim as dim4 } from "colorette";
6287
6596
  init_file_loader();
6288
6597
  init_constants2();
6289
6598
  init_core();
6290
- import { join as join14, relative as relative2 } from "path";
6599
+ import { join as join16, relative as relative2 } from "path";
6291
6600
  function findSingleBuildZip() {
6292
6601
  const workspace = getWorkspace();
6293
- const playcademyDir = join14(workspace, CLI_DIRECTORIES.WORKSPACE);
6602
+ const playcademyDir = join16(workspace, CLI_DIRECTORIES.WORKSPACE);
6294
6603
  const zipFiles = findFilesByExtension(playcademyDir, "zip");
6295
6604
  if (zipFiles.length === 1) {
6296
6605
  return zipFiles[0] ? relative2(workspace, zipFiles[0]) : null;
@@ -6326,14 +6635,14 @@ function hasOptionalFieldsMissing(missing) {
6326
6635
 
6327
6636
  // src/lib/deploy/validate.ts
6328
6637
  init_logger();
6329
- import { existsSync as existsSync12 } from "fs";
6638
+ import { existsSync as existsSync14 } from "fs";
6330
6639
  import { resolve as resolve7 } from "path";
6331
6640
  function validateBuildPath(path2) {
6332
6641
  if (!path2.trim()) {
6333
6642
  return "Build path is required";
6334
6643
  }
6335
6644
  const resolvedPath = resolve7(path2.trim());
6336
- if (!existsSync12(resolvedPath)) {
6645
+ if (!existsSync14(resolvedPath)) {
6337
6646
  return `Build path not found: ${path2.trim()}`;
6338
6647
  }
6339
6648
  return true;
@@ -6552,6 +6861,10 @@ async function confirmDeploymentPlan(plan, context2) {
6552
6861
  integrations: {
6553
6862
  previousKeys: context2.previousIntegrationKeys,
6554
6863
  currentKeys: currentIntegrationKeys
6864
+ },
6865
+ secrets: {
6866
+ previousKeys: context2.deployedGameInfo?.deployedSecrets,
6867
+ currentKeys: context2.currentSecretKeys
6555
6868
  }
6556
6869
  });
6557
6870
  const shouldDeploy = await confirm2({
@@ -6565,9 +6878,9 @@ async function confirmDeploymentPlan(plan, context2) {
6565
6878
 
6566
6879
  // src/lib/deploy/output.ts
6567
6880
  init_src2();
6568
- init_config();
6881
+ init_config2();
6569
6882
  init_logger();
6570
- import { blueBright as blueBright2, underline } from "colorette";
6883
+ import { underline } from "colorette";
6571
6884
  function displayCurrentConfiguration(context2) {
6572
6885
  const { config } = context2;
6573
6886
  logger.newLine();
@@ -6627,12 +6940,12 @@ function reportDeploymentSuccess(result, context2) {
6627
6940
  logger.success(`${game.displayName} ${action} successfully!`);
6628
6941
  logger.newLine();
6629
6942
  const baseUrl = getWebBaseUrl(client.getBaseUrl());
6630
- logger.data("Game URL", underline(blueBright2(`${baseUrl}/g/${game.slug}`)), 1);
6943
+ logger.data("Game URL", underline(`${baseUrl}/g/${game.slug}`), 1);
6631
6944
  if (backendDeployment || previousCustomRoutesHash) {
6632
6945
  const env = getEnvironment();
6633
6946
  const subdomain = env === "staging" ? `${game.slug}-staging` : game.slug;
6634
6947
  const backendUrl = `https://${subdomain}.${GAME_WORKER_DOMAINS.production}/api`;
6635
- logger.data("Backend API", underline(blueBright2(backendUrl)), 1);
6948
+ logger.data("Backend API", underline(`${backendUrl}`), 1);
6636
6949
  }
6637
6950
  if (game.version !== "external") {
6638
6951
  logger.data("Version", game.version || "latest", 1);
@@ -6701,6 +7014,10 @@ async function reportDryRun(plan, context2) {
6701
7014
  integrations: {
6702
7015
  previousKeys: context2.previousIntegrationKeys,
6703
7016
  currentKeys: currentIntegrationKeys
7017
+ },
7018
+ secrets: {
7019
+ previousKeys: context2.deployedGameInfo?.deployedSecrets,
7020
+ currentKeys: context2.currentSecretKeys
6704
7021
  }
6705
7022
  });
6706
7023
  } else {
@@ -7126,28 +7443,28 @@ var INTERACTION_TYPE = Object.fromEntries(
7126
7443
 
7127
7444
  // src/lib/deploy/preparation.ts
7128
7445
  init_src();
7129
- init_config2();
7446
+ init_config3();
7130
7447
  init_client();
7131
7448
  init_context();
7132
7449
 
7133
7450
  // src/lib/games/storage.ts
7134
7451
  init_constants2();
7135
- import { existsSync as existsSync13 } from "node:fs";
7452
+ import { existsSync as existsSync15 } from "node:fs";
7136
7453
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
7137
7454
  import { homedir as homedir2 } from "node:os";
7138
- import { join as join15 } from "node:path";
7455
+ import { join as join17 } from "node:path";
7139
7456
  function getGamesStorePath() {
7140
- return join15(homedir2(), CLI_USER_DIRECTORIES.CONFIG, CLI_FILES.GAMES_STORE);
7457
+ return join17(homedir2(), CLI_USER_DIRECTORIES.CONFIG, CLI_FILES.GAMES_STORE);
7141
7458
  }
7142
7459
  async function ensureConfigDir() {
7143
- const configDir = join15(homedir2(), CLI_USER_DIRECTORIES.CONFIG);
7460
+ const configDir = join17(homedir2(), CLI_USER_DIRECTORIES.CONFIG);
7144
7461
  await mkdir3(configDir, { recursive: true });
7145
7462
  }
7146
7463
  async function loadGameStore() {
7147
7464
  try {
7148
7465
  await ensureConfigDir();
7149
7466
  const storePath = getGamesStorePath();
7150
- if (existsSync13(storePath)) {
7467
+ if (existsSync15(storePath)) {
7151
7468
  const content = await readFile3(storePath, "utf-8");
7152
7469
  return JSON.parse(content);
7153
7470
  }
@@ -7231,6 +7548,11 @@ async function prepareDeploymentContext(options) {
7231
7548
  buildHash = await hashFile(finalConfig.buildPath);
7232
7549
  }
7233
7550
  }
7551
+ let currentSecretKeys;
7552
+ if (finalConfig.slug && gameIsDeployed) {
7553
+ const { keys } = await fetchSecretsForDeployment(finalConfig.slug);
7554
+ currentSecretKeys = keys;
7555
+ }
7234
7556
  return {
7235
7557
  config: finalConfig,
7236
7558
  fullConfig,
@@ -7249,6 +7571,7 @@ async function prepareDeploymentContext(options) {
7249
7571
  previousBackendSize: deployedGameInfo?.backendSize,
7250
7572
  previousIntegrationKeys: deployedGameInfo?.integrationKeys,
7251
7573
  previousIntegrationsHash: deployedGameInfo?.integrationsHash,
7574
+ currentSecretKeys,
7252
7575
  isDryRun: options.dryRun ?? false,
7253
7576
  deployBackend: options.backend !== false,
7254
7577
  forceBackend: options.forceBackend ?? false,
@@ -7284,11 +7607,24 @@ async function analyzeChanges(context2) {
7284
7607
  let backendChanged;
7285
7608
  let customRoutesChanged = false;
7286
7609
  let schemaChanged = false;
7610
+ let secretsChanged = false;
7611
+ let hasSecrets = false;
7287
7612
  if (!deployBackend) {
7288
7613
  backendChanged = void 0;
7289
7614
  } else {
7290
7615
  const hasCustomRoutes2 = hasLocalCustomRoutes(projectPath, context2.fullConfig);
7291
7616
  const hasIntegrations = !!context2.fullConfig?.integrations;
7617
+ if (context2.currentSecretKeys) {
7618
+ const currentSecretKeys = context2.currentSecretKeys;
7619
+ const previousSecretKeys = context2.deployedGameInfo?.deployedSecrets || [];
7620
+ hasSecrets = currentSecretKeys.length > 0;
7621
+ const secretsComparison = compareSecrets(
7622
+ Object.fromEntries(currentSecretKeys.map((k) => [k, ""])),
7623
+ // We only care about keys for change detection
7624
+ previousSecretKeys
7625
+ );
7626
+ secretsChanged = secretsComparison.hasChanges;
7627
+ }
7292
7628
  if (hasCustomRoutes2) {
7293
7629
  if (!context2.previousCustomRoutesHash) {
7294
7630
  customRoutesChanged = true;
@@ -7313,10 +7649,10 @@ async function analyzeChanges(context2) {
7313
7649
  if (context2.fullConfig) {
7314
7650
  schemaChanged = await hasSchemaChanged(context2.deployedGameInfo?.schemaSnapshot);
7315
7651
  }
7316
- if (!hasCustomRoutes2 && !hasIntegrations) {
7652
+ if (!hasCustomRoutes2 && !hasIntegrations && !hasSecrets) {
7317
7653
  backendChanged = void 0;
7318
7654
  } else {
7319
- backendChanged = customRoutesChanged || integrationKeysChanged || schemaChanged;
7655
+ backendChanged = customRoutesChanged || integrationKeysChanged || schemaChanged || secretsChanged;
7320
7656
  }
7321
7657
  }
7322
7658
  let integrationsMetadataChanged;
@@ -7336,7 +7672,8 @@ async function analyzeChanges(context2) {
7336
7672
  backend: backendChanged,
7337
7673
  customRoutes: customRoutesChanged,
7338
7674
  integrations: integrationsMetadataChanged,
7339
- schema: schemaChanged
7675
+ schema: schemaChanged,
7676
+ secrets: secretsChanged
7340
7677
  };
7341
7678
  }
7342
7679
  async function calculateDeploymentPlan(context2, changes) {
@@ -7379,7 +7716,8 @@ async function calculateDeploymentPlan(context2, changes) {
7379
7716
  }
7380
7717
  }
7381
7718
  const shouldDeployFrontend = changes.build !== false || changes.config;
7382
- const needsBackend2 = hasLocalCustomRoutes(projectPath, context2.fullConfig) || !!context2.fullConfig?.integrations;
7719
+ const hasSecretsForUpdate = (context2.currentSecretKeys?.length ?? 0) > 0;
7720
+ const needsBackend2 = hasLocalCustomRoutes(projectPath, context2.fullConfig) || !!context2.fullConfig?.integrations || hasSecretsForUpdate;
7383
7721
  const shouldDeployBackend = deployBackend && (backendHasChanges || forceBackend || needsBackend2);
7384
7722
  return {
7385
7723
  action: "update-existing",
@@ -7395,7 +7733,7 @@ async function calculateDeploymentPlan(context2, changes) {
7395
7733
  // src/lib/deploy/steps.ts
7396
7734
  init_src();
7397
7735
  init_logger();
7398
- import { existsSync as existsSync14 } from "fs";
7736
+ import { existsSync as existsSync16 } from "fs";
7399
7737
  import { readFile as readFile4 } from "fs/promises";
7400
7738
  import { basename as basename2, resolve as resolve8 } from "path";
7401
7739
  function prepareGameMetadata(config) {
@@ -7415,7 +7753,7 @@ function prepareGameMetadata(config) {
7415
7753
  }
7416
7754
  async function prepareBuildFile(buildPath) {
7417
7755
  const resolvedPath = resolve8(buildPath);
7418
- if (resolvedPath.endsWith(".zip") && existsSync14(resolvedPath)) {
7756
+ if (resolvedPath.endsWith(".zip") && existsSync16(resolvedPath)) {
7419
7757
  const buffer = await readFile4(resolvedPath);
7420
7758
  const uint8Array = new Uint8Array(buffer);
7421
7759
  const blob = new Blob([uint8Array], { type: "application/zip" });
@@ -7576,7 +7914,8 @@ async function deployBackendIfNeeded(game, context2) {
7576
7914
  forceBackend,
7577
7915
  previousIntegrationKeys,
7578
7916
  context2.deployedGameInfo?.schemaSnapshot,
7579
- debug
7917
+ debug,
7918
+ context2.deployedGameInfo?.deployedSecrets
7580
7919
  );
7581
7920
  return backendDeployment;
7582
7921
  }
@@ -7611,14 +7950,16 @@ async function saveDeploymentState(game, backendDeployment, context2) {
7611
7950
  customRoutesSize: backendDeployment.customRoutesSize ?? void 0,
7612
7951
  backendSize: backendDeployment.backendSize,
7613
7952
  backendDeployedAt: backendDeployment.deployedAt,
7614
- schemaSnapshot: backendDeployment.schemaSnapshot
7953
+ schemaSnapshot: backendDeployment.schemaSnapshot,
7954
+ deployedSecrets: backendDeployment.deployedSecrets
7615
7955
  },
7616
7956
  ...!backendDeployment && context2.deployedGameInfo && {
7617
7957
  customRoutesHash: context2.deployedGameInfo.customRoutesHash,
7618
7958
  customRoutesSize: context2.deployedGameInfo.customRoutesSize,
7619
7959
  backendSize: context2.deployedGameInfo.backendSize,
7620
7960
  backendDeployedAt: context2.deployedGameInfo.backendDeployedAt,
7621
- schemaSnapshot: context2.deployedGameInfo.schemaSnapshot
7961
+ schemaSnapshot: context2.deployedGameInfo.schemaSnapshot,
7962
+ deployedSecrets: context2.deployedGameInfo.deployedSecrets
7622
7963
  },
7623
7964
  integrationKeys,
7624
7965
  // Always save (even if empty array)
@@ -7630,20 +7971,20 @@ async function saveDeploymentState(game, backendDeployment, context2) {
7630
7971
  // src/lib/dev/backend.ts
7631
7972
  init_constants2();
7632
7973
  init_core();
7633
- import { existsSync as existsSync15 } from "fs";
7634
- import { join as join16 } from "path";
7974
+ import { existsSync as existsSync17 } from "fs";
7975
+ import { join as join18 } from "path";
7635
7976
  function hasCustomRoutes(config) {
7636
7977
  const workspace = getWorkspace();
7637
7978
  const customRoutesConfig = config?.integrations?.customRoutes;
7638
7979
  const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
7639
- return existsSync15(join16(workspace, customRoutesDir));
7980
+ return existsSync17(join18(workspace, customRoutesDir));
7640
7981
  }
7641
7982
  function needsBackend(config) {
7642
7983
  return !!config?.integrations || hasCustomRoutes(config);
7643
7984
  }
7644
7985
 
7645
7986
  // src/lib/dev/display.ts
7646
- import { blueBright as blueBright3, dim as dim5 } from "colorette";
7987
+ import { dim as dim5 } from "colorette";
7647
7988
 
7648
7989
  // ../edge-play/src/register-routes.ts
7649
7990
  init_constants4();
@@ -7653,6 +7994,57 @@ init_constants4();
7653
7994
 
7654
7995
  // src/lib/dev/display.ts
7655
7996
  init_core();
7997
+
7998
+ // src/lib/dev/route-sorting.ts
7999
+ function getSegmentType(segment) {
8000
+ if (segment.startsWith(":") && segment.includes("{.*}")) {
8001
+ return 2 /* CatchAll */;
8002
+ }
8003
+ if (segment.startsWith(":")) {
8004
+ return 1 /* Dynamic */;
8005
+ }
8006
+ return 0 /* Static */;
8007
+ }
8008
+ function compareSegments(a, b) {
8009
+ const typeA = getSegmentType(a);
8010
+ const typeB = getSegmentType(b);
8011
+ if (typeA !== typeB) {
8012
+ if (typeA === 1 /* Dynamic */ && typeB === 0 /* Static */) {
8013
+ return -1;
8014
+ }
8015
+ if (typeA === 0 /* Static */ && typeB === 1 /* Dynamic */) {
8016
+ return 1;
8017
+ }
8018
+ if (typeA === 2 /* CatchAll */ && typeB === 0 /* Static */) {
8019
+ return -1;
8020
+ }
8021
+ if (typeA === 0 /* Static */ && typeB === 2 /* CatchAll */) {
8022
+ return 1;
8023
+ }
8024
+ return typeA - typeB;
8025
+ }
8026
+ return a.localeCompare(b);
8027
+ }
8028
+ function sortRoutes(routes) {
8029
+ return [...routes].sort((a, b) => {
8030
+ const segmentsA = a.path.split("/").filter(Boolean);
8031
+ const segmentsB = b.path.split("/").filter(Boolean);
8032
+ const maxLength = Math.max(segmentsA.length, segmentsB.length);
8033
+ for (let i = 0; i < maxLength; i++) {
8034
+ const segA = segmentsA[i];
8035
+ const segB = segmentsB[i];
8036
+ if (segA === void 0) return -1;
8037
+ if (segB === void 0) return 1;
8038
+ const comparison = compareSegments(segA, segB);
8039
+ if (comparison !== 0) {
8040
+ return comparison;
8041
+ }
8042
+ }
8043
+ return 0;
8044
+ });
8045
+ }
8046
+
8047
+ // src/lib/dev/display.ts
7656
8048
  function displayRegisteredRoutes(integrations, customRoutes = []) {
7657
8049
  const healthRoutes = [
7658
8050
  { path: ROUTES.HEALTH, method: "GET" }
@@ -7663,29 +8055,27 @@ function displayRegisteredRoutes(integrations, customRoutes = []) {
7663
8055
  const method = hasSandboxCreds ? "POST" : "POST (not configured)";
7664
8056
  timebackRoutes.push({ path: ROUTES.TIMEBACK.END_ACTIVITY, method });
7665
8057
  }
7666
- const customRoutesList = customRoutes.map((route) => ({
7667
- path: route.path,
7668
- method: route.methods?.join(", ") || "*"
7669
- }));
8058
+ const customRoutesList = customRoutes.map((route) => {
8059
+ const methods = route.methods?.join(", ") || "*";
8060
+ const isCatchAll = /{\.\*}/.test(route.path);
8061
+ return {
8062
+ path: route.path,
8063
+ method: isCatchAll ? `${methods} (catch-all)` : methods
8064
+ };
8065
+ });
7670
8066
  const allRoutes = [...healthRoutes, ...timebackRoutes, ...customRoutesList];
7671
- const maxPathLength = Math.max(...allRoutes.map((r) => r.path.length));
7672
- const displayRoute = (route) => {
8067
+ const sortedRoutes = sortRoutes(allRoutes);
8068
+ const maxPathLength = Math.max(...sortedRoutes.map((r) => r.path.length));
8069
+ sortedRoutes.forEach((route) => {
7673
8070
  const paddedPath = route.path.padEnd(maxPathLength + 2, " ");
7674
- logger.raw(`${blueBright3(paddedPath)} ${dim5(route.method)}`, 1);
7675
- };
7676
- healthRoutes.forEach(displayRoute);
7677
- if (timebackRoutes.length > 0) {
7678
- timebackRoutes.forEach(displayRoute);
7679
- }
7680
- if (customRoutesList.length > 0) {
7681
- customRoutesList.forEach(displayRoute);
7682
- }
8071
+ logger.customRaw(`<${paddedPath}> ${dim5(route.method)}`, 1);
8072
+ });
7683
8073
  }
7684
8074
 
7685
8075
  // src/lib/dev/reload.ts
7686
8076
  init_constants2();
7687
8077
  init_core();
7688
- import { join as join17, relative as relative3 } from "path";
8078
+ import { join as join19, relative as relative3 } from "path";
7689
8079
  import chokidar from "chokidar";
7690
8080
  import { bold as bold4, cyan as cyan3, dim as dim6, green as green3 } from "colorette";
7691
8081
  function formatTime() {
@@ -7702,9 +8092,9 @@ function startHotReload(onReload, options = {}) {
7702
8092
  const customRoutesConfig = options.config?.integrations?.customRoutes;
7703
8093
  const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
7704
8094
  const watchPaths = [
7705
- join17(workspace, customRoutesDir),
7706
- join17(workspace, "playcademy.config.js"),
7707
- join17(workspace, "playcademy.config.json")
8095
+ join19(workspace, customRoutesDir),
8096
+ join19(workspace, "playcademy.config.js"),
8097
+ join19(workspace, "playcademy.config.json")
7708
8098
  ];
7709
8099
  const watcher = chokidar.watch(watchPaths, {
7710
8100
  persistent: true,
@@ -7746,53 +8136,65 @@ function startHotReload(onReload, options = {}) {
7746
8136
  // src/lib/dev/server.ts
7747
8137
  init_src2();
7748
8138
  import { mkdir as mkdir4 } from "fs/promises";
7749
- import { join as join19 } from "path";
8139
+ import { join as join21 } from "path";
7750
8140
  import { Log, LogLevel, Miniflare } from "miniflare";
7751
8141
 
7752
8142
  // ../utils/src/port.ts
7753
- import { existsSync as existsSync16, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
8143
+ import { existsSync as existsSync18, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "node:fs";
7754
8144
  import { createServer as createServer2 } from "node:net";
7755
8145
  import { homedir as homedir3 } from "node:os";
7756
- import { join as join18 } from "node:path";
8146
+ import { join as join20 } from "node:path";
7757
8147
  async function isPortAvailableOnHost(port, host) {
7758
8148
  return new Promise((resolve11) => {
7759
8149
  const server = createServer2();
7760
- server.once("error", () => {
7761
- resolve11(false);
8150
+ let resolved = false;
8151
+ const cleanup = (result) => {
8152
+ if (resolved) return;
8153
+ resolved = true;
8154
+ try {
8155
+ server.close();
8156
+ } catch {
8157
+ }
8158
+ resolve11(result);
8159
+ };
8160
+ const timeout = setTimeout(() => cleanup(true), 100);
8161
+ server.once("error", (err) => {
8162
+ clearTimeout(timeout);
8163
+ if (err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL") {
8164
+ cleanup(true);
8165
+ } else {
8166
+ cleanup(false);
8167
+ }
7762
8168
  });
7763
8169
  server.once("listening", () => {
7764
- server.close();
7765
- resolve11(true);
8170
+ clearTimeout(timeout);
8171
+ cleanup(true);
7766
8172
  });
7767
- server.listen(port, host);
8173
+ server.listen(port, host).unref();
7768
8174
  });
7769
8175
  }
7770
8176
  async function findAvailablePort(startPort = 4321) {
7771
- const [localhostAvailable, ipv4AllAvailable, ipv6AllAvailable] = await Promise.all([
7772
- isPortAvailableOnHost(startPort, "127.0.0.1"),
7773
- isPortAvailableOnHost(startPort, "0.0.0.0"),
7774
- isPortAvailableOnHost(startPort, "::")
7775
- ]);
7776
- if (localhostAvailable && ipv4AllAvailable && ipv6AllAvailable) {
8177
+ if (await isPortAvailableOnHost(startPort, "0.0.0.0")) {
7777
8178
  return startPort;
8179
+ } else {
8180
+ return findAvailablePort(startPort + 1);
7778
8181
  }
7779
- return findAvailablePort(startPort + 1);
7780
8182
  }
7781
8183
  function getRegistryPath() {
7782
8184
  const home = homedir3();
7783
- const dir = join18(home, ".playcademy");
7784
- if (!existsSync16(dir)) {
8185
+ const dir = join20(home, ".playcademy");
8186
+ if (!existsSync18(dir)) {
7785
8187
  mkdirSync4(dir, { recursive: true });
7786
8188
  }
7787
- return join18(dir, ".proc");
8189
+ return join20(dir, ".proc");
7788
8190
  }
7789
8191
  function readRegistry() {
7790
8192
  const registryPath = getRegistryPath();
7791
- if (!existsSync16(registryPath)) {
8193
+ if (!existsSync18(registryPath)) {
7792
8194
  return {};
7793
8195
  }
7794
8196
  try {
7795
- const content = readFileSync5(registryPath, "utf-8");
8197
+ const content = readFileSync6(registryPath, "utf-8");
7796
8198
  return JSON.parse(content);
7797
8199
  } catch {
7798
8200
  return {};
@@ -7800,7 +8202,7 @@ function readRegistry() {
7800
8202
  }
7801
8203
  function writeRegistry(registry) {
7802
8204
  const registryPath = getRegistryPath();
7803
- writeFileSync6(registryPath, JSON.stringify(registry, null, 2), "utf-8");
8205
+ writeFileSync7(registryPath, JSON.stringify(registry, null, 2), "utf-8");
7804
8206
  }
7805
8207
  function getServerKey(type, port) {
7806
8208
  return `${type}-${port}`;
@@ -7900,9 +8302,19 @@ async function startDevServer(options) {
7900
8302
  const kvDir = hasKV ? await ensureKvDirectory() : void 0;
7901
8303
  const hasBucket = hasBucketSetup(config);
7902
8304
  const bucketDir = hasBucket ? await ensureBucketDirectory() : void 0;
8305
+ const workspace = getWorkspace();
8306
+ const envSecrets = await readEnvFile(workspace);
7903
8307
  const log2 = logger2 ? new FilteredLog(LogLevel.INFO) : new Log(LogLevel.NONE);
7904
- const sandboxInfo = readServerInfo("sandbox", getWorkspace());
8308
+ const sandboxInfo = readServerInfo("sandbox", workspace);
7905
8309
  const baseUrl = platformUrl ?? sandboxInfo?.url ?? process.env.PLAYCADEMY_BASE_URL ?? `http://localhost:${DEFAULT_PORTS.SANDBOX}`;
8310
+ const bindings = {
8311
+ PLAYCADEMY_API_KEY: process.env.PLAYCADEMY_API_KEY || "dev-api-key",
8312
+ GAME_ID: CORE_GAME_UUIDS.PLAYGROUND,
8313
+ PLAYCADEMY_BASE_URL: baseUrl
8314
+ };
8315
+ for (const [key, value] of Object.entries(envSecrets)) {
8316
+ bindings[`secrets_${key}`] = value;
8317
+ }
7906
8318
  const mf = new Miniflare({
7907
8319
  port,
7908
8320
  log: log2,
@@ -7913,11 +8325,7 @@ async function startDevServer(options) {
7913
8325
  contents: bundle.code
7914
8326
  }
7915
8327
  ],
7916
- bindings: {
7917
- PLAYCADEMY_API_KEY: process.env.PLAYCADEMY_API_KEY || "dev-api-key",
7918
- GAME_ID: CORE_GAME_UUIDS.PLAYGROUND,
7919
- PLAYCADEMY_BASE_URL: baseUrl
7920
- },
8328
+ bindings,
7921
8329
  d1Databases: hasDatabase ? ["DB"] : [],
7922
8330
  d1Persist: dbDir,
7923
8331
  kvNamespaces: hasKV ? ["KV"] : [],
@@ -7933,7 +8341,7 @@ async function startDevServer(options) {
7933
8341
  return { server: mf, port };
7934
8342
  }
7935
8343
  async function ensureDatabaseDirectory() {
7936
- const dbDir = join19(getWorkspace(), CLI_DIRECTORIES.DATABASE);
8344
+ const dbDir = join21(getWorkspace(), CLI_DIRECTORIES.DATABASE);
7937
8345
  try {
7938
8346
  await mkdir4(dbDir, { recursive: true });
7939
8347
  } catch (error) {
@@ -7942,7 +8350,7 @@ async function ensureDatabaseDirectory() {
7942
8350
  return dbDir;
7943
8351
  }
7944
8352
  async function ensureKvDirectory() {
7945
- const kvDir = join19(getWorkspace(), CLI_DIRECTORIES.KV);
8353
+ const kvDir = join21(getWorkspace(), CLI_DIRECTORIES.KV);
7946
8354
  try {
7947
8355
  await mkdir4(kvDir, { recursive: true });
7948
8356
  } catch (error) {
@@ -7951,7 +8359,7 @@ async function ensureKvDirectory() {
7951
8359
  return kvDir;
7952
8360
  }
7953
8361
  async function ensureBucketDirectory() {
7954
- const bucketDir = join19(getWorkspace(), CLI_DIRECTORIES.BUCKET);
8362
+ const bucketDir = join21(getWorkspace(), CLI_DIRECTORIES.BUCKET);
7955
8363
  try {
7956
8364
  await mkdir4(bucketDir, { recursive: true });
7957
8365
  } catch (error) {
@@ -8036,7 +8444,7 @@ function displayResourcesStatus(resources, logger2) {
8036
8444
  }
8037
8445
 
8038
8446
  // src/commands/init/config.ts
8039
- import { writeFileSync as writeFileSync7 } from "fs";
8447
+ import { writeFileSync as writeFileSync8 } from "fs";
8040
8448
  import { resolve as resolve9 } from "path";
8041
8449
  init_file_loader();
8042
8450
  init_constants2();
@@ -8088,7 +8496,7 @@ var configCommand = new Command("config").description("Create playcademy.config
8088
8496
  emoji: gameInfo.emoji,
8089
8497
  timeback: timebackConfig ?? void 0
8090
8498
  });
8091
- writeFileSync7(resolve9(getWorkspace(), configFileName), configContent, "utf-8");
8499
+ writeFileSync8(resolve9(getWorkspace(), configFileName), configContent, "utf-8");
8092
8500
  displayConfigSuccess(!!timebackConfig);
8093
8501
  } catch (error) {
8094
8502
  logger.newLine();
@@ -8207,7 +8615,7 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
8207
8615
  bucket: bucket ?? void 0,
8208
8616
  timeback: timebackConfig ?? void 0
8209
8617
  });
8210
- writeFileSync8(resolve10(getWorkspace(), configFileName), configContent, "utf-8");
8618
+ writeFileSync9(resolve10(getWorkspace(), configFileName), configContent, "utf-8");
8211
8619
  if (database || kv || bucket) {
8212
8620
  await ensurePlaycademyTypes();
8213
8621
  }
@@ -8225,14 +8633,14 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
8225
8633
  });
8226
8634
  async function addPlaycademySdk() {
8227
8635
  const pkgPath = resolve10(getWorkspace(), "package.json");
8228
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
8636
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
8229
8637
  const hasSdk = pkg.dependencies?.["@playcademy/sdk"] || pkg.devDependencies?.["@playcademy/sdk"];
8230
8638
  if (hasSdk) {
8231
8639
  return false;
8232
8640
  }
8233
8641
  if (!pkg.dependencies) pkg.dependencies = {};
8234
8642
  pkg.dependencies["@playcademy/sdk"] = "latest";
8235
- writeFileSync8(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
8643
+ writeFileSync9(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
8236
8644
  return true;
8237
8645
  }
8238
8646
  initCommand.addCommand(configCommand);
@@ -8823,7 +9231,7 @@ var getStatusCommand = new Command11("status").description("Check your developer
8823
9231
  });
8824
9232
 
8825
9233
  // src/commands/dev/server.ts
8826
- import { blueBright as blueBright4, underline as underline2 } from "colorette";
9234
+ import { underline as underline2 } from "colorette";
8827
9235
  function setupCleanupHandlers(workspace, getServer) {
8828
9236
  let isShuttingDown = false;
8829
9237
  const cleanup = () => {
@@ -8873,7 +9281,7 @@ async function runDevServer(options) {
8873
9281
  logger.warn("No backend integrations or custom routes configured");
8874
9282
  logger.newLine();
8875
9283
  logger.admonition("tip", "How to Add Backend", [
8876
- `Add integrations to ${blueBright4("playcademy.config.js")} or run \`playcademy api init\``
9284
+ `Add integrations to <playcademy.config.js> or run \`playcademy api init\``
8877
9285
  ]);
8878
9286
  logger.newLine();
8879
9287
  logger.remark("Nothing to do");
@@ -8887,7 +9295,7 @@ async function runDevServer(options) {
8887
9295
  logger: options.logger !== false
8888
9296
  });
8889
9297
  serverRef.current = server;
8890
- logger.success(`Game API started: ${blueBright4(underline2(`http://localhost:${port}/api`))}`);
9298
+ logger.success(`Game API started: ${underline2(`<http://localhost:${port}/api>`)}`);
8891
9299
  logger.newLine();
8892
9300
  const customRoutesDir = getCustomRoutesDirectory(workspace, config);
8893
9301
  const customRoutes = await discoverRoutes(customRoutesDir);
@@ -9101,16 +9509,16 @@ async function runDbInit() {
9101
9509
  init_src2();
9102
9510
  init_src();
9103
9511
  import { spawn } from "child_process";
9104
- import { existsSync as existsSync17, rmSync as rmSync2 } from "fs";
9105
- import { join as join20 } from "path";
9512
+ import { existsSync as existsSync19, rmSync as rmSync2 } from "fs";
9513
+ import { join as join22 } from "path";
9106
9514
  import { confirm as confirm6 } from "@inquirer/prompts";
9107
9515
  import { Miniflare as Miniflare2 } from "miniflare";
9108
9516
  init_constants2();
9109
9517
  async function runDbReset() {
9110
9518
  try {
9111
9519
  const workspace = getWorkspace();
9112
- const dbDir = join20(workspace, CLI_DIRECTORIES.DATABASE);
9113
- if (!existsSync17(dbDir)) {
9520
+ const dbDir = join22(workspace, CLI_DIRECTORIES.DATABASE);
9521
+ if (!existsSync19(dbDir)) {
9114
9522
  logger.warn("No database found to reset");
9115
9523
  logger.newLine();
9116
9524
  logger.remark("Nothing to do");
@@ -9213,8 +9621,8 @@ async function runDbReset() {
9213
9621
  // src/commands/db/seed.ts
9214
9622
  init_package_manager();
9215
9623
  import { execSync as execSync5 } from "child_process";
9216
- import { existsSync as existsSync18 } from "fs";
9217
- import { join as join21 } from "path";
9624
+ import { existsSync as existsSync20 } from "fs";
9625
+ import { join as join23 } from "path";
9218
9626
  async function runDbSeed(options) {
9219
9627
  const workspace = getWorkspace();
9220
9628
  try {
@@ -9223,8 +9631,8 @@ async function runDbSeed(options) {
9223
9631
  logger.newLine();
9224
9632
  }
9225
9633
  if (options.file) {
9226
- const seedPath = join21(workspace, options.file);
9227
- if (!existsSync18(seedPath)) {
9634
+ const seedPath = join23(workspace, options.file);
9635
+ if (!existsSync20(seedPath)) {
9228
9636
  logger.error(`Seed file not found: ${options.file}`);
9229
9637
  logger.newLine();
9230
9638
  process.exit(1);
@@ -9255,7 +9663,7 @@ import { Command as Command15 } from "commander";
9255
9663
  // src/commands/kv/clear.ts
9256
9664
  init_string();
9257
9665
  init_constants2();
9258
- import { join as join22 } from "path";
9666
+ import { join as join24 } from "path";
9259
9667
  import { confirm as confirm7 } from "@inquirer/prompts";
9260
9668
  import { Miniflare as Miniflare3 } from "miniflare";
9261
9669
  async function runKVClear(options = {}) {
@@ -9292,7 +9700,7 @@ async function runKVClear(options = {}) {
9292
9700
  sourcemap: false,
9293
9701
  minify: false
9294
9702
  });
9295
- const kvDir = join22(getWorkspace(), CLI_DIRECTORIES.KV);
9703
+ const kvDir = join24(getWorkspace(), CLI_DIRECTORIES.KV);
9296
9704
  const mf = new Miniflare3({
9297
9705
  modules: [
9298
9706
  {
@@ -9370,7 +9778,7 @@ async function runKVClear(options = {}) {
9370
9778
 
9371
9779
  // src/commands/kv/delete.ts
9372
9780
  init_constants2();
9373
- import { join as join23 } from "path";
9781
+ import { join as join25 } from "path";
9374
9782
  import { Miniflare as Miniflare4 } from "miniflare";
9375
9783
  async function runKVDelete(key, options = {}) {
9376
9784
  try {
@@ -9415,7 +9823,7 @@ async function runKVDelete(key, options = {}) {
9415
9823
  sourcemap: false,
9416
9824
  minify: false
9417
9825
  });
9418
- const kvDir = join23(getWorkspace(), CLI_DIRECTORIES.KV);
9826
+ const kvDir = join25(getWorkspace(), CLI_DIRECTORIES.KV);
9419
9827
  const mf = new Miniflare4({
9420
9828
  modules: [
9421
9829
  {
@@ -9458,7 +9866,7 @@ async function runKVDelete(key, options = {}) {
9458
9866
 
9459
9867
  // src/commands/kv/get.ts
9460
9868
  init_constants2();
9461
- import { join as join24 } from "path";
9869
+ import { join as join26 } from "path";
9462
9870
  import { Miniflare as Miniflare5 } from "miniflare";
9463
9871
  async function runKVGet(key, options = {}) {
9464
9872
  try {
@@ -9503,7 +9911,7 @@ async function runKVGet(key, options = {}) {
9503
9911
  sourcemap: false,
9504
9912
  minify: false
9505
9913
  });
9506
- const kvDir = join24(getWorkspace(), CLI_DIRECTORIES.KV);
9914
+ const kvDir = join26(getWorkspace(), CLI_DIRECTORIES.KV);
9507
9915
  const mf = new Miniflare5({
9508
9916
  modules: [
9509
9917
  {
@@ -9648,7 +10056,7 @@ async function runKVInit() {
9648
10056
 
9649
10057
  // src/commands/kv/inspect.ts
9650
10058
  init_constants2();
9651
- import { join as join25 } from "path";
10059
+ import { join as join27 } from "path";
9652
10060
  import { Miniflare as Miniflare6 } from "miniflare";
9653
10061
  async function runKVInspect(key, options = {}) {
9654
10062
  try {
@@ -9693,7 +10101,7 @@ async function runKVInspect(key, options = {}) {
9693
10101
  sourcemap: false,
9694
10102
  minify: false
9695
10103
  });
9696
- const kvDir = join25(getWorkspace(), CLI_DIRECTORIES.KV);
10104
+ const kvDir = join27(getWorkspace(), CLI_DIRECTORIES.KV);
9697
10105
  const mf = new Miniflare6({
9698
10106
  modules: [
9699
10107
  {
@@ -9787,7 +10195,7 @@ async function runKVInspect(key, options = {}) {
9787
10195
  // src/commands/kv/list.ts
9788
10196
  init_string();
9789
10197
  init_constants2();
9790
- import { join as join26 } from "path";
10198
+ import { join as join28 } from "path";
9791
10199
  import { Miniflare as Miniflare7 } from "miniflare";
9792
10200
  async function runKVList(options = {}) {
9793
10201
  try {
@@ -9823,7 +10231,7 @@ async function runKVList(options = {}) {
9823
10231
  sourcemap: false,
9824
10232
  minify: false
9825
10233
  });
9826
- const kvDir = join26(getWorkspace(), CLI_DIRECTORIES.KV);
10234
+ const kvDir = join28(getWorkspace(), CLI_DIRECTORIES.KV);
9827
10235
  const mf = new Miniflare7({
9828
10236
  modules: [
9829
10237
  {
@@ -9878,7 +10286,7 @@ async function runKVList(options = {}) {
9878
10286
  init_file_loader();
9879
10287
  init_string();
9880
10288
  init_constants2();
9881
- import { join as join27 } from "path";
10289
+ import { join as join29 } from "path";
9882
10290
  import { confirm as confirm8 } from "@inquirer/prompts";
9883
10291
  import { Miniflare as Miniflare8 } from "miniflare";
9884
10292
  async function runKVSeed(seedFile, options = {}) {
@@ -9948,7 +10356,7 @@ async function runKVSeed(seedFile, options = {}) {
9948
10356
  sourcemap: false,
9949
10357
  minify: false
9950
10358
  });
9951
- const kvDir = join27(workspace, CLI_DIRECTORIES.KV);
10359
+ const kvDir = join29(workspace, CLI_DIRECTORIES.KV);
9952
10360
  const mf = new Miniflare8({
9953
10361
  modules: [
9954
10362
  {
@@ -10035,7 +10443,7 @@ async function runKVSeed(seedFile, options = {}) {
10035
10443
  // src/commands/kv/set.ts
10036
10444
  init_file_loader();
10037
10445
  init_constants2();
10038
- import { join as join28 } from "path";
10446
+ import { join as join30 } from "path";
10039
10447
  import { Miniflare as Miniflare9 } from "miniflare";
10040
10448
  async function runKVSet(key, value, options = {}) {
10041
10449
  try {
@@ -10109,7 +10517,7 @@ async function runKVSet(key, value, options = {}) {
10109
10517
  sourcemap: false,
10110
10518
  minify: false
10111
10519
  });
10112
- const kvDir = join28(getWorkspace(), CLI_DIRECTORIES.KV);
10520
+ const kvDir = join30(getWorkspace(), CLI_DIRECTORIES.KV);
10113
10521
  const mf = new Miniflare9({
10114
10522
  modules: [
10115
10523
  {
@@ -10153,7 +10561,7 @@ async function runKVSet(key, value, options = {}) {
10153
10561
  // src/commands/kv/stats.ts
10154
10562
  init_string();
10155
10563
  init_constants2();
10156
- import { join as join29 } from "path";
10564
+ import { join as join31 } from "path";
10157
10565
  import { Miniflare as Miniflare10 } from "miniflare";
10158
10566
  async function runKVStats(options = {}) {
10159
10567
  try {
@@ -10189,7 +10597,7 @@ async function runKVStats(options = {}) {
10189
10597
  sourcemap: false,
10190
10598
  minify: false
10191
10599
  });
10192
- const kvDir = join29(getWorkspace(), CLI_DIRECTORIES.KV);
10600
+ const kvDir = join31(getWorkspace(), CLI_DIRECTORIES.KV);
10193
10601
  const mf = new Miniflare10({
10194
10602
  modules: [
10195
10603
  {
@@ -10312,7 +10720,7 @@ import { Command as Command16 } from "commander";
10312
10720
 
10313
10721
  // src/commands/bucket/delete.ts
10314
10722
  init_constants2();
10315
- import { join as join30 } from "path";
10723
+ import { join as join32 } from "path";
10316
10724
  import { Miniflare as Miniflare11 } from "miniflare";
10317
10725
  async function runBucketDelete(key, options = {}) {
10318
10726
  try {
@@ -10357,7 +10765,7 @@ async function runBucketDelete(key, options = {}) {
10357
10765
  sourcemap: false,
10358
10766
  minify: false
10359
10767
  });
10360
- const bucketDir = join30(getWorkspace(), CLI_DIRECTORIES.BUCKET);
10768
+ const bucketDir = join32(getWorkspace(), CLI_DIRECTORIES.BUCKET);
10361
10769
  const mf = new Miniflare11({
10362
10770
  modules: [
10363
10771
  {
@@ -10415,8 +10823,8 @@ async function runBucketDelete(key, options = {}) {
10415
10823
 
10416
10824
  // src/commands/bucket/get.ts
10417
10825
  init_constants2();
10418
- import { writeFileSync as writeFileSync9 } from "fs";
10419
- import { join as join31 } from "path";
10826
+ import { writeFileSync as writeFileSync10 } from "fs";
10827
+ import { join as join33 } from "path";
10420
10828
  import { Miniflare as Miniflare12 } from "miniflare";
10421
10829
  async function runBucketGet(key, options = {}) {
10422
10830
  try {
@@ -10461,7 +10869,7 @@ async function runBucketGet(key, options = {}) {
10461
10869
  sourcemap: false,
10462
10870
  minify: false
10463
10871
  });
10464
- const bucketDir = join31(getWorkspace(), CLI_DIRECTORIES.BUCKET);
10872
+ const bucketDir = join33(getWorkspace(), CLI_DIRECTORIES.BUCKET);
10465
10873
  const mf = new Miniflare12({
10466
10874
  modules: [
10467
10875
  {
@@ -10501,7 +10909,7 @@ async function runBucketGet(key, options = {}) {
10501
10909
  }
10502
10910
  if (options.output) {
10503
10911
  const buffer = await object.arrayBuffer();
10504
- writeFileSync9(options.output, Buffer.from(buffer));
10912
+ writeFileSync10(options.output, Buffer.from(buffer));
10505
10913
  if (!options.raw) {
10506
10914
  logger.success(`Downloaded '${key}' to '${options.output}'`);
10507
10915
  logger.newLine();
@@ -10616,7 +11024,7 @@ async function runBucketInit() {
10616
11024
 
10617
11025
  // src/commands/bucket/list.ts
10618
11026
  init_constants2();
10619
- import { join as join32 } from "path";
11027
+ import { join as join34 } from "path";
10620
11028
  import { Miniflare as Miniflare13 } from "miniflare";
10621
11029
  async function runBucketList(options = {}) {
10622
11030
  try {
@@ -10652,7 +11060,7 @@ async function runBucketList(options = {}) {
10652
11060
  sourcemap: false,
10653
11061
  minify: false
10654
11062
  });
10655
- const bucketDir = join32(getWorkspace(), CLI_DIRECTORIES.BUCKET);
11063
+ const bucketDir = join34(getWorkspace(), CLI_DIRECTORIES.BUCKET);
10656
11064
  const mf = new Miniflare13({
10657
11065
  modules: [
10658
11066
  {
@@ -10735,8 +11143,8 @@ function formatBytes(bytes) {
10735
11143
 
10736
11144
  // src/commands/bucket/put.ts
10737
11145
  init_constants2();
10738
- import { readFileSync as readFileSync7, statSync as statSync2 } from "fs";
10739
- import { join as join33 } from "path";
11146
+ import { readFileSync as readFileSync8, statSync as statSync2 } from "fs";
11147
+ import { join as join35 } from "path";
10740
11148
  import { Miniflare as Miniflare14 } from "miniflare";
10741
11149
  async function runBucketPut(key, filePath, options = {}) {
10742
11150
  try {
@@ -10780,7 +11188,7 @@ async function runBucketPut(key, filePath, options = {}) {
10780
11188
  let fileBuffer;
10781
11189
  let fileSize;
10782
11190
  try {
10783
- fileBuffer = readFileSync7(filePath);
11191
+ fileBuffer = readFileSync8(filePath);
10784
11192
  fileSize = statSync2(filePath).size;
10785
11193
  } catch {
10786
11194
  if (!options.raw && !options.json) {
@@ -10793,7 +11201,7 @@ async function runBucketPut(key, filePath, options = {}) {
10793
11201
  sourcemap: false,
10794
11202
  minify: false
10795
11203
  });
10796
- const bucketDir = join33(getWorkspace(), CLI_DIRECTORIES.BUCKET);
11204
+ const bucketDir = join35(getWorkspace(), CLI_DIRECTORIES.BUCKET);
10797
11205
  const mf = new Miniflare14({
10798
11206
  modules: [
10799
11207
  {
@@ -10891,12 +11299,153 @@ bucketCommand.command("delete <key>").alias("del").alias("rm").description("Dele
10891
11299
  "Environment to use with --remote: staging (default) or production"
10892
11300
  ).action(runBucketDelete);
10893
11301
 
10894
- // src/commands/profiles/index.ts
11302
+ // src/commands/secret/index.ts
10895
11303
  import { Command as Command20 } from "commander";
10896
11304
 
11305
+ // src/commands/secret/delete.ts
11306
+ init_src();
11307
+ import { confirm as confirm9 } from "@inquirer/prompts";
11308
+ import { Command as Command17 } from "commander";
11309
+ var deleteCommand2 = new Command17("delete").description("Delete a game secret").argument("<key>", "Secret key to delete").option("--env <environment>", "Environment (staging or production)").option("-f, --force", "Skip confirmation").action(async (key, options) => {
11310
+ const { env } = options;
11311
+ try {
11312
+ const environment = ensureEnvironment(env);
11313
+ const client = await requireAuthenticatedClient();
11314
+ logger.newLine();
11315
+ const workspace = getWorkspace();
11316
+ const deployedGame = await getDeployedGame(workspace);
11317
+ if (!deployedGame) {
11318
+ logger.admonition("warning", "Deploy First", [
11319
+ `Deploy your game before managing secrets: \`playcademy deploy\``,
11320
+ `For local development, use an \`.env\` file in your project root`
11321
+ ]);
11322
+ logger.newLine();
11323
+ process.exit(1);
11324
+ }
11325
+ const game = await client.games.fetch(deployedGame.gameId);
11326
+ if (!options.force) {
11327
+ const confirmed = await confirm9({
11328
+ message: `Delete secret "${key}" from "${game.slug}" in ${environment}?`,
11329
+ default: false
11330
+ });
11331
+ if (!confirmed) {
11332
+ logger.newLine();
11333
+ logger.remark("Nothing to do");
11334
+ logger.newLine();
11335
+ return;
11336
+ }
11337
+ }
11338
+ await runStep(
11339
+ `Deleting secret from ${environment}`,
11340
+ () => client.dev.games.secrets.delete(game.slug, key),
11341
+ `Secret deleted successfully from ${environment}`
11342
+ );
11343
+ logger.newLine();
11344
+ } catch (error) {
11345
+ logAndExit(error, logger, { prefix: "Failed to delete secret" });
11346
+ }
11347
+ });
11348
+
11349
+ // src/commands/secret/list.ts
11350
+ init_src();
11351
+ init_string();
11352
+ import { Command as Command18 } from "commander";
11353
+ var listCommand2 = new Command18("list").description("List game secret keys").option("--env <environment>", "Environment (staging or production)").action(async (options) => {
11354
+ const { env } = options;
11355
+ try {
11356
+ const environment = ensureEnvironment(env);
11357
+ const client = await requireAuthenticatedClient();
11358
+ logger.newLine();
11359
+ const workspace = getWorkspace();
11360
+ const deployedGame = await getDeployedGame(workspace);
11361
+ if (!deployedGame) {
11362
+ logger.admonition("warning", "Deploy First", [
11363
+ `Deploy your game before managing secrets: \`playcademy deploy\``,
11364
+ `For local development, use an \`.env\` file in your project root`
11365
+ ]);
11366
+ logger.newLine();
11367
+ process.exit(1);
11368
+ }
11369
+ const game = await client.games.fetch(deployedGame.gameId);
11370
+ const keys = await runStep(
11371
+ `Fetching secrets from ${environment}`,
11372
+ () => client.dev.games.secrets.list(game.slug),
11373
+ (keys2) => `Found ${keys2.length} ${pluralize(keys2.length, "secret")}`
11374
+ );
11375
+ if (keys.length === 0) {
11376
+ logger.newLine();
11377
+ logger.remark("No secrets configured");
11378
+ logger.newLine();
11379
+ return;
11380
+ }
11381
+ logger.newLine();
11382
+ keys.forEach((key) => {
11383
+ logger.highlight(key, 1);
11384
+ logger.dim("(value hidden)", 2);
11385
+ });
11386
+ logger.newLine();
11387
+ } catch (error) {
11388
+ logAndExit(error, logger, { prefix: "Failed to list secrets" });
11389
+ }
11390
+ });
11391
+
11392
+ // src/commands/secret/set.ts
11393
+ init_src();
11394
+ import { Command as Command19 } from "commander";
11395
+ var setCommand = new Command19("set").description("Set or update a game secret").argument("<key>", "Secret key (e.g., STRIPE_KEY)").argument("<value>", "Secret value").option("--env <environment>", "Environment (staging or production)").action(async (key, value, options) => {
11396
+ const { env } = options;
11397
+ try {
11398
+ const environment = ensureEnvironment(env);
11399
+ const client = await requireAuthenticatedClient();
11400
+ logger.newLine();
11401
+ const workspace = getWorkspace();
11402
+ const deployedGame = await getDeployedGame(workspace);
11403
+ if (!deployedGame) {
11404
+ logger.admonition("warning", "Deploy First", [
11405
+ `Deploy your game before managing secrets: \`playcademy deploy\``,
11406
+ `For local development, use an \`.env\` file in your project root`
11407
+ ]);
11408
+ logger.newLine();
11409
+ process.exit(1);
11410
+ }
11411
+ const game = await client.games.fetch(deployedGame.gameId);
11412
+ await runStep(
11413
+ `Setting secret "${key}" in ${environment}`,
11414
+ () => client.dev.games.secrets.set(game.slug, { [key]: value }),
11415
+ `Secret "${key}" set successfully in ${environment}`
11416
+ );
11417
+ logger.newLine();
11418
+ } catch (error) {
11419
+ logAndExit(error, logger, { prefix: "Failed to set secret" });
11420
+ }
11421
+ });
11422
+
11423
+ // src/commands/secret/index.ts
11424
+ var secretCommand = new Command20("secrets").description("Manage game secrets").addCommand(setCommand).addCommand(listCommand2).addCommand(deleteCommand2);
11425
+
11426
+ // src/commands/types.ts
11427
+ init_src();
11428
+ import { Command as Command21 } from "commander";
11429
+ var typesCommand = new Command21("types").description("Generate TypeScript type definitions").action(async () => {
11430
+ try {
11431
+ logger.newLine();
11432
+ await runStep(
11433
+ "Generating TypeScript types",
11434
+ () => ensurePlaycademyTypes({ verbose: true }),
11435
+ "Types generated successfully"
11436
+ );
11437
+ logger.newLine();
11438
+ } catch (error) {
11439
+ logAndExit(error, logger, { prefix: "Failed to generate types" });
11440
+ }
11441
+ });
11442
+
11443
+ // src/commands/profiles/index.ts
11444
+ import { Command as Command25 } from "commander";
11445
+
10897
11446
  // src/commands/profiles/list.ts
10898
11447
  init_string();
10899
- import { Command as Command17 } from "commander";
11448
+ import { Command as Command22 } from "commander";
10900
11449
  async function listProfilesAction() {
10901
11450
  try {
10902
11451
  const profilesMap = await listProfiles();
@@ -10938,12 +11487,12 @@ async function listProfilesAction() {
10938
11487
  process.exit(1);
10939
11488
  }
10940
11489
  }
10941
- var listCommand2 = new Command17("list").alias("ls").description("List all stored authentication profiles").action(listProfilesAction);
11490
+ var listCommand3 = new Command22("list").alias("ls").description("List all stored authentication profiles").action(listProfilesAction);
10942
11491
 
10943
11492
  // src/commands/profiles/remove.ts
10944
11493
  import { bold as bold6 } from "colorette";
10945
- import { Command as Command18 } from "commander";
10946
- var removeCommand = new Command18("remove").alias("rm").description('Remove an authentication profile (defaults to "default")').argument("[name]", "Profile name to remove", "default").option("--env <environment>", "Environment to remove profile from (staging or production)").action(async (name, options) => {
11494
+ import { Command as Command23 } from "commander";
11495
+ var removeCommand = new Command23("remove").alias("rm").description('Remove an authentication profile (defaults to "default")').argument("[name]", "Profile name to remove", "default").option("--env <environment>", "Environment to remove profile from (staging or production)").action(async (name, options) => {
10947
11496
  const { env } = options;
10948
11497
  const environment = ensureEnvironment(env);
10949
11498
  try {
@@ -10968,9 +11517,9 @@ var removeCommand = new Command18("remove").alias("rm").description('Remove an a
10968
11517
 
10969
11518
  // src/commands/profiles/reset.ts
10970
11519
  init_string();
10971
- import { confirm as confirm9 } from "@inquirer/prompts";
10972
- import { Command as Command19 } from "commander";
10973
- var resetCommand = new Command19("reset").description(
11520
+ import { confirm as confirm10 } from "@inquirer/prompts";
11521
+ import { Command as Command24 } from "commander";
11522
+ var resetCommand = new Command24("reset").description(
10974
11523
  "Remove all authentication profiles across all environments (requires confirmation)"
10975
11524
  ).alias("clear").action(async () => {
10976
11525
  try {
@@ -11006,7 +11555,7 @@ var resetCommand = new Command19("reset").description(
11006
11555
  logger.newLine();
11007
11556
  }
11008
11557
  }
11009
- const confirmed = await confirm9({
11558
+ const confirmed = await confirm10({
11010
11559
  message: "Are you sure you want to remove all profiles?",
11011
11560
  default: false
11012
11561
  });
@@ -11046,19 +11595,19 @@ var resetCommand = new Command19("reset").description(
11046
11595
  });
11047
11596
 
11048
11597
  // src/commands/profiles/index.ts
11049
- var profilesCommand = new Command20("profiles").description("Manage authentication profiles").action(listProfilesAction);
11050
- profilesCommand.addCommand(listCommand2);
11598
+ var profilesCommand = new Command25("profiles").description("Manage authentication profiles").action(listProfilesAction);
11599
+ profilesCommand.addCommand(listCommand3);
11051
11600
  profilesCommand.addCommand(removeCommand);
11052
11601
  profilesCommand.addCommand(resetCommand);
11053
11602
 
11054
11603
  // src/commands/timeback/index.ts
11055
- import { Command as Command26 } from "commander";
11604
+ import { Command as Command31 } from "commander";
11056
11605
 
11057
11606
  // src/commands/timeback/cleanup.ts
11058
11607
  init_src();
11059
- import { confirm as confirm10 } from "@inquirer/prompts";
11060
- import { Command as Command21 } from "commander";
11061
- var cleanupCommand = new Command21("cleanup").description("Remove TimeBack integration for your game").option(
11608
+ import { confirm as confirm11 } from "@inquirer/prompts";
11609
+ import { Command as Command26 } from "commander";
11610
+ var cleanupCommand = new Command26("cleanup").description("Remove TimeBack integration for your game").option(
11062
11611
  "--env <environment>",
11063
11612
  "Environment to remove TimeBack integration from (staging or production)"
11064
11613
  ).action(async (options) => {
@@ -11080,7 +11629,7 @@ var cleanupCommand = new Command21("cleanup").description("Remove TimeBack integ
11080
11629
  return;
11081
11630
  }
11082
11631
  displayCleanupWarning(integration, game.displayName, logger);
11083
- const confirmed = await confirm10({
11632
+ const confirmed = await confirm11({
11084
11633
  message: "Are you sure you want to remove TimeBack integration?",
11085
11634
  default: false
11086
11635
  });
@@ -11109,8 +11658,8 @@ var cleanupCommand = new Command21("cleanup").description("Remove TimeBack integ
11109
11658
  });
11110
11659
 
11111
11660
  // src/commands/timeback/init.ts
11112
- import { Command as Command22 } from "commander";
11113
- var initCommand2 = new Command22("init").description("Add TimeBack integration to your project").action(async () => {
11661
+ import { Command as Command27 } from "commander";
11662
+ var initCommand2 = new Command27("init").description("Add TimeBack integration to your project").action(async () => {
11114
11663
  try {
11115
11664
  logger.newLine();
11116
11665
  const configPath = await findConfigPath().catch(() => {
@@ -11163,8 +11712,8 @@ var initCommand2 = new Command22("init").description("Add TimeBack integration t
11163
11712
 
11164
11713
  // src/commands/timeback/setup.ts
11165
11714
  init_src();
11166
- import { Command as Command23 } from "commander";
11167
- var setupCommand = new Command23("setup").description("Set up TimeBack integration for your game").option("--dry-run", "Validate configuration without creating resources").option("--verbose, -v", "Output detailed information").option(
11715
+ import { Command as Command28 } from "commander";
11716
+ var setupCommand = new Command28("setup").description("Set up TimeBack integration for your game").option("--dry-run", "Validate configuration without creating resources").option("--verbose, -v", "Output detailed information").option(
11168
11717
  "--env <environment>",
11169
11718
  "Environment to set up TimeBack integration in (staging or production)"
11170
11719
  ).action(async (options) => {
@@ -11256,10 +11805,10 @@ var setupCommand = new Command23("setup").description("Set up TimeBack integrati
11256
11805
  // src/commands/timeback/update.ts
11257
11806
  init_src();
11258
11807
  init_string();
11259
- import { confirm as confirm11 } from "@inquirer/prompts";
11808
+ import { confirm as confirm12 } from "@inquirer/prompts";
11260
11809
  import { green as green4, red as red3 } from "colorette";
11261
- import { Command as Command24 } from "commander";
11262
- var updateCommand = new Command24("update").description("Update TimeBack integration configuration for your game").option("--verbose, -v", "Output detailed information").option(
11810
+ import { Command as Command29 } from "commander";
11811
+ var updateCommand = new Command29("update").description("Update TimeBack integration configuration for your game").option("--verbose, -v", "Output detailed information").option(
11263
11812
  "--env <environment>",
11264
11813
  "Environment to update TimeBack integration in (staging or production)"
11265
11814
  ).action(async (options) => {
@@ -11337,7 +11886,7 @@ var updateCommand = new Command24("update").description("Update TimeBack integra
11337
11886
  logger.data(change.label, `${red3(change.current)} \u2192 ${green4(change.next)}`, 1);
11338
11887
  }
11339
11888
  logger.newLine();
11340
- const confirmed = await confirm11({
11889
+ const confirmed = await confirm12({
11341
11890
  message: `Update ${changeDetails.length} ${pluralize(changeDetails.length, "field")} in TimeBack?`,
11342
11891
  default: false
11343
11892
  });
@@ -11377,8 +11926,8 @@ var updateCommand = new Command24("update").description("Update TimeBack integra
11377
11926
 
11378
11927
  // src/commands/timeback/verify.ts
11379
11928
  init_src();
11380
- import { Command as Command25 } from "commander";
11381
- var verifyCommand = new Command25("verify").description("Verify TimeBack integration for your game").option("--verbose, -v", "Output detailed resource information").option(
11929
+ import { Command as Command30 } from "commander";
11930
+ var verifyCommand = new Command30("verify").description("Verify TimeBack integration for your game").option("--verbose, -v", "Output detailed resource information").option(
11382
11931
  "--env <environment>",
11383
11932
  "Environment to verify TimeBack integration in (staging or production)"
11384
11933
  ).action(async (options) => {
@@ -11428,7 +11977,7 @@ var verifyCommand = new Command25("verify").description("Verify TimeBack integra
11428
11977
  });
11429
11978
 
11430
11979
  // src/commands/timeback/index.ts
11431
- var timebackCommand = new Command26("timeback").description(
11980
+ var timebackCommand = new Command31("timeback").description(
11432
11981
  "TimeBack integration management"
11433
11982
  );
11434
11983
  timebackCommand.addCommand(initCommand2);
@@ -11438,15 +11987,15 @@ timebackCommand.addCommand(verifyCommand);
11438
11987
  timebackCommand.addCommand(cleanupCommand);
11439
11988
 
11440
11989
  // src/commands/debug/index.ts
11441
- import { Command as Command28 } from "commander";
11990
+ import { Command as Command33 } from "commander";
11442
11991
 
11443
11992
  // src/commands/debug/bundle.ts
11444
11993
  init_src();
11445
11994
  init_constants2();
11446
- import { writeFileSync as writeFileSync10 } from "fs";
11447
- import { join as join34 } from "path";
11448
- import { Command as Command27 } from "commander";
11449
- var bundleCommand = new Command27("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) => {
11995
+ import { writeFileSync as writeFileSync11 } from "fs";
11996
+ import { join as join36 } from "path";
11997
+ import { Command as Command32 } from "commander";
11998
+ var bundleCommand = new Command32("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) => {
11450
11999
  try {
11451
12000
  const workspace = getWorkspace();
11452
12001
  logger.newLine();
@@ -11474,8 +12023,8 @@ var bundleCommand = new Command27("bundle").description("Bundle and inspect the
11474
12023
  }),
11475
12024
  (result) => `Bundled ${formatSize(result.code.length)}`
11476
12025
  );
11477
- const outputPath = join34(workspace, options.output);
11478
- writeFileSync10(outputPath, bundle.code, "utf-8");
12026
+ const outputPath = join36(workspace, options.output);
12027
+ writeFileSync11(outputPath, bundle.code, "utf-8");
11479
12028
  logger.success(`Bundle saved to ${options.output}`);
11480
12029
  logger.newLine();
11481
12030
  logger.highlight("Bundle Analysis");
@@ -11515,7 +12064,7 @@ var bundleCommand = new Command27("bundle").description("Bundle and inspect the
11515
12064
  });
11516
12065
 
11517
12066
  // src/commands/debug/index.ts
11518
- var debugCommand = new Command28("debug").description("Debug and inspect game builds").addCommand(bundleCommand);
12067
+ var debugCommand = new Command33("debug").description("Debug and inspect game builds").addCommand(bundleCommand);
11519
12068
 
11520
12069
  // src/index.ts
11521
12070
  var __dirname = dirname5(fileURLToPath3(import.meta.url));
@@ -11534,6 +12083,8 @@ program.addCommand(apiCommand);
11534
12083
  program.addCommand(dbCommand);
11535
12084
  program.addCommand(kvCommand);
11536
12085
  program.addCommand(bucketCommand);
12086
+ program.addCommand(secretCommand);
12087
+ program.addCommand(typesCommand);
11537
12088
  program.addCommand(gamesCommand);
11538
12089
  program.addCommand(deployCommand);
11539
12090
  program.addCommand(timebackCommand);