@playcademy/sandbox 0.3.17-beta.37 → 0.3.17-beta.38

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/cli.js CHANGED
@@ -339,7 +339,8 @@ var PLAYCADEMY_BASE_URLS, GAME_WORKER_DOMAINS;
339
339
  var init_domains = __esm(() => {
340
340
  PLAYCADEMY_BASE_URLS = {
341
341
  production: "https://hub.playcademy.net",
342
- staging: "https://hub.dev.playcademy.net"
342
+ staging: "https://hub.dev.playcademy.net",
343
+ local: "http://localhost:5174"
343
344
  };
344
345
  GAME_WORKER_DOMAINS = {
345
346
  production: "playcademy.gg",
@@ -447,7 +448,8 @@ var WORKER_NAMING, SECRETS_PREFIX = "secrets_", CLOUDFLARE_COMPATIBILITY_DATE =
447
448
  var init_workers = __esm(() => {
448
449
  WORKER_NAMING = {
449
450
  STAGING_PREFIX: "staging-",
450
- STAGING_SUFFIX: "-staging"
451
+ STAGING_SUFFIX: "-staging",
452
+ LOCAL_PREFIX: "local-"
451
453
  };
452
454
  });
453
455
 
@@ -1330,7 +1332,7 @@ var package_default;
1330
1332
  var init_package = __esm(() => {
1331
1333
  package_default = {
1332
1334
  name: "@playcademy/sandbox",
1333
- version: "0.3.17-beta.37",
1335
+ version: "0.3.17-beta.38",
1334
1336
  description: "Local development server for Playcademy game development",
1335
1337
  type: "module",
1336
1338
  exports: {
@@ -5875,21 +5877,16 @@ var init_esm = __esm(() => {
5875
5877
  // ../api-core/src/config/schema.ts
5876
5878
  function createMinimalConfig(overrides) {
5877
5879
  return apiConfigSchema.parse({
5878
- stage: "local",
5879
- isLocal: false,
5880
+ sstStage: "test",
5880
5881
  ...overrides
5881
5882
  });
5882
5883
  }
5883
5884
  function getPlatformEnvironment(config2) {
5884
- return config2.stage === "production" ? "production" : "staging";
5885
+ return config2.sstStage === "production" ? "production" : "staging";
5885
5886
  }
5886
- function isProduction2(config2) {
5887
- return config2.stage === "production";
5888
- }
5889
- var stageSchema, ltiConfigSchema, realtimeConfigSchema, apiConfigSchema;
5887
+ var ltiConfigSchema, realtimeConfigSchema, apiConfigSchema;
5890
5888
  var init_schema = __esm(() => {
5891
5889
  init_esm();
5892
- stageSchema = exports_external.enum(["production", "dev", "local"]);
5893
5890
  ltiConfigSchema = exports_external.object({
5894
5891
  audience: exports_external.string(),
5895
5892
  jwksUrl: exports_external.string().url(),
@@ -5900,7 +5897,7 @@ var init_schema = __esm(() => {
5900
5897
  publishSecret: exports_external.string()
5901
5898
  });
5902
5899
  apiConfigSchema = exports_external.object({
5903
- stage: stageSchema,
5900
+ sstStage: exports_external.string(),
5904
5901
  isLocal: exports_external.boolean().default(false),
5905
5902
  baseUrl: exports_external.string().url().optional(),
5906
5903
  gameDomain: exports_external.string().optional(),
@@ -26292,9 +26289,38 @@ var init_playcademy = __esm(() => {
26292
26289
  init_infra();
26293
26290
  });
26294
26291
 
26292
+ // ../utils/src/tunnel.ts
26293
+ async function getTunnelUrl() {
26294
+ let response;
26295
+ try {
26296
+ response = await fetch(`${METRICS_BASE}/config`);
26297
+ } catch {
26298
+ throw new Error("Local tunnel is not running. Start it with `bun dev` or `bun scripts/infra/tunnel.ts`.");
26299
+ }
26300
+ if (!response.ok) {
26301
+ throw new Error(`Tunnel metrics endpoint returned ${response.status}`);
26302
+ }
26303
+ const data = await response.json();
26304
+ const hostname = data.config.ingress.find((r) => r.hostname)?.hostname;
26305
+ if (!hostname) {
26306
+ throw new Error("Tunnel is running but no hostname found in ingress config");
26307
+ }
26308
+ return `https://${hostname}`;
26309
+ }
26310
+ var TUNNEL_METRICS_PORT = 20241, METRICS_BASE;
26311
+ var init_tunnel = __esm(() => {
26312
+ METRICS_BASE = `http://127.0.0.1:${TUNNEL_METRICS_PORT}`;
26313
+ });
26314
+
26295
26315
  // ../api-core/src/utils/deployment.util.ts
26296
- function getDeploymentId(gameSlug, isProduction3) {
26297
- return isProduction3 ? gameSlug : `${WORKER_NAMING.STAGING_PREFIX}${gameSlug}`;
26316
+ function getDeploymentId(gameSlug, sstStage) {
26317
+ if (sstStage === "production") {
26318
+ return gameSlug;
26319
+ }
26320
+ if (sstStage === "dev") {
26321
+ return `${WORKER_NAMING.STAGING_PREFIX}${gameSlug}`;
26322
+ }
26323
+ return `${WORKER_NAMING.LOCAL_PREFIX}${sstStage}-${gameSlug}`;
26298
26324
  }
26299
26325
  function getGameWorkerApiKeyName(slug) {
26300
26326
  return `game-worker-${slug}`.substring(0, 32);
@@ -26519,8 +26545,7 @@ class DeployService {
26519
26545
  const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
26520
26546
  const flags2 = this.validateDeployRequest(request, slug);
26521
26547
  const { hasBackend, hasFrontend } = flags2;
26522
- const isProd = isProduction2(this.deps.config);
26523
- const deploymentId = getDeploymentId(slug, isProd);
26548
+ const deploymentId = getDeploymentId(slug, this.deps.config.sstStage);
26524
26549
  let frontendAssetsPath;
26525
26550
  let tempDir;
26526
26551
  if (hasFrontend) {
@@ -26534,7 +26559,15 @@ class DeployService {
26534
26559
  frontendAssetsPath = extracted.assetsPath;
26535
26560
  yield { type: "status", data: { message: "Extracting assets" } };
26536
26561
  }
26537
- const env = { GAME_ID: game.id, PLAYCADEMY_BASE_URL: this.deps.config.baseUrl };
26562
+ let platformBaseUrl = this.deps.config.baseUrl;
26563
+ if (this.deps.config.isLocal) {
26564
+ try {
26565
+ platformBaseUrl = await getTunnelUrl();
26566
+ } catch {
26567
+ throw new ValidationError("Local tunnel is not running. Ensure cloudflared is installed (`brew install cloudflared`) and the tunnel DevCommand started successfully.");
26568
+ }
26569
+ }
26570
+ const env = { GAME_ID: game.id, PLAYCADEMY_BASE_URL: platformBaseUrl };
26538
26571
  yield {
26539
26572
  type: "status",
26540
26573
  data: { message: hasBackend ? "Deploying backend code" : "Deploying to platform" }
@@ -26583,14 +26616,14 @@ class DeployService {
26583
26616
  return;
26584
26617
  }
26585
26618
  const workerBindings = {};
26586
- if (bindings?.database?.length) {
26587
- workerBindings.d1 = bindings.database;
26619
+ if (bindings?.database) {
26620
+ workerBindings.d1 = [deploymentId];
26588
26621
  }
26589
- if (bindings?.keyValue?.length) {
26590
- workerBindings.kv = bindings.keyValue;
26622
+ if (bindings?.keyValue) {
26623
+ workerBindings.kv = [deploymentId];
26591
26624
  }
26592
- if (bindings?.bucket?.length) {
26593
- workerBindings.r2 = bindings.bucket;
26625
+ if (bindings?.bucket) {
26626
+ workerBindings.r2 = [deploymentId];
26594
26627
  }
26595
26628
  if (bindings?.queues) {
26596
26629
  let toQueueName = function(queueKey) {
@@ -26657,7 +26690,7 @@ var init_deploy_service = __esm(() => {
26657
26690
  init_src();
26658
26691
  init_tables_index();
26659
26692
  init_src2();
26660
- init_config2();
26693
+ init_tunnel();
26661
26694
  init_errors();
26662
26695
  init_deployment_util();
26663
26696
  logger3 = log.scope("DeployService");
@@ -28217,8 +28250,7 @@ class DatabaseService {
28217
28250
  async reset(slug, user, schema2) {
28218
28251
  const d1 = this.getD1();
28219
28252
  const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
28220
- const isProd = isProduction2(this.deps.config);
28221
- const deploymentId = getDeploymentId(slug, isProd);
28253
+ const deploymentId = getDeploymentId(slug, this.deps.config.sstStage);
28222
28254
  logger9.debug("Resetting database", {
28223
28255
  userId: user.id,
28224
28256
  gameId: game.id,
@@ -28294,7 +28326,6 @@ var init_database_service = __esm(() => {
28294
28326
  init_drizzle_orm();
28295
28327
  init_tables_index();
28296
28328
  init_src2();
28297
- init_config2();
28298
28329
  init_errors();
28299
28330
  init_deployment_util();
28300
28331
  logger9 = log.scope("DatabaseService");
@@ -28813,8 +28844,7 @@ class SecretsService {
28813
28844
  return this.deps.cloudflare;
28814
28845
  }
28815
28846
  getDeploymentId(slug) {
28816
- const isProd = isProduction2(this.deps.config);
28817
- return getDeploymentId(slug, isProd);
28847
+ return getDeploymentId(slug, this.deps.config.sstStage);
28818
28848
  }
28819
28849
  async listKeys(slug, user) {
28820
28850
  const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
@@ -28942,7 +28972,6 @@ var logger13, INTERNAL_SECRET_KEYS;
28942
28972
  var init_secrets_service = __esm(() => {
28943
28973
  init_src();
28944
28974
  init_src2();
28945
- init_config2();
28946
28975
  init_errors();
28947
28976
  init_deployment_util();
28948
28977
  logger13 = log.scope("SecretsService");
@@ -28994,8 +29023,7 @@ class SeedService {
28994
29023
  async seed(slug, code, user, secrets) {
28995
29024
  const cf = this.getCloudflare();
28996
29025
  const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
28997
- const isProd = isProduction2(this.deps.config);
28998
- const deploymentId = getDeploymentId(slug, isProd);
29026
+ const deploymentId = getDeploymentId(slug, this.deps.config.sstStage);
28999
29027
  const uniqueSuffix = Date.now().toString(36);
29000
29028
  const seedDeploymentId = `seed-${deploymentId}-${uniqueSuffix}`;
29001
29029
  logger14.debug("Seeding database", {
@@ -29234,7 +29262,6 @@ var init_seed_service = __esm(() => {
29234
29262
  init_src();
29235
29263
  init_setup2();
29236
29264
  init_src2();
29237
- init_config2();
29238
29265
  init_errors();
29239
29266
  init_deployment_util();
29240
29267
  logger14 = log.scope("SeedService");
@@ -34885,7 +34912,7 @@ class LogsService {
34885
34912
  constructor(deps) {
34886
34913
  this.deps = deps;
34887
34914
  }
34888
- async generateToken(user, slug2, environment) {
34915
+ async generateToken(user, slug2, sstStage) {
34889
34916
  const db2 = this.deps.db;
34890
34917
  if (user.role === "admin") {
34891
34918
  const game = await db2.query.games.findFirst({
@@ -34895,7 +34922,7 @@ class LogsService {
34895
34922
  if (!game) {
34896
34923
  throw new NotFoundError("Game", slug2);
34897
34924
  }
34898
- logger28.info("Admin accessing game logs", { adminId: user.id, slug: slug2, environment });
34925
+ logger28.info("Admin accessing game logs", { adminId: user.id, slug: slug2, sstStage });
34899
34926
  } else {
34900
34927
  const isApprovedDev = user.developerStatus === "approved";
34901
34928
  if (!isApprovedDev) {
@@ -34921,8 +34948,7 @@ class LogsService {
34921
34948
  throw new NotFoundError("Game", slug2);
34922
34949
  }
34923
34950
  }
34924
- const isProduction3 = environment === "production";
34925
- const workerId = getDeploymentId(slug2, isProduction3);
34951
+ const workerId = getDeploymentId(slug2, sstStage);
34926
34952
  const token = await this.deps.mintLogStreamToken(user.id, workerId);
34927
34953
  logger28.debug("Generated log stream token", {
34928
34954
  userId: user.id,
@@ -35668,7 +35694,7 @@ function createServices(ctx) {
35668
35694
  discord,
35669
35695
  cloudflare,
35670
35696
  storage,
35671
- stage: config2.stage
35697
+ stage: config2.sstStage
35672
35698
  });
35673
35699
  const player = createPlayerServices({
35674
35700
  db: db2,
@@ -38902,7 +38928,7 @@ var init_providers = __esm(() => {
38902
38928
  function buildConfig(options) {
38903
38929
  const baseUrl = `http://localhost:${options.port ?? 3000}`;
38904
38930
  return createMinimalConfig({
38905
- stage: "local",
38931
+ sstStage: "sandbox",
38906
38932
  baseUrl,
38907
38933
  gameDomain: "localhost",
38908
38934
  uploadBucket: "sandbox-uploads",
@@ -38939,7 +38965,7 @@ function createSandboxContext(options) {
38939
38965
  Object.assign(services, createServices(ctx));
38940
38966
  cachedServiceContext = ctx;
38941
38967
  log.debug("[Sandbox] ServiceContext initialized", {
38942
- stage: config2.stage,
38968
+ sstStage: config2.sstStage,
38943
38969
  baseUrl: config2.baseUrl,
38944
38970
  hasTimeback: Boolean(timeback2)
38945
38971
  });
@@ -94941,9 +94967,9 @@ var init_schemas2 = __esm(() => {
94941
94967
  compatibilityDate: exports_external.string().optional(),
94942
94968
  compatibilityFlags: exports_external.array(exports_external.string()).optional(),
94943
94969
  bindings: exports_external.object({
94944
- database: exports_external.array(exports_external.string()).optional(),
94945
- keyValue: exports_external.array(exports_external.string()).optional(),
94946
- bucket: exports_external.array(exports_external.string()).optional(),
94970
+ database: exports_external.union([exports_external.literal(true), exports_external.array(exports_external.string())]).optional(),
94971
+ keyValue: exports_external.union([exports_external.literal(true), exports_external.array(exports_external.string())]).optional(),
94972
+ bucket: exports_external.union([exports_external.literal(true), exports_external.array(exports_external.string())]).optional(),
94947
94973
  queues: exports_external.record(exports_external.string(), exports_external.union([
94948
94974
  exports_external.literal(true),
94949
94975
  exports_external.object({
@@ -96914,8 +96940,8 @@ var init_logs_controller = __esm(() => {
96914
96940
  let body2;
96915
96941
  try {
96916
96942
  const json4 = await ctx.request.json();
96917
- if (json4.environment !== "staging" && json4.environment !== "production") {
96918
- throw ApiError.badRequest('Invalid environment. Must be "staging" or "production".');
96943
+ if (json4.environment !== "local" && json4.environment !== "staging" && json4.environment !== "production") {
96944
+ throw ApiError.badRequest('Invalid environment. Must be "local", "staging", or "production".');
96919
96945
  }
96920
96946
  body2 = json4;
96921
96947
  } catch (error2) {
@@ -96929,7 +96955,13 @@ var init_logs_controller = __esm(() => {
96929
96955
  slug: slug2,
96930
96956
  environment: body2.environment
96931
96957
  });
96932
- return ctx.services.logs.generateToken(ctx.user, slug2, body2.environment);
96958
+ const envToSstStage = {
96959
+ local: ctx.config.sstStage,
96960
+ staging: "dev",
96961
+ production: "production"
96962
+ };
96963
+ const sstStage = envToSstStage[body2.environment] ?? "dev";
96964
+ return ctx.services.logs.generateToken(ctx.user, slug2, sstStage);
96933
96965
  });
96934
96966
  logs = {
96935
96967
  generateToken
@@ -97281,6 +97313,7 @@ var init_seed_controller = __esm(() => {
97281
97313
  var logger61, start2, end, mintToken, sessions2;
97282
97314
  var init_session_controller = __esm(() => {
97283
97315
  init_src2();
97316
+ init_tunnel();
97284
97317
  init_errors();
97285
97318
  init_utils11();
97286
97319
  logger61 = log.scope("SessionController");
@@ -97315,7 +97348,14 @@ var init_session_controller = __esm(() => {
97315
97348
  throw ApiError.badRequest("Missing game ID or slug");
97316
97349
  }
97317
97350
  logger61.debug("Minting token", { userId: ctx.user.id, gameIdOrSlug, launchId: ctx.launchId });
97318
- return ctx.services.session.mintToken(gameIdOrSlug, ctx.user.id);
97351
+ const { token, exp } = await ctx.services.session.mintToken(gameIdOrSlug, ctx.user.id);
97352
+ let baseUrl;
97353
+ if (ctx.config.isLocal) {
97354
+ try {
97355
+ baseUrl = await getTunnelUrl();
97356
+ } catch {}
97357
+ }
97358
+ return { token, exp, baseUrl };
97319
97359
  });
97320
97360
  sessions2 = {
97321
97361
  start: start2,
package/dist/constants.js CHANGED
@@ -174,7 +174,8 @@ var PLAYCADEMY_BASE_URLS, GAME_WORKER_DOMAINS;
174
174
  var init_domains = __esm(() => {
175
175
  PLAYCADEMY_BASE_URLS = {
176
176
  production: "https://hub.playcademy.net",
177
- staging: "https://hub.dev.playcademy.net"
177
+ staging: "https://hub.dev.playcademy.net",
178
+ local: "http://localhost:5174"
178
179
  };
179
180
  GAME_WORKER_DOMAINS = {
180
181
  production: "playcademy.gg",
@@ -282,7 +283,8 @@ var WORKER_NAMING, SECRETS_PREFIX = "secrets_", CLOUDFLARE_COMPATIBILITY_DATE =
282
283
  var init_workers = __esm(() => {
283
284
  WORKER_NAMING = {
284
285
  STAGING_PREFIX: "staging-",
285
- STAGING_SUFFIX: "-staging"
286
+ STAGING_SUFFIX: "-staging",
287
+ LOCAL_PREFIX: "local-"
286
288
  };
287
289
  });
288
290
 
package/dist/server.js CHANGED
@@ -338,7 +338,8 @@ var PLAYCADEMY_BASE_URLS, GAME_WORKER_DOMAINS;
338
338
  var init_domains = __esm(() => {
339
339
  PLAYCADEMY_BASE_URLS = {
340
340
  production: "https://hub.playcademy.net",
341
- staging: "https://hub.dev.playcademy.net"
341
+ staging: "https://hub.dev.playcademy.net",
342
+ local: "http://localhost:5174"
342
343
  };
343
344
  GAME_WORKER_DOMAINS = {
344
345
  production: "playcademy.gg",
@@ -446,7 +447,8 @@ var WORKER_NAMING, SECRETS_PREFIX = "secrets_", CLOUDFLARE_COMPATIBILITY_DATE =
446
447
  var init_workers = __esm(() => {
447
448
  WORKER_NAMING = {
448
449
  STAGING_PREFIX: "staging-",
449
- STAGING_SUFFIX: "-staging"
450
+ STAGING_SUFFIX: "-staging",
451
+ LOCAL_PREFIX: "local-"
450
452
  };
451
453
  });
452
454
 
@@ -1329,7 +1331,7 @@ var package_default;
1329
1331
  var init_package = __esm(() => {
1330
1332
  package_default = {
1331
1333
  name: "@playcademy/sandbox",
1332
- version: "0.3.17-beta.37",
1334
+ version: "0.3.17-beta.38",
1333
1335
  description: "Local development server for Playcademy game development",
1334
1336
  type: "module",
1335
1337
  exports: {
@@ -5874,21 +5876,16 @@ var init_esm = __esm(() => {
5874
5876
  // ../api-core/src/config/schema.ts
5875
5877
  function createMinimalConfig(overrides) {
5876
5878
  return apiConfigSchema.parse({
5877
- stage: "local",
5878
- isLocal: false,
5879
+ sstStage: "test",
5879
5880
  ...overrides
5880
5881
  });
5881
5882
  }
5882
5883
  function getPlatformEnvironment(config2) {
5883
- return config2.stage === "production" ? "production" : "staging";
5884
+ return config2.sstStage === "production" ? "production" : "staging";
5884
5885
  }
5885
- function isProduction2(config2) {
5886
- return config2.stage === "production";
5887
- }
5888
- var stageSchema, ltiConfigSchema, realtimeConfigSchema, apiConfigSchema;
5886
+ var ltiConfigSchema, realtimeConfigSchema, apiConfigSchema;
5889
5887
  var init_schema = __esm(() => {
5890
5888
  init_esm();
5891
- stageSchema = exports_external.enum(["production", "dev", "local"]);
5892
5889
  ltiConfigSchema = exports_external.object({
5893
5890
  audience: exports_external.string(),
5894
5891
  jwksUrl: exports_external.string().url(),
@@ -5899,7 +5896,7 @@ var init_schema = __esm(() => {
5899
5896
  publishSecret: exports_external.string()
5900
5897
  });
5901
5898
  apiConfigSchema = exports_external.object({
5902
- stage: stageSchema,
5899
+ sstStage: exports_external.string(),
5903
5900
  isLocal: exports_external.boolean().default(false),
5904
5901
  baseUrl: exports_external.string().url().optional(),
5905
5902
  gameDomain: exports_external.string().optional(),
@@ -26291,9 +26288,38 @@ var init_playcademy = __esm(() => {
26291
26288
  init_infra();
26292
26289
  });
26293
26290
 
26291
+ // ../utils/src/tunnel.ts
26292
+ async function getTunnelUrl() {
26293
+ let response;
26294
+ try {
26295
+ response = await fetch(`${METRICS_BASE}/config`);
26296
+ } catch {
26297
+ throw new Error("Local tunnel is not running. Start it with `bun dev` or `bun scripts/infra/tunnel.ts`.");
26298
+ }
26299
+ if (!response.ok) {
26300
+ throw new Error(`Tunnel metrics endpoint returned ${response.status}`);
26301
+ }
26302
+ const data = await response.json();
26303
+ const hostname = data.config.ingress.find((r) => r.hostname)?.hostname;
26304
+ if (!hostname) {
26305
+ throw new Error("Tunnel is running but no hostname found in ingress config");
26306
+ }
26307
+ return `https://${hostname}`;
26308
+ }
26309
+ var TUNNEL_METRICS_PORT = 20241, METRICS_BASE;
26310
+ var init_tunnel = __esm(() => {
26311
+ METRICS_BASE = `http://127.0.0.1:${TUNNEL_METRICS_PORT}`;
26312
+ });
26313
+
26294
26314
  // ../api-core/src/utils/deployment.util.ts
26295
- function getDeploymentId(gameSlug, isProduction3) {
26296
- return isProduction3 ? gameSlug : `${WORKER_NAMING.STAGING_PREFIX}${gameSlug}`;
26315
+ function getDeploymentId(gameSlug, sstStage) {
26316
+ if (sstStage === "production") {
26317
+ return gameSlug;
26318
+ }
26319
+ if (sstStage === "dev") {
26320
+ return `${WORKER_NAMING.STAGING_PREFIX}${gameSlug}`;
26321
+ }
26322
+ return `${WORKER_NAMING.LOCAL_PREFIX}${sstStage}-${gameSlug}`;
26297
26323
  }
26298
26324
  function getGameWorkerApiKeyName(slug) {
26299
26325
  return `game-worker-${slug}`.substring(0, 32);
@@ -26518,8 +26544,7 @@ class DeployService {
26518
26544
  const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
26519
26545
  const flags2 = this.validateDeployRequest(request, slug);
26520
26546
  const { hasBackend, hasFrontend } = flags2;
26521
- const isProd = isProduction2(this.deps.config);
26522
- const deploymentId = getDeploymentId(slug, isProd);
26547
+ const deploymentId = getDeploymentId(slug, this.deps.config.sstStage);
26523
26548
  let frontendAssetsPath;
26524
26549
  let tempDir;
26525
26550
  if (hasFrontend) {
@@ -26533,7 +26558,15 @@ class DeployService {
26533
26558
  frontendAssetsPath = extracted.assetsPath;
26534
26559
  yield { type: "status", data: { message: "Extracting assets" } };
26535
26560
  }
26536
- const env = { GAME_ID: game.id, PLAYCADEMY_BASE_URL: this.deps.config.baseUrl };
26561
+ let platformBaseUrl = this.deps.config.baseUrl;
26562
+ if (this.deps.config.isLocal) {
26563
+ try {
26564
+ platformBaseUrl = await getTunnelUrl();
26565
+ } catch {
26566
+ throw new ValidationError("Local tunnel is not running. Ensure cloudflared is installed (`brew install cloudflared`) and the tunnel DevCommand started successfully.");
26567
+ }
26568
+ }
26569
+ const env = { GAME_ID: game.id, PLAYCADEMY_BASE_URL: platformBaseUrl };
26537
26570
  yield {
26538
26571
  type: "status",
26539
26572
  data: { message: hasBackend ? "Deploying backend code" : "Deploying to platform" }
@@ -26582,14 +26615,14 @@ class DeployService {
26582
26615
  return;
26583
26616
  }
26584
26617
  const workerBindings = {};
26585
- if (bindings?.database?.length) {
26586
- workerBindings.d1 = bindings.database;
26618
+ if (bindings?.database) {
26619
+ workerBindings.d1 = [deploymentId];
26587
26620
  }
26588
- if (bindings?.keyValue?.length) {
26589
- workerBindings.kv = bindings.keyValue;
26621
+ if (bindings?.keyValue) {
26622
+ workerBindings.kv = [deploymentId];
26590
26623
  }
26591
- if (bindings?.bucket?.length) {
26592
- workerBindings.r2 = bindings.bucket;
26624
+ if (bindings?.bucket) {
26625
+ workerBindings.r2 = [deploymentId];
26593
26626
  }
26594
26627
  if (bindings?.queues) {
26595
26628
  let toQueueName = function(queueKey) {
@@ -26656,7 +26689,7 @@ var init_deploy_service = __esm(() => {
26656
26689
  init_src();
26657
26690
  init_tables_index();
26658
26691
  init_src2();
26659
- init_config2();
26692
+ init_tunnel();
26660
26693
  init_errors();
26661
26694
  init_deployment_util();
26662
26695
  logger3 = log.scope("DeployService");
@@ -28216,8 +28249,7 @@ class DatabaseService {
28216
28249
  async reset(slug, user, schema2) {
28217
28250
  const d1 = this.getD1();
28218
28251
  const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
28219
- const isProd = isProduction2(this.deps.config);
28220
- const deploymentId = getDeploymentId(slug, isProd);
28252
+ const deploymentId = getDeploymentId(slug, this.deps.config.sstStage);
28221
28253
  logger9.debug("Resetting database", {
28222
28254
  userId: user.id,
28223
28255
  gameId: game.id,
@@ -28293,7 +28325,6 @@ var init_database_service = __esm(() => {
28293
28325
  init_drizzle_orm();
28294
28326
  init_tables_index();
28295
28327
  init_src2();
28296
- init_config2();
28297
28328
  init_errors();
28298
28329
  init_deployment_util();
28299
28330
  logger9 = log.scope("DatabaseService");
@@ -28812,8 +28843,7 @@ class SecretsService {
28812
28843
  return this.deps.cloudflare;
28813
28844
  }
28814
28845
  getDeploymentId(slug) {
28815
- const isProd = isProduction2(this.deps.config);
28816
- return getDeploymentId(slug, isProd);
28846
+ return getDeploymentId(slug, this.deps.config.sstStage);
28817
28847
  }
28818
28848
  async listKeys(slug, user) {
28819
28849
  const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
@@ -28941,7 +28971,6 @@ var logger13, INTERNAL_SECRET_KEYS;
28941
28971
  var init_secrets_service = __esm(() => {
28942
28972
  init_src();
28943
28973
  init_src2();
28944
- init_config2();
28945
28974
  init_errors();
28946
28975
  init_deployment_util();
28947
28976
  logger13 = log.scope("SecretsService");
@@ -28993,8 +29022,7 @@ class SeedService {
28993
29022
  async seed(slug, code, user, secrets) {
28994
29023
  const cf = this.getCloudflare();
28995
29024
  const game = await this.deps.validateDeveloperAccessBySlug(user, slug);
28996
- const isProd = isProduction2(this.deps.config);
28997
- const deploymentId = getDeploymentId(slug, isProd);
29025
+ const deploymentId = getDeploymentId(slug, this.deps.config.sstStage);
28998
29026
  const uniqueSuffix = Date.now().toString(36);
28999
29027
  const seedDeploymentId = `seed-${deploymentId}-${uniqueSuffix}`;
29000
29028
  logger14.debug("Seeding database", {
@@ -29233,7 +29261,6 @@ var init_seed_service = __esm(() => {
29233
29261
  init_src();
29234
29262
  init_setup2();
29235
29263
  init_src2();
29236
- init_config2();
29237
29264
  init_errors();
29238
29265
  init_deployment_util();
29239
29266
  logger14 = log.scope("SeedService");
@@ -34884,7 +34911,7 @@ class LogsService {
34884
34911
  constructor(deps) {
34885
34912
  this.deps = deps;
34886
34913
  }
34887
- async generateToken(user, slug2, environment) {
34914
+ async generateToken(user, slug2, sstStage) {
34888
34915
  const db2 = this.deps.db;
34889
34916
  if (user.role === "admin") {
34890
34917
  const game = await db2.query.games.findFirst({
@@ -34894,7 +34921,7 @@ class LogsService {
34894
34921
  if (!game) {
34895
34922
  throw new NotFoundError("Game", slug2);
34896
34923
  }
34897
- logger28.info("Admin accessing game logs", { adminId: user.id, slug: slug2, environment });
34924
+ logger28.info("Admin accessing game logs", { adminId: user.id, slug: slug2, sstStage });
34898
34925
  } else {
34899
34926
  const isApprovedDev = user.developerStatus === "approved";
34900
34927
  if (!isApprovedDev) {
@@ -34920,8 +34947,7 @@ class LogsService {
34920
34947
  throw new NotFoundError("Game", slug2);
34921
34948
  }
34922
34949
  }
34923
- const isProduction3 = environment === "production";
34924
- const workerId = getDeploymentId(slug2, isProduction3);
34950
+ const workerId = getDeploymentId(slug2, sstStage);
34925
34951
  const token = await this.deps.mintLogStreamToken(user.id, workerId);
34926
34952
  logger28.debug("Generated log stream token", {
34927
34953
  userId: user.id,
@@ -35667,7 +35693,7 @@ function createServices(ctx) {
35667
35693
  discord,
35668
35694
  cloudflare,
35669
35695
  storage,
35670
- stage: config2.stage
35696
+ stage: config2.sstStage
35671
35697
  });
35672
35698
  const player = createPlayerServices({
35673
35699
  db: db2,
@@ -38901,7 +38927,7 @@ var init_providers = __esm(() => {
38901
38927
  function buildConfig(options) {
38902
38928
  const baseUrl = `http://localhost:${options.port ?? 3000}`;
38903
38929
  return createMinimalConfig({
38904
- stage: "local",
38930
+ sstStage: "sandbox",
38905
38931
  baseUrl,
38906
38932
  gameDomain: "localhost",
38907
38933
  uploadBucket: "sandbox-uploads",
@@ -38938,7 +38964,7 @@ function createSandboxContext(options) {
38938
38964
  Object.assign(services, createServices(ctx));
38939
38965
  cachedServiceContext = ctx;
38940
38966
  log.debug("[Sandbox] ServiceContext initialized", {
38941
- stage: config2.stage,
38967
+ sstStage: config2.sstStage,
38942
38968
  baseUrl: config2.baseUrl,
38943
38969
  hasTimeback: Boolean(timeback2)
38944
38970
  });
@@ -94940,9 +94966,9 @@ var init_schemas2 = __esm(() => {
94940
94966
  compatibilityDate: exports_external.string().optional(),
94941
94967
  compatibilityFlags: exports_external.array(exports_external.string()).optional(),
94942
94968
  bindings: exports_external.object({
94943
- database: exports_external.array(exports_external.string()).optional(),
94944
- keyValue: exports_external.array(exports_external.string()).optional(),
94945
- bucket: exports_external.array(exports_external.string()).optional(),
94969
+ database: exports_external.union([exports_external.literal(true), exports_external.array(exports_external.string())]).optional(),
94970
+ keyValue: exports_external.union([exports_external.literal(true), exports_external.array(exports_external.string())]).optional(),
94971
+ bucket: exports_external.union([exports_external.literal(true), exports_external.array(exports_external.string())]).optional(),
94946
94972
  queues: exports_external.record(exports_external.string(), exports_external.union([
94947
94973
  exports_external.literal(true),
94948
94974
  exports_external.object({
@@ -96913,8 +96939,8 @@ var init_logs_controller = __esm(() => {
96913
96939
  let body2;
96914
96940
  try {
96915
96941
  const json4 = await ctx.request.json();
96916
- if (json4.environment !== "staging" && json4.environment !== "production") {
96917
- throw ApiError.badRequest('Invalid environment. Must be "staging" or "production".');
96942
+ if (json4.environment !== "local" && json4.environment !== "staging" && json4.environment !== "production") {
96943
+ throw ApiError.badRequest('Invalid environment. Must be "local", "staging", or "production".');
96918
96944
  }
96919
96945
  body2 = json4;
96920
96946
  } catch (error2) {
@@ -96928,7 +96954,13 @@ var init_logs_controller = __esm(() => {
96928
96954
  slug: slug2,
96929
96955
  environment: body2.environment
96930
96956
  });
96931
- return ctx.services.logs.generateToken(ctx.user, slug2, body2.environment);
96957
+ const envToSstStage = {
96958
+ local: ctx.config.sstStage,
96959
+ staging: "dev",
96960
+ production: "production"
96961
+ };
96962
+ const sstStage = envToSstStage[body2.environment] ?? "dev";
96963
+ return ctx.services.logs.generateToken(ctx.user, slug2, sstStage);
96932
96964
  });
96933
96965
  logs = {
96934
96966
  generateToken
@@ -97280,6 +97312,7 @@ var init_seed_controller = __esm(() => {
97280
97312
  var logger61, start2, end, mintToken, sessions2;
97281
97313
  var init_session_controller = __esm(() => {
97282
97314
  init_src2();
97315
+ init_tunnel();
97283
97316
  init_errors();
97284
97317
  init_utils11();
97285
97318
  logger61 = log.scope("SessionController");
@@ -97314,7 +97347,14 @@ var init_session_controller = __esm(() => {
97314
97347
  throw ApiError.badRequest("Missing game ID or slug");
97315
97348
  }
97316
97349
  logger61.debug("Minting token", { userId: ctx.user.id, gameIdOrSlug, launchId: ctx.launchId });
97317
- return ctx.services.session.mintToken(gameIdOrSlug, ctx.user.id);
97350
+ const { token, exp } = await ctx.services.session.mintToken(gameIdOrSlug, ctx.user.id);
97351
+ let baseUrl;
97352
+ if (ctx.config.isLocal) {
97353
+ try {
97354
+ baseUrl = await getTunnelUrl();
97355
+ } catch {}
97356
+ }
97357
+ return { token, exp, baseUrl };
97318
97358
  });
97319
97359
  sessions2 = {
97320
97360
  start: start2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.3.17-beta.37",
3
+ "version": "0.3.17-beta.38",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {