playcademy 0.6.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
@@ -64,6 +64,7 @@ var init_package_json = __esm({
64
64
  var file_loader_exports = {};
65
65
  __export(file_loader_exports, {
66
66
  findFile: () => findFile,
67
+ getCurrentDirectoryName: () => getCurrentDirectoryName,
67
68
  getFileExtension: () => getFileExtension,
68
69
  getPackageField: () => getPackageField,
69
70
  getPackageName: () => getPackageName,
@@ -202,6 +203,11 @@ async function getPackageNameVersion(options) {
202
203
  }
203
204
  return getPackageNameVersionFromData(pkg);
204
205
  }
206
+ function getCurrentDirectoryName(fallback = "unknown-directory") {
207
+ const cwd = process.cwd();
208
+ const dirName = cwd.split("/").pop();
209
+ return dirName || fallback;
210
+ }
205
211
  function getFileExtension(path) {
206
212
  return path.split(".").pop()?.toLowerCase();
207
213
  }
@@ -2507,6 +2513,16 @@ var CONFIG_FILE_NAMES = [
2507
2513
  "playcademy.config.mjs"
2508
2514
  ];
2509
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
+
2510
2526
  // src/lib/auth/storage.ts
2511
2527
  import { access, mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
2512
2528
  import { homedir } from "node:os";
@@ -2515,7 +2531,7 @@ import { join } from "node:path";
2515
2531
  // src/lib/core/context.ts
2516
2532
  var context = {};
2517
2533
  function setCliContext(ctx) {
2518
- context = ctx;
2534
+ context = { ...context, ...ctx };
2519
2535
  }
2520
2536
  function getCliContext() {
2521
2537
  return context;
@@ -2525,26 +2541,47 @@ function getWorkspace() {
2525
2541
  }
2526
2542
 
2527
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
+ }
2528
2570
  function getEnvironment() {
2529
2571
  const context2 = getCliContext();
2530
2572
  if (context2.environment) {
2531
2573
  return context2.environment;
2532
2574
  }
2533
2575
  const env = process.env.PLAYCADEMY_ENV?.toLowerCase();
2534
- if (env === "staging") {
2535
- return "staging";
2576
+ if (env === "staging" || env === "production") {
2577
+ return env;
2536
2578
  }
2537
- return "production";
2579
+ return "staging";
2538
2580
  }
2539
2581
  function getBaseUrl() {
2540
2582
  const explicitUrl = process.env.PLAYCADEMY_BASE_URL;
2541
- if (explicitUrl) {
2542
- return explicitUrl;
2543
- }
2544
- if (getEnvironment() === "staging") {
2545
- return "https://hub.dev.playcademy.net";
2546
- }
2547
- return "https://hub.playcademy.com";
2583
+ if (explicitUrl) return explicitUrl;
2584
+ return PLAYCADEMY_BASE_URLS[getEnvironment()];
2548
2585
  }
2549
2586
  function getApiUrl(path) {
2550
2587
  const baseUrl = getBaseUrl();
@@ -2567,6 +2604,12 @@ async function ensureAuthDir() {
2567
2604
  const authDir = join(homedir(), ".playcademy");
2568
2605
  await mkdir(authDir, { recursive: true });
2569
2606
  }
2607
+ function createEmptyEnvironmentProfiles() {
2608
+ return {
2609
+ default: null,
2610
+ profiles: {}
2611
+ };
2612
+ }
2570
2613
  async function loadAuthStore() {
2571
2614
  try {
2572
2615
  await ensureAuthDir();
@@ -2577,8 +2620,8 @@ async function loadAuthStore() {
2577
2620
  } catch {
2578
2621
  }
2579
2622
  return {
2580
- default: null,
2581
- profiles: {}
2623
+ staging: createEmptyEnvironmentProfiles(),
2624
+ production: createEmptyEnvironmentProfiles()
2582
2625
  };
2583
2626
  }
2584
2627
  async function saveAuthStore(store) {
@@ -2595,45 +2638,79 @@ async function getCurrentProfile() {
2595
2638
  };
2596
2639
  }
2597
2640
  const store = await loadAuthStore();
2641
+ const environment = getEnvironment();
2598
2642
  const profileName = getProfileName();
2643
+ const envProfiles = store[environment];
2644
+ if (!envProfiles) {
2645
+ return null;
2646
+ }
2599
2647
  if (profileName === "default") {
2600
- return store.default;
2648
+ return envProfiles.default;
2601
2649
  }
2602
- return store.profiles[profileName] || null;
2650
+ return envProfiles.profiles[profileName] || null;
2603
2651
  }
2604
- async function saveProfile(profileName, profile) {
2652
+ async function saveProfile(environment, profileName, profile) {
2605
2653
  const store = await loadAuthStore();
2654
+ if (!store[environment]) {
2655
+ store[environment] = createEmptyEnvironmentProfiles();
2656
+ }
2657
+ const envProfiles = store[environment];
2606
2658
  if (profileName === "default") {
2607
- store.default = profile;
2659
+ envProfiles.default = profile;
2608
2660
  } else {
2609
- store.profiles[profileName] = profile;
2661
+ envProfiles.profiles[profileName] = profile;
2610
2662
  }
2611
2663
  await saveAuthStore(store);
2612
2664
  }
2613
- async function removeProfile(profileName) {
2665
+ async function removeProfile(environment, profileName) {
2614
2666
  const store = await loadAuthStore();
2667
+ if (!store[environment]) {
2668
+ return;
2669
+ }
2670
+ const envProfiles = store[environment];
2615
2671
  if (profileName === "default") {
2616
- store.default = null;
2672
+ envProfiles.default = null;
2617
2673
  } else {
2618
- delete store.profiles[profileName];
2674
+ delete envProfiles.profiles[profileName];
2619
2675
  }
2620
2676
  await saveAuthStore(store);
2621
2677
  }
2622
- async function getProfile(profileName) {
2678
+ async function getProfile(environment, profileName) {
2623
2679
  const store = await loadAuthStore();
2680
+ const envProfiles = store[environment];
2681
+ if (!envProfiles) {
2682
+ return null;
2683
+ }
2624
2684
  if (profileName === "default") {
2625
- return store.default;
2685
+ return envProfiles.default;
2626
2686
  }
2627
- return store.profiles[profileName] || null;
2687
+ return envProfiles.profiles[profileName] || null;
2628
2688
  }
2629
2689
  async function listProfiles() {
2630
2690
  const store = await loadAuthStore();
2631
- const profiles = [];
2632
- if (store.default) {
2633
- profiles.push("default");
2634
- }
2635
- profiles.push(...Object.keys(store.profiles));
2636
- 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;
2637
2714
  }
2638
2715
 
2639
2716
  // src/lib/auth/http-server/server.ts
@@ -7254,15 +7331,7 @@ function createDevNamespace(client) {
7254
7331
  throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
7255
7332
  }
7256
7333
  }
7257
- const baseUrl = (() => {
7258
- const anyClient = client;
7259
- try {
7260
- return typeof anyClient.getBaseUrl === "function" ? anyClient.getBaseUrl() : "/api";
7261
- } catch {
7262
- return "/api";
7263
- }
7264
- })();
7265
- const finalizeUrl = baseUrl.replace(/\/$/, "") + "/games/uploads/finalize/";
7334
+ const finalizeUrl = `${client.baseUrl}/games/uploads/finalize/`;
7266
7335
  const authToken = client.getToken();
7267
7336
  const tokenType = client.getTokenType();
7268
7337
  const headers = {
@@ -8740,13 +8809,13 @@ var init_achievements = () => {
8740
8809
  function createTimebackNamespace(client) {
8741
8810
  return {
8742
8811
  recordProgress: (progressData) => {
8743
- return client["request"]("/api/integrations/timeback/progress", "POST", { progressData }, void 0, true);
8812
+ return client["requestGameBackend"]("/api/integrations/timeback/progress", "POST", { progressData });
8744
8813
  },
8745
8814
  recordSessionEnd: (sessionData) => {
8746
- return client["request"]("/api/integrations/timeback/session-end", "POST", { sessionData }, void 0, true);
8815
+ return client["requestGameBackend"]("/api/integrations/timeback/session-end", "POST", { sessionData });
8747
8816
  },
8748
8817
  awardXP: (xpAmount, metadata) => {
8749
- 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 });
8750
8819
  },
8751
8820
  management: {
8752
8821
  setup: (request2) => {
@@ -9055,7 +9124,7 @@ var init_client = __esm2(() => {
9055
9124
  authContext;
9056
9125
  initPayload;
9057
9126
  constructor(config) {
9058
- this.baseUrl = config?.baseUrl || "";
9127
+ this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
9059
9128
  this.gameUrl = config?.gameUrl;
9060
9129
  this.gameId = config?.gameId;
9061
9130
  this.config = config || {};
@@ -9071,8 +9140,9 @@ var init_client = __esm2(() => {
9071
9140
  return `${effectiveBaseUrl}/api`;
9072
9141
  }
9073
9142
  getGameBackendUrl() {
9074
- if (!this.gameUrl)
9075
- 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
+ }
9076
9146
  const isRelative = this.gameUrl.startsWith("/");
9077
9147
  const isBrowser2 = typeof window !== "undefined";
9078
9148
  const effectiveGameUrl = isRelative && isBrowser2 ? `${window.location.origin}${this.gameUrl}` : this.gameUrl;
@@ -9109,17 +9179,29 @@ var init_client = __esm2(() => {
9109
9179
  listener(payload);
9110
9180
  });
9111
9181
  }
9112
- async request(path, method, body, headers, useGameBackend = false) {
9182
+ async request(path, method, body, headers) {
9113
9183
  const effectiveHeaders = {
9114
9184
  ...headers,
9115
9185
  ...this.authStrategy.getHeaders()
9116
9186
  };
9117
- const targetBaseUrl = useGameBackend ? this.getGameBackendUrl() : this.baseUrl;
9118
9187
  return request({
9119
9188
  path,
9120
9189
  method,
9121
9190
  body,
9122
- 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(),
9123
9205
  extraHeaders: effectiveHeaders
9124
9206
  });
9125
9207
  }
@@ -9180,13 +9262,184 @@ var init_client = __esm2(() => {
9180
9262
  init_client();
9181
9263
  init_messaging();
9182
9264
 
9265
+ // src/lib/core/logger.ts
9266
+ import {
9267
+ blue,
9268
+ bold,
9269
+ cyan,
9270
+ dim,
9271
+ gray,
9272
+ green,
9273
+ greenBright,
9274
+ red,
9275
+ yellow,
9276
+ yellowBright
9277
+ } from "colorette";
9278
+ import { colorize } from "json-colorizer";
9279
+ function customTransform(text2) {
9280
+ const highlightCode = (text3) => text3.replace(/`([^`]+)`/g, (_, code) => greenBright(code));
9281
+ return highlightCode(text2);
9282
+ }
9283
+ var logger = {
9284
+ table: (data) => {
9285
+ console.table(data);
9286
+ },
9287
+ /**
9288
+ * Info message - general information
9289
+ */
9290
+ info: (message, indent = 0) => {
9291
+ const spaces = " ".repeat(indent);
9292
+ console.log(`${spaces}${blue("\u2139")} ${bold(customTransform(message))}`);
9293
+ },
9294
+ /**
9295
+ * Admonition - highlighted note/tip/warning box (Docusaurus-style)
9296
+ */
9297
+ admonition: (type, title, lines, indent = 0) => {
9298
+ const spaces = " ".repeat(indent);
9299
+ const configs = {
9300
+ note: { color: green },
9301
+ tip: { color: cyan },
9302
+ info: { color: blue },
9303
+ warning: { color: yellow }
9304
+ };
9305
+ const { color: color2 } = configs[type];
9306
+ console.log(`${spaces}${color2("\u250C\u2500")} ${bold(color2(title.toUpperCase()))}`);
9307
+ if (lines && lines.length > 0) {
9308
+ lines.forEach((line2) => {
9309
+ console.log(`${spaces}${color2("\u2502")} ${customTransform(line2)}`);
9310
+ });
9311
+ }
9312
+ console.log(`${spaces}${color2("\u2514\u2500")}`);
9313
+ },
9314
+ /**
9315
+ * Dim message - less important information
9316
+ */
9317
+ dim: (message, indent = 0) => {
9318
+ const spaces = " ".repeat(indent);
9319
+ console.log(`${spaces}${dim(customTransform(message))}`);
9320
+ },
9321
+ /**
9322
+ * Success message - operation completed successfully
9323
+ */
9324
+ success: (message, indent = 0) => {
9325
+ const spaces = " ".repeat(indent);
9326
+ console.log(`${spaces}${green("\u2714")} ${bold(customTransform(message))}`);
9327
+ },
9328
+ remark: (message, indent = 0) => {
9329
+ const spaces = " ".repeat(indent);
9330
+ console.log(`${spaces}${bold(yellowBright("\u2726"))} ${bold(customTransform(message))}`);
9331
+ },
9332
+ /**
9333
+ * Error message - operation failed
9334
+ */
9335
+ error: (message, indent = 0) => {
9336
+ const spaces = " ".repeat(indent);
9337
+ console.error(`${spaces}${red("\u2716")} ${customTransform(message)}`);
9338
+ },
9339
+ bold: (message, indent = 0) => {
9340
+ const spaces = " ".repeat(indent);
9341
+ console.log(`${spaces}${bold(customTransform(message))}`);
9342
+ },
9343
+ /**
9344
+ * Warning message - something to be aware of
9345
+ */
9346
+ warn: (message, indent = 0) => {
9347
+ const spaces = " ".repeat(indent);
9348
+ console.warn(`${spaces}${yellow("\u26A0")} ${bold(customTransform(message))}`);
9349
+ },
9350
+ /**
9351
+ * Debug message - only shown when DEBUG env var is set
9352
+ */
9353
+ debug: (message, indent = 0) => {
9354
+ const spaces = " ".repeat(indent);
9355
+ if (process.env.DEBUG) {
9356
+ console.log(gray("[DEBUG]"), `${spaces}${message}`);
9357
+ }
9358
+ },
9359
+ /**
9360
+ * Step message - shows progress through a process
9361
+ */
9362
+ step: (step, total, message, indent = 0) => {
9363
+ const spaces = " ".repeat(indent);
9364
+ console.log(spaces + cyan(`[${step}/${total}]`), customTransform(message));
9365
+ },
9366
+ /**
9367
+ * Highlighted message - draws attention
9368
+ */
9369
+ highlight: (message, indent = 0) => {
9370
+ const spaces = " ".repeat(indent);
9371
+ console.log(bold(`${spaces}${cyan(customTransform(message))}`));
9372
+ },
9373
+ /**
9374
+ * Aside message - for side information
9375
+ */
9376
+ aside: (message, indent = 0) => {
9377
+ const spaces = " ".repeat(indent);
9378
+ console.log(`${spaces}${bold(customTransform(message))}`);
9379
+ },
9380
+ /**
9381
+ * Data display - for structured data output
9382
+ */
9383
+ data: (label, value, indent = 0) => {
9384
+ const spaces = " ".repeat(indent);
9385
+ if (value !== void 0) {
9386
+ console.log(`${spaces}${dim(label + ":")} ${bold(value)}`);
9387
+ } else {
9388
+ console.log(`${spaces}${dim(label)}`);
9389
+ }
9390
+ },
9391
+ /**
9392
+ * JSON output - pretty-printed JSON
9393
+ */
9394
+ json: (data, indent = 0) => {
9395
+ const spaces = " ".repeat(indent);
9396
+ const jsonString = colorize(JSON.stringify(data, null, 2));
9397
+ jsonString.split("\n").forEach((line2) => {
9398
+ console.log(`${spaces}${line2}`);
9399
+ });
9400
+ },
9401
+ /**
9402
+ * New line
9403
+ */
9404
+ newLine: () => {
9405
+ console.log();
9406
+ },
9407
+ /**
9408
+ * Raw output - no formatting, useful for ASCII art or pre-formatted text
9409
+ */
9410
+ raw: (text2, indent = 0) => {
9411
+ const spaces = " ".repeat(indent);
9412
+ console.log(`${spaces}${text2}`);
9413
+ },
9414
+ /**
9415
+ * Display a configuration error with helpful suggestions
9416
+ */
9417
+ configError: (error, indent = 0) => {
9418
+ const spaces = " ".repeat(indent);
9419
+ const isConfigError = error && typeof error === "object" && "name" in error && error.name === "ConfigError";
9420
+ if (isConfigError && "message" in error && "field" in error && "suggestion" in error) {
9421
+ const configErr = error;
9422
+ console.error(`${spaces}${red("\u2716")} ${bold(configErr.message)}`);
9423
+ if (configErr.field) {
9424
+ console.error(`${spaces} ${dim("Field:")} ${configErr.field}`);
9425
+ }
9426
+ if (configErr.suggestion) {
9427
+ console.error(`${spaces} ${dim("Fix:")} ${configErr.suggestion}`);
9428
+ }
9429
+ } else if (error instanceof Error) {
9430
+ console.error(`${spaces}${red("\u2716")} ${bold(error.message)}`);
9431
+ } else {
9432
+ console.error(`${spaces}${red("\u2716")} ${bold(String(error))}`);
9433
+ }
9434
+ }
9435
+ };
9436
+
9183
9437
  // src/lib/core/client.ts
9184
9438
  async function createClient() {
9185
9439
  const profile = await getCurrentProfile();
9186
9440
  const baseUrl = getBaseUrl();
9187
- const apiBaseUrl = `${baseUrl}/api`;
9188
9441
  const client = new PlaycademyClient({
9189
- baseUrl: apiBaseUrl,
9442
+ baseUrl,
9190
9443
  token: profile?.token
9191
9444
  });
9192
9445
  if (profile?.token && profile?.tokenType) {
@@ -9196,8 +9449,15 @@ async function createClient() {
9196
9449
  }
9197
9450
  async function requireAuthenticatedClient() {
9198
9451
  const profile = await getCurrentProfile();
9452
+ const environment = getEnvironment();
9199
9453
  if (!profile) {
9200
- throw new Error('Not authenticated. Please run "playcademy login" first.');
9454
+ logger.newLine();
9455
+ logger.admonition("warning", "Login Required", [
9456
+ `You are not logged into ${environment}.`,
9457
+ environment === "production" ? `Run \`playcademy login --env ${environment}\` to authenticate.` : "Run `playcademy login` to authenticate."
9458
+ ]);
9459
+ logger.newLine();
9460
+ process.exit(1);
9201
9461
  }
9202
9462
  const client = await createClient();
9203
9463
  return client;
@@ -9236,10 +9496,10 @@ var cursor = {
9236
9496
  function color(text2, colorCode) {
9237
9497
  return `${colorCode}${text2}${styles.reset}`;
9238
9498
  }
9239
- function dim(text2) {
9499
+ function dim2(text2) {
9240
9500
  return color(text2, styles.dim);
9241
9501
  }
9242
- function bold(text2) {
9502
+ function bold2(text2) {
9243
9503
  return color(text2, styles.bold);
9244
9504
  }
9245
9505
  var isInteractive = typeof process !== "undefined" && process.stdout?.isTTY && !process.env.CI && process.env.TERM !== "dumb";
@@ -9369,7 +9629,7 @@ var Spinner = class {
9369
9629
  // ../utils/src/log.ts
9370
9630
  function formatDuration(ms) {
9371
9631
  const duration = ms < 1e3 ? `${Math.round(ms)}ms` : `${(ms / 1e3).toFixed(2)}s`;
9372
- return bold(dim(`[${duration}]`));
9632
+ return bold2(dim2(`[${duration}]`));
9373
9633
  }
9374
9634
  async function runStep(text2, action, successText, options) {
9375
9635
  const effectiveAction = action ?? (async () => void 0);
@@ -9393,7 +9653,7 @@ async function runStep(text2, action, successText, options) {
9393
9653
  } else {
9394
9654
  finalSuccessText = successText ?? text2;
9395
9655
  }
9396
- finalSuccessText = bold(finalSuccessText);
9656
+ finalSuccessText = bold2(finalSuccessText);
9397
9657
  if (hasAction) {
9398
9658
  const durationText = formatDuration(duration);
9399
9659
  finalSuccessText = `${finalSuccessText} ${durationText}`;
@@ -9478,178 +9738,6 @@ async function getGameFromConfig(client) {
9478
9738
  return { game, config };
9479
9739
  }
9480
9740
 
9481
- // src/lib/core/logger.ts
9482
- import {
9483
- blue,
9484
- bold as bold2,
9485
- cyan,
9486
- dim as dim2,
9487
- gray,
9488
- green,
9489
- greenBright,
9490
- red,
9491
- yellow,
9492
- yellowBright
9493
- } from "colorette";
9494
- import { colorize } from "json-colorizer";
9495
- function customTransform(text2) {
9496
- const highlightCode = (text3) => text3.replace(/`([^`]+)`/g, (_, code) => greenBright(code));
9497
- return highlightCode(text2);
9498
- }
9499
- var logger = {
9500
- table: (data) => {
9501
- console.table(data);
9502
- },
9503
- /**
9504
- * Info message - general information
9505
- */
9506
- info: (message, indent = 0) => {
9507
- const spaces = " ".repeat(indent);
9508
- console.log(`${spaces}${blue("\u2139")} ${bold2(customTransform(message))}`);
9509
- },
9510
- /**
9511
- * Admonition - highlighted note/tip/warning box (Docusaurus-style)
9512
- */
9513
- admonition: (type, title, lines, indent = 0) => {
9514
- const spaces = " ".repeat(indent);
9515
- const configs = {
9516
- note: { color: green },
9517
- tip: { color: cyan },
9518
- info: { color: blue },
9519
- warning: { color: yellow }
9520
- };
9521
- const { color: color2 } = configs[type];
9522
- console.log(`${spaces}${color2("\u250C\u2500")} ${bold2(color2(title.toUpperCase()))}`);
9523
- if (lines && lines.length > 0) {
9524
- lines.forEach((line2) => {
9525
- console.log(`${spaces}${color2("\u2502")} ${customTransform(line2)}`);
9526
- });
9527
- }
9528
- console.log(`${spaces}${color2("\u2514\u2500")}`);
9529
- },
9530
- /**
9531
- * Dim message - less important information
9532
- */
9533
- dim: (message, indent = 0) => {
9534
- const spaces = " ".repeat(indent);
9535
- console.log(`${spaces}${dim2(customTransform(message))}`);
9536
- },
9537
- /**
9538
- * Success message - operation completed successfully
9539
- */
9540
- success: (message, indent = 0) => {
9541
- const spaces = " ".repeat(indent);
9542
- console.log(`${spaces}${green("\u2714")} ${bold2(customTransform(message))}`);
9543
- },
9544
- remark: (message, indent = 0) => {
9545
- const spaces = " ".repeat(indent);
9546
- console.log(`${spaces}${bold2(yellowBright("\u2726"))} ${bold2(customTransform(message))}`);
9547
- },
9548
- /**
9549
- * Error message - operation failed
9550
- */
9551
- error: (message, indent = 0) => {
9552
- const spaces = " ".repeat(indent);
9553
- console.error(`${spaces}${red("\u2716")} ${customTransform(message)}`);
9554
- },
9555
- bold: (message, indent = 0) => {
9556
- const spaces = " ".repeat(indent);
9557
- console.log(`${spaces}${bold2(customTransform(message))}`);
9558
- },
9559
- /**
9560
- * Warning message - something to be aware of
9561
- */
9562
- warn: (message, indent = 0) => {
9563
- const spaces = " ".repeat(indent);
9564
- console.warn(`${spaces}${yellow("\u26A0")} ${bold2(customTransform(message))}`);
9565
- },
9566
- /**
9567
- * Debug message - only shown when DEBUG env var is set
9568
- */
9569
- debug: (message, indent = 0) => {
9570
- const spaces = " ".repeat(indent);
9571
- if (process.env.DEBUG) {
9572
- console.log(gray("[DEBUG]"), `${spaces}${message}`);
9573
- }
9574
- },
9575
- /**
9576
- * Step message - shows progress through a process
9577
- */
9578
- step: (step, total, message, indent = 0) => {
9579
- const spaces = " ".repeat(indent);
9580
- console.log(spaces + cyan(`[${step}/${total}]`), customTransform(message));
9581
- },
9582
- /**
9583
- * Highlighted message - draws attention
9584
- */
9585
- highlight: (message, indent = 0) => {
9586
- const spaces = " ".repeat(indent);
9587
- console.log(bold2(`${spaces}${cyan(customTransform(message))}`));
9588
- },
9589
- /**
9590
- * Aside message - for side information
9591
- */
9592
- aside: (message, indent = 0) => {
9593
- const spaces = " ".repeat(indent);
9594
- console.log(`${spaces}${bold2(customTransform(message))}`);
9595
- },
9596
- /**
9597
- * Data display - for structured data output
9598
- */
9599
- data: (label, value, indent = 0) => {
9600
- const spaces = " ".repeat(indent);
9601
- if (value !== void 0) {
9602
- console.log(`${spaces}${dim2(label + ":")} ${bold2(value)}`);
9603
- } else {
9604
- console.log(`${spaces}${dim2(label)}`);
9605
- }
9606
- },
9607
- /**
9608
- * JSON output - pretty-printed JSON
9609
- */
9610
- json: (data, indent = 0) => {
9611
- const spaces = " ".repeat(indent);
9612
- const jsonString = colorize(JSON.stringify(data, null, 2));
9613
- jsonString.split("\n").forEach((line2) => {
9614
- console.log(`${spaces}${line2}`);
9615
- });
9616
- },
9617
- /**
9618
- * New line
9619
- */
9620
- newLine: () => {
9621
- console.log();
9622
- },
9623
- /**
9624
- * Raw output - no formatting, useful for ASCII art or pre-formatted text
9625
- */
9626
- raw: (text2, indent = 0) => {
9627
- const spaces = " ".repeat(indent);
9628
- console.log(`${spaces}${text2}`);
9629
- },
9630
- /**
9631
- * Display a configuration error with helpful suggestions
9632
- */
9633
- configError: (error, indent = 0) => {
9634
- const spaces = " ".repeat(indent);
9635
- const isConfigError = error && typeof error === "object" && "name" in error && error.name === "ConfigError";
9636
- if (isConfigError && "message" in error && "field" in error && "suggestion" in error) {
9637
- const configErr = error;
9638
- console.error(`${spaces}${red("\u2716")} ${bold2(configErr.message)}`);
9639
- if (configErr.field) {
9640
- console.error(`${spaces} ${dim2("Field:")} ${configErr.field}`);
9641
- }
9642
- if (configErr.suggestion) {
9643
- console.error(`${spaces} ${dim2("Fix:")} ${configErr.suggestion}`);
9644
- }
9645
- } else if (error instanceof Error) {
9646
- console.error(`${spaces}${red("\u2716")} ${bold2(error.message)}`);
9647
- } else {
9648
- console.error(`${spaces}${red("\u2716")} ${bold2(String(error))}`);
9649
- }
9650
- }
9651
- };
9652
-
9653
9741
  // src/lib/deploy/backend.ts
9654
9742
  import { createHash as createHash2 } from "node:crypto";
9655
9743
  import { existsSync as existsSync3 } from "node:fs";
@@ -10226,16 +10314,25 @@ async function validateDeployConfig(config) {
10226
10314
  // src/lib/deploy/interaction.ts
10227
10315
  async function selectEnvironment(options) {
10228
10316
  let environment = options.env;
10229
- const shouldPrompt = !environment && !options.dryRun && !process.env.PLAYCADEMY_ENV && !process.env.PLAYCADEMY_BASE_URL;
10230
- 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) {
10231
10328
  logger.newLine();
10232
10329
  environment = await select({
10233
10330
  message: "Select deployment environment:",
10234
10331
  choices: [
10235
- { value: "production", name: "Production (hub.playcademy.com)" },
10236
- { 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)" }
10237
10334
  ],
10238
- default: "production"
10335
+ default: "staging"
10239
10336
  });
10240
10337
  }
10241
10338
  if (environment) {
@@ -10383,10 +10480,12 @@ function displayCurrentConfiguration(context2) {
10383
10480
  } else if (config.externalUrl) {
10384
10481
  logger.data("URL", config.externalUrl, 1);
10385
10482
  }
10386
- if (existingGame) {
10483
+ if (context2.isGameDeployed) {
10387
10484
  logger.data("Status", "Deployed", 1);
10388
- } else {
10485
+ } else if (existingGame) {
10389
10486
  logger.data("Status", "Not deployed", 1);
10487
+ } else {
10488
+ logger.data("Status", "New deployment", 1);
10390
10489
  }
10391
10490
  logger.newLine();
10392
10491
  }
@@ -10415,14 +10514,15 @@ async function reportNoChanges(context2) {
10415
10514
  function reportDeploymentSuccess(result, context2) {
10416
10515
  const { game, backendDeployment } = result;
10417
10516
  const { client, previousBackendHash } = context2;
10418
- const action = context2.isNewDeployment ? "deployed" : "updated";
10517
+ const action = context2.isGameDeployed ? "updated" : "deployed";
10419
10518
  logger.newLine();
10420
10519
  logger.success(`${game.displayName} ${action} successfully!`);
10421
10520
  logger.newLine();
10422
10521
  const baseUrl = getWebBaseUrl(client.getBaseUrl());
10423
10522
  logger.data("Game URL", underline(blueBright(`${baseUrl}/g/${game.slug}`)), 1);
10424
10523
  if (backendDeployment || previousBackendHash) {
10425
- const backendUrl = `https://${game.slug}.playcademy.gg/api`;
10524
+ const gameDomain = GAME_BACKEND_DOMAINS[getEnvironment()];
10525
+ const backendUrl = `https://${game.slug}.${gameDomain}/api`;
10426
10526
  logger.data("Backend API", underline(blueBright(backendUrl)), 1);
10427
10527
  }
10428
10528
  if (game.version !== "external") {
@@ -10446,10 +10546,10 @@ import { stat as stat2 } from "node:fs/promises";
10446
10546
 
10447
10547
  // ../data/src/domains/game/helpers.ts
10448
10548
  var isHostedGame = (game) => {
10449
- return game.gameType === "hosted" && game.assetBundleBase !== null;
10549
+ return game.gameType === "hosted" && !!game.assetBundleBase;
10450
10550
  };
10451
10551
  var isExternalGame = (game) => {
10452
- return game.gameType === "external" && game.externalUrl !== null;
10552
+ return game.gameType === "external" && !!game.externalUrl;
10453
10553
  };
10454
10554
  function isGameDeployed(game) {
10455
10555
  return isHostedGame(game) || isExternalGame(game);
@@ -10515,9 +10615,9 @@ async function prepareDeploymentContext(options) {
10515
10615
  configFileName,
10516
10616
  client,
10517
10617
  projectPath,
10518
- existingGame: gameIsDeployed ? existingGame : void 0,
10618
+ existingGame,
10519
10619
  deployedGameInfo: deployedGameInfo ?? void 0,
10520
- isNewDeployment: !gameIsDeployed,
10620
+ isGameDeployed: gameIsDeployed,
10521
10621
  buildHash,
10522
10622
  buildSize,
10523
10623
  previousBuildHash: deployedGameInfo?.buildHash,
@@ -10567,8 +10667,8 @@ async function analyzeChanges(context2) {
10567
10667
  };
10568
10668
  }
10569
10669
  async function calculateDeploymentPlan(context2, changes) {
10570
- const { config, isNewDeployment, projectPath, deployBackend } = context2;
10571
- if (isNewDeployment) {
10670
+ const { config, isGameDeployed: isGameDeployed2, projectPath, deployBackend } = context2;
10671
+ if (!isGameDeployed2) {
10572
10672
  const needsBackend3 = hasLocalBackend(projectPath) || !!context2.fullConfig?.integrations;
10573
10673
  const shouldDeployBackend2 = deployBackend && needsBackend3;
10574
10674
  return {
@@ -11423,19 +11523,28 @@ initCommand.addCommand(configCommand);
11423
11523
 
11424
11524
  // src/commands/login.ts
11425
11525
  import { input as input4, password, select as select3 } from "@inquirer/prompts";
11526
+ import { bold as bold4, dim as dim6, whiteBright } from "colorette";
11426
11527
  import { Command as Command4 } from "commander";
11427
11528
  import open from "open";
11428
11529
  init_file_loader();
11429
- 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) => {
11430
- const { email, password: password2, sso } = options;
11431
- 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);
11432
11533
  const profileName = getProfileName();
11433
- const existingProfile = await getProfile(profileName);
11534
+ logger.newLine();
11535
+ const existingProfile = await getProfile(environment, profileName);
11434
11536
  if (existingProfile) {
11435
- const email2 = existingProfile.email;
11436
- logger.info(
11437
- `Already logged in to profile "${profileName}"` + (email2 ? ` as ${email2}` : "")
11438
- );
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
+ ]);
11439
11548
  logger.newLine();
11440
11549
  return;
11441
11550
  }
@@ -11450,7 +11559,7 @@ var loginCommand = new Command4("login").description("Authenticate with Playcade
11450
11559
  });
11451
11560
  logger.newLine();
11452
11561
  if (authMethod === "sso") {
11453
- await handleSsoLogin(profileName);
11562
+ await handleSsoLogin(environment, profileName);
11454
11563
  return;
11455
11564
  }
11456
11565
  const finalEmail = email || await promptForEmail();
@@ -11474,7 +11583,8 @@ var loginCommand = new Command4("login").description("Authenticate with Playcade
11474
11583
  const apiKeyResult = await runStep(
11475
11584
  "Creating API key for CLI usage",
11476
11585
  async () => {
11477
- const nameVersion = await getPackageNameVersion();
11586
+ const pkg = await loadPackageJson({ required: false });
11587
+ const nameVersion = pkg ? getPackageNameVersionFromData(pkg) : `${getCurrentDirectoryName("playcademy-cli")}@0.0.0`;
11478
11588
  const result = await client.auth.apiKeys.create({
11479
11589
  name: nameVersion,
11480
11590
  expiresIn: null,
@@ -11502,14 +11612,17 @@ var loginCommand = new Command4("login").description("Authenticate with Playcade
11502
11612
  logger.newLine();
11503
11613
  await runStep(
11504
11614
  `Saving credentials to profile "${profileName}"`,
11505
- () => saveProfile(profileName, authProfile),
11615
+ () => saveProfile(environment, profileName, authProfile),
11506
11616
  "Credentials saved successfully"
11507
11617
  );
11508
- logger.success(`Logged in as ${authProfile.email}`);
11509
- if (profileName !== "default") {
11510
- logger.newLine();
11511
- logger.info(`Profile saved as "${profileName}"`);
11512
- }
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
+ ]);
11513
11626
  logger.newLine();
11514
11627
  } catch (error) {
11515
11628
  logger.newLine();
@@ -11520,7 +11633,7 @@ var loginCommand = new Command4("login").description("Authenticate with Playcade
11520
11633
  process.exit(1);
11521
11634
  }
11522
11635
  });
11523
- async function handleSsoLogin(profileName) {
11636
+ async function handleSsoLogin(environment, profileName) {
11524
11637
  try {
11525
11638
  const serverPromise = startCallbackServer(SSO_AUTH_TIMEOUT_MS);
11526
11639
  const baseUrl = getBaseUrl();
@@ -11543,7 +11656,8 @@ async function handleSsoLogin(profileName) {
11543
11656
  const apiKeyResult = await runStep(
11544
11657
  "Creating API key for CLI usage",
11545
11658
  async () => {
11546
- const nameVersion = await getPackageNameVersion();
11659
+ const pkg = await loadPackageJson({ required: false });
11660
+ const nameVersion = pkg ? getPackageNameVersionFromData(pkg) : `${getCurrentDirectoryName("playcademy-cli")}@0.0.0`;
11547
11661
  const response = await client.auth.apiKeys.create({
11548
11662
  name: nameVersion,
11549
11663
  expiresIn: null,
@@ -11564,21 +11678,20 @@ async function handleSsoLogin(profileName) {
11564
11678
  email: result.data.email
11565
11679
  };
11566
11680
  logger.newLine();
11567
- logger.admonition("warning", "Your API Key", [apiKeyResult.apiKey]);
11568
- 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
+ ]);
11569
11687
  logger.newLine();
11570
11688
  await runStep(
11571
11689
  `Saving credentials to profile "${profileName}"`,
11572
- () => saveProfile(profileName, authProfile),
11573
- "Credentials saved successfully"
11690
+ () => saveProfile(environment, profileName, authProfile),
11691
+ "Credentials saved successfully",
11692
+ { silent: true }
11574
11693
  );
11575
- logger.success(`Logged in as ${authProfile.email}`);
11576
- if (profileName !== "default") {
11577
- logger.newLine();
11578
- logger.info(`Profile saved as "${profileName}"`);
11579
- logger.newLine();
11580
- logger.aside(`Use \`--profile ${profileName}\` with commands to use this profile`, 1);
11581
- }
11694
+ logger.success(`Logged in to ${environment} as ${authProfile.email}`);
11582
11695
  logger.newLine();
11583
11696
  } catch (error) {
11584
11697
  logger.newLine();
@@ -11617,27 +11730,23 @@ async function promptForPassword() {
11617
11730
 
11618
11731
  // src/commands/logout.ts
11619
11732
  import { Command as Command5 } from "commander";
11620
- var logoutCommand = new Command5("logout").description("Log out from Playcademy").option("--profile <name>", "Profile to log out from", "default").action(async (options) => {
11621
- 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);
11622
11736
  try {
11623
11737
  logger.newLine();
11624
- const profile = await getProfile(profileName);
11738
+ const profile = await getProfile(environment, profileName);
11625
11739
  if (!profile) {
11626
- logger.error(`Profile "${profileName}" not found`);
11740
+ logger.error(`Profile "${profileName}" not found in ${environment}`);
11741
+ logger.newLine();
11627
11742
  logger.info("Use `playcademy profiles list` to see available profiles");
11628
11743
  logger.newLine();
11629
11744
  process.exit(1);
11630
11745
  }
11631
- await removeProfile(profileName);
11632
- logger.success(`Logged out from profile "${profileName}"`);
11633
- if (profile.email) {
11634
- logger.info(`Previously logged in as: ${profile.email}`);
11635
- }
11636
- if (profileName === "default") {
11637
- logger.info("To log in again: playcademy login");
11638
- } else {
11639
- logger.info(`To log in again: playcademy login --profile ${profileName}`);
11640
- }
11746
+ await removeProfile(environment, profileName);
11747
+ logger.success(
11748
+ `Logged out from ${environment} profile "${profileName}" (${profile.email})`
11749
+ );
11641
11750
  logger.newLine();
11642
11751
  } catch (error) {
11643
11752
  logger.newLine();
@@ -11651,18 +11760,24 @@ var logoutCommand = new Command5("logout").description("Log out from Playcademy"
11651
11760
 
11652
11761
  // src/commands/me.ts
11653
11762
  import { Command as Command6 } from "commander";
11654
- 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;
11655
11768
  try {
11769
+ const environment = ensureEnvironment(env, logger);
11656
11770
  const client = await requireAuthenticatedClient();
11657
11771
  logger.newLine();
11658
11772
  const user = await runStep(
11659
- "Fetching user information",
11773
+ `Fetching user information from ${environment}`,
11660
11774
  client.users.me,
11661
11775
  "User information retrieved"
11662
11776
  );
11663
11777
  logger.newLine();
11664
11778
  logger.data("ID", user.id, 1);
11665
11779
  logger.data("Email", user.email, 1);
11780
+ logger.data("Environment", environment, 1);
11666
11781
  if (user.username) {
11667
11782
  logger.data("Username", user.username, 1);
11668
11783
  }
@@ -11710,7 +11825,8 @@ async function executeDeployment(plan, context2) {
11710
11825
  return { game, backendDeployment };
11711
11826
  }
11712
11827
  async function runDeployment(options) {
11713
- await selectEnvironment(options);
11828
+ const environment = ensureEnvironment(options.env, logger);
11829
+ await selectEnvironment({ env: environment, dryRun: options.dryRun });
11714
11830
  const context2 = await prepareDeploymentContext(options);
11715
11831
  displayCurrentConfiguration(context2);
11716
11832
  const didPrompt = await promptForMissingConfig(context2);
@@ -11775,17 +11891,19 @@ import { Command as Command10 } from "commander";
11775
11891
  // src/commands/games/delete.ts
11776
11892
  import { confirm as confirm5, input as input5, select as select4 } from "@inquirer/prompts";
11777
11893
  import { Command as Command8 } from "commander";
11778
- 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;
11779
11896
  try {
11780
- logger.newLine();
11897
+ const environment = ensureEnvironment(env, logger);
11781
11898
  const client = await requireAuthenticatedClient();
11899
+ logger.newLine();
11782
11900
  const games2 = await runStep(
11783
- "Fetching games",
11901
+ `Fetching games from ${environment}`,
11784
11902
  client.games.list,
11785
- (games3) => `Found ${games3.length} game${games3.length === 1 ? "" : "s"}`
11903
+ (games3) => `Found ${games3.length} game${games3.length === 1 ? "" : "s"} in ${environment}`
11786
11904
  );
11787
11905
  if (games2.length === 0) {
11788
- logger.info("No games found");
11906
+ logger.info(`No games found in ${environment}`);
11789
11907
  return;
11790
11908
  }
11791
11909
  logger.newLine();
@@ -11833,11 +11951,11 @@ var deleteCommand = new Command8("delete").alias("rm").alias("remove").descripti
11833
11951
  logger.newLine();
11834
11952
  }
11835
11953
  await runStep(
11836
- "Deleting game",
11954
+ `Deleting game from ${environment}`,
11837
11955
  async () => {
11838
11956
  await client.dev.games.delete(game.id);
11839
11957
  },
11840
- "Game deleted successfully"
11958
+ `Game deleted successfully from ${environment}`
11841
11959
  );
11842
11960
  const deployedGame = await getDeployedGame(getWorkspace());
11843
11961
  if (deployedGame && deployedGame.gameId === game.id) {
@@ -11856,14 +11974,16 @@ var deleteCommand = new Command8("delete").alias("rm").alias("remove").descripti
11856
11974
 
11857
11975
  // src/commands/games/list.ts
11858
11976
  import { Command as Command9 } from "commander";
11859
- 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;
11860
11979
  try {
11980
+ const environment = ensureEnvironment(env, logger);
11861
11981
  const client = await requireAuthenticatedClient();
11862
11982
  logger.newLine();
11863
11983
  const games2 = await runStep(
11864
- "Fetching games",
11984
+ `Fetching games from ${environment}`,
11865
11985
  client.games.list,
11866
- (games3) => `Found ${games3.length} game${games3.length === 1 ? "" : "s"}`
11986
+ (games3) => `Found ${games3.length} game${games3.length === 1 ? "" : "s"} in ${environment}`
11867
11987
  );
11868
11988
  if (games2.length === 0) {
11869
11989
  logger.info("No games found");
@@ -11902,22 +12022,30 @@ import { Command as Command13 } from "commander";
11902
12022
 
11903
12023
  // src/commands/dev/apply.ts
11904
12024
  import { Command as Command11 } from "commander";
11905
- 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;
11906
12030
  try {
11907
- logger.newLine();
12031
+ const environment = ensureEnvironment(env, logger);
11908
12032
  const client = await requireAuthenticatedClient();
12033
+ logger.newLine();
11909
12034
  const currentStatus = await runStep(
11910
- "Checking current developer status",
12035
+ `Checking current developer status in ${environment}`,
11911
12036
  client.dev.status.get,
11912
- "Current status retrieved"
12037
+ `Current status retrieved from ${environment}`
11913
12038
  );
12039
+ logger.newLine();
11914
12040
  if (currentStatus === "approved") {
11915
- logger.success("You are already an approved developer!");
12041
+ logger.admonition("note", "Approved!", ["You are already an approved developer!"]);
11916
12042
  logger.newLine();
11917
12043
  return;
11918
12044
  }
11919
12045
  if (currentStatus === "pending") {
11920
- logger.info("Your developer application is pending review");
12046
+ logger.admonition("warning", "Developer Status", [
12047
+ "Your developer application is pending review"
12048
+ ]);
11921
12049
  logger.newLine();
11922
12050
  return;
11923
12051
  }
@@ -11927,14 +12055,15 @@ var applyCommand = new Command11("apply").description("Apply for developer statu
11927
12055
  "Developer application submitted successfully"
11928
12056
  );
11929
12057
  logger.newLine();
11930
- logger.success("Your application has been submitted!");
11931
- logger.info("What happens next:", 1);
11932
- logger.data("1.", "We will review your application shortly", 1);
11933
- logger.data("2.", "Once approved, you can create and deploy games", 1);
11934
- 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
+ ]);
11935
12065
  logger.newLine();
11936
12066
  } catch (error) {
11937
- logger.newLine();
11938
12067
  logger.error(
11939
12068
  `Failed to apply for developer status: ${error instanceof Error ? error.message : "Unknown error"}`
11940
12069
  );
@@ -11945,31 +12074,37 @@ var applyCommand = new Command11("apply").description("Apply for developer statu
11945
12074
 
11946
12075
  // src/commands/dev/get-status.ts
11947
12076
  import { Command as Command12 } from "commander";
11948
- 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;
11949
12082
  try {
11950
- logger.newLine();
12083
+ const environment = ensureEnvironment(env, logger);
11951
12084
  const client = await requireAuthenticatedClient();
12085
+ logger.newLine();
11952
12086
  const status = await runStep(
11953
- "Fetching developer status",
12087
+ `Fetching developer status from ${environment}`,
11954
12088
  () => client.dev.status.get(),
11955
- "Developer status retrieved"
12089
+ `Developer status retrieved from ${environment}`
11956
12090
  );
11957
- logger.newLine();
11958
- logger.highlight("Developer Status");
11959
- logger.data("Status", status, 1);
11960
12091
  switch (status) {
11961
12092
  case "none":
11962
12093
  logger.newLine();
11963
- logger.info("You have not applied for developer status yet.");
11964
- 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
+ ]);
11965
12098
  break;
11966
12099
  case "pending":
11967
12100
  logger.newLine();
11968
- logger.info("Your application is pending review.");
12101
+ logger.admonition("warning", "Developer Status", [
12102
+ "Your application is pending review."
12103
+ ]);
11969
12104
  break;
11970
12105
  case "approved":
11971
12106
  logger.newLine();
11972
- logger.success("You are an approved developer!");
12107
+ logger.admonition("note", "Approved!", ["You are an approved developer!"]);
11973
12108
  break;
11974
12109
  default:
11975
12110
  logger.newLine();
@@ -12012,7 +12147,7 @@ async function runDevServer(options) {
12012
12147
  });
12013
12148
  const hotReload = options.reload !== false;
12014
12149
  let startMessage = `Game API started: ${blueBright4(underline2(`http://localhost:${port}/api`))}`;
12015
- if (hotReload) startMessage += hotReload ? dim(" (hot reload enabled)") : "";
12150
+ if (hotReload) startMessage += hotReload ? dim2(" (hot reload enabled)") : "";
12016
12151
  logger.success(startMessage);
12017
12152
  logger.newLine();
12018
12153
  let customRoutes = await discoverRoutes(join12(getWorkspace(), "api"));
@@ -12055,26 +12190,39 @@ import { Command as Command17 } from "commander";
12055
12190
  import { Command as Command14 } from "commander";
12056
12191
  async function listProfilesAction() {
12057
12192
  try {
12058
- const profiles = await listProfiles();
12193
+ const profilesMap = await listProfiles();
12059
12194
  logger.newLine();
12060
- 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) {
12061
12203
  logger.warn("No authentication profiles found");
12062
12204
  logger.newLine();
12063
12205
  logger.admonition("tip", "Getting Started", [
12064
12206
  "Run `playcademy login` to create your first profile"
12065
12207
  ]);
12208
+ logger.newLine();
12066
12209
  return;
12067
12210
  }
12068
- const tableData = [];
12069
- for (const profileName of profiles.values()) {
12070
- const profile = await getProfile(profileName);
12071
- tableData.push({
12072
- Profile: profileName,
12073
- Email: profile?.email ?? ""
12074
- });
12075
- }
12076
- if (tableData.length > 0) {
12077
- 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
+ }
12078
12226
  }
12079
12227
  } catch (error) {
12080
12228
  logger.error(
@@ -12082,28 +12230,29 @@ async function listProfilesAction() {
12082
12230
  );
12083
12231
  process.exit(1);
12084
12232
  }
12085
- logger.newLine();
12086
12233
  }
12087
12234
  var listCommand2 = new Command14("list").alias("ls").description("List all stored authentication profiles").action(listProfilesAction);
12088
12235
 
12089
12236
  // src/commands/profiles/remove.ts
12237
+ import { bold as bold5 } from "colorette";
12090
12238
  import { Command as Command15 } from "commander";
12091
- 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);
12092
12242
  try {
12093
12243
  logger.newLine();
12094
- const profiles = await listProfiles();
12095
- if (!profiles.includes(name)) {
12096
- 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}`);
12097
12248
  logger.newLine();
12098
12249
  process.exit(1);
12099
12250
  }
12100
- await removeProfile(name);
12101
- logger.success(`Profile "${name}" removed successfully`);
12102
- if (name !== "default") {
12103
- logger.info(`To re-authenticate: playcademy login --profile ${name}`);
12104
- } else {
12105
- logger.info("To re-authenticate: playcademy login");
12106
- }
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
+ ]);
12107
12256
  logger.newLine();
12108
12257
  } catch (error) {
12109
12258
  logger.newLine();
@@ -12118,10 +12267,16 @@ var removeCommand = new Command15("remove <name>").alias("rm").description("Remo
12118
12267
  // src/commands/profiles/reset.ts
12119
12268
  import { confirm as confirm6 } from "@inquirer/prompts";
12120
12269
  import { Command as Command16 } from "commander";
12121
- 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 () => {
12122
12273
  try {
12123
- const profiles = await listProfiles();
12124
- 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) {
12125
12280
  logger.newLine();
12126
12281
  logger.warn("No authentication profiles found");
12127
12282
  logger.newLine();
@@ -12129,14 +12284,21 @@ var resetCommand = new Command16("reset").description("Remove all authentication
12129
12284
  }
12130
12285
  logger.newLine();
12131
12286
  logger.warn(
12132
- `This will remove ${profiles.length} authentication profile${profiles.length > 1 ? "s" : ""}:`
12287
+ `This will remove ${totalProfiles} authentication profile${totalProfiles > 1 ? "s" : ""} across all environments:`
12133
12288
  );
12134
12289
  logger.newLine();
12135
- for (const profileName of profiles) {
12136
- const profile = await getProfile(profileName);
12137
- 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();
12138
12301
  }
12139
- logger.newLine();
12140
12302
  const confirmed = await confirm6({
12141
12303
  message: "Are you sure you want to remove all profiles?",
12142
12304
  default: false
@@ -12146,23 +12308,27 @@ var resetCommand = new Command16("reset").description("Remove all authentication
12146
12308
  return;
12147
12309
  }
12148
12310
  let removedCount = 0;
12149
- for (const profileName of profiles) {
12150
- try {
12151
- await removeProfile(profileName);
12152
- removedCount++;
12153
- } catch (error) {
12154
- logger.newLine();
12155
- logger.error(`Failed to remove profile "${profileName}"`);
12156
- logger.error(error instanceof Error ? error.message : "Unknown error");
12157
- 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
+ }
12158
12324
  }
12159
12325
  }
12160
- if (removedCount === profiles.length) {
12326
+ if (removedCount === totalProfiles) {
12161
12327
  logger.success(
12162
12328
  `${removedCount > 1 ? "All " : ""}${removedCount} profile${removedCount > 1 ? "s" : ""} removed successfully`
12163
12329
  );
12164
12330
  } else {
12165
- logger.warn(`Removed ${removedCount} of ${profiles.length} profiles`);
12331
+ logger.warn(`Removed ${removedCount} of ${totalProfiles} profiles`);
12166
12332
  }
12167
12333
  logger.newLine();
12168
12334
  } catch (error) {
@@ -12187,13 +12353,18 @@ import { Command as Command22 } from "commander";
12187
12353
  // src/commands/timeback/cleanup.ts
12188
12354
  import { confirm as confirm7 } from "@inquirer/prompts";
12189
12355
  import { Command as Command18 } from "commander";
12190
- 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;
12191
12361
  try {
12192
- logger.newLine();
12362
+ const environment = ensureEnvironment(env, logger);
12193
12363
  const client = await requireAuthenticatedClient();
12364
+ logger.newLine();
12194
12365
  const { game } = await getGameFromConfig(client);
12195
12366
  const integration = await runStep(
12196
- "Checking for TimeBack integration",
12367
+ `Checking for TimeBack integration in ${environment}`,
12197
12368
  () => client.timeback.management.get(game.id),
12198
12369
  "Integration status checked"
12199
12370
  );
@@ -12216,9 +12387,9 @@ var cleanupCommand = new Command18("cleanup").description("Remove TimeBack integ
12216
12387
  }
12217
12388
  logger.newLine();
12218
12389
  await runStep(
12219
- "Removing TimeBack integration",
12390
+ `Removing TimeBack integration from ${environment}`,
12220
12391
  () => client.timeback.management.cleanup(game.id),
12221
- "TimeBack integration removed"
12392
+ `TimeBack integration removed from ${environment}`
12222
12393
  );
12223
12394
  logger.newLine();
12224
12395
  logger.admonition("note", "Note", [
@@ -12237,8 +12408,13 @@ var cleanupCommand = new Command18("cleanup").description("Remove TimeBack integ
12237
12408
  // src/commands/timeback/setup.ts
12238
12409
  import { Command as Command19 } from "commander";
12239
12410
  import dedent from "dedent";
12240
- 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;
12241
12416
  try {
12417
+ ensureEnvironment(env, logger);
12242
12418
  logger.newLine();
12243
12419
  const config = await runStep(
12244
12420
  "Loading configuration",
@@ -12340,8 +12516,13 @@ var setupCommand = new Command19("setup").description("Set up TimeBack integrati
12340
12516
  import { confirm as confirm8 } from "@inquirer/prompts";
12341
12517
  import { green as green3, red as red3 } from "colorette";
12342
12518
  import { Command as Command20 } from "commander";
12343
- 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;
12344
12524
  try {
12525
+ ensureEnvironment(env, logger);
12345
12526
  logger.newLine();
12346
12527
  const config = await runStep(
12347
12528
  "Loading configuration",
@@ -12447,13 +12628,18 @@ var updateCommand = new Command20("update").description("Update TimeBack integra
12447
12628
 
12448
12629
  // src/commands/timeback/verify.ts
12449
12630
  import { Command as Command21 } from "commander";
12450
- 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;
12451
12636
  try {
12452
- logger.newLine();
12637
+ const environment = ensureEnvironment(env, logger);
12453
12638
  const client = await requireAuthenticatedClient();
12639
+ logger.newLine();
12454
12640
  const { game } = await getGameFromConfig(client);
12455
12641
  const result = await runStep(
12456
- "Verifying TimeBack integration",
12642
+ `Verifying TimeBack integration in ${environment}`,
12457
12643
  () => client.timeback.management.verify(game.id),
12458
12644
  "Verification complete"
12459
12645
  );
@@ -12494,11 +12680,12 @@ timebackCommand.addCommand(cleanupCommand);
12494
12680
  var __dirname = dirname4(fileURLToPath2(import.meta.url));
12495
12681
  var packageJson = await loadPackageJson({ cwd: __dirname, searchUp: true, required: true });
12496
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) => {
12497
- const options = thisCommand.opts();
12683
+ const options = thisCommand.optsWithGlobals();
12498
12684
  setCliContext({ profile: options.profile, workspace: process.cwd() });
12499
12685
  });
12500
12686
  program.addCommand(initCommand);
12501
12687
  program.addCommand(loginCommand);
12688
+ program.addCommand(logoutCommand);
12502
12689
  program.addCommand(profilesCommand);
12503
12690
  program.addCommand(meCommand);
12504
12691
  program.addCommand(devCommand);
@@ -12536,6 +12723,7 @@ export {
12536
12723
  displayRegisteredRoutes,
12537
12724
  displayResourcesStatus,
12538
12725
  displaySuccessMessage,
12726
+ ensureEnvironment,
12539
12727
  ensureGameExists,
12540
12728
  findConfigPath,
12541
12729
  generateEntryCode,
@@ -12543,6 +12731,7 @@ export {
12543
12731
  generateJsonConfig,
12544
12732
  getApiUrl,
12545
12733
  getAuthPath,
12734
+ getAuthenticatedEnvironments,
12546
12735
  getBackendHash,
12547
12736
  getBackendSize,
12548
12737
  getBaseUrl,
@@ -12574,6 +12763,7 @@ export {
12574
12763
  loadGameStore,
12575
12764
  logger,
12576
12765
  needsBackend,
12766
+ normalizeEnvironment,
12577
12767
  prepareDeploymentContext,
12578
12768
  processConfigVariables,
12579
12769
  promptForApiRoutes,