@bigcommerce/catalyst 1.0.0 → 1.1.1

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