playcademy 0.7.0 → 0.8.0

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
@@ -2513,6 +2513,16 @@ var CONFIG_FILE_NAMES = [
2513
2513
  "playcademy.config.mjs"
2514
2514
  ];
2515
2515
 
2516
+ // src/constants/urls.ts
2517
+ var PLAYCADEMY_BASE_URLS = {
2518
+ staging: "https://hub.dev.playcademy.net",
2519
+ production: "https://hub.playcademy.com"
2520
+ };
2521
+ var GAME_BACKEND_DOMAINS = {
2522
+ staging: "staging.playcademy.gg",
2523
+ production: "playcademy.gg"
2524
+ };
2525
+
2516
2526
  // src/lib/auth/storage.ts
2517
2527
  import { access, mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
2518
2528
  import { homedir } from "node:os";
@@ -2521,7 +2531,7 @@ import { join } from "node:path";
2521
2531
  // src/lib/core/context.ts
2522
2532
  var context = {};
2523
2533
  function setCliContext(ctx) {
2524
- context = ctx;
2534
+ context = { ...context, ...ctx };
2525
2535
  }
2526
2536
  function getCliContext() {
2527
2537
  return context;
@@ -2531,26 +2541,47 @@ function getWorkspace() {
2531
2541
  }
2532
2542
 
2533
2543
  // src/lib/core/config.ts
2544
+ function normalizeEnvironment(env) {
2545
+ if (!env) return null;
2546
+ const normalized = env.toLowerCase().trim();
2547
+ if (normalized === "staging" || normalized === "stage") {
2548
+ return "staging";
2549
+ }
2550
+ if (normalized === "production" || normalized === "prod") {
2551
+ return "production";
2552
+ }
2553
+ return null;
2554
+ }
2555
+ function ensureEnvironment(env, logger2) {
2556
+ if (env) {
2557
+ const normalized = normalizeEnvironment(env);
2558
+ if (!normalized) {
2559
+ logger2.newLine();
2560
+ logger2.error(`Invalid environment: "${env}"`);
2561
+ logger2.newLine();
2562
+ logger2.info("Valid environments: staging, production");
2563
+ logger2.newLine();
2564
+ process.exit(1);
2565
+ }
2566
+ setCliContext({ environment: normalized });
2567
+ }
2568
+ return getEnvironment();
2569
+ }
2534
2570
  function getEnvironment() {
2535
2571
  const context2 = getCliContext();
2536
2572
  if (context2.environment) {
2537
2573
  return context2.environment;
2538
2574
  }
2539
2575
  const env = process.env.PLAYCADEMY_ENV?.toLowerCase();
2540
- if (env === "staging") {
2541
- return "staging";
2576
+ if (env === "staging" || env === "production") {
2577
+ return env;
2542
2578
  }
2543
- return "production";
2579
+ return "staging";
2544
2580
  }
2545
2581
  function getBaseUrl() {
2546
2582
  const explicitUrl = process.env.PLAYCADEMY_BASE_URL;
2547
- if (explicitUrl) {
2548
- return explicitUrl;
2549
- }
2550
- if (getEnvironment() === "staging") {
2551
- return "https://hub.dev.playcademy.net";
2552
- }
2553
- return "https://hub.playcademy.com";
2583
+ if (explicitUrl) return explicitUrl;
2584
+ return PLAYCADEMY_BASE_URLS[getEnvironment()];
2554
2585
  }
2555
2586
  function getApiUrl(path) {
2556
2587
  const baseUrl = getBaseUrl();
@@ -2573,6 +2604,12 @@ async function ensureAuthDir() {
2573
2604
  const authDir = join(homedir(), ".playcademy");
2574
2605
  await mkdir(authDir, { recursive: true });
2575
2606
  }
2607
+ function createEmptyEnvironmentProfiles() {
2608
+ return {
2609
+ default: null,
2610
+ profiles: {}
2611
+ };
2612
+ }
2576
2613
  async function loadAuthStore() {
2577
2614
  try {
2578
2615
  await ensureAuthDir();
@@ -2583,8 +2620,8 @@ async function loadAuthStore() {
2583
2620
  } catch {
2584
2621
  }
2585
2622
  return {
2586
- default: null,
2587
- profiles: {}
2623
+ staging: createEmptyEnvironmentProfiles(),
2624
+ production: createEmptyEnvironmentProfiles()
2588
2625
  };
2589
2626
  }
2590
2627
  async function saveAuthStore(store) {
@@ -2601,45 +2638,79 @@ async function getCurrentProfile() {
2601
2638
  };
2602
2639
  }
2603
2640
  const store = await loadAuthStore();
2641
+ const environment = getEnvironment();
2604
2642
  const profileName = getProfileName();
2643
+ const envProfiles = store[environment];
2644
+ if (!envProfiles) {
2645
+ return null;
2646
+ }
2605
2647
  if (profileName === "default") {
2606
- return store.default;
2648
+ return envProfiles.default;
2607
2649
  }
2608
- return store.profiles[profileName] || null;
2650
+ return envProfiles.profiles[profileName] || null;
2609
2651
  }
2610
- async function saveProfile(profileName, profile) {
2652
+ async function saveProfile(environment, profileName, profile) {
2611
2653
  const store = await loadAuthStore();
2654
+ if (!store[environment]) {
2655
+ store[environment] = createEmptyEnvironmentProfiles();
2656
+ }
2657
+ const envProfiles = store[environment];
2612
2658
  if (profileName === "default") {
2613
- store.default = profile;
2659
+ envProfiles.default = profile;
2614
2660
  } else {
2615
- store.profiles[profileName] = profile;
2661
+ envProfiles.profiles[profileName] = profile;
2616
2662
  }
2617
2663
  await saveAuthStore(store);
2618
2664
  }
2619
- async function removeProfile(profileName) {
2665
+ async function removeProfile(environment, profileName) {
2620
2666
  const store = await loadAuthStore();
2667
+ if (!store[environment]) {
2668
+ return;
2669
+ }
2670
+ const envProfiles = store[environment];
2621
2671
  if (profileName === "default") {
2622
- store.default = null;
2672
+ envProfiles.default = null;
2623
2673
  } else {
2624
- delete store.profiles[profileName];
2674
+ delete envProfiles.profiles[profileName];
2625
2675
  }
2626
2676
  await saveAuthStore(store);
2627
2677
  }
2628
- async function getProfile(profileName) {
2678
+ async function getProfile(environment, profileName) {
2629
2679
  const store = await loadAuthStore();
2680
+ const envProfiles = store[environment];
2681
+ if (!envProfiles) {
2682
+ return null;
2683
+ }
2630
2684
  if (profileName === "default") {
2631
- return store.default;
2685
+ return envProfiles.default;
2632
2686
  }
2633
- return store.profiles[profileName] || null;
2687
+ return envProfiles.profiles[profileName] || null;
2634
2688
  }
2635
2689
  async function listProfiles() {
2636
2690
  const store = await loadAuthStore();
2637
- const profiles = [];
2638
- if (store.default) {
2639
- profiles.push("default");
2640
- }
2641
- profiles.push(...Object.keys(store.profiles));
2642
- return profiles;
2691
+ const profileMap = /* @__PURE__ */ new Map();
2692
+ const stagingProfiles = [];
2693
+ if (store.staging.default) {
2694
+ stagingProfiles.push("default");
2695
+ }
2696
+ stagingProfiles.push(...Object.keys(store.staging.profiles));
2697
+ profileMap.set("staging", stagingProfiles);
2698
+ const productionProfiles = [];
2699
+ if (store.production.default) {
2700
+ productionProfiles.push("default");
2701
+ }
2702
+ productionProfiles.push(...Object.keys(store.production.profiles));
2703
+ profileMap.set("production", productionProfiles);
2704
+ return profileMap;
2705
+ }
2706
+ async function getAuthenticatedEnvironments() {
2707
+ const store = await loadAuthStore();
2708
+ const environments = [];
2709
+ const hasStaging = store.staging.default !== null || Object.keys(store.staging.profiles).length > 0;
2710
+ const hasProduction = store.production.default !== null || Object.keys(store.production.profiles).length > 0;
2711
+ if (hasStaging) environments.push("staging");
2712
+ if (hasProduction) environments.push("production");
2713
+ return environments;
2643
2714
  }
2644
2715
 
2645
2716
  // src/lib/auth/http-server/server.ts
@@ -7260,15 +7331,7 @@ function createDevNamespace(client) {
7260
7331
  throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
7261
7332
  }
7262
7333
  }
7263
- const baseUrl = (() => {
7264
- const anyClient = client;
7265
- try {
7266
- return typeof anyClient.getBaseUrl === "function" ? anyClient.getBaseUrl() : "/api";
7267
- } catch {
7268
- return "/api";
7269
- }
7270
- })();
7271
- const finalizeUrl = baseUrl.replace(/\/$/, "") + "/games/uploads/finalize/";
7334
+ const finalizeUrl = `${client.baseUrl}/games/uploads/finalize/`;
7272
7335
  const authToken = client.getToken();
7273
7336
  const tokenType = client.getTokenType();
7274
7337
  const headers = {
@@ -8746,13 +8809,13 @@ var init_achievements = () => {
8746
8809
  function createTimebackNamespace(client) {
8747
8810
  return {
8748
8811
  recordProgress: (progressData) => {
8749
- return client["request"]("/api/integrations/timeback/progress", "POST", { progressData }, void 0, true);
8812
+ return client["requestGameBackend"]("/api/integrations/timeback/progress", "POST", { progressData });
8750
8813
  },
8751
8814
  recordSessionEnd: (sessionData) => {
8752
- return client["request"]("/api/integrations/timeback/session-end", "POST", { sessionData }, void 0, true);
8815
+ return client["requestGameBackend"]("/api/integrations/timeback/session-end", "POST", { sessionData });
8753
8816
  },
8754
8817
  awardXP: (xpAmount, metadata) => {
8755
- return client["request"]("/api/integrations/timeback/award-xp", "POST", { xpAmount, metadata }, void 0, true);
8818
+ return client["requestGameBackend"]("/api/integrations/timeback/award-xp", "POST", { xpAmount, metadata });
8756
8819
  },
8757
8820
  management: {
8758
8821
  setup: (request2) => {
@@ -9061,7 +9124,7 @@ var init_client = __esm2(() => {
9061
9124
  authContext;
9062
9125
  initPayload;
9063
9126
  constructor(config) {
9064
- this.baseUrl = config?.baseUrl || "";
9127
+ this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
9065
9128
  this.gameUrl = config?.gameUrl;
9066
9129
  this.gameId = config?.gameId;
9067
9130
  this.config = config || {};
@@ -9077,8 +9140,9 @@ var init_client = __esm2(() => {
9077
9140
  return `${effectiveBaseUrl}/api`;
9078
9141
  }
9079
9142
  getGameBackendUrl() {
9080
- if (!this.gameUrl)
9081
- return this.getBaseUrl();
9143
+ if (!this.gameUrl) {
9144
+ throw new PlaycademyError("Game backend URL not configured. gameUrl must be set to use game backend features.");
9145
+ }
9082
9146
  const isRelative = this.gameUrl.startsWith("/");
9083
9147
  const isBrowser2 = typeof window !== "undefined";
9084
9148
  const effectiveGameUrl = isRelative && isBrowser2 ? `${window.location.origin}${this.gameUrl}` : this.gameUrl;
@@ -9115,17 +9179,29 @@ var init_client = __esm2(() => {
9115
9179
  listener(payload);
9116
9180
  });
9117
9181
  }
9118
- async request(path, method, body, headers, useGameBackend = false) {
9182
+ async request(path, method, body, headers) {
9119
9183
  const effectiveHeaders = {
9120
9184
  ...headers,
9121
9185
  ...this.authStrategy.getHeaders()
9122
9186
  };
9123
- const targetBaseUrl = useGameBackend ? this.getGameBackendUrl() : this.baseUrl;
9124
9187
  return request({
9125
9188
  path,
9126
9189
  method,
9127
9190
  body,
9128
- baseUrl: targetBaseUrl,
9191
+ baseUrl: this.baseUrl,
9192
+ extraHeaders: effectiveHeaders
9193
+ });
9194
+ }
9195
+ async requestGameBackend(path, method, body, headers) {
9196
+ const effectiveHeaders = {
9197
+ ...headers,
9198
+ ...this.authStrategy.getHeaders()
9199
+ };
9200
+ return request({
9201
+ path,
9202
+ method,
9203
+ body,
9204
+ baseUrl: this.getGameBackendUrl(),
9129
9205
  extraHeaders: effectiveHeaders
9130
9206
  });
9131
9207
  }
@@ -9362,9 +9438,8 @@ var logger = {
9362
9438
  async function createClient() {
9363
9439
  const profile = await getCurrentProfile();
9364
9440
  const baseUrl = getBaseUrl();
9365
- const apiBaseUrl = `${baseUrl}/api`;
9366
9441
  const client = new PlaycademyClient({
9367
- baseUrl: apiBaseUrl,
9442
+ baseUrl,
9368
9443
  token: profile?.token
9369
9444
  });
9370
9445
  if (profile?.token && profile?.tokenType) {
@@ -9374,11 +9449,12 @@ async function createClient() {
9374
9449
  }
9375
9450
  async function requireAuthenticatedClient() {
9376
9451
  const profile = await getCurrentProfile();
9452
+ const environment = getEnvironment();
9377
9453
  if (!profile) {
9378
9454
  logger.newLine();
9379
9455
  logger.admonition("warning", "Login Required", [
9380
- "You need to be logged in to run this command.",
9381
- "Run `playcademy login` to authenticate."
9456
+ `You are not logged into ${environment}.`,
9457
+ environment === "production" ? `Run \`playcademy login --env ${environment}\` to authenticate.` : "Run `playcademy login` to authenticate."
9382
9458
  ]);
9383
9459
  logger.newLine();
9384
9460
  process.exit(1);
@@ -10238,16 +10314,25 @@ async function validateDeployConfig(config) {
10238
10314
  // src/lib/deploy/interaction.ts
10239
10315
  async function selectEnvironment(options) {
10240
10316
  let environment = options.env;
10241
- const shouldPrompt = !environment && !options.dryRun && !process.env.PLAYCADEMY_ENV && !process.env.PLAYCADEMY_BASE_URL;
10242
- if (shouldPrompt) {
10317
+ if (environment || process.env.PLAYCADEMY_ENV || process.env.PLAYCADEMY_BASE_URL) {
10318
+ if (environment) {
10319
+ setCliContext({ environment });
10320
+ }
10321
+ return;
10322
+ }
10323
+ const authenticatedEnvs = await getAuthenticatedEnvironments();
10324
+ if (authenticatedEnvs.length === 1) {
10325
+ environment = authenticatedEnvs[0];
10326
+ logger.info(`Deploying to ${environment}`, 1);
10327
+ } else if (authenticatedEnvs.length === 2 && !options.dryRun) {
10243
10328
  logger.newLine();
10244
10329
  environment = await select({
10245
10330
  message: "Select deployment environment:",
10246
10331
  choices: [
10247
- { value: "production", name: "Production (hub.playcademy.com)" },
10248
- { value: "staging", name: "Staging (hub.dev.playcademy.net)" }
10332
+ { value: "staging", name: "Staging (hub.dev.playcademy.net)" },
10333
+ { value: "production", name: "Production (hub.playcademy.com)" }
10249
10334
  ],
10250
- default: "production"
10335
+ default: "staging"
10251
10336
  });
10252
10337
  }
10253
10338
  if (environment) {
@@ -10395,10 +10480,12 @@ function displayCurrentConfiguration(context2) {
10395
10480
  } else if (config.externalUrl) {
10396
10481
  logger.data("URL", config.externalUrl, 1);
10397
10482
  }
10398
- if (existingGame) {
10483
+ if (context2.isGameDeployed) {
10399
10484
  logger.data("Status", "Deployed", 1);
10400
- } else {
10485
+ } else if (existingGame) {
10401
10486
  logger.data("Status", "Not deployed", 1);
10487
+ } else {
10488
+ logger.data("Status", "New deployment", 1);
10402
10489
  }
10403
10490
  logger.newLine();
10404
10491
  }
@@ -10427,14 +10514,15 @@ async function reportNoChanges(context2) {
10427
10514
  function reportDeploymentSuccess(result, context2) {
10428
10515
  const { game, backendDeployment } = result;
10429
10516
  const { client, previousBackendHash } = context2;
10430
- const action = context2.isNewDeployment ? "deployed" : "updated";
10517
+ const action = context2.isGameDeployed ? "updated" : "deployed";
10431
10518
  logger.newLine();
10432
10519
  logger.success(`${game.displayName} ${action} successfully!`);
10433
10520
  logger.newLine();
10434
10521
  const baseUrl = getWebBaseUrl(client.getBaseUrl());
10435
10522
  logger.data("Game URL", underline(blueBright(`${baseUrl}/g/${game.slug}`)), 1);
10436
10523
  if (backendDeployment || previousBackendHash) {
10437
- const backendUrl = `https://${game.slug}.playcademy.gg/api`;
10524
+ const gameDomain = GAME_BACKEND_DOMAINS[getEnvironment()];
10525
+ const backendUrl = `https://${game.slug}.${gameDomain}/api`;
10438
10526
  logger.data("Backend API", underline(blueBright(backendUrl)), 1);
10439
10527
  }
10440
10528
  if (game.version !== "external") {
@@ -10458,10 +10546,10 @@ import { stat as stat2 } from "node:fs/promises";
10458
10546
 
10459
10547
  // ../data/src/domains/game/helpers.ts
10460
10548
  var isHostedGame = (game) => {
10461
- return game.gameType === "hosted" && game.assetBundleBase !== null;
10549
+ return game.gameType === "hosted" && !!game.assetBundleBase;
10462
10550
  };
10463
10551
  var isExternalGame = (game) => {
10464
- return game.gameType === "external" && game.externalUrl !== null;
10552
+ return game.gameType === "external" && !!game.externalUrl;
10465
10553
  };
10466
10554
  function isGameDeployed(game) {
10467
10555
  return isHostedGame(game) || isExternalGame(game);
@@ -10527,9 +10615,9 @@ async function prepareDeploymentContext(options) {
10527
10615
  configFileName,
10528
10616
  client,
10529
10617
  projectPath,
10530
- existingGame: gameIsDeployed ? existingGame : void 0,
10618
+ existingGame,
10531
10619
  deployedGameInfo: deployedGameInfo ?? void 0,
10532
- isNewDeployment: !gameIsDeployed,
10620
+ isGameDeployed: gameIsDeployed,
10533
10621
  buildHash,
10534
10622
  buildSize,
10535
10623
  previousBuildHash: deployedGameInfo?.buildHash,
@@ -10579,8 +10667,8 @@ async function analyzeChanges(context2) {
10579
10667
  };
10580
10668
  }
10581
10669
  async function calculateDeploymentPlan(context2, changes) {
10582
- const { config, isNewDeployment, projectPath, deployBackend } = context2;
10583
- if (isNewDeployment) {
10670
+ const { config, isGameDeployed: isGameDeployed2, projectPath, deployBackend } = context2;
10671
+ if (!isGameDeployed2) {
10584
10672
  const needsBackend3 = hasLocalBackend(projectPath) || !!context2.fullConfig?.integrations;
10585
10673
  const shouldDeployBackend2 = deployBackend && needsBackend3;
10586
10674
  return {
@@ -11435,19 +11523,28 @@ initCommand.addCommand(configCommand);
11435
11523
 
11436
11524
  // src/commands/login.ts
11437
11525
  import { input as input4, password, select as select3 } from "@inquirer/prompts";
11526
+ import { bold as bold4, dim as dim6, whiteBright } from "colorette";
11438
11527
  import { Command as Command4 } from "commander";
11439
11528
  import open from "open";
11440
11529
  init_file_loader();
11441
- var loginCommand = new Command4("login").description("Authenticate with Playcademy").option("-e, --email <email>", "Email address (for email/password auth)").option("-p, --password <password>", "Password (for email/password auth)").option("--sso", "Use Timeback SSO authentication").action(async (options) => {
11442
- const { email, password: password2, sso } = options;
11443
- logger.newLine();
11530
+ var loginCommand = new Command4("login").description("Authenticate with Playcademy").option("-e, --email <email>", "Email address (for email/password auth)").option("-p, --password <password>", "Password (for email/password auth)").option("--sso", "Use Timeback SSO authentication").option("--env <environment>", "Environment to login to (staging or production)").action(async (options) => {
11531
+ const { email, password: password2, sso, env } = options;
11532
+ const environment = ensureEnvironment(env, logger);
11444
11533
  const profileName = getProfileName();
11445
- const existingProfile = await getProfile(profileName);
11534
+ logger.newLine();
11535
+ const existingProfile = await getProfile(environment, profileName);
11446
11536
  if (existingProfile) {
11447
- const email2 = existingProfile.email;
11448
- logger.info(
11449
- `Already logged in to profile "${profileName}"` + (email2 ? ` as ${email2}` : "")
11450
- );
11537
+ const email2 = existingProfile.email || "unknown user";
11538
+ const otherEnv = environment === "staging" ? "production" : "staging";
11539
+ const otherProfile = await getProfile(otherEnv, profileName);
11540
+ logger.admonition("note", "I know you!", [
11541
+ "You are already logged in",
11542
+ "",
11543
+ dim6("Email: ") + bold4(email2),
11544
+ dim6("Environment: ") + bold4(environment),
11545
+ dim6("Profile: ") + bold4(profileName),
11546
+ ...otherProfile ? [dim6("Other Profile: ") + bold4(otherEnv)] : []
11547
+ ]);
11451
11548
  logger.newLine();
11452
11549
  return;
11453
11550
  }
@@ -11462,7 +11559,7 @@ var loginCommand = new Command4("login").description("Authenticate with Playcade
11462
11559
  });
11463
11560
  logger.newLine();
11464
11561
  if (authMethod === "sso") {
11465
- await handleSsoLogin(profileName);
11562
+ await handleSsoLogin(environment, profileName);
11466
11563
  return;
11467
11564
  }
11468
11565
  const finalEmail = email || await promptForEmail();
@@ -11515,14 +11612,17 @@ var loginCommand = new Command4("login").description("Authenticate with Playcade
11515
11612
  logger.newLine();
11516
11613
  await runStep(
11517
11614
  `Saving credentials to profile "${profileName}"`,
11518
- () => saveProfile(profileName, authProfile),
11615
+ () => saveProfile(environment, profileName, authProfile),
11519
11616
  "Credentials saved successfully"
11520
11617
  );
11521
- logger.success(`Logged in as ${authProfile.email}`);
11522
- if (profileName !== "default") {
11523
- logger.newLine();
11524
- logger.info(`Profile saved as "${profileName}"`);
11525
- }
11618
+ logger.newLine();
11619
+ logger.admonition("tip", "Logged In Successfully!", [
11620
+ `Email: ${authProfile.email}`,
11621
+ `Environment: ${environment}`,
11622
+ ...profileName !== "default" ? [`Profile: ${profileName}`] : [],
11623
+ "",
11624
+ ...profileName !== "default" ? [`Use \`--profile ${profileName}\` with commands to use this profile`] : []
11625
+ ]);
11526
11626
  logger.newLine();
11527
11627
  } catch (error) {
11528
11628
  logger.newLine();
@@ -11533,7 +11633,7 @@ var loginCommand = new Command4("login").description("Authenticate with Playcade
11533
11633
  process.exit(1);
11534
11634
  }
11535
11635
  });
11536
- async function handleSsoLogin(profileName) {
11636
+ async function handleSsoLogin(environment, profileName) {
11537
11637
  try {
11538
11638
  const serverPromise = startCallbackServer(SSO_AUTH_TIMEOUT_MS);
11539
11639
  const baseUrl = getBaseUrl();
@@ -11578,21 +11678,20 @@ async function handleSsoLogin(profileName) {
11578
11678
  email: result.data.email
11579
11679
  };
11580
11680
  logger.newLine();
11581
- logger.admonition("warning", "Your API Key", [apiKeyResult.apiKey]);
11582
- logger.warn("Save this key securely - it will not be shown again!", 1);
11681
+ logger.admonition("warning", "Your API Key", [
11682
+ "",
11683
+ whiteBright(bold4(apiKeyResult.apiKey)),
11684
+ "",
11685
+ "Save this key securely - it will not be shown again!"
11686
+ ]);
11583
11687
  logger.newLine();
11584
11688
  await runStep(
11585
11689
  `Saving credentials to profile "${profileName}"`,
11586
- () => saveProfile(profileName, authProfile),
11587
- "Credentials saved successfully"
11690
+ () => saveProfile(environment, profileName, authProfile),
11691
+ "Credentials saved successfully",
11692
+ { silent: true }
11588
11693
  );
11589
- logger.success(`Logged in as ${authProfile.email}`);
11590
- if (profileName !== "default") {
11591
- logger.newLine();
11592
- logger.info(`Profile saved as "${profileName}"`);
11593
- logger.newLine();
11594
- logger.aside(`Use \`--profile ${profileName}\` with commands to use this profile`, 1);
11595
- }
11694
+ logger.success(`Logged in to ${environment} as ${authProfile.email}`);
11596
11695
  logger.newLine();
11597
11696
  } catch (error) {
11598
11697
  logger.newLine();
@@ -11631,27 +11730,23 @@ async function promptForPassword() {
11631
11730
 
11632
11731
  // src/commands/logout.ts
11633
11732
  import { Command as Command5 } from "commander";
11634
- var logoutCommand = new Command5("logout").description("Log out from Playcademy").option("--profile <name>", "Profile to log out from", "default").action(async (options) => {
11635
- const { profile: profileName } = options;
11733
+ var logoutCommand = new Command5("logout").description("Log out from Playcademy").argument("[profile]", "Profile name to logout from", "default").option("--env <environment>", "Environment to logout from (staging or production)").action(async (profileName, options) => {
11734
+ const { env } = options;
11735
+ const environment = ensureEnvironment(env, logger);
11636
11736
  try {
11637
11737
  logger.newLine();
11638
- const profile = await getProfile(profileName);
11738
+ const profile = await getProfile(environment, profileName);
11639
11739
  if (!profile) {
11640
- logger.error(`Profile "${profileName}" not found`);
11740
+ logger.error(`Profile "${profileName}" not found in ${environment}`);
11741
+ logger.newLine();
11641
11742
  logger.info("Use `playcademy profiles list` to see available profiles");
11642
11743
  logger.newLine();
11643
11744
  process.exit(1);
11644
11745
  }
11645
- await removeProfile(profileName);
11646
- logger.success(`Logged out from profile "${profileName}"`);
11647
- if (profile.email) {
11648
- logger.info(`Previously logged in as: ${profile.email}`);
11649
- }
11650
- if (profileName === "default") {
11651
- logger.info("To log in again: playcademy login");
11652
- } else {
11653
- logger.info(`To log in again: playcademy login --profile ${profileName}`);
11654
- }
11746
+ await removeProfile(environment, profileName);
11747
+ logger.success(
11748
+ `Logged out from ${environment} profile "${profileName}" (${profile.email})`
11749
+ );
11655
11750
  logger.newLine();
11656
11751
  } catch (error) {
11657
11752
  logger.newLine();
@@ -11665,18 +11760,24 @@ var logoutCommand = new Command5("logout").description("Log out from Playcademy"
11665
11760
 
11666
11761
  // src/commands/me.ts
11667
11762
  import { Command as Command6 } from "commander";
11668
- var meCommand = new Command6("me").description("Display current user information").action(async () => {
11763
+ var meCommand = new Command6("me").description("Display current user information").option(
11764
+ "--env <environment>",
11765
+ "Environment to check user information from (staging or production)"
11766
+ ).action(async (options) => {
11767
+ const { env } = options;
11669
11768
  try {
11769
+ const environment = ensureEnvironment(env, logger);
11670
11770
  const client = await requireAuthenticatedClient();
11671
11771
  logger.newLine();
11672
11772
  const user = await runStep(
11673
- "Fetching user information",
11773
+ `Fetching user information from ${environment}`,
11674
11774
  client.users.me,
11675
11775
  "User information retrieved"
11676
11776
  );
11677
11777
  logger.newLine();
11678
11778
  logger.data("ID", user.id, 1);
11679
11779
  logger.data("Email", user.email, 1);
11780
+ logger.data("Environment", environment, 1);
11680
11781
  if (user.username) {
11681
11782
  logger.data("Username", user.username, 1);
11682
11783
  }
@@ -11724,7 +11825,8 @@ async function executeDeployment(plan, context2) {
11724
11825
  return { game, backendDeployment };
11725
11826
  }
11726
11827
  async function runDeployment(options) {
11727
- await selectEnvironment(options);
11828
+ const environment = ensureEnvironment(options.env, logger);
11829
+ await selectEnvironment({ env: environment, dryRun: options.dryRun });
11728
11830
  const context2 = await prepareDeploymentContext(options);
11729
11831
  displayCurrentConfiguration(context2);
11730
11832
  const didPrompt = await promptForMissingConfig(context2);
@@ -11789,17 +11891,19 @@ import { Command as Command10 } from "commander";
11789
11891
  // src/commands/games/delete.ts
11790
11892
  import { confirm as confirm5, input as input5, select as select4 } from "@inquirer/prompts";
11791
11893
  import { Command as Command8 } from "commander";
11792
- var deleteCommand = new Command8("delete").alias("rm").alias("remove").description("Delete a game").argument("[slug]", "Game slug to delete").option("-f, --force", "Skip confirmation prompt").action(async (slug, options) => {
11894
+ var deleteCommand = new Command8("delete").alias("rm").alias("remove").description("Delete a game").argument("[slug]", "Game slug to delete").option("-f, --force", "Skip confirmation prompt").option("--env <environment>", "Environment to delete game from (staging or production)").action(async (slug, options) => {
11895
+ const { env } = options;
11793
11896
  try {
11794
- logger.newLine();
11897
+ const environment = ensureEnvironment(env, logger);
11795
11898
  const client = await requireAuthenticatedClient();
11899
+ logger.newLine();
11796
11900
  const games2 = await runStep(
11797
- "Fetching games",
11901
+ `Fetching games from ${environment}`,
11798
11902
  client.games.list,
11799
- (games3) => `Found ${games3.length} game${games3.length === 1 ? "" : "s"}`
11903
+ (games3) => `Found ${games3.length} game${games3.length === 1 ? "" : "s"} in ${environment}`
11800
11904
  );
11801
11905
  if (games2.length === 0) {
11802
- logger.info("No games found");
11906
+ logger.info(`No games found in ${environment}`);
11803
11907
  return;
11804
11908
  }
11805
11909
  logger.newLine();
@@ -11847,11 +11951,11 @@ var deleteCommand = new Command8("delete").alias("rm").alias("remove").descripti
11847
11951
  logger.newLine();
11848
11952
  }
11849
11953
  await runStep(
11850
- "Deleting game",
11954
+ `Deleting game from ${environment}`,
11851
11955
  async () => {
11852
11956
  await client.dev.games.delete(game.id);
11853
11957
  },
11854
- "Game deleted successfully"
11958
+ `Game deleted successfully from ${environment}`
11855
11959
  );
11856
11960
  const deployedGame = await getDeployedGame(getWorkspace());
11857
11961
  if (deployedGame && deployedGame.gameId === game.id) {
@@ -11870,14 +11974,16 @@ var deleteCommand = new Command8("delete").alias("rm").alias("remove").descripti
11870
11974
 
11871
11975
  // src/commands/games/list.ts
11872
11976
  import { Command as Command9 } from "commander";
11873
- var listCommand = new Command9("list").alias("ls").description("List all games").action(async () => {
11977
+ var listCommand = new Command9("list").alias("ls").description("List all games").option("--env <environment>", "Environment to list games from (staging or production)").action(async (options) => {
11978
+ const { env } = options;
11874
11979
  try {
11980
+ const environment = ensureEnvironment(env, logger);
11875
11981
  const client = await requireAuthenticatedClient();
11876
11982
  logger.newLine();
11877
11983
  const games2 = await runStep(
11878
- "Fetching games",
11984
+ `Fetching games from ${environment}`,
11879
11985
  client.games.list,
11880
- (games3) => `Found ${games3.length} game${games3.length === 1 ? "" : "s"}`
11986
+ (games3) => `Found ${games3.length} game${games3.length === 1 ? "" : "s"} in ${environment}`
11881
11987
  );
11882
11988
  if (games2.length === 0) {
11883
11989
  logger.info("No games found");
@@ -11916,22 +12022,30 @@ import { Command as Command13 } from "commander";
11916
12022
 
11917
12023
  // src/commands/dev/apply.ts
11918
12024
  import { Command as Command11 } from "commander";
11919
- var applyCommand = new Command11("apply").description("Apply for developer status").action(async () => {
12025
+ var applyCommand = new Command11("apply").description("Apply for developer status").option(
12026
+ "--env <environment>",
12027
+ "Environment to apply for developer status in (staging or production)"
12028
+ ).action(async (options) => {
12029
+ const { env } = options;
11920
12030
  try {
11921
- logger.newLine();
12031
+ const environment = ensureEnvironment(env, logger);
11922
12032
  const client = await requireAuthenticatedClient();
12033
+ logger.newLine();
11923
12034
  const currentStatus = await runStep(
11924
- "Checking current developer status",
12035
+ `Checking current developer status in ${environment}`,
11925
12036
  client.dev.status.get,
11926
- "Current status retrieved"
12037
+ `Current status retrieved from ${environment}`
11927
12038
  );
12039
+ logger.newLine();
11928
12040
  if (currentStatus === "approved") {
11929
- logger.success("You are already an approved developer!");
12041
+ logger.admonition("note", "Approved!", ["You are already an approved developer!"]);
11930
12042
  logger.newLine();
11931
12043
  return;
11932
12044
  }
11933
12045
  if (currentStatus === "pending") {
11934
- logger.info("Your developer application is pending review");
12046
+ logger.admonition("warning", "Developer Status", [
12047
+ "Your developer application is pending review"
12048
+ ]);
11935
12049
  logger.newLine();
11936
12050
  return;
11937
12051
  }
@@ -11941,14 +12055,15 @@ var applyCommand = new Command11("apply").description("Apply for developer statu
11941
12055
  "Developer application submitted successfully"
11942
12056
  );
11943
12057
  logger.newLine();
11944
- logger.success("Your application has been submitted!");
11945
- logger.info("What happens next:", 1);
11946
- logger.data("1.", "We will review your application shortly", 1);
11947
- logger.data("2.", "Once approved, you can create and deploy games", 1);
11948
- logger.data("3.", "Use `playcademy games create` to create your first game", 1);
12058
+ logger.admonition("tip", "Application Submitted!", [
12059
+ "Your developer application has been submitted.",
12060
+ "",
12061
+ "What happens next:",
12062
+ " \u2022 We will review your application shortly",
12063
+ " \u2022 Once approved, you can create and deploy games"
12064
+ ]);
11949
12065
  logger.newLine();
11950
12066
  } catch (error) {
11951
- logger.newLine();
11952
12067
  logger.error(
11953
12068
  `Failed to apply for developer status: ${error instanceof Error ? error.message : "Unknown error"}`
11954
12069
  );
@@ -11959,31 +12074,37 @@ var applyCommand = new Command11("apply").description("Apply for developer statu
11959
12074
 
11960
12075
  // src/commands/dev/get-status.ts
11961
12076
  import { Command as Command12 } from "commander";
11962
- var getStatusCommand = new Command12("status").description("Check your developer status").action(async () => {
12077
+ var getStatusCommand = new Command12("status").description("Check your developer status").option(
12078
+ "--env <environment>",
12079
+ "Environment to check developer status from (staging or production)"
12080
+ ).action(async (options) => {
12081
+ const { env } = options;
11963
12082
  try {
11964
- logger.newLine();
12083
+ const environment = ensureEnvironment(env, logger);
11965
12084
  const client = await requireAuthenticatedClient();
12085
+ logger.newLine();
11966
12086
  const status = await runStep(
11967
- "Fetching developer status",
12087
+ `Fetching developer status from ${environment}`,
11968
12088
  () => client.dev.status.get(),
11969
- "Developer status retrieved"
12089
+ `Developer status retrieved from ${environment}`
11970
12090
  );
11971
- logger.newLine();
11972
- logger.highlight("Developer Status");
11973
- logger.data("Status", status, 1);
11974
12091
  switch (status) {
11975
12092
  case "none":
11976
12093
  logger.newLine();
11977
- logger.info("You have not applied for developer status yet.");
11978
- logger.info("Run `playcademy dev apply` to apply.");
12094
+ logger.admonition("warning", "Become a Developer", [
12095
+ "Want to create and deploy games?",
12096
+ "Run `playcademy dev apply` to apply for developer status"
12097
+ ]);
11979
12098
  break;
11980
12099
  case "pending":
11981
12100
  logger.newLine();
11982
- logger.info("Your application is pending review.");
12101
+ logger.admonition("warning", "Developer Status", [
12102
+ "Your application is pending review."
12103
+ ]);
11983
12104
  break;
11984
12105
  case "approved":
11985
12106
  logger.newLine();
11986
- logger.success("You are an approved developer!");
12107
+ logger.admonition("note", "Approved!", ["You are an approved developer!"]);
11987
12108
  break;
11988
12109
  default:
11989
12110
  logger.newLine();
@@ -12069,26 +12190,39 @@ import { Command as Command17 } from "commander";
12069
12190
  import { Command as Command14 } from "commander";
12070
12191
  async function listProfilesAction() {
12071
12192
  try {
12072
- const profiles = await listProfiles();
12193
+ const profilesMap = await listProfiles();
12073
12194
  logger.newLine();
12074
- if (profiles.length === 0) {
12195
+ let hasAnyProfiles = false;
12196
+ for (const [, profiles] of profilesMap.entries()) {
12197
+ if (profiles.length > 0) {
12198
+ hasAnyProfiles = true;
12199
+ break;
12200
+ }
12201
+ }
12202
+ if (!hasAnyProfiles) {
12075
12203
  logger.warn("No authentication profiles found");
12076
12204
  logger.newLine();
12077
12205
  logger.admonition("tip", "Getting Started", [
12078
12206
  "Run `playcademy login` to create your first profile"
12079
12207
  ]);
12208
+ logger.newLine();
12080
12209
  return;
12081
12210
  }
12082
- const tableData = [];
12083
- for (const profileName of profiles.values()) {
12084
- const profile = await getProfile(profileName);
12085
- tableData.push({
12086
- Profile: profileName,
12087
- Email: profile?.email ?? ""
12088
- });
12089
- }
12090
- if (tableData.length > 0) {
12091
- logger.table(tableData);
12211
+ for (const [environment, profiles] of profilesMap.entries()) {
12212
+ if (profiles.length === 0) continue;
12213
+ logger.highlight(`${environment.charAt(0).toUpperCase() + environment.slice(1)}:`);
12214
+ const tableData = [];
12215
+ for (const profileName of profiles) {
12216
+ const profile = await getProfile(environment, profileName);
12217
+ tableData.push({
12218
+ Profile: profileName,
12219
+ Email: profile?.email ?? ""
12220
+ });
12221
+ }
12222
+ if (tableData.length > 0) {
12223
+ logger.table(tableData);
12224
+ logger.newLine();
12225
+ }
12092
12226
  }
12093
12227
  } catch (error) {
12094
12228
  logger.error(
@@ -12096,28 +12230,29 @@ async function listProfilesAction() {
12096
12230
  );
12097
12231
  process.exit(1);
12098
12232
  }
12099
- logger.newLine();
12100
12233
  }
12101
12234
  var listCommand2 = new Command14("list").alias("ls").description("List all stored authentication profiles").action(listProfilesAction);
12102
12235
 
12103
12236
  // src/commands/profiles/remove.ts
12237
+ import { bold as bold5 } from "colorette";
12104
12238
  import { Command as Command15 } from "commander";
12105
- var removeCommand = new Command15("remove <name>").alias("rm").description("Remove an authentication profile").action(async (name) => {
12239
+ var removeCommand = new Command15("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) => {
12240
+ const { env } = options;
12241
+ const environment = ensureEnvironment(env, logger);
12106
12242
  try {
12107
12243
  logger.newLine();
12108
- const profiles = await listProfiles();
12109
- if (!profiles.includes(name)) {
12110
- logger.error(`Profile "${name}" not found`);
12244
+ const profilesMap = await listProfiles();
12245
+ const envProfiles = profilesMap.get(environment) || [];
12246
+ if (!envProfiles.includes(name)) {
12247
+ logger.error(`Profile "${name}" not found in ${environment}`);
12111
12248
  logger.newLine();
12112
12249
  process.exit(1);
12113
12250
  }
12114
- await removeProfile(name);
12115
- logger.success(`Profile "${name}" removed successfully`);
12116
- if (name !== "default") {
12117
- logger.info(`To re-authenticate: playcademy login --profile ${name}`);
12118
- } else {
12119
- logger.info("To re-authenticate: playcademy login");
12120
- }
12251
+ await removeProfile(environment, name);
12252
+ logger.admonition("note", "Removed!", [
12253
+ `Profile ${bold5(name)} removed from ${environment}`,
12254
+ environment === "production" ? `To re-authenticate run \`playcademy login --env ${environment}\`` : "To re-authenticate run `playcademy login`"
12255
+ ]);
12121
12256
  logger.newLine();
12122
12257
  } catch (error) {
12123
12258
  logger.newLine();
@@ -12132,10 +12267,16 @@ var removeCommand = new Command15("remove <name>").alias("rm").description("Remo
12132
12267
  // src/commands/profiles/reset.ts
12133
12268
  import { confirm as confirm6 } from "@inquirer/prompts";
12134
12269
  import { Command as Command16 } from "commander";
12135
- var resetCommand = new Command16("reset").description("Remove all authentication profiles (requires confirmation)").alias("clear").action(async () => {
12270
+ var resetCommand = new Command16("reset").description(
12271
+ "Remove all authentication profiles across all environments (requires confirmation)"
12272
+ ).alias("clear").action(async () => {
12136
12273
  try {
12137
- const profiles = await listProfiles();
12138
- if (profiles.length === 0) {
12274
+ const profilesMap = await listProfiles();
12275
+ let totalProfiles = 0;
12276
+ for (const profiles of profilesMap.values()) {
12277
+ totalProfiles += profiles.length;
12278
+ }
12279
+ if (totalProfiles === 0) {
12139
12280
  logger.newLine();
12140
12281
  logger.warn("No authentication profiles found");
12141
12282
  logger.newLine();
@@ -12143,14 +12284,21 @@ var resetCommand = new Command16("reset").description("Remove all authentication
12143
12284
  }
12144
12285
  logger.newLine();
12145
12286
  logger.warn(
12146
- `This will remove ${profiles.length} authentication profile${profiles.length > 1 ? "s" : ""}:`
12287
+ `This will remove ${totalProfiles} authentication profile${totalProfiles > 1 ? "s" : ""} across all environments:`
12147
12288
  );
12148
12289
  logger.newLine();
12149
- for (const profileName of profiles) {
12150
- const profile = await getProfile(profileName);
12151
- logger.data(profileName, profile?.email || "", 1);
12290
+ for (const [environment, profiles] of profilesMap.entries()) {
12291
+ if (profiles.length === 0) continue;
12292
+ logger.highlight(
12293
+ `${environment.charAt(0).toUpperCase() + environment.slice(1)}:`,
12294
+ 1
12295
+ );
12296
+ for (const profileName of profiles) {
12297
+ const profile = await getProfile(environment, profileName);
12298
+ logger.data(profileName, profile?.email || "", 2);
12299
+ }
12300
+ logger.newLine();
12152
12301
  }
12153
- logger.newLine();
12154
12302
  const confirmed = await confirm6({
12155
12303
  message: "Are you sure you want to remove all profiles?",
12156
12304
  default: false
@@ -12160,23 +12308,27 @@ var resetCommand = new Command16("reset").description("Remove all authentication
12160
12308
  return;
12161
12309
  }
12162
12310
  let removedCount = 0;
12163
- for (const profileName of profiles) {
12164
- try {
12165
- await removeProfile(profileName);
12166
- removedCount++;
12167
- } catch (error) {
12168
- logger.newLine();
12169
- logger.error(`Failed to remove profile "${profileName}"`);
12170
- logger.error(error instanceof Error ? error.message : "Unknown error");
12171
- logger.newLine();
12311
+ for (const [environment, profiles] of profilesMap.entries()) {
12312
+ for (const profileName of profiles) {
12313
+ try {
12314
+ await removeProfile(environment, profileName);
12315
+ removedCount++;
12316
+ } catch (error) {
12317
+ logger.newLine();
12318
+ logger.error(
12319
+ `Failed to remove profile "${profileName}" from ${environment}`
12320
+ );
12321
+ logger.error(error instanceof Error ? error.message : "Unknown error");
12322
+ logger.newLine();
12323
+ }
12172
12324
  }
12173
12325
  }
12174
- if (removedCount === profiles.length) {
12326
+ if (removedCount === totalProfiles) {
12175
12327
  logger.success(
12176
12328
  `${removedCount > 1 ? "All " : ""}${removedCount} profile${removedCount > 1 ? "s" : ""} removed successfully`
12177
12329
  );
12178
12330
  } else {
12179
- logger.warn(`Removed ${removedCount} of ${profiles.length} profiles`);
12331
+ logger.warn(`Removed ${removedCount} of ${totalProfiles} profiles`);
12180
12332
  }
12181
12333
  logger.newLine();
12182
12334
  } catch (error) {
@@ -12201,13 +12353,18 @@ import { Command as Command22 } from "commander";
12201
12353
  // src/commands/timeback/cleanup.ts
12202
12354
  import { confirm as confirm7 } from "@inquirer/prompts";
12203
12355
  import { Command as Command18 } from "commander";
12204
- var cleanupCommand = new Command18("cleanup").description("Remove TimeBack integration for your game").action(async () => {
12356
+ var cleanupCommand = new Command18("cleanup").description("Remove TimeBack integration for your game").option(
12357
+ "--env <environment>",
12358
+ "Environment to remove TimeBack integration from (staging or production)"
12359
+ ).action(async (options) => {
12360
+ const { env } = options;
12205
12361
  try {
12206
- logger.newLine();
12362
+ const environment = ensureEnvironment(env, logger);
12207
12363
  const client = await requireAuthenticatedClient();
12364
+ logger.newLine();
12208
12365
  const { game } = await getGameFromConfig(client);
12209
12366
  const integration = await runStep(
12210
- "Checking for TimeBack integration",
12367
+ `Checking for TimeBack integration in ${environment}`,
12211
12368
  () => client.timeback.management.get(game.id),
12212
12369
  "Integration status checked"
12213
12370
  );
@@ -12230,9 +12387,9 @@ var cleanupCommand = new Command18("cleanup").description("Remove TimeBack integ
12230
12387
  }
12231
12388
  logger.newLine();
12232
12389
  await runStep(
12233
- "Removing TimeBack integration",
12390
+ `Removing TimeBack integration from ${environment}`,
12234
12391
  () => client.timeback.management.cleanup(game.id),
12235
- "TimeBack integration removed"
12392
+ `TimeBack integration removed from ${environment}`
12236
12393
  );
12237
12394
  logger.newLine();
12238
12395
  logger.admonition("note", "Note", [
@@ -12251,8 +12408,13 @@ var cleanupCommand = new Command18("cleanup").description("Remove TimeBack integ
12251
12408
  // src/commands/timeback/setup.ts
12252
12409
  import { Command as Command19 } from "commander";
12253
12410
  import dedent from "dedent";
12254
- var setupCommand = new Command19("setup").description("Set up TimeBack integration for your game").option("--dry-run", "Validate configuration without creating resources").option("--verbose, -v", "Output detailed information").action(async (options) => {
12411
+ var setupCommand = new Command19("setup").description("Set up TimeBack integration for your game").option("--dry-run", "Validate configuration without creating resources").option("--verbose, -v", "Output detailed information").option(
12412
+ "--env <environment>",
12413
+ "Environment to set up TimeBack integration in (staging or production)"
12414
+ ).action(async (options) => {
12415
+ const { env } = options;
12255
12416
  try {
12417
+ ensureEnvironment(env, logger);
12256
12418
  logger.newLine();
12257
12419
  const config = await runStep(
12258
12420
  "Loading configuration",
@@ -12354,8 +12516,13 @@ var setupCommand = new Command19("setup").description("Set up TimeBack integrati
12354
12516
  import { confirm as confirm8 } from "@inquirer/prompts";
12355
12517
  import { green as green3, red as red3 } from "colorette";
12356
12518
  import { Command as Command20 } from "commander";
12357
- var updateCommand = new Command20("update").description("Update TimeBack integration configuration for your game").option("--verbose, -v", "Output detailed information").action(async (options) => {
12519
+ var updateCommand = new Command20("update").description("Update TimeBack integration configuration for your game").option("--verbose, -v", "Output detailed information").option(
12520
+ "--env <environment>",
12521
+ "Environment to update TimeBack integration in (staging or production)"
12522
+ ).action(async (options) => {
12523
+ const { env } = options;
12358
12524
  try {
12525
+ ensureEnvironment(env, logger);
12359
12526
  logger.newLine();
12360
12527
  const config = await runStep(
12361
12528
  "Loading configuration",
@@ -12461,13 +12628,18 @@ var updateCommand = new Command20("update").description("Update TimeBack integra
12461
12628
 
12462
12629
  // src/commands/timeback/verify.ts
12463
12630
  import { Command as Command21 } from "commander";
12464
- var verifyCommand = new Command21("verify").description("Verify TimeBack integration for your game").option("--verbose, -v", "Output detailed resource information").action(async (options) => {
12631
+ var verifyCommand = new Command21("verify").description("Verify TimeBack integration for your game").option("--verbose, -v", "Output detailed resource information").option(
12632
+ "--env <environment>",
12633
+ "Environment to verify TimeBack integration in (staging or production)"
12634
+ ).action(async (options) => {
12635
+ const { env } = options;
12465
12636
  try {
12466
- logger.newLine();
12637
+ const environment = ensureEnvironment(env, logger);
12467
12638
  const client = await requireAuthenticatedClient();
12639
+ logger.newLine();
12468
12640
  const { game } = await getGameFromConfig(client);
12469
12641
  const result = await runStep(
12470
- "Verifying TimeBack integration",
12642
+ `Verifying TimeBack integration in ${environment}`,
12471
12643
  () => client.timeback.management.verify(game.id),
12472
12644
  "Verification complete"
12473
12645
  );
@@ -12508,11 +12680,12 @@ timebackCommand.addCommand(cleanupCommand);
12508
12680
  var __dirname = dirname4(fileURLToPath2(import.meta.url));
12509
12681
  var packageJson = await loadPackageJson({ cwd: __dirname, searchUp: true, required: true });
12510
12682
  program.name("playcademy").description("CLI for deploying and managing Playcademy games").version(packageJson?.version || "0.0.0").option("--profile <name>", "Use a specific authentication profile", "default").hook("preAction", (thisCommand) => {
12511
- const options = thisCommand.opts();
12683
+ const options = thisCommand.optsWithGlobals();
12512
12684
  setCliContext({ profile: options.profile, workspace: process.cwd() });
12513
12685
  });
12514
12686
  program.addCommand(initCommand);
12515
12687
  program.addCommand(loginCommand);
12688
+ program.addCommand(logoutCommand);
12516
12689
  program.addCommand(profilesCommand);
12517
12690
  program.addCommand(meCommand);
12518
12691
  program.addCommand(devCommand);
@@ -12550,6 +12723,7 @@ export {
12550
12723
  displayRegisteredRoutes,
12551
12724
  displayResourcesStatus,
12552
12725
  displaySuccessMessage,
12726
+ ensureEnvironment,
12553
12727
  ensureGameExists,
12554
12728
  findConfigPath,
12555
12729
  generateEntryCode,
@@ -12557,6 +12731,7 @@ export {
12557
12731
  generateJsonConfig,
12558
12732
  getApiUrl,
12559
12733
  getAuthPath,
12734
+ getAuthenticatedEnvironments,
12560
12735
  getBackendHash,
12561
12736
  getBackendSize,
12562
12737
  getBaseUrl,
@@ -12588,6 +12763,7 @@ export {
12588
12763
  loadGameStore,
12589
12764
  logger,
12590
12765
  needsBackend,
12766
+ normalizeEnvironment,
12591
12767
  prepareDeploymentContext,
12592
12768
  processConfigVariables,
12593
12769
  promptForApiRoutes,
package/dist/types.d.ts CHANGED
@@ -23,15 +23,28 @@ interface AuthProfile {
23
23
  /** ISO 8601 date string - when the token expires (if applicable) */
24
24
  expiresAt?: string;
25
25
  }
26
+ /**
27
+ * Environment-specific auth profiles
28
+ * Each environment (staging, production) has its own set of profiles
29
+ */
30
+ interface EnvironmentAuthProfiles {
31
+ /** Default authentication profile for this environment */
32
+ default: AuthProfile | null;
33
+ /** Named authentication profiles for this environment */
34
+ profiles: Record<string, AuthProfile>;
35
+ }
26
36
  /**
27
37
  * The complete auth store structure
28
38
  * CLI-specific type for ~/.playcademy/auth.json
39
+ *
40
+ * Profiles are scoped per environment to ensure staging and production
41
+ * credentials are kept separate for security.
29
42
  */
30
43
  interface AuthStore {
31
- /** Default authentication profile used when no profile is specified */
32
- default: AuthProfile | null;
33
- /** Named authentication profiles for different environments/contexts */
34
- profiles: Record<string, AuthProfile>;
44
+ /** Staging environment profiles */
45
+ staging: EnvironmentAuthProfiles;
46
+ /** Production environment profiles */
47
+ production: EnvironmentAuthProfiles;
35
48
  }
36
49
  /**
37
50
  * Login credentials for email/password authentication
@@ -486,7 +499,7 @@ interface DeploymentContext {
486
499
  projectPath: string;
487
500
  existingGame?: Game;
488
501
  deployedGameInfo?: DeployedGameInfo;
489
- isNewDeployment: boolean;
502
+ isGameDeployed: boolean;
490
503
  buildHash?: string;
491
504
  buildSize?: number;
492
505
  previousBuildHash?: string;
@@ -625,4 +638,4 @@ interface PreviewResponse {
625
638
  qrCode?: string;
626
639
  }
627
640
 
628
- export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, AuthProfile, AuthStore, BackendDeploymentWithHash, CallbackServerResult, CreateApiKeyResponse, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentPlan, DeploymentResult, GameStore, IntegrationsConfig, LoginCredentials, LoginResponse, PlaycademyConfig, PreviewOptions, PreviewResponse, SignInResponse, SsoCallbackData, TimebackIntegrationConfig, TokenType, UpdateExistingGameOptions };
641
+ export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, AuthProfile, AuthStore, BackendDeploymentWithHash, CallbackServerResult, CreateApiKeyResponse, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentPlan, DeploymentResult, EnvironmentAuthProfiles, GameStore, IntegrationsConfig, LoginCredentials, LoginResponse, PlaycademyConfig, PreviewOptions, PreviewResponse, SignInResponse, SsoCallbackData, TimebackIntegrationConfig, TokenType, UpdateExistingGameOptions };
package/dist/utils.js CHANGED
@@ -4548,15 +4548,7 @@ function createDevNamespace(client) {
4548
4548
  throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
4549
4549
  }
4550
4550
  }
4551
- const baseUrl = (() => {
4552
- const anyClient = client;
4553
- try {
4554
- return typeof anyClient.getBaseUrl === "function" ? anyClient.getBaseUrl() : "/api";
4555
- } catch {
4556
- return "/api";
4557
- }
4558
- })();
4559
- const finalizeUrl = baseUrl.replace(/\/$/, "") + "/games/uploads/finalize/";
4551
+ const finalizeUrl = `${client.baseUrl}/games/uploads/finalize/`;
4560
4552
  const authToken = client.getToken();
4561
4553
  const tokenType = client.getTokenType();
4562
4554
  const headers = {
@@ -6034,13 +6026,13 @@ var init_achievements = () => {
6034
6026
  function createTimebackNamespace(client) {
6035
6027
  return {
6036
6028
  recordProgress: (progressData) => {
6037
- return client["request"]("/api/integrations/timeback/progress", "POST", { progressData }, void 0, true);
6029
+ return client["requestGameBackend"]("/api/integrations/timeback/progress", "POST", { progressData });
6038
6030
  },
6039
6031
  recordSessionEnd: (sessionData) => {
6040
- return client["request"]("/api/integrations/timeback/session-end", "POST", { sessionData }, void 0, true);
6032
+ return client["requestGameBackend"]("/api/integrations/timeback/session-end", "POST", { sessionData });
6041
6033
  },
6042
6034
  awardXP: (xpAmount, metadata) => {
6043
- return client["request"]("/api/integrations/timeback/award-xp", "POST", { xpAmount, metadata }, void 0, true);
6035
+ return client["requestGameBackend"]("/api/integrations/timeback/award-xp", "POST", { xpAmount, metadata });
6044
6036
  },
6045
6037
  management: {
6046
6038
  setup: (request2) => {
@@ -6349,7 +6341,7 @@ var init_client = __esm2(() => {
6349
6341
  authContext;
6350
6342
  initPayload;
6351
6343
  constructor(config) {
6352
- this.baseUrl = config?.baseUrl || "";
6344
+ this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
6353
6345
  this.gameUrl = config?.gameUrl;
6354
6346
  this.gameId = config?.gameId;
6355
6347
  this.config = config || {};
@@ -6365,8 +6357,9 @@ var init_client = __esm2(() => {
6365
6357
  return `${effectiveBaseUrl}/api`;
6366
6358
  }
6367
6359
  getGameBackendUrl() {
6368
- if (!this.gameUrl)
6369
- return this.getBaseUrl();
6360
+ if (!this.gameUrl) {
6361
+ throw new PlaycademyError("Game backend URL not configured. gameUrl must be set to use game backend features.");
6362
+ }
6370
6363
  const isRelative = this.gameUrl.startsWith("/");
6371
6364
  const isBrowser2 = typeof window !== "undefined";
6372
6365
  const effectiveGameUrl = isRelative && isBrowser2 ? `${window.location.origin}${this.gameUrl}` : this.gameUrl;
@@ -6403,17 +6396,29 @@ var init_client = __esm2(() => {
6403
6396
  listener(payload);
6404
6397
  });
6405
6398
  }
6406
- async request(path, method, body, headers, useGameBackend = false) {
6399
+ async request(path, method, body, headers) {
6400
+ const effectiveHeaders = {
6401
+ ...headers,
6402
+ ...this.authStrategy.getHeaders()
6403
+ };
6404
+ return request({
6405
+ path,
6406
+ method,
6407
+ body,
6408
+ baseUrl: this.baseUrl,
6409
+ extraHeaders: effectiveHeaders
6410
+ });
6411
+ }
6412
+ async requestGameBackend(path, method, body, headers) {
6407
6413
  const effectiveHeaders = {
6408
6414
  ...headers,
6409
6415
  ...this.authStrategy.getHeaders()
6410
6416
  };
6411
- const targetBaseUrl = useGameBackend ? this.getGameBackendUrl() : this.baseUrl;
6412
6417
  return request({
6413
6418
  path,
6414
6419
  method,
6415
6420
  body,
6416
- baseUrl: targetBaseUrl,
6421
+ baseUrl: this.getGameBackendUrl(),
6417
6422
  extraHeaders: effectiveHeaders
6418
6423
  });
6419
6424
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playcademy",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "module": "./dist/index.js",
6
6
  "exports": {