@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/README.md +1 -0
- package/dist/cli.js +1478 -301
- package/dist/cli.js.map +1 -1
- package/package.json +7 -1
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
|
|
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: "
|
|
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
|
|
199
|
-
import { Command as
|
|
200
|
-
import { colorize as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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, "
|
|
1173
|
-
const middlewarePath = join5(projectDir, "
|
|
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, "
|
|
1164
|
+
const instrumentationPath = join5(projectDir, "instrumentation.ts");
|
|
1181
1165
|
if (!existsSync2(instrumentationPath)) return;
|
|
1182
1166
|
if (!process.stdin.isTTY) {
|
|
1183
1167
|
consola.warn(
|
|
1184
|
-
"
|
|
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
|
|
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
|
|
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
|
|
1200
|
-
const corePackageJsonPath = join5(projectDir, "
|
|
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
|
|
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, "
|
|
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, "
|
|
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
|
|
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
|
|
1695
|
+
import { join as join11 } from "path";
|
|
1713
1696
|
|
|
1714
|
-
// src/cli/lib/
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
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/
|
|
1781
|
-
import {
|
|
1782
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1820
|
-
projectDir
|
|
1821
|
-
ghRef,
|
|
1822
|
-
resetMain = false
|
|
1754
|
+
ref,
|
|
1755
|
+
projectDir
|
|
1823
1756
|
}) => {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
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
|
|
1843
|
-
var installDependencies = async (projectDir) => {
|
|
1844
|
-
const spinner =
|
|
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
|
|
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/
|
|
1910
|
-
import {
|
|
1911
|
-
import {
|
|
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
|
-
|
|
1919
|
-
|
|
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 =
|
|
1924
|
-
const pkg =
|
|
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
|
|
1953
|
+
import { join as join10 } from "path";
|
|
1941
1954
|
var writeEnv = (projectDir, envVars) => {
|
|
1942
1955
|
outputFileSync2(
|
|
1943
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
"
|
|
2102
|
+
"Extract a specific ref (tag, branch, or commit) from the source repository",
|
|
2098
2103
|
"@bigcommerce/catalyst-core@latest"
|
|
2099
|
-
).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
|
-
|
|
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
|
-
|
|
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}
|
|
2259
|
+
const steps = [`cd ${projectName} && ${packageManager} run dev`];
|
|
2253
2260
|
if (useCommerceHosting) {
|
|
2254
2261
|
steps.push(
|
|
2255
|
-
`Run 'cd ${projectName}
|
|
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 {
|
|
2272
|
-
import
|
|
2273
|
-
import { z as
|
|
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 =
|
|
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 =
|
|
2311
|
-
data:
|
|
2312
|
-
upload_url:
|
|
2313
|
-
upload_uuid:
|
|
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 =
|
|
2317
|
-
data:
|
|
2318
|
-
deployment_uuid:
|
|
2323
|
+
var CreateDeploymentSchema = z11.object({
|
|
2324
|
+
data: z11.object({
|
|
2325
|
+
deployment_uuid: z11.uuid()
|
|
2319
2326
|
})
|
|
2320
2327
|
});
|
|
2321
|
-
var ProjectSchema =
|
|
2322
|
-
data:
|
|
2323
|
-
|
|
2324
|
-
uuid:
|
|
2325
|
-
name:
|
|
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 =
|
|
2330
|
-
deployment_uuid:
|
|
2331
|
-
deployment_status:
|
|
2332
|
-
event:
|
|
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:
|
|
2341
|
+
progress: z11.number()
|
|
2335
2342
|
}).nullable(),
|
|
2336
|
-
deployment_hostnames:
|
|
2337
|
-
error:
|
|
2338
|
-
code:
|
|
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 =
|
|
2344
|
-
const distDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
2614
|
+
await cleanupCloudflareIncompatibilities(process.cwd());
|
|
2608
2615
|
}
|
|
2609
2616
|
if (options.prebuilt) {
|
|
2610
|
-
const distDir =
|
|
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
|
|
3135
|
+
import { Command as Command7 } from "commander";
|
|
2680
3136
|
var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
2681
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
3204
|
+
).addCommand(add2).addCommand(remove2).addCommand(list2);
|
|
2749
3205
|
|
|
2750
3206
|
// src/cli/commands/logs.ts
|
|
2751
|
-
import { Command as
|
|
2752
|
-
import { colorize as
|
|
2753
|
-
import { z as
|
|
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
|
|
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 =
|
|
2765
|
-
uuid:
|
|
2766
|
-
project_uuid:
|
|
2767
|
-
request:
|
|
2768
|
-
method:
|
|
2769
|
-
url:
|
|
2770
|
-
status_code:
|
|
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:
|
|
2773
|
-
|
|
2774
|
-
timestamp:
|
|
2775
|
-
level:
|
|
2776
|
-
messages:
|
|
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:
|
|
2780
|
-
timestamp:
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
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
|
|
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
|
|
2977
|
-
import { Command as
|
|
2978
|
-
import { colorize as
|
|
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 =
|
|
3518
|
+
const projectDir = process.cwd();
|
|
2982
3519
|
if (getProjectState().isTransformed) {
|
|
2983
3520
|
await cleanupCloudflareIncompatibilities(projectDir);
|
|
2984
3521
|
return;
|
|
2985
3522
|
}
|
|
2986
|
-
const shouldSetup = await
|
|
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
|
|
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 ? ` ${
|
|
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) => ` ${
|
|
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
|
|
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
|
|
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
|
|
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} ${
|
|
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
|
|
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
|
|
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
|
|
3736
|
+
import { Command as Command10 } from "commander";
|
|
3200
3737
|
import { execa as execa2 } from "execa";
|
|
3201
|
-
import { existsSync as
|
|
3202
|
-
import { join as
|
|
3203
|
-
var start = new
|
|
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 =
|
|
3221
|
-
const devVars =
|
|
3222
|
-
if (
|
|
3223
|
-
if (!
|
|
3224
|
-
const target = relative(
|
|
3225
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
3255
|
-
import { colorize as
|
|
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
|
|
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
|
|
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: ${
|
|
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
|
|
3300
|
-
var version = new
|
|
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 (
|
|
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
|
|
3369
|
-
consola.log(
|
|
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
|
|
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(
|
|
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) => {
|