@bigcommerce/catalyst 1.0.0 → 1.1.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.
Files changed (3) hide show
  1. package/dist/cli.js +1441 -590
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +3 -1
package/dist/cli.js CHANGED
@@ -1,24 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/cli/lib/auth-errors.ts
4
- var UnauthorizedError = class extends Error {
5
- constructor() {
6
- super(
7
- "Your access token is invalid or has expired.\nRun `catalyst auth login` to re-authenticate."
8
- );
9
- this.name = "UnauthorizedError";
3
+ // src/cli/lib/errors.ts
4
+ var UserActionableError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "UserActionableError";
10
8
  }
11
9
  };
12
- function assertAuthorized(response) {
13
- if (response.status === 401) {
14
- throw new UnauthorizedError();
15
- }
16
- }
17
10
 
18
11
  // src/cli/lib/logger.ts
19
12
  import { createConsola } from "consola";
20
13
  var consola = createConsola({
21
- level: "3" ? parseInt("3", 10) : 3
14
+ level: "3" ? parseInt("3", 10) : 3,
15
+ // Drop the per-line timestamp. It adds noise for a short-lived CLI and, on
16
+ // long lines, consola can't right-align it so it falls back to an inline
17
+ // `[time]` prefix that breaks copy-pasteable command suggestions.
18
+ formatOptions: { date: false }
22
19
  });
23
20
 
24
21
  // src/cli/lib/telemetry.ts
@@ -28,7 +25,7 @@ import { randomBytes, randomUUID } from "crypto";
28
25
  // package.json
