@bigcommerce/catalyst 1.4.0 → 1.4.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.
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ import { randomBytes, randomUUID } from "crypto";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@bigcommerce/catalyst",
28
- version: "1.4.0",
28
+ version: "1.4.1",
29
29
  type: "module",
30
30
  repository: {
31
31
  type: "git",
@@ -215,7 +215,7 @@ import { Command as Command15 } from "commander";
215
215
  import { colorize as colorize13 } from "consola/utils";
216
216
 
217
217
  // src/cli/commands/auth.ts
218
- import { Command } from "commander";
218
+ import { Command, Option as Option2 } from "commander";
219
219
  import { z as z5 } from "zod";
220
220
 
221
221
  // src/cli/lib/auth-errors.ts
@@ -685,6 +685,19 @@ async function fetchStoreProfile2(storeHash, accessToken, apiHost) {
685
685
  }
686
686
  return result.data.data;
687
687
  }
688
+ async function checkCredentials(storeHash, accessToken, apiHost) {
689
+ try {
690
+ const { store_name: storeName } = await fetchStoreProfile2(storeHash, accessToken, apiHost);
691
+ return { valid: true, storeName };
692
+ } catch (error) {
693
+ if (error instanceof UnauthorizedError) return { valid: false, reason: "unauthorized" };
694
+ return {
695
+ valid: false,
696
+ reason: "unverifiable",
697
+ message: error instanceof Error ? error.message : String(error)
698
+ };
699
+ }
700
+ }
688
701
  var whoami = new Command("whoami").configureHelp({ showGlobalOptions: true }).description("Verify stored credentials and display store/project info.").addHelpText(
689
702
  "after",
690
703
  `
@@ -739,7 +752,7 @@ Example:
739
752
  }
740
753
  });
741
754
  var login2 = new Command("login").configureHelp({ showGlobalOptions: true }).description(
742
- "Authenticate via browser using the OAuth device code flow. Falls back to an interactive store hash + access token prompt if the browser flow is unavailable. If already logged in, displays current credentials and suggests running `catalyst auth logout` to re-authenticate."
755
+ "Authenticate via browser using the OAuth device code flow. Falls back to an interactive store hash + access token prompt if the browser flow is unavailable. Passing --store-hash and --access-token verifies and stores those credentials instead. If already logged in with credentials that still work, displays them and suggests `catalyst auth logout`; if the stored token has expired or been revoked, re-authenticates in place."
743
756
  ).addHelpText(
744
757
  "after",
745
758
  `
@@ -748,19 +761,67 @@ Examples:
748
761
  $ catalyst auth login
749
762
 
750
763
  # Login with existing credentials (skips interactive flow)
751
- $ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN>`
752
- ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).action(async (options) => {
764
+ $ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN>
765
+
766
+ # Re-authenticate without checking the stored credentials first
767
+ $ catalyst auth login --force`
768
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
769
+ new Option2(
770
+ "--force",
771
+ "Skip the check on any credentials already stored and re-authenticate regardless."
772
+ )
773
+ ).action(async (options, command) => {
753
774
  try {
754
775
  const config2 = getProjectConfig();
755
776
  const apiHost = resolveApiHost(options, config2);
756
777
  const storeHash = options.storeHash ?? config2.get("storeHash");
757
778
  const accessToken = options.accessToken ?? config2.get("accessToken");
758
- if (storeHash && accessToken) {
759
- consola.info(`Already logged in to store ${storeHash}.`);
760
- consola.info("Run `catalyst auth logout` first to re-authenticate.");
779
+ const suppliedOnCli = command.getOptionValueSource("storeHash") === "cli" && command.getOptionValueSource("accessToken") === "cli";
780
+ if (suppliedOnCli && storeHash && accessToken) {
781
+ if (!options.force) {
782
+ const status = await checkCredentials(storeHash, accessToken, apiHost);
783
+ if (!status.valid && status.reason === "unauthorized") {
784
+ consola.error(`The credentials provided for store ${storeHash} were rejected.`);
785
+ consola.info(
786
+ "Check the store hash and access token, or run `catalyst auth login` without them to authenticate in the browser."
787
+ );
788
+ process.exit(1);
789
+ return;
790
+ }
791
+ if (!status.valid) {
792
+ consola.warn(`Couldn't verify the credentials provided: ${status.message}`);
793
+ }
794
+ }
795
+ config2.set("storeHash", storeHash);
796
+ config2.set("accessToken", accessToken);
797
+ consola.success(`Logged in to store ${storeHash}.`);
761
798
  process.exit(0);
762
799
  return;
763
800
  }
801
+ if (storeHash && accessToken && !options.force) {
802
+ const status = await checkCredentials(storeHash, accessToken, apiHost);
803
+ if (status.valid) {
804
+ consola.info(`Already logged in to ${status.storeName} (${storeHash}).`);
805
+ consola.info(
806
+ "Run `catalyst auth logout` first to re-authenticate, or `catalyst auth login --force` to skip this check."
807
+ );
808
+ process.exit(0);
809
+ return;
810
+ }
811
+ if (status.reason === "unverifiable") {
812
+ consola.warn(
813
+ `Found credentials for store ${storeHash} but couldn't verify them: ${status.message}`
814
+ );
815
+ consola.info(
816
+ "Keeping them. Re-run once the API is reachable, or `catalyst auth login --force` to re-authenticate regardless."
817
+ );
818
+ process.exit(0);
819
+ return;
820
+ }
821
+ consola.warn(
822
+ `Stored credentials for store ${storeHash} are no longer valid \u2014 re-authenticating.`
823
+ );
824
+ }
764
825
  const credentials = await login(options.loginUrl, apiHost);
765
826
  config2.set("storeHash", credentials.storeHash);
766
827
  config2.set("accessToken", credentials.accessToken);
@@ -807,7 +868,7 @@ Example:
807
868
  var auth = new Command("auth").configureHelp({ showGlobalOptions: true }).description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(login2).addCommand(logout);
808
869
 
809
870
  // src/cli/commands/build.ts
810
- import { Command as Command2, InvalidArgumentError, Option as Option2 } from "commander";
871
+ import { Command as Command2, InvalidArgumentError, Option as Option3 } from "commander";
811
872
  import { execa as execa2 } from "execa";
812
873
  import { copyFile, cp, rm, writeFile } from "fs/promises";
813
874
  import { join as join6 } from "path";
@@ -918,7 +979,7 @@ var cleanupCloudflareIncompatibilities = async (projectDir) => {
918
979
  return;
919
980
  }
920
981
  const shouldRemove = await confirm2({
921
- message: 'Catalyst found instrumentation.ts, which is incompatible with the Cloudflare Workers bundle when it uses @vercel/otel (causes "Failed to prepare server" at cold start). Remove it and drop @vercel/otel from package.json?',
982
+ message: 'Catalyst found instrumentation.ts, which is incompatible with the Cloudflare Workers bundle when it uses @vercel/otel (causes "Failed to prepare server" at cold start).\nRemove it and drop @vercel/otel from package.json? (required for native hosting)',
922
983
  default: true
923
984
  });
924
985
  if (!shouldRemove) {
@@ -1523,12 +1584,12 @@ Examples:
1523
1584
  # Build with a specific Wrangler version
1524
1585
  $ catalyst build --wrangler-version 4.24.3`
1525
1586
  ).addOption(
1526
- new Option2(
1587
+ new Option3(
1527
1588
  "--project-uuid <uuid>",
1528
1589
  "Project UUID to be included in the deployment configuration."
1529
1590
  ).env("CATALYST_PROJECT_UUID")
1530
1591
  ).addOption(
1531
- new Option2(
1592
+ new Option3(
1532
1593
  "--wrangler-version <version>",
1533
1594
  `Wrangler version or dist-tag to build with. Defaults to ${WRANGLER_VERSION}.`
1534
1595
  ).argParser(parseWranglerVersion)
@@ -1557,7 +1618,7 @@ Examples:
1557
1618
 
1558
1619
  // src/cli/commands/channels.ts
1559
1620
  import { confirm as confirm3, select as select4 } from "@inquirer/prompts";
1560
- import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option3 } from "commander";
1621
+ import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option4 } from "commander";
1561
1622
  import { colorize as colorize5 } from "consola/utils";
1562
1623
 
1563
1624
  // src/cli/lib/channel-checkout-url-flow.ts
@@ -1900,7 +1961,7 @@ async function resolveChannel(options) {
1900
1961
  );
1901
1962
  }
1902
1963
  const id = await select2({
1903
- message: "Select the channel to update.",
1964
+ message: options.message ?? "Select the channel to update.",
1904
1965
  choices: catalystChannels.map((c) => ({
1905
1966
  name: c.name,
1906
1967
  value: c.id,
@@ -2317,22 +2378,22 @@ Examples:
2317
2378
  # Fall back to the shared checkout domain
2318
2379
  $ catalyst channels update --channel-id 123 --remove-checkout-url`
2319
2380
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
2320
- new Option3(
2381
+ new Option4(
2321
2382
  "--channel-id <id>",
2322
2383
  "Skip the channel prompt and target this channel directly."
2323
2384
  ).argParser((value) => Number(value))
2324
2385
  ).addOption(
2325
- new Option3(
2386
+ new Option4(
2326
2387
  "--hostname <hostname>",
2327
2388
  "Skip the hostname prompt and use this hostname directly. Must be one of the project's deployment_hostnames."
2328
2389
  )
2329
2390
  ).addOption(
2330
- new Option3(
2391
+ new Option4(
2331
2392
  "--checkout-url <url>",
2332
2393
  "Set the channel's checkout URL. Must share a main domain with the storefront URL."
2333
2394
  ).argParser(parseCheckoutUrl)
2334
2395
  ).addOption(
2335
- new Option3(
2396
+ new Option4(
2336
2397
  "--remove-checkout-url",
2337
2398
  "Remove the channel's custom checkout URL so checkout falls back to the shared checkout domain."
2338
2399
  ).conflicts("checkoutUrl")
@@ -2406,14 +2467,14 @@ Examples:
2406
2467
  # Append extra environment variables to .env.local
2407
2468
  $ catalyst channels link --channel-id 123 --env MY_FLAG=1`
2408
2469
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
2409
- new Option3("--channel-id <id>", "Link this channel directly, skipping the picker.").argParser(
2470
+ new Option4("--channel-id <id>", "Link this channel directly, skipping the picker.").argParser(
2410
2471
  parseChannelId
2411
2472
  )
2412
2473
  ).option(
2413
2474
  "--env <vars...>",
2414
2475
  "Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable)."
2415
2476
  ).addOption(
2416
- new Option3("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
2477
+ new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
2417
2478
  ).action(async (options) => {
2418
2479
  const config2 = getProjectConfig();
2419
2480
  const apiHost = resolveApiHost(options, config2);
@@ -2484,11 +2545,11 @@ Examples:
2484
2545
  ).option(
2485
2546
  "--additional-locales <locales...>",
2486
2547
  "Additional storefront locales, max 4 (e.g. --additional-locales es fr). Skips the additional-languages prompt."
2487
- ).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(
2548
+ ).addOption(new Option4("--sample-data", "Install sample data on the new channel.")).addOption(new Option4("--no-sample-data", "Create the channel without sample data.")).option(
2488
2549
  "--link",
2489
2550
  "Link the new channel to this project \u2014 write its credentials to .env.local without prompting."
2490
2551
  ).addOption(
2491
- new Option3("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
2552
+ new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
2492
2553
  ).action(async (options) => {
2493
2554
  if (options.additionalLocales && options.additionalLocales.length > 4) {
2494
2555
  consola.error("You can only set up to 4 additional locales.");
@@ -2570,7 +2631,10 @@ async function resolveChannelTarget(options) {
2570
2631
  storeHash,
2571
2632
  accessToken,
2572
2633
  apiHost,
2573
- channelId: options.channelId
2634
+ channelId: options.channelId,
2635
+ // Read-only: these commands report on a channel, so the picker must not
2636
+ // say "to update" — the shared default is written for `channels update`.
2637
+ message: "Select a channel."
2574
2638
  });
2575
2639
  return {
2576
2640
  config: config2,
@@ -2592,7 +2656,7 @@ Examples:
2592
2656
  # Target a channel directly
2593
2657
  $ catalyst channels info --channel-id 123`
2594
2658
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(
2595
- new Option3(
2659
+ new Option4(
2596
2660
  "--channel-id <id>",
2597
2661
  "Skip the channel prompt and target this channel directly."
2598
2662
  ).argParser(parseChannelId)
@@ -2638,7 +2702,7 @@ Examples:
2638
2702
  var channels = new Command3("channels").alias("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(info, { isDefault: true }).addCommand(create).addCommand(link).addCommand(update);
2639
2703
 
2640
2704
  // src/cli/commands/create.ts
2641
- import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as Option4 } from "@commander-js/extra-typings";
2705
+ import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as Option5 } from "@commander-js/extra-typings";
2642
2706
  import { input as input5, select as select5 } from "@inquirer/prompts";
2643
2707
  import { execSync as execSync2 } from "child_process";
2644
2708
  import { colorize as colorize6 } from "consola/utils";
@@ -2915,7 +2979,7 @@ Examples:
2915
2979
  "Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable).",
2916
2980
  parseEnvFlag
2917
2981
  ).addOption(
2918
- new Option4(
2982
+ new Option5(
2919
2983
  "--hosting <mode>",
2920
2984
  '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.'
2921
2985
  ).choices(["self-hosted", "commerce"])
@@ -2923,11 +2987,11 @@ Examples:
2923
2987
  "--use-existing",
2924
2988
  "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."
2925
2989
  ).addOption(
2926
- new Option4("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
2990
+ new Option5("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
2927
2991
  ).addOption(
2928
- new Option4("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
2992
+ new Option5("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
2929
2993
  ).addOption(
2930
- new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
2994
+ new Option5("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
2931
2995
  ).action(async (options) => {
2932
2996
  const { ghRef, repository } = options;
2933
2997
  if (options.useExisting && options.hosting !== "commerce") {
@@ -3083,7 +3147,7 @@ ${steps.map((step) => ` ${colorize6("yellow", step)}`).join("\n")}`
3083
3147
  });
3084
3148
 
3085
3149
  // src/cli/commands/debug.ts
3086
- import { Command as Command5, Option as Option5 } from "commander";
3150
+ import { Command as Command5, Option as Option6 } from "commander";
3087
3151
 
3088
3152
  // src/cli/lib/collect-diagnostics.ts
3089
3153
  import { parse } from "dotenv";
@@ -3334,7 +3398,7 @@ Examples:
3334
3398
 
3335
3399
  # Print machine-readable JSON (useful for copy/paste or piping)
3336
3400
  $ catalyst debug --json`
3337
- ).addOption(new Option5("--json", "Output the report as JSON.")).action((options) => {
3401
+ ).addOption(new Option6("--json", "Output the report as JSON.")).action((options) => {
3338
3402
  const diagnostics = collectDiagnostics();
3339
3403
  if (options.json) {
3340
3404
  consola.log(JSON.stringify(diagnostics, null, 2));
@@ -3346,7 +3410,7 @@ Examples:
3346
3410
  // src/cli/commands/deploy.ts
3347
3411
  import { confirm as confirm4 } from "@inquirer/prompts";
3348
3412
  import AdmZip from "adm-zip";
3349
- import { Command as Command6, Option as Option6 } from "commander";
3413
+ import { Command as Command6, Option as Option7 } from "commander";
3350
3414
  import { colorize as colorize7 } from "consola/utils";
3351
3415
  import { access as access2, readdir, readFile } from "fs/promises";
3352
3416
  import { join as join13 } from "path";
@@ -3616,7 +3680,7 @@ Example:
3616
3680
  "--update-checkout-url",
3617
3681
  "After a successful deploy, prompt to update a channel's checkout URL."
3618
3682
  ).addOption(
3619
- new Option6(
3683
+ new Option7(
3620
3684
  "--secret <value>",
3621
3685
  "Secret to set for this deployment (repeatable). Overrides a stored value for the same key. Format: --secret KEY=VALUE"
3622
3686
  ).argParser((value, previous = []) => {
@@ -3626,7 +3690,7 @@ Example:
3626
3690
  "--prebuilt",
3627
3691
  "Skip the build step. Requires .bigcommerce/dist/ to already contain build output."
3628
3692
  ).addOption(
3629
- new Option6(
3693
+ new Option7(
3630
3694
  "--wrangler-version <version>",
3631
3695
  `Wrangler version or dist-tag to build with. Ignored with --prebuilt. Defaults to ${WRANGLER_VERSION}.`
3632
3696
  ).argParser(parseWranglerVersion)
@@ -3785,7 +3849,7 @@ Example:
3785
3849
 
3786
3850
  // src/cli/commands/domains.ts
3787
3851
  import { confirm as confirm5, select as select6 } from "@inquirer/prompts";
3788
- import { Command as Command7, Option as Option7 } from "commander";
3852
+ import { Command as Command7, Option as Option8 } from "commander";
3789
3853
  import { colorize as colorize9 } from "consola/utils";
3790
3854
 
3791
3855
  // src/cli/lib/domains.ts
@@ -4357,7 +4421,7 @@ Examples:
4357
4421
  # Show pending domains only
4358
4422
  $ catalyst domains list --status pending`
4359
4423
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--domain <domain>", "Only show a specific domain.").addOption(
4360
- new Option7("--status <status>", "Filter by verification status.").choices(
4424
+ new Option8("--status <status>", "Filter by verification status.").choices(
4361
4425
  DOMAIN_STATUS_FILTERS
4362
4426
  )
4363
4427
  ).action(async (options) => {
@@ -4662,7 +4726,7 @@ var env = new Command8("env").configureHelp({ showGlobalOptions: true }).descrip
4662
4726
  ).addCommand(add2).addCommand(remove2).addCommand(list2);
4663
4727
 
4664
4728
  // src/cli/commands/logs.ts
4665
- import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as Option8 } from "commander";
4729
+ import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as Option9 } from "commander";
4666
4730
  import { colorize as colorize10 } from "consola/utils";
4667
4731
  import { z as z16 } from "zod";
4668
4732
  var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
@@ -4879,7 +4943,7 @@ Examples:
4879
4943
  # Tail logs as raw JSON (useful for piping to other tools)
4880
4944
  $ catalyst logs tail --format json`
4881
4945
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
4882
- new Option8("--format <format>", "Output format for log events.").choices(["json", "pretty", "default", "short", "request"]).default("default")
4946
+ new Option9("--format <format>", "Output format for log events.").choices(["json", "pretty", "default", "short", "request"]).default("default")
4883
4947
  ).action(async (options) => {
4884
4948
  try {
4885
4949
  const config2 = getProjectConfig();
@@ -4980,7 +5044,7 @@ Examples:
4980
5044
  # Raw JSON (NDJSON) for piping to other tools
4981
5045
  $ catalyst logs query --since 2d --format json`
4982
5046
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
4983
- new Option8(
5047
+ new Option9(
4984
5048
  "--since <duration>",
4985
5049
  "Relative window ending at --end (default: now), e.g. 30m, 6h, 2d (units: s, m, h, d)."
4986
5050
  ).conflicts("start")
@@ -4988,28 +5052,28 @@ Examples:
4988
5052
  "--end <time>",
4989
5053
  "Window end: ISO-8601 timestamp or Unix epoch (seconds). Defaults to now. The window must not exceed 7 days."
4990
5054
  ).option("--method <method>", "Filter by request HTTP method (case-insensitive).").addOption(
4991
- new Option8("--status-code <code>", "Filter by response status code (100-599).").argParser(
5055
+ new Option9("--status-code <code>", "Filter by response status code (100-599).").argParser(
4992
5056
  parseIntInRange("--status-code", 100, 599)
4993
5057
  )
4994
5058
  ).option("--url-like <substring>", "Filter by URL substring (case-sensitive).").addOption(
4995
- new Option8("--level-min <level>", "Minimum log level.").choices(Array.from(LOG_LEVELS))
5059
+ new Option9("--level-min <level>", "Minimum log level.").choices(Array.from(LOG_LEVELS))
4996
5060
  ).addOption(
4997
- new Option8(
5061
+ new Option9(
4998
5062
  "--limit <count>",
4999
5063
  "Maximum entries per page (1-500). Defaults to the API default (100)."
5000
5064
  ).argParser(parseIntInRange("--limit", 1, 500))
5001
5065
  ).addOption(
5002
- new Option8(
5066
+ new Option9(
5003
5067
  "--after <cursor>",
5004
5068
  "Return the page after (older than) this cursor, from a previous page's end_cursor."
5005
5069
  ).conflicts("before")
5006
5070
  ).addOption(
5007
- new Option8(
5071
+ new Option9(
5008
5072
  "--before <cursor>",
5009
5073
  "Return the page before (newer than) this cursor, from a previous page's start_cursor."
5010
5074
  )
5011
5075
  ).addOption(
5012
- new Option8("--format <format>", "Output format for log entries.").choices(["json", "pretty", "default", "short", "request"]).default("default")
5076
+ new Option9("--format <format>", "Output format for log entries.").choices(["json", "pretty", "default", "short", "request"]).default("default")
5013
5077
  ).action(async (options) => {
5014
5078
  try {
5015
5079
  const config2 = getProjectConfig();
@@ -5352,7 +5416,7 @@ Example:
5352
5416
  });
5353
5417
 
5354
5418
  // src/cli/commands/telemetry.ts
5355
- import { Argument, Command as Command12, Option as Option9 } from "commander";
5419
+ import { Argument, Command as Command12, Option as Option10 } from "commander";
5356
5420
  import { colorize as colorize12 } from "consola/utils";
5357
5421
  var telemetryService = getTelemetry();
5358
5422
  var isEnabled = telemetryService.isEnabled();
@@ -5370,7 +5434,7 @@ Examples:
5370
5434
 
5371
5435
  # Disable telemetry collection
5372
5436
  $ catalyst telemetry --disable`
5373
- ).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new Option9("--enable", `Enables CLI telemetry collection.`).conflicts("disable")).option("--disable", `Disables CLI telemetry collection.`).action((arg, options) => {
5437
+ ).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new Option10("--enable", `Enables CLI telemetry collection.`).conflicts("disable")).option("--disable", `Disables CLI telemetry collection.`).action((arg, options) => {
5374
5438
  if (options.enable || arg === "enable") {
5375
5439
  telemetryService.setEnabled(true);
5376
5440
  isEnabled = true;
@@ -5397,7 +5461,7 @@ Examples:
5397
5461
  });
5398
5462
 
5399
5463
  // src/cli/commands/upgrade.ts
5400
- import { Command as Command13, Option as Option10 } from "@commander-js/extra-typings";
5464
+ import { Command as Command13, Option as Option11 } from "@commander-js/extra-typings";
5401
5465
  import { confirm as confirm7 } from "@inquirer/prompts";
5402
5466
  import { execa as execa4 } from "execa";
5403
5467
  import {
@@ -5559,6 +5623,72 @@ async function findStaleCli(pkgRaw) {
5559
5623
  const current = minVersion2(range)?.version;
5560
5624
  return current !== void 0 && semverLt2(current, latest) ? { current, latest } : null;
5561
5625
  }
5626
+ var CLI_MANAGED_SCRIPTS = ["build", "start", "deploy"];
5627
+ var ScriptsPackageJson = z17.looseObject({
5628
+ scripts: z17.record(z17.string(), z17.string()).optional()
5629
+ });
5630
+ var readScripts = (raw) => {
5631
+ try {
5632
+ return ScriptsPackageJson.parse(JSON.parse(raw)).scripts ?? {};
5633
+ } catch {
5634
+ return {};
5635
+ }
5636
+ };
5637
+ var SCRIPTS_BLOCK = /"scripts"\s*:\s*\{(?:[^{}"]|"(?:[^"\\]|\\.)*")*\}/;
5638
+ var rewriteScriptCommand = (raw, name, command) => {
5639
+ const block = SCRIPTS_BLOCK.exec(raw);
5640
+ if (!block) return raw;
5641
+ const splice = (replacement) => raw.slice(0, block.index) + replacement + raw.slice(block.index + block[0].length);
5642
+ const updated = block[0].replace(
5643
+ new RegExp(`("${escapeRegExp(name)}"\\s*:\\s*)"(?:[^"\\\\]|\\\\.)*"`),
5644
+ (_match, key) => `${key}${JSON.stringify(command)}`
5645
+ );
5646
+ if (updated !== block[0]) return splice(updated);
5647
+ const closeIdx = block[0].length - 1;
5648
+ const head = block[0].slice(0, closeIdx).replace(/\s+$/, "");
5649
+ const closeIndent = /\n([ \t]*)$/.exec(block[0].slice(0, closeIdx))?.[1] ?? "";
5650
+ const entryIndent = /\n([ \t]*)"/.exec(block[0])?.[1] ?? `${closeIndent} `;
5651
+ const separator = head.endsWith("{") ? "" : ",";
5652
+ return splice(
5653
+ `${head}${separator}
5654
+ ${entryIndent}${JSON.stringify(name)}: ${JSON.stringify(command)}
5655
+ ${closeIndent}}`
5656
+ );
5657
+ };
5658
+ async function normalizeManagedScripts(ourPkgRaw, baseDir, theirsDir) {
5659
+ const basePath = join15(baseDir, "package.json");
5660
+ const theirsPath = join15(theirsDir, "package.json");
5661
+ const [originalBase, originalTheirs] = await Promise.all([
5662
+ readFile2(basePath, "utf-8").catch(() => null),
5663
+ readFile2(theirsPath, "utf-8").catch(() => null)
5664
+ ]);
5665
+ if (originalBase === null || originalTheirs === null) return { pinned: [], upstreamChanged: [] };
5666
+ const ourScripts = readScripts(ourPkgRaw);
5667
+ const baseScripts = readScripts(originalBase);
5668
+ const theirsScripts = readScripts(originalTheirs);
5669
+ const decisions = CLI_MANAGED_SCRIPTS.flatMap((name) => {
5670
+ const ours = ourScripts[name];
5671
+ return ours !== void 0 && ours !== baseScripts[name] ? [{ name, ours }] : [];
5672
+ });
5673
+ const upstreamChanged = decisions.flatMap(({ name }) => {
5674
+ const base = baseScripts[name] ?? null;
5675
+ const theirs = theirsScripts[name] ?? null;
5676
+ return base !== theirs ? [{ name, base, theirs }] : [];
5677
+ });
5678
+ const baseRaw = decisions.reduce(
5679
+ (acc, { name, ours }) => rewriteScriptCommand(acc, name, ours),
5680
+ originalBase
5681
+ );
5682
+ const theirsRaw = decisions.reduce(
5683
+ (acc, { name, ours }) => rewriteScriptCommand(acc, name, ours),
5684
+ originalTheirs
5685
+ );
5686
+ await Promise.all([
5687
+ baseRaw === originalBase ? null : writeFile2(basePath, baseRaw),
5688
+ theirsRaw === originalTheirs ? null : writeFile2(theirsPath, theirsRaw)
5689
+ ]);
5690
+ return { pinned: decisions.map(({ name }) => name), upstreamChanged };
5691
+ }
5562
5692
  async function mergeFile(oursPath, basePath, theirsPath) {
5563
5693
  const merged = await execa4(
5564
5694
  "git",
@@ -5930,6 +6060,20 @@ ${rows}
5930
6060
  They resolve to your local packages/ copies, which \`catalyst upgrade\` does not update \u2014 so they never receive upgrades.`
5931
6061
  );
5932
6062
  }
6063
+ function printManagedScriptFindings(findings) {
6064
+ if (findings.length === 0) return;
6065
+ const pad = Math.max(...findings.map((finding) => finding.name.length));
6066
+ const show = (command) => command ?? "(absent)";
6067
+ const rows = findings.map(
6068
+ (finding) => ` ${finding.name.padEnd(pad)} ${show(finding.base)} \u2192 ${show(finding.theirs)}`
6069
+ ).join("\n");
6070
+ consola.warn(
6071
+ `Core's own version of ${findings.length} npm script${findings.length === 1 ? "" : "s"} the CLI manages for you changed:
6072
+ ${rows}
6073
+
6074
+ Your project keeps its \`catalyst\`-backed command, which is almost always what you want \u2014 \`catalyst build\`/\`start\`/\`deploy\` wrap the equivalent \`next\` command. Apply the change by hand only if you have opted out of the CLI scripts.`
6075
+ );
6076
+ }
5933
6077
  function printSummary({
5934
6078
  result,
5935
6079
  relDir,
@@ -5973,23 +6117,42 @@ Resolve them, then: git add ${relDir} && git commit${install}`
5973
6117
  );
5974
6118
  }
5975
6119
  }
6120
+ async function reconcileNativeHosting(catalystRoot, result) {
6121
+ const state = getProjectState(catalystRoot);
6122
+ if (!state.isLinked && !state.isTransformed) return;
6123
+ const instrumentationRel = "instrumentation.ts";
6124
+ const instrumentationPath = join15(catalystRoot, instrumentationRel);
6125
+ const hadInstrumentation = await pathExists(instrumentationPath);
6126
+ try {
6127
+ await cleanupCloudflareIncompatibilities(catalystRoot);
6128
+ } catch (err) {
6129
+ consola.warn(
6130
+ `Couldn't fully reconcile native-hosting files (${err instanceof Error ? err.message : String(err)}). Remove @vercel/otel from package.json by hand if the merge brought it back.`
6131
+ );
6132
+ }
6133
+ if (hadInstrumentation && !await pathExists(instrumentationPath)) {
6134
+ result.conflicted = result.conflicted.filter((rel) => rel !== instrumentationRel);
6135
+ result.applied = result.applied.filter((rel) => rel !== instrumentationRel);
6136
+ result.added = result.added.filter((rel) => rel !== instrumentationRel);
6137
+ }
6138
+ }
5976
6139
  var upgrade = new Command13("upgrade").configureHelp({ showGlobalOptions: true }).aliases([
5977
6140
  "up",
5978
6141
  // These are hidden aliases
5979
6142
  "upgrayedd",
5980
6143
  "ugrad"
5981
6144
  ]).description("Upgrade your Catalyst project to a newer version by applying a 3-way merge.").argument("[version]", "Target version to upgrade to (default: latest)").addOption(
5982
- new Option10(
6145
+ new Option11(
5983
6146
  "--ref <ref>",
5984
6147
  "Full git tag to upgrade to, e.g. an integration family like @bigcommerce/catalyst-makeswift@1.7.0. Overrides [version]."
5985
6148
  )
5986
- ).addOption(new Option10("--from <ref>", "Base ref to upgrade from (when catalyst.ref is missing)")).option("--dry-run", "Generate and display the diff without applying it").option("--yes", "Skip confirmation prompts (e.g. inferred base ref)").addOption(
5987
- new Option10(
6149
+ ).addOption(new Option11("--from <ref>", "Base ref to upgrade from (when catalyst.ref is missing)")).option("--dry-run", "Generate and display the diff without applying it").option("--yes", "Skip confirmation prompts (e.g. inferred base ref)").addOption(
6150
+ new Option11(
5988
6151
  "--strategy <strategy>",
5989
6152
  "Merge engine: tree (git merge-tree, full fidelity), per-file (git merge-file, no-history fallback), or auto (tree when git >= 2.38, else per-file)"
5990
6153
  ).choices(["auto", "tree", "per-file"]).default("auto").hideHelp()
5991
6154
  ).addOption(
5992
- new Option10("--repository <owner/repo>", "GitHub repository to pull versions from").default(
6155
+ new Option11("--repository <owner/repo>", "GitHub repository to pull versions from").default(
5993
6156
  DEFAULT_REPOSITORY
5994
6157
  )
5995
6158
  ).addHelpText(
@@ -6104,6 +6267,11 @@ to raise the GitHub API rate limit.`
6104
6267
  baseVersions,
6105
6268
  targetVersions
6106
6269
  );
6270
+ const { upstreamChanged: managedScripts } = await normalizeManagedScripts(
6271
+ project.rawContent,
6272
+ baseDir,
6273
+ theirsDir
6274
+ );
6107
6275
  if (options.dryRun) {
6108
6276
  const diffSpinner = yoctoSpinner4().start("Generating diff...");
6109
6277
  const diff = await execa4(
@@ -6124,6 +6292,7 @@ to raise the GitHub API rate limit.`
6124
6292
  consola.log("\nDiff preview (--dry-run, not applied):\n");
6125
6293
  consola.log(diff.stdout);
6126
6294
  printWorkspaceFindings(workspaceDeps);
6295
+ printManagedScriptFindings(managedScripts);
6127
6296
  return;
6128
6297
  }
6129
6298
  const strategy = await resolveStrategy(options.strategy);
@@ -6171,6 +6340,7 @@ to raise the GitHub API rate limit.`
6171
6340
  );
6172
6341
  }
6173
6342
  }
6343
+ printManagedScriptFindings(managedScripts);
6174
6344
  printWorkspaceFindings(workspaceDeps);
6175
6345
  let migratedDeps = 0;
6176
6346
  if (workspaceDeps.length) {
@@ -6190,6 +6360,7 @@ to raise the GitHub API rate limit.`
6190
6360
  );
6191
6361
  }
6192
6362
  }
6363
+ await reconcileNativeHosting(catalystRoot, result);
6193
6364
  try {
6194
6365
  await applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stampedText !== null);
6195
6366
  } catch (err) {