@bigcommerce/catalyst 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.1.1",
28
+ version: "1.3.0",
29
29
  type: "module",
30
30
  repository: {
31
31
  type: "git",
@@ -119,6 +119,8 @@ function getUserConfig() {
119
119
  // src/cli/lib/telemetry.ts
120
120
  var TELEMETRY_KEY_ENABLED = "telemetry.enabled";
121
121
  var TELEMETRY_KEY_ID = `telemetry.anonymousId`;
122
+ var TELEMETRY_FLUSH_INTERVAL_MS = 200;
123
+ var TELEMETRY_HTTP_REQUEST_TIMEOUT_MS = 200;
122
124
  var Telemetry = class {
123
125
  constructor() {
124
126
  this.commandName = "unknown";
@@ -133,7 +135,10 @@ var Telemetry = class {
133
135
  this.correlationId = randomUUID();
134
136
  this.startTime = Date.now();
135
137
  this.analytics = new Analytics({
136
- writeKey: "OCkxLj3KTc1N3ebEbnFBqbhxfE3tVb3Z"
138
+ writeKey: "OCkxLj3KTc1N3ebEbnFBqbhxfE3tVb3Z",
139
+ flushInterval: TELEMETRY_FLUSH_INTERVAL_MS,
140
+ httpRequestTimeout: TELEMETRY_HTTP_REQUEST_TIMEOUT_MS,
141
+ maxRetries: 0
137
142
  });
138
143
  }
139
144
  durationMs() {
@@ -589,9 +594,9 @@ async function deleteProject(projectUuid, storeHash, accessToken, apiHost) {
589
594
  // src/cli/lib/project-config.ts
590
595
  import Conf2 from "conf";
591
596
  import { join } from "path";
592
- function getProjectConfig() {
597
+ function getProjectConfig(cwd = process.cwd()) {
593
598
  return new Conf2({
594
- cwd: join(process.cwd(), ".bigcommerce"),
599
+ cwd: join(cwd, ".bigcommerce"),
595
600
  projectSuffix: "",
596
601
  configName: "project",
597
602
  schema: {
@@ -642,7 +647,7 @@ var resolveProjectUuid = (options) => {
642
647
  const projectUuid = options.projectUuid ?? config2.get("projectUuid");
643
648
  if (!projectUuid) {
644
649
  throw new Error(
645
- "Project UUID is required. Please run either `catalyst project link` or `catalyst project create` or this command again with --project-uuid <uuid>."
650
+ "Project UUID is required. Please run either `catalyst projects link` or `catalyst projects create` or this command again with --project-uuid <uuid>."
646
651
  );
647
652
  }
648
653
  return projectUuid;
@@ -698,8 +703,8 @@ Example:
698
703
  const store = await fetchStoreProfile2(storeHash, accessToken, apiHost);
699
704
  const projectUuid = config2.get("projectUuid");
700
705
  if (projectUuid) {
701
- const projects = await fetchProjects(storeHash, accessToken, apiHost);
702
- const linkedProject = projects.find((p) => p.uuid === projectUuid);
706
+ const projects2 = await fetchProjects(storeHash, accessToken, apiHost);
707
+ const linkedProject = projects2.find((p) => p.uuid === projectUuid);
703
708
  if (linkedProject) {
704
709
  consola.info(
705
710
  `Logged in to ${store.store_name} (${storeHash}), connected to project ${linkedProject.name} (${projectUuid})`
@@ -1018,6 +1023,10 @@ async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERS
1018
1023
  cwd: coreDir
1019
1024
  }
1020
1025
  );
1026
+ await copyFile(
1027
+ join4(getModuleCliPath(), "templates", "public_headers"),
1028
+ join4(openNextOutDir, "assets", "_headers")
1029
+ );
1021
1030
  await execa2(
1022
1031
  "pnpm",
1023
1032
  [
@@ -1082,13 +1091,13 @@ Examples:
1082
1091
  const projectUuid = options.projectUuid ?? config2.get("projectUuid");
1083
1092
  if (!projectUuid) {
1084
1093
  throw new Error(
1085
- "Project UUID is required. Please run `catalyst project create` or `catalyst project link` or this command again with --project-uuid <uuid>."
1094
+ "Project UUID is required. Please run `catalyst projects create` or `catalyst projects link` or this command again with --project-uuid <uuid>."
1086
1095
  );
1087
1096
  }
1088
1097
  await buildCatalystProject(projectUuid, options.wranglerVersion);
1089
1098
  });
1090
1099
 
1091
- // src/cli/commands/channel.ts
1100
+ // src/cli/commands/channels.ts
1092
1101
  import { select as select4 } from "@inquirer/prompts";
1093
1102
  import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option3 } from "commander";
1094
1103
  import { colorize as colorize5 } from "consola/utils";
@@ -1118,8 +1127,8 @@ var channelsResponseSchema = z7.object({
1118
1127
  data: z7.array(channelSchema)
1119
1128
  });
1120
1129
  var CHANNEL_PLATFORM_ORDER = ["catalyst", "next", "bigcommerce"];
1121
- function sortChannelsByPlatform(channels) {
1122
- return [...channels].sort((a, b) => {
1130
+ function sortChannelsByPlatform(channels2) {
1131
+ return [...channels2].sort((a, b) => {
1123
1132
  const aIndex = CHANNEL_PLATFORM_ORDER.indexOf(a.platform);
1124
1133
  const bIndex = CHANNEL_PLATFORM_ORDER.indexOf(b.platform);
1125
1134
  if (aIndex === -1 && bIndex === -1) return 0;
@@ -1284,6 +1293,16 @@ var OPENNEXT_CLOUDFLARE_VERSION = "1.17.3";
1284
1293
  var corePackageJsonSchema = z8.looseObject({
1285
1294
  dependencies: z8.record(z8.string(), z8.string()).optional()
1286
1295
  });
1296
+ var projectJsonSchema2 = z8.looseObject({});
1297
+ var readProjectJson = (path) => {
1298
+ if (!existsSync3(path)) return {};
1299
+ try {
1300
+ const parsed = projectJsonSchema2.safeParse(JSON.parse(readFileSync2(path, "utf-8")));
1301
+ return parsed.success ? parsed.data : {};
1302
+ } catch {
1303
+ return {};
1304
+ }
1305
+ };
1287
1306
  var writeJson = (path, value) => {
1288
1307
  mkdirSync(dirname2(path), { recursive: true });
1289
1308
  writeFileSync(path, `${JSON.stringify(value, null, 2)}
@@ -1343,13 +1362,15 @@ var setupCommerceHosting = async ({
1343
1362
  "@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
1344
1363
  };
1345
1364
  writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
1365
+ const projectJsonPath = join5(projectDir, ".bigcommerce", "project.json");
1346
1366
  const projectJson = {
1367
+ ...readProjectJson(projectJsonPath),
1347
1368
  projectUuid,
1348
1369
  framework: "catalyst"
1349
1370
  };
1350
1371
  if (storeHash) projectJson.storeHash = storeHash;
1351
1372
  if (accessToken) projectJson.accessToken = accessToken;
1352
- writeJson(join5(projectDir, ".bigcommerce", "project.json"), projectJson);
1373
+ writeJson(projectJsonPath, projectJson);
1353
1374
  convertProxyToMiddleware(projectDir);
1354
1375
  };
1355
1376
  var NoLinkedProjectError = class extends Error {
@@ -1405,7 +1426,7 @@ async function selectOrCreateInfrastructureProject(api, linkedProjectUuid) {
1405
1426
  }
1406
1427
  async function promptForCommerceHostingProject(api, defaultName, autoUseDefaultName, useExistingOnCollision) {
1407
1428
  const existingProjects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
1408
- const takenNames = existingProjects.map((project2) => project2.name);
1429
+ const takenNames = existingProjects.map((project) => project.name);
1409
1430
  if (autoUseDefaultName) {
1410
1431
  return autoCreateCommerceHostingProject(
1411
1432
  api,
@@ -1415,7 +1436,7 @@ async function promptForCommerceHostingProject(api, defaultName, autoUseDefaultN
1415
1436
  );
1416
1437
  }
1417
1438
  const conflict = existingProjects.find(
1418
- (project2) => project2.name.toLowerCase() === defaultName.toLowerCase()
1439
+ (project) => project.name.toLowerCase() === defaultName.toLowerCase()
1419
1440
  );
1420
1441
  if (!conflict) {
1421
1442
  return autoCreateCommerceHostingProject(
@@ -1447,10 +1468,10 @@ async function promptForCommerceHostingProject(api, defaultName, autoUseDefaultN
1447
1468
  const selected = await select({
1448
1469
  message: "Which Commerce Hosting project would you like to use?",
1449
1470
  choices: [
1450
- ...existingProjects.map((project2) => ({
1451
- name: project2.name,
1452
- value: project2,
1453
- description: project2.uuid
1471
+ ...existingProjects.map((project) => ({
1472
+ name: project.name,
1473
+ value: project,
1474
+ description: project.uuid
1454
1475
  })),
1455
1476
  new Separator(),
1456
1477
  { name: "Create a new project", value: "create-new" }
@@ -1514,7 +1535,7 @@ async function resolveCollisionChoice(existingName, useExistingOnCollision) {
1514
1535
  }
1515
1536
  async function autoCreateCommerceHostingProject(api, name, existingProjects, useExistingOnCollision) {
1516
1537
  const existing = existingProjects.find(
1517
- (project2) => project2.name.toLowerCase() === name.toLowerCase()
1538
+ (project) => project.name.toLowerCase() === name.toLowerCase()
1518
1539
  );
1519
1540
  if (existing) {
1520
1541
  const shouldUseExisting = await resolveCollisionChoice(existing.name, useExistingOnCollision);
@@ -1551,8 +1572,8 @@ async function resolveProject(options) {
1551
1572
  apiHost: options.apiHost
1552
1573
  };
1553
1574
  if (options.projectUuid) {
1554
- const projects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
1555
- const matched = projects.find((p) => p.uuid === options.projectUuid);
1575
+ const projects2 = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
1576
+ const matched = projects2.find((p) => p.uuid === options.projectUuid);
1556
1577
  if (matched) return matched;
1557
1578
  consola.warn(
1558
1579
  `Project ${options.projectUuid} not found on this store. Pick another to continue.`
@@ -1565,13 +1586,13 @@ async function resolveChannel(options) {
1565
1586
  return { id: options.channelId };
1566
1587
  }
1567
1588
  consola.start("Fetching channels...");
1568
- const channels = await fetchAvailableChannels(
1589
+ const channels2 = await fetchAvailableChannels(
1569
1590
  options.storeHash,
1570
1591
  options.accessToken,
1571
1592
  options.apiHost
1572
1593
  );
1573
1594
  consola.success("Channels fetched.");
1574
- const catalystChannels = channels.filter((c) => c.platform === "catalyst");
1595
+ const catalystChannels = channels2.filter((c) => c.platform === "catalyst");
1575
1596
  if (catalystChannels.length === 0) {
1576
1597
  throw new Error(
1577
1598
  "No Catalyst channels found on this store. Create one with `catalyst create` and try again."
@@ -1588,19 +1609,19 @@ async function resolveChannel(options) {
1588
1609
  const matched = catalystChannels.find((c) => c.id === id);
1589
1610
  return { id, name: matched?.name };
1590
1611
  }
1591
- async function resolveHostname(project2, options) {
1612
+ async function resolveHostname(project, options) {
1592
1613
  if (options.hostname) {
1593
1614
  return options.hostname;
1594
1615
  }
1595
- if (project2.deployment_hostnames.length === 0) {
1616
+ if (project.deployment_hostnames.length === 0) {
1596
1617
  throw new Error(
1597
- `Project "${project2.name}" has no deployment hostnames yet. Run \`catalyst deploy\` first to create one.`
1618
+ `Project "${project.name}" has no deployment hostnames yet. Run \`catalyst deploy\` first to create one.`
1598
1619
  );
1599
1620
  }
1600
1621
  const ordered = options.preferHostname ? [
1601
- ...project2.deployment_hostnames.filter((h) => h === options.preferHostname),
1602
- ...project2.deployment_hostnames.filter((h) => h !== options.preferHostname)
1603
- ] : project2.deployment_hostnames;
1622
+ ...project.deployment_hostnames.filter((h) => h === options.preferHostname),
1623
+ ...project.deployment_hostnames.filter((h) => h !== options.preferHostname)
1624
+ ] : project.deployment_hostnames;
1604
1625
  const selected = await select2({
1605
1626
  message: "Select the hostname to point the channel at.",
1606
1627
  choices: ordered.map((h) => ({ name: h, value: h }))
@@ -1608,18 +1629,18 @@ async function resolveHostname(project2, options) {
1608
1629
  return selected;
1609
1630
  }
1610
1631
  async function runChannelSiteUrlFlow(options) {
1611
- const project2 = await resolveProject(options);
1612
- const channel2 = await resolveChannel(options);
1613
- const hostname = await resolveHostname(project2, options);
1632
+ const project = await resolveProject(options);
1633
+ const channel = await resolveChannel(options);
1634
+ const hostname = await resolveHostname(project, options);
1614
1635
  const siteUrl = hostname.startsWith("https://") ? hostname : `https://${hostname}`;
1615
1636
  await updateChannelSiteUrl(
1616
- channel2.id,
1637
+ channel.id,
1617
1638
  siteUrl,
1618
1639
  options.storeHash,
1619
1640
  options.accessToken,
1620
1641
  options.apiHost
1621
1642
  );
1622
- const channelLabel = channel2.name ? `"${channel2.name}" (${channel2.id})` : String(channel2.id);
1643
+ const channelLabel = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
1623
1644
  consola.success(`Updated channel ${channelLabel} site URL to ${siteUrl}.`);
1624
1645
  }
1625
1646
 
@@ -1744,7 +1765,8 @@ async function runCreateChannelFlow(options) {
1744
1765
  { name: "No", value: false }
1745
1766
  ]
1746
1767
  });
1747
- return createChannel(
1768
+ consola.start("Creating channel...");
1769
+ const channel = await createChannel(
1748
1770
  name,
1749
1771
  storefrontLocale,
1750
1772
  additionalLocales,
@@ -1753,6 +1775,8 @@ async function runCreateChannelFlow(options) {
1753
1775
  accessToken,
1754
1776
  cliApiOrigin
1755
1777
  );
1778
+ consola.success("Channel created.");
1779
+ return channel;
1756
1780
  }
1757
1781
 
1758
1782
  // src/cli/lib/env-config.ts
@@ -1869,7 +1893,7 @@ var writeEnv = (projectDir, envVars) => {
1869
1893
  `);
1870
1894
  };
1871
1895
 
1872
- // src/cli/commands/channel.ts
1896
+ // src/cli/commands/channels.ts
1873
1897
  var parseChannelId = (value) => {
1874
1898
  const parsed = Number.parseInt(value, 10);
1875
1899
  if (Number.isNaN(parsed)) {
@@ -1902,10 +1926,10 @@ var update = new Command3("update").configureHelp({ showGlobalOptions: true }).d
1902
1926
  `
1903
1927
  Examples:
1904
1928
  # Pick a channel and hostname interactively
1905
- $ catalyst channel update
1929
+ $ catalyst channels update
1906
1930
 
1907
1931
  # Skip both prompts
1908
- $ catalyst channel update --channel-id 123 --hostname my-storefront.example.com`
1932
+ $ catalyst channels update --channel-id 123 --hostname my-storefront.example.com`
1909
1933
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
1910
1934
  new Option3(
1911
1935
  "--channel-id <id>",
@@ -1933,7 +1957,7 @@ Examples:
1933
1957
  } catch (error) {
1934
1958
  if (error instanceof NoLinkedProjectError) {
1935
1959
  consola.info(
1936
- "When you're ready to create a project, run `catalyst project create` or re-run `catalyst channel update`."
1960
+ "When you're ready to create a project, run `catalyst projects create` or re-run `catalyst channels update`."
1937
1961
  );
1938
1962
  process.exit(0);
1939
1963
  return;
@@ -1949,13 +1973,13 @@ var link = new Command3("link").configureHelp({ showGlobalOptions: true }).descr
1949
1973
  `
1950
1974
  Examples:
1951
1975
  # Pick a channel interactively (logs you in if needed)
1952
- $ catalyst channel link
1976
+ $ catalyst channels link
1953
1977
 
1954
1978
  # Non-interactive
1955
- $ catalyst channel link --store-hash <hash> --access-token <token> --channel-id 123
1979
+ $ catalyst channels link --store-hash <hash> --access-token <token> --channel-id 123
1956
1980
 
1957
1981
  # Append extra environment variables to .env.local
1958
- $ catalyst channel link --channel-id 123 --env MY_FLAG=1`
1982
+ $ catalyst channels link --channel-id 123 --env MY_FLAG=1`
1959
1983
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
1960
1984
  new Option3("--channel-id <id>", "Link this channel directly, skipping the picker.").argParser(
1961
1985
  parseChannelId
@@ -1971,7 +1995,7 @@ Examples:
1971
1995
  const credentials = await resolveCredentialsWithLogin(options, config2, apiHost);
1972
1996
  if (!credentials) {
1973
1997
  consola.info(
1974
- "Login aborted. Re-run `catalyst channel link` when you have your credentials ready."
1998
+ "Login aborted. Re-run `catalyst channels link` when you have your credentials ready."
1975
1999
  );
1976
2000
  process.exit(0);
1977
2001
  return;
@@ -1981,8 +2005,8 @@ Examples:
1981
2005
  let channelId = options.channelId;
1982
2006
  let channelName;
1983
2007
  if (channelId === void 0) {
1984
- const channels = await fetchAvailableChannels(storeHash, accessToken, apiHost);
1985
- if (channels.length === 0) {
2008
+ const availableChannels = await fetchAvailableChannels(storeHash, accessToken, apiHost);
2009
+ if (availableChannels.length === 0) {
1986
2010
  consola.info(
1987
2011
  "No storefront channels found on this store. Create one with `catalyst create` and try again."
1988
2012
  );
@@ -1991,13 +2015,13 @@ Examples:
1991
2015
  }
1992
2016
  channelId = await select4({
1993
2017
  message: "Which channel would you like to link?",
1994
- choices: sortChannelsByPlatform(channels).map((c) => ({
2018
+ choices: sortChannelsByPlatform(availableChannels).map((c) => ({
1995
2019
  name: c.name,
1996
2020
  value: c.id,
1997
2021
  description: channelPlatformLabel(c.platform)
1998
2022
  }))
1999
2023
  });
2000
- channelName = channels.find((c) => c.id === channelId)?.name;
2024
+ channelName = availableChannels.find((c) => c.id === channelId)?.name;
2001
2025
  }
2002
2026
  const initData = await getChannelInit(channelId, storeHash, accessToken, options.cliApiOrigin);
2003
2027
  const envVars = { ...initData.envVars };
@@ -2022,13 +2046,13 @@ var create = new Command3("create").configureHelp({ showGlobalOptions: true }).d
2022
2046
  `
2023
2047
  Examples:
2024
2048
  # Create a channel interactively (logs you in if needed)
2025
- $ catalyst channel create
2049
+ $ catalyst channels create
2026
2050
 
2027
2051
  # Non-interactive, and link it to this project afterwards
2028
- $ catalyst channel create --name "My Store" --locale en --no-sample-data --link
2052
+ $ catalyst channels create --name "My Store" --locale en --no-sample-data --link
2029
2053
 
2030
2054
  # Add additional storefront languages (max 4)
2031
- $ catalyst channel create --name "My Store" --additional-locales es fr`
2055
+ $ catalyst channels create --name "My Store" --additional-locales es fr`
2032
2056
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).option("--name <name>", "Name for the new channel. Skips the name prompt.").option(
2033
2057
  "--locale <locale>",
2034
2058
  'Default storefront locale (e.g. "en"). Skips the default-language prompt.'
@@ -2051,7 +2075,7 @@ Examples:
2051
2075
  const credentials = await resolveCredentialsWithLogin(options, config2, apiHost);
2052
2076
  if (!credentials) {
2053
2077
  consola.info(
2054
- "Login aborted. Re-run `catalyst channel create` when you have your credentials ready."
2078
+ "Login aborted. Re-run `catalyst channels create` when you have your credentials ready."
2055
2079
  );
2056
2080
  process.exit(0);
2057
2081
  return;
@@ -2096,7 +2120,7 @@ Examples:
2096
2120
  }
2097
2121
  process.exit(0);
2098
2122
  });
2099
- var channel = new Command3("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(create).addCommand(link).addCommand(update);
2123
+ var channels = new Command3("channels").alias("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(create).addCommand(link).addCommand(update);
2100
2124
 
2101
2125
  // src/cli/commands/create.ts
2102
2126
  import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as Option4 } from "@commander-js/extra-typings";
@@ -2105,10 +2129,27 @@ import { execSync as execSync2 } from "child_process";
2105
2129
  import { colorize as colorize6 } from "consola/utils";
2106
2130
  import { pathExistsSync } from "fs-extra/esm";
2107
2131
  import kebabCase from "lodash.kebabcase";
2108
- import { join as join10 } from "path";
2132
+ import { join as join11 } from "path";
2109
2133
 
2110
2134
  // src/cli/lib/detect-package-manager.ts
2135
+ import { access } from "fs/promises";
2136
+ import { join as join7 } from "path";
2111
2137
  import { detectPackageManager as detectFromDir } from "nypm";
2138
+ var PROJECT_LOCKFILES = [
2139
+ ["pnpm-lock.yaml", "pnpm"],
2140
+ ["yarn.lock", "yarn"],
2141
+ ["bun.lock", "bun"],
2142
+ ["bun.lockb", "bun"],
2143
+ ["package-lock.json", "npm"]
2144
+ ];
2145
+ var detectLockfileManager = async (dir) => {
2146
+ const found = await Promise.all(
2147
+ PROJECT_LOCKFILES.map(
2148
+ async ([file, manager]) => access(join7(dir, file)).then(() => manager).catch(() => null)
2149
+ )
2150
+ );
2151
+ return found.find((manager) => manager !== null) ?? null;
2152
+ };
2112
2153
  var detectPackageManager = () => {
2113
2154
  const userAgent = process.env.npm_config_user_agent ?? "";
2114
2155
  const name = userAgent.split(" ")[0]?.split("/")[0];
@@ -2184,7 +2225,7 @@ var extractCatalyst = async ({
2184
2225
  // src/cli/lib/init-git-repo.ts
2185
2226
  import { execSync } from "child_process";
2186
2227
  import { rmSync } from "fs";
2187
- import { join as join7 } from "path";
2228
+ import { join as join8 } from "path";
2188
2229
  var run = (command, cwd) => execSync(command, { cwd, stdio: "ignore" });
2189
2230
  var initGitRepo = (projectDir) => {
2190
2231
  try {
@@ -2193,7 +2234,7 @@ var initGitRepo = (projectDir) => {
2193
2234
  run('git commit -m "Initial commit from Catalyst"', projectDir);
2194
2235
  consola.success("Initialized a git repository.");
2195
2236
  } catch {
2196
- rmSync(join7(projectDir, ".git"), { recursive: true, force: true });
2237
+ rmSync(join8(projectDir, ".git"), { recursive: true, force: true });
2197
2238
  consola.warn("Could not initialize a git repository. Skipping.");
2198
2239
  }
2199
2240
  };
@@ -2237,7 +2278,7 @@ var installDependencies = async (projectDir, packageManager = "pnpm") => {
2237
2278
 
2238
2279
  // src/cli/lib/rewrite-core-package.ts
2239
2280
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2240
- import { join as join8 } from "path";
2281
+ import { join as join9 } from "path";
2241
2282
  import { z as z10 } from "zod";
2242
2283
  var NATIVE_BUILD_ALLOWLIST = [
2243
2284
  "@parcel/watcher",
@@ -2279,7 +2320,7 @@ var resolvePublishedVersion = async (pkgName) => {
2279
2320
  return `^${version2}`;
2280
2321
  };
2281
2322
  var rewriteCorePackage = async (projectDir, ref) => {
2282
- const packageJsonPath = join8(projectDir, "package.json");
2323
+ const packageJsonPath = join9(projectDir, "package.json");
2283
2324
  const pkg = corePackageJsonSchema2.parse(JSON.parse(readFileSync4(packageJsonPath, "utf-8")));
2284
2325
  const workspaceDeps = DEP_FIELDS.flatMap((field) => {
2285
2326
  const deps = pkg[field];
@@ -2304,12 +2345,12 @@ var rewriteCorePackage = async (projectDir, ref) => {
2304
2345
  pkg.catalyst = { ...pkg.catalyst, version: pkg.version, ref };
2305
2346
  writeFileSync2(packageJsonPath, `${JSON.stringify(sortPackageJsonFields(pkg), null, 2)}
2306
2347
  `);
2307
- writeFileSync2(join8(projectDir, ".npmrc"), "legacy-peer-deps=true\n");
2348
+ writeFileSync2(join9(projectDir, ".npmrc"), "legacy-peer-deps=true\n");
2308
2349
  };
2309
2350
 
2310
2351
  // src/cli/lib/setup-core-project.ts
2311
2352
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
2312
- import { dirname as dirname3, join as join9 } from "path";
2353
+ import { dirname as dirname3, join as join10 } from "path";
2313
2354
  import { z as z11 } from "zod";
2314
2355
  var corePackageJsonSchema3 = z11.looseObject({
2315
2356
  scripts: z11.record(z11.string(), z11.string()).optional(),
@@ -2321,7 +2362,7 @@ var writeJson2 = (path, value) => {
2321
2362
  `);
2322
2363
  };
2323
2364
  var setupCoreProject = (projectDir) => {
2324
- const corePackageJsonPath = join9(projectDir, "package.json");
2365
+ const corePackageJsonPath = join10(projectDir, "package.json");
2325
2366
  const pkg = corePackageJsonSchema3.parse(JSON.parse(readFileSync5(corePackageJsonPath, "utf-8")));
2326
2367
  pkg.scripts = {
2327
2368
  ...pkg.scripts,
@@ -2361,10 +2402,10 @@ function parseEnvFlag(value, previous = {}) {
2361
2402
  return { ...previous, [key]: val };
2362
2403
  }
2363
2404
  async function handleChannelSelection(storeHash, accessToken, apiHost) {
2364
- const channels = await fetchAvailableChannels(storeHash, accessToken, apiHost);
2405
+ const channels2 = await fetchAvailableChannels(storeHash, accessToken, apiHost);
2365
2406
  const existingChannel = await select5({
2366
2407
  message: "Which channel would you like to use?",
2367
- choices: sortChannelsByPlatform(channels).map((ch) => ({
2408
+ choices: sortChannelsByPlatform(channels2).map((ch) => ({
2368
2409
  name: ch.name,
2369
2410
  value: ch,
2370
2411
  description: `Channel Platform: ${channelPlatformLabel(ch.platform)}`
@@ -2380,7 +2421,7 @@ async function setupProject(options) {
2380
2421
  }
2381
2422
  if (projectName) {
2382
2423
  projectName = kebabCase(projectName);
2383
- projectDir = join10(options.projectDir, projectName);
2424
+ projectDir = join11(options.projectDir, projectName);
2384
2425
  if (pathExistsSync(projectDir)) {
2385
2426
  consola.error(`${projectDir} already exists`);
2386
2427
  process.exit(1);
@@ -2390,7 +2431,7 @@ async function setupProject(options) {
2390
2431
  const validateProjectName = (i) => {
2391
2432
  const formatted = kebabCase(i);
2392
2433
  if (!formatted) return "Project name is required";
2393
- const targetDir = join10(options.projectDir, formatted);
2434
+ const targetDir = join11(options.projectDir, formatted);
2394
2435
  if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
2395
2436
  projectName = formatted;
2396
2437
  projectDir = targetDir;
@@ -2573,6 +2614,11 @@ Examples:
2573
2614
  accessToken
2574
2615
  });
2575
2616
  }
2617
+ if (storeHash && accessToken) {
2618
+ const projectConfig = getProjectConfig(projectDir);
2619
+ projectConfig.set("storeHash", storeHash);
2620
+ projectConfig.set("accessToken", accessToken);
2621
+ }
2576
2622
  writeEnv(projectDir, envVars);
2577
2623
  await installDependencies(projectDir, packageManager);
2578
2624
  initGitRepo(projectDir);
@@ -2605,7 +2651,7 @@ import { Command as Command5, Option as Option5 } from "commander";
2605
2651
  import { parse } from "dotenv";
2606
2652
  import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
2607
2653
  import { release } from "os";
2608
- import { dirname as dirname4, join as join11 } from "path";
2654
+ import { dirname as dirname4, join as join12 } from "path";
2609
2655
  import { z as z12 } from "zod";
2610
2656
  var CLI_ENV_VARS = [
2611
2657
  "CATALYST_STORE_HASH",
@@ -2622,7 +2668,7 @@ var BUILD_ENV_VARS = [
2622
2668
  "AUTH_SECRET"
2623
2669
  ];
2624
2670
  var ENV_FILES = [".env.local", ".env"];
2625
- var PROJECT_LOCKFILES = [
2671
+ var PROJECT_LOCKFILES2 = [
2626
2672
  ["pnpm-lock.yaml", "pnpm"],
2627
2673
  ["yarn.lock", "yarn"],
2628
2674
  ["bun.lock", "bun"],
@@ -2633,7 +2679,7 @@ var hasValue = (value) => value !== void 0 && value.trim() !== "";
2633
2679
  var buildEnvLayers = (cwd, env2) => {
2634
2680
  const layers = [{ source: "process.env", values: env2 }];
2635
2681
  ENV_FILES.forEach((file) => {
2636
- const path = join11(cwd, file);
2682
+ const path = join12(cwd, file);
2637
2683
  if (!existsSync5(path)) {
2638
2684
  return;
2639
2685
  }
@@ -2656,7 +2702,7 @@ var resolveValue = (envSource, configValue) => {
2656
2702
  return { present: false, source: "unset" };
2657
2703
  };
2658
2704
  var asString = (value) => typeof value === "string" ? value : void 0;
2659
- var lockfileManager = (dir) => PROJECT_LOCKFILES.find(([file]) => existsSync5(join11(dir, file)))?.[1];
2705
+ var lockfileManager = (dir) => PROJECT_LOCKFILES2.find(([file]) => existsSync5(join12(dir, file)))?.[1];
2660
2706
  var detectProjectPackageManager2 = (cwd) => {
2661
2707
  let dir = cwd;
2662
2708
  for (; ; ) {
@@ -2671,7 +2717,7 @@ var detectProjectPackageManager2 = (cwd) => {
2671
2717
  dir = parent;
2672
2718
  }
2673
2719
  };
2674
- var projectJsonSchema2 = z12.looseObject({});
2720
+ var projectJsonSchema3 = z12.looseObject({});
2675
2721
  var safeReadJson2 = (path) => {
2676
2722
  try {
2677
2723
  return JSON.parse(readFileSync6(path, "utf-8"));
@@ -2679,9 +2725,9 @@ var safeReadJson2 = (path) => {
2679
2725
  return null;
2680
2726
  }
2681
2727
  };
2682
- var readProjectJson = (cwd) => {
2683
- const result = projectJsonSchema2.safeParse(
2684
- safeReadJson2(join11(cwd, ".bigcommerce", "project.json"))
2728
+ var readProjectJson2 = (cwd) => {
2729
+ const result = projectJsonSchema3.safeParse(
2730
+ safeReadJson2(join12(cwd, ".bigcommerce", "project.json"))
2685
2731
  );
2686
2732
  return result.success ? result.data : null;
2687
2733
  };
@@ -2691,7 +2737,7 @@ var storefrontPackageSchema = z12.looseObject({
2691
2737
  catalyst: z12.looseObject({ version: z12.string().optional() }).optional()
2692
2738
  });
2693
2739
  var readStorefrontInfo = (cwd) => {
2694
- const result = storefrontPackageSchema.safeParse(safeReadJson2(join11(cwd, "package.json")));
2740
+ const result = storefrontPackageSchema.safeParse(safeReadJson2(join12(cwd, "package.json")));
2695
2741
  if (!result.success) {
2696
2742
  return { name: null, version: null };
2697
2743
  }
@@ -2712,8 +2758,8 @@ function collectDiagnostics({
2712
2758
  env: env2 = process.env
2713
2759
  } = {}) {
2714
2760
  const state = getProjectState(cwd);
2715
- const telemetry3 = getTelemetry();
2716
- const projectJson = readProjectJson(cwd);
2761
+ const telemetry2 = getTelemetry();
2762
+ const projectJson = readProjectJson2(cwd);
2717
2763
  const storefront = readStorefrontInfo(cwd);
2718
2764
  const cliEnv = [{ source: "process.env", values: env2 }];
2719
2765
  const buildEnv = buildEnvLayers(cwd, env2);
@@ -2764,16 +2810,16 @@ function collectDiagnostics({
2764
2810
  buildEnvVars: resolveEnvVars(buildEnv, BUILD_ENV_VARS)
2765
2811
  },
2766
2812
  telemetry: {
2767
- enabled: telemetry3.isEnabled(),
2768
- correlationId: telemetry3.correlationId
2813
+ enabled: telemetry2.isEnabled(),
2814
+ correlationId: telemetry2.correlationId
2769
2815
  },
2770
2816
  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"))
2817
+ ".env.local": existsSync5(join12(cwd, ".env.local")),
2818
+ ".env": existsSync5(join12(cwd, ".env")),
2819
+ ".bigcommerce/project.json": existsSync5(join12(cwd, ".bigcommerce", "project.json")),
2820
+ ".bigcommerce/wrangler.jsonc": existsSync5(join12(cwd, ".bigcommerce", "wrangler.jsonc")),
2821
+ ".open-next/": existsSync5(join12(cwd, ".open-next")),
2822
+ "package.json": existsSync5(join12(cwd, "package.json"))
2777
2823
  }
2778
2824
  };
2779
2825
  }
@@ -2864,8 +2910,8 @@ import { confirm as confirm3 } from "@inquirer/prompts";
2864
2910
  import AdmZip from "adm-zip";
2865
2911
  import { Command as Command6, Option as Option6 } from "commander";
2866
2912
  import { colorize as colorize7 } from "consola/utils";
2867
- import { access, readdir, readFile } from "fs/promises";
2868
- import { join as join12 } from "path";
2913
+ import { access as access2, readdir, readFile } from "fs/promises";
2914
+ import { join as join13 } from "path";
2869
2915
  import yoctoSpinner3 from "yocto-spinner";
2870
2916
  import { z as z13 } from "zod";
2871
2917
 
@@ -2937,10 +2983,10 @@ var DeploymentStatusSchema = z13.object({
2937
2983
  });
2938
2984
  var generateBundleZip = async () => {
2939
2985
  consola.info("Generating bundle...");
2940
- const bigcommerceDir = join12(process.cwd(), ".bigcommerce");
2941
- const distDir = join12(process.cwd(), ".bigcommerce", "dist");
2986
+ const bigcommerceDir = join13(process.cwd(), ".bigcommerce");
2987
+ const distDir = join13(process.cwd(), ".bigcommerce", "dist");
2942
2988
  try {
2943
- await access(distDir);
2989
+ await access2(distDir);
2944
2990
  } catch {
2945
2991
  throw new Error(`Dist directory not found: ${distDir}`);
2946
2992
  }
@@ -2948,7 +2994,7 @@ var generateBundleZip = async () => {
2948
2994
  if (buildDirContents.length === 0) {
2949
2995
  throw new Error(`Dist directory is empty: ${distDir}`);
2950
2996
  }
2951
- const outputZip = join12(bigcommerceDir, "bundle.zip");
2997
+ const outputZip = join13(bigcommerceDir, "bundle.zip");
2952
2998
  const zip = new AdmZip();
2953
2999
  zip.addLocalFolder(distDir, "output");
2954
3000
  zip.writeZip(outputZip);
@@ -2980,7 +3026,7 @@ var generateUploadSignature = async (storeHash, accessToken, apiHost) => {
2980
3026
  };
2981
3027
  var uploadBundleZip = async (uploadUrl) => {
2982
3028
  consola.info("Uploading bundle...");
2983
- const zipPath = join12(process.cwd(), ".bigcommerce", "bundle.zip");
3029
+ const zipPath = join13(process.cwd(), ".bigcommerce", "bundle.zip");
2984
3030
  const fileBuffer = await readFile(zipPath);
2985
3031
  const response = await fetch(uploadUrl, {
2986
3032
  method: "PUT",
@@ -3115,7 +3161,7 @@ var fetchProject = async (projectUuid, storeHash, accessToken, apiHost) => {
3115
3161
  }
3116
3162
  const res = await response.json();
3117
3163
  const { data } = ProjectSchema.parse(res);
3118
- return data.find((project2) => project2.uuid === projectUuid);
3164
+ return data.find((project) => project.uuid === projectUuid);
3119
3165
  };
3120
3166
  var deploy = new Command6("deploy").configureHelp({ showGlobalOptions: true }).description("Deploy your application to Cloudflare.").addHelpText(
3121
3167
  "after",
@@ -3147,8 +3193,8 @@ Example:
3147
3193
  const config2 = getProjectConfig();
3148
3194
  const apiHost = resolveApiHost(options, config2);
3149
3195
  const { storeHash, accessToken } = resolveCredentials(options, config2);
3150
- const telemetry3 = getTelemetry();
3151
- await telemetry3.identify(storeHash);
3196
+ const telemetry2 = getTelemetry();
3197
+ await telemetry2.identify(storeHash);
3152
3198
  const linkedProjectUuid = options.projectUuid ?? config2.get("projectUuid");
3153
3199
  let projectUuid;
3154
3200
  const promptForProject = async () => {
@@ -3161,7 +3207,7 @@ Example:
3161
3207
  } catch (error) {
3162
3208
  if (error instanceof NoLinkedProjectError) {
3163
3209
  consola.info(
3164
- "When you're ready to create a project, run `catalyst project create` or re-run `catalyst deploy`."
3210
+ "When you're ready to create a project, run `catalyst projects create` or re-run `catalyst deploy`."
3165
3211
  );
3166
3212
  process.exit(0);
3167
3213
  }
@@ -3206,9 +3252,9 @@ Example:
3206
3252
  await cleanupCloudflareIncompatibilities(process.cwd());
3207
3253
  }
3208
3254
  if (options.prebuilt) {
3209
- const distDir = join12(process.cwd(), ".bigcommerce", "dist");
3255
+ const distDir = join13(process.cwd(), ".bigcommerce", "dist");
3210
3256
  try {
3211
- await access(distDir);
3257
+ await access2(distDir);
3212
3258
  } catch {
3213
3259
  throw new Error(
3214
3260
  "No build output found at .bigcommerce/dist/. Run `catalyst build` first or remove `--prebuilt` to build automatically."
@@ -3290,6 +3336,7 @@ var LOG_LEVELS = ["debug", "info", "warn", "error"];
3290
3336
  var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
3291
3337
  var logEntrySchema = z14.object({
3292
3338
  timestamp: z14.string().nullable().optional(),
3339
+ entry_type: z14.string().optional(),
3293
3340
  level: z14.string().optional(),
3294
3341
  messages: z14.array(z14.unknown()).optional(),
3295
3342
  is_exception: z14.boolean().optional(),
@@ -3301,7 +3348,22 @@ var logEntrySchema = z14.object({
3301
3348
  }).nullable().optional()
3302
3349
  });
3303
3350
  var queryLogsSchema = z14.object({
3304
- data: z14.array(logEntrySchema)
3351
+ data: z14.array(logEntrySchema),
3352
+ meta: z14.object({
3353
+ cursor_pagination: z14.object({
3354
+ // The REST proxy exposes page availability via links, while the
3355
+ // gRPC response uses has_next_page/has_prev_page. Accept both while
3356
+ // the API contract is transitioning.
3357
+ has_next_page: z14.boolean().optional(),
3358
+ has_prev_page: z14.boolean().optional(),
3359
+ start_cursor: z14.string().nullable().optional(),
3360
+ end_cursor: z14.string().nullable().optional(),
3361
+ links: z14.object({
3362
+ next: z14.string().optional(),
3363
+ previous: z14.string().optional()
3364
+ }).optional()
3365
+ }).optional()
3366
+ }).optional()
3305
3367
  });
3306
3368
  var v3ErrorSchema = z14.object({
3307
3369
  title: z14.string().optional(),
@@ -3391,8 +3453,16 @@ function isRedundantExceptionName(name, message) {
3391
3453
  return parts.length > 0 && parts.every((part) => message.includes(part));
3392
3454
  }
3393
3455
  var stringifyMessage = (message) => typeof message === "string" ? message : JSON.stringify(message);
3456
+ var joinMessages = (messages) => (messages ?? []).map(stringifyMessage).join(" ").replace(/\s+/g, " ").trim();
3457
+ function isRequestLogEntry(entry) {
3458
+ if (entry.entry_type != null) return entry.entry_type === "request";
3459
+ const method = entry.request?.method;
3460
+ const url = entry.request?.url;
3461
+ if (!method || !url) return false;
3462
+ return joinMessages(entry.messages) === `${method} ${url}`;
3463
+ }
3394
3464
  function formatLogEntry(entry, format = "default") {
3395
- const message = (entry.messages ?? []).map(stringifyMessage).join(" ").replace(/\s+/g, " ").trim();
3465
+ const message = joinMessages(entry.messages);
3396
3466
  if (format === "short") return message;
3397
3467
  const level = entry.level ?? "unknown";
3398
3468
  const coloredLevel = colorize8(LEVEL_COLORS[level.toLowerCase()] ?? "white", level.toUpperCase());
@@ -3405,17 +3475,23 @@ function formatLogEntry(entry, format = "default") {
3405
3475
  const status = entry.request.status_code ? ` (${entry.request.status_code})` : "";
3406
3476
  if (parts.length > 0) requestStr = ` ${parts.join(" ")}${status}`;
3407
3477
  }
3408
- return `[${entry.timestamp ?? UNKNOWN_TIME}] [${coloredLevel}]${requestStr}${exceptionStr} ${message}`;
3478
+ if (format === "request" && isRequestLogEntry(entry)) {
3479
+ return `[${entry.timestamp ?? UNKNOWN_TIME}]${requestStr}`;
3480
+ }
3481
+ return `[${entry.timestamp ?? UNKNOWN_TIME}]${requestStr} [${coloredLevel}]${exceptionStr} ${message}`;
3409
3482
  }
3410
3483
  async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
3411
3484
  const search = new URLSearchParams();
3412
3485
  search.set("start", params.start);
3413
3486
  search.set("end", params.end);
3487
+ if (params.entryType) search.set("entry_type", params.entryType);
3414
3488
  if (params.method) search.set("method", params.method);
3415
3489
  if (params.statusCode != null) search.set("status_code", String(params.statusCode));
3416
3490
  if (params.urlLike) search.set("url:like", params.urlLike);
3417
3491
  if (params.levelMin) search.set("level:min", params.levelMin);
3418
3492
  if (params.limit != null) search.set("limit", String(params.limit));
3493
+ if (params.after) search.set("after", params.after);
3494
+ if (params.before) search.set("before", params.before);
3419
3495
  const response = await fetch(
3420
3496
  `https://${apiHost}/stores/${storeHash}/v3/infrastructure/logs/${projectUuid}?${search.toString()}`,
3421
3497
  {
@@ -3434,7 +3510,9 @@ async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
3434
3510
  );
3435
3511
  }
3436
3512
  if (response.status === 404) {
3437
- throw new UserActionableError("Project not found. Check the project UUID.");
3513
+ throw new UserActionableError(
3514
+ "Project not found. Check the project UUID, and that --store-hash and --access-token belong to the store that owns the project."
3515
+ );
3438
3516
  }
3439
3517
  if (response.status === 400 || response.status === 422) {
3440
3518
  const body = await response.json().catch(() => null);
@@ -3449,11 +3527,18 @@ async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
3449
3527
 
3450
3528
  // src/cli/lib/domains.ts
3451
3529
  var domainStatusSchema = z15.enum(["pending", "verified", "failed", "unknown"]);
3530
+ var pointingRecordsSchema = z15.object({
3531
+ a_record_value: z15.string().nullish(),
3532
+ cname_record_value: z15.string().nullish()
3533
+ });
3452
3534
  var domainSchema = z15.object({
3453
3535
  domain: z15.string(),
3454
3536
  project_uuid: z15.string(),
3455
3537
  verification_status: domainStatusSchema
3456
3538
  });
3539
+ var createdDomainSchema = domainSchema.extend({
3540
+ pointing_records: pointingRecordsSchema.nullish()
3541
+ });
3457
3542
  var ownershipVerificationSchema = z15.object({
3458
3543
  type: z15.string(),
3459
3544
  name: z15.string(),
@@ -3494,6 +3579,9 @@ function parseBoundProjectUuid(body) {
3494
3579
  var domainResponseSchema = z15.object({
3495
3580
  data: domainSchema
3496
3581
  });
3582
+ var createdDomainResponseSchema = z15.object({
3583
+ data: createdDomainSchema
3584
+ });
3497
3585
  var domainListResponseSchema = z15.object({
3498
3586
  data: z15.array(domainSchema)
3499
3587
  });
@@ -3562,7 +3650,7 @@ async function createDomain(domain, projectUuid, storeHash, accessToken, apiHost
3562
3650
  });
3563
3651
  await assertDomainResponse(response, "Failed to add domain");
3564
3652
  const result = await response.json();
3565
- return domainResponseSchema.parse(result).data;
3653
+ return createdDomainResponseSchema.parse(result).data;
3566
3654
  }
3567
3655
  async function getDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
3568
3656
  const response = await fetch(domainUrl(storeHash, projectUuid, domain, apiHost), {
@@ -3633,6 +3721,7 @@ async function transferDomain(domain, projectUuid, newProjectUuid, storeHash, ac
3633
3721
  var WAIT_INTERVAL_MS = 5e3;
3634
3722
  var WAIT_TIMEOUT_MS = 5 * 60 * 1e3;
3635
3723
  var DOMAIN_STATUS_FILTERS = ["pending", "verified", "failed"];
3724
+ var RECORD_TYPE_WIDTH = "CNAME".length;
3636
3725
  var STATUS_COLORS = {
3637
3726
  pending: "yellow",
3638
3727
  verified: "green",
@@ -3666,20 +3755,20 @@ async function resolveDestinationProject(domain, context, toProjectUuid) {
3666
3755
  return toProjectUuid;
3667
3756
  }
3668
3757
  consola.start("Fetching projects...");
3669
- const projects = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
3758
+ const projects2 = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
3670
3759
  consola.success("Projects fetched.");
3671
- const destinations = projects.filter((project2) => project2.uuid !== context.projectUuid);
3760
+ const destinations = projects2.filter((project) => project.uuid !== context.projectUuid);
3672
3761
  if (destinations.length === 0) {
3673
3762
  throw new UserActionableError(
3674
- "No other projects to transfer to. Create another project with `catalyst project create` first."
3763
+ "No other projects to transfer to. Create another project with `catalyst projects create` first."
3675
3764
  );
3676
3765
  }
3677
3766
  return select6({
3678
3767
  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
3768
+ choices: destinations.map((project) => ({
3769
+ name: project.name,
3770
+ value: project.uuid,
3771
+ description: project.uuid
3683
3772
  }))
3684
3773
  });
3685
3774
  }
@@ -3689,6 +3778,19 @@ function formatDomainStatus(status) {
3689
3778
  function formatDomain(domain) {
3690
3779
  return `${domain.domain} ${formatDomainStatus(domain.verification_status)}`;
3691
3780
  }
3781
+ function formatPointingRecords(records) {
3782
+ const rows = [];
3783
+ if (records?.a_record_value) {
3784
+ rows.push(`${"A".padEnd(RECORD_TYPE_WIDTH)} ${records.a_record_value}`);
3785
+ }
3786
+ if (records?.cname_record_value) {
3787
+ rows.push(`${"CNAME".padEnd(RECORD_TYPE_WIDTH)} ${records.cname_record_value}`);
3788
+ }
3789
+ if (rows.length === 0) {
3790
+ return null;
3791
+ }
3792
+ return rows.map((row) => ` ${row}`).join("\n");
3793
+ }
3692
3794
  function formatOwnershipVerification(record) {
3693
3795
  const lines = [
3694
3796
  "Add this DNS record to verify ownership:",
@@ -3720,6 +3822,10 @@ async function waitForDomainVerification({
3720
3822
  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(
3721
3823
  "after",
3722
3824
  `
3825
+ The DNS records that point the domain at this project are printed once the domain
3826
+ is added. They are returned only by this command, so save them: publish the CNAME
3827
+ for a subdomain, or the A record for an apex domain.
3828
+
3723
3829
  Examples:
3724
3830
  $ catalyst domains add www.example.com
3725
3831
 
@@ -3729,9 +3835,9 @@ Examples:
3729
3835
  const context = resolveDomainCommandContext(options);
3730
3836
  await getTelemetry().identify(context.storeHash);
3731
3837
  consola.start(`Adding domain ${domain}...`);
3732
- let result;
3838
+ let created;
3733
3839
  try {
3734
- result = await createDomain(
3840
+ created = await createDomain(
3735
3841
  domain,
3736
3842
  context.projectUuid,
3737
3843
  context.storeHash,
@@ -3758,12 +3864,26 @@ Examples:
3758
3864
  }
3759
3865
  throw error;
3760
3866
  }
3761
- consola.success(`Domain ${result.domain} added.`);
3867
+ consola.success(`Domain ${created.domain} added.`);
3868
+ const records = formatPointingRecords(created.pointing_records);
3869
+ let result = created;
3762
3870
  if (options.wait && result.verification_status === "pending") {
3763
3871
  consola.start(`Waiting for ${result.domain} to verify...`);
3764
3872
  result = await waitForDomainVerification({ domain: result.domain, ...context });
3765
3873
  }
3766
3874
  consola.log(formatDomain(result));
3875
+ if (records) {
3876
+ consola.info(`Point ${created.domain} at this project with one of these DNS records:`);
3877
+ consola.log(records);
3878
+ consola.info(
3879
+ "Use the CNAME for a subdomain, or the A record for an apex domain. Save them now \u2014 only `domains add` returns them."
3880
+ );
3881
+ }
3882
+ if (result.verification_status === "pending") {
3883
+ consola.info(
3884
+ `Run \`catalyst domains status ${result.domain}\` to check verification progress.`
3885
+ );
3886
+ }
3767
3887
  process.exit(0);
3768
3888
  });
3769
3889
  var list = new Command7("list").configureHelp({ showGlobalOptions: true }).description("List custom domains for the current Native Hosting project.").addHelpText(
@@ -3903,14 +4023,14 @@ Examples:
3903
4023
  );
3904
4024
  if (!owned) {
3905
4025
  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);
4026
+ const projects2 = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
4027
+ const candidates = projects2.filter((project) => project.uuid !== context.projectUuid);
3908
4028
  const matches = await Promise.all(
3909
- candidates.map(async (project2) => ({
3910
- project: project2,
4029
+ candidates.map(async (project) => ({
4030
+ project,
3911
4031
  found: await findDomain(
3912
4032
  domain,
3913
- project2.uuid,
4033
+ project.uuid,
3914
4034
  context.storeHash,
3915
4035
  context.accessToken,
3916
4036
  context.apiHost
@@ -4005,7 +4125,7 @@ Examples:
4005
4125
  consola.success(`Domain ${current.domain} removed.`);
4006
4126
  process.exit(0);
4007
4127
  });
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);
4128
+ var domains = new Command7("domains").alias("domain").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);
4009
4129
 
4010
4130
  // src/cli/commands/env.ts
4011
4131
  import { Command as Command8 } from "commander";
@@ -4083,7 +4203,6 @@ var env = new Command8("env").configureHelp({ showGlobalOptions: true }).descrip
4083
4203
  import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as Option8 } from "commander";
4084
4204
  import { colorize as colorize10 } from "consola/utils";
4085
4205
  import { z as z16 } from "zod";
4086
- var telemetry = new Telemetry();
4087
4206
  var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
4088
4207
  var MAX_RETRIES = 5;
4089
4208
  var isFatalStatusCode = (status) => status >= 400 && status < 500;
@@ -4118,8 +4237,19 @@ var StreamError = class extends Error {
4118
4237
  }
4119
4238
  };
4120
4239
  var formatMessages = (messages) => messages.map((m) => typeof m === "string" ? m : JSON.stringify(m)).join(" ");
4240
+ var formatRequestLine = (event, entry) => {
4241
+ const { request } = event;
4242
+ const msg = entry ? formatMessages(entry.messages) : "";
4243
+ const msgPart = msg ? ` ${msg}` : "";
4244
+ const level = msg ? entry?.level.toUpperCase() : void 0;
4245
+ const levelPart = level ? ` [${colorize10(LEVEL_COLORS2[level] ?? "white", level)}]` : "";
4246
+ return `[${entry?.timestamp ?? event.timestamp}] ${request.method} ${request.url} (${request.status_code})${levelPart}${msgPart}`;
4247
+ };
4121
4248
  var formatLogEvent = (event, format) => {
4122
- const { request, logs: logEntries, exceptions } = event;
4249
+ const { logs: logEntries, exceptions } = event;
4250
+ if (format === "request" && logEntries.length === 0) {
4251
+ consola.log(formatRequestLine(event));
4252
+ }
4123
4253
  logEntries.forEach((entry) => {
4124
4254
  const msg = formatMessages(entry.messages);
4125
4255
  const level = entry.level.toUpperCase();
@@ -4129,9 +4259,7 @@ var formatLogEvent = (event, format) => {
4129
4259
  consola.log(msg);
4130
4260
  break;
4131
4261
  case "request":
4132
- consola.log(
4133
- `[${entry.timestamp}] [${coloredLevel}] ${request.method} ${request.url} (${request.status_code}) ${msg}`
4134
- );
4262
+ consola.log(formatRequestLine(event, entry));
4135
4263
  break;
4136
4264
  default:
4137
4265
  consola.log(`[${entry.timestamp}] [${coloredLevel}] ${msg}`);
@@ -4267,6 +4395,19 @@ var tailLogs = async (projectUuid, storeHash, accessToken, apiHost, format, conn
4267
4395
  var tail = new Command9("tail").configureHelp({ showGlobalOptions: true }).description("Tail live logs from your deployed application.").addHelpText(
4268
4396
  "after",
4269
4397
  `
4398
+ Formats:
4399
+ default One line per log message: timestamp, level, and message.
4400
+ short Message text only.
4401
+ request One line per log message: timestamp, request method, URL, status
4402
+ code, level, and message. Requests that logged no messages are
4403
+ printed too, without the level and message.
4404
+ pretty Indented JSON of the whole event.
4405
+ json Raw JSON, one event per line (useful for piping to other tools).
4406
+
4407
+ The \`default\` and \`short\` formats only print requests that produced a log
4408
+ message. Use \`--format request\` (or \`json\`/\`pretty\`) to see every request,
4409
+ including ones with no message body.
4410
+
4270
4411
  Examples:
4271
4412
  $ catalyst logs tail
4272
4413
 
@@ -4282,7 +4423,7 @@ Examples:
4282
4423
  const config2 = getProjectConfig();
4283
4424
  const apiHost = resolveApiHost(options, config2);
4284
4425
  const { storeHash, accessToken } = resolveCredentials(options, config2);
4285
- await telemetry.identify(storeHash);
4426
+ await getTelemetry().identify(storeHash);
4286
4427
  const projectUuid = resolveProjectUuid(options);
4287
4428
  await tailLogs(projectUuid, storeHash, accessToken, apiHost, options.format);
4288
4429
  } catch (error) {
@@ -4297,17 +4438,74 @@ var parseIntInRange = (flag, min, max) => (value) => {
4297
4438
  }
4298
4439
  return parsed;
4299
4440
  };
4441
+ function invocationPrefix() {
4442
+ const packageManager = process.env.npm_config_user_agent?.split("/")[0];
4443
+ switch (packageManager) {
4444
+ case "pnpm":
4445
+ case "yarn":
4446
+ return `${packageManager} catalyst`;
4447
+ case "bun":
4448
+ return "bunx catalyst";
4449
+ case "npm":
4450
+ return "npx catalyst";
4451
+ default:
4452
+ return "catalyst";
4453
+ }
4454
+ }
4455
+ function printPaginationHints(pagination, start2, end, options) {
4456
+ const hasNextPage = Boolean(pagination?.links?.next) || pagination?.has_next_page === true;
4457
+ const hasPrevPage = Boolean(pagination?.links?.previous) || pagination?.has_prev_page === true;
4458
+ const endCursor = pagination?.end_cursor;
4459
+ const startCursor = pagination?.start_cursor;
4460
+ if (!(hasNextPage && endCursor) && !(hasPrevPage && startCursor)) return;
4461
+ const quoteArg = (value) => /\s/.test(value) ? `'${value}'` : value;
4462
+ const baseFlags = [
4463
+ `--start ${quoteArg(start2)}`,
4464
+ `--end ${quoteArg(end)}`,
4465
+ ...options.method ? [`--method ${quoteArg(options.method)}`] : [],
4466
+ ...options.statusCode != null ? [`--status-code ${options.statusCode}`] : [],
4467
+ ...options.urlLike ? [`--url-like ${quoteArg(options.urlLike)}`] : [],
4468
+ ...options.levelMin ? [`--level-min ${options.levelMin}`] : [],
4469
+ ...options.limit != null ? [`--limit ${options.limit}`] : [],
4470
+ // The entry_type filter derives from --format (default/short request only
4471
+ // application logs), so echoing --format is enough to reproduce it.
4472
+ ...options.format !== "default" ? [`--format ${options.format}`] : []
4473
+ ];
4474
+ const command = `${invocationPrefix()} logs query ${baseFlags.join(" ")}`;
4475
+ if (hasNextPage && endCursor) {
4476
+ consola.info(`More results available. Next page:
4477
+ ${command} --after ${quoteArg(endCursor)}`);
4478
+ }
4479
+ if (hasPrevPage && startCursor) {
4480
+ consola.info(
4481
+ `Newer results available. Previous page:
4482
+ ${command} --before ${quoteArg(startCursor)}`
4483
+ );
4484
+ }
4485
+ }
4300
4486
  var query = new Command9("query").configureHelp({ showGlobalOptions: true }).description("Query historical logs from your deployed application.").addHelpText(
4301
4487
  "after",
4302
4488
  `
4303
4489
  Specify a time window with \`--since\` (relative to now) or \`--start\`/\`--end\`
4304
4490
  (ISO-8601 timestamps or Unix epoch seconds, UTC). The window may not exceed
4305
- 7 days. Entries print oldest-first; timestamps are UTC.
4491
+ 7 days. Entries print oldest-first; timestamps are UTC. Page toward older
4492
+ entries by passing a previous page's end cursor to \`--after\` (the CLI prints
4493
+ a ready-to-run command when more entries are available), or toward newer
4494
+ entries with \`--before\`. Cursors are only valid with the same window and
4495
+ filters they came from. With \`--format json\`, the pagination cursors are
4496
+ emitted as a final \`{"meta": ...}\` line after the entries.
4497
+
4498
+ The \`default\` and \`short\` formats show only application log output,
4499
+ excluding Cloudflare's auto-generated per-request rows. Use
4500
+ \`--format request\` (or \`json\`/\`pretty\`) to include them.
4306
4501
 
4307
4502
  Examples:
4308
4503
  # Last hour of logs
4309
4504
  $ catalyst logs query --since 1h
4310
4505
 
4506
+ # Newest 20 errors in the window (follow the printed --after hint for more)
4507
+ $ catalyst logs query --since 24h --level-min error --limit 20
4508
+
4311
4509
  # Everything from today (UTC)
4312
4510
  $ catalyst logs query --start 2026-06-11T00:00:00Z
4313
4511
 
@@ -4334,7 +4532,20 @@ Examples:
4334
4532
  ).option("--url-like <substring>", "Filter by URL substring (case-sensitive).").addOption(
4335
4533
  new Option8("--level-min <level>", "Minimum log level.").choices(Array.from(LOG_LEVELS))
4336
4534
  ).addOption(
4337
- new Option8("--limit <n>", "Maximum number of entries to return (1-500).").argParser(parseIntInRange("--limit", 1, 500)).default(100)
4535
+ new Option8(
4536
+ "--limit <count>",
4537
+ "Maximum entries per page (1-500). Defaults to the API default (100)."
4538
+ ).argParser(parseIntInRange("--limit", 1, 500))
4539
+ ).addOption(
4540
+ new Option8(
4541
+ "--after <cursor>",
4542
+ "Return the page after (older than) this cursor, from a previous page's end_cursor."
4543
+ ).conflicts("before")
4544
+ ).addOption(
4545
+ new Option8(
4546
+ "--before <cursor>",
4547
+ "Return the page before (newer than) this cursor, from a previous page's start_cursor."
4548
+ )
4338
4549
  ).addOption(
4339
4550
  new Option8("--format <format>", "Output format for log entries.").choices(["json", "pretty", "default", "short", "request"]).default("default")
4340
4551
  ).action(async (options) => {
@@ -4342,23 +4553,34 @@ Examples:
4342
4553
  const config2 = getProjectConfig();
4343
4554
  const apiHost = resolveApiHost(options, config2);
4344
4555
  const { storeHash, accessToken } = resolveCredentials(options, config2);
4345
- await telemetry.identify(storeHash);
4556
+ await getTelemetry().identify(storeHash);
4346
4557
  const projectUuid = resolveProjectUuid(options);
4347
4558
  const { start: start2, end } = resolveTimeWindow(options);
4559
+ const hideRequestRows = options.format === "default" || options.format === "short";
4348
4560
  const result = await queryLogs(projectUuid, storeHash, accessToken, apiHost, {
4349
4561
  start: start2,
4350
4562
  end,
4563
+ entryType: hideRequestRows ? "log" : void 0,
4351
4564
  method: options.method,
4352
4565
  statusCode: options.statusCode,
4353
4566
  urlLike: options.urlLike,
4354
4567
  levelMin: options.levelMin,
4355
- limit: options.limit
4568
+ limit: options.limit,
4569
+ after: options.after,
4570
+ before: options.before
4356
4571
  });
4357
4572
  const entries = result.data;
4358
4573
  const ordered = [...entries].reverse();
4359
4574
  if (options.format === "json") {
4360
4575
  ordered.forEach((entry) => process.stdout.write(`${JSON.stringify(entry)}
4361
4576
  `));
4577
+ const cursorPagination = result.meta?.cursor_pagination;
4578
+ if (cursorPagination) {
4579
+ process.stdout.write(
4580
+ `${JSON.stringify({ meta: { cursor_pagination: cursorPagination } })}
4581
+ `
4582
+ );
4583
+ }
4362
4584
  return;
4363
4585
  }
4364
4586
  if (options.format === "pretty") {
@@ -4370,23 +4592,22 @@ Examples:
4370
4592
  return;
4371
4593
  }
4372
4594
  const lineFormat = options.format;
4373
- ordered.forEach((entry) => consola.log(formatLogEntry(entry, lineFormat)));
4595
+ const visible = hideRequestRows ? ordered.filter((entry) => !isRequestLogEntry(entry)) : ordered;
4596
+ const hiddenCount = ordered.length - visible.length;
4597
+ visible.forEach((entry) => consola.log(formatLogEntry(entry, lineFormat)));
4598
+ const shownPart = `${visible.length} ${visible.length === 1 ? "entry" : "entries"} shown`;
4374
4599
  consola.info(
4375
- `${entries.length} ${entries.length === 1 ? "entry" : "entries"} shown (oldest first, times in UTC).`
4600
+ hiddenCount > 0 ? `${shownPart}, ${hiddenCount} request ${hiddenCount === 1 ? "row" : "rows"} hidden (oldest first, times in UTC). Use --format request to see request rows.` : `${shownPart} (oldest first, times in UTC).`
4376
4601
  );
4377
- if (entries.length === options.limit) {
4378
- consola.info(
4379
- `Limit of ${options.limit} reached; older entries may exist. Narrow the time window or raise --limit (max 500) to see more.`
4380
- );
4381
- }
4602
+ printPaginationHints(result.meta?.cursor_pagination, start2, end, options);
4382
4603
  } catch (error) {
4383
4604
  consola.error(error instanceof Error ? error.message : error);
4384
4605
  process.exit(1);
4385
4606
  }
4386
4607
  });
4387
- var logs = new Command9("logs").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
4608
+ var logs = new Command9("logs").alias("log").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
4388
4609
 
4389
- // src/cli/commands/project.ts
4610
+ // src/cli/commands/projects.ts
4390
4611
  import { confirm as confirm5, input as input5, select as select7 } from "@inquirer/prompts";
4391
4612
  import { Command as Command10 } from "commander";
4392
4613
  import { colorize as colorize11 } from "consola/utils";
@@ -4415,22 +4636,22 @@ var list3 = new Command10("list").configureHelp({ showGlobalOptions: true }).des
4415
4636
  "after",
4416
4637
  `
4417
4638
  Example:
4418
- $ catalyst project list`
4639
+ $ catalyst projects list`
4419
4640
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).action(async (options) => {
4420
4641
  const config2 = getProjectConfig();
4421
4642
  const apiHost = resolveApiHost(options, config2);
4422
4643
  const { storeHash, accessToken } = resolveCredentials(options, config2);
4423
4644
  await getTelemetry().identify(storeHash);
4424
4645
  consola.start("Fetching projects...");
4425
- const projects = await fetchProjects(storeHash, accessToken, apiHost);
4646
+ const storeProjects = await fetchProjects(storeHash, accessToken, apiHost);
4426
4647
  consola.success("Projects fetched.");
4427
- if (projects.length === 0) {
4648
+ if (storeProjects.length === 0) {
4428
4649
  consola.info("No projects found.");
4429
4650
  process.exit(0);
4430
4651
  return;
4431
4652
  }
4432
4653
  const linkedProjectUuid = config2.get("projectUuid");
4433
- const projectList = projects.map((p) => {
4654
+ const projectList = storeProjects.map((p) => {
4434
4655
  const marker = p.uuid === linkedProjectUuid ? ` ${colorize11("green", "[linked]")}` : "";
4435
4656
  const hostnames = p.deployment_hostnames.length === 0 ? [" (not deployed)"] : p.deployment_hostnames.map(
4436
4657
  (hostname) => ` ${colorize11("blue", `https://${hostname}`)}`
@@ -4446,7 +4667,7 @@ var create3 = new Command10("create").configureHelp({ showGlobalOptions: true })
4446
4667
  "after",
4447
4668
  `
4448
4669
  Example:
4449
- $ catalyst project create`
4670
+ $ catalyst projects create`
4450
4671
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).action(async (options) => {
4451
4672
  const config2 = getProjectConfig();
4452
4673
  const apiHost = resolveApiHost(options, config2);
@@ -4466,7 +4687,7 @@ Example:
4466
4687
  } catch (error) {
4467
4688
  if (error instanceof LoginAbortedError) {
4468
4689
  consola.info(
4469
- "Login aborted. Re-run `catalyst project create` when you have your credentials ready."
4690
+ "Login aborted. Re-run `catalyst projects create` when you have your credentials ready."
4470
4691
  );
4471
4692
  process.exit(0);
4472
4693
  return;
@@ -4492,10 +4713,10 @@ var link2 = new Command10("link").configureHelp({ showGlobalOptions: true }).des
4492
4713
  `
4493
4714
  Examples:
4494
4715
  # Link interactively (prompts to select or create)
4495
- $ catalyst project link
4716
+ $ catalyst projects link
4496
4717
 
4497
4718
  # Link using a project UUID directly
4498
- $ catalyst project link --project-uuid <UUID>`
4719
+ $ catalyst projects link --project-uuid <UUID>`
4499
4720
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).action(async (options) => {
4500
4721
  const config2 = getProjectConfig();
4501
4722
  const apiHost = resolveApiHost(options, config2);
@@ -4525,7 +4746,7 @@ Examples:
4525
4746
  } catch (error) {
4526
4747
  if (error instanceof NoLinkedProjectError) {
4527
4748
  consola.info(
4528
- "When you're ready to create a project, run `catalyst project create` or re-run `catalyst project link`."
4749
+ "When you're ready to create a project, run `catalyst projects create` or re-run `catalyst projects link`."
4529
4750
  );
4530
4751
  process.exit(0);
4531
4752
  throw error;
@@ -4543,13 +4764,13 @@ var del = new Command10("delete").configureHelp({ showGlobalOptions: true }).des
4543
4764
  `
4544
4765
  Examples:
4545
4766
  # Select a project to delete interactively
4546
- $ catalyst project delete
4767
+ $ catalyst projects delete
4547
4768
 
4548
4769
  # Delete a specific project (still prompts for confirmation)
4549
- $ catalyst project delete --project-uuid <UUID>
4770
+ $ catalyst projects delete --project-uuid <UUID>
4550
4771
 
4551
4772
  # Skip the confirmation prompt
4552
- $ catalyst project delete --project-uuid <UUID> --force`
4773
+ $ catalyst projects delete --project-uuid <UUID> --force`
4553
4774
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--force", "Skip the confirmation prompt before deleting.").action(async (options) => {
4554
4775
  const config2 = getProjectConfig();
4555
4776
  const apiHost = resolveApiHost(options, config2);
@@ -4559,9 +4780,9 @@ Examples:
4559
4780
  let targetName;
4560
4781
  if (!targetUuid) {
4561
4782
  consola.start("Fetching projects...");
4562
- const projects = await fetchProjects(storeHash, accessToken, apiHost);
4783
+ const storeProjects = await fetchProjects(storeHash, accessToken, apiHost);
4563
4784
  consola.success("Projects fetched.");
4564
- if (projects.length === 0) {
4785
+ if (storeProjects.length === 0) {
4565
4786
  consola.info("No projects found.");
4566
4787
  process.exit(0);
4567
4788
  return;
@@ -4570,7 +4791,7 @@ Examples:
4570
4791
  const selected = await select7({
4571
4792
  message: "Select a project to delete (Press <enter> to select).",
4572
4793
  choices: [
4573
- ...projects.map((p) => ({
4794
+ ...storeProjects.map((p) => ({
4574
4795
  name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize11("green", "[linked]")}` : p.name,
4575
4796
  value: p.uuid,
4576
4797
  description: p.uuid
@@ -4583,7 +4804,7 @@ Examples:
4583
4804
  process.exit(0);
4584
4805
  return;
4585
4806
  }
4586
- const matched = projects.find((p) => p.uuid === selected);
4807
+ const matched = storeProjects.find((p) => p.uuid === selected);
4587
4808
  if (!matched) {
4588
4809
  throw new Error(`Selected project ${String(selected)} not found in fetched list.`);
4589
4810
  }
@@ -4611,13 +4832,13 @@ Examples:
4611
4832
  }
4612
4833
  process.exit(0);
4613
4834
  });
4614
- var project = new Command10("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure project.").addCommand(create3).addCommand(list3).addCommand(link2).addCommand(del);
4835
+ var projects = new Command10("projects").alias("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure projects.").addCommand(create3).addCommand(list3).addCommand(link2).addCommand(del);
4615
4836
 
4616
4837
  // src/cli/commands/start.ts
4617
4838
  import { Command as Command11 } from "commander";
4618
4839
  import { execa as execa3 } from "execa";
4619
4840
  import { existsSync as existsSync6, lstatSync, symlinkSync } from "fs";
4620
- import { join as join13, relative } from "path";
4841
+ import { join as join14, relative } from "path";
4621
4842
  var start = new Command11("start").configureHelp({ showGlobalOptions: true }).description(
4622
4843
  "Start a local preview of your Catalyst storefront using the OpenNext Cloudflare adapter."
4623
4844
  ).addHelpText(
@@ -4635,11 +4856,11 @@ Example:
4635
4856
  });
4636
4857
  return;
4637
4858
  }
4638
- const envLocal = join13(process.cwd(), ".env.local");
4639
- const devVars = join13(process.cwd(), ".bigcommerce", ".dev.vars");
4859
+ const envLocal = join14(process.cwd(), ".env.local");
4860
+ const devVars = join14(process.cwd(), ".bigcommerce", ".dev.vars");
4640
4861
  if (existsSync6(envLocal)) {
4641
4862
  if (!existsSync6(devVars)) {
4642
- const target = relative(join13(process.cwd(), ".bigcommerce"), envLocal);
4863
+ const target = relative(join14(process.cwd(), ".bigcommerce"), envLocal);
4643
4864
  symlinkSync(target, devVars);
4644
4865
  consola.success("Symlinked .bigcommerce/.dev.vars \u2192 .env.local");
4645
4866
  } else if (!lstatSync(devVars).isSymbolicLink()) {
@@ -4659,7 +4880,7 @@ Example:
4659
4880
  "opennextjs-cloudflare",
4660
4881
  "preview",
4661
4882
  "--config",
4662
- join13(".bigcommerce", "wrangler.jsonc")
4883
+ join14(".bigcommerce", "wrangler.jsonc")
4663
4884
  ],
4664
4885
  {
4665
4886
  stdio: "inherit",
@@ -4673,7 +4894,7 @@ import { Argument, Command as Command12, Option as Option9 } from "commander";
4673
4894
  import { colorize as colorize12 } from "consola/utils";
4674
4895
  var telemetryService = getTelemetry();
4675
4896
  var isEnabled = telemetryService.isEnabled();
4676
- var telemetry2 = new Command12("telemetry").configureHelp({ showGlobalOptions: true }).description(
4897
+ var telemetry = new Command12("telemetry").configureHelp({ showGlobalOptions: true }).description(
4677
4898
  "View or change CLI telemetry collection status. Enabling telemetry helps BigCommerce support diagnose and troubleshoot errors you encounter when using the CLI."
4678
4899
  ).addHelpText(
4679
4900
  "after",
@@ -4718,7 +4939,7 @@ import { Command as Command13, Option as Option10 } from "@commander-js/extra-ty
4718
4939
  import { confirm as confirm6 } from "@inquirer/prompts";
4719
4940
  import { execa as execa4 } from "execa";
4720
4941
  import {
4721
- access as access2,
4942
+ access as access3,
4722
4943
  copyFile as copyFile2,
4723
4944
  cp as cp2,
4724
4945
  mkdir,
@@ -4730,7 +4951,8 @@ import {
4730
4951
  writeFile as writeFile2
4731
4952
  } from "fs/promises";
4732
4953
  import { homedir, tmpdir } from "os";
4733
- import { dirname as dirname5, join as join14, relative as relative2 } from "path";
4954
+ import { dirname as dirname5, join as join15, relative as relative2 } from "path";
4955
+ import { minVersion, satisfies, lt as semverLt, validRange, valid as validSemver2 } from "semver";
4734
4956
  import yoctoSpinner4 from "yocto-spinner";
4735
4957
  import { z as z17 } from "zod";
4736
4958
  var CorePackageJson = z17.object({
@@ -4738,6 +4960,20 @@ var CorePackageJson = z17.object({
4738
4960
  version: z17.string(),
4739
4961
  catalyst: z17.object({ version: z17.string(), ref: z17.string() }).optional()
4740
4962
  });
4963
+ var DEP_FIELDS2 = [
4964
+ "dependencies",
4965
+ "devDependencies",
4966
+ "optionalDependencies",
4967
+ "peerDependencies"
4968
+ ];
4969
+ var DepsPackageJson = z17.looseObject({
4970
+ dependencies: z17.record(z17.string(), z17.string()).optional(),
4971
+ devDependencies: z17.record(z17.string(), z17.string()).optional(),
4972
+ optionalDependencies: z17.record(z17.string(), z17.string()).optional(),
4973
+ peerDependencies: z17.record(z17.string(), z17.string()).optional()
4974
+ });
4975
+ var WorkspacePackageJson = z17.object({ name: z17.string(), version: z17.string() });
4976
+ var CATALYST_CLI_PACKAGE = "@bigcommerce/catalyst";
4741
4977
  var DEFAULT_REPOSITORY = "bigcommerce/catalyst";
4742
4978
  var MOVING_TAGS = /* @__PURE__ */ new Set(["latest", "canary", "alpha"]);
4743
4979
  var KNOWN_FAMILIES = /* @__PURE__ */ new Set([
@@ -4746,14 +4982,14 @@ var KNOWN_FAMILIES = /* @__PURE__ */ new Set([
4746
4982
  "@bigcommerce/catalyst-b2b-makeswift",
4747
4983
  "@bigcommerce/catalyst-b2b"
4748
4984
  ]);
4749
- var CACHE_DIR = join14(homedir(), ".cache", "catalyst-cli", "cores");
4750
- var pathExists = (p) => access2(p).then(() => true).catch(() => false);
4985
+ var CACHE_DIR = join15(homedir(), ".cache", "catalyst-cli", "cores");
4986
+ var pathExists = (p) => access3(p).then(() => true).catch(() => false);
4751
4987
  async function listFiles(dir, prefix = "") {
4752
4988
  const entries = await readdir2(dir, { withFileTypes: true }).catch(() => []);
4753
4989
  const nested = await Promise.all(
4754
4990
  entries.map((entry) => {
4755
4991
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
4756
- return entry.isDirectory() ? listFiles(join14(dir, entry.name), rel) : Promise.resolve([rel]);
4992
+ return entry.isDirectory() ? listFiles(join15(dir, entry.name), rel) : Promise.resolve([rel]);
4757
4993
  })
4758
4994
  );
4759
4995
  return nested.flat();
@@ -4769,7 +5005,7 @@ async function computeBaseSimilarity(baseDir, destDir) {
4769
5005
  const baseFiles = await listFiles(baseDir);
4770
5006
  if (baseFiles.length === 0) return 0;
4771
5007
  const matches = await Promise.all(
4772
- baseFiles.map((rel) => filesEqual(join14(baseDir, rel), join14(destDir, rel)))
5008
+ baseFiles.map((rel) => filesEqual(join15(baseDir, rel), join15(destDir, rel)))
4773
5009
  );
4774
5010
  return matches.filter(Boolean).length / baseFiles.length;
4775
5011
  }
@@ -4788,10 +5024,79 @@ function parseRef(ref) {
4788
5024
  return { packageName: ref.slice(0, lastAt), version: ref.slice(lastAt + 1) };
4789
5025
  }
4790
5026
  async function readResolvedVersion(coreDir) {
4791
- const raw = await readFile2(join14(coreDir, "package.json"), "utf-8");
5027
+ const raw = await readFile2(join15(coreDir, "package.json"), "utf-8");
4792
5028
  const pkg = CorePackageJson.parse(JSON.parse(raw));
4793
5029
  return pkg.catalyst?.version ?? pkg.version;
4794
5030
  }
5031
+ var readDepSpecifiers = (raw) => {
5032
+ let parsed;
5033
+ try {
5034
+ parsed = DepsPackageJson.parse(JSON.parse(raw));
5035
+ } catch {
5036
+ return {};
5037
+ }
5038
+ return Object.fromEntries(DEP_FIELDS2.flatMap((field) => Object.entries(parsed[field] ?? {})));
5039
+ };
5040
+ var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5041
+ var rewriteWorkspaceSpecifier = (raw, name, version2) => raw.replace(
5042
+ new RegExp(`("${escapeRegExp(name)}"\\s*:\\s*)"workspace:[^"]*"`, "g"),
5043
+ (_match, key) => `${key}"^${version2}"`
5044
+ );
5045
+ async function normalizeWorkspaceDeps(ourPkgRaw, baseDir, theirsDir, baseVersions, targetVersions) {
5046
+ const basePath = join15(baseDir, "package.json");
5047
+ const theirsPath = join15(theirsDir, "package.json");
5048
+ const [originalBase, originalTheirs] = await Promise.all([
5049
+ readFile2(basePath, "utf-8").catch(() => null),
5050
+ readFile2(theirsPath, "utf-8").catch(() => null)
5051
+ ]);
5052
+ if (originalBase === null || originalTheirs === null) return { workspace: [], bumped: [] };
5053
+ const ourSpecifiers = readDepSpecifiers(ourPkgRaw);
5054
+ const names = new Set(
5055
+ [
5056
+ ...Object.entries(readDepSpecifiers(originalBase)),
5057
+ ...Object.entries(readDepSpecifiers(originalTheirs))
5058
+ ].filter(([, range]) => range?.startsWith("workspace:")).map(([name]) => name)
5059
+ );
5060
+ const decisions = [...names].map((name) => ({
5061
+ name,
5062
+ ours: ourSpecifiers[name],
5063
+ baseVersion: baseVersions[name],
5064
+ targetVersion: targetVersions[name]
5065
+ }));
5066
+ const workspace = decisions.flatMap(
5067
+ ({ name, ours, targetVersion }) => ours?.startsWith("workspace:") && targetVersion !== void 0 ? [{ name, ours, version: targetVersion }] : []
5068
+ );
5069
+ const normalizable = decisions.filter(({ ours }) => !ours?.startsWith("workspace:"));
5070
+ const baseRaw = normalizable.reduce(
5071
+ (acc, { name, baseVersion }) => baseVersion === void 0 ? acc : rewriteWorkspaceSpecifier(acc, name, baseVersion),
5072
+ originalBase
5073
+ );
5074
+ const theirsRaw = normalizable.reduce(
5075
+ (acc, { name, targetVersion }) => targetVersion === void 0 ? acc : rewriteWorkspaceSpecifier(acc, name, targetVersion),
5076
+ originalTheirs
5077
+ );
5078
+ const bumped = normalizable.filter(
5079
+ ({ baseVersion, targetVersion }) => baseVersion !== void 0 && targetVersion !== void 0 && baseVersion !== targetVersion
5080
+ ).map(({ name }) => name);
5081
+ await Promise.all([
5082
+ baseRaw === originalBase ? null : writeFile2(basePath, baseRaw),
5083
+ theirsRaw === originalTheirs ? null : writeFile2(theirsPath, theirsRaw)
5084
+ ]);
5085
+ return { workspace, bumped };
5086
+ }
5087
+ var migrateWorkspaceDeps = (raw, findings) => findings.reduce((acc, { name, version: version2 }) => rewriteWorkspaceSpecifier(acc, name, version2), raw);
5088
+ async function findStaleCli(pkgRaw) {
5089
+ const range = readDepSpecifiers(pkgRaw)[CATALYST_CLI_PACKAGE];
5090
+ if (range === void 0 || !validRange(range)) return null;
5091
+ const body = await fetch(`https://registry.npmjs.org/${CATALYST_CLI_PACKAGE}/latest`, {
5092
+ // A hanging registry would otherwise stall the CLI indefinitely.
5093
+ signal: AbortSignal.timeout(5e3)
5094
+ }).then((res) => res.ok ? res.json() : null).catch(() => null);
5095
+ const latest = z17.object({ version: z17.string() }).safeParse(body).data?.version;
5096
+ if (latest === void 0 || !validSemver2(latest) || satisfies(latest, range)) return null;
5097
+ const current = minVersion(range)?.version;
5098
+ return current !== void 0 && semverLt(current, latest) ? { current, latest } : null;
5099
+ }
4795
5100
  async function mergeFile(oursPath, basePath, theirsPath) {
4796
5101
  const merged = await execa4(
4797
5102
  "git",
@@ -4815,9 +5120,9 @@ async function mergeFile(oursPath, basePath, theirsPath) {
4815
5120
  return (merged.exitCode ?? 0) > 0;
4816
5121
  }
4817
5122
  async function mergeModified(rel, baseDir, theirsDir, catalystRoot) {
4818
- const basePath = join14(baseDir, rel);
4819
- const theirsPath = join14(theirsDir, rel);
4820
- const oursPath = join14(catalystRoot, rel);
5123
+ const basePath = join15(baseDir, rel);
5124
+ const theirsPath = join15(theirsDir, rel);
5125
+ const oursPath = join15(catalystRoot, rel);
4821
5126
  if (await filesEqual(basePath, theirsPath)) return null;
4822
5127
  if (!await pathExists(oursPath)) {
4823
5128
  await copyInto(theirsPath, oursPath);
@@ -4833,8 +5138,8 @@ async function mergeModified(rel, baseDir, theirsDir, catalystRoot) {
4833
5138
  return await mergeFile(oursPath, basePath, theirsPath) ? "conflicted" : "applied";
4834
5139
  }
4835
5140
  async function mergeAdded(rel, theirsDir, catalystRoot, emptyFile) {
4836
- const theirsPath = join14(theirsDir, rel);
4837
- const oursPath = join14(catalystRoot, rel);
5141
+ const theirsPath = join15(theirsDir, rel);
5142
+ const oursPath = join15(catalystRoot, rel);
4838
5143
  if (!await pathExists(oursPath)) {
4839
5144
  await copyInto(theirsPath, oursPath);
4840
5145
  return "added";
@@ -4845,8 +5150,8 @@ async function mergeAdded(rel, theirsDir, catalystRoot, emptyFile) {
4845
5150
  return hadConflict ? "conflicted" : "applied";
4846
5151
  }
4847
5152
  async function mergeDeleted(rel, baseDir, catalystRoot) {
4848
- const basePath = join14(baseDir, rel);
4849
- const oursPath = join14(catalystRoot, rel);
5153
+ const basePath = join15(baseDir, rel);
5154
+ const oursPath = join15(catalystRoot, rel);
4850
5155
  if (!await pathExists(oursPath)) return null;
4851
5156
  if (await filesEqual(oursPath, basePath)) {
4852
5157
  await rm2(oursPath, { force: true });
@@ -4892,15 +5197,15 @@ async function resolveStrategy(strategy) {
4892
5197
  return "per-file";
4893
5198
  }
4894
5199
  async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
4895
- const scratch = await mkdtemp(join14(tmpdir(), "catalyst-merge-"));
5200
+ const scratch = await mkdtemp(join15(tmpdir(), "catalyst-merge-"));
4896
5201
  try {
4897
5202
  await execa4("git", ["init", "-q", scratch]);
4898
5203
  await execa4("git", ["config", "core.autocrlf", "false"], {
4899
- env: { ...process.env, GIT_DIR: join14(scratch, ".git") }
5204
+ env: { ...process.env, GIT_DIR: join15(scratch, ".git") }
4900
5205
  });
4901
5206
  const env2 = {
4902
5207
  ...process.env,
4903
- GIT_DIR: join14(scratch, ".git"),
5208
+ GIT_DIR: join15(scratch, ".git"),
4904
5209
  GIT_AUTHOR_NAME: "catalyst",
4905
5210
  GIT_AUTHOR_EMAIL: "catalyst@bigcommerce.com",
4906
5211
  GIT_COMMITTER_NAME: "catalyst",
@@ -4910,7 +5215,7 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
4910
5215
  const sideEnv = {
4911
5216
  ...env2,
4912
5217
  GIT_WORK_TREE: workTree,
4913
- GIT_INDEX_FILE: join14(scratch, `index.${tag}`)
5218
+ GIT_INDEX_FILE: join15(scratch, `index.${tag}`)
4914
5219
  };
4915
5220
  await execa4("git", ["add", "-A"], { env: sideEnv });
4916
5221
  return (await execa4("git", ["write-tree"], { env: sideEnv })).stdout.trim();
@@ -4954,7 +5259,7 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
4954
5259
  const outEnv = {
4955
5260
  ...env2,
4956
5261
  GIT_WORK_TREE: catalystRoot,
4957
- GIT_INDEX_FILE: join14(scratch, "index.out")
5262
+ GIT_INDEX_FILE: join15(scratch, "index.out")
4958
5263
  };
4959
5264
  await execa4("git", ["read-tree", mergedTree], { env: outEnv });
4960
5265
  await execa4("git", ["checkout-index", "-a", "-f"], { env: outEnv });
@@ -4973,7 +5278,7 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
4973
5278
  }
4974
5279
  }
4975
5280
  const deleted = changes.filter((change) => change.status === "D").map((change) => change.path);
4976
- await Promise.all(deleted.map((path) => rm2(join14(catalystRoot, path), { force: true })));
5281
+ await Promise.all(deleted.map((path) => rm2(join15(catalystRoot, path), { force: true })));
4977
5282
  return {
4978
5283
  applied: changes.filter((change) => change.status !== "A" && change.status !== "D").map((change) => change.path),
4979
5284
  added: changes.filter((change) => change.status === "A").map((change) => change.path),
@@ -5016,9 +5321,9 @@ async function applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stam
5016
5321
  stillConflicted.map(async (rel) => {
5017
5322
  const repoPath = toRepoPath(rel);
5018
5323
  const [base, ours, theirs] = await Promise.all([
5019
- hashBlob(join14(baseDir, rel)),
5324
+ hashBlob(join15(baseDir, rel)),
5020
5325
  headEntry(repoPath),
5021
- hashBlob(join14(theirsDir, rel))
5326
+ hashBlob(join15(theirsDir, rel))
5022
5327
  ]);
5023
5328
  return [
5024
5329
  `0 ${NULL_OID} ${repoPath}`,
@@ -5041,10 +5346,10 @@ async function resolveProject2(cwd) {
5041
5346
  } catch {
5042
5347
  return null;
5043
5348
  }
5044
- const candidateDirs = [.../* @__PURE__ */ new Set([join14(gitRoot, "core"), gitRoot, join14(cwd, "core"), cwd])];
5349
+ const candidateDirs = [.../* @__PURE__ */ new Set([join15(gitRoot, "core"), gitRoot, join15(cwd, "core"), cwd])];
5045
5350
  const parsed = (await Promise.all(
5046
5351
  candidateDirs.map(async (dir) => {
5047
- const pkgPath = join14(dir, "package.json");
5352
+ const pkgPath = join15(dir, "package.json");
5048
5353
  const raw = await readFile2(pkgPath, "utf-8").catch(() => null);
5049
5354
  if (!raw) return null;
5050
5355
  let pkgJson;
@@ -5068,13 +5373,44 @@ async function resolveProject2(cwd) {
5068
5373
  pkg: chosen.pkg
5069
5374
  };
5070
5375
  }
5376
+ var readWorkspaceVersions = async (repoDir) => {
5377
+ const packagesDir = join15(repoDir, "packages");
5378
+ const entries = await readdir2(packagesDir, { withFileTypes: true }).catch(() => []);
5379
+ const manifests = await Promise.all(
5380
+ entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
5381
+ const raw = await readFile2(join15(packagesDir, entry.name, "package.json"), "utf-8").catch(
5382
+ () => null
5383
+ );
5384
+ if (raw === null) return null;
5385
+ try {
5386
+ return WorkspacePackageJson.parse(JSON.parse(raw));
5387
+ } catch {
5388
+ return null;
5389
+ }
5390
+ })
5391
+ );
5392
+ return Object.fromEntries(
5393
+ manifests.filter((pkg) => pkg !== null).map(({ name, version: version2 }) => [name, version2])
5394
+ );
5395
+ };
5396
+ var readCachedVersions = async (path) => {
5397
+ const raw = await readFile2(path, "utf-8").catch(() => null);
5398
+ if (raw === null) return null;
5399
+ try {
5400
+ return z17.record(z17.string(), z17.string()).parse(JSON.parse(raw));
5401
+ } catch {
5402
+ return null;
5403
+ }
5404
+ };
5071
5405
  async function downloadCore(repository, ref, destDir, token) {
5072
5406
  const cacheable = !MOVING_TAGS.has(parseRef(ref).version);
5073
5407
  const cacheKey = `${repository.replace(/[^a-zA-Z0-9._-]/g, "_")}__${ref.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
5074
- const cachePath = join14(CACHE_DIR, cacheKey);
5075
- if (cacheable && await pathExists(cachePath) && await pathExists(join14(cachePath, "package.json"))) {
5408
+ const cachePath = join15(CACHE_DIR, cacheKey);
5409
+ const versionsPath = `${cachePath}.versions.json`;
5410
+ const cachedVersions = cacheable && await pathExists(join15(cachePath, "package.json")) ? await readCachedVersions(versionsPath) : null;
5411
+ if (cachedVersions) {
5076
5412
  await cp2(cachePath, destDir, { recursive: true });
5077
- return;
5413
+ return cachedVersions;
5078
5414
  }
5079
5415
  const url = `https://api.github.com/repos/${repository}/tarball/${encodeURIComponent(ref)}`;
5080
5416
  const headers = {
@@ -5094,22 +5430,24 @@ async function downloadCore(repository, ref, destDir, token) {
5094
5430
  await writeFile2(tarballPath, Buffer.from(await res.arrayBuffer()));
5095
5431
  await mkdir(rawDir, { recursive: true });
5096
5432
  await execa4("tar", ["-xzf", tarballPath, "-C", rawDir, "--strip-components=1"]);
5097
- const coreDir = join14(rawDir, "core");
5433
+ const coreDir = join15(rawDir, "core");
5098
5434
  const sourceDir = await pathExists(coreDir) ? coreDir : rawDir;
5435
+ const workspaceVersions = await readWorkspaceVersions(rawDir);
5099
5436
  await rename(sourceDir, destDir);
5100
5437
  await rm2(tarballPath, { force: true });
5101
5438
  await rm2(rawDir, { recursive: true, force: true });
5102
5439
  if (cacheable) {
5103
5440
  await mkdir(CACHE_DIR, { recursive: true });
5104
- await cp2(destDir, cachePath, { recursive: true }).catch(() => {
5441
+ await cp2(destDir, cachePath, { recursive: true }).then(() => writeFile2(versionsPath, JSON.stringify(workspaceVersions))).catch(() => {
5105
5442
  });
5106
5443
  }
5444
+ return workspaceVersions;
5107
5445
  }
5108
- async function resolveBaseRef(project2, fromOption, assumeYes) {
5109
- if (project2.pkg.catalyst?.ref) return project2.pkg.catalyst.ref;
5446
+ async function resolveBaseRef(project, fromOption, assumeYes) {
5447
+ if (project.pkg.catalyst?.ref) return project.pkg.catalyst.ref;
5110
5448
  if (fromOption) return fromOption;
5111
- const family = project2.pkg.name && KNOWN_FAMILIES.has(project2.pkg.name) ? project2.pkg.name : "@bigcommerce/catalyst-core";
5112
- const guess = `${family}@${project2.pkg.version}`;
5449
+ const family = project.pkg.name && KNOWN_FAMILIES.has(project.pkg.name) ? project.pkg.name : "@bigcommerce/catalyst-core";
5450
+ const guess = `${family}@${project.pkg.version}`;
5113
5451
  consola.warn(`No \`catalyst.ref\` found in package.json. Inferred base: ${guess}`);
5114
5452
  if (assumeYes) return guess;
5115
5453
  const ok = await confirm6({ message: `Upgrade from ${guess}?`, default: true }).catch(() => false);
@@ -5119,7 +5457,25 @@ async function resolveBaseRef(project2, fromOption, assumeYes) {
5119
5457
  }
5120
5458
  return guess;
5121
5459
  }
5122
- function printSummary(result, relDir, stampedPkg) {
5460
+ function printWorkspaceFindings(findings) {
5461
+ if (findings.length === 0) return;
5462
+ const pad = Math.max(...findings.map((finding) => finding.name.length));
5463
+ const rows = findings.map((finding) => ` ${finding.name.padEnd(pad)} ${finding.ours} \u2192 ^${finding.version}`).join("\n");
5464
+ consola.warn(
5465
+ `Catalyst dependencies still using the workspace protocol (${findings.length}):
5466
+ ${rows}
5467
+
5468
+ They resolve to your local packages/ copies, which \`catalyst upgrade\` does not update \u2014 so they never receive upgrades.`
5469
+ );
5470
+ }
5471
+ function printSummary({
5472
+ result,
5473
+ relDir,
5474
+ stampedPkg,
5475
+ migratedDeps,
5476
+ packageManager,
5477
+ staleCli
5478
+ }) {
5123
5479
  const unresolved = result.conflicted.filter((f) => !(stampedPkg && f === "package.json"));
5124
5480
  const parts = [
5125
5481
  `${result.applied.length} updated`,
@@ -5129,18 +5485,31 @@ function printSummary(result, relDir, stampedPkg) {
5129
5485
  if (unresolved.length) parts.push(`${unresolved.length} with conflicts`);
5130
5486
  consola.log(`
5131
5487
  ${parts.join(", ")}`);
5488
+ const pkgConflicted = unresolved.includes("package.json");
5489
+ const depsChanged = migratedDeps > 0 || [...result.applied, ...result.added, ...result.conflicted].includes("package.json");
5490
+ if (depsChanged && !pkgConflicted) {
5491
+ consola.info(
5492
+ `Dependencies changed \u2014 run \`${packageManager} install\` to update your lockfile.`
5493
+ );
5494
+ }
5132
5495
  if (unresolved.length) {
5133
5496
  const files = unresolved.map((f) => ` ${f}`).join("\n");
5497
+ const install = depsChanged && pkgConflicted ? ` && ${packageManager} install` : "";
5134
5498
  consola.warn(
5135
5499
  `
5136
5500
  Staged the clean changes. ${unresolved.length} file(s) need conflict resolution (the <<<ours/===/theirs>>> markers):
5137
5501
  ${files}
5138
5502
 
5139
- Resolve them, then: git add ${relDir} && git commit`
5503
+ Resolve them, then: git add ${relDir} && git commit${install}`
5140
5504
  );
5141
5505
  } else {
5142
5506
  consola.success("Staged all changes \u2014 review with `git diff --cached`, then commit.");
5143
5507
  }
5508
+ if (staleCli) {
5509
+ consola.info(
5510
+ `Your Catalyst CLI is behind (${staleCli.current} \u2192 ${staleCli.latest}). Update it with \`${packageManager} add -D ${CATALYST_CLI_PACKAGE}@${staleCli.latest}\`.`
5511
+ );
5512
+ }
5144
5513
  }
5145
5514
  var upgrade = new Command13("upgrade").configureHelp({ showGlobalOptions: true }).aliases([
5146
5515
  "up",
@@ -5181,15 +5550,15 @@ Conflicts are written as standard <<<ours/===/theirs>>> markers \u2014 the upgra
5181
5550
  never aborts. Versions are git tags on the repository (not npm). Set GITHUB_TOKEN
5182
5551
  to raise the GitHub API rate limit.`
5183
5552
  ).action(async (version2, options) => {
5184
- const project2 = await resolveProject2(process.cwd());
5185
- if (!project2) {
5553
+ const project = await resolveProject2(process.cwd());
5554
+ if (!project) {
5186
5555
  consola.error(
5187
5556
  "Run `catalyst upgrade` from inside a Catalyst git repository (flat core/ repo or a project containing core/)."
5188
5557
  );
5189
5558
  process.exit(1);
5190
5559
  }
5191
- const { gitRoot, catalystRoot, relDir, pkgPath } = project2;
5192
- const baseRef = await resolveBaseRef(project2, options.from, options.yes ?? false);
5560
+ const { gitRoot, catalystRoot, relDir, pkgPath } = project;
5561
+ const baseRef = await resolveBaseRef(project, options.from, options.yes ?? false);
5193
5562
  if (!baseRef) process.exit(1);
5194
5563
  let basePackage;
5195
5564
  let upstreamRef;
@@ -5224,7 +5593,7 @@ to raise the GitHub API rate limit.`
5224
5593
  process.exit(1);
5225
5594
  }
5226
5595
  }
5227
- if (!project2.pkg.catalyst?.ref && !options.from && !options.dryRun) {
5596
+ if (!project.pkg.catalyst?.ref && !options.from && !options.dryRun) {
5228
5597
  const { version: baseVersion } = parseRef(baseRef);
5229
5598
  const rawPkg = await readFile2(pkgPath, "utf-8");
5230
5599
  const parsedPkg = z17.record(z17.string(), z17.unknown()).parse(JSON.parse(rawPkg));
@@ -5235,15 +5604,18 @@ to raise the GitHub API rate limit.`
5235
5604
  consola.success(`Added catalyst.ref \u2192 ${baseRef}`);
5236
5605
  }
5237
5606
  const token = process.env.GITHUB_TOKEN;
5238
- const tmpDir = await mkdtemp(join14(tmpdir(), "catalyst-upgrade-"));
5607
+ const tmpDir = await mkdtemp(join15(tmpdir(), "catalyst-upgrade-"));
5239
5608
  try {
5240
- const baseDir = join14(tmpDir, "base");
5241
- const theirsDir = join14(tmpDir, "theirs");
5242
- const emptyFile = join14(tmpDir, ".empty");
5609
+ const baseDir = join15(tmpDir, "base");
5610
+ const theirsDir = join15(tmpDir, "theirs");
5611
+ const emptyFile = join15(tmpDir, ".empty");
5243
5612
  await writeFile2(emptyFile, "");
5613
+ const staleCliCheck = findStaleCli(project.rawContent).catch(() => null);
5244
5614
  const downloadSpinner = yoctoSpinner4().start(`Downloading ${baseRef} and ${upstreamRef}...`);
5615
+ let baseVersions;
5616
+ let targetVersions;
5245
5617
  try {
5246
- await Promise.all([
5618
+ [baseVersions, targetVersions] = await Promise.all([
5247
5619
  downloadCore(options.repository, baseRef, baseDir, token),
5248
5620
  downloadCore(options.repository, upstreamRef, theirsDir, token)
5249
5621
  ]);
@@ -5252,7 +5624,7 @@ to raise the GitHub API rate limit.`
5252
5624
  downloadSpinner.error("Download failed");
5253
5625
  throw err;
5254
5626
  }
5255
- if (!project2.pkg.catalyst?.ref && !options.from) {
5627
+ if (!project.pkg.catalyst?.ref && !options.from) {
5256
5628
  const similarity = await computeBaseSimilarity(baseDir, catalystRoot);
5257
5629
  const pct = Math.round(similarity * 100);
5258
5630
  if (similarity < 0.5) {
@@ -5263,6 +5635,13 @@ to raise the GitHub API rate limit.`
5263
5635
  }
5264
5636
  const resolvedVersion = MOVING_TAGS.has(upstreamTagVersion) ? await readResolvedVersion(theirsDir) : upstreamTagVersion;
5265
5637
  const newRef = MOVING_TAGS.has(upstreamTagVersion) ? `${basePackage}@${resolvedVersion}` : upstreamRef;
5638
+ const { workspace: workspaceDeps, bumped } = await normalizeWorkspaceDeps(
5639
+ project.rawContent,
5640
+ baseDir,
5641
+ theirsDir,
5642
+ baseVersions,
5643
+ targetVersions
5644
+ );
5266
5645
  if (options.dryRun) {
5267
5646
  const diffSpinner = yoctoSpinner4().start("Generating diff...");
5268
5647
  const diff = await execa4(
@@ -5282,6 +5661,7 @@ to raise the GitHub API rate limit.`
5282
5661
  diffSpinner.success(`Diff ready \u2014 ${fileCount} file(s) affected.`);
5283
5662
  consola.log("\nDiff preview (--dry-run, not applied):\n");
5284
5663
  consola.log(diff.stdout);
5664
+ printWorkspaceFindings(workspaceDeps);
5285
5665
  return;
5286
5666
  }
5287
5667
  const strategy = await resolveStrategy(options.strategy);
@@ -5298,13 +5678,13 @@ to raise the GitHub API rate limit.`
5298
5678
  mergeSpinner.success("Merged cleanly.");
5299
5679
  }
5300
5680
  const patchedRaw = await readFile2(pkgPath, "utf-8");
5301
- let stampedPkg = false;
5681
+ let stampedText = null;
5302
5682
  try {
5303
5683
  const patchedPkg = z17.record(z17.string(), z17.unknown()).parse(JSON.parse(patchedRaw));
5304
5684
  patchedPkg.catalyst = { version: resolvedVersion, ref: newRef };
5305
- await writeFile2(pkgPath, `${JSON.stringify(patchedPkg, null, 2)}
5306
- `);
5307
- stampedPkg = true;
5685
+ stampedText = `${JSON.stringify(patchedPkg, null, 2)}
5686
+ `;
5687
+ await writeFile2(pkgPath, stampedText);
5308
5688
  consola.success(`catalyst.ref updated \u2192 ${newRef}`);
5309
5689
  } catch {
5310
5690
  const catalystReplacement = JSON.stringify(
@@ -5329,14 +5709,35 @@ to raise the GitHub API rate limit.`
5329
5709
  );
5330
5710
  }
5331
5711
  }
5712
+ printWorkspaceFindings(workspaceDeps);
5713
+ let migratedDeps = 0;
5714
+ if (workspaceDeps.length) {
5715
+ const accepted = stampedText !== null && (options.yes === true || Boolean(process.stdin.isTTY) && await confirm6({
5716
+ message: "Replace them with the published versions?",
5717
+ default: true
5718
+ }).catch(() => false));
5719
+ if (accepted && stampedText !== null) {
5720
+ await writeFile2(pkgPath, migrateWorkspaceDeps(stampedText, workspaceDeps));
5721
+ migratedDeps = workspaceDeps.length;
5722
+ consola.success(
5723
+ `Migrated ${migratedDeps} dependenc${migratedDeps === 1 ? "y" : "ies"} to published versions.`
5724
+ );
5725
+ } else {
5726
+ consola.info(
5727
+ "Keeping the workspace references. Switching abandons any local packages/ copy you've customized; otherwise apply the versions above in package.json and reinstall."
5728
+ );
5729
+ }
5730
+ }
5332
5731
  try {
5333
- await applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stampedPkg);
5732
+ await applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stampedText !== null);
5334
5733
  } catch (err) {
5335
5734
  consola.warn(
5336
5735
  `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.`
5337
5736
  );
5338
5737
  }
5339
5738
  const gitVersion = await execa4("git", ["--version"]).then((r) => r.stdout.trim()).catch(() => "unknown");
5739
+ const packageManager = await detectLockfileManager(gitRoot) ?? "pnpm";
5740
+ const staleCli = await staleCliCheck;
5340
5741
  await getTelemetry().track("upgrade", {
5341
5742
  strategy,
5342
5743
  gitVersion,
@@ -5345,9 +5746,20 @@ to raise the GitHub API rate limit.`
5345
5746
  added: result.added.length,
5346
5747
  deleted: result.deleted.length,
5347
5748
  conflicts: result.conflicted.length,
5348
- hasConflicts: result.conflicted.length > 0
5749
+ hasConflicts: result.conflicted.length > 0,
5750
+ workspaceDepsFound: workspaceDeps.length,
5751
+ workspaceDepsMigrated: migratedDeps,
5752
+ catalystDepsBumped: bumped.length,
5753
+ cliOutdated: staleCli !== null
5754
+ });
5755
+ printSummary({
5756
+ result,
5757
+ relDir,
5758
+ stampedPkg: stampedText !== null,
5759
+ migratedDeps,
5760
+ packageManager,
5761
+ staleCli
5349
5762
  });
5350
- printSummary(result, relDir, stampedPkg);
5351
5763
  } finally {
5352
5764
  await rm2(tmpDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 });
5353
5765
  }
@@ -5402,17 +5814,17 @@ function getCommandPath(cmd) {
5402
5814
  return parts.join(" ");
5403
5815
  }
5404
5816
  var telemetryPreHook = async (thisCommand, actionCommand) => {
5405
- const telemetry3 = getTelemetry();
5817
+ const telemetry2 = getTelemetry();
5406
5818
  const commandName = getCommandPath(actionCommand);
5407
- telemetry3.commandName = commandName;
5408
- return await telemetry3.track(commandName, {
5819
+ telemetry2.commandName = commandName;
5820
+ return await telemetry2.track(commandName, {
5409
5821
  ...parseArguments(thisCommand.args)
5410
5822
  });
5411
5823
  };
5412
5824
  var telemetryPostHook = async () => {
5413
- const telemetry3 = getTelemetry();
5414
- await telemetry3.track("command_completed", { durationMs: telemetry3.durationMs() });
5415
- await telemetry3.analytics.closeAndFlush();
5825
+ const telemetry2 = getTelemetry();
5826
+ await telemetry2.track("command_completed", { durationMs: telemetry2.durationMs() });
5827
+ await telemetry2.analytics.closeAndFlush();
5416
5828
  };
5417
5829
 
5418
5830
  // src/cli/program.ts
@@ -5424,21 +5836,21 @@ consola.log(colorize13("cyanBright", `\u25E2 ${package_default.name} v${package_
5424
5836
  `));
5425
5837
  program.name(package_default.name).version(package_default.version).summary("CLI tool for Catalyst development").description(
5426
5838
  "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);
5839
+ ).configureHelp({ showGlobalOptions: true }).addCommand(version).addCommand(create2).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(projects).addCommand(env).addCommand(channels).addCommand(auth).addCommand(upgrade).addCommand(telemetry).addCommand(debug).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
5428
5840
 
5429
5841
  // src/cli/index.ts
5430
5842
  var handleFatalError = async (error) => {
5431
- const telemetry3 = getTelemetry();
5843
+ const telemetry2 = getTelemetry();
5432
5844
  const errorMessage = error instanceof Error ? error.message : String(error);
5433
5845
  const errorStack = error instanceof Error ? error.stack : void 0;
5434
5846
  try {
5435
- await telemetry3.track("error", {
5436
- commandName: telemetry3.commandName,
5847
+ await telemetry2.track("error", {
5848
+ commandName: telemetry2.commandName,
5437
5849
  errorMessage,
5438
5850
  errorStack,
5439
- durationMs: telemetry3.durationMs()
5851
+ durationMs: telemetry2.durationMs()
5440
5852
  });
5441
- await telemetry3.analytics.closeAndFlush();
5853
+ await telemetry2.analytics.closeAndFlush();
5442
5854
  } catch {
5443
5855
  }
5444
5856
  if (error instanceof UserActionableError) {
@@ -5446,9 +5858,9 @@ var handleFatalError = async (error) => {
5446
5858
  process.exit(1);
5447
5859
  }
5448
5860
  consola.error(errorMessage);
5449
- if (telemetry3.isEnabled()) {
5861
+ if (telemetry2.isEnabled()) {
5450
5862
  consola.info(
5451
- `Correlation ID: ${telemetry3.correlationId}
5863
+ `Correlation ID: ${telemetry2.correlationId}
5452
5864
  Share this Correlation ID with BigCommerce support.`
5453
5865
  );
5454
5866
  } else {