29
26
  var package_default = {
30
27
  name: "@bigcommerce/catalyst",
31
- version: "1.0.0",
28
+ version: "1.1.0",
32
29
  type: "module",
33
30
  repository: {
34
31
  type: "git",
@@ -69,6 +66,7 @@ var package_default = {
69
66
  "lodash.kebabcase": "^4.1.1",
70
67
  nypm: "^0.5.4",
71
68
  open: "^10.1.0",
69
+ semver: "^7.7.4",
72
70
  "std-env": "^3.9.0",
73
71
  tar: "^7.5.7",
74
72
  "yocto-spinner": "^1.0.0",
@@ -82,6 +80,7 @@ var package_default = {
82
80
  "@types/fs-extra": "^11.0.4",
83
81
  "@types/lodash.kebabcase": "^4.1.9",
84
82
  "@types/node": "^22.15.30",
83
+ "@types/semver": "^7.7.0",
85
84
  "@vitest/coverage-v8": "^3.2.4",
86
85
  "@vitest/ui": "^3.2.4",
87
86
  eslint: "^8.57.1",
@@ -201,26 +200,65 @@ function getTelemetry() {
201
200
  }
202
201
 
203
202
  // src/cli/program.ts
204
- import { Option as Option11 } from "@commander-js/extra-typings";
205
- import { Command as Command14 } from "commander";
206
- import { colorize as colorize11 } from "consola/utils";
207
- import { config } from "dotenv";
208
- import { existsSync as existsSync4 } from "fs";
209
- import { resolve } from "path";
203
+ import { Command as Command15 } from "commander";
204
+ import { colorize as colorize13 } from "consola/utils";
210
205
 
211
206
  // src/cli/commands/auth.ts
212
- import { Command, Option as Option2 } from "commander";
213
- import { z as z4 } from "zod";
207
+ import { Command } from "commander";
208
+ import { z as z5 } from "zod";
209
+
210
+ // src/cli/lib/auth-errors.ts
211
+ var UnauthorizedError = class extends UserActionableError {
212
+ constructor() {
213
+ super(
214
+ "Your access token is invalid or has expired.\nRun `catalyst auth login` to re-authenticate."
215
+ );
216
+ this.name = "UnauthorizedError";
217
+ }
218
+ };
219
+ function assertAuthorized(response) {
220
+ if (response.status === 401) {
221
+ throw new UnauthorizedError();
222
+ }
223
+ }
214
224
 
215
225
  // src/cli/lib/login.ts
216
226
  import { confirm, input, password } from "@inquirer/prompts";
217
227
  import { colorize } from "consola/utils";
218
228
  import open from "open";
219
229
  import yoctoSpinner from "yocto-spinner";
220
- import { z as z2 } from "zod";
230
+ import { z as z3 } from "zod";
221
231
 
222
232
  // src/cli/lib/auth.ts
233
+ import { z as z2 } from "zod";
234
+
235
+ // src/cli/lib/http-errors.ts
223
236
  import { z } from "zod";
237
+ var apiErrorBodySchema = z.object({
238
+ title: z.string().optional(),
239
+ detail: z.string().optional(),
240
+ errors: z.record(z.string(), z.string()).optional()
241
+ });
242
+ var FALLBACK_CLIENT_ERROR = "The request was rejected. Check your input \u2014 your access token may be missing a required scope, or the resource may not exist.";
243
+ var FALLBACK_SERVER_ERROR = "Something went wrong on our end. Please try again. If the issue persists, contact support.";
244
+ function extractApiErrorMessage(body) {
245
+ const parsed = apiErrorBodySchema.safeParse(body);
246
+ if (!parsed.success) return void 0;
247
+ const { title, detail, errors } = parsed.data;
248
+ const fieldErrors = errors && Object.keys(errors).length > 0 ? Object.entries(errors).map(([field, message]) => `${field}: ${message}`).join("; ") : void 0;
249
+ const headline = detail ?? title;
250
+ if (headline && fieldErrors) return `${headline} (${fieldErrors})`;
251
+ return headline ?? fieldErrors;
252
+ }
253
+ async function httpError(response, action) {
254
+ const body = await response.json().catch(() => null);
255
+ const isServerError = response.status >= 500;
256
+ const detail = extractApiErrorMessage(body) ?? (isServerError ? FALLBACK_SERVER_ERROR : FALLBACK_CLIENT_ERROR);
257
+ const message = `${action}: ${detail}`;
258
+ return isServerError ? new Error(message) : new UserActionableError(message);
259
+ }
260
+
261
+ // src/cli/lib/auth.ts
224
262
  var DEVICE_OAUTH_CLIENT_ID = "b8063bu6hhml4e0lqh22yut63atsbyv";
225
263
  var DEVICE_OAUTH_SCOPES = [
226
264
  "store_v2_information",
@@ -230,18 +268,18 @@ var DEVICE_OAUTH_SCOPES = [
230
268
  "store_channel_settings"
231
269
  ].join(" ");
232
270
  var DEFAULT_LOGIN_URL = "https://login.bigcommerce.com";
233
- var DeviceCodeResponseSchema = z.object({
234
- device_code: z.string(),
235
- user_code: z.string(),
236
- verification_uri: z.string(),
237
- expires_in: z.number(),
238
- interval: z.number()
271
+ var DeviceCodeResponseSchema = z2.object({
272
+ device_code: z2.string(),
273
+ user_code: z2.string(),
274
+ verification_uri: z2.string(),
275
+ expires_in: z2.number(),
276
+ interval: z2.number()
239
277
  });
240
- var DeviceCodeSuccessSchema = z.object({
241
- access_token: z.string(),
242
- store_hash: z.string(),
243
- context: z.string(),
244
- api_uri: z.string()
278
+ var DeviceCodeSuccessSchema = z2.object({
279
+ access_token: z2.string(),
280
+ store_hash: z2.string(),
281
+ context: z2.string(),
282
+ api_uri: z2.string()
245
283
  });
246
284
  async function requestDeviceCode(loginUrl) {
247
285
  const response = await fetch(`${loginUrl}/device/token`, {
@@ -253,7 +291,7 @@ async function requestDeviceCode(loginUrl) {
253
291
  })
254
292
  });
255
293
  if (!response.ok) {
256
- throw new Error(`Failed to request device code: ${response.status} ${response.statusText}`);
294
+ throw await httpError(response, "Failed to request device code");
257
295
  }
258
296
  const res = await response.json();
259
297
  return DeviceCodeResponseSchema.parse(res);
@@ -284,6 +322,33 @@ async function waitForDeviceToken(loginUrl, deviceCode, interval) {
284
322
  return waitForDeviceToken(loginUrl, deviceCode, interval);
285
323
  }
286
324
 
325
+ // src/cli/lib/clipboard.ts
326
+ import { execa } from "execa";
327
+ function clipboardCommand() {
328
+ switch (process.platform) {
329
+ case "darwin":
330
+ return { command: "pbcopy", args: [] };
331
+ case "win32":
332
+ return { command: "clip", args: [] };
333
+ case "linux":
334
+ return process.env.WAYLAND_DISPLAY ? { command: "wl-copy", args: [] } : { command: "xclip", args: ["-selection", "clipboard"] };
335
+ default:
336
+ return null;
337
+ }
338
+ }
339
+ async function copyToClipboard(text) {
340
+ const clipboard = clipboardCommand();
341
+ if (!clipboard) {
342
+ return false;
343
+ }
344
+ try {
345
+ await execa(clipboard.command, clipboard.args, { input: text });
346
+ return true;
347
+ } catch {
348
+ return false;
349
+ }
350
+ }
351
+
287
352
  // src/cli/lib/login.ts
288
353
  var LoginAbortedError = class extends Error {
289
354
  constructor() {
@@ -291,9 +356,9 @@ var LoginAbortedError = class extends Error {
291
356
  this.name = "LoginAbortedError";
292
357
  }
293
358
  };
294
- var StoreProfileSchema = z2.object({
295
- data: z2.object({
296
- store_name: z2.string()
359
+ var StoreProfileSchema = z3.object({
360
+ data: z3.object({
361
+ store_name: z3.string()
297
362
  })
298
363
  });
299
364
  async function fetchStoreProfile(storeHash, accessToken, apiHost) {
@@ -319,6 +384,13 @@ async function deviceCodeLogin(loginUrl) {
319
384
  consola.info(
320
385
  `${colorize("yellow", "Your one-time code:")} ${colorize("bold", deviceCode.user_code)}`
321
386
  );
387
+ if (process.stdin.isTTY) {
388
+ await input({ message: "Press Enter to open your browser and sign in" });
389
+ const copied = await copyToClipboard(deviceCode.user_code);
390
+ if (copied) {
391
+ consola.info("Copied the code to your clipboard \u2014 paste it into the sign-in page.");
392
+ }
393
+ }
322
394
  try {
323
395
  await open(deviceCode.verification_uri);
324
396
  consola.info(`Opened ${deviceCode.verification_uri} in your browser.`);
@@ -346,12 +418,12 @@ Grant these OAuth scopes: ${DEVICE_OAUTH_SCOPES}`
346
418
  const storeHashInput = await input({ message: "Store hash:" });
347
419
  const storeHash = storeHashInput.trim();
348
420
  if (!storeHash) {
349
- throw new Error("Store hash is required.");
421
+ throw new UserActionableError("Store hash is required.");
350
422
  }
351
423
  const accessTokenInput = await password({ message: "Access token:", mask: true });
352
424
  const accessToken = accessTokenInput.trim();
353
425
  if (!accessToken) {
354
- throw new Error("Access token is required.");
426
+ throw new UserActionableError("Access token is required.");
355
427
  }
356
428
  const spinner = yoctoSpinner().start("Validating credentials...");
357
429
  try {
@@ -360,7 +432,7 @@ Grant these OAuth scopes: ${DEVICE_OAUTH_SCOPES}`
360
432
  } catch (error) {
361
433
  spinner.error("Failed to validate credentials.");
362
434
  const message = error instanceof Error ? error.message : String(error);
363
- throw new Error(
435
+ throw new UserActionableError(
364
436
  `Could not validate credentials (${message}). Double-check your store hash and access token, then try again.`
365
437
  );
366
438
  }
@@ -384,19 +456,19 @@ async function login(loginUrl, apiHost) {
384
456
  }
385
457
 
386
458
  // src/cli/lib/project.ts
387
- import { z as z3 } from "zod";
388
- var InfrastructureProjectValidationError = class extends Error {
459
+ import { z as z4 } from "zod";
460
+ var InfrastructureProjectValidationError = class extends UserActionableError {
389
461
  constructor(message) {
390
462
  super(message);
391
463
  this.name = "InfrastructureProjectValidationError";
392
464
  }
393
465
  };
394
- var fetchProjectsSchema = z3.object({
395
- data: z3.array(
396
- z3.object({
397
- uuid: z3.string(),
398
- name: z3.string(),
399
- deployment_hostnames: z3.array(z3.string())
466
+ var fetchProjectsSchema = z4.object({
467
+ data: z4.array(
468
+ z4.object({
469
+ uuid: z4.string(),
470
+ name: z4.string(),
471
+ deployment_hostnames: z4.array(z4.string())
400
472
  })
401
473
  )
402
474
  });
@@ -417,9 +489,7 @@ async function hasProjectsAccess(storeHash, accessToken, apiHost) {
417
489
  assertAuthorized(response);
418
490
  if (response.status === 200) return true;
419
491
  if (response.status === 403) return false;
420
- throw new Error(
421
- `GET /v3/infrastructure/projects failed: ${response.status} ${response.statusText}`
422
- );
492
+ throw await httpError(response, "Failed to check project access");
423
493
  }
424
494
  async function fetchProjects(storeHash, accessToken, apiHost) {
425
495
  const response = await fetch(projectsUrl(storeHash, apiHost), {
@@ -428,29 +498,29 @@ async function fetchProjects(storeHash, accessToken, apiHost) {
428
498
  });
429
499
  assertAuthorized(response);
430
500
  if (response.status === 403) {
431
- throw new Error(
501
+ throw new UserActionableError(
432
502
  "Infrastructure Projects API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
433
503
  );
434
504
  }
435
505
  if (!response.ok) {
436
- throw new Error(`Failed to fetch projects: ${response.statusText}`);
506
+ throw await httpError(response, "Failed to fetch projects");
437
507
  }
438
508
  const res = await response.json();
439
509
  const { data } = fetchProjectsSchema.parse(res);
440
510
  return data;
441
511
  }
442
- var createProjectSchema = z3.object({
443
- data: z3.object({
444
- uuid: z3.string(),
445
- name: z3.string(),
446
- date_created: z3.coerce.date(),
447
- date_modified: z3.coerce.date()
512
+ var createProjectSchema = z4.object({
513
+ data: z4.object({
514
+ uuid: z4.string(),
515
+ name: z4.string(),
516
+ date_created: z4.coerce.date(),
517
+ date_modified: z4.coerce.date()
448
518
  })
449
519
  });
450
- var validationErrorBodySchema = z3.object({
451
- title: z3.string().optional(),
452
- detail: z3.string().optional(),
453
- errors: z3.record(z3.string(), z3.string()).optional()
520
+ var validationErrorBodySchema = z4.object({
521
+ title: z4.string().optional(),
522
+ detail: z4.string().optional(),
523
+ errors: z4.record(z4.string(), z4.string()).optional()
454
524
  });
455
525
  function extractValidationMessage(body) {
456
526
  const parsed = validationErrorBodySchema.safeParse(body);
@@ -478,8 +548,8 @@ async function createProject(name, storeHash, accessToken, apiHost) {
478
548
  const message = extractValidationMessage(body) ?? fallback;
479
549
  throw new InfrastructureProjectValidationError(message);
480
550
  }
481
- if (response.status === 403) {
482
- throw new Error(
551
+ if (response.status === 403 || response.status === 404) {
552
+ throw new UserActionableError(
483
553
  "Infrastructure Projects API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
484
554
  );
485
555
  }
@@ -487,7 +557,7 @@ async function createProject(name, storeHash, accessToken, apiHost) {
487
557
  throw new Error("Failed to create project, is the name already in use?");
488
558
  }
489
559
  if (!response.ok) {
490
- throw new Error(`Failed to create project: ${response.statusText}`);
560
+ throw await httpError(response, "Failed to create project");
491
561
  }
492
562
  const res = await response.json();
493
563
  const { data } = createProjectSchema.parse(res);
@@ -500,15 +570,15 @@ async function deleteProject(projectUuid, storeHash, accessToken, apiHost) {
500
570
  });
501
571
  assertAuthorized(response);
502
572
  if (response.status === 403) {
503
- throw new Error(
573
+ throw new UserActionableError(
504
574
  "Infrastructure Projects API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
505
575
  );
506
576
  }
507
577
  if (response.status === 404) {
508
- throw new Error(`Project ${projectUuid} not found.`);
578
+ throw new UserActionableError(`Project ${projectUuid} not found.`);
509
579
  }
510
580
  if (!response.ok) {
511
- throw new Error(`Failed to delete project: ${response.statusText}`);
581
+ throw await httpError(response, "Failed to delete project");
512
582
  }
513
583
  }
514
584
 
@@ -529,6 +599,7 @@ function getProjectConfig() {
529
599
  },
530
600
  storeHash: { type: "string" },
531
601
  accessToken: { type: "string" },
602
+ apiHost: { type: "string" },
532
603
  env: {
533
604
  type: "object",
534
605
  additionalProperties: { type: "string" },
@@ -540,6 +611,7 @@ function getProjectConfig() {
540
611
 
541
612
  // src/cli/lib/shared-options.ts
542
613
  import { Option } from "commander";
614
+ var DEFAULT_API_HOST = "api.bigcommerce.com";
543
615
  var storeHashOption = () => new Option(
544
616
  "--store-hash <hash>",
545
617
  "BigCommerce store hash. Can be found in the URL of your store Control Panel. Read from .bigcommerce/project.json or .env when not provided."
@@ -548,12 +620,19 @@ var accessTokenOption = () => new Option(
548
620
  "--access-token <token>",
549
621
  "BigCommerce access token. Can be found after creating a store-level API account. Read from .bigcommerce/project.json or .env when not provided."
550
622
  ).env("CATALYST_ACCESS_TOKEN");
551
- var apiHostOption = () => new Option("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp();
623
+ var apiHostOption = () => new Option(
624
+ "--api-host <host>",
625
+ "BigCommerce API host. Read from .bigcommerce/project.json or CATALYST_API_HOST when not provided. Defaults to api.bigcommerce.com."
626
+ ).env("CATALYST_API_HOST").hideHelp();
552
627
  var loginUrlOption = () => new Option("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp();
553
628
  var projectUuidOption = () => new Option(
554
629
  "--project-uuid <uuid>",
555
630
  "BigCommerce infrastructure project UUID. Read from .bigcommerce/project.json or .env when not provided."
556
631
  ).env("CATALYST_PROJECT_UUID");
632
+ var envPathOption = () => new Option(
633
+ "--env-path <path>",
634
+ "Path to an environment file to load for the build (relative to the current working directory). When omitted, .env.local and .env are auto-loaded from the current directory. Other commands never load env files."
635
+ );
557
636
  var resolveProjectUuid = (options) => {
558
637
  const config2 = getProjectConfig();
559
638
  const projectUuid = options.projectUuid ?? config2.get("projectUuid");
@@ -564,11 +643,12 @@ var resolveProjectUuid = (options) => {
564
643
  }
565
644
  return projectUuid;
566
645
  };
646
+ var resolveApiHost = (options, config2) => options.apiHost ?? config2.get("apiHost") ?? DEFAULT_API_HOST;
567
647
 
568
648
  // src/cli/commands/auth.ts
569
- var StoreProfileSchema2 = z4.object({
570
- data: z4.object({
571
- store_name: z4.string()
649
+ var StoreProfileSchema2 = z5.object({
650
+ data: z5.object({
651
+ store_name: z5.string()
572
652
  })
573
653
  });
574
654
  async function fetchStoreProfile2(storeHash, accessToken, apiHost) {
@@ -597,21 +677,10 @@ Example:
597
677
  $ catalyst auth whoami
598
678
 
599
679
  Logged in to My Store (abc123), connected to project my-project (43eba682-0c48-11f1-9bd5-827a48b0ce1e)`
600
- ).addOption(
601
- new Option2(
602
- "--store-hash <hash>",
603
- "BigCommerce store hash. Can be found in the URL of your store Control Panel."
604
- ).env("CATALYST_STORE_HASH")
605
- ).addOption(
606
- new Option2(
607
- "--access-token <token>",
608
- "BigCommerce access token. Can be found after creating a store-level API account."
609
- ).env("CATALYST_ACCESS_TOKEN")
610
- ).addOption(
611
- new Option2("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp()
612
- ).action(async (options) => {
680
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).action(async (options) => {
613
681
  try {
614
682
  const config2 = getProjectConfig();
683
+ const apiHost = resolveApiHost(options, config2);
615
684
  const storeHash = options.storeHash ?? config2.get("storeHash");
616
685
  const accessToken = options.accessToken ?? config2.get("accessToken");
617
686
  if (!storeHash || !accessToken) {
@@ -622,10 +691,10 @@ Example:
622
691
  process.exit(1);
623
692
  return;
624
693
  }
625
- const store = await fetchStoreProfile2(storeHash, accessToken, options.apiHost);
694
+ const store = await fetchStoreProfile2(storeHash, accessToken, apiHost);
626
695
  const projectUuid = config2.get("projectUuid");
627
696
  if (projectUuid) {
628
- const projects = await fetchProjects(storeHash, accessToken, options.apiHost);
697
+ const projects = await fetchProjects(storeHash, accessToken, apiHost);
629
698
  const linkedProject = projects.find((p) => p.uuid === projectUuid);
630
699
  if (linkedProject) {
631
700
  consola.info(
@@ -665,21 +734,10 @@ Examples:
665
734
 
666
735
  # Login with existing credentials (skips interactive flow)
667
736
  $ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN>`
668
- ).addOption(
669
- new Option2(
670
- "--store-hash <hash>",
671
- "BigCommerce store hash. Can be found in the URL of your store Control Panel."
672
- ).env("CATALYST_STORE_HASH")
673
- ).addOption(
674
- new Option2(
675
- "--access-token <token>",
676
- "BigCommerce access token. Can be found after creating a store-level API account."
677
- ).env("CATALYST_ACCESS_TOKEN")
678
- ).addOption(
679
- new Option2("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp()
680
- ).addOption(loginUrlOption()).action(async (options) => {
737
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).action(async (options) => {
681
738
  try {
682
739
  const config2 = getProjectConfig();
740
+ const apiHost = resolveApiHost(options, config2);
683
741
  const storeHash = options.storeHash ?? config2.get("storeHash");
684
742
  const accessToken = options.accessToken ?? config2.get("accessToken");
685
743
  if (storeHash && accessToken) {
@@ -688,7 +746,7 @@ Examples:
688
746
  process.exit(0);
689
747
  return;
690
748
  }
691
- const credentials = await login(options.loginUrl, options.apiHost);
749
+ const credentials = await login(options.loginUrl, apiHost);
692
750
  config2.set("storeHash", credentials.storeHash);
693
751
  config2.set("accessToken", credentials.accessToken);
694
752
  consola.success(`Logged in to store ${credentials.storeHash}.`);
@@ -734,10 +792,43 @@ Example:
734
792
  var auth = new Command("auth").configureHelp({ showGlobalOptions: true }).description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(login2).addCommand(logout);
735
793
 
736
794
  // src/cli/commands/build.ts
737
- import { Command as Command2, Option as Option3 } from "commander";
738
- import { execa } from "execa";
795
+ import { Command as Command2, InvalidArgumentError, Option as Option2 } from "commander";
796
+ import { execa as execa2 } from "execa";
739
797
  import { copyFile, cp, rm, writeFile } from "fs/promises";
740
798
  import { join as join4 } from "path";
799
+ import { valid as validSemver } from "semver";
800
+
801
+ // src/cli/lib/build-env.ts
802
+ import { colorize as colorize2 } from "consola/utils";
803
+ import { config } from "dotenv";
804
+ import { existsSync } from "fs";
805
+ import { resolve } from "path";
806
+ var AUTO_ENV_FILES = [".env.local", ".env"];
807
+ function loadBuildEnv({ envPath, cwd = process.cwd() }) {
808
+ if (envPath !== void 0) {
809
+ const envFilePath = resolve(cwd, envPath);
810
+ const result = config({ path: envFilePath, override: true });
811
+ if (result.error) {
812
+ const errCode = "code" in result.error && typeof result.error.code === "string" ? result.error.code : void 0;
813
+ const message = errCode === "ENOENT" ? `Env file not found: ${envFilePath}` : `Failed to load --env-path ${envPath}: ${result.error.message}`;
814
+ throw new UserActionableError(message);
815
+ }
816
+ consola.log(colorize2("cyanBright", `Loaded environment variables from ${envFilePath}
817
+ `));
818
+ return;
819
+ }
820
+ const loaded = AUTO_ENV_FILES.filter((file) => {
821
+ const envFilePath = resolve(cwd, file);
822
+ if (!existsSync(envFilePath)) {
823
+ return false;
824
+ }
825
+ return !config({ path: envFilePath, override: false }).error;
826
+ });
827
+ if (loaded.length > 0) {
828
+ consola.log(colorize2("cyanBright", `Loaded environment variables from ${loaded.join(", ")}
829
+ `));
830
+ }
831
+ }
741
832
 
742
833
  // src/cli/lib/get-module-cli-path.ts
743
834
  import { dirname, join as join2 } from "path";
@@ -749,14 +840,14 @@ function getModuleCliPath() {
749
840
  }
750
841
 
751
842
  // src/cli/lib/project-state.ts
752
- import { existsSync, readFileSync } from "fs";
843
+ import { existsSync as existsSync2, readFileSync } from "fs";
753
844
  import { join as join3 } from "path";
754
- import { z as z5 } from "zod";
755
- var projectJsonSchema = z5.looseObject({
756
- projectUuid: z5.string().optional()
845
+ import { z as z6 } from "zod";
846
+ var projectJsonSchema = z6.looseObject({
847
+ projectUuid: z6.string().optional()
757
848
  });
758
- var packageJsonSchema = z5.looseObject({
759
- dependencies: z5.record(z5.string(), z5.string()).optional()
849
+ var packageJsonSchema = z6.looseObject({
850
+ dependencies: z6.record(z6.string(), z6.string()).optional()
760
851
  });
761
852
  var safeReadJson = (path) => {
762
853
  try {
@@ -770,8 +861,8 @@ function getProjectState(cwd = process.cwd()) {
770
861
  safeReadJson(join3(cwd, ".bigcommerce", "project.json"))
771
862
  );
772
863
  const projectUuid = projectJson.success ? projectJson.data.projectUuid : void 0;
773
- const hasMiddleware = existsSync(join3(cwd, "middleware.ts"));
774
- const hasProxy = existsSync(join3(cwd, "proxy.ts"));
864
+ const hasMiddleware = existsSync2(join3(cwd, "middleware.ts"));
865
+ const hasProxy = existsSync2(join3(cwd, "proxy.ts"));
775
866
  const pkgJson = packageJsonSchema.safeParse(safeReadJson(join3(cwd, "package.json")));
776
867
  const hasOpenNextDep = pkgJson.success ? Boolean(pkgJson.data.dependencies?.["@opennextjs/cloudflare"]) : false;
777
868
  const isLinked = Boolean(projectUuid);
@@ -788,6 +879,34 @@ function getProjectState(cwd = process.cwd()) {
788
879
  };
789
880
  }
790
881
 
882
+ // src/cli/lib/required-build-env.ts
883
+ var REQUIRED_BUILD_ENV_VARS = [
884
+ "BIGCOMMERCE_STORE_HASH",
885
+ "BIGCOMMERCE_STOREFRONT_TOKEN",
886
+ "BIGCOMMERCE_CHANNEL_ID",
887
+ "AUTH_SECRET"
888
+ ];
889
+ var findMissingBuildEnv = (env2 = process.env) => {
890
+ return REQUIRED_BUILD_ENV_VARS.filter((name) => {
891
+ const value = env2[name];
892
+ return value === void 0 || value.trim() === "";
893
+ });
894
+ };
895
+ var assertRequiredBuildEnv = (env2 = process.env) => {
896
+ const missing = findMissingBuildEnv(env2);
897
+ if (missing.length === 0) {
898
+ return;
899
+ }
900
+ const missingList = missing.map((name) => ` - ${name}`).join("\n");
901
+ throw new UserActionableError(
902
+ `Missing required environment variable${missing.length === 1 ? "" : "s"} for the build:
903
+ ${missingList}
904
+
905
+ These are read by the build itself, so they must be set before it runs. The build automatically loads \`.env.local\` and \`.env\` from the current directory (${process.cwd()}) \u2014 add the variables above to one of those files (\`.env.local\` takes precedence).
906
+ To load an env file from a different location, pass \`--env-path <path>\` (for example \`--env-path ../.env.local\` when running from a subdirectory).`
907
+ );
908
+ };
909
+
791
910
  // src/cli/lib/wrangler-config.ts
792
911
  function getCompatibilityDate(now = /* @__PURE__ */ new Date()) {
793
912
  const date = new Date(now);
@@ -853,7 +972,17 @@ function getWranglerConfig(projectUuid) {
853
972
 
854
973
  // src/cli/commands/build.ts
855
974
  var WRANGLER_VERSION = "4.90.0";
856
- async function buildCatalystProject(projectUuid) {
975
+ var DIST_TAG_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
976
+ var parseWranglerVersion = (value) => {
977
+ if (!validSemver(value) && !DIST_TAG_PATTERN.test(value)) {
978
+ throw new InvalidArgumentError(
979
+ `"${value}" is not a valid Wrangler version or dist-tag (e.g. 4.90.0 or latest).`
980
+ );
981
+ }
982
+ return value;
983
+ };
984
+ async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERSION) {
985
+ assertRequiredBuildEnv();
857
986
  const coreDir = process.cwd();
858
987
  const openNextOutDir = join4(coreDir, ".open-next");
859
988
  const bigcommerceDistDir = join4(coreDir, ".bigcommerce", "dist");
@@ -870,7 +999,7 @@ async function buildCatalystProject(projectUuid) {
870
999
  );
871
1000
  consola.success("Templates copied");
872
1001
  consola.start("Building project...");
873
- await execa(
1002
+ await execa2(
874
1003
  "pnpm",
875
1004
  [
876
1005
  "exec",
@@ -885,11 +1014,11 @@ async function buildCatalystProject(projectUuid) {
885
1014
  cwd: coreDir
886
1015
  }
887
1016
  );
888
- await execa(
1017
+ await execa2(
889
1018
  "pnpm",
890
1019
  [
891
1020
  "dlx",
892
- `wrangler@${WRANGLER_VERSION}`,
1021
+ `wrangler@${wranglerVersion}`,
893
1022
  "deploy",
894
1023
  "--config",
895
1024
  join4(coreDir, ".bigcommerce", "wrangler.jsonc"),
@@ -918,18 +1047,28 @@ Examples:
918
1047
  $ catalyst build
919
1048
 
920
1049
  # Include project UUID
921
- $ catalyst build --project-uuid <UUID>`
1050
+ $ catalyst build --project-uuid <UUID>
1051
+
1052
+ # Build with a specific Wrangler version
1053
+ $ catalyst build --wrangler-version 4.24.3`
922
1054
  ).addOption(
923
- new Option3(
1055
+ new Option2(
924
1056
  "--project-uuid <uuid>",
925
1057
  "Project UUID to be included in the deployment configuration."
926
1058
  ).env("CATALYST_PROJECT_UUID")
927
- ).action(async (options) => {
1059
+ ).addOption(
1060
+ new Option2(
1061
+ "--wrangler-version <version>",
1062
+ `Wrangler version or dist-tag to build with. Defaults to ${WRANGLER_VERSION}.`
1063
+ ).argParser(parseWranglerVersion)
1064
+ ).addOption(envPathOption()).action(async (options) => {
1065
+ loadBuildEnv({ envPath: options.envPath });
928
1066
  const state = getProjectState();
929
1067
  if (!state.isTransformed) {
930
1068
  consola.info("Project is not set up for Commerce Hosting \u2014 running `next build`.");
931
1069
  consola.info("To deploy to Commerce Hosting, run `catalyst deploy`.");
932
- await execa("pnpm", ["exec", "next", "build"], {
1070
+ assertRequiredBuildEnv();
1071
+ await execa2("pnpm", ["exec", "next", "build"], {
933
1072
  stdio: "inherit",
934
1073
  cwd: process.cwd()
935
1074
  });
@@ -942,21 +1081,19 @@ Examples:
942
1081
  "Project UUID is required. Please run `catalyst project create` or `catalyst project link` or this command again with --project-uuid <uuid>."
943
1082
  );
944
1083
  }
945
- await buildCatalystProject(projectUuid);
1084
+ await buildCatalystProject(projectUuid, options.wranglerVersion);
946
1085
  });
947
1086
 
948
1087
  // src/cli/commands/channel.ts
949
- import { select as select3 } from "@inquirer/prompts";
950
- import { Command as Command3, InvalidArgumentError, Option as Option4 } from "commander";
951
- import { colorize as colorize3 } from "consola/utils";
952
- import { outputFileSync } from "fs-extra/esm";
953
- import { join as join6 } from "path";
1088
+ import { select as select4 } from "@inquirer/prompts";
1089
+ import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option3 } from "commander";
1090
+ import { colorize as colorize5 } from "consola/utils";
954
1091
 
955
1092
  // src/cli/lib/channel-site-flow.ts
956
1093
  import { select as select2 } from "@inquirer/prompts";
957
1094
 
958
1095
  // src/cli/lib/channels.ts
959
- import { z as z6 } from "zod";
1096
+ import { z as z7 } from "zod";
960
1097
  var cliApiUrl = (origin, storeHash, path) => `${origin}/stores/${storeHash}/cli-api/v3${path}`;
961
1098
  var channelsUrl = (storeHash, apiHost, query2 = {}) => {
962
1099
  const params = new URLSearchParams(query2).toString();
@@ -967,14 +1104,14 @@ var authHeaders2 = (accessToken) => ({
967
1104
  "X-Correlation-Id": getTelemetry().correlationId,
968
1105
  Accept: "application/json"
969
1106
  });
970
- var envVarsSchema = z6.record(z6.string(), z6.coerce.string());
971
- var channelSchema = z6.object({
972
- id: z6.number(),
973
- name: z6.string(),
974
- platform: z6.string()
1107
+ var envVarsSchema = z7.record(z7.string(), z7.coerce.string());
1108
+ var channelSchema = z7.object({
1109
+ id: z7.number(),
1110
+ name: z7.string(),
1111
+ platform: z7.string()
975
1112
  });
976
- var channelsResponseSchema = z6.object({
977
- data: z6.array(channelSchema)
1113
+ var channelsResponseSchema = z7.object({
1114
+ data: z7.array(channelSchema)
978
1115
  });
979
1116
  var CHANNEL_PLATFORM_ORDER = ["catalyst", "next", "bigcommerce"];
980
1117
  function sortChannelsByPlatform(channels) {
@@ -990,30 +1127,30 @@ function sortChannelsByPlatform(channels) {
990
1127
  function channelPlatformLabel(platform) {
991
1128
  return platform === "bigcommerce" ? "Stencil" : platform.charAt(0).toUpperCase() + platform.slice(1);
992
1129
  }
993
- var initResponseSchema = z6.object({
994
- data: z6.object({
995
- storefront_api_token: z6.string(),
1130
+ var initResponseSchema = z7.object({
1131
+ data: z7.object({
1132
+ storefront_api_token: z7.string(),
996
1133
  envVars: envVarsSchema
997
1134
  })
998
1135
  });
999
- var createChannelResponseSchema = z6.object({
1000
- data: z6.object({
1001
- id: z6.number(),
1002
- storefront_api_token: z6.string(),
1136
+ var createChannelResponseSchema = z7.object({
1137
+ data: z7.object({
1138
+ id: z7.number(),
1139
+ storefront_api_token: z7.string(),
1003
1140
  envVars: envVarsSchema
1004
1141
  })
1005
1142
  });
1006
- var eligibilityResponseSchema = z6.object({
1007
- data: z6.object({
1008
- eligible: z6.boolean(),
1009
- message: z6.string()
1143
+ var eligibilityResponseSchema = z7.object({
1144
+ data: z7.object({
1145
+ eligible: z7.boolean(),
1146
+ message: z7.string()
1010
1147
  })
1011
1148
  });
1012
- var channelSiteSchema = z6.object({
1013
- data: z6.object({
1014
- id: z6.number(),
1015
- url: z6.string(),
1016
- channel_id: z6.number()
1149
+ var channelSiteSchema = z7.object({
1150
+ data: z7.object({
1151
+ id: z7.number(),
1152
+ url: z7.string(),
1153
+ channel_id: z7.number()
1017
1154
  })
1018
1155
  });
1019
1156
  async function getChannelInit(channelId, storeHash, accessToken, origin) {
@@ -1022,9 +1159,7 @@ async function getChannelInit(channelId, storeHash, accessToken, origin) {
1022
1159
  headers: authHeaders2(accessToken)
1023
1160
  });
1024
1161
  if (!response.ok) {
1025
- throw new Error(
1026
- `GET /channels/${channelId}/init failed: ${response.status} ${response.statusText}`
1027
- );
1162
+ throw await httpError(response, "Failed to initialize channel");
1028
1163
  }
1029
1164
  const { data } = initResponseSchema.parse(await response.json());
1030
1165
  return { storefrontToken: data.storefront_api_token, envVars: data.envVars };
@@ -1035,9 +1170,7 @@ async function checkChannelEligibility(storeHash, accessToken, origin) {
1035
1170
  headers: authHeaders2(accessToken)
1036
1171
  });
1037
1172
  if (!response.ok) {
1038
- throw new Error(
1039
- `GET /channels/catalyst/eligibility failed: ${response.status} ${response.statusText}`
1040
- );
1173
+ throw await httpError(response, "Failed to check Catalyst eligibility");
1041
1174
  }
1042
1175
  return eligibilityResponseSchema.parse(await response.json()).data;
1043
1176
  }
@@ -1055,7 +1188,7 @@ async function createChannel(name, storefrontLocale, additionalLocales, installS
1055
1188
  })
1056
1189
  });
1057
1190
  if (!response.ok) {
1058
- throw new Error(`POST /channels/catalyst failed: ${response.status} ${response.statusText}`);
1191
+ throw await httpError(response, "Failed to create channel");
1059
1192
  }
1060
1193
  const { data } = createChannelResponseSchema.parse(await response.json());
1061
1194
  return {
@@ -1078,7 +1211,7 @@ async function fetchAvailableChannels(storeHash, accessToken, apiHost) {
1078
1211
  );
1079
1212
  assertAuthorized(response);
1080
1213
  if (!response.ok) {
1081
- throw new Error(`GET /v3/channels failed: ${response.status} ${response.statusText}`);
1214
+ throw await httpError(response, "Failed to fetch channels");
1082
1215
  }
1083
1216
  return channelsResponseSchema.parse(await response.json()).data;
1084
1217
  }
@@ -1097,12 +1230,12 @@ async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken,
1097
1230
  }
1098
1231
  );
1099
1232
  if (response.status === 401 || response.status === 403) {
1100
- throw new Error(
1233
+ throw new UserActionableError(
1101
1234
  `Failed to update channel site (${response.status}). Re-run \`catalyst auth login\` to refresh your access token with the store_channel_settings scope.`
1102
1235
  );
1103
1236
  }
1104
1237
  if (!response.ok) {
1105
- throw new Error(`Failed to update channel site: ${response.status} ${response.statusText}`);
1238
+ throw await httpError(response, "Failed to update channel site");
1106
1239
  }
1107
1240
  const res = await response.json();
1108
1241
  const { data } = channelSiteSchema.parse(res);
@@ -1111,10 +1244,10 @@ async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken,
1111
1244
 
1112
1245
  // src/cli/lib/commerce-hosting.ts
1113
1246
  import { confirm as confirm2, input as input2, select, Separator } from "@inquirer/prompts";
1114
- import { colorize as colorize2 } from "consola/utils";
1115
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
1247
+ import { colorize as colorize3 } from "consola/utils";
1248
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
1116
1249
  import { dirname as dirname2, join as join5 } from "path";
1117
- import { z as z7 } from "zod";
1250
+ import { z as z8 } from "zod";
1118
1251
 
1119
1252
  // src/cli/lib/sort-package-json.ts
1120
1253
  var FIELD_ORDER = [
@@ -1144,8 +1277,8 @@ function sortPackageJsonFields(pkg) {
1144
1277
 
1145
1278
  // src/cli/lib/commerce-hosting.ts
1146
1279
  var OPENNEXT_CLOUDFLARE_VERSION = "1.17.3";
1147
- var corePackageJsonSchema = z7.looseObject({
1148
- dependencies: z7.record(z7.string(), z7.string()).optional()
1280
+ var corePackageJsonSchema = z8.looseObject({
1281
+ dependencies: z8.record(z8.string(), z8.string()).optional()
1149
1282
  });
1150
1283
  var writeJson = (path, value) => {
1151
1284
  mkdirSync(dirname2(path), { recursive: true });
@@ -1155,14 +1288,14 @@ var writeJson = (path, value) => {
1155
1288
  var convertProxyToMiddleware = (projectDir) => {
1156
1289
  const proxyPath = join5(projectDir, "proxy.ts");
1157
1290
  const middlewarePath = join5(projectDir, "middleware.ts");
1158
- if (!existsSync2(proxyPath)) return;
1291
+ if (!existsSync3(proxyPath)) return;
1159
1292
  const contents = readFileSync2(proxyPath, "utf-8").replace("export const proxy", "export const middleware").replace("export const config = {", "export const config = {\n runtime: 'experimental-edge',");
1160
1293
  writeFileSync(middlewarePath, contents);
1161
1294
  unlinkSync(proxyPath);
1162
1295
  };
1163
1296
  var cleanupCloudflareIncompatibilities = async (projectDir) => {
1164
1297
  const instrumentationPath = join5(projectDir, "instrumentation.ts");
1165
- if (!existsSync2(instrumentationPath)) return;
1298
+ if (!existsSync3(instrumentationPath)) return;
1166
1299
  if (!process.stdin.isTTY) {
1167
1300
  consola.warn(
1168
1301
  "instrumentation.ts is present and may be incompatible with the Cloudflare Workers bundle (the default @vercel/otel scaffolding throws at cold start under workerd). Skipping automatic cleanup in non-interactive mode \u2014 re-run interactively to remove it, or delete/gate it manually."
@@ -1182,7 +1315,7 @@ var cleanupCloudflareIncompatibilities = async (projectDir) => {
1182
1315
  unlinkSync(instrumentationPath);
1183
1316
  consola.info("Removed instrumentation.ts (incompatible with Cloudflare Workers).");
1184
1317
  const corePackageJsonPath = join5(projectDir, "package.json");
1185
- if (existsSync2(corePackageJsonPath)) {
1318
+ if (existsSync3(corePackageJsonPath)) {
1186
1319
  const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
1187
1320
  if (pkg.dependencies && "@vercel/otel" in pkg.dependencies) {
1188
1321
  const { "@vercel/otel": _removedVercelOtel, ...preservedDeps } = pkg.dependencies;
@@ -1243,7 +1376,7 @@ async function selectOrCreateInfrastructureProject(api, linkedProjectUuid) {
1243
1376
  }
1244
1377
  const choices = [
1245
1378
  ...existingProjects.map((p) => ({
1246
- name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize2("green", "[linked]")}` : p.name,
1379
+ name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize3("green", "[linked]")}` : p.name,
1247
1380
  value: p.uuid,
1248
1381
  description: p.uuid
1249
1382
  })),
@@ -1340,7 +1473,7 @@ async function promptAndCreateCommerceHostingProject(api, takenNames, defaultNam
1340
1473
  },
1341
1474
  theme: {
1342
1475
  style: {
1343
- help: () => colorize2(
1476
+ help: () => colorize3(
1344
1477
  "dim",
1345
1478
  "(The project that hosts your storefront on Commerce \u2014 often matches your folder name.)"
1346
1479
  )
@@ -1486,17 +1619,149 @@ async function runChannelSiteUrlFlow(options) {
1486
1619
  consola.success(`Updated channel ${channelLabel} site URL to ${siteUrl}.`);
1487
1620
  }
1488
1621
 
1622
+ // src/cli/lib/create-channel-flow.ts
1623
+ import { checkbox, input as input3, select as select3 } from "@inquirer/prompts";
1624
+ import { colorize as colorize4 } from "consola/utils";
1625
+
1626
+ // src/cli/lib/localization.ts
1627
+ import { z as z9 } from "zod";
1628
+ var allowedLocales = [
1629
+ "en",
1630
+ "da",
1631
+ "es-AR",
1632
+ "es-CL",
1633
+ "es-CO",
1634
+ "es-MX",
1635
+ "es-PE",
1636
+ "es-419",
1637
+ "es",
1638
+ "it",
1639
+ "nl",
1640
+ "pl",
1641
+ "pt",
1642
+ "de",
1643
+ "fr",
1644
+ "ja",
1645
+ "no",
1646
+ "pt-BR",
1647
+ "sv"
1648
+ ];
1649
+ var AvailableLocalesSuccessSchema = z9.object({
1650
+ data: z9.array(
1651
+ z9.object({
1652
+ id: z9.string(),
1653
+ name: z9.string(),
1654
+ fallback: z9.string().nullable(),
1655
+ is_supported: z9.boolean()
1656
+ })
1657
+ )
1658
+ });
1659
+ var getAvailableLocales = async (storeHash, accessToken, apiHost) => {
1660
+ const response = await fetch(
1661
+ `https://${apiHost}/stores/${storeHash}/v3/settings/store/available-locales`,
1662
+ {
1663
+ method: "GET",
1664
+ headers: {
1665
+ "X-Auth-Token": accessToken,
1666
+ "X-Correlation-Id": getTelemetry().correlationId,
1667
+ Accept: "application/json"
1668
+ }
1669
+ }
1670
+ );
1671
+ assertAuthorized(response);
1672
+ if (!response.ok) {
1673
+ throw await httpError(response, "Failed to fetch available locales");
1674
+ }
1675
+ return AvailableLocalesSuccessSchema.parse(await response.json()).data.filter(({ id }) => allowedLocales.includes(id)).map(({ name, id }) => ({ name: `${name} (${id})`, value: id }));
1676
+ };
1677
+
1678
+ // src/cli/lib/create-channel-flow.ts
1679
+ var ALLOWED_CHANNEL_NAME_CHARS = "letters, numbers, spaces, hyphens (-), and underscores (_)";
1680
+ var CHANNEL_NAME_PATTERN = /^[\p{L}\p{N} _-]+$/u;
1681
+ function getChannelNameError(name) {
1682
+ if (name.trim().length === 0) {
1683
+ return "Channel name cannot be empty.";
1684
+ }
1685
+ if (!CHANNEL_NAME_PATTERN.test(name)) {
1686
+ return `"${name}" is not a valid channel name. Channel names may contain only ${ALLOWED_CHANNEL_NAME_CHARS}.`;
1687
+ }
1688
+ return void 0;
1689
+ }
1690
+ async function runCreateChannelFlow(options) {
1691
+ const { storeHash, accessToken, apiHost, cliApiOrigin } = options;
1692
+ if (options.name !== void 0) {
1693
+ const nameError = getChannelNameError(options.name);
1694
+ if (nameError) {
1695
+ throw new UserActionableError(nameError);
1696
+ }
1697
+ }
1698
+ const name = options.name ?? await input3({
1699
+ message: "What would you like to name your new channel?",
1700
+ validate: (value) => getChannelNameError(value) ?? true
1701
+ });
1702
+ let availableLocales;
1703
+ const loadLocales = async () => {
1704
+ availableLocales ?? (availableLocales = await getAvailableLocales(storeHash, accessToken, apiHost));
1705
+ return availableLocales;
1706
+ };
1707
+ const storefrontLocale = options.locale ?? await select3({
1708
+ message: "Which default language would you like to set for your channel?",
1709
+ default: "en",
1710
+ choices: await loadLocales(),
1711
+ theme: {
1712
+ style: {
1713
+ help: () => colorize4("dim", "(Select locale from the list or start typing the name)")
1714
+ }
1715
+ }
1716
+ });
1717
+ let additionalLocales = options.additionalLocales;
1718
+ if (additionalLocales === void 0) {
1719
+ const shouldAddAdditionalLocales = await select3({
1720
+ message: "Would you like to add additional languages?",
1721
+ choices: [
1722
+ { name: "Yes", value: true },
1723
+ { name: "No", value: false }
1724
+ ]
1725
+ });
1726
+ additionalLocales = [];
1727
+ if (shouldAddAdditionalLocales) {
1728
+ const localeChoices = (await loadLocales()).filter(({ value }) => value !== storefrontLocale).map(({ name: localeName, value }) => ({ name: localeName, value, description: value }));
1729
+ additionalLocales = await checkbox({
1730
+ message: "Which additional languages would you like to add to your channel?",
1731
+ choices: localeChoices,
1732
+ validate: (items) => items.length <= 4 || "You can only select up to 4 additional languages."
1733
+ });
1734
+ }
1735
+ }
1736
+ const shouldInstallSampleData = options.sampleData ?? await select3({
1737
+ message: "Would you like to install sample data?",
1738
+ choices: [
1739
+ { name: "Yes", value: true },
1740
+ { name: "No", value: false }
1741
+ ]
1742
+ });
1743
+ return createChannel(
1744
+ name,
1745
+ storefrontLocale,
1746
+ additionalLocales,
1747
+ shouldInstallSampleData,
1748
+ storeHash,
1749
+ accessToken,
1750
+ cliApiOrigin
1751
+ );
1752
+ }
1753
+
1489
1754
  // src/cli/lib/env-config.ts
1490
1755
  var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
1491
- var parseEnvAssignment = (input5) => {
1492
- const separatorIndex = input5.indexOf("=");
1756
+ var parseEnvAssignment = (input6) => {
1757
+ const separatorIndex = input6.indexOf("=");
1493
1758
  if (separatorIndex === -1) {
1494
- throw new Error(`Invalid env var format: ${input5}. Expected format: KEY=VALUE`);
1759
+ throw new Error(`Invalid env var format: ${input6}. Expected format: KEY=VALUE`);
1495
1760
  }
1496
- const key = input5.slice(0, separatorIndex).trim();
1497
- const value = input5.slice(separatorIndex + 1).trim();
1761
+ const key = input6.slice(0, separatorIndex).trim();
1762
+ const value = input6.slice(separatorIndex + 1).trim();
1498
1763
  if (!key || !value) {
1499
- throw new Error(`Invalid env var format: ${input5}. Expected format: KEY=VALUE`);
1764
+ throw new Error(`Invalid env var format: ${input6}. Expected format: KEY=VALUE`);
1500
1765
  }
1501
1766
  if (!ENV_KEY_PATTERN.test(key)) {
1502
1767
  throw new Error(
@@ -1533,22 +1798,89 @@ function resolveCredentials(options, config2) {
1533
1798
  return { storeHash, accessToken };
1534
1799
  }
1535
1800
 
1801
+ // src/cli/lib/write-env.ts
1802
+ import { outputFileSync } from "fs-extra/esm";
1803
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1804
+ import { join as join6 } from "path";
1805
+ var ACTIVE_KEY_LINE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
1806
+ var keyFromLine = (line) => {
1807
+ const match = ACTIVE_KEY_LINE.exec(line);
1808
+ return match ? match[1] : null;
1809
+ };
1810
+ var parseEnvValues = (contents) => {
1811
+ const values = /* @__PURE__ */ new Map();
1812
+ contents.split("\n").forEach((line) => {
1813
+ const match = ACTIVE_KEY_LINE.exec(line);
1814
+ if (match) {
1815
+ values.set(match[1], match[2]);
1816
+ }
1817
+ });
1818
+ return values;
1819
+ };
1820
+ var writeEnv = (projectDir, envVars) => {
1821
+ const examplePath = join6(projectDir, ".env.example");
1822
+ const localPath = join6(projectDir, ".env.local");
1823
+ const existingLocal = existsSync4(localPath) ? parseEnvValues(readFileSync3(localPath, "utf-8")) : /* @__PURE__ */ new Map();
1824
+ const resolve2 = (key) => {
1825
+ if (key in envVars && envVars[key] !== "") {
1826
+ return envVars[key];
1827
+ }
1828
+ return existingLocal.get(key);
1829
+ };
1830
+ if (!existsSync4(examplePath)) {
1831
+ const keys = [.../* @__PURE__ */ new Set([...existingLocal.keys(), ...Object.keys(envVars)])];
1832
+ outputFileSync(localPath, `${keys.map((key) => `${key}=${resolve2(key) ?? ""}`).join("\n")}
1833
+ `);
1834
+ return;
1835
+ }
1836
+ const exampleLines = readFileSync3(examplePath, "utf-8").replace(/\n$/, "").split("\n");
1837
+ const exampleKeys = /* @__PURE__ */ new Set();
1838
+ const lines = exampleLines.map((line) => {
1839
+ const key = keyFromLine(line);
1840
+ if (key === null) {
1841
+ return line;
1842
+ }
1843
+ exampleKeys.add(key);
1844
+ const value = resolve2(key);
1845
+ return value === void 0 ? line : `${key}=${value}`;
1846
+ });
1847
+ const extraKeys = [];
1848
+ [...existingLocal.keys()].forEach((key) => {
1849
+ if (!exampleKeys.has(key)) {
1850
+ extraKeys.push(key);
1851
+ }
1852
+ });
1853
+ Object.keys(envVars).forEach((key) => {
1854
+ if (!exampleKeys.has(key) && !extraKeys.includes(key)) {
1855
+ extraKeys.push(key);
1856
+ }
1857
+ });
1858
+ if (extraKeys.length > 0) {
1859
+ lines.push("", "# Additional variables set by the Catalyst CLI (not in .env.example).");
1860
+ extraKeys.forEach((key) => {
1861
+ lines.push(`${key}=${resolve2(key) ?? ""}`);
1862
+ });
1863
+ }
1864
+ outputFileSync(localPath, `${lines.join("\n")}
1865
+ `);
1866
+ };
1867
+
1536
1868
  // src/cli/commands/channel.ts
1537
1869
  var parseChannelId = (value) => {
1538
1870
  const parsed = Number.parseInt(value, 10);
1539
1871
  if (Number.isNaN(parsed)) {
1540
- throw new InvalidArgumentError(`"${value}" is not a valid channel ID (expected a number).`);
1872
+ throw new InvalidArgumentError2(`"${value}" is not a valid channel ID (expected a number).`);
1541
1873
  }
1542
1874
  return parsed;
1543
1875
  };
1544
- async function resolveCredentialsWithLogin(options, config2) {
1876
+ async function resolveCredentialsWithLogin(options, config2, apiHost) {
1545
1877
  const storeHash = options.storeHash ?? config2.get("storeHash");
1546
1878
  const accessToken = options.accessToken ?? config2.get("accessToken");
1547
1879
  if (storeHash && accessToken) {
1548
1880
  return { storeHash, accessToken };
1549
1881
  }
1550
1882
  try {
1551
- const credentials = await login(options.loginUrl, options.apiHost);
1883
+ const credentials = await login(options.loginUrl, apiHost);
1552
1884
  config2.set("storeHash", credentials.storeHash);
1553
1885
  config2.set("accessToken", credentials.accessToken);
1554
1886
  return credentials;
@@ -1571,24 +1903,25 @@ Examples:
1571
1903
  # Skip both prompts
1572
1904
  $ catalyst channel update --channel-id 123 --hostname my-storefront.example.com`
1573
1905
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
1574
- new Option4(
1906
+ new Option3(
1575
1907
  "--channel-id <id>",
1576
1908
  "Skip the channel prompt and target this channel directly."
1577
1909
  ).argParser((value) => Number(value))
1578
1910
  ).addOption(
1579
- new Option4(
1911
+ new Option3(
1580
1912
  "--hostname <hostname>",
1581
1913
  "Skip the hostname prompt and use this hostname directly. Must be one of the project's deployment_hostnames."
1582
1914
  )
1583
1915
  ).action(async (options) => {
1584
1916
  const config2 = getProjectConfig();
1917
+ const apiHost = resolveApiHost(options, config2);
1585
1918
  const { storeHash, accessToken } = resolveCredentials(options, config2);
1586
1919
  await getTelemetry().identify(storeHash);
1587
1920
  try {
1588
1921
  await runChannelSiteUrlFlow({
1589
1922
  storeHash,
1590
1923
  accessToken,
1591
- apiHost: options.apiHost,
1924
+ apiHost,
1592
1925
  projectUuid: options.projectUuid ?? config2.get("projectUuid"),
1593
1926
  channelId: options.channelId,
1594
1927
  hostname: options.hostname
@@ -1620,17 +1953,18 @@ Examples:
1620
1953
  # Append extra environment variables to .env.local
1621
1954
  $ catalyst channel link --channel-id 123 --env MY_FLAG=1`
1622
1955
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
1623
- new Option4("--channel-id <id>", "Link this channel directly, skipping the picker.").argParser(
1956
+ new Option3("--channel-id <id>", "Link this channel directly, skipping the picker.").argParser(
1624
1957
  parseChannelId
1625
1958
  )
1626
1959
  ).option(
1627
1960
  "--env <vars...>",
1628
1961
  "Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable)."
1629
1962
  ).addOption(
1630
- new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
1963
+ new Option3("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
1631
1964
  ).action(async (options) => {
1632
1965
  const config2 = getProjectConfig();
1633
- const credentials = await resolveCredentialsWithLogin(options, config2);
1966
+ const apiHost = resolveApiHost(options, config2);
1967
+ const credentials = await resolveCredentialsWithLogin(options, config2, apiHost);
1634
1968
  if (!credentials) {
1635
1969
  consola.info(
1636
1970
  "Login aborted. Re-run `catalyst channel link` when you have your credentials ready."
@@ -1643,7 +1977,7 @@ Examples:
1643
1977
  let channelId = options.channelId;
1644
1978
  let channelName;
1645
1979
  if (channelId === void 0) {
1646
- const channels = await fetchAvailableChannels(storeHash, accessToken, options.apiHost);
1980
+ const channels = await fetchAvailableChannels(storeHash, accessToken, apiHost);
1647
1981
  if (channels.length === 0) {
1648
1982
  consola.info(
1649
1983
  "No storefront channels found on this store. Create one with `catalyst create` and try again."
@@ -1651,7 +1985,7 @@ Examples:
1651
1985
  process.exit(0);
1652
1986
  return;
1653
1987
  }
1654
- channelId = await select3({
1988
+ channelId = await select4({
1655
1989
  message: "Which channel would you like to link?",
1656
1990
  choices: sortChannelsByPlatform(channels).map((c) => ({
1657
1991
  name: c.name,
@@ -1669,32 +2003,108 @@ Examples:
1669
2003
  envVars[key] = value;
1670
2004
  });
1671
2005
  }
1672
- outputFileSync(
1673
- join6(process.cwd(), ".env.local"),
1674
- `${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
1675
- `
1676
- );
2006
+ writeEnv(process.cwd(), envVars);
1677
2007
  const label = channelName ? `"${channelName}" (${channelId})` : `${channelId}`;
1678
2008
  consola.success(
1679
- `Linked to channel ${label} and wrote ${colorize3("cyanBright", ".env.local")}.`
2009
+ `Linked to channel ${label} and wrote ${colorize5("cyanBright", ".env.local")}.`
1680
2010
  );
1681
2011
  consola.log(`Next steps:
1682
2012
 
1683
- ${colorize3("yellow", "pnpm run dev")}`);
2013
+ ${colorize5("yellow", "pnpm run dev")}`);
2014
+ process.exit(0);
2015
+ });
2016
+ var create = new Command3("create").configureHelp({ showGlobalOptions: true }).description("Create a new Catalyst storefront channel on your BigCommerce store.").addHelpText(
2017
+ "after",
2018
+ `
2019
+ Examples:
2020
+ # Create a channel interactively (logs you in if needed)
2021
+ $ catalyst channel create
2022
+
2023
+ # Non-interactive, and link it to this project afterwards
2024
+ $ catalyst channel create --name "My Store" --locale en --no-sample-data --link
2025
+
2026
+ # Add additional storefront languages (max 4)
2027
+ $ catalyst channel create --name "My Store" --additional-locales es fr`
2028
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).option("--name <name>", "Name for the new channel. Skips the name prompt.").option(
2029
+ "--locale <locale>",
2030
+ 'Default storefront locale (e.g. "en"). Skips the default-language prompt.'
2031
+ ).option(
2032
+ "--additional-locales <locales...>",
2033
+ "Additional storefront locales, max 4 (e.g. --additional-locales es fr). Skips the additional-languages prompt."
2034
+ ).addOption(new Option3("--sample-data", "Install sample data on the new channel.")).addOption(new Option3("--no-sample-data", "Create the channel without sample data.")).option(
2035
+ "--link",
2036
+ "Link the new channel to this project \u2014 write its credentials to .env.local without prompting."
2037
+ ).addOption(
2038
+ new Option3("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
2039
+ ).action(async (options) => {
2040
+ if (options.additionalLocales && options.additionalLocales.length > 4) {
2041
+ consola.error("You can only set up to 4 additional locales.");
2042
+ process.exit(1);
2043
+ return;
2044
+ }
2045
+ const config2 = getProjectConfig();
2046
+ const apiHost = resolveApiHost(options, config2);
2047
+ const credentials = await resolveCredentialsWithLogin(options, config2, apiHost);
2048
+ if (!credentials) {
2049
+ consola.info(
2050
+ "Login aborted. Re-run `catalyst channel create` when you have your credentials ready."
2051
+ );
2052
+ process.exit(0);
2053
+ return;
2054
+ }
2055
+ const { storeHash, accessToken } = credentials;
2056
+ await getTelemetry().identify(storeHash);
2057
+ const eligibility = await checkChannelEligibility(storeHash, accessToken, options.cliApiOrigin);
2058
+ if (!eligibility.eligible) {
2059
+ consola.warn(eligibility.message);
2060
+ process.exit(0);
2061
+ return;
2062
+ }
2063
+ const channelData = await runCreateChannelFlow({
2064
+ storeHash,
2065
+ accessToken,
2066
+ apiHost,
2067
+ cliApiOrigin: options.cliApiOrigin,
2068
+ name: options.name,
2069
+ locale: options.locale,
2070
+ additionalLocales: options.additionalLocales,
2071
+ sampleData: options.sampleData
2072
+ });
2073
+ consola.success(`Created channel ${channelData.channelId}.`);
2074
+ consola.warn(
2075
+ "A preview storefront has been deployed in your BigCommerce control panel. This preview may look different from your local environment as it may be running different code. Additionally, it may take a few minutes for the channel storefront to be accessible."
2076
+ );
2077
+ const shouldLink = options.link ? true : await select4({
2078
+ message: "Would you like to link this channel to your project?",
2079
+ choices: [
2080
+ { name: "Yes", value: true },
2081
+ { name: "No", value: false }
2082
+ ]
2083
+ });
2084
+ if (shouldLink) {
2085
+ writeEnv(process.cwd(), channelData.envVars);
2086
+ consola.success(
2087
+ `Linked to channel ${channelData.channelId} and wrote ${colorize5("cyanBright", ".env.local")}.`
2088
+ );
2089
+ consola.log(`Next steps:
2090
+
2091
+ ${colorize5("yellow", "pnpm run dev")}`);
2092
+ }
1684
2093
  process.exit(0);
1685
2094
  });
1686
- var channel = new Command3("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(link).addCommand(update);
2095
+ var channel = new Command3("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(create).addCommand(link).addCommand(update);
1687
2096
 
1688
2097
  // src/cli/commands/create.ts
1689
- import { Command as Command4, InvalidArgumentError as InvalidArgumentError2, Option as Option5 } from "@commander-js/extra-typings";
1690
- import { checkbox, input as input3, select as select4 } from "@inquirer/prompts";
2098
+ import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as Option4 } from "@commander-js/extra-typings";
2099
+ import { input as input4, select as select5 } from "@inquirer/prompts";
1691
2100
  import { execSync as execSync2 } from "child_process";
1692
- import { colorize as colorize4 } from "consola/utils";
2101
+ import { colorize as colorize6 } from "consola/utils";
1693
2102
  import { pathExistsSync } from "fs-extra/esm";
1694
2103
  import kebabCase from "lodash.kebabcase";
1695
- import { join as join11 } from "path";
2104
+ import { join as join10 } from "path";
1696
2105
 
1697
2106
  // src/cli/lib/detect-package-manager.ts
2107
+ import { detectPackageManager as detectFromDir } from "nypm";
1698
2108
  var detectPackageManager = () => {
1699
2109
  const userAgent = process.env.npm_config_user_agent ?? "";
1700
2110
  const name = userAgent.split(" ")[0]?.split("/")[0];
@@ -1706,6 +2116,14 @@ var detectPackageManager = () => {
1706
2116
  }
1707
2117
  return "npm";
1708
2118
  };
2119
+ var detectProjectPackageManager = async (projectDir) => {
2120
+ const detected = await detectFromDir(projectDir);
2121
+ const name = detected?.name;
2122
+ if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") {
2123
+ return name;
2124
+ }
2125
+ return "npm";
2126
+ };
1709
2127
 
1710
2128
  // src/cli/lib/extract-catalyst.ts
1711
2129
  import { mkdirSync as mkdirSync2 } from "fs";
@@ -1779,12 +2197,32 @@ var initGitRepo = (projectDir) => {
1779
2197
  // src/cli/lib/install-dependencies.ts
1780
2198
  import { installDependencies as installDeps } from "nypm";
1781
2199
  import yoctoSpinner2 from "yocto-spinner";
2200
+ var restoreEnv = (key, previous) => {
2201
+ if (previous === void 0) {
2202
+ Reflect.deleteProperty(process.env, key);
2203
+ } else {
2204
+ process.env[key] = previous;
2205
+ }
2206
+ };
2207
+ var withQuietInstallEnv = async (run2) => {
2208
+ var _a, _b;
2209
+ const prevNodeNoWarnings = process.env.NODE_NO_WARNINGS;
2210
+ const prevDownloadPrompt = process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT;
2211
+ (_a = process.env).NODE_NO_WARNINGS ?? (_a.NODE_NO_WARNINGS = "1");
2212
+ (_b = process.env).COREPACK_ENABLE_DOWNLOAD_PROMPT ?? (_b.COREPACK_ENABLE_DOWNLOAD_PROMPT = "0");
2213
+ try {
2214
+ return await run2();
2215
+ } finally {
2216
+ restoreEnv("NODE_NO_WARNINGS", prevNodeNoWarnings);
2217
+ restoreEnv("COREPACK_ENABLE_DOWNLOAD_PROMPT", prevDownloadPrompt);
2218
+ }
2219
+ };
1782
2220
  var installDependencies = async (projectDir, packageManager = "pnpm") => {
1783
2221
  const spinner = yoctoSpinner2().start(
1784
2222
  `Installing dependencies with ${packageManager}. This could take a minute...`
1785
2223
  );
1786
2224
  try {
1787
- await installDeps({ cwd: projectDir, silent: true, packageManager });
2225
+ await withQuietInstallEnv(() => installDeps({ cwd: projectDir, silent: true, packageManager }));
1788
2226
  spinner.success("Dependencies installed successfully.");
1789
2227
  } catch (error) {
1790
2228
  const message = error instanceof Error ? error.message : "unknown error";
@@ -1793,64 +2231,10 @@ var installDependencies = async (projectDir, packageManager = "pnpm") => {
1793
2231
  }
1794
2232
  };
1795
2233
 
1796
- // src/cli/lib/localization.ts
1797
- import { z as z8 } from "zod";
1798
- var allowedLocales = [
1799
- "en",
1800
- "da",
1801
- "es-AR",
1802
- "es-CL",
1803
- "es-CO",
1804
- "es-MX",
1805
- "es-PE",
1806
- "es-419",
1807
- "es",
1808
- "it",
1809
- "nl",
1810
- "pl",
1811
- "pt",
1812
- "de",
1813
- "fr",
1814
- "ja",
1815
- "no",
1816
- "pt-BR",
1817
- "sv"
1818
- ];
1819
- var AvailableLocalesSuccessSchema = z8.object({
1820
- data: z8.array(
1821
- z8.object({
1822
- id: z8.string(),
1823
- name: z8.string(),
1824
- fallback: z8.string().nullable(),
1825
- is_supported: z8.boolean()
1826
- })
1827
- )
1828
- });
1829
- var getAvailableLocales = async (storeHash, accessToken, apiHost) => {
1830
- const response = await fetch(
1831
- `https://${apiHost}/stores/${storeHash}/v3/settings/store/available-locales`,
1832
- {
1833
- method: "GET",
1834
- headers: {
1835
- "X-Auth-Token": accessToken,
1836
- "X-Correlation-Id": getTelemetry().correlationId,
1837
- Accept: "application/json"
1838
- }
1839
- }
1840
- );
1841
- assertAuthorized(response);
1842
- if (!response.ok) {
1843
- throw new Error(
1844
- `GET /v3/settings/store/available-locales failed: ${response.status} ${response.statusText}`
1845
- );
1846
- }
1847
- return AvailableLocalesSuccessSchema.parse(await response.json()).data.filter(({ id }) => allowedLocales.includes(id)).map(({ name, id }) => ({ name: `${name} (${id})`, value: id }));
1848
- };
1849
-
1850
2234
  // src/cli/lib/rewrite-core-package.ts
1851
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
2235
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
1852
2236
  import { join as join8 } from "path";
1853
- import { z as z9 } from "zod";
2237
+ import { z as z10 } from "zod";
1854
2238
  var NATIVE_BUILD_ALLOWLIST = [
1855
2239
  "@parcel/watcher",
1856
2240
  "@swc/core",
@@ -1868,18 +2252,18 @@ var DEP_FIELDS = [
1868
2252
  "optionalDependencies",
1869
2253
  "peerDependencies"
1870
2254
  ];
1871
- var corePackageJsonSchema2 = z9.looseObject({
1872
- version: z9.string().optional(),
1873
- private: z9.boolean().optional(),
1874
- scripts: z9.record(z9.string(), z9.string()).optional(),
1875
- dependencies: z9.record(z9.string(), z9.string()).optional(),
1876
- devDependencies: z9.record(z9.string(), z9.string()).optional(),
1877
- optionalDependencies: z9.record(z9.string(), z9.string()).optional(),
1878
- peerDependencies: z9.record(z9.string(), z9.string()).optional(),
1879
- pnpm: z9.looseObject({}).optional(),
1880
- catalyst: z9.looseObject({}).optional()
2255
+ var corePackageJsonSchema2 = z10.looseObject({
2256
+ version: z10.string().optional(),
2257
+ private: z10.boolean().optional(),
2258
+ scripts: z10.record(z10.string(), z10.string()).optional(),
2259
+ dependencies: z10.record(z10.string(), z10.string()).optional(),
2260
+ devDependencies: z10.record(z10.string(), z10.string()).optional(),
2261
+ optionalDependencies: z10.record(z10.string(), z10.string()).optional(),
2262
+ peerDependencies: z10.record(z10.string(), z10.string()).optional(),
2263
+ pnpm: z10.looseObject({}).optional(),
2264
+ catalyst: z10.looseObject({}).optional()
1881
2265
  });
1882
- var registryVersionSchema = z9.object({ version: z9.string() });
2266
+ var registryVersionSchema = z10.object({ version: z10.string() });
1883
2267
  var resolvePublishedVersion = async (pkgName) => {
1884
2268
  const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`);
1885
2269
  if (!response.ok) {
@@ -1892,7 +2276,7 @@ var resolvePublishedVersion = async (pkgName) => {
1892
2276
  };
1893
2277
  var rewriteCorePackage = async (projectDir, ref) => {
1894
2278
  const packageJsonPath = join8(projectDir, "package.json");
1895
- const pkg = corePackageJsonSchema2.parse(JSON.parse(readFileSync3(packageJsonPath, "utf-8")));
2279
+ const pkg = corePackageJsonSchema2.parse(JSON.parse(readFileSync4(packageJsonPath, "utf-8")));
1896
2280
  const workspaceDeps = DEP_FIELDS.flatMap((field) => {
1897
2281
  const deps = pkg[field];
1898
2282
  if (!deps) return [];
@@ -1920,12 +2304,12 @@ var rewriteCorePackage = async (projectDir, ref) => {
1920
2304
  };
1921
2305
 
1922
2306
  // src/cli/lib/setup-core-project.ts
1923
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2307
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1924
2308
  import { dirname as dirname3, join as join9 } from "path";
1925
- import { z as z10 } from "zod";
1926
- var corePackageJsonSchema3 = z10.looseObject({
1927
- scripts: z10.record(z10.string(), z10.string()).optional(),
1928
- dependencies: z10.record(z10.string(), z10.string()).optional()
2309
+ import { z as z11 } from "zod";
2310
+ var corePackageJsonSchema3 = z11.looseObject({
2311
+ scripts: z11.record(z11.string(), z11.string()).optional(),
2312
+ devDependencies: z11.record(z11.string(), z11.string()).optional()
1929
2313
  });
1930
2314
  var writeJson2 = (path, value) => {
1931
2315
  mkdirSync3(dirname3(path), { recursive: true });
@@ -1934,31 +2318,20 @@ var writeJson2 = (path, value) => {
1934
2318
  };
1935
2319
  var setupCoreProject = (projectDir) => {
1936
2320
  const corePackageJsonPath = join9(projectDir, "package.json");
1937
- const pkg = corePackageJsonSchema3.parse(JSON.parse(readFileSync4(corePackageJsonPath, "utf-8")));
2321
+ const pkg = corePackageJsonSchema3.parse(JSON.parse(readFileSync5(corePackageJsonPath, "utf-8")));
1938
2322
  pkg.scripts = {
1939
2323
  ...pkg.scripts,
1940
2324
  build: "npm run generate && catalyst build",
1941
2325
  start: "catalyst start",
1942
2326
  deploy: "npm run generate && catalyst deploy"
1943
2327
  };
1944
- pkg.dependencies = {
1945
- ...pkg.dependencies,
2328
+ pkg.devDependencies = {
2329
+ ...pkg.devDependencies,
1946
2330
  "@bigcommerce/catalyst": package_default.version
1947
2331
  };
1948
2332
  writeJson2(corePackageJsonPath, sortPackageJsonFields(pkg));
1949
2333
  };
1950
2334
 
1951
- // src/cli/lib/write-env.ts
1952
- import { outputFileSync as outputFileSync2 } from "fs-extra/esm";
1953
- import { join as join10 } from "path";
1954
- var writeEnv = (projectDir, envVars) => {
1955
- outputFileSync2(
1956
- join10(projectDir, ".env.local"),
1957
- `${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
1958
- `
1959
- );
1960
- };
1961
-
1962
2335
  // src/cli/commands/create.ts
1963
2336
  function getPlatformCheckCommand(command) {
1964
2337
  const isWindows = process.platform === "win32";
@@ -1967,73 +2340,25 @@ function getPlatformCheckCommand(command) {
1967
2340
  function parseChannelId2(value) {
1968
2341
  const parsed = parseInt(value, 10);
1969
2342
  if (Number.isNaN(parsed)) {
1970
- throw new InvalidArgumentError2(`"${value}" is not a valid channel ID (expected a number).`);
2343
+ throw new InvalidArgumentError3(`"${value}" is not a valid channel ID (expected a number).`);
1971
2344
  }
1972
2345
  return parsed;
1973
2346
  }
1974
2347
  function parseEnvFlag(value, previous = {}) {
1975
2348
  const eqIdx = value.indexOf("=");
1976
2349
  if (eqIdx <= 0) {
1977
- throw new InvalidArgumentError2(`Expected KEY=VALUE, got "${value}".`);
2350
+ throw new InvalidArgumentError3(`Expected KEY=VALUE, got "${value}".`);
1978
2351
  }
1979
2352
  const key = value.substring(0, eqIdx);
1980
2353
  const val = value.substring(eqIdx + 1);
1981
2354
  if (!val) {
1982
- throw new InvalidArgumentError2(`Expected KEY=VALUE with non-empty value, got "${value}".`);
2355
+ throw new InvalidArgumentError3(`Expected KEY=VALUE with non-empty value, got "${value}".`);
1983
2356
  }
1984
2357
  return { ...previous, [key]: val };
1985
2358
  }
1986
- async function handleChannelCreation(storeHash, accessToken, apiHost, cliApiOrigin) {
1987
- const newChannelName = await input3({
1988
- message: "What would you like to name your new channel?"
1989
- });
1990
- const availableLocales = await getAvailableLocales(storeHash, accessToken, apiHost);
1991
- const storefrontLocale = await select4({
1992
- message: "Which default language would you like to set for your channel?",
1993
- default: "en",
1994
- choices: availableLocales,
1995
- theme: {
1996
- style: {
1997
- help: () => colorize4("dim", "(Select locale from the list or start typing the name)")
1998
- }
1999
- }
2000
- });
2001
- const shouldAddAdditionalLocales = await select4({
2002
- message: "Would you like to add additional languages?",
2003
- choices: [
2004
- { name: "Yes", value: true },
2005
- { name: "No", value: false }
2006
- ]
2007
- });
2008
- let additionalLocales = [];
2009
- if (shouldAddAdditionalLocales) {
2010
- const localeChoices = availableLocales.filter(({ value }) => value !== storefrontLocale).map(({ name, value }) => ({ name, value, description: value }));
2011
- additionalLocales = await checkbox({
2012
- message: "Which additional languages would you like to add to your channel?",
2013
- choices: localeChoices,
2014
- validate: (items) => items.length <= 4 || "You can only select up to 4 additional languages."
2015
- });
2016
- }
2017
- const shouldInstallSampleData = await select4({
2018
- message: "Would you like to install sample data?",
2019
- choices: [
2020
- { name: "Yes", value: true },
2021
- { name: "No", value: false }
2022
- ]
2023
- });
2024
- return createChannel(
2025
- newChannelName,
2026
- storefrontLocale,
2027
- additionalLocales,
2028
- shouldInstallSampleData,
2029
- storeHash,
2030
- accessToken,
2031
- cliApiOrigin
2032
- );
2033
- }
2034
2359
  async function handleChannelSelection(storeHash, accessToken, apiHost) {
2035
2360
  const channels = await fetchAvailableChannels(storeHash, accessToken, apiHost);
2036
- const existingChannel = await select4({
2361
+ const existingChannel = await select5({
2037
2362
  message: "Which channel would you like to use?",
2038
2363
  choices: sortChannelsByPlatform(channels).map((ch) => ({
2039
2364
  name: ch.name,
@@ -2051,7 +2376,7 @@ async function setupProject(options) {
2051
2376
  }
2052
2377
  if (projectName) {
2053
2378
  projectName = kebabCase(projectName);
2054
- projectDir = join11(options.projectDir, projectName);
2379
+ projectDir = join10(options.projectDir, projectName);
2055
2380
  if (pathExistsSync(projectDir)) {
2056
2381
  consola.error(`${projectDir} already exists`);
2057
2382
  process.exit(1);
@@ -2061,13 +2386,13 @@ async function setupProject(options) {
2061
2386
  const validateProjectName = (i) => {
2062
2387
  const formatted = kebabCase(i);
2063
2388
  if (!formatted) return "Project name is required";
2064
- const targetDir = join11(options.projectDir, formatted);
2389
+ const targetDir = join10(options.projectDir, formatted);
2065
2390
  if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
2066
2391
  projectName = formatted;
2067
2392
  projectDir = targetDir;
2068
2393
  return true;
2069
2394
  };
2070
- await input3({
2395
+ await input4({
2071
2396
  message: "What do you want to name your project directory?",
2072
2397
  default: "my-catalyst-app",
2073
2398
  validate: validateProjectName
@@ -2085,7 +2410,7 @@ function checkRequiredTools() {
2085
2410
  process.exit(1);
2086
2411
  }
2087
2412
  }
2088
- var create = new Command4("create").configureHelp({ showGlobalOptions: true }).description("Scaffold and connect a Catalyst storefront to your BigCommerce store.").addHelpText(
2413
+ var create2 = new Command4("create").configureHelp({ showGlobalOptions: true }).description("Scaffold and connect a Catalyst storefront to your BigCommerce store.").addHelpText(
2089
2414
  "after",
2090
2415
  `
2091
2416
  Examples:
@@ -2106,7 +2431,7 @@ Examples:
2106
2431
  "Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable).",
2107
2432
  parseEnvFlag
2108
2433
  ).addOption(
2109
- new Option5(
2434
+ new Option4(
2110
2435
  "--hosting <mode>",
2111
2436
  'Hosting mode: "self-hosted" (default) or "commerce" to set up Commerce Hosting at create time. When omitted, scaffolding is hosting-agnostic; run `catalyst deploy` later to opt in.'
2112
2437
  ).choices(["self-hosted", "commerce"])
@@ -2114,11 +2439,11 @@ Examples:
2114
2439
  "--use-existing",
2115
2440
  "Only used with --hosting commerce and --project-name. When the named project already exists on the store, reuse it instead of prompting. Has no effect without --hosting commerce."
2116
2441
  ).addOption(
2117
- new Option5("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
2442
+ new Option4("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
2118
2443
  ).addOption(
2119
- new Option5("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
2444
+ new Option4("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
2120
2445
  ).addOption(
2121
- new Option5("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
2446
+ new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
2122
2447
  ).action(async (options) => {
2123
2448
  const { ghRef, repository } = options;
2124
2449
  if (options.useExisting && options.hosting !== "commerce") {
@@ -2172,7 +2497,7 @@ Examples:
2172
2497
  }
2173
2498
  let shouldCreateChannel;
2174
2499
  if (eligibility.eligible) {
2175
- shouldCreateChannel = await select4({
2500
+ shouldCreateChannel = await select5({
2176
2501
  message: "Would you like to create a new channel?",
2177
2502
  choices: [
2178
2503
  { name: "Yes", value: true },
@@ -2181,12 +2506,12 @@ Examples:
2181
2506
  });
2182
2507
  }
2183
2508
  if (shouldCreateChannel) {
2184
- const channelData = await handleChannelCreation(
2509
+ const channelData = await runCreateChannelFlow({
2185
2510
  storeHash,
2186
2511
  accessToken,
2187
2512
  apiHost,
2188
2513
  cliApiOrigin
2189
- );
2514
+ });
2190
2515
  channelId = channelData.channelId;
2191
2516
  storefrontToken = channelData.storefrontToken;
2192
2517
  envVars = { ...channelData.envVars };
@@ -2265,19 +2590,280 @@ Examples:
2265
2590
  consola.log(
2266
2591
  `Next steps:
2267
2592
 
2268
- ${steps.map((step) => ` ${colorize4("yellow", step)}`).join("\n")}`
2593
+ ${steps.map((step) => ` ${colorize6("yellow", step)}`).join("\n")}`
2269
2594
  );
2270
2595
  });
2271
2596
 
2597
+ // src/cli/commands/debug.ts
2598
+ import { Command as Command5, Option as Option5 } from "commander";
2599
+
2600
+ // src/cli/lib/collect-diagnostics.ts
2601
+ import { parse } from "dotenv";
2602
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
2603
+ import { release } from "os";
2604
+ import { dirname as dirname4, join as join11 } from "path";
2605
+ import { z as z12 } from "zod";
2606
+ var CLI_ENV_VARS = [
2607
+ "CATALYST_STORE_HASH",
2608
+ "CATALYST_ACCESS_TOKEN",
2609
+ "CATALYST_PROJECT_UUID",
2610
+ "CATALYST_API_HOST",
2611
+ "BIGCOMMERCE_LOGIN_URL",
2612
+ "CATALYST_TELEMETRY_DISABLED"
2613
+ ];
2614
+ var BUILD_ENV_VARS = [
2615
+ "BIGCOMMERCE_STORE_HASH",
2616
+ "BIGCOMMERCE_STOREFRONT_TOKEN",
2617
+ "BIGCOMMERCE_CHANNEL_ID",
2618
+ "AUTH_SECRET"
2619
+ ];
2620
+ var ENV_FILES = [".env.local", ".env"];
2621
+ var PROJECT_LOCKFILES = [
2622
+ ["pnpm-lock.yaml", "pnpm"],
2623
+ ["yarn.lock", "yarn"],
2624
+ ["bun.lock", "bun"],
2625
+ ["bun.lockb", "bun"],
2626
+ ["package-lock.json", "npm"]
2627
+ ];
2628
+ var hasValue = (value) => value !== void 0 && value.trim() !== "";
2629
+ var buildEnvLayers = (cwd, env2) => {
2630
+ const layers = [{ source: "process.env", values: env2 }];
2631
+ ENV_FILES.forEach((file) => {
2632
+ const path = join11(cwd, file);
2633
+ if (!existsSync5(path)) {
2634
+ return;
2635
+ }
2636
+ try {
2637
+ layers.push({ source: file, values: parse(readFileSync6(path, "utf-8")) });
2638
+ } catch {
2639
+ }
2640
+ });
2641
+ return layers;
2642
+ };
2643
+ var resolveEnvSource = (layers, ...keys) => layers.find((layer) => keys.some((key) => hasValue(layer.values[key])))?.source;
2644
+ var resolveEnvVars = (layers, names) => Object.fromEntries(names.map((name) => [name, resolveEnvSource(layers, name) ?? "unset"]));
2645
+ var resolveValue = (envSource, configValue) => {
2646
+ if (envSource) {
2647
+ return { present: true, source: envSource };
2648
+ }
2649
+ if (hasValue(configValue)) {
2650
+ return { present: true, source: "project.json" };
2651
+ }
2652
+ return { present: false, source: "unset" };
2653
+ };
2654
+ var asString = (value) => typeof value === "string" ? value : void 0;
2655
+ var lockfileManager = (dir) => PROJECT_LOCKFILES.find(([file]) => existsSync5(join11(dir, file)))?.[1];
2656
+ var detectProjectPackageManager2 = (cwd) => {
2657
+ let dir = cwd;
2658
+ for (; ; ) {
2659
+ const match = lockfileManager(dir);
2660
+ if (match) {
2661
+ return match;
2662
+ }
2663
+ const parent = dirname4(dir);
2664
+ if (parent === dir) {
2665
+ return detectPackageManager();
2666
+ }
2667
+ dir = parent;
2668
+ }
2669
+ };
2670
+ var projectJsonSchema2 = z12.looseObject({});
2671
+ var safeReadJson2 = (path) => {
2672
+ try {
2673
+ return JSON.parse(readFileSync6(path, "utf-8"));
2674
+ } catch {
2675
+ return null;
2676
+ }
2677
+ };
2678
+ var readProjectJson = (cwd) => {
2679
+ const result = projectJsonSchema2.safeParse(
2680
+ safeReadJson2(join11(cwd, ".bigcommerce", "project.json"))
2681
+ );
2682
+ return result.success ? result.data : null;
2683
+ };
2684
+ var storefrontPackageSchema = z12.looseObject({
2685
+ name: z12.string().optional(),
2686
+ version: z12.string().optional(),
2687
+ catalyst: z12.looseObject({ version: z12.string().optional() }).optional()
2688
+ });
2689
+ var readStorefrontInfo = (cwd) => {
2690
+ const result = storefrontPackageSchema.safeParse(safeReadJson2(join11(cwd, "package.json")));
2691
+ if (!result.success) {
2692
+ return { name: null, version: null };
2693
+ }
2694
+ return {
2695
+ name: result.data.name ?? null,
2696
+ version: result.data.catalyst?.version ?? result.data.version ?? null
2697
+ };
2698
+ };
2699
+ var readStoredEnvKeys = (projectJson) => {
2700
+ const env2 = projectJson?.env;
2701
+ if (typeof env2 === "object" && env2 !== null && !Array.isArray(env2)) {
2702
+ return Object.keys(env2).sort();
2703
+ }
2704
+ return [];
2705
+ };
2706
+ function collectDiagnostics({
2707
+ cwd = process.cwd(),
2708
+ env: env2 = process.env
2709
+ } = {}) {
2710
+ const state = getProjectState(cwd);
2711
+ const telemetry3 = getTelemetry();
2712
+ const projectJson = readProjectJson(cwd);
2713
+ const storefront = readStorefrontInfo(cwd);
2714
+ const cliEnv = [{ source: "process.env", values: env2 }];
2715
+ const buildEnv = buildEnvLayers(cwd, env2);
2716
+ return {
2717
+ cli: {
2718
+ name: package_default.name,
2719
+ version: package_default.version
2720
+ },
2721
+ runtime: {
2722
+ node: process.version,
2723
+ platform: process.platform,
2724
+ arch: process.arch,
2725
+ osRelease: release(),
2726
+ packageManager: detectProjectPackageManager2(cwd)
2727
+ },
2728
+ project: {
2729
+ cwd,
2730
+ storefrontName: storefront.name,
2731
+ storefrontVersion: storefront.version,
2732
+ projectUuid: state.projectUuid ?? null,
2733
+ isLinked: state.isLinked,
2734
+ isTransformed: state.isTransformed,
2735
+ isFullySetUp: state.isFullySetUp,
2736
+ hasMiddleware: state.hasMiddleware,
2737
+ hasProxy: state.hasProxy,
2738
+ hasOpenNextDep: state.hasOpenNextDep
2739
+ },
2740
+ config: {
2741
+ storeHash: resolveValue(
2742
+ resolveEnvSource(cliEnv, "CATALYST_STORE_HASH", "BIGCOMMERCE_STORE_HASH"),
2743
+ asString(projectJson?.storeHash)
2744
+ ),
2745
+ accessToken: resolveValue(
2746
+ resolveEnvSource(cliEnv, "CATALYST_ACCESS_TOKEN"),
2747
+ asString(projectJson?.accessToken)
2748
+ ),
2749
+ projectUuid: resolveValue(
2750
+ resolveEnvSource(cliEnv, "CATALYST_PROJECT_UUID"),
2751
+ asString(projectJson?.projectUuid)
2752
+ ),
2753
+ apiHost: resolveValue(
2754
+ resolveEnvSource(cliEnv, "CATALYST_API_HOST"),
2755
+ asString(projectJson?.apiHost)
2756
+ ),
2757
+ projectJsonKeys: projectJson ? Object.keys(projectJson).sort() : [],
2758
+ storedEnvKeys: readStoredEnvKeys(projectJson),
2759
+ cliEnvVars: resolveEnvVars(cliEnv, CLI_ENV_VARS),
2760
+ buildEnvVars: resolveEnvVars(buildEnv, BUILD_ENV_VARS)
2761
+ },
2762
+ telemetry: {
2763
+ enabled: telemetry3.isEnabled(),
2764
+ correlationId: telemetry3.correlationId
2765
+ },
2766
+ files: {
2767
+ ".env.local": existsSync5(join11(cwd, ".env.local")),
2768
+ ".env": existsSync5(join11(cwd, ".env")),
2769
+ ".bigcommerce/project.json": existsSync5(join11(cwd, ".bigcommerce", "project.json")),
2770
+ ".bigcommerce/wrangler.jsonc": existsSync5(join11(cwd, ".bigcommerce", "wrangler.jsonc")),
2771
+ ".open-next/": existsSync5(join11(cwd, ".open-next")),
2772
+ "package.json": existsSync5(join11(cwd, "package.json"))
2773
+ }
2774
+ };
2775
+ }
2776
+
2777
+ // src/cli/commands/debug.ts
2778
+ var yesNo = (value) => value ? "yes" : "no";
2779
+ var presence = (value) => value ? "present" : "absent";
2780
+ var formatResolved = (value) => value.present ? `present (source: ${value.source})` : "not set";
2781
+ var formatSource = (source) => source === "unset" ? "not set" : `set (${source})`;
2782
+ var formatStorefront = (name, version2) => {
2783
+ if (!version2) {
2784
+ return "(unknown)";
2785
+ }
2786
+ return name ? `${name}@${version2}` : version2;
2787
+ };
2788
+ var formatList = (values) => values.length > 0 ? values.join(", ") : "(none)";
2789
+ var envVarLines = (vars) => Object.entries(vars).map(([name, source]) => ` ${name}: ${formatSource(source)}`);
2790
+ var formatReport = (d) => {
2791
+ const lines = [
2792
+ "Catalyst CLI Diagnostics",
2793
+ "",
2794
+ "CLI",
2795
+ ` Package: ${d.cli.name}`,
2796
+ ` Version: ${d.cli.version}`,
2797
+ "",
2798
+ "Runtime",
2799
+ ` Node: ${d.runtime.node}`,
2800
+ ` Platform: ${d.runtime.platform} (${d.runtime.arch})`,
2801
+ ` OS release: ${d.runtime.osRelease}`,
2802
+ ` Package manager: ${d.runtime.packageManager}`,
2803
+ "",
2804
+ "Project",
2805
+ ` Directory: ${d.project.cwd}`,
2806
+ ` Storefront: ${formatStorefront(d.project.storefrontName, d.project.storefrontVersion)}`,
2807
+ ` Project UUID: ${d.project.projectUuid ?? "(not linked)"}`,
2808
+ ` Linked: ${yesNo(d.project.isLinked)}`,
2809
+ ` Transformed: ${yesNo(d.project.isTransformed)}`,
2810
+ ` Fully set up: ${yesNo(d.project.isFullySetUp)}`,
2811
+ ` middleware.ts: ${presence(d.project.hasMiddleware)}`,
2812
+ ` proxy.ts: ${presence(d.project.hasProxy)}`,
2813
+ ` OpenNext dep: ${d.project.hasOpenNextDep ? "installed" : "not installed"}`,
2814
+ "",
2815
+ "Config (resolved without secrets)",
2816
+ ` Store hash: ${formatResolved(d.config.storeHash)}`,
2817
+ ` Access token: ${formatResolved(d.config.accessToken)}`,
2818
+ ` Project UUID: ${formatResolved(d.config.projectUuid)}`,
2819
+ ` API host: ${d.config.apiHost.present ? formatResolved(d.config.apiHost) : "default (api.bigcommerce.com)"}`,
2820
+ ` project.json keys: ${formatList(d.config.projectJsonKeys)}`,
2821
+ ` Stored env keys: ${formatList(d.config.storedEnvKeys)}`,
2822
+ "",
2823
+ "CLI environment variables (used to run the CLI)",
2824
+ ...envVarLines(d.config.cliEnvVars),
2825
+ "",
2826
+ "Build environment variables (used to build the Next.js app)",
2827
+ ...envVarLines(d.config.buildEnvVars),
2828
+ "",
2829
+ "Telemetry",
2830
+ ` Enabled: ${yesNo(d.telemetry.enabled)}`,
2831
+ ` Correlation ID: ${d.telemetry.correlationId}`,
2832
+ "",
2833
+ "Files",
2834
+ ...Object.entries(d.files).map(([name, exists]) => ` ${name}: ${presence(exists)}`)
2835
+ ];
2836
+ return lines.join("\n");
2837
+ };
2838
+ var debug = new Command5("debug").configureHelp({ showGlobalOptions: true }).description(
2839
+ "Print a diagnostic report (CLI, runtime, project, and config state) to include when filing a bug report. Never prints secret values \u2014 credentials and env vars are reported by presence only."
2840
+ ).addHelpText(
2841
+ "after",
2842
+ `
2843
+ Examples:
2844
+ # Print a human-readable report
2845
+ $ catalyst debug
2846
+
2847
+ # Print machine-readable JSON (useful for copy/paste or piping)
2848
+ $ catalyst debug --json`
2849
+ ).addOption(new Option5("--json", "Output the report as JSON.")).action((options) => {
2850
+ const diagnostics = collectDiagnostics();
2851
+ if (options.json) {
2852
+ consola.log(JSON.stringify(diagnostics, null, 2));
2853
+ return;
2854
+ }
2855
+ consola.log(formatReport(diagnostics));
2856
+ });
2857
+
2272
2858
  // src/cli/commands/deploy.ts
2273
2859
  import { confirm as confirm3 } from "@inquirer/prompts";
2274
2860
  import AdmZip from "adm-zip";
2275
- import { Command as Command5, Option as Option6 } from "commander";
2276
- import { colorize as colorize5 } from "consola/utils";
2861
+ import { Command as Command6, Option as Option6 } from "commander";
2862
+ import { colorize as colorize7 } from "consola/utils";
2277
2863
  import { access, readdir, readFile } from "fs/promises";
2278
2864
  import { join as join12 } from "path";
2279
2865
  import yoctoSpinner3 from "yocto-spinner";
2280
- import { z as z11 } from "zod";
2866
+ import { z as z13 } from "zod";
2281
2867
 
2282
2868
  // src/cli/lib/deployment-errors.ts
2283
2869
  var ERROR_MESSAGES = {
@@ -2296,7 +2882,7 @@ function getDeploymentErrorMessage(code) {
2296
2882
  }
2297
2883
 
2298
2884
  // src/cli/commands/deploy.ts
2299
- var stepsEnum = z11.enum([
2885
+ var stepsEnum = z13.enum([
2300
2886
  "initializing",
2301
2887
  "downloading",
2302
2888
  "unzipping",
@@ -2314,35 +2900,35 @@ var STEPS = {
2314
2900
  finalizing: "Finalizing...",
2315
2901
  complete: "Complete"
2316
2902
  };
2317
- var UploadSignatureSchema = z11.object({
2318
- data: z11.object({
2319
- upload_url: z11.url(),
2320
- upload_uuid: z11.string()
2903
+ var UploadSignatureSchema = z13.object({
2904
+ data: z13.object({
2905
+ upload_url: z13.url(),
2906
+ upload_uuid: z13.string()
2321
2907
  })
2322
2908
  });
2323
- var CreateDeploymentSchema = z11.object({
2324
- data: z11.object({
2325
- deployment_uuid: z11.uuid()
2909
+ var CreateDeploymentSchema = z13.object({
2910
+ data: z13.object({
2911
+ deployment_uuid: z13.uuid()
2326
2912
  })
2327
2913
  });
2328
- var ProjectSchema = z11.object({
2329
- data: z11.array(
2330
- z11.object({
2331
- uuid: z11.uuid(),
2332
- name: z11.string()
2914
+ var ProjectSchema = z13.object({
2915
+ data: z13.array(
2916
+ z13.object({
2917
+ uuid: z13.uuid(),
2918
+ name: z13.string()
2333
2919
  })
2334
2920
  )
2335
2921
  });
2336
- var DeploymentStatusSchema = z11.object({
2337
- deployment_uuid: z11.uuid(),
2338
- deployment_status: z11.enum(["queued", "in_progress", "failed", "completed"]),
2339
- event: z11.object({
2922
+ var DeploymentStatusSchema = z13.object({
2923
+ deployment_uuid: z13.uuid(),
2924
+ deployment_status: z13.enum(["queued", "in_progress", "failed", "completed"]),
2925
+ event: z13.object({
2340
2926
  step: stepsEnum,
2341
- progress: z11.number()
2927
+ progress: z13.number()
2342
2928
  }).nullable(),
2343
- deployment_hostnames: z11.array(z11.string()).optional(),
2344
- error: z11.object({
2345
- code: z11.number()
2929
+ deployment_hostnames: z13.array(z13.string()).optional(),
2930
+ error: z13.object({
2931
+ code: z13.number()
2346
2932
  }).optional()
2347
2933
  });
2348
2934
  var generateBundleZip = async () => {
@@ -2381,7 +2967,7 @@ var generateUploadSignature = async (storeHash, accessToken, apiHost) => {
2381
2967
  );
2382
2968
  assertAuthorized(response);
2383
2969
  if (!response.ok) {
2384
- throw new Error(`Failed to fetch upload signature: ${response.status} ${response.statusText}`);
2970
+ throw await httpError(response, "Failed to generate upload signature");
2385
2971
  }
2386
2972
  const res = await response.json();
2387
2973
  const { data } = UploadSignatureSchema.parse(res);
@@ -2400,7 +2986,7 @@ var uploadBundleZip = async (uploadUrl) => {
2400
2986
  body: fileBuffer
2401
2987
  });
2402
2988
  if (!response.ok) {
2403
- throw new Error(`Failed to upload bundle: ${response.status} ${response.statusText}`);
2989
+ throw await httpError(response, "Failed to upload bundle");
2404
2990
  }
2405
2991
  consola.success("Bundle uploaded successfully.");
2406
2992
  return true;
@@ -2436,7 +3022,7 @@ var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, a
2436
3022
  );
2437
3023
  assertAuthorized(response);
2438
3024
  if (!response.ok) {
2439
- throw new Error(`Failed to create deployment: ${response.status} ${response.statusText}`);
3025
+ throw await httpError(response, "Failed to create deployment");
2440
3026
  }
2441
3027
  const res = await response.json();
2442
3028
  const { data } = CreateDeploymentSchema.parse(res);
@@ -2460,7 +3046,7 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
2460
3046
  );
2461
3047
  assertAuthorized(response);
2462
3048
  if (!response.ok) {
2463
- throw new Error(`Failed to open event stream: ${response.status} ${response.statusText}`);
3049
+ throw await httpError(response, "Failed to open deployment event stream");
2464
3050
  }
2465
3051
  const reader = response.body?.getReader();
2466
3052
  if (!reader) {
@@ -2501,7 +3087,7 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
2501
3087
  spinner.success("Deployment completed successfully.");
2502
3088
  if (deploymentHostname) {
2503
3089
  consola.success(
2504
- `View your deployment at: ${colorize5("blue", `https://${deploymentHostname}`)}`
3090
+ `View your deployment at: ${colorize7("blue", `https://${deploymentHostname}`)}`
2505
3091
  );
2506
3092
  }
2507
3093
  return deploymentHostname;
@@ -2521,13 +3107,13 @@ var fetchProject = async (projectUuid, storeHash, accessToken, apiHost) => {
2521
3107
  );
2522
3108
  assertAuthorized(response);
2523
3109
  if (!response.ok) {
2524
- throw new Error(`Failed to fetch projects: ${response.status} ${response.statusText}`);
3110
+ throw await httpError(response, "Failed to fetch projects");
2525
3111
  }
2526
3112
  const res = await response.json();
2527
3113
  const { data } = ProjectSchema.parse(res);
2528
3114
  return data.find((project2) => project2.uuid === projectUuid);
2529
3115
  };
2530
- var deploy = new Command5("deploy").configureHelp({ showGlobalOptions: true }).description("Deploy your application to Cloudflare.").addHelpText(
3116
+ var deploy = new Command6("deploy").configureHelp({ showGlobalOptions: true }).description("Deploy your application to Cloudflare.").addHelpText(
2531
3117
  "after",
2532
3118
  `
2533
3119
  Environment variables saved with \`catalyst env add\` are sent automatically on every deploy.
@@ -2548,8 +3134,14 @@ Example:
2548
3134
  ).option("--dry-run", "Run the command to generate the bundle without uploading or deploying.").option(
2549
3135
  "--prebuilt",
2550
3136
  "Skip the build step. Requires .bigcommerce/dist/ to already contain build output."
2551
- ).action(async (options) => {
3137
+ ).addOption(
3138
+ new Option6(
3139
+ "--wrangler-version <version>",
3140
+ `Wrangler version or dist-tag to build with. Ignored with --prebuilt. Defaults to ${WRANGLER_VERSION}.`
3141
+ ).argParser(parseWranglerVersion)
3142
+ ).addOption(envPathOption()).action(async (options) => {
2552
3143
  const config2 = getProjectConfig();
3144
+ const apiHost = resolveApiHost(options, config2);
2553
3145
  const { storeHash, accessToken } = resolveCredentials(options, config2);
2554
3146
  const telemetry3 = getTelemetry();
2555
3147
  await telemetry3.identify(storeHash);
@@ -2560,7 +3152,7 @@ Example:
2560
3152
  return await selectOrCreateInfrastructureProject({
2561
3153
  storeHash,
2562
3154
  accessToken,
2563
- apiHost: options.apiHost
3155
+ apiHost
2564
3156
  });
2565
3157
  } catch (error) {
2566
3158
  if (error instanceof NoLinkedProjectError) {
@@ -2573,12 +3165,7 @@ Example:
2573
3165
  }
2574
3166
  };
2575
3167
  if (linkedProjectUuid) {
2576
- const existing = await fetchProject(
2577
- linkedProjectUuid,
2578
- storeHash,
2579
- accessToken,
2580
- options.apiHost
2581
- );
3168
+ const existing = await fetchProject(linkedProjectUuid, storeHash, accessToken, apiHost);
2582
3169
  if (existing) {
2583
3170
  projectUuid = linkedProjectUuid;
2584
3171
  } else {
@@ -2609,7 +3196,8 @@ Example:
2609
3196
  const projectDir = process.cwd();
2610
3197
  await setupCommerceHosting({ projectDir, projectUuid, storeHash, accessToken });
2611
3198
  consola.success("Commerce Hosting setup complete.");
2612
- await installDependencies(projectDir);
3199
+ const packageManager = await detectProjectPackageManager(projectDir);
3200
+ await installDependencies(projectDir, packageManager);
2613
3201
  } else {
2614
3202
  await cleanupCloudflareIncompatibilities(process.cwd());
2615
3203
  }
@@ -2630,7 +3218,8 @@ Example:
2630
3218
  }
2631
3219
  consola.info("Using existing build output (--prebuilt).");
2632
3220
  } else {
2633
- await buildCatalystProject(projectUuid);
3221
+ loadBuildEnv({ envPath: options.envPath });
3222
+ await buildCatalystProject(projectUuid, options.wranglerVersion);
2634
3223
  }
2635
3224
  await generateBundleZip();
2636
3225
  if (options.dryRun) {
@@ -2641,7 +3230,7 @@ Example:
2641
3230
  consola.info("- Create deployment");
2642
3231
  process.exit(0);
2643
3232
  }
2644
- const uploadSignature = await generateUploadSignature(storeHash, accessToken, options.apiHost);
3233
+ const uploadSignature = await generateUploadSignature(storeHash, accessToken, apiHost);
2645
3234
  await uploadBundleZip(uploadSignature.upload_url);
2646
3235
  const flagSecrets = parseEnvironmentVariables(options.secret) ?? [];
2647
3236
  const persistedSecrets = toDeploymentSecrets(getStoredEnv(config2));
@@ -2652,14 +3241,14 @@ Example:
2652
3241
  uploadSignature.upload_uuid,
2653
3242
  storeHash,
2654
3243
  accessToken,
2655
- options.apiHost,
3244
+ apiHost,
2656
3245
  environmentVariables
2657
3246
  );
2658
3247
  const deploymentHostname = await getDeploymentStatus(
2659
3248
  deploymentUuid,
2660
3249
  storeHash,
2661
3250
  accessToken,
2662
- options.apiHost
3251
+ apiHost
2663
3252
  );
2664
3253
  if (!options.updateSiteUrl) {
2665
3254
  return;
@@ -2668,7 +3257,7 @@ Example:
2668
3257
  await runChannelSiteUrlFlow({
2669
3258
  storeHash,
2670
3259
  accessToken,
2671
- apiHost: options.apiHost,
3260
+ apiHost,
2672
3261
  projectUuid,
2673
3262
  preferHostname: deploymentHostname
2674
3263
  });
@@ -2683,36 +3272,36 @@ Example:
2683
3272
  });
2684
3273
 
2685
3274
  // src/cli/commands/domains.ts
2686
- import { confirm as confirm4 } from "@inquirer/prompts";
2687
- import { Command as Command6, Option as Option7 } from "commander";
2688
- import { colorize as colorize7 } from "consola/utils";
3275
+ import { confirm as confirm4, select as select6 } from "@inquirer/prompts";
3276
+ import { Command as Command7, Option as Option7 } from "commander";
3277
+ import { colorize as colorize9 } from "consola/utils";
2689
3278
 
2690
3279
  // src/cli/lib/domains.ts
2691
- import { z as z13 } from "zod";
3280
+ import { z as z15 } from "zod";
2692
3281
 
2693
3282
  // src/cli/lib/observability.ts
2694
- import { colorize as colorize6 } from "consola/utils";
2695
- import { z as z12 } from "zod";
3283
+ import { colorize as colorize8 } from "consola/utils";
3284
+ import { z as z14 } from "zod";
2696
3285
  var LOG_LEVELS = ["debug", "info", "warn", "error"];
2697
3286
  var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
2698
- var logEntrySchema = z12.object({
2699
- timestamp: z12.string().nullable().optional(),
2700
- level: z12.string().optional(),
2701
- messages: z12.array(z12.unknown()).optional(),
2702
- is_exception: z12.boolean().optional(),
2703
- exception_name: z12.string().optional(),
2704
- request: z12.object({
2705
- method: z12.string().optional(),
2706
- url: z12.string().optional(),
2707
- status_code: z12.number().optional()
3287
+ var logEntrySchema = z14.object({
3288
+ timestamp: z14.string().nullable().optional(),
3289
+ level: z14.string().optional(),
3290
+ messages: z14.array(z14.unknown()).optional(),
3291
+ is_exception: z14.boolean().optional(),
3292
+ exception_name: z14.string().optional(),
3293
+ request: z14.object({
3294
+ method: z14.string().optional(),
3295
+ url: z14.string().optional(),
3296
+ status_code: z14.number().optional()
2708
3297
  }).nullable().optional()
2709
3298
  });
2710
- var queryLogsSchema = z12.object({
2711
- data: z12.array(logEntrySchema)
3299
+ var queryLogsSchema = z14.object({
3300
+ data: z14.array(logEntrySchema)
2712
3301
  });
2713
- var v3ErrorSchema = z12.object({
2714
- title: z12.string().optional(),
2715
- errors: z12.record(z12.string(), z12.string()).optional()
3302
+ var v3ErrorSchema = z14.object({
3303
+ title: z14.string().optional(),
3304
+ errors: z14.record(z14.string(), z14.string()).optional()
2716
3305
  });
2717
3306
  function formatV3Error(body) {
2718
3307
  const parsed = v3ErrorSchema.safeParse(body);
@@ -2749,7 +3338,7 @@ function resolveTimeWindow(opts, nowMs = Date.now()) {
2749
3338
  if (end) {
2750
3339
  endMs = parseTimeInput(end);
2751
3340
  if (endMs === null) {
2752
- throw new Error(
3341
+ throw new UserActionableError(
2753
3342
  `Invalid --end value "${end}". Provide an ISO-8601 timestamp or a Unix epoch (seconds).`
2754
3343
  );
2755
3344
  }
@@ -2760,7 +3349,7 @@ function resolveTimeWindow(opts, nowMs = Date.now()) {
2760
3349
  if (since) {
2761
3350
  const durationMs = parseDuration(since);
2762
3351
  if (durationMs === null) {
2763
- throw new Error(
3352
+ throw new UserActionableError(
2764
3353
  `Invalid --since value "${since}". Provide a duration like 30m, 6h, or 2d (units: s, m, h, d).`
2765
3354
  );
2766
3355
  }
@@ -2769,18 +3358,20 @@ function resolveTimeWindow(opts, nowMs = Date.now()) {
2769
3358
  } else if (start2) {
2770
3359
  startMs = parseTimeInput(start2);
2771
3360
  if (startMs === null) {
2772
- throw new Error(
3361
+ throw new UserActionableError(
2773
3362
  `Invalid --start value "${start2}". Provide an ISO-8601 timestamp or a Unix epoch (seconds).`
2774
3363
  );
2775
3364
  }
2776
3365
  } else {
2777
- throw new Error("Provide a time window with --since <duration> or --start <time>.");
3366
+ throw new UserActionableError(
3367
+ "Provide a time window with --since <duration> or --start <time>."
3368
+ );
2778
3369
  }
2779
3370
  if (startMs > endMs) {
2780
- throw new Error("Invalid time window: --start must be before or equal to --end.");
3371
+ throw new UserActionableError("Invalid time window: --start must be before or equal to --end.");
2781
3372
  }
2782
3373
  if (endMs - startMs > SEVEN_DAYS_MS) {
2783
- throw new Error("Invalid time window: the range must not exceed 7 days.");
3374
+ throw new UserActionableError("Invalid time window: the range must not exceed 7 days.");
2784
3375
  }
2785
3376
  return { start: start2, end };
2786
3377
  }
@@ -2800,8 +3391,8 @@ function formatLogEntry(entry, format = "default") {
2800
3391
  const message = (entry.messages ?? []).map(stringifyMessage).join(" ").replace(/\s+/g, " ").trim();
2801
3392
  if (format === "short") return message;
2802
3393
  const level = entry.level ?? "unknown";
2803
- const coloredLevel = colorize6(LEVEL_COLORS[level.toLowerCase()] ?? "white", level.toUpperCase());
2804
- const exceptionStr = entry.is_exception && entry.exception_name && !isRedundantExceptionName(entry.exception_name, message) ? ` ${colorize6("red", `[${entry.exception_name}]`)}` : "";
3394
+ const coloredLevel = colorize8(LEVEL_COLORS[level.toLowerCase()] ?? "white", level.toUpperCase());
3395
+ const exceptionStr = entry.is_exception && entry.exception_name && !isRedundantExceptionName(entry.exception_name, message) ? ` ${colorize8("red", `[${entry.exception_name}]`)}` : "";
2805
3396
  let requestStr = "";
2806
3397
  if (format === "request" && entry.request) {
2807
3398
  const parts = [];
@@ -2834,36 +3425,73 @@ async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
2834
3425
  );
2835
3426
  assertAuthorized(response);
2836
3427
  if (response.status === 403) {
2837
- throw new Error(
3428
+ throw new UserActionableError(
2838
3429
  "Infrastructure Logs API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
2839
3430
  );
2840
3431
  }
2841
3432
  if (response.status === 404) {
2842
- throw new Error("Project not found. Check the project UUID.");
3433
+ throw new UserActionableError("Project not found. Check the project UUID.");
2843
3434
  }
2844
3435
  if (response.status === 400 || response.status === 422) {
2845
3436
  const body = await response.json().catch(() => null);
2846
- throw new Error(formatV3Error(body) ?? "Invalid log query.");
3437
+ throw new UserActionableError(formatV3Error(body) ?? "Invalid log query.");
2847
3438
  }
2848
3439
  if (!response.ok) {
2849
- throw new Error(`Failed to fetch logs: ${response.status} ${response.statusText}`);
3440
+ throw await httpError(response, "Failed to fetch logs");
2850
3441
  }
2851
3442
  const res = await response.json();
2852
3443
  return queryLogsSchema.parse(res);
2853
3444
  }
2854
3445
 
2855
3446
  // src/cli/lib/domains.ts
2856
- var domainStatusSchema = z13.enum(["pending", "verified", "failed", "unknown"]);
2857
- var domainSchema = z13.object({
2858
- domain: z13.string(),
2859
- project_uuid: z13.string(),
3447
+ var domainStatusSchema = z15.enum(["pending", "verified", "failed", "unknown"]);
3448
+ var domainSchema = z15.object({
3449
+ domain: z15.string(),
3450
+ project_uuid: z15.string(),
2860
3451
  verification_status: domainStatusSchema
2861
3452
  });
2862
- var domainResponseSchema = z13.object({
3453
+ var ownershipVerificationSchema = z15.object({
3454
+ type: z15.string(),
3455
+ name: z15.string(),
3456
+ value: z15.string().optional()
3457
+ });
3458
+ var ownershipVerificationMetaSchema = z15.object({
3459
+ meta: z15.object({
3460
+ ownership_verification: ownershipVerificationSchema
3461
+ })
3462
+ });
3463
+ var DomainOwnershipVerificationError = class extends UserActionableError {
3464
+ constructor(message, ownershipVerification) {
3465
+ super(message);
3466
+ this.name = "DomainOwnershipVerificationError";
3467
+ this.ownershipVerification = ownershipVerification;
3468
+ }
3469
+ };
3470
+ function parseOwnershipVerification(body) {
3471
+ const parsed = ownershipVerificationMetaSchema.safeParse(body);
3472
+ return parsed.success ? parsed.data.meta.ownership_verification : void 0;
3473
+ }
3474
+ var boundProjectMetaSchema = z15.object({
3475
+ meta: z15.object({
3476
+ project_uuid: z15.string()
3477
+ })
3478
+ });
3479
+ var DomainBoundToProjectError = class extends UserActionableError {
3480
+ constructor(message, projectUuid) {
3481
+ super(message);
3482
+ this.name = "DomainBoundToProjectError";
3483
+ this.projectUuid = projectUuid;
3484
+ }
3485
+ };
3486
+ function parseBoundProjectUuid(body) {
3487
+ const parsed = boundProjectMetaSchema.safeParse(body);
3488
+ return parsed.success ? parsed.data.meta.project_uuid : void 0;
3489
+ }
3490
+ var domainResponseSchema = z15.object({
2863
3491
  data: domainSchema
2864
3492
  });
2865
- var domainListResponseSchema = z13.object({
2866
- data: z13.array(domainSchema)
3493
+ var domainListResponseSchema = z15.object({
3494
+ data: z15.array(domainSchema)
2867
3495
  });
2868
3496
  var DOMAINS_API_NOT_ENABLED = "Infrastructure Domains API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it.";
2869
3497
  function domainsUrl(storeHash, projectUuid, apiHost) {
@@ -2872,6 +3500,12 @@ function domainsUrl(storeHash, projectUuid, apiHost) {
2872
3500
  function domainUrl(storeHash, projectUuid, domain, apiHost) {
2873
3501
  return `${domainsUrl(storeHash, projectUuid, apiHost)}/${encodeURIComponent(domain)}`;
2874
3502
  }
3503
+ function domainClaimUrl(storeHash, projectUuid, domain, apiHost) {
3504
+ return `${domainUrl(storeHash, projectUuid, domain, apiHost)}/claim`;
3505
+ }
3506
+ function domainTransferUrl(storeHash, projectUuid, domain, apiHost) {
3507
+ return `${domainUrl(storeHash, projectUuid, domain, apiHost)}/transfer`;
3508
+ }
2875
3509
  function authHeaders3(accessToken) {
2876
3510
  return {
2877
3511
  Accept: "application/json",
@@ -2882,8 +3516,7 @@ function authHeaders3(accessToken) {
2882
3516
  function formatResponseStatus(response, message) {
2883
3517
  return [response.status, response.statusText || message].filter(Boolean).join(" ");
2884
3518
  }
2885
- async function getErrorMessage(response, action) {
2886
- const body = await response.json().catch(() => null);
3519
+ function buildErrorMessage(response, body, action) {
2887
3520
  const message = formatV3Error(body);
2888
3521
  if (response.status >= 500) {
2889
3522
  const status = formatResponseStatus(response, message);
@@ -2894,11 +3527,25 @@ async function getErrorMessage(response, action) {
2894
3527
  async function assertDomainResponse(response, action) {
2895
3528
  assertAuthorized(response);
2896
3529
  if (response.status === 403) {
2897
- throw new Error(DOMAINS_API_NOT_ENABLED);
3530
+ throw new UserActionableError(DOMAINS_API_NOT_ENABLED);
2898
3531
  }
2899
- if (!response.ok) {
2900
- throw new Error(await getErrorMessage(response, action));
3532
+ if (response.ok) {
3533
+ return;
3534
+ }
3535
+ const body = await response.json().catch(() => null);
3536
+ const message = buildErrorMessage(response, body, action);
3537
+ const ownershipVerification = parseOwnershipVerification(body);
3538
+ if (ownershipVerification) {
3539
+ throw new DomainOwnershipVerificationError(message, ownershipVerification);
3540
+ }
3541
+ const boundProjectUuid = parseBoundProjectUuid(body);
3542
+ if (boundProjectUuid) {
3543
+ throw new DomainBoundToProjectError(message, boundProjectUuid);
2901
3544
  }
3545
+ if (response.status >= 500) {
3546
+ throw new Error(message);
3547
+ }
3548
+ throw new UserActionableError(message);
2902
3549
  }
2903
3550
  async function createDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
2904
3551
  const response = await fetch(domainsUrl(storeHash, projectUuid, apiHost), {
@@ -2922,6 +3569,18 @@ async function getDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
2922
3569
  const result = await response.json();
2923
3570
  return domainResponseSchema.parse(result).data;
2924
3571
  }
3572
+ async function findDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
3573
+ const response = await fetch(domainUrl(storeHash, projectUuid, domain, apiHost), {
3574
+ method: "GET",
3575
+ headers: authHeaders3(accessToken)
3576
+ });
3577
+ if (response.status === 404) {
3578
+ return null;
3579
+ }
3580
+ await assertDomainResponse(response, "Failed to fetch domain");
3581
+ const result = await response.json();
3582
+ return domainResponseSchema.parse(result).data;
3583
+ }
2925
3584
  async function listDomains(projectUuid, storeHash, accessToken, apiHost, filters = {}) {
2926
3585
  const search = new URLSearchParams();
2927
3586
  if (filters.domains && filters.domains.length > 0) {
@@ -2947,6 +3606,24 @@ async function deleteDomain(domain, projectUuid, storeHash, accessToken, apiHost
2947
3606
  });
2948
3607
  await assertDomainResponse(response, `Failed to remove domain: ${response.statusText}`);
2949
3608
  }
3609
+ async function claimDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
3610
+ const response = await fetch(domainClaimUrl(storeHash, projectUuid, domain, apiHost), {
3611
+ method: "POST",
3612
+ headers: authHeaders3(accessToken)
3613
+ });
3614
+ await assertDomainResponse(response, "Failed to claim domain");
3615
+ }
3616
+ async function transferDomain(domain, projectUuid, newProjectUuid, storeHash, accessToken, apiHost) {
3617
+ const response = await fetch(domainTransferUrl(storeHash, projectUuid, domain, apiHost), {
3618
+ method: "POST",
3619
+ headers: {
3620
+ ...authHeaders3(accessToken),
3621
+ "Content-Type": "application/json"
3622
+ },
3623
+ body: JSON.stringify({ new_project_uuid: newProjectUuid })
3624
+ });
3625
+ await assertDomainResponse(response, "Failed to transfer domain");
3626
+ }
2950
3627
 
2951
3628
  // src/cli/commands/domains.ts
2952
3629
  var WAIT_INTERVAL_MS = 5e3;
@@ -2967,21 +3644,58 @@ var STATUS_LABELS = {
2967
3644
  var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
2968
3645
  function resolveDomainCommandContext(options) {
2969
3646
  const config2 = getProjectConfig();
3647
+ const apiHost = resolveApiHost(options, config2);
2970
3648
  const { storeHash, accessToken } = resolveCredentials(options, config2);
2971
3649
  const projectUuid = resolveProjectUuid(options);
2972
3650
  return {
2973
3651
  projectUuid,
2974
3652
  storeHash,
2975
3653
  accessToken,
2976
- apiHost: options.apiHost
3654
+ apiHost
2977
3655
  };
2978
3656
  }
3657
+ async function resolveDestinationProject(domain, context, toProjectUuid) {
3658
+ if (toProjectUuid) {
3659
+ if (toProjectUuid === context.projectUuid) {
3660
+ throw new UserActionableError("The destination project must differ from the source project.");
3661
+ }
3662
+ return toProjectUuid;
3663
+ }
3664
+ consola.start("Fetching projects...");
3665
+ const projects = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
3666
+ consola.success("Projects fetched.");
3667
+ const destinations = projects.filter((project2) => project2.uuid !== context.projectUuid);
3668
+ if (destinations.length === 0) {
3669
+ throw new UserActionableError(
3670
+ "No other projects to transfer to. Create another project with `catalyst project create` first."
3671
+ );
3672
+ }
3673
+ return select6({
3674
+ message: `Select the project to transfer ${domain} to (Press <enter> to select).`,
3675
+ choices: destinations.map((project2) => ({
3676
+ name: project2.name,
3677
+ value: project2.uuid,
3678
+ description: project2.uuid
3679
+ }))
3680
+ });
3681
+ }
2979
3682
  function formatDomainStatus(status) {
2980
- return colorize7(STATUS_COLORS[status], STATUS_LABELS[status]);
3683
+ return colorize9(STATUS_COLORS[status], STATUS_LABELS[status]);
2981
3684
  }
2982
3685
  function formatDomain(domain) {
2983
3686
  return `${domain.domain} ${formatDomainStatus(domain.verification_status)}`;
2984
3687
  }
3688
+ function formatOwnershipVerification(record) {
3689
+ const lines = [
3690
+ "Add this DNS record to verify ownership:",
3691
+ ` Type: ${record.type}`,
3692
+ ` Name: ${record.name}`
3693
+ ];
3694
+ if (record.value) {
3695
+ lines.push(` Value: ${record.value}`);
3696
+ }
3697
+ return lines.join("\n");
3698
+ }
2985
3699
  async function waitForDomainVerification({
2986
3700
  domain,
2987
3701
  projectUuid,
@@ -2999,7 +3713,7 @@ async function waitForDomainVerification({
2999
3713
  }
3000
3714
  return current;
3001
3715
  }
3002
- var add = new Command6("add").configureHelp({ showGlobalOptions: true }).description("Add a custom domain to the current Native Hosting project.").argument("<domain>", "Custom domain to add.").addHelpText(
3716
+ var add = new Command7("add").configureHelp({ showGlobalOptions: true }).description("Add a custom domain to the current Native Hosting project.").argument("<domain>", "Custom domain to add.").addHelpText(
3003
3717
  "after",
3004
3718
  `
3005
3719
  Examples:
@@ -3011,13 +3725,35 @@ Examples:
3011
3725
  const context = resolveDomainCommandContext(options);
3012
3726
  await getTelemetry().identify(context.storeHash);
3013
3727
  consola.start(`Adding domain ${domain}...`);
3014
- let result = await createDomain(
3015
- domain,
3016
- context.projectUuid,
3017
- context.storeHash,
3018
- context.accessToken,
3019
- context.apiHost
3020
- );
3728
+ let result;
3729
+ try {
3730
+ result = await createDomain(
3731
+ domain,
3732
+ context.projectUuid,
3733
+ context.storeHash,
3734
+ context.accessToken,
3735
+ context.apiHost
3736
+ );
3737
+ } catch (error) {
3738
+ if (error instanceof DomainOwnershipVerificationError) {
3739
+ consola.warn(`${domain} is already in use on another store.`);
3740
+ consola.log(formatOwnershipVerification(error.ownershipVerification));
3741
+ consola.info("Once the record is live, claim it with:");
3742
+ consola.log(` catalyst domains claim ${domain}`);
3743
+ process.exit(1);
3744
+ return;
3745
+ }
3746
+ if (error instanceof DomainBoundToProjectError) {
3747
+ consola.warn(`${domain} is already bound to another project in this store.`);
3748
+ consola.info("To move it to this project, run:");
3749
+ consola.log(
3750
+ ` catalyst domains transfer ${domain} --project-uuid ${error.projectUuid} --to-project-uuid ${context.projectUuid}`
3751
+ );
3752
+ process.exit(1);
3753
+ return;
3754
+ }
3755
+ throw error;
3756
+ }
3021
3757
  consola.success(`Domain ${result.domain} added.`);
3022
3758
  if (options.wait && result.verification_status === "pending") {
3023
3759
  consola.start(`Waiting for ${result.domain} to verify...`);
@@ -3026,7 +3762,7 @@ Examples:
3026
3762
  consola.log(formatDomain(result));
3027
3763
  process.exit(0);
3028
3764
  });
3029
- var list = new Command6("list").configureHelp({ showGlobalOptions: true }).description("List custom domains for the current Native Hosting project.").addHelpText(
3765
+ var list = new Command7("list").configureHelp({ showGlobalOptions: true }).description("List custom domains for the current Native Hosting project.").addHelpText(
3030
3766
  "after",
3031
3767
  `
3032
3768
  Examples:
@@ -3061,7 +3797,7 @@ Examples:
3061
3797
  result.forEach((item) => consola.log(formatDomain(item)));
3062
3798
  process.exit(0);
3063
3799
  });
3064
- var showStatus = new Command6("status").configureHelp({ showGlobalOptions: true }).description("Show the status of a custom domain on the current Native Hosting project.").argument("<domain>", "Custom domain to check.").addHelpText(
3800
+ var showStatus = new Command7("status").configureHelp({ showGlobalOptions: true }).description("Show the status of a custom domain on the current Native Hosting project.").argument("<domain>", "Custom domain to check.").addHelpText(
3065
3801
  "after",
3066
3802
  `
3067
3803
  Examples:
@@ -3088,7 +3824,143 @@ Examples:
3088
3824
  consola.log(formatDomain(result));
3089
3825
  process.exit(0);
3090
3826
  });
3091
- var remove = new Command6("remove").configureHelp({ showGlobalOptions: true }).description("Remove a custom domain from the current Native Hosting project.").argument("<domain>", "Custom domain to remove.").addHelpText(
3827
+ var claim = new Command7("claim").configureHelp({ showGlobalOptions: true }).description("Claim a custom domain that is currently in use on another store.").argument("<domain>", "Custom domain to claim.").addHelpText(
3828
+ "after",
3829
+ `
3830
+ Claiming requires publishing the ownership-verification TXT record returned when
3831
+ you try to add a domain that is already in use on another store. Publish the
3832
+ record with your DNS provider, then run this command to complete the claim.
3833
+
3834
+ Examples:
3835
+ $ catalyst domains claim www.example.com
3836
+
3837
+ # Wait until the claimed domain leaves pending verification
3838
+ $ catalyst domains claim www.example.com --wait`
3839
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--wait", "Poll until domain verification completes or times out.").action(async (domain, options) => {
3840
+ const context = resolveDomainCommandContext(options);
3841
+ await getTelemetry().identify(context.storeHash);
3842
+ consola.start(`Claiming domain ${domain}...`);
3843
+ try {
3844
+ await claimDomain(
3845
+ domain,
3846
+ context.projectUuid,
3847
+ context.storeHash,
3848
+ context.accessToken,
3849
+ context.apiHost
3850
+ );
3851
+ } catch (error) {
3852
+ if (error instanceof DomainOwnershipVerificationError) {
3853
+ consola.warn(`Ownership of ${domain} could not be verified yet.`);
3854
+ consola.log(formatOwnershipVerification(error.ownershipVerification));
3855
+ consola.info("Once the record is live, run the claim again:");
3856
+ consola.log(` catalyst domains claim ${domain}`);
3857
+ process.exit(1);
3858
+ return;
3859
+ }
3860
+ throw error;
3861
+ }
3862
+ consola.success(`Domain ${domain} claimed.`);
3863
+ let result = await getDomain(
3864
+ domain,
3865
+ context.projectUuid,
3866
+ context.storeHash,
3867
+ context.accessToken,
3868
+ context.apiHost
3869
+ );
3870
+ if (options.wait && result.verification_status === "pending") {
3871
+ consola.start(`Waiting for ${result.domain} to verify...`);
3872
+ result = await waitForDomainVerification({ domain: result.domain, ...context });
3873
+ }
3874
+ consola.log(formatDomain(result));
3875
+ process.exit(0);
3876
+ });
3877
+ var transfer = new Command7("transfer").configureHelp({ showGlobalOptions: true }).description("Transfer a custom domain to another project in the same store.").argument("<domain>", "Custom domain to transfer.").addHelpText(
3878
+ "after",
3879
+ `
3880
+ The domain is transferred from the current project to the destination project.
3881
+ Omit --to-project-uuid to pick the destination from a list of your projects.
3882
+
3883
+ Examples:
3884
+ # Pick the destination project interactively
3885
+ $ catalyst domains transfer www.example.com
3886
+
3887
+ # Transfer to a specific project
3888
+ $ catalyst domains transfer www.example.com --to-project-uuid <uuid>`
3889
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--to-project-uuid <uuid>", "Destination project UUID. Prompts to choose one if omitted.").option("--wait", "Poll until domain verification completes or times out.").action(async (domain, options) => {
3890
+ const context = resolveDomainCommandContext(options);
3891
+ await getTelemetry().identify(context.storeHash);
3892
+ consola.start(`Checking ${domain} on the current project...`);
3893
+ const owned = await findDomain(
3894
+ domain,
3895
+ context.projectUuid,
3896
+ context.storeHash,
3897
+ context.accessToken,
3898
+ context.apiHost
3899
+ );
3900
+ if (!owned) {
3901
+ consola.start(`Locating the project that owns ${domain}...`);
3902
+ const projects = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
3903
+ const candidates = projects.filter((project2) => project2.uuid !== context.projectUuid);
3904
+ const matches = await Promise.all(
3905
+ candidates.map(async (project2) => ({
3906
+ project: project2,
3907
+ found: await findDomain(
3908
+ domain,
3909
+ project2.uuid,
3910
+ context.storeHash,
3911
+ context.accessToken,
3912
+ context.apiHost
3913
+ )
3914
+ }))
3915
+ );
3916
+ const owner = matches.find((match) => match.found)?.project;
3917
+ if (!owner) {
3918
+ throw new UserActionableError(
3919
+ `${domain} isn't on any project in this store. If it's in use on another store, claim it with \`catalyst domains claim ${domain}\`; otherwise double-check the domain name.`
3920
+ );
3921
+ }
3922
+ const toSuffix = options.toProjectUuid ? ` --to-project-uuid ${options.toProjectUuid}` : "";
3923
+ consola.warn(
3924
+ `${domain} is on project "${owner.name}" (${owner.uuid}), not the current project.`
3925
+ );
3926
+ consola.info("Re-run the transfer from that project:");
3927
+ consola.log(` catalyst domains transfer ${domain} --project-uuid ${owner.uuid}${toSuffix}`);
3928
+ process.exit(1);
3929
+ return;
3930
+ }
3931
+ consola.success(`${domain} found on the current project.`);
3932
+ const newProjectUuid = await resolveDestinationProject(domain, context, options.toProjectUuid);
3933
+ consola.start(`Transferring domain ${domain}...`);
3934
+ await transferDomain(
3935
+ domain,
3936
+ context.projectUuid,
3937
+ newProjectUuid,
3938
+ context.storeHash,
3939
+ context.accessToken,
3940
+ context.apiHost
3941
+ );
3942
+ consola.success(`Domain ${domain} transferred.`);
3943
+ let result = await getDomain(
3944
+ domain,
3945
+ newProjectUuid,
3946
+ context.storeHash,
3947
+ context.accessToken,
3948
+ context.apiHost
3949
+ );
3950
+ if (options.wait && result.verification_status === "pending") {
3951
+ consola.start(`Waiting for ${result.domain} to verify...`);
3952
+ result = await waitForDomainVerification({
3953
+ domain: result.domain,
3954
+ projectUuid: newProjectUuid,
3955
+ storeHash: context.storeHash,
3956
+ accessToken: context.accessToken,
3957
+ apiHost: context.apiHost
3958
+ });
3959
+ }
3960
+ consola.log(formatDomain(result));
3961
+ process.exit(0);
3962
+ });
3963
+ var remove = new Command7("remove").configureHelp({ showGlobalOptions: true }).description("Remove a custom domain from the current Native Hosting project.").argument("<domain>", "Custom domain to remove.").addHelpText(
3092
3964
  "after",
3093
3965
  `
3094
3966
  Examples:
@@ -3129,12 +4001,12 @@ Examples:
3129
4001
  consola.success(`Domain ${current.domain} removed.`);
3130
4002
  process.exit(0);
3131
4003
  });
3132
- var domains = new Command6("domains").configureHelp({ showGlobalOptions: true }).description("Manage custom domains for the current Native Hosting project.").addCommand(add).addCommand(list).addCommand(showStatus).addCommand(remove);
4004
+ var domains = new Command7("domains").configureHelp({ showGlobalOptions: true }).description("Manage custom domains for the current Native Hosting project.").addCommand(add).addCommand(list).addCommand(showStatus).addCommand(claim).addCommand(transfer).addCommand(remove);
3133
4005
 
3134
4006
  // src/cli/commands/env.ts
3135
- import { Command as Command7 } from "commander";
4007
+ import { Command as Command8 } from "commander";
3136
4008
  var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022";
3137
- var add2 = new Command7("add").configureHelp({ showGlobalOptions: true }).description(
4009
+ var add2 = new Command8("add").configureHelp({ showGlobalOptions: true }).description(
3138
4010
  "Add or update one or more deployment environment variables. Stored in .bigcommerce/project.json and sent as secrets on every `catalyst deploy`."
3139
4011
  ).argument("<vars...>", "One or more environment variables in KEY=VALUE format.").addHelpText(
3140
4012
  "after",
@@ -3156,7 +4028,7 @@ Example:
3156
4028
  keys.forEach((key) => consola.log(` ${key}=${MASK}`));
3157
4029
  process.exit(0);
3158
4030
  });
3159
- var remove2 = new Command7("remove").configureHelp({ showGlobalOptions: true }).description("Remove one or more stored deployment environment variables.").argument("<keys...>", "One or more environment variable names to remove.").addHelpText(
4031
+ var remove2 = new Command8("remove").configureHelp({ showGlobalOptions: true }).description("Remove one or more stored deployment environment variables.").argument("<keys...>", "One or more environment variable names to remove.").addHelpText(
3160
4032
  "after",
3161
4033
  `
3162
4034
  Example:
@@ -3182,7 +4054,7 @@ Example:
3182
4054
  }
3183
4055
  process.exit(0);
3184
4056
  });
3185
- var list2 = new Command7("list").configureHelp({ showGlobalOptions: true }).description("List stored deployment environment variables (values are masked).").addHelpText(
4057
+ var list2 = new Command8("list").configureHelp({ showGlobalOptions: true }).description("List stored deployment environment variables (values are masked).").addHelpText(
3186
4058
  "after",
3187
4059
  `
3188
4060
  Example:
@@ -3199,14 +4071,14 @@ Example:
3199
4071
  keys.forEach((key) => consola.log(`${key}=${MASK}`));
3200
4072
  process.exit(0);
3201
4073
  });
3202
- var env = new Command7("env").configureHelp({ showGlobalOptions: true }).description(
4074
+ var env = new Command8("env").configureHelp({ showGlobalOptions: true }).description(
3203
4075
  "Manage persistent deployment environment variables. These are sent as secrets on every `catalyst deploy`, so you no longer need to pass `--secret` each time."
3204
4076
  ).addCommand(add2).addCommand(remove2).addCommand(list2);
3205
4077
 
3206
4078
  // src/cli/commands/logs.ts
3207
- import { Command as Command8, InvalidArgumentError as InvalidArgumentError3, Option as Option8 } from "commander";
3208
- import { colorize as colorize8 } from "consola/utils";
3209
- import { z as z14 } from "zod";
4079
+ import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as Option8 } from "commander";
4080
+ import { colorize as colorize10 } from "consola/utils";
4081
+ import { z as z16 } from "zod";
3210
4082
  var telemetry = new Telemetry();
3211
4083
  var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
3212
4084
  var MAX_RETRIES = 5;
@@ -3217,23 +4089,23 @@ var LEVEL_COLORS2 = {
3217
4089
  ERROR: "red",
3218
4090
  DEBUG: "gray"
3219
4091
  };
3220
- var LogEventSchema = z14.object({
3221
- uuid: z14.string(),
3222
- project_uuid: z14.string(),
3223
- request: z14.object({
3224
- method: z14.string(),
3225
- url: z14.string(),
3226
- status_code: z14.number()
4092
+ var LogEventSchema = z16.object({
4093
+ uuid: z16.string(),
4094
+ project_uuid: z16.string(),
4095
+ request: z16.object({
4096
+ method: z16.string(),
4097
+ url: z16.string(),
4098
+ status_code: z16.number()
3227
4099
  }),
3228
- logs: z14.array(
3229
- z14.object({
3230
- timestamp: z14.string(),
3231
- level: z14.string(),
3232
- messages: z14.array(z14.unknown())
4100
+ logs: z16.array(
4101
+ z16.object({
4102
+ timestamp: z16.string(),
4103
+ level: z16.string(),
4104
+ messages: z16.array(z16.unknown())
3233
4105
  })
3234
4106
  ),
3235
- exceptions: z14.array(z14.unknown()),
3236
- timestamp: z14.string()
4107
+ exceptions: z16.array(z16.unknown()),
4108
+ timestamp: z16.string()
3237
4109
  }).loose();
3238
4110
  var StreamError = class extends Error {
3239
4111
  constructor(message, fatal) {
@@ -3247,7 +4119,7 @@ var formatLogEvent = (event, format) => {
3247
4119
  logEntries.forEach((entry) => {
3248
4120
  const msg = formatMessages(entry.messages);
3249
4121
  const level = entry.level.toUpperCase();
3250
- const coloredLevel = colorize8(LEVEL_COLORS2[level] ?? "white", level);
4122
+ const coloredLevel = colorize10(LEVEL_COLORS2[level] ?? "white", level);
3251
4123
  switch (format) {
3252
4124
  case "short":
3253
4125
  consola.log(msg);
@@ -3318,10 +4190,8 @@ var openLogStream = async (projectUuid, storeHash, accessToken, apiHost) => {
3318
4190
  throw new StreamError(new UnauthorizedError().message, true);
3319
4191
  }
3320
4192
  if (!response.ok) {
3321
- throw new StreamError(
3322
- `Failed to open log stream: ${response.status} ${response.statusText}`,
3323
- isFatalStatusCode(response.status)
3324
- );
4193
+ const error = await httpError(response, "Failed to open log stream");
4194
+ throw new StreamError(error.message, isFatalStatusCode(response.status));
3325
4195
  }
3326
4196
  const reader = response.body?.getReader();
3327
4197
  if (!reader) {
@@ -3390,7 +4260,7 @@ var tailLogs = async (projectUuid, storeHash, accessToken, apiHost, format, conn
3390
4260
  }
3391
4261
  }
3392
4262
  };
3393
- var tail = new Command8("tail").configureHelp({ showGlobalOptions: true }).description("Tail live logs from your deployed application.").addHelpText(
4263
+ var tail = new Command9("tail").configureHelp({ showGlobalOptions: true }).description("Tail live logs from your deployed application.").addHelpText(
3394
4264
  "after",
3395
4265
  `
3396
4266
  Examples:
@@ -3406,10 +4276,11 @@ Examples:
3406
4276
  ).action(async (options) => {
3407
4277
  try {
3408
4278
  const config2 = getProjectConfig();
4279
+ const apiHost = resolveApiHost(options, config2);
3409
4280
  const { storeHash, accessToken } = resolveCredentials(options, config2);
3410
4281
  await telemetry.identify(storeHash);
3411
4282
  const projectUuid = resolveProjectUuid(options);
3412
- await tailLogs(projectUuid, storeHash, accessToken, options.apiHost, options.format);
4283
+ await tailLogs(projectUuid, storeHash, accessToken, apiHost, options.format);
3413
4284
  } catch (error) {
3414
4285
  consola.error(error);
3415
4286
  process.exit(1);
@@ -3418,11 +4289,11 @@ Examples:
3418
4289
  var parseIntInRange = (flag, min, max) => (value) => {
3419
4290
  const parsed = Number(value);
3420
4291
  if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
3421
- throw new InvalidArgumentError3(`${flag} must be an integer between ${min} and ${max}.`);
4292
+ throw new InvalidArgumentError4(`${flag} must be an integer between ${min} and ${max}.`);
3422
4293
  }
3423
4294
  return parsed;
3424
4295
  };
3425
- var query = new Command8("query").configureHelp({ showGlobalOptions: true }).description("Query historical logs from your deployed application.").addHelpText(
4296
+ var query = new Command9("query").configureHelp({ showGlobalOptions: true }).description("Query historical logs from your deployed application.").addHelpText(
3426
4297
  "after",
3427
4298
  `
3428
4299
  Specify a time window with \`--since\` (relative to now) or \`--start\`/\`--end\`
@@ -3465,11 +4336,12 @@ Examples:
3465
4336
  ).action(async (options) => {
3466
4337
  try {
3467
4338
  const config2 = getProjectConfig();
4339
+ const apiHost = resolveApiHost(options, config2);
3468
4340
  const { storeHash, accessToken } = resolveCredentials(options, config2);
3469
4341
  await telemetry.identify(storeHash);
3470
4342
  const projectUuid = resolveProjectUuid(options);
3471
4343
  const { start: start2, end } = resolveTimeWindow(options);
3472
- const result = await queryLogs(projectUuid, storeHash, accessToken, options.apiHost, {
4344
+ const result = await queryLogs(projectUuid, storeHash, accessToken, apiHost, {
3473
4345
  start: start2,
3474
4346
  end,
3475
4347
  method: options.method,
@@ -3508,12 +4380,12 @@ Examples:
3508
4380
  process.exit(1);
3509
4381
  }
3510
4382
  });
3511
- var logs = new Command8("logs").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
4383
+ var logs = new Command9("logs").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
3512
4384
 
3513
4385
  // src/cli/commands/project.ts
3514
- import { confirm as confirm5, input as input4, select as select5 } from "@inquirer/prompts";
3515
- import { Command as Command9 } from "commander";
3516
- import { colorize as colorize9 } from "consola/utils";
4386
+ import { confirm as confirm5, input as input5, select as select7 } from "@inquirer/prompts";
4387
+ import { Command as Command10 } from "commander";
4388
+ import { colorize as colorize11 } from "consola/utils";
3517
4389
  async function offerCommerceHostingSetup(projectUuid, credentials) {
3518
4390
  const projectDir = process.cwd();
3519
4391
  if (getProjectState().isTransformed) {
@@ -3532,19 +4404,21 @@ async function offerCommerceHostingSetup(projectUuid, credentials) {
3532
4404
  accessToken: credentials?.accessToken
3533
4405
  });
3534
4406
  consola.success("Commerce Hosting setup complete.");
3535
- await installDependencies(projectDir);
4407
+ const packageManager = await detectProjectPackageManager(projectDir);
4408
+ await installDependencies(projectDir, packageManager);
3536
4409
  }
3537
- var list3 = new Command9("list").configureHelp({ showGlobalOptions: true }).description("List BigCommerce infrastructure projects for your store.").addHelpText(
4410
+ var list3 = new Command10("list").configureHelp({ showGlobalOptions: true }).description("List BigCommerce infrastructure projects for your store.").addHelpText(
3538
4411
  "after",
3539
4412
  `
3540
4413
  Example:
3541
4414
  $ catalyst project list`
3542
4415
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).action(async (options) => {
3543
4416
  const config2 = getProjectConfig();
4417
+ const apiHost = resolveApiHost(options, config2);
3544
4418
  const { storeHash, accessToken } = resolveCredentials(options, config2);
3545
4419
  await getTelemetry().identify(storeHash);
3546
4420
  consola.start("Fetching projects...");
3547
- const projects = await fetchProjects(storeHash, accessToken, options.apiHost);
4421
+ const projects = await fetchProjects(storeHash, accessToken, apiHost);
3548
4422
  consola.success("Projects fetched.");
3549
4423
  if (projects.length === 0) {
3550
4424
  consola.info("No projects found.");
@@ -3553,16 +4427,16 @@ Example:
3553
4427
  }
3554
4428
  const linkedProjectUuid = config2.get("projectUuid");
3555
4429
  const projectList = projects.map((p) => {
3556
- const marker = p.uuid === linkedProjectUuid ? ` ${colorize9("green", "[linked]")}` : "";
4430
+ const marker = p.uuid === linkedProjectUuid ? ` ${colorize11("green", "[linked]")}` : "";
3557
4431
  const hostnames = p.deployment_hostnames.length === 0 ? [" (not deployed)"] : p.deployment_hostnames.map(
3558
- (hostname) => ` ${colorize9("blue", `https://${hostname}`)}`
4432
+ (hostname) => ` ${colorize11("blue", `https://${hostname}`)}`
3559
4433
  );
3560
4434
  return [`${p.name} (${p.uuid})${marker}`, ...hostnames].join("\n");
3561
4435
  });
3562
4436
  consola.log(projectList.join("\n\n"));
3563
4437
  process.exit(0);
3564
4438
  });
3565
- var create2 = new Command9("create").configureHelp({ showGlobalOptions: true }).description(
4439
+ var create3 = new Command10("create").configureHelp({ showGlobalOptions: true }).description(
3566
4440
  "Create a new BigCommerce infrastructure project and link it to your local Catalyst project. Walks you through logging in if you are not authenticated yet."
3567
4441
  ).addHelpText(
3568
4442
  "after",
@@ -3571,6 +4445,7 @@ Example:
3571
4445
  $ catalyst project create`
3572
4446
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).action(async (options) => {
3573
4447
  const config2 = getProjectConfig();
4448
+ const apiHost = resolveApiHost(options, config2);
3574
4449
  let storeHash = options.storeHash ?? config2.get("storeHash");
3575
4450
  let accessToken = options.accessToken ?? config2.get("accessToken");
3576
4451
  if (!storeHash || !accessToken) {
@@ -3578,7 +4453,7 @@ Example:
3578
4453
  "You're not logged in yet. Let's get you authenticated before creating a project."
3579
4454
  );
3580
4455
  try {
3581
- const credentials = await login(options.loginUrl, options.apiHost);
4456
+ const credentials = await login(options.loginUrl, apiHost);
3582
4457
  storeHash = credentials.storeHash;
3583
4458
  accessToken = credentials.accessToken;
3584
4459
  config2.set("storeHash", storeHash);
@@ -3596,8 +4471,8 @@ Example:
3596
4471
  }
3597
4472
  }
3598
4473
  await getTelemetry().identify(storeHash);
3599
- const newProjectName = await input4({ message: "Enter a name for the new project:" });
3600
- const data = await createProject(newProjectName, storeHash, accessToken, options.apiHost);
4474
+ const newProjectName = await input5({ message: "Enter a name for the new project:" });
4475
+ const data = await createProject(newProjectName, storeHash, accessToken, apiHost);
3601
4476
  consola.success(`Project "${data.name}" created successfully.`);
3602
4477
  consola.start("Writing project UUID to .bigcommerce/project.json...");
3603
4478
  config2.set("projectUuid", data.uuid);
@@ -3606,7 +4481,7 @@ Example:
3606
4481
  consola.success("Project UUID written to .bigcommerce/project.json.");
3607
4482
  process.exit(0);
3608
4483
  });
3609
- var link2 = new Command9("link").configureHelp({ showGlobalOptions: true }).description(
4484
+ var link2 = new Command10("link").configureHelp({ showGlobalOptions: true }).description(
3610
4485
  "Link your local Catalyst project to a BigCommerce infrastructure project. You can provide a project UUID directly, or fetch and select from available projects using your store credentials."
3611
4486
  ).addHelpText(
3612
4487
  "after",
@@ -3619,6 +4494,7 @@ Examples:
3619
4494
  $ catalyst project link --project-uuid <UUID>`
3620
4495
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).action(async (options) => {
3621
4496
  const config2 = getProjectConfig();
4497
+ const apiHost = resolveApiHost(options, config2);
3622
4498
  const writeProjectConfig = (uuid, credentials) => {
3623
4499
  consola.start("Writing project UUID to .bigcommerce/project.json...");
3624
4500
  config2.set("projectUuid", uuid);
@@ -3639,7 +4515,7 @@ Examples:
3639
4515
  let selected;
3640
4516
  try {
3641
4517
  selected = await selectOrCreateInfrastructureProject(
3642
- { storeHash, accessToken, apiHost: options.apiHost },
4518
+ { storeHash, accessToken, apiHost },
3643
4519
  config2.get("projectUuid")
3644
4520
  );
3645
4521
  } catch (error) {
@@ -3656,7 +4532,7 @@ Examples:
3656
4532
  await offerCommerceHostingSetup(selected.uuid, { storeHash, accessToken });
3657
4533
  process.exit(0);
3658
4534
  });
3659
- var del = new Command9("delete").configureHelp({ showGlobalOptions: true }).description(
4535
+ var del = new Command10("delete").configureHelp({ showGlobalOptions: true }).description(
3660
4536
  "Permanently delete a BigCommerce infrastructure project. This action is irreversible."
3661
4537
  ).addHelpText(
3662
4538
  "after",
@@ -3672,13 +4548,14 @@ Examples:
3672
4548
  $ catalyst project delete --project-uuid <UUID> --force`
3673
4549
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--force", "Skip the confirmation prompt before deleting.").action(async (options) => {
3674
4550
  const config2 = getProjectConfig();
4551
+ const apiHost = resolveApiHost(options, config2);
3675
4552
  const { storeHash, accessToken } = resolveCredentials(options, config2);
3676
4553
  await getTelemetry().identify(storeHash);
3677
4554
  let targetUuid = options.projectUuid;
3678
4555
  let targetName;
3679
4556
  if (!targetUuid) {
3680
4557
  consola.start("Fetching projects...");
3681
- const projects = await fetchProjects(storeHash, accessToken, options.apiHost);
4558
+ const projects = await fetchProjects(storeHash, accessToken, apiHost);
3682
4559
  consola.success("Projects fetched.");
3683
4560
  if (projects.length === 0) {
3684
4561
  consola.info("No projects found.");
@@ -3686,11 +4563,11 @@ Examples:
3686
4563
  return;
3687
4564
  }
3688
4565
  const linkedProjectUuid = config2.get("projectUuid");
3689
- const selected = await select5({
4566
+ const selected = await select7({
3690
4567
  message: "Select a project to delete (Press <enter> to select).",
3691
4568
  choices: [
3692
4569
  ...projects.map((p) => ({
3693
- name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize9("green", "[linked]")}` : p.name,
4570
+ name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize11("green", "[linked]")}` : p.name,
3694
4571
  value: p.uuid,
3695
4572
  description: p.uuid
3696
4573
  })),
@@ -3722,7 +4599,7 @@ Examples:
3722
4599
  }
3723
4600
  }
3724
4601
  consola.start(`Deleting project ${targetUuid}...`);
3725
- await deleteProject(targetUuid, storeHash, accessToken, options.apiHost);
4602
+ await deleteProject(targetUuid, storeHash, accessToken, apiHost);
3726
4603
  consola.success(`Project ${targetUuid} deleted.`);
3727
4604
  if (config2.get("projectUuid") === targetUuid) {
3728
4605
  config2.delete("projectUuid");
@@ -3730,14 +4607,14 @@ Examples:
3730
4607
  }
3731
4608
  process.exit(0);
3732
4609
  });
3733
- var project = new Command9("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure project.").addCommand(create2).addCommand(list3).addCommand(link2).addCommand(del);
4610
+ var project = new Command10("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure project.").addCommand(create3).addCommand(list3).addCommand(link2).addCommand(del);
3734
4611
 
3735
4612
  // src/cli/commands/start.ts
3736
- import { Command as Command10 } from "commander";
3737
- import { execa as execa2 } from "execa";
3738
- import { existsSync as existsSync3, lstatSync, symlinkSync } from "fs";
4613
+ import { Command as Command11 } from "commander";
4614
+ import { execa as execa3 } from "execa";
4615
+ import { existsSync as existsSync6, lstatSync, symlinkSync } from "fs";
3739
4616
  import { join as join13, relative } from "path";
3740
- var start = new Command10("start").configureHelp({ showGlobalOptions: true }).description(
4617
+ var start = new Command11("start").configureHelp({ showGlobalOptions: true }).description(
3741
4618
  "Start a local preview of your Catalyst storefront using the OpenNext Cloudflare adapter."
3742
4619
  ).addHelpText(
3743
4620
  "after",
@@ -3748,7 +4625,7 @@ Example:
3748
4625
  const state = getProjectState();
3749
4626
  if (!state.isTransformed) {
3750
4627
  consola.info("Project is not set up for Commerce Hosting \u2014 running `next start`.");
3751
- await execa2("pnpm", ["exec", "next", "start"], {
4628
+ await execa3("pnpm", ["exec", "next", "start"], {
3752
4629
  stdio: "inherit",
3753
4630
  cwd: process.cwd()
3754
4631
  });
@@ -3756,8 +4633,8 @@ Example:
3756
4633
  }
3757
4634
  const envLocal = join13(process.cwd(), ".env.local");
3758
4635
  const devVars = join13(process.cwd(), ".bigcommerce", ".dev.vars");
3759
- if (existsSync3(envLocal)) {
3760
- if (!existsSync3(devVars)) {
4636
+ if (existsSync6(envLocal)) {
4637
+ if (!existsSync6(devVars)) {
3761
4638
  const target = relative(join13(process.cwd(), ".bigcommerce"), envLocal);
3762
4639
  symlinkSync(target, devVars);
3763
4640
  consola.success("Symlinked .bigcommerce/.dev.vars \u2192 .env.local");
@@ -3771,7 +4648,7 @@ Example:
3771
4648
  "No .env.local file found. The preview server may fail without environment variables.\nCreate a .env.local file in your project root with the required variables."
3772
4649
  );
3773
4650
  }
3774
- await execa2(
4651
+ await execa3(
3775
4652
  "pnpm",
3776
4653
  [
3777
4654
  "exec",
@@ -3788,11 +4665,11 @@ Example:
3788
4665
  });
3789
4666
 
3790
4667
  // src/cli/commands/telemetry.ts
3791
- import { Argument, Command as Command11, Option as Option9 } from "commander";
3792
- import { colorize as colorize10 } from "consola/utils";
4668
+ import { Argument, Command as Command12, Option as Option9 } from "commander";
4669
+ import { colorize as colorize12 } from "consola/utils";
3793
4670
  var telemetryService = getTelemetry();
3794
4671
  var isEnabled = telemetryService.isEnabled();
3795
- var telemetry2 = new Command11("telemetry").configureHelp({ showGlobalOptions: true }).description(
4672
+ var telemetry2 = new Command12("telemetry").configureHelp({ showGlobalOptions: true }).description(
3796
4673
  "View or change CLI telemetry collection status. Enabling telemetry helps BigCommerce support diagnose and troubleshoot errors you encounter when using the CLI."
3797
4674
  ).addHelpText(
3798
4675
  "after",
@@ -3823,7 +4700,7 @@ Examples:
3823
4700
  consola.info("Catalyst CLI Telemetry\n");
3824
4701
  }
3825
4702
  consola.info(
3826
- `Status: ${colorize10("bold", isEnabled ? colorize10("green", "Enabled") : colorize10("red", "Disabled"))}`
4703
+ `Status: ${colorize12("bold", isEnabled ? colorize12("green", "Enabled") : colorize12("red", "Disabled"))}`
3827
4704
  );
3828
4705
  if (!isEnabled) {
3829
4706
  consola.info(
@@ -3833,9 +4710,9 @@ Examples:
3833
4710
  });
3834
4711
 
3835
4712
  // src/cli/commands/upgrade.ts
3836
- import { Command as Command12, Option as Option10 } from "@commander-js/extra-typings";
4713
+ import { Command as Command13, Option as Option10 } from "@commander-js/extra-typings";
3837
4714
  import { confirm as confirm6 } from "@inquirer/prompts";
3838
- import { execa as execa3 } from "execa";
4715
+ import { execa as execa4 } from "execa";
3839
4716
  import {
3840
4717
  access as access2,
3841
4718
  copyFile as copyFile2,
@@ -3849,13 +4726,13 @@ import {
3849
4726
  writeFile as writeFile2
3850
4727
  } from "fs/promises";
3851
4728
  import { homedir, tmpdir } from "os";
3852
- import { dirname as dirname4, join as join14, relative as relative2 } from "path";
4729
+ import { dirname as dirname5, join as join14, relative as relative2 } from "path";
3853
4730
  import yoctoSpinner4 from "yocto-spinner";
3854
- import { z as z15 } from "zod";
3855
- var CorePackageJson = z15.object({
3856
- name: z15.string().optional(),
3857
- version: z15.string(),
3858
- catalyst: z15.object({ version: z15.string(), ref: z15.string() }).optional()
4731
+ import { z as z17 } from "zod";
4732
+ var CorePackageJson = z17.object({
4733
+ name: z17.string().optional(),
4734
+ version: z17.string(),
4735
+ catalyst: z17.object({ version: z17.string(), ref: z17.string() }).optional()
3859
4736
  });
3860
4737
  var DEFAULT_REPOSITORY = "bigcommerce/catalyst";
3861
4738
  var MOVING_TAGS = /* @__PURE__ */ new Set(["latest", "canary", "alpha"]);
@@ -3898,7 +4775,7 @@ async function isBinary(p) {
3898
4775
  return buf.subarray(0, 8e3).includes(0);
3899
4776
  }
3900
4777
  async function copyInto(src, dest) {
3901
- await mkdir(dirname4(dest), { recursive: true });
4778
+ await mkdir(dirname5(dest), { recursive: true });
3902
4779
  await copyFile2(src, dest);
3903
4780
  }
3904
4781
  function parseRef(ref) {
@@ -3912,7 +4789,7 @@ async function readResolvedVersion(coreDir) {
3912
4789
  return pkg.catalyst?.version ?? pkg.version;
3913
4790
  }
3914
4791
  async function mergeFile(oursPath, basePath, theirsPath) {
3915
- const merged = await execa3(
4792
+ const merged = await execa4(
3916
4793
  "git",
3917
4794
  [
3918
4795
  "merge-file",
@@ -3995,7 +4872,7 @@ async function mergeCorePerFile(baseDir, theirsDir, catalystRoot, emptyFile) {
3995
4872
  }
3996
4873
  async function gitSupportsMergeTree() {
3997
4874
  try {
3998
- const { stdout } = await execa3("git", ["--version"]);
4875
+ const { stdout } = await execa4("git", ["--version"]);
3999
4876
  const match = /(\d+)\.(\d+)/.exec(stdout);
4000
4877
  if (!match) return false;
4001
4878
  const [major, minor] = [Number(match[1]), Number(match[2])];
@@ -4013,8 +4890,8 @@ async function resolveStrategy(strategy) {
4013
4890
  async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
4014
4891
  const scratch = await mkdtemp(join14(tmpdir(), "catalyst-merge-"));
4015
4892
  try {
4016
- await execa3("git", ["init", "-q", scratch]);
4017
- await execa3("git", ["config", "core.autocrlf", "false"], {
4893
+ await execa4("git", ["init", "-q", scratch]);
4894
+ await execa4("git", ["config", "core.autocrlf", "false"], {
4018
4895
  env: { ...process.env, GIT_DIR: join14(scratch, ".git") }
4019
4896
  });
4020
4897
  const env2 = {
@@ -4031,10 +4908,10 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
4031
4908
  GIT_WORK_TREE: workTree,
4032
4909
  GIT_INDEX_FILE: join14(scratch, `index.${tag}`)
4033
4910
  };
4034
- await execa3("git", ["add", "-A"], { env: sideEnv });
4035
- return (await execa3("git", ["write-tree"], { env: sideEnv })).stdout.trim();
4911
+ await execa4("git", ["add", "-A"], { env: sideEnv });
4912
+ return (await execa4("git", ["write-tree"], { env: sideEnv })).stdout.trim();
4036
4913
  };
4037
- const commitTree = (tree, parent) => execa3("git", ["commit-tree", tree, "-m", "x", ...parent ? ["-p", parent] : []], {
4914
+ const commitTree = (tree, parent) => execa4("git", ["commit-tree", tree, "-m", "x", ...parent ? ["-p", parent] : []], {
4038
4915
  env: env2
4039
4916
  }).then((r) => r.stdout.trim());
4040
4917
  const baseTree = await writeTree(baseDir, "base");
@@ -4046,10 +4923,10 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
4046
4923
  commitTree(theirsTree, baseCommit)
4047
4924
  ]);
4048
4925
  await Promise.all([
4049
- execa3("git", ["branch", "ours", oursCommit], { env: env2 }),
4050
- execa3("git", ["branch", "theirs", theirsCommit], { env: env2 })
4926
+ execa4("git", ["branch", "ours", oursCommit], { env: env2 }),
4927
+ execa4("git", ["branch", "theirs", theirsCommit], { env: env2 })
4051
4928
  ]);
4052
- const merge = await execa3(
4929
+ const merge = await execa4(
4053
4930
  "git",
4054
4931
  [
4055
4932
  "merge-tree",
@@ -4075,9 +4952,9 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
4075
4952
  GIT_WORK_TREE: catalystRoot,
4076
4953
  GIT_INDEX_FILE: join14(scratch, "index.out")
4077
4954
  };
4078
- await execa3("git", ["read-tree", mergedTree], { env: outEnv });
4079
- await execa3("git", ["checkout-index", "-a", "-f"], { env: outEnv });
4080
- const diff = await execa3(
4955
+ await execa4("git", ["read-tree", mergedTree], { env: outEnv });
4956
+ await execa4("git", ["checkout-index", "-a", "-f"], { env: outEnv });
4957
+ const diff = await execa4(
4081
4958
  "git",
4082
4959
  ["diff", "-z", "--no-renames", "--name-status", oursTree, mergedTree],
4083
4960
  { env: env2 }
@@ -4112,19 +4989,19 @@ async function applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stam
4112
4989
  if (stampedPkg) clean.push(toRepoPath(pkgRel));
4113
4990
  const toStage = [...new Set(clean)].filter((path) => !conflictedRepoPaths.has(path));
4114
4991
  if (toStage.length) {
4115
- await execa3("git", ["add", "-A", "--", ...toStage], { cwd: gitRoot });
4992
+ await execa4("git", ["add", "-A", "--", ...toStage], { cwd: gitRoot });
4116
4993
  }
4117
4994
  if (stillConflicted.length === 0) return;
4118
4995
  const OID = /^[0-9a-f]{40,64}$/;
4119
- const gitFormat = (await execa3("git", ["rev-parse", "--show-object-format"], { cwd: gitRoot, reject: false })).stdout.trim();
4996
+ const gitFormat = (await execa4("git", ["rev-parse", "--show-object-format"], { cwd: gitRoot, reject: false })).stdout.trim();
4120
4997
  const NULL_OID = gitFormat === "sha256" ? "0".repeat(64) : "0".repeat(40);
4121
4998
  const hashBlob = async (file) => {
4122
4999
  if (!await pathExists(file)) return null;
4123
- const oid = (await execa3("git", ["hash-object", "-w", "--", file], { cwd: gitRoot })).stdout.trim();
5000
+ const oid = (await execa4("git", ["hash-object", "-w", "--", file], { cwd: gitRoot })).stdout.trim();
4124
5001
  return OID.test(oid) ? oid : null;
4125
5002
  };
4126
5003
  const headEntry = async (repoPath) => {
4127
- const { stdout } = await execa3("git", ["ls-tree", "HEAD", "--", repoPath], {
5004
+ const { stdout } = await execa4("git", ["ls-tree", "HEAD", "--", repoPath], {
4128
5005
  cwd: gitRoot,
4129
5006
  reject: false
4130
5007
  });
@@ -4147,7 +5024,7 @@ async function applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stam
4147
5024
  ].join("\n");
4148
5025
  })
4149
5026
  );
4150
- await execa3("git", ["update-index", "--index-info"], {
5027
+ await execa4("git", ["update-index", "--index-info"], {
4151
5028
  cwd: gitRoot,
4152
5029
  input: `${records.join("\n")}
4153
5030
  `
@@ -4156,7 +5033,7 @@ async function applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stam
4156
5033
  async function resolveProject2(cwd) {
4157
5034
  let gitRoot;
4158
5035
  try {
4159
- gitRoot = (await execa3("git", ["rev-parse", "--show-toplevel"], { cwd })).stdout.trim();
5036
+ gitRoot = (await execa4("git", ["rev-parse", "--show-toplevel"], { cwd })).stdout.trim();
4160
5037
  } catch {
4161
5038
  return null;
4162
5039
  }
@@ -4212,7 +5089,7 @@ async function downloadCore(repository, ref, destDir, token) {
4212
5089
  const rawDir = `${destDir}.raw`;
4213
5090
  await writeFile2(tarballPath, Buffer.from(await res.arrayBuffer()));
4214
5091
  await mkdir(rawDir, { recursive: true });
4215
- await execa3("tar", ["-xzf", tarballPath, "-C", rawDir, "--strip-components=1"]);
5092
+ await execa4("tar", ["-xzf", tarballPath, "-C", rawDir, "--strip-components=1"]);
4216
5093
  const coreDir = join14(rawDir, "core");
4217
5094
  const sourceDir = await pathExists(coreDir) ? coreDir : rawDir;
4218
5095
  await rename(sourceDir, destDir);
@@ -4261,7 +5138,7 @@ Resolve them, then: git add ${relDir} && git commit`
4261
5138
  consola.success("Staged all changes \u2014 review with `git diff --cached`, then commit.");
4262
5139
  }
4263
5140
  }
4264
- var upgrade = new Command12("upgrade").configureHelp({ showGlobalOptions: true }).aliases([
5141
+ var upgrade = new Command13("upgrade").configureHelp({ showGlobalOptions: true }).aliases([
4265
5142
  "up",
4266
5143
  // These are hidden aliases
4267
5144
  "upgrayedd",
@@ -4329,7 +5206,7 @@ to raise the GitHub API rate limit.`
4329
5206
  }
4330
5207
  if (!options.dryRun) {
4331
5208
  const statusArgs = relDir === "." ? [] : ["--", relDir];
4332
- const status = await execa3("git", ["status", "--porcelain", ...statusArgs], {
5209
+ const status = await execa4("git", ["status", "--porcelain", ...statusArgs], {
4333
5210
  cwd: gitRoot,
4334
5211
  reject: false
4335
5212
  });
@@ -4346,11 +5223,11 @@ to raise the GitHub API rate limit.`
4346
5223
  if (!project2.pkg.catalyst?.ref && !options.from && !options.dryRun) {
4347
5224
  const { version: baseVersion } = parseRef(baseRef);
4348
5225
  const rawPkg = await readFile2(pkgPath, "utf-8");
4349
- const parsedPkg = z15.record(z15.string(), z15.unknown()).parse(JSON.parse(rawPkg));
5226
+ const parsedPkg = z17.record(z17.string(), z17.unknown()).parse(JSON.parse(rawPkg));
4350
5227
  parsedPkg.catalyst = { version: baseVersion, ref: baseRef };
4351
5228
  await writeFile2(pkgPath, `${JSON.stringify(parsedPkg, null, 2)}
4352
5229
  `);
4353
- await execa3("git", ["add", "--", pkgPath], { cwd: gitRoot });
5230
+ await execa4("git", ["add", "--", pkgPath], { cwd: gitRoot });
4354
5231
  consola.success(`Added catalyst.ref \u2192 ${baseRef}`);
4355
5232
  }
4356
5233
  const token = process.env.GITHUB_TOKEN;
@@ -4384,7 +5261,7 @@ to raise the GitHub API rate limit.`
4384
5261
  const newRef = MOVING_TAGS.has(upstreamTagVersion) ? `${basePackage}@${resolvedVersion}` : upstreamRef;
4385
5262
  if (options.dryRun) {
4386
5263
  const diffSpinner = yoctoSpinner4().start("Generating diff...");
4387
- const diff = await execa3(
5264
+ const diff = await execa4(
4388
5265
  "git",
4389
5266
  ["diff", "--no-index", "--binary", "--diff-algorithm=histogram", "base", "theirs"],
4390
5267
  { cwd: tmpDir, reject: false, stripFinalNewline: false }
@@ -4419,7 +5296,7 @@ to raise the GitHub API rate limit.`
4419
5296
  const patchedRaw = await readFile2(pkgPath, "utf-8");
4420
5297
  let stampedPkg = false;
4421
5298
  try {
4422
- const patchedPkg = z15.record(z15.string(), z15.unknown()).parse(JSON.parse(patchedRaw));
5299
+ const patchedPkg = z17.record(z17.string(), z17.unknown()).parse(JSON.parse(patchedRaw));
4423
5300
  patchedPkg.catalyst = { version: resolvedVersion, ref: newRef };
4424
5301
  await writeFile2(pkgPath, `${JSON.stringify(patchedPkg, null, 2)}
4425
5302
  `);
@@ -4455,7 +5332,7 @@ to raise the GitHub API rate limit.`
4455
5332
  `Couldn't auto-stage the changes (${err instanceof Error ? err.message : String(err)}). Your files are merged on disk \u2014 run \`git add ${relDir}\` yourself.`
4456
5333
  );
4457
5334
  }
4458
- const gitVersion = await execa3("git", ["--version"]).then((r) => r.stdout.trim()).catch(() => "unknown");
5335
+ const gitVersion = await execa4("git", ["--version"]).then((r) => r.stdout.trim()).catch(() => "unknown");
4459
5336
  await getTelemetry().track("upgrade", {
4460
5337
  strategy,
4461
5338
  gitVersion,
@@ -4473,8 +5350,8 @@ to raise the GitHub API rate limit.`
4473
5350
  });
4474
5351
 
4475
5352
  // src/cli/commands/version.ts
4476
- import { Command as Command13 } from "commander";
4477
- var version = new Command13("version").configureHelp({ showGlobalOptions: true }).description("Display detailed version information.").addHelpText(
5353
+ import { Command as Command14 } from "commander";
5354
+ var version = new Command14("version").configureHelp({ showGlobalOptions: true }).description("Display detailed version information.").addHelpText(
4478
5355
  "after",
4479
5356
  `
4480
5357
  Example:
@@ -4535,41 +5412,15 @@ var telemetryPostHook = async () => {
4535
5412
  };
4536
5413
 
4537
5414
  // src/cli/program.ts
4538
- var defaultEnvPath = resolve(process.cwd(), ".env.local");
4539
- if (existsSync4(defaultEnvPath)) {
4540
- config({ path: defaultEnvPath });
4541
- }
4542
5415
  if (!process.env.CATALYST_STORE_HASH && process.env.BIGCOMMERCE_STORE_HASH) {
4543
5416
  process.env.CATALYST_STORE_HASH = process.env.BIGCOMMERCE_STORE_HASH;
4544
5417
  }
4545
- var program = new Command14();
4546
- consola.log(colorize11("cyanBright", `\u25E2 ${package_default.name} v${package_default.version}
5418
+ var program = new Command15();
5419
+ consola.log(colorize13("cyanBright", `\u25E2 ${package_default.name} v${package_default.version}
4547
5420
  `));
4548
5421
  program.name(package_default.name).version(package_default.version).summary("CLI tool for Catalyst development").description(
4549
- "CLI tool for Catalyst development.\n\nConfiguration priority: flags > env file (--env-path) > process.env > .env.local (auto-loaded from cwd) > .bigcommerce/project.json.\n\nCATALYST_STORE_HASH falls back to BIGCOMMERCE_STORE_HASH if unset.\n\nRun `catalyst <command> --help` for details on a specific command."
4550
- ).configureHelp({ showGlobalOptions: true }).addOption(
4551
- new Option11(
4552
- "--env-path <path>",
4553
- "Path to environment file to load (relative to current working directory)"
4554
- // We are using argParser, because commander loads in environment variables before executing hooks.
4555
- ).argParser((value) => {
4556
- if (value) {
4557
- const envFilePath = resolve(process.cwd(), value);
4558
- const result = config({
4559
- path: envFilePath,
4560
- override: true
4561
- });
4562
- if (result.error) {
4563
- const errCode = "code" in result.error && typeof result.error.code === "string" ? result.error.code : void 0;
4564
- const message = errCode === "ENOENT" ? `Env file not found: ${envFilePath}` : `Failed to load --env-path ${value}: ${result.error.message}`;
4565
- throw new Error(message);
4566
- }
4567
- consola.log(colorize11("cyanBright", `Loaded environment variables from ${envFilePath}
4568
- `));
4569
- }
4570
- return value;
4571
- })
4572
- ).addCommand(version).addCommand(create).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(project).addCommand(env).addCommand(channel).addCommand(auth).addCommand(upgrade).addCommand(telemetry2).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
5422
+ "CLI tool for Catalyst development.\n\nConfiguration priority: flags > process.env > .bigcommerce/project.json. `build` and `deploy` additionally load env files (--env-path, or an auto-loaded .env.local/.env) for the build.\n\nCATALYST_STORE_HASH falls back to BIGCOMMERCE_STORE_HASH if unset.\n\nRun `catalyst <command> --help` for details on a specific command."
5423
+ ).configureHelp({ showGlobalOptions: true }).addCommand(version).addCommand(create2).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(project).addCommand(env).addCommand(channel).addCommand(auth).addCommand(upgrade).addCommand(telemetry2).addCommand(debug).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
4573
5424
 
4574
5425
  // src/cli/index.ts
4575
5426
  var handleFatalError = async (error) => {
@@ -4586,7 +5437,7 @@ var handleFatalError = async (error) => {
4586
5437
  await telemetry3.analytics.closeAndFlush();
4587
5438
  } catch {
4588
5439
  }
4589
- if (error instanceof UnauthorizedError) {
5440
+ if (error instanceof UserActionableError) {
4590
5441
  consola.error(errorMessage);
4591
5442
  process.exit(1);
4592
5443
  }