@bigcommerce/catalyst 1.0.0-alpha.6 → 1.0.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
@@ -28,8 +28,13 @@ import { randomBytes, randomUUID } from "crypto";
28
28
  // package.json
29
29
  var package_default = {
30
30
  name: "@bigcommerce/catalyst",
31
- version: "1.0.0-alpha.6",
31
+ version: "1.0.0",
32
32
  type: "module",
33
+ repository: {
34
+ type: "git",
35
+ url: "https://github.com/bigcommerce/catalyst",
36
+ directory: "packages/catalyst"
37
+ },
33
38
  bin: {
34
39
  catalyst: "dist/cli.js"
35
40
  },
@@ -65,6 +70,7 @@ var package_default = {
65
70
  nypm: "^0.5.4",
66
71
  open: "^10.1.0",
67
72
  "std-env": "^3.9.0",
73
+ tar: "^7.5.7",
68
74
  "yocto-spinner": "^1.0.0",
69
75
  zod: "^4.0.5"
70
76
  },
@@ -128,7 +134,7 @@ var Telemetry = class {
128
134
  this.correlationId = randomUUID();
129
135
  this.startTime = Date.now();
130
136
  this.analytics = new Analytics({
131
- writeKey: "not-a-valid-segment-write-key"
137
+ writeKey: "OCkxLj3KTc1N3ebEbnFBqbhxfE3tVb3Z"
132
138
  });
133
139
  }
134
140
  durationMs() {
@@ -195,11 +201,11 @@ function getTelemetry() {
195
201
  }
196
202
 
197
203
  // src/cli/program.ts
198
- import { Option as Option9 } from "@commander-js/extra-typings";
199
- import { Command as Command12 } from "commander";
200
- import { colorize as colorize9 } from "consola/utils";
204
+ import { Option as Option11 } from "@commander-js/extra-typings";
205
+ import { Command as Command14 } from "commander";
206
+ import { colorize as colorize11 } from "consola/utils";
201
207
  import { config } from "dotenv";
202
- import { existsSync as existsSync5 } from "fs";
208
+ import { existsSync as existsSync4 } from "fs";
203
209
  import { resolve } from "path";
204
210
 
205
211
  // src/cli/commands/auth.ts
@@ -423,7 +429,7 @@ async function fetchProjects(storeHash, accessToken, apiHost) {
423
429
  assertAuthorized(response);
424
430
  if (response.status === 403) {
425
431
  throw new Error(
426
- "Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
432
+ "Infrastructure Projects API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
427
433
  );
428
434
  }
429
435
  if (!response.ok) {
@@ -474,7 +480,7 @@ async function createProject(name, storeHash, accessToken, apiHost) {
474
480
  }
475
481
  if (response.status === 403) {
476
482
  throw new Error(
477
- "Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
483
+ "Infrastructure Projects API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
478
484
  );
479
485
  }
480
486
  if (response.status === 502) {
@@ -495,7 +501,7 @@ async function deleteProject(projectUuid, storeHash, accessToken, apiHost) {
495
501
  assertAuthorized(response);
496
502
  if (response.status === 403) {
497
503
  throw new Error(
498
- "Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
504
+ "Infrastructure Projects API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
499
505
  );
500
506
  }
501
507
  if (response.status === 404) {
@@ -1106,15 +1112,7 @@ async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken,
1106
1112
  // src/cli/lib/commerce-hosting.ts
1107
1113
  import { confirm as confirm2, input as input2, select, Separator } from "@inquirer/prompts";
1108
1114
  import { colorize as colorize2 } from "consola/utils";
1109
- import {
1110
- existsSync as existsSync2,
1111
- lstatSync,
1112
- mkdirSync,
1113
- readFileSync as readFileSync2,
1114
- symlinkSync,
1115
- unlinkSync,
1116
- writeFileSync
1117
- } from "fs";
1115
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
1118
1116
  import { dirname as dirname2, join as join5 } from "path";
1119
1117
  import { z as z7 } from "zod";
1120
1118
 
@@ -1154,57 +1152,43 @@ var writeJson = (path, value) => {
1154
1152
  writeFileSync(path, `${JSON.stringify(value, null, 2)}
1155
1153
  `);
1156
1154
  };
1157
- var symlinkRootEnvToCore = (projectDir) => {
1158
- const coreEnvPath = join5(projectDir, "core", ".env.local");
1159
- if (lstatSync(coreEnvPath, { throwIfNoEntry: false })) return;
1160
- try {
1161
- symlinkSync("../.env.local", coreEnvPath);
1162
- } catch (error) {
1163
- const message = error instanceof Error ? error.message : "unknown error";
1164
- consola.warn(
1165
- `Could not create symlink at core/.env.local: ${message}
1166
- On Windows, enable Developer Mode or run as administrator to allow symlinks.
1167
- You will need to keep .env.local and core/.env.local in sync manually.`
1168
- );
1169
- }
1170
- };
1171
1155
  var convertProxyToMiddleware = (projectDir) => {
1172
- const proxyPath = join5(projectDir, "core", "proxy.ts");
1173
- const middlewarePath = join5(projectDir, "core", "middleware.ts");
1156
+ const proxyPath = join5(projectDir, "proxy.ts");
1157
+ const middlewarePath = join5(projectDir, "middleware.ts");
1174
1158
  if (!existsSync2(proxyPath)) return;
1175
1159
  const contents = readFileSync2(proxyPath, "utf-8").replace("export const proxy", "export const middleware").replace("export const config = {", "export const config = {\n runtime: 'experimental-edge',");
1176
1160
  writeFileSync(middlewarePath, contents);
1177
1161
  unlinkSync(proxyPath);
1178
1162
  };
1179
1163
  var cleanupCloudflareIncompatibilities = async (projectDir) => {
1180
- const instrumentationPath = join5(projectDir, "core", "instrumentation.ts");
1164
+ const instrumentationPath = join5(projectDir, "instrumentation.ts");
1181
1165
  if (!existsSync2(instrumentationPath)) return;
1182
1166
  if (!process.stdin.isTTY) {
1183
1167
  consola.warn(
1184
- "core/instrumentation.ts is present and may be incompatible with the Cloudflare Workers bundle (the default @vercel/otel scaffolding throws at cold start under workerd). Skipping automatic cleanup in non-interactive mode \u2014 re-run interactively to remove it, or delete/gate it manually."
1168
+ "instrumentation.ts is present and may be incompatible with the Cloudflare Workers bundle (the default @vercel/otel scaffolding throws at cold start under workerd). Skipping automatic cleanup in non-interactive mode \u2014 re-run interactively to remove it, or delete/gate it manually."
1185
1169
  );
1186
1170
  return;
1187
1171
  }
1188
1172
  const shouldRemove = await confirm2({
1189
- message: 'Catalyst found core/instrumentation.ts, which is incompatible with the Cloudflare Workers bundle when it uses @vercel/otel (causes "Failed to prepare server" at cold start). Remove it and drop @vercel/otel from core/package.json?',
1173
+ message: 'Catalyst found instrumentation.ts, which is incompatible with the Cloudflare Workers bundle when it uses @vercel/otel (causes "Failed to prepare server" at cold start). Remove it and drop @vercel/otel from package.json?',
1190
1174
  default: true
1191
1175
  });
1192
1176
  if (!shouldRemove) {
1193
1177
  consola.info(
1194
- 'Leaving core/instrumentation.ts in place. The Cloudflare worker will continue to log "Failed to prepare server" at cold start until this is resolved manually.'
1178
+ 'Leaving instrumentation.ts in place. The Cloudflare worker will continue to log "Failed to prepare server" at cold start until this is resolved manually.'
1195
1179
  );
1196
1180
  return;
1197
1181
  }
1198
1182
  unlinkSync(instrumentationPath);
1199
- consola.info("Removed core/instrumentation.ts (incompatible with Cloudflare Workers).");
1200
- const corePackageJsonPath = join5(projectDir, "core", "package.json");
1183
+ consola.info("Removed instrumentation.ts (incompatible with Cloudflare Workers).");
1184
+ const corePackageJsonPath = join5(projectDir, "package.json");
1201
1185
  if (existsSync2(corePackageJsonPath)) {
1202
1186
  const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
1203
1187
  if (pkg.dependencies && "@vercel/otel" in pkg.dependencies) {
1204
1188
  const { "@vercel/otel": _removedVercelOtel, ...preservedDeps } = pkg.dependencies;
1205
1189
  pkg.dependencies = preservedDeps;
1206
1190
  writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
1207
- consola.info("Dropped @vercel/otel from core/package.json.");
1191
+ consola.info("Dropped @vercel/otel from package.json.");
1208
1192
  }
1209
1193
  }
1210
1194
  };
@@ -1215,7 +1199,7 @@ var setupCommerceHosting = async ({
1215
1199
  accessToken
1216
1200
  }) => {
1217
1201
  await cleanupCloudflareIncompatibilities(projectDir);
1218
- const corePackageJsonPath = join5(projectDir, "core", "package.json");
1202
+ const corePackageJsonPath = join5(projectDir, "package.json");
1219
1203
  const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
1220
1204
  pkg.dependencies = {
1221
1205
  ...pkg.dependencies,
@@ -1228,8 +1212,7 @@ var setupCommerceHosting = async ({
1228
1212
  };
1229
1213
  if (storeHash) projectJson.storeHash = storeHash;
1230
1214
  if (accessToken) projectJson.accessToken = accessToken;
1231
- writeJson(join5(projectDir, "core", ".bigcommerce", "project.json"), projectJson);
1232
- symlinkRootEnvToCore(projectDir);
1215
+ writeJson(join5(projectDir, ".bigcommerce", "project.json"), projectJson);
1233
1216
  convertProxyToMiddleware(projectDir);
1234
1217
  };
1235
1218
  var NoLinkedProjectError = class extends Error {
@@ -1705,145 +1688,103 @@ var channel = new Command3("channel").configureHelp({ showGlobalOptions: true })
1705
1688
  // src/cli/commands/create.ts
1706
1689
  import { Command as Command4, InvalidArgumentError as InvalidArgumentError2, Option as Option5 } from "@commander-js/extra-typings";
1707
1690
  import { checkbox, input as input3, select as select4 } from "@inquirer/prompts";
1708
- import { execSync as execSync4 } from "child_process";
1691
+ import { execSync as execSync2 } from "child_process";
1709
1692
  import { colorize as colorize4 } from "consola/utils";
1710
1693
  import { pathExistsSync } from "fs-extra/esm";
1711
1694
  import kebabCase from "lodash.kebabcase";
1712
- import { join as join10 } from "path";
1695
+ import { join as join11 } from "path";
1713
1696
 
1714
- // src/cli/lib/build-workspace-packages.ts
1715
- import { execSync } from "child_process";
1716
- import { existsSync as existsSync3 } from "fs";
1717
- import { join as join7 } from "path";
1718
- import yoctoSpinner2 from "yocto-spinner";
1719
- var WORKSPACE_PACKAGES = ["packages/catalyst", "packages/client"];
1720
- var buildWorkspacePackages = (projectDir) => {
1721
- const hasCore = existsSync3(join7(projectDir, "core"));
1722
- const hasAllWorkspacePackages = WORKSPACE_PACKAGES.every(
1723
- (pkg) => existsSync3(join7(projectDir, pkg))
1724
- );
1725
- if (!hasCore || !hasAllWorkspacePackages) return;
1726
- WORKSPACE_PACKAGES.forEach((pkg) => {
1727
- const spinner = yoctoSpinner2().start(`Building ${pkg}...`);
1728
- try {
1729
- execSync("pnpm build", { cwd: join7(projectDir, pkg), stdio: "ignore" });
1730
- spinner.success(`Built ${pkg}.`);
1731
- } catch (error) {
1732
- const message = error instanceof Error ? error.message : "unknown error";
1733
- spinner.error(`Failed to build ${pkg}: ${message}`);
1734
- throw error;
1735
- }
1736
- });
1737
- };
1738
-
1739
- // src/cli/lib/clone-catalyst.ts
1740
- import { execSync as execSync3 } from "child_process";
1741
-
1742
- // src/cli/lib/checkout-ref.ts
1743
- import { sync as spawnSync } from "cross-spawn";
1744
-
1745
- // src/cli/lib/is-exec-exception.ts
1746
- function isExecException(error) {
1747
- return typeof error === "object" && error !== null && "stdout" in error && "stderr" in error;
1748
- }
1749
-
1750
- // src/cli/lib/checkout-ref.ts
1751
- function checkoutRef(repoDir, ref) {
1752
- try {
1753
- const spawn = spawnSync("git", ["checkout", ref, "--"], {
1754
- cwd: repoDir,
1755
- encoding: "utf8",
1756
- shell: false
1757
- });
1758
- const stderr = spawn.stderr.trim();
1759
- if (spawn.status !== 0 && stderr) {
1760
- throw new Error(stderr);
1761
- }
1762
- consola.success(`Checked out ref ${ref} successfully.`);
1763
- } catch (error) {
1764
- if (isExecException(error)) {
1765
- const stderr = error.stderr ? error.stderr.toString() : "";
1766
- if (stderr.includes(`fatal: reference is not a tree: ${ref}`) || stderr.includes(`fatal: ambiguous argument '${ref}'`) || stderr.includes(`unknown revision or path not in the working tree`)) {
1767
- consola.error(`Ref '${ref}' not found in the repository.`);
1768
- } else {
1769
- consola.error(`Error checking out ref '${ref}':`, stderr.trim());
1770
- }
1771
- } else if (error instanceof Error) {
1772
- consola.error(`Error checking out ref '${ref}':`, error.message);
1773
- } else {
1774
- consola.error(`Unknown error occurred while checking out ref '${ref}'.`);
1775
- }
1776
- consola.warn(`Falling back to the default branch.`);
1697
+ // src/cli/lib/detect-package-manager.ts
1698
+ var detectPackageManager = () => {
1699
+ const userAgent = process.env.npm_config_user_agent ?? "";
1700
+ const name = userAgent.split(" ")[0]?.split("/")[0];
1701
+ if (name === "pnpm" || name === "yarn" || name === "bun") {
1702
+ return name;
1777
1703
  }
1778
- }
1704
+ if (process.versions.bun) {
1705
+ return "bun";
1706
+ }
1707
+ return "npm";
1708
+ };
1779
1709
 
1780
- // src/cli/lib/has-github-ssh.ts
1781
- import { execSync as execSync2 } from "child_process";
1782
- function hasGitHubSSH() {
1710
+ // src/cli/lib/extract-catalyst.ts
1711
+ import { mkdirSync as mkdirSync2 } from "fs";
1712
+ import { Readable } from "stream";
1713
+ import { pipeline } from "stream/promises";
1714
+ import { x } from "tar";
1715
+ var SUBDIR = "core";
1716
+ var MAX_ATTEMPTS = 3;
1717
+ var codeloadUrl = (repository, ref) => `https://codeload.github.com/${repository}/tar.gz/${encodeURIComponent(ref)}`;
1718
+ var wait = (ms) => new Promise((resolve2) => {
1719
+ setTimeout(resolve2, ms);
1720
+ });
1721
+ var downloadAndExtract = async (repository, ref, projectDir) => {
1722
+ const url = codeloadUrl(repository, ref);
1723
+ const response = await fetch(url);
1724
+ if (!response.ok || !response.body) {
1725
+ throw new Error(`Failed to download ${url} (HTTP ${response.status})`);
1726
+ }
1727
+ await pipeline(
1728
+ // Node's global fetch types `body` as the DOM ReadableStream; it is the same
1729
+ // object Readable.fromWeb expects at runtime.
1730
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1731
+ Readable.fromWeb(response.body),
1732
+ x({
1733
+ cwd: projectDir,
1734
+ strip: 2,
1735
+ filter: (path) => path.split("/")[1] === SUBDIR
1736
+ })
1737
+ );
1738
+ };
1739
+ var attemptDownload = async (repository, ref, projectDir, attempt = 1) => {
1783
1740
  try {
1784
- const output = execSync2("ssh -T git@github.com", {
1785
- encoding: "utf8",
1786
- stdio: "pipe"
1787
- }).toString();
1788
- return output.includes("successfully authenticated");
1741
+ await downloadAndExtract(repository, ref, projectDir);
1789
1742
  } catch (error) {
1790
- if (isExecException(error)) {
1791
- const stdout = error.stdout ? error.stdout.toString() : "";
1792
- const stderr = error.stderr ? error.stderr.toString() : "";
1793
- const combinedOutput = stdout + stderr;
1794
- if (combinedOutput.includes("successfully authenticated")) {
1795
- return true;
1796
- }
1743
+ const message = error instanceof Error ? error.message : "unknown error";
1744
+ if (attempt >= MAX_ATTEMPTS) {
1745
+ throw new Error(`Failed to download Catalyst after ${MAX_ATTEMPTS} attempts: ${message}`);
1797
1746
  }
1798
- return false;
1799
- }
1800
- }
1801
-
1802
- // src/cli/lib/reset-branch-to-ref.ts
1803
- import { sync as spawnSync2 } from "cross-spawn";
1804
- function resetBranchToRef(projectDir, ghRef) {
1805
- const spawn = spawnSync2("git", ["reset", "--hard", ghRef, "--"], {
1806
- cwd: projectDir,
1807
- encoding: "utf8",
1808
- shell: false
1809
- });
1810
- const stderr = spawn.stderr.trim();
1811
- if (spawn.status !== 0 && stderr) {
1812
- throw new Error(stderr);
1747
+ consola.warn(`Download attempt ${attempt} failed (${message}). Retrying...`);
1748
+ await wait(attempt * 1e3);
1749
+ return attemptDownload(repository, ref, projectDir, attempt + 1);
1813
1750
  }
1814
- }
1815
-
1816
- // src/cli/lib/clone-catalyst.ts
1817
- var cloneCatalyst = ({
1751
+ };
1752
+ var extractCatalyst = async ({
1818
1753
  repository,
1819
- projectName,
1820
- projectDir,
1821
- ghRef,
1822
- resetMain = false
1754
+ ref,
1755
+ projectDir
1823
1756
  }) => {
1824
- const useSSH = hasGitHubSSH();
1825
- consola.info(`Cloning ${repository} using ${useSSH ? "SSH" : "HTTPS"}...`);
1826
- const cloneCommand = `git clone ${useSSH ? `git@github.com:${repository}` : `https://github.com/${repository}`}.git${projectName ? ` ${projectName}` : ""}`;
1827
- execSync3(cloneCommand, { stdio: "inherit" });
1828
- execSync3("git remote rename origin upstream", { cwd: projectDir, stdio: "inherit" });
1829
- if (ghRef) {
1830
- if (resetMain) {
1831
- execSync3("git checkout -b main", { cwd: projectDir, stdio: "inherit" });
1832
- resetBranchToRef(projectDir, ghRef);
1833
- consola.success(`Reset main to ${ghRef} successfully.`);
1834
- return;
1835
- }
1836
- checkoutRef(projectDir, ghRef);
1757
+ consola.info(`Downloading ${repository}#${ref}...`);
1758
+ mkdirSync2(projectDir, { recursive: true });
1759
+ await attemptDownload(repository, ref, projectDir);
1760
+ };
1761
+
1762
+ // src/cli/lib/init-git-repo.ts
1763
+ import { execSync } from "child_process";
1764
+ import { rmSync } from "fs";
1765
+ import { join as join7 } from "path";
1766
+ var run = (command, cwd) => execSync(command, { cwd, stdio: "ignore" });
1767
+ var initGitRepo = (projectDir) => {
1768
+ try {
1769
+ run("git init", projectDir);
1770
+ run("git add -A", projectDir);
1771
+ run('git commit -m "Initial commit from Catalyst"', projectDir);
1772
+ consola.success("Initialized a git repository.");
1773
+ } catch {
1774
+ rmSync(join7(projectDir, ".git"), { recursive: true, force: true });
1775
+ consola.warn("Could not initialize a git repository. Skipping.");
1837
1776
  }
1838
1777
  };
1839
1778
 
1840
1779
  // src/cli/lib/install-dependencies.ts
1841
1780
  import { installDependencies as installDeps } from "nypm";
1842
- import yoctoSpinner3 from "yocto-spinner";
1843
- var installDependencies = async (projectDir) => {
1844
- const spinner = yoctoSpinner3().start("Installing dependencies. This could take a minute...");
1781
+ import yoctoSpinner2 from "yocto-spinner";
1782
+ var installDependencies = async (projectDir, packageManager = "pnpm") => {
1783
+ const spinner = yoctoSpinner2().start(
1784
+ `Installing dependencies with ${packageManager}. This could take a minute...`
1785
+ );
1845
1786
  try {
1846
- await installDeps({ cwd: projectDir, silent: true, packageManager: "pnpm" });
1787
+ await installDeps({ cwd: projectDir, silent: true, packageManager });
1847
1788
  spinner.success("Dependencies installed successfully.");
1848
1789
  } catch (error) {
1849
1790
  const message = error instanceof Error ? error.message : "unknown error";
@@ -1906,22 +1847,94 @@ var getAvailableLocales = async (storeHash, accessToken, apiHost) => {
1906
1847
  return AvailableLocalesSuccessSchema.parse(await response.json()).data.filter(({ id }) => allowedLocales.includes(id)).map(({ name, id }) => ({ name: `${name} (${id})`, value: id }));
1907
1848
  };
1908
1849
 
1909
- // src/cli/lib/setup-core-project.ts
1910
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1911
- import { dirname as dirname3, join as join8 } from "path";
1850
+ // src/cli/lib/rewrite-core-package.ts
1851
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1852
+ import { join as join8 } from "path";
1912
1853
  import { z as z9 } from "zod";
1854
+ var NATIVE_BUILD_ALLOWLIST = [
1855
+ "@parcel/watcher",
1856
+ "@swc/core",
1857
+ "@vercel/speed-insights",
1858
+ "esbuild",
1859
+ "msw",
1860
+ "protobufjs",
1861
+ "puppeteer",
1862
+ "sharp",
1863
+ "workerd"
1864
+ ];
1865
+ var DEP_FIELDS = [
1866
+ "dependencies",
1867
+ "devDependencies",
1868
+ "optionalDependencies",
1869
+ "peerDependencies"
1870
+ ];
1913
1871
  var corePackageJsonSchema2 = z9.looseObject({
1872
+ version: z9.string().optional(),
1873
+ private: z9.boolean().optional(),
1914
1874
  scripts: z9.record(z9.string(), z9.string()).optional(),
1915
- dependencies: z9.record(z9.string(), z9.string()).optional()
1875
+ dependencies: z9.record(z9.string(), z9.string()).optional(),
1876
+ devDependencies: z9.record(z9.string(), z9.string()).optional(),
1877
+ optionalDependencies: z9.record(z9.string(), z9.string()).optional(),
1878
+ peerDependencies: z9.record(z9.string(), z9.string()).optional(),
1879
+ pnpm: z9.looseObject({}).optional(),
1880
+ catalyst: z9.looseObject({}).optional()
1881
+ });
1882
+ var registryVersionSchema = z9.object({ version: z9.string() });
1883
+ var resolvePublishedVersion = async (pkgName) => {
1884
+ const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`);
1885
+ if (!response.ok) {
1886
+ throw new Error(
1887
+ `Could not resolve a published version for "${pkgName}" (HTTP ${response.status}). The extracted project still references it via the workspace protocol and cannot be installed.`
1888
+ );
1889
+ }
1890
+ const { version: version2 } = registryVersionSchema.parse(await response.json());
1891
+ return `^${version2}`;
1892
+ };
1893
+ var rewriteCorePackage = async (projectDir, ref) => {
1894
+ const packageJsonPath = join8(projectDir, "package.json");
1895
+ const pkg = corePackageJsonSchema2.parse(JSON.parse(readFileSync3(packageJsonPath, "utf-8")));
1896
+ const workspaceDeps = DEP_FIELDS.flatMap((field) => {
1897
+ const deps = pkg[field];
1898
+ if (!deps) return [];
1899
+ return Object.entries(deps).filter(([, range]) => range.startsWith("workspace:")).map(([name]) => ({ deps, name }));
1900
+ });
1901
+ await Promise.all(
1902
+ workspaceDeps.map(async ({ deps, name }) => {
1903
+ const resolved = await resolvePublishedVersion(name);
1904
+ deps[name] = resolved;
1905
+ consola.info(`Resolved ${name} to ${resolved}`);
1906
+ })
1907
+ );
1908
+ delete pkg.private;
1909
+ if (pkg.scripts) {
1910
+ pkg.scripts = Object.fromEntries(
1911
+ Object.entries(pkg.scripts).filter(([, command]) => !command.includes("turbo"))
1912
+ );
1913
+ }
1914
+ pkg.pnpm = { ...pkg.pnpm, onlyBuiltDependencies: NATIVE_BUILD_ALLOWLIST };
1915
+ pkg.trustedDependencies = NATIVE_BUILD_ALLOWLIST;
1916
+ pkg.catalyst = { ...pkg.catalyst, version: pkg.version, ref };
1917
+ writeFileSync2(packageJsonPath, `${JSON.stringify(sortPackageJsonFields(pkg), null, 2)}
1918
+ `);
1919
+ writeFileSync2(join8(projectDir, ".npmrc"), "legacy-peer-deps=true\n");
1920
+ };
1921
+
1922
+ // src/cli/lib/setup-core-project.ts
1923
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1924
+ import { dirname as dirname3, join as join9 } from "path";
1925
+ import { z as z10 } from "zod";
1926
+ var corePackageJsonSchema3 = z10.looseObject({
1927
+ scripts: z10.record(z10.string(), z10.string()).optional(),
1928
+ dependencies: z10.record(z10.string(), z10.string()).optional()
1916
1929
  });
1917
1930
  var writeJson2 = (path, value) => {
1918
- mkdirSync2(dirname3(path), { recursive: true });
1919
- writeFileSync2(path, `${JSON.stringify(value, null, 2)}
1931
+ mkdirSync3(dirname3(path), { recursive: true });
1932
+ writeFileSync3(path, `${JSON.stringify(value, null, 2)}
1920
1933
  `);
1921
1934
  };
1922
1935
  var setupCoreProject = (projectDir) => {
1923
- const corePackageJsonPath = join8(projectDir, "core", "package.json");
1924
- const pkg = corePackageJsonSchema2.parse(JSON.parse(readFileSync3(corePackageJsonPath, "utf-8")));
1936
+ const corePackageJsonPath = join9(projectDir, "package.json");
1937
+ const pkg = corePackageJsonSchema3.parse(JSON.parse(readFileSync4(corePackageJsonPath, "utf-8")));
1925
1938
  pkg.scripts = {
1926
1939
  ...pkg.scripts,
1927
1940
  build: "npm run generate && catalyst build",
@@ -1937,10 +1950,10 @@ var setupCoreProject = (projectDir) => {
1937
1950
 
1938
1951
  // src/cli/lib/write-env.ts
1939
1952
  import { outputFileSync as outputFileSync2 } from "fs-extra/esm";
1940
- import { join as join9 } from "path";
1953
+ import { join as join10 } from "path";
1941
1954
  var writeEnv = (projectDir, envVars) => {
1942
1955
  outputFileSync2(
1943
- join9(projectDir, "core", ".env.local"),
1956
+ join10(projectDir, ".env.local"),
1944
1957
  `${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
1945
1958
  `
1946
1959
  );
@@ -2038,7 +2051,7 @@ async function setupProject(options) {
2038
2051
  }
2039
2052
  if (projectName) {
2040
2053
  projectName = kebabCase(projectName);
2041
- projectDir = join10(options.projectDir, projectName);
2054
+ projectDir = join11(options.projectDir, projectName);
2042
2055
  if (pathExistsSync(projectDir)) {
2043
2056
  consola.error(`${projectDir} already exists`);
2044
2057
  process.exit(1);
@@ -2048,7 +2061,7 @@ async function setupProject(options) {
2048
2061
  const validateProjectName = (i) => {
2049
2062
  const formatted = kebabCase(i);
2050
2063
  if (!formatted) return "Project name is required";
2051
- const targetDir = join10(options.projectDir, formatted);
2064
+ const targetDir = join11(options.projectDir, formatted);
2052
2065
  if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
2053
2066
  projectName = formatted;
2054
2067
  projectDir = targetDir;
@@ -2066,19 +2079,11 @@ async function setupProject(options) {
2066
2079
  }
2067
2080
  function checkRequiredTools() {
2068
2081
  try {
2069
- execSync4(getPlatformCheckCommand("git"), { stdio: "ignore" });
2082
+ execSync2(getPlatformCheckCommand("git"), { stdio: "ignore" });
2070
2083
  } catch {
2071
2084
  consola.error("git is required to create a Catalyst project");
2072
2085
  process.exit(1);
2073
2086
  }
2074
- try {
2075
- execSync4(getPlatformCheckCommand("pnpm"), { stdio: "ignore" });
2076
- } catch {
2077
- consola.error(
2078
- "pnpm is required to create a Catalyst project. Enable it by running `corepack enable pnpm`."
2079
- );
2080
- process.exit(1);
2081
- }
2082
2087
  }
2083
2088
  var create = new Command4("create").configureHelp({ showGlobalOptions: true }).description("Scaffold and connect a Catalyst storefront to your BigCommerce store.").addHelpText(
2084
2089
  "after",
@@ -2094,9 +2099,9 @@ Examples:
2094
2099
  $ catalyst create --project-name my-store --hosting commerce`
2095
2100
  ).option("--project-name <name>", "Name of your Catalyst project").option("--project-dir <dir>", "Directory in which to create your project", process.cwd()).addOption(storeHashOption()).addOption(accessTokenOption()).option("--channel-id <id>", "BigCommerce channel ID", parseChannelId2).option("--storefront-token <token>", "BigCommerce storefront token").option(
2096
2101
  "--gh-ref <ref>",
2097
- "Clone a specific ref from the source repository",
2102
+ "Extract a specific ref (tag, branch, or commit) from the source repository",
2098
2103
  "@bigcommerce/catalyst-core@latest"
2099
- ).option("--reset-main", "Reset the main branch to the gh-ref").option("--repository <repository>", "GitHub repository to clone from", "bigcommerce/catalyst").option(
2104
+ ).option("--repository <repository>", "GitHub repository to extract from", "bigcommerce/catalyst").option(
2100
2105
  "--env <vars...>",
2101
2106
  "Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable).",
2102
2107
  parseEnvFlag
@@ -2226,8 +2231,10 @@ Examples:
2226
2231
  );
2227
2232
  }
2228
2233
  consola.info(`Creating '${projectName}' at '${projectDir}'`);
2234
+ const packageManager = detectPackageManager();
2229
2235
  try {
2230
- cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
2236
+ await extractCatalyst({ repository, ref: ghRef, projectDir });
2237
+ await rewriteCorePackage(projectDir, ghRef);
2231
2238
  setupCoreProject(projectDir);
2232
2239
  if (useCommerceHosting && commerceHostingProject && storeHash && accessToken) {
2233
2240
  await setupCommerceHosting({
@@ -2238,8 +2245,8 @@ Examples:
2238
2245
  });
2239
2246
  }
2240
2247
  writeEnv(projectDir, envVars);
2241
- await installDependencies(projectDir);
2242
- buildWorkspacePackages(projectDir);
2248
+ await installDependencies(projectDir, packageManager);
2249
+ initGitRepo(projectDir);
2243
2250
  } catch (error) {
2244
2251
  if (pathExistsSync(projectDir)) {
2245
2252
  consola.warn(
@@ -2249,10 +2256,10 @@ Examples:
2249
2256
  throw error;
2250
2257
  }
2251
2258
  consola.success(`Created '${projectName}' at '${projectDir}'`);
2252
- const steps = [`cd ${projectName}/core && pnpm run dev`];
2259
+ const steps = [`cd ${projectName} && ${packageManager} run dev`];
2253
2260
  if (useCommerceHosting) {
2254
2261
  steps.push(
2255
- `Run 'cd ${projectName}/core && pnpm run deploy' when ready to deploy to Commerce Hosting.`
2262
+ `Run 'cd ${projectName} && ${packageManager} run deploy' when ready to deploy to Commerce Hosting.`
2256
2263
  );
2257
2264
  }
2258
2265
  consola.log(
@@ -2268,9 +2275,9 @@ import AdmZip from "adm-zip";
2268
2275
  import { Command as Command5, Option as Option6 } from "commander";
2269
2276
  import { colorize as colorize5 } from "consola/utils";
2270
2277
  import { access, readdir, readFile } from "fs/promises";
2271
- import { dirname as dirname4, join as join11 } from "path";
2272
- import yoctoSpinner4 from "yocto-spinner";
2273
- import { z as z10 } from "zod";
2278
+ import { join as join12 } from "path";
2279
+ import yoctoSpinner3 from "yocto-spinner";
2280
+ import { z as z11 } from "zod";
2274
2281
 
2275
2282
  // src/cli/lib/deployment-errors.ts
2276
2283
  var ERROR_MESSAGES = {
@@ -2289,7 +2296,7 @@ function getDeploymentErrorMessage(code) {
2289
2296
  }
2290
2297
 
2291
2298
  // src/cli/commands/deploy.ts
2292
- var stepsEnum = z10.enum([
2299
+ var stepsEnum = z11.enum([
2293
2300
  "initializing",
2294
2301
  "downloading",
2295
2302
  "unzipping",
@@ -2307,41 +2314,41 @@ var STEPS = {
2307
2314
  finalizing: "Finalizing...",
2308
2315
  complete: "Complete"
2309
2316
  };
2310
- var UploadSignatureSchema = z10.object({
2311
- data: z10.object({
2312
- upload_url: z10.url(),
2313
- upload_uuid: z10.string()
2317
+ var UploadSignatureSchema = z11.object({
2318
+ data: z11.object({
2319
+ upload_url: z11.url(),
2320
+ upload_uuid: z11.string()
2314
2321
  })
2315
2322
  });
2316
- var CreateDeploymentSchema = z10.object({
2317
- data: z10.object({
2318
- deployment_uuid: z10.uuid()
2323
+ var CreateDeploymentSchema = z11.object({
2324
+ data: z11.object({
2325
+ deployment_uuid: z11.uuid()
2319
2326
  })
2320
2327
  });
2321
- var ProjectSchema = z10.object({
2322
- data: z10.array(
2323
- z10.object({
2324
- uuid: z10.uuid(),
2325
- name: z10.string()
2328
+ var ProjectSchema = z11.object({
2329
+ data: z11.array(
2330
+ z11.object({
2331
+ uuid: z11.uuid(),
2332
+ name: z11.string()
2326
2333
  })
2327
2334
  )
2328
2335
  });
2329
- var DeploymentStatusSchema = z10.object({
2330
- deployment_uuid: z10.uuid(),
2331
- deployment_status: z10.enum(["queued", "in_progress", "failed", "completed"]),
2332
- event: z10.object({
2336
+ var DeploymentStatusSchema = z11.object({
2337
+ deployment_uuid: z11.uuid(),
2338
+ deployment_status: z11.enum(["queued", "in_progress", "failed", "completed"]),
2339
+ event: z11.object({
2333
2340
  step: stepsEnum,
2334
- progress: z10.number()
2341
+ progress: z11.number()
2335
2342
  }).nullable(),
2336
- deployment_hostnames: z10.array(z10.string()).optional(),
2337
- error: z10.object({
2338
- code: z10.number()
2343
+ deployment_hostnames: z11.array(z11.string()).optional(),
2344
+ error: z11.object({
2345
+ code: z11.number()
2339
2346
  }).optional()
2340
2347
  });
2341
2348
  var generateBundleZip = async () => {
2342
2349
  consola.info("Generating bundle...");
2343
- const bigcommerceDir = join11(process.cwd(), ".bigcommerce");
2344
- const distDir = join11(process.cwd(), ".bigcommerce", "dist");
2350
+ const bigcommerceDir = join12(process.cwd(), ".bigcommerce");
2351
+ const distDir = join12(process.cwd(), ".bigcommerce", "dist");
2345
2352
  try {
2346
2353
  await access(distDir);
2347
2354
  } catch {
@@ -2351,7 +2358,7 @@ var generateBundleZip = async () => {
2351
2358
  if (buildDirContents.length === 0) {
2352
2359
  throw new Error(`Dist directory is empty: ${distDir}`);
2353
2360
  }
2354
- const outputZip = join11(bigcommerceDir, "bundle.zip");
2361
+ const outputZip = join12(bigcommerceDir, "bundle.zip");
2355
2362
  const zip = new AdmZip();
2356
2363
  zip.addLocalFolder(distDir, "output");
2357
2364
  zip.writeZip(outputZip);
@@ -2383,7 +2390,7 @@ var generateUploadSignature = async (storeHash, accessToken, apiHost) => {
2383
2390
  };
2384
2391
  var uploadBundleZip = async (uploadUrl) => {
2385
2392
  consola.info("Uploading bundle...");
2386
- const zipPath = join11(process.cwd(), ".bigcommerce", "bundle.zip");
2393
+ const zipPath = join12(process.cwd(), ".bigcommerce", "bundle.zip");
2387
2394
  const fileBuffer = await readFile(zipPath);
2388
2395
  const response = await fetch(uploadUrl, {
2389
2396
  method: "PUT",
@@ -2438,7 +2445,7 @@ var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, a
2438
2445
  };
2439
2446
  var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost) => {
2440
2447
  consola.info("Fetching deployment status...");
2441
- const spinner = yoctoSpinner4().start("Fetching...");
2448
+ const spinner = yoctoSpinner3().start("Fetching...");
2442
2449
  const response = await fetch(
2443
2450
  `https://${apiHost}/stores/${storeHash}/v3/infrastructure/deployments/${deploymentUuid}/events`,
2444
2451
  {
@@ -2599,15 +2606,15 @@ Example:
2599
2606
  consola.info("When you're ready to deploy, re-run `catalyst deploy` to complete setup.");
2600
2607
  process.exit(0);
2601
2608
  }
2602
- const projectDir = dirname4(process.cwd());
2609
+ const projectDir = process.cwd();
2603
2610
  await setupCommerceHosting({ projectDir, projectUuid, storeHash, accessToken });
2604
2611
  consola.success("Commerce Hosting setup complete.");
2605
2612
  await installDependencies(projectDir);
2606
2613
  } else {
2607
- await cleanupCloudflareIncompatibilities(dirname4(process.cwd()));
2614
+ await cleanupCloudflareIncompatibilities(process.cwd());
2608
2615
  }
2609
2616
  if (options.prebuilt) {
2610
- const distDir = join11(process.cwd(), ".bigcommerce", "dist");
2617
+ const distDir = join12(process.cwd(), ".bigcommerce", "dist");
2611
2618
  try {
2612
2619
  await access(distDir);
2613
2620
  } catch {
@@ -2675,10 +2682,459 @@ Example:
2675
2682
  }
2676
2683
  });
2677
2684
 
2685
+ // src/cli/commands/domains.ts
2686
+ import { confirm as confirm4 } from "@inquirer/prompts";
2687
+ import { Command as Command6, Option as Option7 } from "commander";
2688
+ import { colorize as colorize7 } from "consola/utils";
2689
+
2690
+ // src/cli/lib/domains.ts
2691
+ import { z as z13 } from "zod";
2692
+
2693
+ // src/cli/lib/observability.ts
2694
+ import { colorize as colorize6 } from "consola/utils";
2695
+ import { z as z12 } from "zod";
2696
+ var LOG_LEVELS = ["debug", "info", "warn", "error"];
2697
+ var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
2698
+ var logEntrySchema = z12.object({
2699
+ timestamp: z12.string().nullable().optional(),
2700
+ level: z12.string().optional(),
2701
+ messages: z12.array(z12.unknown()).optional(),
2702
+ is_exception: z12.boolean().optional(),
2703
+ exception_name: z12.string().optional(),
2704
+ request: z12.object({
2705
+ method: z12.string().optional(),
2706
+ url: z12.string().optional(),
2707
+ status_code: z12.number().optional()
2708
+ }).nullable().optional()
2709
+ });
2710
+ var queryLogsSchema = z12.object({
2711
+ data: z12.array(logEntrySchema)
2712
+ });
2713
+ var v3ErrorSchema = z12.object({
2714
+ title: z12.string().optional(),
2715
+ errors: z12.record(z12.string(), z12.string()).optional()
2716
+ });
2717
+ function formatV3Error(body) {
2718
+ const parsed = v3ErrorSchema.safeParse(body);
2719
+ if (!parsed.success) return void 0;
2720
+ const { title, errors } = parsed.data;
2721
+ const fieldErrors = errors && Object.keys(errors).length > 0 ? Object.entries(errors).map(([field, message]) => `${field}: ${message}`).join("; ") : void 0;
2722
+ if (title && fieldErrors) return `${title} (${fieldErrors})`;
2723
+ return title ?? fieldErrors;
2724
+ }
2725
+ function parseTimeInput(value) {
2726
+ const trimmed = value.trim();
2727
+ if (/^\d+$/.test(trimmed)) {
2728
+ return Number(trimmed) * 1e3;
2729
+ }
2730
+ const ms = Date.parse(trimmed);
2731
+ return Number.isNaN(ms) ? null : ms;
2732
+ }
2733
+ var DURATION_UNIT_MS = {
2734
+ s: 1e3,
2735
+ m: 60 * 1e3,
2736
+ h: 60 * 60 * 1e3,
2737
+ d: 24 * 60 * 60 * 1e3
2738
+ };
2739
+ function parseDuration(value) {
2740
+ const match = /^(\d+)([smhd])$/.exec(value.trim());
2741
+ if (!match) return null;
2742
+ return Number(match[1]) * DURATION_UNIT_MS[match[2]];
2743
+ }
2744
+ function resolveTimeWindow(opts, nowMs = Date.now()) {
2745
+ const { since } = opts;
2746
+ let { start: start2, end } = opts;
2747
+ let startMs;
2748
+ let endMs;
2749
+ if (end) {
2750
+ endMs = parseTimeInput(end);
2751
+ if (endMs === null) {
2752
+ throw new Error(
2753
+ `Invalid --end value "${end}". Provide an ISO-8601 timestamp or a Unix epoch (seconds).`
2754
+ );
2755
+ }
2756
+ } else {
2757
+ endMs = nowMs;
2758
+ end = new Date(endMs).toISOString();
2759
+ }
2760
+ if (since) {
2761
+ const durationMs = parseDuration(since);
2762
+ if (durationMs === null) {
2763
+ throw new Error(
2764
+ `Invalid --since value "${since}". Provide a duration like 30m, 6h, or 2d (units: s, m, h, d).`
2765
+ );
2766
+ }
2767
+ startMs = endMs - durationMs;
2768
+ start2 = new Date(startMs).toISOString();
2769
+ } else if (start2) {
2770
+ startMs = parseTimeInput(start2);
2771
+ if (startMs === null) {
2772
+ throw new Error(
2773
+ `Invalid --start value "${start2}". Provide an ISO-8601 timestamp or a Unix epoch (seconds).`
2774
+ );
2775
+ }
2776
+ } else {
2777
+ throw new Error("Provide a time window with --since <duration> or --start <time>.");
2778
+ }
2779
+ if (startMs > endMs) {
2780
+ throw new Error("Invalid time window: --start must be before or equal to --end.");
2781
+ }
2782
+ if (endMs - startMs > SEVEN_DAYS_MS) {
2783
+ throw new Error("Invalid time window: the range must not exceed 7 days.");
2784
+ }
2785
+ return { start: start2, end };
2786
+ }
2787
+ var LEVEL_COLORS = {
2788
+ debug: "gray",
2789
+ info: "green",
2790
+ warn: "yellow",
2791
+ error: "red"
2792
+ };
2793
+ var UNKNOWN_TIME = "unknown";
2794
+ function isRedundantExceptionName(name, message) {
2795
+ const parts = name.split("<*>").map((part) => part.trim()).filter((part) => part.length > 0);
2796
+ return parts.length > 0 && parts.every((part) => message.includes(part));
2797
+ }
2798
+ var stringifyMessage = (message) => typeof message === "string" ? message : JSON.stringify(message);
2799
+ function formatLogEntry(entry, format = "default") {
2800
+ const message = (entry.messages ?? []).map(stringifyMessage).join(" ").replace(/\s+/g, " ").trim();
2801
+ if (format === "short") return message;
2802
+ const level = entry.level ?? "unknown";
2803
+ const coloredLevel = colorize6(LEVEL_COLORS[level.toLowerCase()] ?? "white", level.toUpperCase());
2804
+ const exceptionStr = entry.is_exception && entry.exception_name && !isRedundantExceptionName(entry.exception_name, message) ? ` ${colorize6("red", `[${entry.exception_name}]`)}` : "";
2805
+ let requestStr = "";
2806
+ if (format === "request" && entry.request) {
2807
+ const parts = [];
2808
+ if (entry.request.method) parts.push(entry.request.method);
2809
+ if (entry.request.url) parts.push(entry.request.url);
2810
+ const status = entry.request.status_code ? ` (${entry.request.status_code})` : "";
2811
+ if (parts.length > 0) requestStr = ` ${parts.join(" ")}${status}`;
2812
+ }
2813
+ return `[${entry.timestamp ?? UNKNOWN_TIME}] [${coloredLevel}]${requestStr}${exceptionStr} ${message}`;
2814
+ }
2815
+ async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
2816
+ const search = new URLSearchParams();
2817
+ search.set("start", params.start);
2818
+ search.set("end", params.end);
2819
+ if (params.method) search.set("method", params.method);
2820
+ if (params.statusCode != null) search.set("status_code", String(params.statusCode));
2821
+ if (params.urlLike) search.set("url:like", params.urlLike);
2822
+ if (params.levelMin) search.set("level:min", params.levelMin);
2823
+ if (params.limit != null) search.set("limit", String(params.limit));
2824
+ const response = await fetch(
2825
+ `https://${apiHost}/stores/${storeHash}/v3/infrastructure/logs/${projectUuid}?${search.toString()}`,
2826
+ {
2827
+ method: "GET",
2828
+ headers: {
2829
+ "X-Auth-Token": accessToken,
2830
+ "X-Correlation-Id": getTelemetry().correlationId,
2831
+ Accept: "application/json"
2832
+ }
2833
+ }
2834
+ );
2835
+ assertAuthorized(response);
2836
+ if (response.status === 403) {
2837
+ throw new Error(
2838
+ "Infrastructure Logs API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
2839
+ );
2840
+ }
2841
+ if (response.status === 404) {
2842
+ throw new Error("Project not found. Check the project UUID.");
2843
+ }
2844
+ if (response.status === 400 || response.status === 422) {
2845
+ const body = await response.json().catch(() => null);
2846
+ throw new Error(formatV3Error(body) ?? "Invalid log query.");
2847
+ }
2848
+ if (!response.ok) {
2849
+ throw new Error(`Failed to fetch logs: ${response.status} ${response.statusText}`);
2850
+ }
2851
+ const res = await response.json();
2852
+ return queryLogsSchema.parse(res);
2853
+ }
2854
+
2855
+ // src/cli/lib/domains.ts
2856
+ var domainStatusSchema = z13.enum(["pending", "verified", "failed", "unknown"]);
2857
+ var domainSchema = z13.object({
2858
+ domain: z13.string(),
2859
+ project_uuid: z13.string(),
2860
+ verification_status: domainStatusSchema
2861
+ });
2862
+ var domainResponseSchema = z13.object({
2863
+ data: domainSchema
2864
+ });
2865
+ var domainListResponseSchema = z13.object({
2866
+ data: z13.array(domainSchema)
2867
+ });
2868
+ var DOMAINS_API_NOT_ENABLED = "Infrastructure Domains API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it.";
2869
+ function domainsUrl(storeHash, projectUuid, apiHost) {
2870
+ return `https://${apiHost}/stores/${storeHash}/v3/infrastructure/projects/${projectUuid}/domains`;
2871
+ }
2872
+ function domainUrl(storeHash, projectUuid, domain, apiHost) {
2873
+ return `${domainsUrl(storeHash, projectUuid, apiHost)}/${encodeURIComponent(domain)}`;
2874
+ }
2875
+ function authHeaders3(accessToken) {
2876
+ return {
2877
+ Accept: "application/json",
2878
+ "X-Auth-Token": accessToken,
2879
+ "X-Correlation-Id": getTelemetry().correlationId
2880
+ };
2881
+ }
2882
+ function formatResponseStatus(response, message) {
2883
+ return [response.status, response.statusText || message].filter(Boolean).join(" ");
2884
+ }
2885
+ async function getErrorMessage(response, action) {
2886
+ const body = await response.json().catch(() => null);
2887
+ const message = formatV3Error(body);
2888
+ if (response.status >= 500) {
2889
+ const status = formatResponseStatus(response, message);
2890
+ return `${action}: ${status}. This is a server-side response from the Domains API.`;
2891
+ }
2892
+ return message ?? `${action}: ${response.statusText}`;
2893
+ }
2894
+ async function assertDomainResponse(response, action) {
2895
+ assertAuthorized(response);
2896
+ if (response.status === 403) {
2897
+ throw new Error(DOMAINS_API_NOT_ENABLED);
2898
+ }
2899
+ if (!response.ok) {
2900
+ throw new Error(await getErrorMessage(response, action));
2901
+ }
2902
+ }
2903
+ async function createDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
2904
+ const response = await fetch(domainsUrl(storeHash, projectUuid, apiHost), {
2905
+ method: "POST",
2906
+ headers: {
2907
+ ...authHeaders3(accessToken),
2908
+ "Content-Type": "application/json"
2909
+ },
2910
+ body: JSON.stringify({ domain })
2911
+ });
2912
+ await assertDomainResponse(response, "Failed to add domain");
2913
+ const result = await response.json();
2914
+ return domainResponseSchema.parse(result).data;
2915
+ }
2916
+ async function getDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
2917
+ const response = await fetch(domainUrl(storeHash, projectUuid, domain, apiHost), {
2918
+ method: "GET",
2919
+ headers: authHeaders3(accessToken)
2920
+ });
2921
+ await assertDomainResponse(response, "Failed to fetch domain");
2922
+ const result = await response.json();
2923
+ return domainResponseSchema.parse(result).data;
2924
+ }
2925
+ async function listDomains(projectUuid, storeHash, accessToken, apiHost, filters = {}) {
2926
+ const search = new URLSearchParams();
2927
+ if (filters.domains && filters.domains.length > 0) {
2928
+ search.set("domain:in", filters.domains.join(","));
2929
+ }
2930
+ if (filters.verificationStatus) {
2931
+ search.set("verification_status", filters.verificationStatus);
2932
+ }
2933
+ const query2 = search.toString();
2934
+ const url = `${domainsUrl(storeHash, projectUuid, apiHost)}${query2 ? `?${query2}` : ""}`;
2935
+ const response = await fetch(url, {
2936
+ method: "GET",
2937
+ headers: authHeaders3(accessToken)
2938
+ });
2939
+ await assertDomainResponse(response, "Failed to fetch domains");
2940
+ const result = await response.json();
2941
+ return domainListResponseSchema.parse(result).data;
2942
+ }
2943
+ async function deleteDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
2944
+ const response = await fetch(domainUrl(storeHash, projectUuid, domain, apiHost), {
2945
+ method: "DELETE",
2946
+ headers: authHeaders3(accessToken)
2947
+ });
2948
+ await assertDomainResponse(response, `Failed to remove domain: ${response.statusText}`);
2949
+ }
2950
+
2951
+ // src/cli/commands/domains.ts
2952
+ var WAIT_INTERVAL_MS = 5e3;
2953
+ var WAIT_TIMEOUT_MS = 5 * 60 * 1e3;
2954
+ var DOMAIN_STATUS_FILTERS = ["pending", "verified", "failed"];
2955
+ var STATUS_COLORS = {
2956
+ pending: "yellow",
2957
+ verified: "green",
2958
+ failed: "red",
2959
+ unknown: "gray"
2960
+ };
2961
+ var STATUS_LABELS = {
2962
+ pending: "pending",
2963
+ verified: "active",
2964
+ failed: "failed",
2965
+ unknown: "unknown"
2966
+ };
2967
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
2968
+ function resolveDomainCommandContext(options) {
2969
+ const config2 = getProjectConfig();
2970
+ const { storeHash, accessToken } = resolveCredentials(options, config2);
2971
+ const projectUuid = resolveProjectUuid(options);
2972
+ return {
2973
+ projectUuid,
2974
+ storeHash,
2975
+ accessToken,
2976
+ apiHost: options.apiHost
2977
+ };
2978
+ }
2979
+ function formatDomainStatus(status) {
2980
+ return colorize7(STATUS_COLORS[status], STATUS_LABELS[status]);
2981
+ }
2982
+ function formatDomain(domain) {
2983
+ return `${domain.domain} ${formatDomainStatus(domain.verification_status)}`;
2984
+ }
2985
+ async function waitForDomainVerification({
2986
+ domain,
2987
+ projectUuid,
2988
+ storeHash,
2989
+ accessToken,
2990
+ apiHost,
2991
+ intervalMs = WAIT_INTERVAL_MS,
2992
+ timeoutMs = WAIT_TIMEOUT_MS
2993
+ }) {
2994
+ const startedAt = Date.now();
2995
+ let current = await getDomain(domain, projectUuid, storeHash, accessToken, apiHost);
2996
+ while (current.verification_status === "pending" && Date.now() - startedAt < timeoutMs) {
2997
+ await sleep(intervalMs);
2998
+ current = await getDomain(domain, projectUuid, storeHash, accessToken, apiHost);
2999
+ }
3000
+ return current;
3001
+ }
3002
+ var add = new Command6("add").configureHelp({ showGlobalOptions: true }).description("Add a custom domain to the current Native Hosting project.").argument("<domain>", "Custom domain to add.").addHelpText(
3003
+ "after",
3004
+ `
3005
+ Examples:
3006
+ $ catalyst domains add www.example.com
3007
+
3008
+ # Wait until the domain leaves pending verification
3009
+ $ catalyst domains add www.example.com --wait`
3010
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--wait", "Poll until domain verification completes or times out.").action(async (domain, options) => {
3011
+ const context = resolveDomainCommandContext(options);
3012
+ await getTelemetry().identify(context.storeHash);
3013
+ consola.start(`Adding domain ${domain}...`);
3014
+ let result = await createDomain(
3015
+ domain,
3016
+ context.projectUuid,
3017
+ context.storeHash,
3018
+ context.accessToken,
3019
+ context.apiHost
3020
+ );
3021
+ consola.success(`Domain ${result.domain} added.`);
3022
+ if (options.wait && result.verification_status === "pending") {
3023
+ consola.start(`Waiting for ${result.domain} to verify...`);
3024
+ result = await waitForDomainVerification({ domain: result.domain, ...context });
3025
+ }
3026
+ consola.log(formatDomain(result));
3027
+ process.exit(0);
3028
+ });
3029
+ var list = new Command6("list").configureHelp({ showGlobalOptions: true }).description("List custom domains for the current Native Hosting project.").addHelpText(
3030
+ "after",
3031
+ `
3032
+ Examples:
3033
+ $ catalyst domains list
3034
+
3035
+ # Show pending domains only
3036
+ $ catalyst domains list --status pending`
3037
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--domain <domain>", "Only show a specific domain.").addOption(
3038
+ new Option7("--status <status>", "Filter by verification status.").choices(
3039
+ DOMAIN_STATUS_FILTERS
3040
+ )
3041
+ ).action(async (options) => {
3042
+ const context = resolveDomainCommandContext(options);
3043
+ await getTelemetry().identify(context.storeHash);
3044
+ consola.start("Fetching domains...");
3045
+ const result = await listDomains(
3046
+ context.projectUuid,
3047
+ context.storeHash,
3048
+ context.accessToken,
3049
+ context.apiHost,
3050
+ {
3051
+ domains: options.domain ? [options.domain] : void 0,
3052
+ verificationStatus: options.status
3053
+ }
3054
+ );
3055
+ consola.success("Domains fetched.");
3056
+ if (result.length === 0) {
3057
+ consola.info("No custom domains found.");
3058
+ process.exit(0);
3059
+ return;
3060
+ }
3061
+ result.forEach((item) => consola.log(formatDomain(item)));
3062
+ process.exit(0);
3063
+ });
3064
+ var showStatus = new Command6("status").configureHelp({ showGlobalOptions: true }).description("Show the status of a custom domain on the current Native Hosting project.").argument("<domain>", "Custom domain to check.").addHelpText(
3065
+ "after",
3066
+ `
3067
+ Examples:
3068
+ $ catalyst domains status www.example.com
3069
+
3070
+ # Wait until the domain leaves pending verification
3071
+ $ catalyst domains status www.example.com --wait`
3072
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--wait", "Poll until domain verification completes or times out.").action(async (domain, options) => {
3073
+ const context = resolveDomainCommandContext(options);
3074
+ await getTelemetry().identify(context.storeHash);
3075
+ consola.start(`Fetching status for ${domain}...`);
3076
+ let result = await getDomain(
3077
+ domain,
3078
+ context.projectUuid,
3079
+ context.storeHash,
3080
+ context.accessToken,
3081
+ context.apiHost
3082
+ );
3083
+ consola.success("Domain status fetched.");
3084
+ if (options.wait && result.verification_status === "pending") {
3085
+ consola.start(`Waiting for ${result.domain} to verify...`);
3086
+ result = await waitForDomainVerification({ domain: result.domain, ...context });
3087
+ }
3088
+ consola.log(formatDomain(result));
3089
+ process.exit(0);
3090
+ });
3091
+ var remove = new Command6("remove").configureHelp({ showGlobalOptions: true }).description("Remove a custom domain from the current Native Hosting project.").argument("<domain>", "Custom domain to remove.").addHelpText(
3092
+ "after",
3093
+ `
3094
+ Examples:
3095
+ $ catalyst domains remove www.example.com
3096
+
3097
+ # Skip confirmation for an active domain
3098
+ $ catalyst domains remove www.example.com --force`
3099
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--force", "Skip the confirmation prompt before removing an active domain.").action(async (domain, options) => {
3100
+ const context = resolveDomainCommandContext(options);
3101
+ await getTelemetry().identify(context.storeHash);
3102
+ consola.start(`Fetching status for ${domain}...`);
3103
+ const current = await getDomain(
3104
+ domain,
3105
+ context.projectUuid,
3106
+ context.storeHash,
3107
+ context.accessToken,
3108
+ context.apiHost
3109
+ );
3110
+ if (current.verification_status === "verified" && !options.force) {
3111
+ const confirmed = await confirm4({
3112
+ message: `Remove active domain ${current.domain}? Traffic may stop routing to this project.`,
3113
+ default: false
3114
+ });
3115
+ if (!confirmed) {
3116
+ consola.info("Aborted. No domain was removed.");
3117
+ process.exit(0);
3118
+ return;
3119
+ }
3120
+ }
3121
+ consola.start(`Removing domain ${current.domain}...`);
3122
+ await deleteDomain(
3123
+ current.domain,
3124
+ context.projectUuid,
3125
+ context.storeHash,
3126
+ context.accessToken,
3127
+ context.apiHost
3128
+ );
3129
+ consola.success(`Domain ${current.domain} removed.`);
3130
+ process.exit(0);
3131
+ });
3132
+ var domains = new Command6("domains").configureHelp({ showGlobalOptions: true }).description("Manage custom domains for the current Native Hosting project.").addCommand(add).addCommand(list).addCommand(showStatus).addCommand(remove);
3133
+
2678
3134
  // src/cli/commands/env.ts
2679
- import { Command as Command6 } from "commander";
3135
+ import { Command as Command7 } from "commander";
2680
3136
  var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022";
2681
- var add = new Command6("add").configureHelp({ showGlobalOptions: true }).description(
3137
+ var add2 = new Command7("add").configureHelp({ showGlobalOptions: true }).description(
2682
3138
  "Add or update one or more deployment environment variables. Stored in .bigcommerce/project.json and sent as secrets on every `catalyst deploy`."
2683
3139
  ).argument("<vars...>", "One or more environment variables in KEY=VALUE format.").addHelpText(
2684
3140
  "after",
@@ -2700,7 +3156,7 @@ Example:
2700
3156
  keys.forEach((key) => consola.log(` ${key}=${MASK}`));
2701
3157
  process.exit(0);
2702
3158
  });
2703
- var remove = new Command6("remove").configureHelp({ showGlobalOptions: true }).description("Remove one or more stored deployment environment variables.").argument("<keys...>", "One or more environment variable names to remove.").addHelpText(
3159
+ var remove2 = new Command7("remove").configureHelp({ showGlobalOptions: true }).description("Remove one or more stored deployment environment variables.").argument("<keys...>", "One or more environment variable names to remove.").addHelpText(
2704
3160
  "after",
2705
3161
  `
2706
3162
  Example:
@@ -2726,7 +3182,7 @@ Example:
2726
3182
  }
2727
3183
  process.exit(0);
2728
3184
  });
2729
- var list = new Command6("list").configureHelp({ showGlobalOptions: true }).description("List stored deployment environment variables (values are masked).").addHelpText(
3185
+ var list2 = new Command7("list").configureHelp({ showGlobalOptions: true }).description("List stored deployment environment variables (values are masked).").addHelpText(
2730
3186
  "after",
2731
3187
  `
2732
3188
  Example:
@@ -2743,41 +3199,41 @@ Example:
2743
3199
  keys.forEach((key) => consola.log(`${key}=${MASK}`));
2744
3200
  process.exit(0);
2745
3201
  });
2746
- var env = new Command6("env").configureHelp({ showGlobalOptions: true }).description(
3202
+ var env = new Command7("env").configureHelp({ showGlobalOptions: true }).description(
2747
3203
  "Manage persistent deployment environment variables. These are sent as secrets on every `catalyst deploy`, so you no longer need to pass `--secret` each time."
2748
- ).addCommand(add).addCommand(remove).addCommand(list);
3204
+ ).addCommand(add2).addCommand(remove2).addCommand(list2);
2749
3205
 
2750
3206
  // src/cli/commands/logs.ts
2751
- import { Command as Command7, Option as Option7 } from "commander";
2752
- import { colorize as colorize6 } from "consola/utils";
2753
- import { z as z11 } from "zod";
3207
+ import { Command as Command8, InvalidArgumentError as InvalidArgumentError3, Option as Option8 } from "commander";
3208
+ import { colorize as colorize8 } from "consola/utils";
3209
+ import { z as z14 } from "zod";
2754
3210
  var telemetry = new Telemetry();
2755
3211
  var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
2756
3212
  var MAX_RETRIES = 5;
2757
3213
  var isFatalStatusCode = (status) => status >= 400 && status < 500;
2758
- var LEVEL_COLORS = {
3214
+ var LEVEL_COLORS2 = {
2759
3215
  INFO: "green",
2760
3216
  WARN: "yellow",
2761
3217
  ERROR: "red",
2762
3218
  DEBUG: "gray"
2763
3219
  };
2764
- var LogEventSchema = z11.object({
2765
- uuid: z11.string(),
2766
- project_uuid: z11.string(),
2767
- request: z11.object({
2768
- method: z11.string(),
2769
- url: z11.string(),
2770
- status_code: z11.number()
3220
+ var LogEventSchema = z14.object({
3221
+ uuid: z14.string(),
3222
+ project_uuid: z14.string(),
3223
+ request: z14.object({
3224
+ method: z14.string(),
3225
+ url: z14.string(),
3226
+ status_code: z14.number()
2771
3227
  }),
2772
- logs: z11.array(
2773
- z11.object({
2774
- timestamp: z11.string(),
2775
- level: z11.string(),
2776
- messages: z11.array(z11.unknown())
3228
+ logs: z14.array(
3229
+ z14.object({
3230
+ timestamp: z14.string(),
3231
+ level: z14.string(),
3232
+ messages: z14.array(z14.unknown())
2777
3233
  })
2778
3234
  ),
2779
- exceptions: z11.array(z11.unknown()),
2780
- timestamp: z11.string()
3235
+ exceptions: z14.array(z14.unknown()),
3236
+ timestamp: z14.string()
2781
3237
  }).loose();
2782
3238
  var StreamError = class extends Error {
2783
3239
  constructor(message, fatal) {
@@ -2791,7 +3247,7 @@ var formatLogEvent = (event, format) => {
2791
3247
  logEntries.forEach((entry) => {
2792
3248
  const msg = formatMessages(entry.messages);
2793
3249
  const level = entry.level.toUpperCase();
2794
- const coloredLevel = colorize6(LEVEL_COLORS[level] ?? "white", level);
3250
+ const coloredLevel = colorize8(LEVEL_COLORS2[level] ?? "white", level);
2795
3251
  switch (format) {
2796
3252
  case "short":
2797
3253
  consola.log(msg);
@@ -2934,7 +3390,7 @@ var tailLogs = async (projectUuid, storeHash, accessToken, apiHost, format, conn
2934
3390
  }
2935
3391
  }
2936
3392
  };
2937
- var tail = new Command7("tail").configureHelp({ showGlobalOptions: true }).description("Tail live logs from your deployed application.").addHelpText(
3393
+ var tail = new Command8("tail").configureHelp({ showGlobalOptions: true }).description("Tail live logs from your deployed application.").addHelpText(
2938
3394
  "after",
2939
3395
  `
2940
3396
  Examples:
@@ -2946,7 +3402,7 @@ Examples:
2946
3402
  # Tail logs as raw JSON (useful for piping to other tools)
2947
3403
  $ catalyst logs tail --format json`
2948
3404
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
2949
- new Option7("--format <format>", "Output format for log events.").choices(["json", "pretty", "default", "short", "request"]).default("default")
3405
+ new Option8("--format <format>", "Output format for log events.").choices(["json", "pretty", "default", "short", "request"]).default("default")
2950
3406
  ).action(async (options) => {
2951
3407
  try {
2952
3408
  const config2 = getProjectConfig();
@@ -2959,31 +3415,112 @@ Examples:
2959
3415
  process.exit(1);
2960
3416
  }
2961
3417
  });
2962
- var query = new Command7("query").configureHelp({ showGlobalOptions: true }).description("Query historical logs from your deployed application.").addHelpText(
3418
+ var parseIntInRange = (flag, min, max) => (value) => {
3419
+ const parsed = Number(value);
3420
+ if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
3421
+ throw new InvalidArgumentError3(`${flag} must be an integer between ${min} and ${max}.`);
3422
+ }
3423
+ return parsed;
3424
+ };
3425
+ var query = new Command8("query").configureHelp({ showGlobalOptions: true }).description("Query historical logs from your deployed application.").addHelpText(
2963
3426
  "after",
2964
3427
  `
2965
- Example:
2966
- $ catalyst logs query`
2967
- ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).action((options) => {
2968
- const config2 = getProjectConfig();
2969
- resolveCredentials(options, config2);
2970
- consola.error("The query command is not yet implemented.");
2971
- process.exit(1);
3428
+ Specify a time window with \`--since\` (relative to now) or \`--start\`/\`--end\`
3429
+ (ISO-8601 timestamps or Unix epoch seconds, UTC). The window may not exceed
3430
+ 7 days. Entries print oldest-first; timestamps are UTC.
3431
+
3432
+ Examples:
3433
+ # Last hour of logs
3434
+ $ catalyst logs query --since 1h
3435
+
3436
+ # Everything from today (UTC)
3437
+ $ catalyst logs query --start 2026-06-11T00:00:00Z
3438
+
3439
+ # Errors only for a specific path
3440
+ $ catalyst logs query --since 24h --level-min error --url-like /cart
3441
+
3442
+ # Errors with request details (method, URL, status)
3443
+ $ catalyst logs query --since 1h --level-min error --format request
3444
+
3445
+ # Raw JSON (NDJSON) for piping to other tools
3446
+ $ catalyst logs query --since 2d --format json`
3447
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
3448
+ new Option8(
3449
+ "--since <duration>",
3450
+ "Relative window ending at --end (default: now), e.g. 30m, 6h, 2d (units: s, m, h, d)."
3451
+ ).conflicts("start")
3452
+ ).option("--start <time>", "Window start: ISO-8601 timestamp or Unix epoch (seconds).").option(
3453
+ "--end <time>",
3454
+ "Window end: ISO-8601 timestamp or Unix epoch (seconds). Defaults to now. The window must not exceed 7 days."
3455
+ ).option("--method <method>", "Filter by request HTTP method (case-insensitive).").addOption(
3456
+ new Option8("--status-code <code>", "Filter by response status code (100-599).").argParser(
3457
+ parseIntInRange("--status-code", 100, 599)
3458
+ )
3459
+ ).option("--url-like <substring>", "Filter by URL substring (case-sensitive).").addOption(
3460
+ new Option8("--level-min <level>", "Minimum log level.").choices(Array.from(LOG_LEVELS))
3461
+ ).addOption(
3462
+ new Option8("--limit <n>", "Maximum number of entries to return (1-500).").argParser(parseIntInRange("--limit", 1, 500)).default(100)
3463
+ ).addOption(
3464
+ new Option8("--format <format>", "Output format for log entries.").choices(["json", "pretty", "default", "short", "request"]).default("default")
3465
+ ).action(async (options) => {
3466
+ try {
3467
+ const config2 = getProjectConfig();
3468
+ const { storeHash, accessToken } = resolveCredentials(options, config2);
3469
+ await telemetry.identify(storeHash);
3470
+ const projectUuid = resolveProjectUuid(options);
3471
+ const { start: start2, end } = resolveTimeWindow(options);
3472
+ const result = await queryLogs(projectUuid, storeHash, accessToken, options.apiHost, {
3473
+ start: start2,
3474
+ end,
3475
+ method: options.method,
3476
+ statusCode: options.statusCode,
3477
+ urlLike: options.urlLike,
3478
+ levelMin: options.levelMin,
3479
+ limit: options.limit
3480
+ });
3481
+ const entries = result.data;
3482
+ const ordered = [...entries].reverse();
3483
+ if (options.format === "json") {
3484
+ ordered.forEach((entry) => process.stdout.write(`${JSON.stringify(entry)}
3485
+ `));
3486
+ return;
3487
+ }
3488
+ if (options.format === "pretty") {
3489
+ ordered.forEach((entry) => consola.log(JSON.stringify(entry, null, 2)));
3490
+ return;
3491
+ }
3492
+ if (entries.length === 0) {
3493
+ consola.info("No log entries found for the given window and filters.");
3494
+ return;
3495
+ }
3496
+ const lineFormat = options.format;
3497
+ ordered.forEach((entry) => consola.log(formatLogEntry(entry, lineFormat)));
3498
+ consola.info(
3499
+ `${entries.length} ${entries.length === 1 ? "entry" : "entries"} shown (oldest first, times in UTC).`
3500
+ );
3501
+ if (entries.length === options.limit) {
3502
+ consola.info(
3503
+ `Limit of ${options.limit} reached; older entries may exist. Narrow the time window or raise --limit (max 500) to see more.`
3504
+ );
3505
+ }
3506
+ } catch (error) {
3507
+ consola.error(error instanceof Error ? error.message : error);
3508
+ process.exit(1);
3509
+ }
2972
3510
  });
2973
- var logs = new Command7("logs").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
3511
+ var logs = new Command8("logs").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
2974
3512
 
2975
3513
  // src/cli/commands/project.ts
2976
- import { confirm as confirm4, input as input4, select as select5 } from "@inquirer/prompts";
2977
- import { Command as Command8 } from "commander";
2978
- import { colorize as colorize7 } from "consola/utils";
2979
- import { dirname as dirname5 } from "path";
3514
+ import { confirm as confirm5, input as input4, select as select5 } from "@inquirer/prompts";
3515
+ import { Command as Command9 } from "commander";
3516
+ import { colorize as colorize9 } from "consola/utils";
2980
3517
  async function offerCommerceHostingSetup(projectUuid, credentials) {
2981
- const projectDir = dirname5(process.cwd());
3518
+ const projectDir = process.cwd();
2982
3519
  if (getProjectState().isTransformed) {
2983
3520
  await cleanupCloudflareIncompatibilities(projectDir);
2984
3521
  return;
2985
3522
  }
2986
- const shouldSetup = await confirm4({
3523
+ const shouldSetup = await confirm5({
2987
3524
  message: "Your project has been linked, but is not fully set up for Commerce Hosting deployments yet. Would you like to run the setup now?",
2988
3525
  default: true
2989
3526
  });
@@ -2997,7 +3534,7 @@ async function offerCommerceHostingSetup(projectUuid, credentials) {
2997
3534
  consola.success("Commerce Hosting setup complete.");
2998
3535
  await installDependencies(projectDir);
2999
3536
  }
3000
- var list2 = new Command8("list").configureHelp({ showGlobalOptions: true }).description("List BigCommerce infrastructure projects for your store.").addHelpText(
3537
+ var list3 = new Command9("list").configureHelp({ showGlobalOptions: true }).description("List BigCommerce infrastructure projects for your store.").addHelpText(
3001
3538
  "after",
3002
3539
  `
3003
3540
  Example:
@@ -3016,16 +3553,16 @@ Example:
3016
3553
  }
3017
3554
  const linkedProjectUuid = config2.get("projectUuid");
3018
3555
  const projectList = projects.map((p) => {
3019
- const marker = p.uuid === linkedProjectUuid ? ` ${colorize7("green", "[linked]")}` : "";
3556
+ const marker = p.uuid === linkedProjectUuid ? ` ${colorize9("green", "[linked]")}` : "";
3020
3557
  const hostnames = p.deployment_hostnames.length === 0 ? [" (not deployed)"] : p.deployment_hostnames.map(
3021
- (hostname) => ` ${colorize7("blue", `https://${hostname}`)}`
3558
+ (hostname) => ` ${colorize9("blue", `https://${hostname}`)}`
3022
3559
  );
3023
3560
  return [`${p.name} (${p.uuid})${marker}`, ...hostnames].join("\n");
3024
3561
  });
3025
3562
  consola.log(projectList.join("\n\n"));
3026
3563
  process.exit(0);
3027
3564
  });
3028
- var create2 = new Command8("create").configureHelp({ showGlobalOptions: true }).description(
3565
+ var create2 = new Command9("create").configureHelp({ showGlobalOptions: true }).description(
3029
3566
  "Create a new BigCommerce infrastructure project and link it to your local Catalyst project. Walks you through logging in if you are not authenticated yet."
3030
3567
  ).addHelpText(
3031
3568
  "after",
@@ -3069,7 +3606,7 @@ Example:
3069
3606
  consola.success("Project UUID written to .bigcommerce/project.json.");
3070
3607
  process.exit(0);
3071
3608
  });
3072
- var link2 = new Command8("link").configureHelp({ showGlobalOptions: true }).description(
3609
+ var link2 = new Command9("link").configureHelp({ showGlobalOptions: true }).description(
3073
3610
  "Link your local Catalyst project to a BigCommerce infrastructure project. You can provide a project UUID directly, or fetch and select from available projects using your store credentials."
3074
3611
  ).addHelpText(
3075
3612
  "after",
@@ -3119,7 +3656,7 @@ Examples:
3119
3656
  await offerCommerceHostingSetup(selected.uuid, { storeHash, accessToken });
3120
3657
  process.exit(0);
3121
3658
  });
3122
- var del = new Command8("delete").configureHelp({ showGlobalOptions: true }).description(
3659
+ var del = new Command9("delete").configureHelp({ showGlobalOptions: true }).description(
3123
3660
  "Permanently delete a BigCommerce infrastructure project. This action is irreversible."
3124
3661
  ).addHelpText(
3125
3662
  "after",
@@ -3153,7 +3690,7 @@ Examples:
3153
3690
  message: "Select a project to delete (Press <enter> to select).",
3154
3691
  choices: [
3155
3692
  ...projects.map((p) => ({
3156
- name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize7("green", "[linked]")}` : p.name,
3693
+ name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize9("green", "[linked]")}` : p.name,
3157
3694
  value: p.uuid,
3158
3695
  description: p.uuid
3159
3696
  })),
@@ -3174,7 +3711,7 @@ Examples:
3174
3711
  }
3175
3712
  if (!options.force) {
3176
3713
  const label = targetName ? `"${targetName}" (${targetUuid})` : targetUuid;
3177
- const confirmed = await confirm4({
3714
+ const confirmed = await confirm5({
3178
3715
  message: `Are you sure you want to delete project ${label}? This action is irreversible and will permanently destroy the project and all of its data.`,
3179
3716
  default: false
3180
3717
  });
@@ -3193,14 +3730,14 @@ Examples:
3193
3730
  }
3194
3731
  process.exit(0);
3195
3732
  });
3196
- var project = new Command8("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure project.").addCommand(create2).addCommand(list2).addCommand(link2).addCommand(del);
3733
+ var project = new Command9("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure project.").addCommand(create2).addCommand(list3).addCommand(link2).addCommand(del);
3197
3734
 
3198
3735
  // src/cli/commands/start.ts
3199
- import { Command as Command9 } from "commander";
3736
+ import { Command as Command10 } from "commander";
3200
3737
  import { execa as execa2 } from "execa";
3201
- import { existsSync as existsSync4, lstatSync as lstatSync2, symlinkSync as symlinkSync2 } from "fs";
3202
- import { join as join12, relative } from "path";
3203
- var start = new Command9("start").configureHelp({ showGlobalOptions: true }).description(
3738
+ import { existsSync as existsSync3, lstatSync, symlinkSync } from "fs";
3739
+ import { join as join13, relative } from "path";
3740
+ var start = new Command10("start").configureHelp({ showGlobalOptions: true }).description(
3204
3741
  "Start a local preview of your Catalyst storefront using the OpenNext Cloudflare adapter."
3205
3742
  ).addHelpText(
3206
3743
  "after",
@@ -3217,14 +3754,14 @@ Example:
3217
3754
  });
3218
3755
  return;
3219
3756
  }
3220
- const envLocal = join12(process.cwd(), ".env.local");
3221
- const devVars = join12(process.cwd(), ".bigcommerce", ".dev.vars");
3222
- if (existsSync4(envLocal)) {
3223
- if (!existsSync4(devVars)) {
3224
- const target = relative(join12(process.cwd(), ".bigcommerce"), envLocal);
3225
- symlinkSync2(target, devVars);
3757
+ const envLocal = join13(process.cwd(), ".env.local");
3758
+ const devVars = join13(process.cwd(), ".bigcommerce", ".dev.vars");
3759
+ if (existsSync3(envLocal)) {
3760
+ if (!existsSync3(devVars)) {
3761
+ const target = relative(join13(process.cwd(), ".bigcommerce"), envLocal);
3762
+ symlinkSync(target, devVars);
3226
3763
  consola.success("Symlinked .bigcommerce/.dev.vars \u2192 .env.local");
3227
- } else if (!lstatSync2(devVars).isSymbolicLink()) {
3764
+ } else if (!lstatSync(devVars).isSymbolicLink()) {
3228
3765
  consola.warn(
3229
3766
  ".bigcommerce/.dev.vars already exists and is not a symlink. Wrangler will use it instead of .env.local."
3230
3767
  );
@@ -3241,7 +3778,7 @@ Example:
3241
3778
  "opennextjs-cloudflare",
3242
3779
  "preview",
3243
3780
  "--config",
3244
- join12(".bigcommerce", "wrangler.jsonc")
3781
+ join13(".bigcommerce", "wrangler.jsonc")
3245
3782
  ],
3246
3783
  {
3247
3784
  stdio: "inherit",
@@ -3251,11 +3788,11 @@ Example:
3251
3788
  });
3252
3789
 
3253
3790
  // src/cli/commands/telemetry.ts
3254
- import { Argument, Command as Command10, Option as Option8 } from "commander";
3255
- import { colorize as colorize8 } from "consola/utils";
3791
+ import { Argument, Command as Command11, Option as Option9 } from "commander";
3792
+ import { colorize as colorize10 } from "consola/utils";
3256
3793
  var telemetryService = getTelemetry();
3257
3794
  var isEnabled = telemetryService.isEnabled();
3258
- var telemetry2 = new Command10("telemetry").configureHelp({ showGlobalOptions: true }).description(
3795
+ var telemetry2 = new Command11("telemetry").configureHelp({ showGlobalOptions: true }).description(
3259
3796
  "View or change CLI telemetry collection status. Enabling telemetry helps BigCommerce support diagnose and troubleshoot errors you encounter when using the CLI."
3260
3797
  ).addHelpText(
3261
3798
  "after",
@@ -3269,7 +3806,7 @@ Examples:
3269
3806
 
3270
3807
  # Disable telemetry collection
3271
3808
  $ catalyst telemetry --disable`
3272
- ).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new Option8("--enable", `Enables CLI telemetry collection.`).conflicts("disable")).option("--disable", `Disables CLI telemetry collection.`).action((arg, options) => {
3809
+ ).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new Option9("--enable", `Enables CLI telemetry collection.`).conflicts("disable")).option("--disable", `Disables CLI telemetry collection.`).action((arg, options) => {
3273
3810
  if (options.enable || arg === "enable") {
3274
3811
  telemetryService.setEnabled(true);
3275
3812
  isEnabled = true;
@@ -3286,7 +3823,7 @@ Examples:
3286
3823
  consola.info("Catalyst CLI Telemetry\n");
3287
3824
  }
3288
3825
  consola.info(
3289
- `Status: ${colorize8("bold", isEnabled ? colorize8("green", "Enabled") : colorize8("red", "Disabled"))}`
3826
+ `Status: ${colorize10("bold", isEnabled ? colorize10("green", "Enabled") : colorize10("red", "Disabled"))}`
3290
3827
  );
3291
3828
  if (!isEnabled) {
3292
3829
  consola.info(
@@ -3295,9 +3832,649 @@ Examples:
3295
3832
  }
3296
3833
  });
3297
3834
 
3835
+ // src/cli/commands/upgrade.ts
3836
+ import { Command as Command12, Option as Option10 } from "@commander-js/extra-typings";
3837
+ import { confirm as confirm6 } from "@inquirer/prompts";
3838
+ import { execa as execa3 } from "execa";
3839
+ import {
3840
+ access as access2,
3841
+ copyFile as copyFile2,
3842
+ cp as cp2,
3843
+ mkdir,
3844
+ mkdtemp,
3845
+ readdir as readdir2,
3846
+ readFile as readFile2,
3847
+ rename,
3848
+ rm as rm2,
3849
+ writeFile as writeFile2
3850
+ } from "fs/promises";
3851
+ import { homedir, tmpdir } from "os";
3852
+ import { dirname as dirname4, join as join14, relative as relative2 } from "path";
3853
+ import yoctoSpinner4 from "yocto-spinner";
3854
+ import { z as z15 } from "zod";
3855
+ var CorePackageJson = z15.object({
3856
+ name: z15.string().optional(),
3857
+ version: z15.string(),
3858
+ catalyst: z15.object({ version: z15.string(), ref: z15.string() }).optional()
3859
+ });
3860
+ var DEFAULT_REPOSITORY = "bigcommerce/catalyst";
3861
+ var MOVING_TAGS = /* @__PURE__ */ new Set(["latest", "canary", "alpha"]);
3862
+ var KNOWN_FAMILIES = /* @__PURE__ */ new Set([
3863
+ "@bigcommerce/catalyst-core",
3864
+ "@bigcommerce/catalyst-makeswift",
3865
+ "@bigcommerce/catalyst-b2b-makeswift",
3866
+ "@bigcommerce/catalyst-b2b"
3867
+ ]);
3868
+ var CACHE_DIR = join14(homedir(), ".cache", "catalyst-cli", "cores");
3869
+ var pathExists = (p) => access2(p).then(() => true).catch(() => false);
3870
+ async function listFiles(dir, prefix = "") {
3871
+ const entries = await readdir2(dir, { withFileTypes: true }).catch(() => []);
3872
+ const nested = await Promise.all(
3873
+ entries.map((entry) => {
3874
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
3875
+ return entry.isDirectory() ? listFiles(join14(dir, entry.name), rel) : Promise.resolve([rel]);
3876
+ })
3877
+ );
3878
+ return nested.flat();
3879
+ }
3880
+ async function filesEqual(a, b) {
3881
+ const [ba, bb] = await Promise.all([
3882
+ readFile2(a).catch(() => null),
3883
+ readFile2(b).catch(() => null)
3884
+ ]);
3885
+ return ba !== null && bb !== null && ba.equals(bb);
3886
+ }
3887
+ async function computeBaseSimilarity(baseDir, destDir) {
3888
+ const baseFiles = await listFiles(baseDir);
3889
+ if (baseFiles.length === 0) return 0;
3890
+ const matches = await Promise.all(
3891
+ baseFiles.map((rel) => filesEqual(join14(baseDir, rel), join14(destDir, rel)))
3892
+ );
3893
+ return matches.filter(Boolean).length / baseFiles.length;
3894
+ }
3895
+ async function isBinary(p) {
3896
+ const buf = await readFile2(p).catch(() => null);
3897
+ if (!buf) return false;
3898
+ return buf.subarray(0, 8e3).includes(0);
3899
+ }
3900
+ async function copyInto(src, dest) {
3901
+ await mkdir(dirname4(dest), { recursive: true });
3902
+ await copyFile2(src, dest);
3903
+ }
3904
+ function parseRef(ref) {
3905
+ const lastAt = ref.lastIndexOf("@");
3906
+ if (lastAt <= 0) throw new Error(`Cannot parse ref "${ref}" \u2014 expected <package>@<version>.`);
3907
+ return { packageName: ref.slice(0, lastAt), version: ref.slice(lastAt + 1) };
3908
+ }
3909
+ async function readResolvedVersion(coreDir) {
3910
+ const raw = await readFile2(join14(coreDir, "package.json"), "utf-8");
3911
+ const pkg = CorePackageJson.parse(JSON.parse(raw));
3912
+ return pkg.catalyst?.version ?? pkg.version;
3913
+ }
3914
+ async function mergeFile(oursPath, basePath, theirsPath) {
3915
+ const merged = await execa3(
3916
+ "git",
3917
+ [
3918
+ "merge-file",
3919
+ "-p",
3920
+ "-L",
3921
+ "ours",
3922
+ "-L",
3923
+ "base",
3924
+ "-L",
3925
+ "theirs",
3926
+ oursPath,
3927
+ basePath,
3928
+ theirsPath
3929
+ ],
3930
+ { reject: false, stripFinalNewline: false }
3931
+ );
3932
+ if (merged.stderr) throw new Error(`git merge-file failed: ${merged.stderr}`);
3933
+ await writeFile2(oursPath, merged.stdout);
3934
+ return (merged.exitCode ?? 0) > 0;
3935
+ }
3936
+ async function mergeModified(rel, baseDir, theirsDir, catalystRoot) {
3937
+ const basePath = join14(baseDir, rel);
3938
+ const theirsPath = join14(theirsDir, rel);
3939
+ const oursPath = join14(catalystRoot, rel);
3940
+ if (await filesEqual(basePath, theirsPath)) return null;
3941
+ if (!await pathExists(oursPath)) {
3942
+ await copyInto(theirsPath, oursPath);
3943
+ return "conflicted";
3944
+ }
3945
+ if (await isBinary(basePath) || await isBinary(theirsPath) || await isBinary(oursPath)) {
3946
+ if (await filesEqual(oursPath, basePath)) {
3947
+ await copyInto(theirsPath, oursPath);
3948
+ return "applied";
3949
+ }
3950
+ return "conflicted";
3951
+ }
3952
+ return await mergeFile(oursPath, basePath, theirsPath) ? "conflicted" : "applied";
3953
+ }
3954
+ async function mergeAdded(rel, theirsDir, catalystRoot, emptyFile) {
3955
+ const theirsPath = join14(theirsDir, rel);
3956
+ const oursPath = join14(catalystRoot, rel);
3957
+ if (!await pathExists(oursPath)) {
3958
+ await copyInto(theirsPath, oursPath);
3959
+ return "added";
3960
+ }
3961
+ if (await filesEqual(oursPath, theirsPath)) return "applied";
3962
+ if (await isBinary(oursPath) || await isBinary(theirsPath)) return "conflicted";
3963
+ const hadConflict = await mergeFile(oursPath, emptyFile, theirsPath);
3964
+ return hadConflict ? "conflicted" : "applied";
3965
+ }
3966
+ async function mergeDeleted(rel, baseDir, catalystRoot) {
3967
+ const basePath = join14(baseDir, rel);
3968
+ const oursPath = join14(catalystRoot, rel);
3969
+ if (!await pathExists(oursPath)) return null;
3970
+ if (await filesEqual(oursPath, basePath)) {
3971
+ await rm2(oursPath, { force: true });
3972
+ return "deleted";
3973
+ }
3974
+ return "conflicted";
3975
+ }
3976
+ async function mergeCorePerFile(baseDir, theirsDir, catalystRoot, emptyFile) {
3977
+ const baseFiles = new Set(await listFiles(baseDir));
3978
+ const theirsFiles = new Set(await listFiles(theirsDir));
3979
+ const result = { applied: [], added: [], deleted: [], conflicted: [] };
3980
+ const decide = async (rel) => {
3981
+ const inBase = baseFiles.has(rel);
3982
+ const inTheirs = theirsFiles.has(rel);
3983
+ let outcome;
3984
+ if (inBase && inTheirs) {
3985
+ outcome = await mergeModified(rel, baseDir, theirsDir, catalystRoot);
3986
+ } else if (inTheirs) {
3987
+ outcome = await mergeAdded(rel, theirsDir, catalystRoot, emptyFile);
3988
+ } else {
3989
+ outcome = await mergeDeleted(rel, baseDir, catalystRoot);
3990
+ }
3991
+ if (outcome) result[outcome].push(rel);
3992
+ };
3993
+ await Promise.all([.../* @__PURE__ */ new Set([...baseFiles, ...theirsFiles])].map(decide));
3994
+ return result;
3995
+ }
3996
+ async function gitSupportsMergeTree() {
3997
+ try {
3998
+ const { stdout } = await execa3("git", ["--version"]);
3999
+ const match = /(\d+)\.(\d+)/.exec(stdout);
4000
+ if (!match) return false;
4001
+ const [major, minor] = [Number(match[1]), Number(match[2])];
4002
+ return major > 2 || major === 2 && minor >= 38;
4003
+ } catch {
4004
+ return false;
4005
+ }
4006
+ }
4007
+ async function resolveStrategy(strategy) {
4008
+ if (strategy !== "auto") return strategy;
4009
+ if (await gitSupportsMergeTree()) return "tree";
4010
+ consola.warn("git < 2.38 \u2014 using the per-file merge engine (no `git merge-tree`).");
4011
+ return "per-file";
4012
+ }
4013
+ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
4014
+ const scratch = await mkdtemp(join14(tmpdir(), "catalyst-merge-"));
4015
+ try {
4016
+ await execa3("git", ["init", "-q", scratch]);
4017
+ await execa3("git", ["config", "core.autocrlf", "false"], {
4018
+ env: { ...process.env, GIT_DIR: join14(scratch, ".git") }
4019
+ });
4020
+ const env2 = {
4021
+ ...process.env,
4022
+ GIT_DIR: join14(scratch, ".git"),
4023
+ GIT_AUTHOR_NAME: "catalyst",
4024
+ GIT_AUTHOR_EMAIL: "catalyst@bigcommerce.com",
4025
+ GIT_COMMITTER_NAME: "catalyst",
4026
+ GIT_COMMITTER_EMAIL: "catalyst@bigcommerce.com"
4027
+ };
4028
+ const writeTree = async (workTree, tag) => {
4029
+ const sideEnv = {
4030
+ ...env2,
4031
+ GIT_WORK_TREE: workTree,
4032
+ GIT_INDEX_FILE: join14(scratch, `index.${tag}`)
4033
+ };
4034
+ await execa3("git", ["add", "-A"], { env: sideEnv });
4035
+ return (await execa3("git", ["write-tree"], { env: sideEnv })).stdout.trim();
4036
+ };
4037
+ const commitTree = (tree, parent) => execa3("git", ["commit-tree", tree, "-m", "x", ...parent ? ["-p", parent] : []], {
4038
+ env: env2
4039
+ }).then((r) => r.stdout.trim());
4040
+ const baseTree = await writeTree(baseDir, "base");
4041
+ const oursTree = await writeTree(catalystRoot, "ours");
4042
+ const theirsTree = await writeTree(theirsDir, "theirs");
4043
+ const baseCommit = await commitTree(baseTree);
4044
+ const [oursCommit, theirsCommit] = await Promise.all([
4045
+ commitTree(oursTree, baseCommit),
4046
+ commitTree(theirsTree, baseCommit)
4047
+ ]);
4048
+ await Promise.all([
4049
+ execa3("git", ["branch", "ours", oursCommit], { env: env2 }),
4050
+ execa3("git", ["branch", "theirs", theirsCommit], { env: env2 })
4051
+ ]);
4052
+ const merge = await execa3(
4053
+ "git",
4054
+ [
4055
+ "merge-tree",
4056
+ "--write-tree",
4057
+ "-z",
4058
+ "--name-only",
4059
+ `--merge-base=${baseCommit}`,
4060
+ "ours",
4061
+ "theirs"
4062
+ ],
4063
+ { env: env2, reject: false }
4064
+ );
4065
+ if ((merge.exitCode ?? 0) > 1)
4066
+ throw new Error(merge.stderr || merge.stdout.slice(0, 500) || "git merge-tree failed");
4067
+ const fields = merge.stdout.split("\0");
4068
+ const mergedTree = fields[0];
4069
+ const afterOid = fields.slice(1);
4070
+ const emptyAt = afterOid.indexOf("");
4071
+ const conflicted = afterOid.slice(0, emptyAt === -1 ? afterOid.length : emptyAt);
4072
+ const conflictedSet = new Set(conflicted);
4073
+ const outEnv = {
4074
+ ...env2,
4075
+ GIT_WORK_TREE: catalystRoot,
4076
+ GIT_INDEX_FILE: join14(scratch, "index.out")
4077
+ };
4078
+ await execa3("git", ["read-tree", mergedTree], { env: outEnv });
4079
+ await execa3("git", ["checkout-index", "-a", "-f"], { env: outEnv });
4080
+ const diff = await execa3(
4081
+ "git",
4082
+ ["diff", "-z", "--no-renames", "--name-status", oursTree, mergedTree],
4083
+ { env: env2 }
4084
+ );
4085
+ const tokens = diff.stdout.split("\0");
4086
+ const changes = [];
4087
+ for (let i = 0; i + 1 < tokens.length; i += 2) {
4088
+ const status = tokens[i];
4089
+ const path = tokens[i + 1];
4090
+ if (status && path && !conflictedSet.has(path)) {
4091
+ changes.push({ status, path });
4092
+ }
4093
+ }
4094
+ const deleted = changes.filter((change) => change.status === "D").map((change) => change.path);
4095
+ await Promise.all(deleted.map((path) => rm2(join14(catalystRoot, path), { force: true })));
4096
+ return {
4097
+ applied: changes.filter((change) => change.status !== "A" && change.status !== "D").map((change) => change.path),
4098
+ added: changes.filter((change) => change.status === "A").map((change) => change.path),
4099
+ deleted,
4100
+ conflicted
4101
+ };
4102
+ } finally {
4103
+ await rm2(scratch, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 });
4104
+ }
4105
+ }
4106
+ async function applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stampedPkg) {
4107
+ const toRepoPath = (rel) => relDir === "." ? rel : `${relDir}/${rel}`;
4108
+ const pkgRel = "package.json";
4109
+ const stillConflicted = result.conflicted.filter((rel) => !(stampedPkg && rel === pkgRel));
4110
+ const conflictedRepoPaths = new Set(stillConflicted.map(toRepoPath));
4111
+ const clean = [...result.applied, ...result.added, ...result.deleted].map(toRepoPath);
4112
+ if (stampedPkg) clean.push(toRepoPath(pkgRel));
4113
+ const toStage = [...new Set(clean)].filter((path) => !conflictedRepoPaths.has(path));
4114
+ if (toStage.length) {
4115
+ await execa3("git", ["add", "-A", "--", ...toStage], { cwd: gitRoot });
4116
+ }
4117
+ if (stillConflicted.length === 0) return;
4118
+ const OID = /^[0-9a-f]{40,64}$/;
4119
+ const gitFormat = (await execa3("git", ["rev-parse", "--show-object-format"], { cwd: gitRoot, reject: false })).stdout.trim();
4120
+ const NULL_OID = gitFormat === "sha256" ? "0".repeat(64) : "0".repeat(40);
4121
+ const hashBlob = async (file) => {
4122
+ if (!await pathExists(file)) return null;
4123
+ const oid = (await execa3("git", ["hash-object", "-w", "--", file], { cwd: gitRoot })).stdout.trim();
4124
+ return OID.test(oid) ? oid : null;
4125
+ };
4126
+ const headEntry = async (repoPath) => {
4127
+ const { stdout } = await execa3("git", ["ls-tree", "HEAD", "--", repoPath], {
4128
+ cwd: gitRoot,
4129
+ reject: false
4130
+ });
4131
+ const match = /^(\d{6}) blob ([0-9a-f]+)\t/.exec(stdout);
4132
+ return match ? { mode: match[1], oid: match[2] } : null;
4133
+ };
4134
+ const records = await Promise.all(
4135
+ stillConflicted.map(async (rel) => {
4136
+ const repoPath = toRepoPath(rel);
4137
+ const [base, ours, theirs] = await Promise.all([
4138
+ hashBlob(join14(baseDir, rel)),
4139
+ headEntry(repoPath),
4140
+ hashBlob(join14(theirsDir, rel))
4141
+ ]);
4142
+ return [
4143
+ `0 ${NULL_OID} ${repoPath}`,
4144
+ ...base ? [`100644 ${base} 1 ${repoPath}`] : [],
4145
+ ...ours ? [`${ours.mode} ${ours.oid} 2 ${repoPath}`] : [],
4146
+ ...theirs ? [`100644 ${theirs} 3 ${repoPath}`] : []
4147
+ ].join("\n");
4148
+ })
4149
+ );
4150
+ await execa3("git", ["update-index", "--index-info"], {
4151
+ cwd: gitRoot,
4152
+ input: `${records.join("\n")}
4153
+ `
4154
+ });
4155
+ }
4156
+ async function resolveProject2(cwd) {
4157
+ let gitRoot;
4158
+ try {
4159
+ gitRoot = (await execa3("git", ["rev-parse", "--show-toplevel"], { cwd })).stdout.trim();
4160
+ } catch {
4161
+ return null;
4162
+ }
4163
+ const candidateDirs = [.../* @__PURE__ */ new Set([join14(gitRoot, "core"), gitRoot, join14(cwd, "core"), cwd])];
4164
+ const parsed = (await Promise.all(
4165
+ candidateDirs.map(async (dir) => {
4166
+ const pkgPath = join14(dir, "package.json");
4167
+ const raw = await readFile2(pkgPath, "utf-8").catch(() => null);
4168
+ if (!raw) return null;
4169
+ let pkgJson;
4170
+ try {
4171
+ pkgJson = JSON.parse(raw);
4172
+ } catch {
4173
+ return null;
4174
+ }
4175
+ const result = CorePackageJson.safeParse(pkgJson);
4176
+ return result.success ? { dir, pkgPath, raw, pkg: result.data } : null;
4177
+ })
4178
+ )).filter((entry) => entry !== null);
4179
+ if (parsed.length === 0) return null;
4180
+ const chosen = parsed.find((p) => p.pkg.catalyst?.ref) ?? parsed.find((p) => p.pkg.name !== void 0 && KNOWN_FAMILIES.has(p.pkg.name)) ?? parsed[0];
4181
+ return {
4182
+ gitRoot,
4183
+ catalystRoot: chosen.dir,
4184
+ relDir: relative2(gitRoot, chosen.dir) || ".",
4185
+ pkgPath: chosen.pkgPath,
4186
+ rawContent: chosen.raw,
4187
+ pkg: chosen.pkg
4188
+ };
4189
+ }
4190
+ async function downloadCore(repository, ref, destDir, token) {
4191
+ const cacheable = !MOVING_TAGS.has(parseRef(ref).version);
4192
+ const cacheKey = `${repository.replace(/[^a-zA-Z0-9._-]/g, "_")}__${ref.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
4193
+ const cachePath = join14(CACHE_DIR, cacheKey);
4194
+ if (cacheable && await pathExists(cachePath) && await pathExists(join14(cachePath, "package.json"))) {
4195
+ await cp2(cachePath, destDir, { recursive: true });
4196
+ return;
4197
+ }
4198
+ const url = `https://api.github.com/repos/${repository}/tarball/${encodeURIComponent(ref)}`;
4199
+ const headers = {
4200
+ "User-Agent": "catalyst-cli",
4201
+ Accept: "application/vnd.github+json"
4202
+ };
4203
+ if (token) headers.Authorization = `Bearer ${token}`;
4204
+ const res = await fetch(url, { headers });
4205
+ if (!res.ok) {
4206
+ let hint = "";
4207
+ if (res.status === 404) hint = ` \u2014 tag "${ref}" not found in ${repository}`;
4208
+ if (res.status === 403) hint = " \u2014 GitHub rate limit hit; set GITHUB_TOKEN to raise it";
4209
+ throw new Error(`GitHub returned ${res.status} for ${ref}${hint}`);
4210
+ }
4211
+ const tarballPath = `${destDir}.tgz`;
4212
+ const rawDir = `${destDir}.raw`;
4213
+ await writeFile2(tarballPath, Buffer.from(await res.arrayBuffer()));
4214
+ await mkdir(rawDir, { recursive: true });
4215
+ await execa3("tar", ["-xzf", tarballPath, "-C", rawDir, "--strip-components=1"]);
4216
+ const coreDir = join14(rawDir, "core");
4217
+ const sourceDir = await pathExists(coreDir) ? coreDir : rawDir;
4218
+ await rename(sourceDir, destDir);
4219
+ await rm2(tarballPath, { force: true });
4220
+ await rm2(rawDir, { recursive: true, force: true });
4221
+ if (cacheable) {
4222
+ await mkdir(CACHE_DIR, { recursive: true });
4223
+ await cp2(destDir, cachePath, { recursive: true }).catch(() => {
4224
+ });
4225
+ }
4226
+ }
4227
+ async function resolveBaseRef(project2, fromOption, assumeYes) {
4228
+ if (project2.pkg.catalyst?.ref) return project2.pkg.catalyst.ref;
4229
+ if (fromOption) return fromOption;
4230
+ const family = project2.pkg.name && KNOWN_FAMILIES.has(project2.pkg.name) ? project2.pkg.name : "@bigcommerce/catalyst-core";
4231
+ const guess = `${family}@${project2.pkg.version}`;
4232
+ consola.warn(`No \`catalyst.ref\` found in package.json. Inferred base: ${guess}`);
4233
+ if (assumeYes) return guess;
4234
+ const ok = await confirm6({ message: `Upgrade from ${guess}?`, default: true }).catch(() => false);
4235
+ if (!ok) {
4236
+ consola.info("Re-run with `--from <ref>` to set the base version explicitly.");
4237
+ return null;
4238
+ }
4239
+ return guess;
4240
+ }
4241
+ function printSummary(result, relDir, stampedPkg) {
4242
+ const unresolved = result.conflicted.filter((f) => !(stampedPkg && f === "package.json"));
4243
+ const parts = [
4244
+ `${result.applied.length} updated`,
4245
+ `${result.added.length} added`,
4246
+ `${result.deleted.length} removed`
4247
+ ];
4248
+ if (unresolved.length) parts.push(`${unresolved.length} with conflicts`);
4249
+ consola.log(`
4250
+ ${parts.join(", ")}`);
4251
+ if (unresolved.length) {
4252
+ const files = unresolved.map((f) => ` ${f}`).join("\n");
4253
+ consola.warn(
4254
+ `
4255
+ Staged the clean changes. ${unresolved.length} file(s) need conflict resolution (the <<<ours/===/theirs>>> markers):
4256
+ ${files}
4257
+
4258
+ Resolve them, then: git add ${relDir} && git commit`
4259
+ );
4260
+ } else {
4261
+ consola.success("Staged all changes \u2014 review with `git diff --cached`, then commit.");
4262
+ }
4263
+ }
4264
+ var upgrade = new Command12("upgrade").configureHelp({ showGlobalOptions: true }).aliases([
4265
+ "up",
4266
+ // These are hidden aliases
4267
+ "upgrayedd",
4268
+ "ugrad"
4269
+ ]).description("Upgrade your Catalyst project to a newer version by applying a 3-way merge.").argument("[version]", "Target version to upgrade to (default: latest)").addOption(
4270
+ new Option10(
4271
+ "--ref <ref>",
4272
+ "Full git tag to upgrade to, e.g. an integration family like @bigcommerce/catalyst-makeswift@1.7.0. Overrides [version]."
4273
+ )
4274
+ ).addOption(new Option10("--from <ref>", "Base ref to upgrade from (when catalyst.ref is missing)")).option("--dry-run", "Generate and display the diff without applying it").option("--yes", "Skip confirmation prompts (e.g. inferred base ref)").addOption(
4275
+ new Option10(
4276
+ "--strategy <strategy>",
4277
+ "Merge engine: tree (git merge-tree, full fidelity), per-file (git merge-file, no-history fallback), or auto (tree when git >= 2.38, else per-file)"
4278
+ ).choices(["auto", "tree", "per-file"]).default("auto").hideHelp()
4279
+ ).addOption(
4280
+ new Option10("--repository <owner/repo>", "GitHub repository to pull versions from").default(
4281
+ DEFAULT_REPOSITORY
4282
+ )
4283
+ ).addHelpText(
4284
+ "after",
4285
+ `
4286
+ Examples:
4287
+ # Upgrade to the latest Catalyst version
4288
+ $ catalyst upgrade
4289
+
4290
+ # Upgrade to a specific version
4291
+ $ catalyst upgrade 1.8.0
4292
+
4293
+ # Preview what would change without applying
4294
+ $ catalyst upgrade --dry-run
4295
+
4296
+ # Upgrade to an integration family (makeswift, b2b-makeswift, ...)
4297
+ $ catalyst upgrade --ref @bigcommerce/catalyst-makeswift@1.7.0
4298
+
4299
+ Conflicts are written as standard <<<ours/===/theirs>>> markers \u2014 the upgrade
4300
+ never aborts. Versions are git tags on the repository (not npm). Set GITHUB_TOKEN
4301
+ to raise the GitHub API rate limit.`
4302
+ ).action(async (version2, options) => {
4303
+ const project2 = await resolveProject2(process.cwd());
4304
+ if (!project2) {
4305
+ consola.error(
4306
+ "Run `catalyst upgrade` from inside a Catalyst git repository (flat core/ repo or a project containing core/)."
4307
+ );
4308
+ process.exit(1);
4309
+ }
4310
+ const { gitRoot, catalystRoot, relDir, pkgPath } = project2;
4311
+ const baseRef = await resolveBaseRef(project2, options.from, options.yes ?? false);
4312
+ if (!baseRef) process.exit(1);
4313
+ let basePackage;
4314
+ let upstreamRef;
4315
+ let upstreamTagVersion;
4316
+ try {
4317
+ ({ packageName: basePackage } = parseRef(baseRef));
4318
+ upstreamRef = options.ref ?? `${basePackage}@${version2 ?? "latest"}`;
4319
+ ({ version: upstreamTagVersion } = parseRef(upstreamRef));
4320
+ } catch {
4321
+ consola.error(
4322
+ `Invalid ref format \u2014 expected <package>@<version> (e.g. @bigcommerce/catalyst-core@1.7.0). Got: "${baseRef}"`
4323
+ );
4324
+ process.exit(1);
4325
+ }
4326
+ if (upstreamRef === baseRef) {
4327
+ consola.success("Already up to date.");
4328
+ return;
4329
+ }
4330
+ if (!options.dryRun) {
4331
+ const statusArgs = relDir === "." ? [] : ["--", relDir];
4332
+ const status = await execa3("git", ["status", "--porcelain", ...statusArgs], {
4333
+ cwd: gitRoot,
4334
+ reject: false
4335
+ });
4336
+ if (status.stdout.trim()) {
4337
+ const where = relDir === "." ? "The repo" : `${relDir}/`;
4338
+ consola.error(
4339
+ `${where} has uncommitted changes. Commit or stash them before upgrading, so the upgrade is isolated and reversible:
4340
+ git add ${relDir} && git commit -m "snapshot before upgrade"
4341
+ # or: git stash`
4342
+ );
4343
+ process.exit(1);
4344
+ }
4345
+ }
4346
+ if (!project2.pkg.catalyst?.ref && !options.from && !options.dryRun) {
4347
+ const { version: baseVersion } = parseRef(baseRef);
4348
+ const rawPkg = await readFile2(pkgPath, "utf-8");
4349
+ const parsedPkg = z15.record(z15.string(), z15.unknown()).parse(JSON.parse(rawPkg));
4350
+ parsedPkg.catalyst = { version: baseVersion, ref: baseRef };
4351
+ await writeFile2(pkgPath, `${JSON.stringify(parsedPkg, null, 2)}
4352
+ `);
4353
+ await execa3("git", ["add", "--", pkgPath], { cwd: gitRoot });
4354
+ consola.success(`Added catalyst.ref \u2192 ${baseRef}`);
4355
+ }
4356
+ const token = process.env.GITHUB_TOKEN;
4357
+ const tmpDir = await mkdtemp(join14(tmpdir(), "catalyst-upgrade-"));
4358
+ try {
4359
+ const baseDir = join14(tmpDir, "base");
4360
+ const theirsDir = join14(tmpDir, "theirs");
4361
+ const emptyFile = join14(tmpDir, ".empty");
4362
+ await writeFile2(emptyFile, "");
4363
+ const downloadSpinner = yoctoSpinner4().start(`Downloading ${baseRef} and ${upstreamRef}...`);
4364
+ try {
4365
+ await Promise.all([
4366
+ downloadCore(options.repository, baseRef, baseDir, token),
4367
+ downloadCore(options.repository, upstreamRef, theirsDir, token)
4368
+ ]);
4369
+ downloadSpinner.success("Downloaded both versions.");
4370
+ } catch (err) {
4371
+ downloadSpinner.error("Download failed");
4372
+ throw err;
4373
+ }
4374
+ if (!project2.pkg.catalyst?.ref && !options.from) {
4375
+ const similarity = await computeBaseSimilarity(baseDir, catalystRoot);
4376
+ const pct = Math.round(similarity * 100);
4377
+ if (similarity < 0.5) {
4378
+ consola.warn(
4379
+ `Low confidence in inferred base ${baseRef} (${pct}% of base files match your project). If the merge result looks off, re-run with \`--from <correct-version>\` to set the base explicitly.`
4380
+ );
4381
+ }
4382
+ }
4383
+ const resolvedVersion = MOVING_TAGS.has(upstreamTagVersion) ? await readResolvedVersion(theirsDir) : upstreamTagVersion;
4384
+ const newRef = MOVING_TAGS.has(upstreamTagVersion) ? `${basePackage}@${resolvedVersion}` : upstreamRef;
4385
+ if (options.dryRun) {
4386
+ const diffSpinner = yoctoSpinner4().start("Generating diff...");
4387
+ const diff = await execa3(
4388
+ "git",
4389
+ ["diff", "--no-index", "--binary", "--diff-algorithm=histogram", "base", "theirs"],
4390
+ { cwd: tmpDir, reject: false, stripFinalNewline: false }
4391
+ );
4392
+ if ((diff.exitCode ?? 0) > 1) {
4393
+ diffSpinner.error("Failed to generate diff");
4394
+ throw new Error(diff.stderr);
4395
+ }
4396
+ if (!diff.stdout.trim()) {
4397
+ diffSpinner.success("No differences between versions \u2014 already up to date.");
4398
+ return;
4399
+ }
4400
+ const fileCount = (diff.stdout.match(/^diff --git /gm) ?? []).length;
4401
+ diffSpinner.success(`Diff ready \u2014 ${fileCount} file(s) affected.`);
4402
+ consola.log("\nDiff preview (--dry-run, not applied):\n");
4403
+ consola.log(diff.stdout);
4404
+ return;
4405
+ }
4406
+ const strategy = await resolveStrategy(options.strategy);
4407
+ const mergeSpinner = yoctoSpinner4().start(`Merging changes (${strategy})...`);
4408
+ const result = strategy === "tree" ? await mergeCoreTree(baseDir, theirsDir, catalystRoot) : await mergeCorePerFile(baseDir, theirsDir, catalystRoot, emptyFile);
4409
+ const total = result.applied.length + result.added.length + result.deleted.length + result.conflicted.length;
4410
+ if (total === 0) {
4411
+ mergeSpinner.success("No differences between versions \u2014 already up to date.");
4412
+ return;
4413
+ }
4414
+ if (result.conflicted.length) {
4415
+ mergeSpinner.warning("Merged with conflicts \u2014 resolve the markers, then commit.");
4416
+ } else {
4417
+ mergeSpinner.success("Merged cleanly.");
4418
+ }
4419
+ const patchedRaw = await readFile2(pkgPath, "utf-8");
4420
+ let stampedPkg = false;
4421
+ try {
4422
+ const patchedPkg = z15.record(z15.string(), z15.unknown()).parse(JSON.parse(patchedRaw));
4423
+ patchedPkg.catalyst = { version: resolvedVersion, ref: newRef };
4424
+ await writeFile2(pkgPath, `${JSON.stringify(patchedPkg, null, 2)}
4425
+ `);
4426
+ stampedPkg = true;
4427
+ consola.success(`catalyst.ref updated \u2192 ${newRef}`);
4428
+ } catch {
4429
+ const catalystReplacement = JSON.stringify(
4430
+ { version: resolvedVersion, ref: newRef },
4431
+ null,
4432
+ 2
4433
+ ).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
4434
+ const updated = patchedRaw.replace(
4435
+ /"catalyst":\s*\{[^}]*\}/,
4436
+ () => `"catalyst": ${catalystReplacement}`
4437
+ );
4438
+ if (updated !== patchedRaw) {
4439
+ await writeFile2(pkgPath, updated);
4440
+ consola.success(`catalyst.ref updated \u2192 ${newRef}`);
4441
+ consola.info(
4442
+ "package.json still has conflicts in other sections \u2014 resolve them, then: git add package.json"
4443
+ );
4444
+ } else {
4445
+ consola.warn(
4446
+ `package.json has conflicts \u2014 after resolving, add:
4447
+ "catalyst": { "version": "${resolvedVersion}", "ref": "${newRef}" }`
4448
+ );
4449
+ }
4450
+ }
4451
+ try {
4452
+ await applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stampedPkg);
4453
+ } catch (err) {
4454
+ consola.warn(
4455
+ `Couldn't auto-stage the changes (${err instanceof Error ? err.message : String(err)}). Your files are merged on disk \u2014 run \`git add ${relDir}\` yourself.`
4456
+ );
4457
+ }
4458
+ const gitVersion = await execa3("git", ["--version"]).then((r) => r.stdout.trim()).catch(() => "unknown");
4459
+ await getTelemetry().track("upgrade", {
4460
+ strategy,
4461
+ gitVersion,
4462
+ dryRun: Boolean(options.dryRun),
4463
+ applied: result.applied.length,
4464
+ added: result.added.length,
4465
+ deleted: result.deleted.length,
4466
+ conflicts: result.conflicted.length,
4467
+ hasConflicts: result.conflicted.length > 0
4468
+ });
4469
+ printSummary(result, relDir, stampedPkg);
4470
+ } finally {
4471
+ await rm2(tmpDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 });
4472
+ }
4473
+ });
4474
+
3298
4475
  // src/cli/commands/version.ts
3299
- import { Command as Command11 } from "commander";
3300
- var version = new Command11("version").configureHelp({ showGlobalOptions: true }).description("Display detailed version information.").addHelpText(
4476
+ import { Command as Command13 } from "commander";
4477
+ var version = new Command13("version").configureHelp({ showGlobalOptions: true }).description("Display detailed version information.").addHelpText(
3301
4478
  "after",
3302
4479
  `
3303
4480
  Example:
@@ -3359,19 +4536,19 @@ var telemetryPostHook = async () => {
3359
4536
 
3360
4537
  // src/cli/program.ts
3361
4538
  var defaultEnvPath = resolve(process.cwd(), ".env.local");
3362
- if (existsSync5(defaultEnvPath)) {
4539
+ if (existsSync4(defaultEnvPath)) {
3363
4540
  config({ path: defaultEnvPath });
3364
4541
  }
3365
4542
  if (!process.env.CATALYST_STORE_HASH && process.env.BIGCOMMERCE_STORE_HASH) {
3366
4543
  process.env.CATALYST_STORE_HASH = process.env.BIGCOMMERCE_STORE_HASH;
3367
4544
  }
3368
- var program = new Command12();
3369
- consola.log(colorize9("cyanBright", `\u25E2 ${package_default.name} v${package_default.version}
4545
+ var program = new Command14();
4546
+ consola.log(colorize11("cyanBright", `\u25E2 ${package_default.name} v${package_default.version}
3370
4547
  `));
3371
4548
  program.name(package_default.name).version(package_default.version).summary("CLI tool for Catalyst development").description(
3372
4549
  "CLI tool for Catalyst development.\n\nConfiguration priority: flags > env file (--env-path) > process.env > .env.local (auto-loaded from cwd) > .bigcommerce/project.json.\n\nCATALYST_STORE_HASH falls back to BIGCOMMERCE_STORE_HASH if unset.\n\nRun `catalyst <command> --help` for details on a specific command."
3373
4550
  ).configureHelp({ showGlobalOptions: true }).addOption(
3374
- new Option9(
4551
+ new Option11(
3375
4552
  "--env-path <path>",
3376
4553
  "Path to environment file to load (relative to current working directory)"
3377
4554
  // We are using argParser, because commander loads in environment variables before executing hooks.
@@ -3387,12 +4564,12 @@ program.name(package_default.name).version(package_default.version).summary("CLI
3387
4564
  const message = errCode === "ENOENT" ? `Env file not found: ${envFilePath}` : `Failed to load --env-path ${value}: ${result.error.message}`;
3388
4565
  throw new Error(message);
3389
4566
  }
3390
- consola.log(colorize9("cyanBright", `Loaded environment variables from ${envFilePath}
4567
+ consola.log(colorize11("cyanBright", `Loaded environment variables from ${envFilePath}
3391
4568
  `));
3392
4569
  }
3393
4570
  return value;
3394
4571
  })
3395
- ).addCommand(version).addCommand(create).addCommand(start).addCommand(build).addCommand(deploy).addCommand(logs).addCommand(project).addCommand(env).addCommand(channel).addCommand(auth).addCommand(telemetry2).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
4572
+ ).addCommand(version).addCommand(create).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(project).addCommand(env).addCommand(channel).addCommand(auth).addCommand(upgrade).addCommand(telemetry2).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
3396
4573
 
3397
4574
  // src/cli/index.ts
3398
4575
  var handleFatalError = async (error) => {