@bigcommerce/catalyst 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ import { randomBytes, randomUUID } from "crypto";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@bigcommerce/catalyst",
28
- version: "1.3.0",
28
+ version: "1.4.0",
29
29
  type: "module",
30
30
  repository: {
31
31
  type: "git",
@@ -49,7 +49,7 @@ var package_default = {
49
49
  build: "tsup"
50
50
  },
51
51
  engines: {
52
- node: "^20.0.0 || ^22.0.0 || ^24.0.0"
52
+ node: "^22.0.0 || ^24.0.0"
53
53
  },
54
54
  dependencies: {
55
55
  "@commander-js/extra-typings": "^14.0.0",
@@ -69,6 +69,7 @@ var package_default = {
69
69
  semver: "^7.7.4",
70
70
  "std-env": "^3.9.0",
71
71
  tar: "^7.5.7",
72
+ tldts: "^7.4.12",
72
73
  "yocto-spinner": "^1.0.0",
73
74
  zod: "^4.0.5"
74
75
  },
@@ -91,7 +92,12 @@ var package_default = {
91
92
  vitest: "^3.2.4"
92
93
  },
93
94
  peerDependencies: {
94
- "@opennextjs/cloudflare": "1.17.3"
95
+ "@opennextjs/cloudflare": "^1.17.3"
96
+ },
97
+ peerDependenciesMeta: {
98
+ "@opennextjs/cloudflare": {
99
+ optional: true
100
+ }
95
101
  }
96
102
  };
97
103
 
@@ -804,7 +810,7 @@ var auth = new Command("auth").configureHelp({ showGlobalOptions: true }).descri
804
810
  import { Command as Command2, InvalidArgumentError, Option as Option2 } from "commander";
805
811
  import { execa as execa2 } from "execa";
806
812
  import { copyFile, cp, rm, writeFile } from "fs/promises";
807
- import { join as join4 } from "path";
813
+ import { join as join6 } from "path";
808
814
  import { valid as validSemver } from "semver";
809
815
 
810
816
  // src/cli/lib/build-env.ts
@@ -839,514 +845,148 @@ function loadBuildEnv({ envPath, cwd = process.cwd() }) {
839
845
  }
840
846
  }
841
847
 
842
- // src/cli/lib/get-module-cli-path.ts
848
+ // src/cli/lib/commerce-hosting.ts
849
+ import { confirm as confirm2, input as input2, select, Separator } from "@inquirer/prompts";
850
+ import { colorize as colorize3 } from "consola/utils";
851
+ import { existsSync as existsSync2, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
843
852
  import { dirname, join as join2 } from "path";
844
- import { fileURLToPath } from "url";
845
- function getModuleCliPath() {
846
- const __filename = fileURLToPath(import.meta.url);
847
- const __dirname = dirname(__filename);
848
- return join2(__dirname, "..");
853
+ import { minVersion, satisfies, lt as semverLt, validRange } from "semver";
854
+ import { z as z6 } from "zod";
855
+
856
+ // src/cli/lib/sort-package-json.ts
857
+ var FIELD_ORDER = [
858
+ "name",
859
+ "description",
860
+ "version",
861
+ "private",
862
+ "engines",
863
+ "scripts",
864
+ "dependencies",
865
+ "devDependencies"
866
+ ];
867
+ function sortPackageJsonFields(pkg) {
868
+ const ordered = {};
869
+ FIELD_ORDER.forEach((field) => {
870
+ if (field in pkg) {
871
+ ordered[field] = pkg[field];
872
+ }
873
+ });
874
+ Object.keys(pkg).forEach((key) => {
875
+ if (!(key in ordered)) {
876
+ ordered[key] = pkg[key];
877
+ }
878
+ });
879
+ return ordered;
849
880
  }
850
881
 
851
- // src/cli/lib/project-state.ts
852
- import { existsSync as existsSync2, readFileSync } from "fs";
853
- import { join as join3 } from "path";
854
- import { z as z6 } from "zod";
855
- var projectJsonSchema = z6.looseObject({
856
- projectUuid: z6.string().optional()
857
- });
858
- var packageJsonSchema = z6.looseObject({
882
+ // src/cli/lib/commerce-hosting.ts
883
+ var OPENNEXT_CLOUDFLARE_VERSION = "1.20.6";
884
+ var OPENNEXT_REQUIRED_NEXT_RANGE = ">=15.5.24 <16 || >=16.3.3";
885
+ var corePackageJsonSchema = z6.looseObject({
859
886
  dependencies: z6.record(z6.string(), z6.string()).optional()
860
887
  });
861
- var safeReadJson = (path) => {
888
+ var projectJsonSchema = z6.looseObject({});
889
+ var readProjectJson = (path) => {
890
+ if (!existsSync2(path)) return {};
862
891
  try {
863
- return JSON.parse(readFileSync(path, "utf-8"));
892
+ const parsed = projectJsonSchema.safeParse(JSON.parse(readFileSync(path, "utf-8")));
893
+ return parsed.success ? parsed.data : {};
864
894
  } catch {
865
- return null;
895
+ return {};
866
896
  }
867
897
  };
868
- function getProjectState(cwd = process.cwd()) {
869
- const projectJson = projectJsonSchema.safeParse(
870
- safeReadJson(join3(cwd, ".bigcommerce", "project.json"))
871
- );
872
- const projectUuid = projectJson.success ? projectJson.data.projectUuid : void 0;
873
- const hasMiddleware = existsSync2(join3(cwd, "middleware.ts"));
874
- const hasProxy = existsSync2(join3(cwd, "proxy.ts"));
875
- const pkgJson = packageJsonSchema.safeParse(safeReadJson(join3(cwd, "package.json")));
876
- const hasOpenNextDep = pkgJson.success ? Boolean(pkgJson.data.dependencies?.["@opennextjs/cloudflare"]) : false;
877
- const isLinked = Boolean(projectUuid);
878
- const isTransformed = hasMiddleware && !hasProxy && hasOpenNextDep;
879
- const isFullySetUp = isLinked && isTransformed;
880
- return {
881
- projectUuid,
882
- hasMiddleware,
883
- hasProxy,
884
- hasOpenNextDep,
885
- isLinked,
886
- isTransformed,
887
- isFullySetUp
888
- };
889
- }
890
-
891
- // src/cli/lib/required-build-env.ts
892
- var REQUIRED_BUILD_ENV_VARS = [
893
- "BIGCOMMERCE_STORE_HASH",
894
- "BIGCOMMERCE_STOREFRONT_TOKEN",
895
- "BIGCOMMERCE_CHANNEL_ID",
896
- "AUTH_SECRET"
897
- ];
898
- var findMissingBuildEnv = (env2 = process.env) => {
899
- return REQUIRED_BUILD_ENV_VARS.filter((name) => {
900
- const value = env2[name];
901
- return value === void 0 || value.trim() === "";
902
- });
898
+ var writeJson = (path, value) => {
899
+ mkdirSync(dirname(path), { recursive: true });
900
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
901
+ `);
903
902
  };
904
- var assertRequiredBuildEnv = (env2 = process.env) => {
905
- const missing = findMissingBuildEnv(env2);
906
- if (missing.length === 0) {
903
+ var convertProxyToMiddleware = (projectDir) => {
904
+ const proxyPath = join2(projectDir, "proxy.ts");
905
+ const middlewarePath = join2(projectDir, "middleware.ts");
906
+ if (!existsSync2(proxyPath)) return;
907
+ const contents = readFileSync(proxyPath, "utf-8").replace("export const proxy", "export const middleware").replace("export const config = {", "export const config = {\n runtime: 'experimental-edge',");
908
+ writeFileSync(middlewarePath, contents);
909
+ unlinkSync(proxyPath);
910
+ };
911
+ var cleanupCloudflareIncompatibilities = async (projectDir) => {
912
+ const instrumentationPath = join2(projectDir, "instrumentation.ts");
913
+ if (!existsSync2(instrumentationPath)) return;
914
+ if (!process.stdin.isTTY) {
915
+ consola.warn(
916
+ "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."
917
+ );
907
918
  return;
908
919
  }
909
- const missingList = missing.map((name) => ` - ${name}`).join("\n");
910
- throw new UserActionableError(
911
- `Missing required environment variable${missing.length === 1 ? "" : "s"} for the build:
912
- ${missingList}
913
-
914
- These are read by the build itself, so they must be set before it runs. The build automatically loads \`.env.local\` and \`.env\` from the current directory (${process.cwd()}) \u2014 add the variables above to one of those files (\`.env.local\` takes precedence).
915
- To load an env file from a different location, pass \`--env-path <path>\` (for example \`--env-path ../.env.local\` when running from a subdirectory).`
916
- );
917
- };
918
-
919
- // src/cli/lib/wrangler-config.ts
920
- function getCompatibilityDate(now = /* @__PURE__ */ new Date()) {
921
- const date = new Date(now);
922
- date.setUTCMonth(date.getUTCMonth() - 1);
923
- return date.toISOString().slice(0, 10);
924
- }
925
- function getWranglerConfig(projectUuid) {
926
- return {
927
- $schema: "node_modules/wrangler/config-schema.json",
928
- main: "../.open-next/worker.js",
929
- name: `project-${projectUuid}`,
930
- compatibility_date: getCompatibilityDate(),
931
- compatibility_flags: ["nodejs_compat", "global_fetch_strictly_public"],
932
- observability: {
933
- enabled: true,
934
- head_sampling_rate: 0.05,
935
- logs: {
936
- enabled: true,
937
- head_sampling_rate: 1,
938
- invocation_logs: false
939
- }
940
- },
941
- assets: {
942
- directory: "../.open-next/assets",
943
- binding: "ASSETS"
944
- },
945
- services: [
946
- {
947
- binding: "WORKER_SELF_REFERENCE",
948
- service: `project-${projectUuid}`
949
- }
950
- ],
951
- r2_buckets: [
952
- {
953
- binding: "NEXT_INC_CACHE_R2_BUCKET",
954
- bucket_name: `project-${projectUuid}`
955
- }
956
- ],
957
- durable_objects: {
958
- bindings: [
959
- {
960
- name: "NEXT_CACHE_DO_QUEUE",
961
- class_name: "DOQueueHandler"
962
- },
963
- {
964
- name: "NEXT_TAG_CACHE_DO_SHARDED",
965
- class_name: "DOShardedTagCache"
966
- },
967
- {
968
- name: "NEXT_CACHE_DO_PURGE",
969
- class_name: "BucketCachePurge"
970
- }
971
- ]
972
- },
973
- migrations: [
974
- {
975
- tag: "v1",
976
- new_sqlite_classes: ["DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"]
977
- }
978
- ]
979
- };
980
- }
981
-
982
- // src/cli/commands/build.ts
983
- var WRANGLER_VERSION = "4.90.0";
984
- var DIST_TAG_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
985
- var parseWranglerVersion = (value) => {
986
- if (!validSemver(value) && !DIST_TAG_PATTERN.test(value)) {
987
- throw new InvalidArgumentError(
988
- `"${value}" is not a valid Wrangler version or dist-tag (e.g. 4.90.0 or latest).`
920
+ const shouldRemove = await confirm2({
921
+ message: 'Catalyst found instrumentation.ts, which is incompatible with the Cloudflare Workers bundle when it uses @vercel/otel (causes "Failed to prepare server" at cold start). Remove it and drop @vercel/otel from package.json?',
922
+ default: true
923
+ });
924
+ if (!shouldRemove) {
925
+ consola.info(
926
+ 'Leaving instrumentation.ts in place. The Cloudflare worker will continue to log "Failed to prepare server" at cold start until this is resolved manually.'
989
927
  );
928
+ return;
990
929
  }
991
- return value;
992
- };
993
- async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERSION) {
994
- assertRequiredBuildEnv();
995
- const coreDir = process.cwd();
996
- const openNextOutDir = join4(coreDir, ".open-next");
997
- const bigcommerceDistDir = join4(coreDir, ".bigcommerce", "dist");
998
- const wranglerConfig = getWranglerConfig(projectUuid);
999
- await rm(bigcommerceDistDir, { recursive: true, force: true });
1000
- consola.start("Copying templates...");
1001
- await copyFile(
1002
- join4(getModuleCliPath(), "templates", "open-next.config.ts"),
1003
- join4(coreDir, ".bigcommerce", "open-next.config.ts")
1004
- );
1005
- await writeFile(
1006
- join4(coreDir, ".bigcommerce", "wrangler.jsonc"),
1007
- JSON.stringify(wranglerConfig, null, 2)
1008
- );
1009
- consola.success("Templates copied");
1010
- consola.start("Building project...");
1011
- await execa2(
1012
- "pnpm",
1013
- [
1014
- "exec",
1015
- "opennextjs-cloudflare",
1016
- "build",
1017
- "--skipWranglerConfigCheck",
1018
- "--openNextConfigPath",
1019
- join4(coreDir, ".bigcommerce", "open-next.config.ts")
1020
- ],
1021
- {
1022
- stdout: ["pipe", "inherit"],
1023
- cwd: coreDir
1024
- }
1025
- );
1026
- await copyFile(
1027
- join4(getModuleCliPath(), "templates", "public_headers"),
1028
- join4(openNextOutDir, "assets", "_headers")
1029
- );
1030
- await execa2(
1031
- "pnpm",
1032
- [
1033
- "dlx",
1034
- `wrangler@${wranglerVersion}`,
1035
- "deploy",
1036
- "--config",
1037
- join4(coreDir, ".bigcommerce", "wrangler.jsonc"),
1038
- "--keep-vars",
1039
- "--outdir",
1040
- bigcommerceDistDir,
1041
- "--dry-run"
1042
- ],
1043
- {
1044
- stdout: ["pipe", "inherit"],
1045
- cwd: coreDir
930
+ unlinkSync(instrumentationPath);
931
+ consola.info("Removed instrumentation.ts (incompatible with Cloudflare Workers).");
932
+ const corePackageJsonPath = join2(projectDir, "package.json");
933
+ if (existsSync2(corePackageJsonPath)) {
934
+ const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync(corePackageJsonPath, "utf-8")));
935
+ if (pkg.dependencies && "@vercel/otel" in pkg.dependencies) {
936
+ const { "@vercel/otel": _removedVercelOtel, ...preservedDeps } = pkg.dependencies;
937
+ pkg.dependencies = preservedDeps;
938
+ writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
939
+ consola.info("Dropped @vercel/otel from package.json.");
1046
940
  }
1047
- );
1048
- consola.success("Project built");
1049
- await cp(join4(openNextOutDir, "assets"), join4(bigcommerceDistDir, "assets"), {
1050
- recursive: true,
1051
- force: true
941
+ }
942
+ };
943
+ var reconcileOpenNextVersion = async (projectDir, packageManager, { canUpgrade }) => {
944
+ const corePackageJsonPath = join2(projectDir, "package.json");
945
+ if (!existsSync2(corePackageJsonPath)) return false;
946
+ const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync(corePackageJsonPath, "utf-8")));
947
+ const range = pkg.dependencies?.["@opennextjs/cloudflare"];
948
+ if (range === void 0) return false;
949
+ if (!validRange(range)) return false;
950
+ if (satisfies(OPENNEXT_CLOUDFLARE_VERSION, range)) return false;
951
+ const current = minVersion(range)?.version;
952
+ if (current === void 0 || !semverLt(current, OPENNEXT_CLOUDFLARE_VERSION)) return false;
953
+ const manualStep = `\`${packageManager} add @opennextjs/cloudflare@${OPENNEXT_CLOUDFLARE_VERSION}\``;
954
+ const behind = `This project pins @opennextjs/cloudflare ${current}, but this CLI targets ${OPENNEXT_CLOUDFLARE_VERSION}.`;
955
+ const nextRange = pkg.dependencies?.next;
956
+ const nextVersion = nextRange !== void 0 && validRange(nextRange) ? minVersion(nextRange) : null;
957
+ if (nextVersion !== null && !satisfies(nextVersion.version, OPENNEXT_REQUIRED_NEXT_RANGE)) {
958
+ consola.warn(
959
+ `${behind} That release needs Next.js ${OPENNEXT_REQUIRED_NEXT_RANGE}, but this project is on ${nextVersion.version}. Run \`catalyst upgrade\` to move core and Next first, then re-run the deploy to pick up the adapter.`
960
+ );
961
+ return false;
962
+ }
963
+ if (!canUpgrade) {
964
+ consola.warn(
965
+ `${behind} The deploy will use the existing build output. Re-run without \`--prebuilt\`, or update it with ${manualStep}, to pick up adapter fixes released since then.`
966
+ );
967
+ return false;
968
+ }
969
+ if (!process.stdin.isTTY) {
970
+ consola.warn(
971
+ `${behind} Skipping the upgrade prompt in a non-interactive environment. Update it with ${manualStep} and commit the lockfile to pick up adapter fixes released since then.`
972
+ );
973
+ return false;
974
+ }
975
+ const shouldUpgrade = await confirm2({
976
+ message: `${behind} Adapter fixes released since then are not applied. Update it to ${OPENNEXT_CLOUDFLARE_VERSION} and reinstall now?`,
977
+ default: true
1052
978
  });
1053
- }
1054
- var build = new Command2("build").configureHelp({ showGlobalOptions: true }).description(
1055
- "Build your Catalyst project using the OpenNext/Cloudflare build pipeline. Also runs a Wrangler dry-run to generate deployment artifacts."
1056
- ).addHelpText(
1057
- "after",
1058
- `
1059
- Examples:
1060
- $ catalyst build
1061
-
1062
- # Include project UUID
1063
- $ catalyst build --project-uuid <UUID>
1064
-
1065
- # Build with a specific Wrangler version
1066
- $ catalyst build --wrangler-version 4.24.3`
1067
- ).addOption(
1068
- new Option2(
1069
- "--project-uuid <uuid>",
1070
- "Project UUID to be included in the deployment configuration."
1071
- ).env("CATALYST_PROJECT_UUID")
1072
- ).addOption(
1073
- new Option2(
1074
- "--wrangler-version <version>",
1075
- `Wrangler version or dist-tag to build with. Defaults to ${WRANGLER_VERSION}.`
1076
- ).argParser(parseWranglerVersion)
1077
- ).addOption(envPathOption()).action(async (options) => {
1078
- loadBuildEnv({ envPath: options.envPath });
1079
- const state = getProjectState();
1080
- if (!state.isTransformed) {
1081
- consola.info("Project is not set up for Commerce Hosting \u2014 running `next build`.");
1082
- consola.info("To deploy to Commerce Hosting, run `catalyst deploy`.");
1083
- assertRequiredBuildEnv();
1084
- await execa2("pnpm", ["exec", "next", "build"], {
1085
- stdio: "inherit",
1086
- cwd: process.cwd()
1087
- });
1088
- return;
1089
- }
1090
- const config2 = getProjectConfig();
1091
- const projectUuid = options.projectUuid ?? config2.get("projectUuid");
1092
- if (!projectUuid) {
1093
- throw new Error(
1094
- "Project UUID is required. Please run `catalyst projects create` or `catalyst projects link` or this command again with --project-uuid <uuid>."
1095
- );
1096
- }
1097
- await buildCatalystProject(projectUuid, options.wranglerVersion);
1098
- });
1099
-
1100
- // src/cli/commands/channels.ts
1101
- import { select as select4 } from "@inquirer/prompts";
1102
- import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option3 } from "commander";
1103
- import { colorize as colorize5 } from "consola/utils";
1104
-
1105
- // src/cli/lib/channel-site-flow.ts
1106
- import { select as select2 } from "@inquirer/prompts";
1107
-
1108
- // src/cli/lib/channels.ts
1109
- import { z as z7 } from "zod";
1110
- var cliApiUrl = (origin, storeHash, path) => `${origin}/stores/${storeHash}/cli-api/v3${path}`;
1111
- var channelsUrl = (storeHash, apiHost, query2 = {}) => {
1112
- const params = new URLSearchParams(query2).toString();
1113
- return `https://${apiHost}/stores/${storeHash}/v3/channels${params ? `?${params}` : ""}`;
1114
- };
1115
- var authHeaders2 = (accessToken) => ({
1116
- "X-Auth-Token": accessToken,
1117
- "X-Correlation-Id": getTelemetry().correlationId,
1118
- Accept: "application/json"
1119
- });
1120
- var envVarsSchema = z7.record(z7.string(), z7.coerce.string());
1121
- var channelSchema = z7.object({
1122
- id: z7.number(),
1123
- name: z7.string(),
1124
- platform: z7.string()
1125
- });
1126
- var channelsResponseSchema = z7.object({
1127
- data: z7.array(channelSchema)
1128
- });
1129
- var CHANNEL_PLATFORM_ORDER = ["catalyst", "next", "bigcommerce"];
1130
- function sortChannelsByPlatform(channels2) {
1131
- return [...channels2].sort((a, b) => {
1132
- const aIndex = CHANNEL_PLATFORM_ORDER.indexOf(a.platform);
1133
- const bIndex = CHANNEL_PLATFORM_ORDER.indexOf(b.platform);
1134
- if (aIndex === -1 && bIndex === -1) return 0;
1135
- if (aIndex === -1) return 1;
1136
- if (bIndex === -1) return -1;
1137
- return aIndex - bIndex;
1138
- });
1139
- }
1140
- function channelPlatformLabel(platform) {
1141
- return platform === "bigcommerce" ? "Stencil" : platform.charAt(0).toUpperCase() + platform.slice(1);
1142
- }
1143
- var initResponseSchema = z7.object({
1144
- data: z7.object({
1145
- storefront_api_token: z7.string(),
1146
- envVars: envVarsSchema
1147
- })
1148
- });
1149
- var createChannelResponseSchema = z7.object({
1150
- data: z7.object({
1151
- id: z7.number(),
1152
- storefront_api_token: z7.string(),
1153
- envVars: envVarsSchema
1154
- })
1155
- });
1156
- var eligibilityResponseSchema = z7.object({
1157
- data: z7.object({
1158
- eligible: z7.boolean(),
1159
- message: z7.string()
1160
- })
1161
- });
1162
- var channelSiteSchema = z7.object({
1163
- data: z7.object({
1164
- id: z7.number(),
1165
- url: z7.string(),
1166
- channel_id: z7.number()
1167
- })
1168
- });
1169
- async function getChannelInit(channelId, storeHash, accessToken, origin) {
1170
- const response = await fetch(cliApiUrl(origin, storeHash, `/channels/${channelId}/init`), {
1171
- method: "GET",
1172
- headers: authHeaders2(accessToken)
1173
- });
1174
- if (!response.ok) {
1175
- throw await httpError(response, "Failed to initialize channel");
1176
- }
1177
- const { data } = initResponseSchema.parse(await response.json());
1178
- return { storefrontToken: data.storefront_api_token, envVars: data.envVars };
1179
- }
1180
- async function checkChannelEligibility(storeHash, accessToken, origin) {
1181
- const response = await fetch(cliApiUrl(origin, storeHash, "/channels/catalyst/eligibility"), {
1182
- method: "GET",
1183
- headers: authHeaders2(accessToken)
1184
- });
1185
- if (!response.ok) {
1186
- throw await httpError(response, "Failed to check Catalyst eligibility");
1187
- }
1188
- return eligibilityResponseSchema.parse(await response.json()).data;
1189
- }
1190
- async function createChannel(name, storefrontLocale, additionalLocales, installSampleData, storeHash, accessToken, origin) {
1191
- const response = await fetch(cliApiUrl(origin, storeHash, "/channels/catalyst"), {
1192
- method: "POST",
1193
- headers: { ...authHeaders2(accessToken), "Content-Type": "application/json" },
1194
- body: JSON.stringify({
1195
- name,
1196
- initialData: { type: installSampleData ? "sample" : "none" },
1197
- deployStorefront: true,
1198
- devOrigin: "http://localhost:3000",
1199
- storefrontLanguage: storefrontLocale,
1200
- additionalLocales
1201
- })
1202
- });
1203
- if (!response.ok) {
1204
- throw await httpError(response, "Failed to create channel");
979
+ if (!shouldUpgrade) {
980
+ consola.info(`Keeping @opennextjs/cloudflare ${current}. Update it later with ${manualStep}.`);
981
+ return false;
1205
982
  }
1206
- const { data } = createChannelResponseSchema.parse(await response.json());
1207
- return {
1208
- channelId: data.id,
1209
- storefrontToken: data.storefront_api_token,
1210
- envVars: data.envVars
983
+ pkg.dependencies = {
984
+ ...pkg.dependencies,
985
+ "@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
1211
986
  };
1212
- }
1213
- async function fetchAvailableChannels(storeHash, accessToken, apiHost) {
1214
- const response = await fetch(
1215
- channelsUrl(storeHash, apiHost, { available: "true", type: "storefront" }),
1216
- {
1217
- method: "GET",
1218
- headers: {
1219
- "X-Auth-Token": accessToken,
1220
- "X-Correlation-Id": getTelemetry().correlationId,
1221
- Accept: "application/json"
1222
- }
1223
- }
1224
- );
1225
- assertAuthorized(response);
1226
- if (!response.ok) {
1227
- throw await httpError(response, "Failed to fetch channels");
1228
- }
1229
- return channelsResponseSchema.parse(await response.json()).data;
1230
- }
1231
- async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken, apiHost) {
1232
- const response = await fetch(
1233
- `https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site`,
1234
- {
1235
- method: "PUT",
1236
- headers: {
1237
- "X-Auth-Token": accessToken,
1238
- "Content-Type": "application/json",
1239
- Accept: "application/json",
1240
- "X-Correlation-Id": getTelemetry().correlationId
1241
- },
1242
- body: JSON.stringify({ url: siteUrl })
1243
- }
1244
- );
1245
- if (response.status === 401 || response.status === 403) {
1246
- throw new UserActionableError(
1247
- `Failed to update channel site (${response.status}). Re-run \`catalyst auth login\` to refresh your access token with the store_channel_settings scope.`
1248
- );
1249
- }
1250
- if (!response.ok) {
1251
- throw await httpError(response, "Failed to update channel site");
1252
- }
1253
- const res = await response.json();
1254
- const { data } = channelSiteSchema.parse(res);
1255
- return { id: data.id, url: data.url, channelId: data.channel_id };
1256
- }
1257
-
1258
- // src/cli/lib/commerce-hosting.ts
1259
- import { confirm as confirm2, input as input2, select, Separator } from "@inquirer/prompts";
1260
- import { colorize as colorize3 } from "consola/utils";
1261
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
1262
- import { dirname as dirname2, join as join5 } from "path";
1263
- import { z as z8 } from "zod";
1264
-
1265
- // src/cli/lib/sort-package-json.ts
1266
- var FIELD_ORDER = [
1267
- "name",
1268
- "description",
1269
- "version",
1270
- "private",
1271
- "engines",
1272
- "scripts",
1273
- "dependencies",
1274
- "devDependencies"
1275
- ];
1276
- function sortPackageJsonFields(pkg) {
1277
- const ordered = {};
1278
- FIELD_ORDER.forEach((field) => {
1279
- if (field in pkg) {
1280
- ordered[field] = pkg[field];
1281
- }
1282
- });
1283
- Object.keys(pkg).forEach((key) => {
1284
- if (!(key in ordered)) {
1285
- ordered[key] = pkg[key];
1286
- }
1287
- });
1288
- return ordered;
1289
- }
1290
-
1291
- // src/cli/lib/commerce-hosting.ts
1292
- var OPENNEXT_CLOUDFLARE_VERSION = "1.17.3";
1293
- var corePackageJsonSchema = z8.looseObject({
1294
- dependencies: z8.record(z8.string(), z8.string()).optional()
1295
- });
1296
- var projectJsonSchema2 = z8.looseObject({});
1297
- var readProjectJson = (path) => {
1298
- if (!existsSync3(path)) return {};
1299
- try {
1300
- const parsed = projectJsonSchema2.safeParse(JSON.parse(readFileSync2(path, "utf-8")));
1301
- return parsed.success ? parsed.data : {};
1302
- } catch {
1303
- return {};
1304
- }
1305
- };
1306
- var writeJson = (path, value) => {
1307
- mkdirSync(dirname2(path), { recursive: true });
1308
- writeFileSync(path, `${JSON.stringify(value, null, 2)}
1309
- `);
1310
- };
1311
- var convertProxyToMiddleware = (projectDir) => {
1312
- const proxyPath = join5(projectDir, "proxy.ts");
1313
- const middlewarePath = join5(projectDir, "middleware.ts");
1314
- if (!existsSync3(proxyPath)) return;
1315
- const contents = readFileSync2(proxyPath, "utf-8").replace("export const proxy", "export const middleware").replace("export const config = {", "export const config = {\n runtime: 'experimental-edge',");
1316
- writeFileSync(middlewarePath, contents);
1317
- unlinkSync(proxyPath);
1318
- };
1319
- var cleanupCloudflareIncompatibilities = async (projectDir) => {
1320
- const instrumentationPath = join5(projectDir, "instrumentation.ts");
1321
- if (!existsSync3(instrumentationPath)) return;
1322
- if (!process.stdin.isTTY) {
1323
- consola.warn(
1324
- "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."
1325
- );
1326
- return;
1327
- }
1328
- const shouldRemove = await confirm2({
1329
- 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?',
1330
- default: true
1331
- });
1332
- if (!shouldRemove) {
1333
- consola.info(
1334
- 'Leaving instrumentation.ts in place. The Cloudflare worker will continue to log "Failed to prepare server" at cold start until this is resolved manually.'
1335
- );
1336
- return;
1337
- }
1338
- unlinkSync(instrumentationPath);
1339
- consola.info("Removed instrumentation.ts (incompatible with Cloudflare Workers).");
1340
- const corePackageJsonPath = join5(projectDir, "package.json");
1341
- if (existsSync3(corePackageJsonPath)) {
1342
- const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
1343
- if (pkg.dependencies && "@vercel/otel" in pkg.dependencies) {
1344
- const { "@vercel/otel": _removedVercelOtel, ...preservedDeps } = pkg.dependencies;
1345
- pkg.dependencies = preservedDeps;
1346
- writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
1347
- consola.info("Dropped @vercel/otel from package.json.");
1348
- }
1349
- }
987
+ writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
988
+ consola.info(`Updated @opennextjs/cloudflare to ${OPENNEXT_CLOUDFLARE_VERSION}.`);
989
+ return true;
1350
990
  };
1351
991
  var setupCommerceHosting = async ({
1352
992
  projectDir,
@@ -1355,14 +995,14 @@ var setupCommerceHosting = async ({
1355
995
  accessToken
1356
996
  }) => {
1357
997
  await cleanupCloudflareIncompatibilities(projectDir);
1358
- const corePackageJsonPath = join5(projectDir, "package.json");
1359
- const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
998
+ const corePackageJsonPath = join2(projectDir, "package.json");
999
+ const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync(corePackageJsonPath, "utf-8")));
1360
1000
  pkg.dependencies = {
1361
1001
  ...pkg.dependencies,
1362
1002
  "@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
1363
1003
  };
1364
1004
  writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
1365
- const projectJsonPath = join5(projectDir, ".bigcommerce", "project.json");
1005
+ const projectJsonPath = join2(projectDir, ".bigcommerce", "project.json");
1366
1006
  const projectJson = {
1367
1007
  ...readProjectJson(projectJsonPath),
1368
1008
  projectUuid,
@@ -1533,35 +1173,696 @@ async function resolveCollisionChoice(existingName, useExistingOnCollision) {
1533
1173
  ]
1534
1174
  });
1535
1175
  }
1536
- async function autoCreateCommerceHostingProject(api, name, existingProjects, useExistingOnCollision) {
1537
- const existing = existingProjects.find(
1538
- (project) => project.name.toLowerCase() === name.toLowerCase()
1176
+ async function autoCreateCommerceHostingProject(api, name, existingProjects, useExistingOnCollision) {
1177
+ const existing = existingProjects.find(
1178
+ (project) => project.name.toLowerCase() === name.toLowerCase()
1179
+ );
1180
+ if (existing) {
1181
+ const shouldUseExisting = await resolveCollisionChoice(existing.name, useExistingOnCollision);
1182
+ if (shouldUseExisting) {
1183
+ consola.success(`Using existing Commerce Hosting project "${existing.name}"`);
1184
+ return existing;
1185
+ }
1186
+ consola.error(
1187
+ "Not reusing the existing project. Re-run with a different --project-name, or pass --use-existing to reuse it."
1188
+ );
1189
+ process.exit(1);
1190
+ }
1191
+ try {
1192
+ const created = await createProject(name, api.storeHash, api.accessToken, api.apiHost);
1193
+ consola.success(`Commerce Hosting project "${created.name}" created successfully`);
1194
+ return { uuid: created.uuid, name: created.name, deployment_hostnames: [] };
1195
+ } catch (error) {
1196
+ if (error instanceof InfrastructureProjectValidationError) {
1197
+ consola.error(
1198
+ `Failed to create Commerce Hosting project "${name}": ${error.message}
1199
+ Re-run with a different --project-name.`
1200
+ );
1201
+ process.exit(1);
1202
+ }
1203
+ throw error;
1204
+ }
1205
+ }
1206
+
1207
+ // src/cli/lib/detect-package-manager.ts
1208
+ import { access } from "fs/promises";
1209
+ import { join as join3 } from "path";
1210
+ import { detectPackageManager as detectFromDir } from "nypm";
1211
+ var PROJECT_LOCKFILES = [
1212
+ ["pnpm-lock.yaml", "pnpm"],
1213
+ ["yarn.lock", "yarn"],
1214
+ ["bun.lock", "bun"],
1215
+ ["bun.lockb", "bun"],
1216
+ ["package-lock.json", "npm"]
1217
+ ];
1218
+ var detectLockfileManager = async (dir) => {
1219
+ const found = await Promise.all(
1220
+ PROJECT_LOCKFILES.map(
1221
+ async ([file, manager]) => access(join3(dir, file)).then(() => manager).catch(() => null)
1222
+ )
1223
+ );
1224
+ return found.find((manager) => manager !== null) ?? null;
1225
+ };
1226
+ var detectPackageManager = () => {
1227
+ const userAgent = process.env.npm_config_user_agent ?? "";
1228
+ const name = userAgent.split(" ")[0]?.split("/")[0];
1229
+ if (name === "pnpm" || name === "yarn" || name === "bun") {
1230
+ return name;
1231
+ }
1232
+ if (process.versions.bun) {
1233
+ return "bun";
1234
+ }
1235
+ return "npm";
1236
+ };
1237
+ var detectProjectPackageManager = async (projectDir) => {
1238
+ const detected = await detectFromDir(projectDir);
1239
+ const name = detected?.name;
1240
+ if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") {
1241
+ return name;
1242
+ }
1243
+ return "npm";
1244
+ };
1245
+
1246
+ // src/cli/lib/get-module-cli-path.ts
1247
+ import { dirname as dirname2, join as join4 } from "path";
1248
+ import { fileURLToPath } from "url";
1249
+ function getModuleCliPath() {
1250
+ const __filename = fileURLToPath(import.meta.url);
1251
+ const __dirname = dirname2(__filename);
1252
+ return join4(__dirname, "..");
1253
+ }
1254
+
1255
+ // src/cli/lib/install-dependencies.ts
1256
+ import { installDependencies as installDeps } from "nypm";
1257
+ import yoctoSpinner2 from "yocto-spinner";
1258
+ var restoreEnv = (key, previous) => {
1259
+ if (previous === void 0) {
1260
+ Reflect.deleteProperty(process.env, key);
1261
+ } else {
1262
+ process.env[key] = previous;
1263
+ }
1264
+ };
1265
+ var withQuietInstallEnv = async (run2) => {
1266
+ var _a, _b;
1267
+ const prevNodeNoWarnings = process.env.NODE_NO_WARNINGS;
1268
+ const prevDownloadPrompt = process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT;
1269
+ (_a = process.env).NODE_NO_WARNINGS ?? (_a.NODE_NO_WARNINGS = "1");
1270
+ (_b = process.env).COREPACK_ENABLE_DOWNLOAD_PROMPT ?? (_b.COREPACK_ENABLE_DOWNLOAD_PROMPT = "0");
1271
+ try {
1272
+ return await run2();
1273
+ } finally {
1274
+ restoreEnv("NODE_NO_WARNINGS", prevNodeNoWarnings);
1275
+ restoreEnv("COREPACK_ENABLE_DOWNLOAD_PROMPT", prevDownloadPrompt);
1276
+ }
1277
+ };
1278
+ var installDependencies = async (projectDir, packageManager = "pnpm") => {
1279
+ const spinner = yoctoSpinner2().start(
1280
+ `Installing dependencies with ${packageManager}. This could take a minute...`
1281
+ );
1282
+ try {
1283
+ await withQuietInstallEnv(() => installDeps({ cwd: projectDir, silent: true, packageManager }));
1284
+ spinner.success("Dependencies installed successfully.");
1285
+ } catch (error) {
1286
+ const message = error instanceof Error ? error.message : "unknown error";
1287
+ spinner.error(`Failed to install dependencies: ${message}`);
1288
+ throw error;
1289
+ }
1290
+ };
1291
+
1292
+ // src/cli/lib/project-state.ts
1293
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1294
+ import { join as join5 } from "path";
1295
+ import { z as z7 } from "zod";
1296
+ var projectJsonSchema2 = z7.looseObject({
1297
+ projectUuid: z7.string().optional()
1298
+ });
1299
+ var packageJsonSchema = z7.looseObject({
1300
+ dependencies: z7.record(z7.string(), z7.string()).optional()
1301
+ });
1302
+ var safeReadJson = (path) => {
1303
+ try {
1304
+ return JSON.parse(readFileSync2(path, "utf-8"));
1305
+ } catch {
1306
+ return null;
1307
+ }
1308
+ };
1309
+ function getProjectState(cwd = process.cwd()) {
1310
+ const projectJson = projectJsonSchema2.safeParse(
1311
+ safeReadJson(join5(cwd, ".bigcommerce", "project.json"))
1312
+ );
1313
+ const projectUuid = projectJson.success ? projectJson.data.projectUuid : void 0;
1314
+ const hasMiddleware = existsSync3(join5(cwd, "middleware.ts"));
1315
+ const hasProxy = existsSync3(join5(cwd, "proxy.ts"));
1316
+ const pkgJson = packageJsonSchema.safeParse(safeReadJson(join5(cwd, "package.json")));
1317
+ const hasOpenNextDep = pkgJson.success ? Boolean(pkgJson.data.dependencies?.["@opennextjs/cloudflare"]) : false;
1318
+ const isLinked = Boolean(projectUuid);
1319
+ const isTransformed = hasMiddleware && !hasProxy && hasOpenNextDep;
1320
+ const isFullySetUp = isLinked && isTransformed;
1321
+ return {
1322
+ projectUuid,
1323
+ hasMiddleware,
1324
+ hasProxy,
1325
+ hasOpenNextDep,
1326
+ isLinked,
1327
+ isTransformed,
1328
+ isFullySetUp
1329
+ };
1330
+ }
1331
+
1332
+ // src/cli/lib/required-build-env.ts
1333
+ var REQUIRED_BUILD_ENV_VARS = [
1334
+ "BIGCOMMERCE_STORE_HASH",
1335
+ "BIGCOMMERCE_STOREFRONT_TOKEN",
1336
+ "BIGCOMMERCE_CHANNEL_ID",
1337
+ "AUTH_SECRET"
1338
+ ];
1339
+ var findMissingBuildEnv = (env2 = process.env) => {
1340
+ return REQUIRED_BUILD_ENV_VARS.filter((name) => {
1341
+ const value = env2[name];
1342
+ return value === void 0 || value.trim() === "";
1343
+ });
1344
+ };
1345
+ var assertRequiredBuildEnv = (env2 = process.env) => {
1346
+ const missing = findMissingBuildEnv(env2);
1347
+ if (missing.length === 0) {
1348
+ return;
1349
+ }
1350
+ const missingList = missing.map((name) => ` - ${name}`).join("\n");
1351
+ throw new UserActionableError(
1352
+ `Missing required environment variable${missing.length === 1 ? "" : "s"} for the build:
1353
+ ${missingList}
1354
+
1355
+ These are read by the build itself, so they must be set before it runs. The build automatically loads \`.env.local\` and \`.env\` from the current directory (${process.cwd()}) \u2014 add the variables above to one of those files (\`.env.local\` takes precedence).
1356
+ To load an env file from a different location, pass \`--env-path <path>\` (for example \`--env-path ../.env.local\` when running from a subdirectory).`
1357
+ );
1358
+ };
1359
+
1360
+ // src/cli/lib/wrangler-config.ts
1361
+ function getCompatibilityDate(now = /* @__PURE__ */ new Date()) {
1362
+ const date = new Date(now);
1363
+ date.setUTCMonth(date.getUTCMonth() - 1);
1364
+ return date.toISOString().slice(0, 10);
1365
+ }
1366
+ var LOCAL_ROUTES_KV_NAMESPACE_ID = "catalyst-routes-kv-local";
1367
+ function getWranglerConfig(projectUuid) {
1368
+ return {
1369
+ $schema: "node_modules/wrangler/config-schema.json",
1370
+ main: "../.open-next/worker.js",
1371
+ name: `project-${projectUuid}`,
1372
+ compatibility_date: getCompatibilityDate(),
1373
+ compatibility_flags: ["nodejs_compat", "global_fetch_strictly_public"],
1374
+ observability: {
1375
+ enabled: true,
1376
+ head_sampling_rate: 0.05,
1377
+ logs: {
1378
+ enabled: true,
1379
+ head_sampling_rate: 1,
1380
+ invocation_logs: false
1381
+ }
1382
+ },
1383
+ assets: {
1384
+ directory: "../.open-next/assets",
1385
+ binding: "ASSETS"
1386
+ },
1387
+ services: [
1388
+ {
1389
+ binding: "WORKER_SELF_REFERENCE",
1390
+ service: `project-${projectUuid}`
1391
+ }
1392
+ ],
1393
+ r2_buckets: [
1394
+ {
1395
+ binding: "NEXT_INC_CACHE_R2_BUCKET",
1396
+ bucket_name: `project-${projectUuid}`
1397
+ }
1398
+ ],
1399
+ kv_namespaces: [
1400
+ {
1401
+ binding: "CATALYST_ROUTES_KV",
1402
+ id: LOCAL_ROUTES_KV_NAMESPACE_ID
1403
+ }
1404
+ ],
1405
+ durable_objects: {
1406
+ bindings: [
1407
+ {
1408
+ name: "NEXT_CACHE_DO_QUEUE",
1409
+ class_name: "DOQueueHandler"
1410
+ },
1411
+ {
1412
+ name: "NEXT_TAG_CACHE_DO_SHARDED",
1413
+ class_name: "DOShardedTagCache"
1414
+ },
1415
+ {
1416
+ name: "NEXT_CACHE_DO_PURGE",
1417
+ class_name: "BucketCachePurge"
1418
+ }
1419
+ ]
1420
+ },
1421
+ migrations: [
1422
+ {
1423
+ tag: "v1",
1424
+ new_sqlite_classes: ["DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"]
1425
+ }
1426
+ ]
1427
+ };
1428
+ }
1429
+
1430
+ // src/cli/commands/build.ts
1431
+ var WRANGLER_VERSION = "4.128.0";
1432
+ var DIST_TAG_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
1433
+ var parseWranglerVersion = (value) => {
1434
+ if (!validSemver(value) && !DIST_TAG_PATTERN.test(value)) {
1435
+ throw new InvalidArgumentError(
1436
+ `"${value}" is not a valid Wrangler version or dist-tag (e.g. 4.90.0 or latest).`
1437
+ );
1438
+ }
1439
+ return value;
1440
+ };
1441
+ async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERSION) {
1442
+ assertRequiredBuildEnv();
1443
+ const coreDir = process.cwd();
1444
+ const projectPackageManager = await detectProjectPackageManager(coreDir);
1445
+ if (await reconcileOpenNextVersion(coreDir, projectPackageManager, { canUpgrade: true })) {
1446
+ await installDependencies(coreDir, projectPackageManager);
1447
+ }
1448
+ const openNextOutDir = join6(coreDir, ".open-next");
1449
+ const bigcommerceDistDir = join6(coreDir, ".bigcommerce", "dist");
1450
+ consola.start("Generating GraphQL types...");
1451
+ await execa2("node", [join6(coreDir, "scripts", "generate.cjs")], {
1452
+ stdio: "inherit",
1453
+ cwd: coreDir
1454
+ });
1455
+ consola.success("GraphQL types generated");
1456
+ const wranglerConfig = getWranglerConfig(projectUuid);
1457
+ await rm(bigcommerceDistDir, { recursive: true, force: true });
1458
+ consola.start("Copying templates...");
1459
+ await copyFile(
1460
+ join6(getModuleCliPath(), "templates", "open-next.config.ts"),
1461
+ join6(coreDir, ".bigcommerce", "open-next.config.ts")
1462
+ );
1463
+ await writeFile(
1464
+ join6(coreDir, ".bigcommerce", "wrangler.jsonc"),
1465
+ JSON.stringify(wranglerConfig, null, 2)
1466
+ );
1467
+ consola.success("Templates copied");
1468
+ consola.start("Building project...");
1469
+ await execa2(
1470
+ "pnpm",
1471
+ [
1472
+ "exec",
1473
+ "opennextjs-cloudflare",
1474
+ "build",
1475
+ "--skipWranglerConfigCheck",
1476
+ "--openNextConfigPath",
1477
+ join6(coreDir, ".bigcommerce", "open-next.config.ts")
1478
+ ],
1479
+ {
1480
+ stdout: ["pipe", "inherit"],
1481
+ cwd: coreDir
1482
+ }
1483
+ );
1484
+ await copyFile(
1485
+ join6(getModuleCliPath(), "templates", "public_headers"),
1486
+ join6(openNextOutDir, "assets", "_headers")
1487
+ );
1488
+ await execa2(
1489
+ "pnpm",
1490
+ [
1491
+ "dlx",
1492
+ `wrangler@${wranglerVersion}`,
1493
+ "deploy",
1494
+ "--config",
1495
+ join6(coreDir, ".bigcommerce", "wrangler.jsonc"),
1496
+ "--keep-vars",
1497
+ "--outdir",
1498
+ bigcommerceDistDir,
1499
+ "--dry-run"
1500
+ ],
1501
+ {
1502
+ stdout: ["pipe", "inherit"],
1503
+ cwd: coreDir
1504
+ }
1505
+ );
1506
+ consola.success("Project built");
1507
+ await cp(join6(openNextOutDir, "assets"), join6(bigcommerceDistDir, "assets"), {
1508
+ recursive: true,
1509
+ force: true
1510
+ });
1511
+ }
1512
+ var build = new Command2("build").configureHelp({ showGlobalOptions: true }).description(
1513
+ "Build your Catalyst project using the OpenNext/Cloudflare build pipeline. Also runs a Wrangler dry-run to generate deployment artifacts."
1514
+ ).addHelpText(
1515
+ "after",
1516
+ `
1517
+ Examples:
1518
+ $ catalyst build
1519
+
1520
+ # Include project UUID
1521
+ $ catalyst build --project-uuid <UUID>
1522
+
1523
+ # Build with a specific Wrangler version
1524
+ $ catalyst build --wrangler-version 4.24.3`
1525
+ ).addOption(
1526
+ new Option2(
1527
+ "--project-uuid <uuid>",
1528
+ "Project UUID to be included in the deployment configuration."
1529
+ ).env("CATALYST_PROJECT_UUID")
1530
+ ).addOption(
1531
+ new Option2(
1532
+ "--wrangler-version <version>",
1533
+ `Wrangler version or dist-tag to build with. Defaults to ${WRANGLER_VERSION}.`
1534
+ ).argParser(parseWranglerVersion)
1535
+ ).addOption(envPathOption()).action(async (options) => {
1536
+ loadBuildEnv({ envPath: options.envPath });
1537
+ const state = getProjectState();
1538
+ if (!state.isTransformed) {
1539
+ consola.info("Project is not set up for Commerce Hosting \u2014 running `next build`.");
1540
+ consola.info("To deploy to Commerce Hosting, run `catalyst deploy`.");
1541
+ assertRequiredBuildEnv();
1542
+ await execa2("pnpm", ["exec", "next", "build"], {
1543
+ stdio: "inherit",
1544
+ cwd: process.cwd()
1545
+ });
1546
+ return;
1547
+ }
1548
+ const config2 = getProjectConfig();
1549
+ const projectUuid = options.projectUuid ?? config2.get("projectUuid");
1550
+ if (!projectUuid) {
1551
+ throw new Error(
1552
+ "Project UUID is required. Please run `catalyst projects create` or `catalyst projects link` or this command again with --project-uuid <uuid>."
1553
+ );
1554
+ }
1555
+ await buildCatalystProject(projectUuid, options.wranglerVersion);
1556
+ });
1557
+
1558
+ // src/cli/commands/channels.ts
1559
+ import { confirm as confirm3, select as select4 } from "@inquirer/prompts";
1560
+ import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option3 } from "commander";
1561
+ import { colorize as colorize5 } from "consola/utils";
1562
+
1563
+ // src/cli/lib/channel-checkout-url-flow.ts
1564
+ import { input as input3 } from "@inquirer/prompts";
1565
+
1566
+ // src/cli/lib/channel-site-flow.ts
1567
+ import { select as select2 } from "@inquirer/prompts";
1568
+
1569
+ // src/cli/lib/channels.ts
1570
+ import { z as z8 } from "zod";
1571
+ var cliApiUrl = (origin, storeHash, path) => `${origin}/stores/${storeHash}/cli-api/v3${path}`;
1572
+ var channelsUrl = (storeHash, apiHost, query2 = {}) => {
1573
+ const params = new URLSearchParams(query2).toString();
1574
+ return `https://${apiHost}/stores/${storeHash}/v3/channels${params ? `?${params}` : ""}`;
1575
+ };
1576
+ var authHeaders2 = (accessToken) => ({
1577
+ "X-Auth-Token": accessToken,
1578
+ "X-Correlation-Id": getTelemetry().correlationId,
1579
+ Accept: "application/json"
1580
+ });
1581
+ var envVarsSchema = z8.record(z8.string(), z8.coerce.string());
1582
+ var channelSchema = z8.object({
1583
+ id: z8.number(),
1584
+ name: z8.string(),
1585
+ platform: z8.string()
1586
+ });
1587
+ var channelsResponseSchema = z8.object({
1588
+ data: z8.array(channelSchema)
1589
+ });
1590
+ var CHANNEL_PLATFORM_ORDER = ["catalyst", "next", "bigcommerce"];
1591
+ function sortChannelsByPlatform(channels2) {
1592
+ return [...channels2].sort((a, b) => {
1593
+ const aIndex = CHANNEL_PLATFORM_ORDER.indexOf(a.platform);
1594
+ const bIndex = CHANNEL_PLATFORM_ORDER.indexOf(b.platform);
1595
+ if (aIndex === -1 && bIndex === -1) return 0;
1596
+ if (aIndex === -1) return 1;
1597
+ if (bIndex === -1) return -1;
1598
+ return aIndex - bIndex;
1599
+ });
1600
+ }
1601
+ function channelPlatformLabel(platform) {
1602
+ return platform === "bigcommerce" ? "Stencil" : platform.charAt(0).toUpperCase() + platform.slice(1);
1603
+ }
1604
+ var initResponseSchema = z8.object({
1605
+ data: z8.object({
1606
+ storefront_api_token: z8.string(),
1607
+ envVars: envVarsSchema
1608
+ })
1609
+ });
1610
+ var createChannelResponseSchema = z8.object({
1611
+ data: z8.object({
1612
+ id: z8.number(),
1613
+ storefront_api_token: z8.string(),
1614
+ envVars: envVarsSchema
1615
+ })
1616
+ });
1617
+ var eligibilityResponseSchema = z8.object({
1618
+ data: z8.object({
1619
+ eligible: z8.boolean(),
1620
+ message: z8.string()
1621
+ })
1622
+ });
1623
+ var siteUrlSchema = z8.object({
1624
+ url: z8.string(),
1625
+ type: z8.string()
1626
+ });
1627
+ var channelSiteSchema = z8.object({
1628
+ data: z8.object({
1629
+ id: z8.number(),
1630
+ url: z8.string(),
1631
+ channel_id: z8.number(),
1632
+ ssl_status: z8.string().nullable().optional(),
1633
+ is_checkout_url_customized: z8.boolean().optional(),
1634
+ urls: z8.array(siteUrlSchema).optional()
1635
+ })
1636
+ });
1637
+ function assertChannelSettingsAuthorized(response, action) {
1638
+ if (response.status === 401 || response.status === 403) {
1639
+ throw new UserActionableError(
1640
+ `${action} (${response.status}). Re-run \`catalyst auth login\` to refresh your access token with the store_channel_settings and store_sites scopes.`
1641
+ );
1642
+ }
1643
+ }
1644
+ async function getChannelInit(channelId, storeHash, accessToken, origin) {
1645
+ const response = await fetch(cliApiUrl(origin, storeHash, `/channels/${channelId}/init`), {
1646
+ method: "GET",
1647
+ headers: authHeaders2(accessToken)
1648
+ });
1649
+ if (!response.ok) {
1650
+ throw await httpError(response, "Failed to initialize channel");
1651
+ }
1652
+ const { data } = initResponseSchema.parse(await response.json());
1653
+ return { storefrontToken: data.storefront_api_token, envVars: data.envVars };
1654
+ }
1655
+ async function checkChannelEligibility(storeHash, accessToken, origin) {
1656
+ const response = await fetch(cliApiUrl(origin, storeHash, "/channels/catalyst/eligibility"), {
1657
+ method: "GET",
1658
+ headers: authHeaders2(accessToken)
1659
+ });
1660
+ if (!response.ok) {
1661
+ throw await httpError(response, "Failed to check Catalyst eligibility");
1662
+ }
1663
+ return eligibilityResponseSchema.parse(await response.json()).data;
1664
+ }
1665
+ async function createChannel(name, storefrontLocale, additionalLocales, installSampleData, storeHash, accessToken, origin) {
1666
+ const response = await fetch(cliApiUrl(origin, storeHash, "/channels/catalyst"), {
1667
+ method: "POST",
1668
+ headers: { ...authHeaders2(accessToken), "Content-Type": "application/json" },
1669
+ body: JSON.stringify({
1670
+ name,
1671
+ initialData: { type: installSampleData ? "sample" : "none" },
1672
+ deployStorefront: true,
1673
+ devOrigin: "http://localhost:3000",
1674
+ storefrontLanguage: storefrontLocale,
1675
+ additionalLocales
1676
+ })
1677
+ });
1678
+ if (!response.ok) {
1679
+ throw await httpError(response, "Failed to create channel");
1680
+ }
1681
+ const { data } = createChannelResponseSchema.parse(await response.json());
1682
+ return {
1683
+ channelId: data.id,
1684
+ storefrontToken: data.storefront_api_token,
1685
+ envVars: data.envVars
1686
+ };
1687
+ }
1688
+ async function fetchAvailableChannels(storeHash, accessToken, apiHost) {
1689
+ const response = await fetch(
1690
+ channelsUrl(storeHash, apiHost, { available: "true", type: "storefront" }),
1691
+ {
1692
+ method: "GET",
1693
+ headers: {
1694
+ "X-Auth-Token": accessToken,
1695
+ "X-Correlation-Id": getTelemetry().correlationId,
1696
+ Accept: "application/json"
1697
+ }
1698
+ }
1699
+ );
1700
+ assertAuthorized(response);
1701
+ if (!response.ok) {
1702
+ throw await httpError(response, "Failed to fetch channels");
1703
+ }
1704
+ return channelsResponseSchema.parse(await response.json()).data;
1705
+ }
1706
+ async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken, apiHost) {
1707
+ const response = await fetch(
1708
+ `https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site`,
1709
+ {
1710
+ method: "PUT",
1711
+ headers: {
1712
+ "X-Auth-Token": accessToken,
1713
+ "Content-Type": "application/json",
1714
+ Accept: "application/json",
1715
+ "X-Correlation-Id": getTelemetry().correlationId
1716
+ },
1717
+ body: JSON.stringify({ url: siteUrl })
1718
+ }
1719
+ );
1720
+ assertChannelSettingsAuthorized(response, "Failed to update channel site");
1721
+ if (!response.ok) {
1722
+ throw await httpError(response, "Failed to update channel site");
1723
+ }
1724
+ const res = await response.json();
1725
+ const { data } = channelSiteSchema.parse(res);
1726
+ return { id: data.id, url: data.url, channelId: data.channel_id };
1727
+ }
1728
+ var toChannelSiteDetails = (data) => ({
1729
+ id: data.id,
1730
+ url: data.url,
1731
+ channelId: data.channel_id,
1732
+ sslStatus: data.ssl_status ?? null,
1733
+ isCheckoutUrlCustomized: data.is_checkout_url_customized ?? false,
1734
+ urls: data.urls ?? []
1735
+ });
1736
+ function findChannelSiteUrl(site, type) {
1737
+ return site.urls.find((entry) => entry.type === type)?.url;
1738
+ }
1739
+ async function getChannelSite(channelId, storeHash, accessToken, apiHost) {
1740
+ const response = await fetch(
1741
+ `https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site`,
1742
+ { method: "GET", headers: authHeaders2(accessToken) }
1743
+ );
1744
+ assertChannelSettingsAuthorized(response, "Failed to fetch channel site");
1745
+ if (!response.ok) {
1746
+ throw await httpError(response, "Failed to fetch channel site");
1747
+ }
1748
+ const res = await response.json();
1749
+ return toChannelSiteDetails(channelSiteSchema.parse(res).data);
1750
+ }
1751
+ var checkoutUrlPath = (storeHash, apiHost, channelId) => `https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site/checkout-url`;
1752
+ async function updateChannelCheckoutUrl(channelId, checkoutUrl, storeHash, accessToken, apiHost) {
1753
+ const response = await fetch(checkoutUrlPath(storeHash, apiHost, channelId), {
1754
+ method: "PUT",
1755
+ headers: { ...authHeaders2(accessToken), "Content-Type": "application/json" },
1756
+ body: JSON.stringify({ url: checkoutUrl })
1757
+ });
1758
+ assertChannelSettingsAuthorized(response, "Failed to update channel checkout URL");
1759
+ if (!response.ok) {
1760
+ throw await httpError(response, "Failed to update channel checkout URL");
1761
+ }
1762
+ const res = await response.json();
1763
+ return toChannelSiteDetails(channelSiteSchema.parse(res).data);
1764
+ }
1765
+ async function deleteChannelCheckoutUrl(channelId, storeHash, accessToken, apiHost) {
1766
+ const response = await fetch(checkoutUrlPath(storeHash, apiHost, channelId), {
1767
+ method: "DELETE",
1768
+ headers: authHeaders2(accessToken)
1769
+ });
1770
+ assertChannelSettingsAuthorized(response, "Failed to remove channel checkout URL");
1771
+ if (!response.ok) {
1772
+ throw await httpError(response, "Failed to remove channel checkout URL");
1773
+ }
1774
+ }
1775
+
1776
+ // src/cli/lib/checkout-url.ts
1777
+ import { getDomain } from "tldts";
1778
+ var normalizeHostname = (hostname) => hostname.toLowerCase().replace(/\.$/, "");
1779
+ var parentDomain = (hostname) => hostname.split(".").slice(1).join(".");
1780
+ var isSubdomainOf = (hostname, parent) => hostname.endsWith(`.${parent}`);
1781
+ function hostnameOf(url) {
1782
+ try {
1783
+ return new URL(url).hostname;
1784
+ } catch {
1785
+ return void 0;
1786
+ }
1787
+ }
1788
+ function sharesMainDomain(a, b) {
1789
+ const first = getDomain(normalizeHostname(a));
1790
+ return first !== null && first === getDomain(normalizeHostname(b));
1791
+ }
1792
+ function suggestCheckoutUrl(storefrontUrl) {
1793
+ const host = hostnameOf(storefrontUrl);
1794
+ if (!host) return void 0;
1795
+ const base = normalizeHostname(host).replace(/^www\./, "");
1796
+ return `https://checkout.${base}`;
1797
+ }
1798
+ function normalizeCheckoutUrl(value) {
1799
+ const withScheme = /^[a-z][a-z\d+.-]*:\/\//i.test(value) ? value : `https://${value}`;
1800
+ let parsed;
1801
+ try {
1802
+ parsed = new URL(withScheme);
1803
+ } catch {
1804
+ throw new UserActionableError(
1805
+ `"${value}" is not a valid URL. Pass a hostname or an https URL, e.g. https://checkout.example.com.`
1806
+ );
1807
+ }
1808
+ if (parsed.protocol !== "https:") {
1809
+ throw new UserActionableError(
1810
+ `The checkout URL must use https, but "${value}" uses ${parsed.protocol.replace(":", "")}.`
1811
+ );
1812
+ }
1813
+ return parsed.origin;
1814
+ }
1815
+ async function isManagedHostingHostname(hostname, context) {
1816
+ try {
1817
+ const projects2 = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
1818
+ const zones = projects2.flatMap((project) => project.deployment_hostnames).map((deploymentHostname) => parentDomain(normalizeHostname(deploymentHostname))).filter((zone) => zone.includes("."));
1819
+ if (zones.length === 0) return void 0;
1820
+ const host = normalizeHostname(hostname);
1821
+ return zones.some((zone) => host === zone || isSubdomainOf(host, zone));
1822
+ } catch {
1823
+ return void 0;
1824
+ }
1825
+ }
1826
+ async function warnOnCrossDomainCheckout(site, context) {
1827
+ const storefrontUrl = findChannelSiteUrl(site, "primary") ?? site.url;
1828
+ const storefrontHost = hostnameOf(storefrontUrl);
1829
+ const checkoutUrl = findChannelSiteUrl(site, "checkout");
1830
+ if (!storefrontHost) return { crossDomain: false };
1831
+ const suggestion = suggestCheckoutUrl(storefrontUrl);
1832
+ const checkoutHost = checkoutUrl ? hostnameOf(checkoutUrl) : void 0;
1833
+ if (!checkoutHost || sharesMainDomain(storefrontHost, checkoutHost)) {
1834
+ return { crossDomain: false, suggestion };
1835
+ }
1836
+ if (site.isCheckoutUrlCustomized) {
1837
+ consola.warn(
1838
+ `This channel's checkout URL (${checkoutHost}) is on a different domain than its storefront (${storefrontHost}) \u2014 most likely a storefront URL change left it behind.`
1839
+ );
1840
+ } else {
1841
+ consola.warn(
1842
+ `Checkout is on the default channel's domain (${checkoutHost}), not this channel's storefront (${storefrontHost}).`
1843
+ );
1844
+ }
1845
+ consola.info(
1846
+ "Shopper sessions and carts may not carry into checkout in browsers that restrict cross-domain cookies."
1539
1847
  );
1540
- if (existing) {
1541
- const shouldUseExisting = await resolveCollisionChoice(existing.name, useExistingOnCollision);
1542
- if (shouldUseExisting) {
1543
- consola.success(`Using existing Commerce Hosting project "${existing.name}"`);
1544
- return existing;
1545
- }
1546
- consola.error(
1547
- "Not reusing the existing project. Re-run with a different --project-name, or pass --use-existing to reuse it."
1848
+ const storefrontOnManagedZone = await isManagedHostingHostname(storefrontHost, context);
1849
+ const report = { crossDomain: true, storefrontOnManagedZone, suggestion };
1850
+ if (storefrontOnManagedZone === false) {
1851
+ consola.info(
1852
+ `To put checkout on this channel's own domain, point ${suggestion?.replace("https://", "") ?? "your checkout subdomain"} at BigCommerce and provision a certificate for it there.`
1548
1853
  );
1549
- process.exit(1);
1854
+ return report;
1550
1855
  }
1551
- try {
1552
- const created = await createProject(name, api.storeHash, api.accessToken, api.apiHost);
1553
- consola.success(`Commerce Hosting project "${created.name}" created successfully`);
1554
- return { uuid: created.uuid, name: created.name, deployment_hostnames: [] };
1555
- } catch (error) {
1556
- if (error instanceof InfrastructureProjectValidationError) {
1557
- consola.error(
1558
- `Failed to create Commerce Hosting project "${name}": ${error.message}
1559
- Re-run with a different --project-name.`
1560
- );
1561
- process.exit(1);
1562
- }
1563
- throw error;
1856
+ if (storefrontOnManagedZone === true) {
1857
+ consola.info(
1858
+ `${storefrontHost} is an auto-generated deployment hostname, and a checkout subdomain of one cannot be issued a certificate \u2014 so no checkout URL can be set for this channel until its storefront is on a custom domain. Add one with \`catalyst domains add\`.`
1859
+ );
1860
+ return report;
1564
1861
  }
1862
+ consola.info(
1863
+ "BigCommerce requires the checkout URL to share a main domain with the storefront. Set one with `catalyst channels update --checkout-url <domain>` once it points at BigCommerce."
1864
+ );
1865
+ return report;
1565
1866
  }
1566
1867
 
1567
1868
  // src/cli/lib/channel-site-flow.ts
@@ -1642,10 +1943,67 @@ async function runChannelSiteUrlFlow(options) {
1642
1943
  );
1643
1944
  const channelLabel = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
1644
1945
  consola.success(`Updated channel ${channelLabel} site URL to ${siteUrl}.`);
1946
+ try {
1947
+ const site = await getChannelSite(
1948
+ channel.id,
1949
+ options.storeHash,
1950
+ options.accessToken,
1951
+ options.apiHost
1952
+ );
1953
+ const report = await warnOnCrossDomainCheckout(site, {
1954
+ storeHash: options.storeHash,
1955
+ accessToken: options.accessToken,
1956
+ apiHost: options.apiHost
1957
+ });
1958
+ if (report.crossDomain && report.storefrontOnManagedZone !== true) {
1959
+ consola.info("Set a checkout URL for this channel with:");
1960
+ consola.log(
1961
+ ` catalyst channels update --channel-id ${channel.id} --checkout-url ${report.suggestion ?? "<domain>"}`
1962
+ );
1963
+ }
1964
+ } catch {
1965
+ }
1966
+ return { channelId: channel.id };
1967
+ }
1968
+
1969
+ // src/cli/lib/channel-checkout-url-flow.ts
1970
+ async function runChannelCheckoutUrlFlow(options) {
1971
+ const channel = options.channelId !== void 0 ? { id: options.channelId, name: options.channelName } : await resolveChannel(options);
1972
+ const label = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
1973
+ let storefrontUrl = options.storefrontUrl;
1974
+ if (!storefrontUrl) {
1975
+ const site = await getChannelSite(
1976
+ channel.id,
1977
+ options.storeHash,
1978
+ options.accessToken,
1979
+ options.apiHost
1980
+ );
1981
+ storefrontUrl = findChannelSiteUrl(site, "primary") ?? site.url;
1982
+ const currentCheckoutUrl = findChannelSiteUrl(site, "checkout");
1983
+ consola.info(`Channel ${label} storefront is ${storefrontUrl}.`);
1984
+ if (currentCheckoutUrl) {
1985
+ consola.info(
1986
+ `Checkout is currently ${currentCheckoutUrl}${site.isCheckoutUrlCustomized ? "" : ", inherited from the default channel"}.`
1987
+ );
1988
+ }
1989
+ }
1990
+ const answer = options.url ?? await input3({
1991
+ message: "Checkout URL for this channel (must share a main domain with the storefront)",
1992
+ default: suggestCheckoutUrl(storefrontUrl)
1993
+ });
1994
+ const checkoutUrl = normalizeCheckoutUrl(answer);
1995
+ await updateChannelCheckoutUrl(
1996
+ channel.id,
1997
+ checkoutUrl,
1998
+ options.storeHash,
1999
+ options.accessToken,
2000
+ options.apiHost
2001
+ );
2002
+ consola.success(`Updated channel ${label} checkout URL to ${checkoutUrl}.`);
1645
2003
  }
1646
2004
 
1647
2005
  // src/cli/lib/create-channel-flow.ts
1648
- import { checkbox, input as input3, select as select3 } from "@inquirer/prompts";
2006
+ import { checkbox, input as input4, select as select3 } from "@inquirer/prompts";
1649
2007
  import { colorize as colorize4 } from "consola/utils";
1650
2008
 
1651
2009
  // src/cli/lib/localization.ts
@@ -1720,7 +2078,7 @@ async function runCreateChannelFlow(options) {
1720
2078
  throw new UserActionableError(nameError);
1721
2079
  }
1722
2080
  }
1723
- const name = options.name ?? await input3({
2081
+ const name = options.name ?? await input4({
1724
2082
  message: "What would you like to name your new channel?",
1725
2083
  validate: (value) => getChannelNameError(value) ?? true
1726
2084
  });
@@ -1781,15 +2139,15 @@ async function runCreateChannelFlow(options) {
1781
2139
 
1782
2140
  // src/cli/lib/env-config.ts
1783
2141
  var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
1784
- var parseEnvAssignment = (input6) => {
1785
- const separatorIndex = input6.indexOf("=");
2142
+ var parseEnvAssignment = (input7) => {
2143
+ const separatorIndex = input7.indexOf("=");
1786
2144
  if (separatorIndex === -1) {
1787
- throw new Error(`Invalid env var format: ${input6}. Expected format: KEY=VALUE`);
2145
+ throw new Error(`Invalid env var format: ${input7}. Expected format: KEY=VALUE`);
1788
2146
  }
1789
- const key = input6.slice(0, separatorIndex).trim();
1790
- const value = input6.slice(separatorIndex + 1).trim();
2147
+ const key = input7.slice(0, separatorIndex).trim();
2148
+ const value = input7.slice(separatorIndex + 1).trim();
1791
2149
  if (!key || !value) {
1792
- throw new Error(`Invalid env var format: ${input6}. Expected format: KEY=VALUE`);
2150
+ throw new Error(`Invalid env var format: ${input7}. Expected format: KEY=VALUE`);
1793
2151
  }
1794
2152
  if (!ENV_KEY_PATTERN.test(key)) {
1795
2153
  throw new Error(
@@ -1829,7 +2187,7 @@ function resolveCredentials(options, config2) {
1829
2187
  // src/cli/lib/write-env.ts
1830
2188
  import { outputFileSync } from "fs-extra/esm";
1831
2189
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1832
- import { join as join6 } from "path";
2190
+ import { join as join7 } from "path";
1833
2191
  var ACTIVE_KEY_LINE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
1834
2192
  var keyFromLine = (line) => {
1835
2193
  const match = ACTIVE_KEY_LINE.exec(line);
@@ -1846,8 +2204,8 @@ var parseEnvValues = (contents) => {
1846
2204
  return values;
1847
2205
  };
1848
2206
  var writeEnv = (projectDir, envVars) => {
1849
- const examplePath = join6(projectDir, ".env.example");
1850
- const localPath = join6(projectDir, ".env.local");
2207
+ const examplePath = join7(projectDir, ".env.example");
2208
+ const localPath = join7(projectDir, ".env.local");
1851
2209
  const existingLocal = existsSync4(localPath) ? parseEnvValues(readFileSync3(localPath, "utf-8")) : /* @__PURE__ */ new Map();
1852
2210
  const resolve2 = (key) => {
1853
2211
  if (key in envVars && envVars[key] !== "") {
@@ -1901,6 +2259,22 @@ var parseChannelId = (value) => {
1901
2259
  }
1902
2260
  return parsed;
1903
2261
  };
2262
+ var parseCheckoutUrl = (value) => {
2263
+ try {
2264
+ return normalizeCheckoutUrl(value);
2265
+ } catch (error) {
2266
+ throw new InvalidArgumentError2(error instanceof Error ? error.message : String(error));
2267
+ }
2268
+ };
2269
+ var CHECKOUT_URL_NOTES = `
2270
+ Checkout is hosted by BigCommerce, so a checkout URL must be a domain you have
2271
+ pointed at BigCommerce with a certificate provisioned there \u2014 not one added with
2272
+ \`catalyst domains add\`, which only routes traffic to this project.
2273
+
2274
+ BigCommerce also requires the checkout URL to share a main domain with the
2275
+ channel's storefront URL, so that sessions carry between the two. This means a
2276
+ custom checkout URL is only possible on a custom storefront domain; channels on
2277
+ an auto-generated deployment hostname use the shared checkout domain.`;
1904
2278
  async function resolveCredentialsWithLogin(options, config2, apiHost) {
1905
2279
  const storeHash = options.storeHash ?? config2.get("storeHash");
1906
2280
  const accessToken = options.accessToken ?? config2.get("accessToken");
@@ -1919,17 +2293,29 @@ async function resolveCredentialsWithLogin(options, config2, apiHost) {
1919
2293
  throw error;
1920
2294
  }
1921
2295
  }
1922
- var update = new Command3("update").configureHelp({ showGlobalOptions: true }).description(
1923
- "Update a BigCommerce channel's site URL to point at one of your project's deployment hostnames."
1924
- ).addHelpText(
2296
+ var update = new Command3("update").configureHelp({ showGlobalOptions: true }).description("Update a BigCommerce channel's storefront and checkout URLs.").addHelpText(
1925
2297
  "after",
1926
- `
2298
+ `${CHECKOUT_URL_NOTES}
2299
+
2300
+ With no flags, this updates the storefront URL and prompts for the hostname.
2301
+ Pass a checkout flag on its own to change only the checkout URL, leaving the
2302
+ storefront URL alone.
2303
+
1927
2304
  Examples:
1928
2305
  # Pick a channel and hostname interactively
1929
2306
  $ catalyst channels update
1930
2307
 
1931
- # Skip both prompts
1932
- $ catalyst channels update --channel-id 123 --hostname my-storefront.example.com`
2308
+ # Point the storefront at a deployment hostname
2309
+ $ catalyst channels update --channel-id 123 --hostname my-storefront.example.com
2310
+
2311
+ # Set both at once
2312
+ $ catalyst channels update --channel-id 123 --hostname my-storefront.example.com --checkout-url checkout.example.com
2313
+
2314
+ # Change only the checkout URL
2315
+ $ catalyst channels update --channel-id 123 --checkout-url checkout.example.com
2316
+
2317
+ # Fall back to the shared checkout domain
2318
+ $ catalyst channels update --channel-id 123 --remove-checkout-url`
1933
2319
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
1934
2320
  new Option3(
1935
2321
  "--channel-id <id>",
@@ -1940,29 +2326,68 @@ Examples:
1940
2326
  "--hostname <hostname>",
1941
2327
  "Skip the hostname prompt and use this hostname directly. Must be one of the project's deployment_hostnames."
1942
2328
  )
2329
+ ).addOption(
2330
+ new Option3(
2331
+ "--checkout-url <url>",
2332
+ "Set the channel's checkout URL. Must share a main domain with the storefront URL."
2333
+ ).argParser(parseCheckoutUrl)
2334
+ ).addOption(
2335
+ new Option3(
2336
+ "--remove-checkout-url",
2337
+ "Remove the channel's custom checkout URL so checkout falls back to the shared checkout domain."
2338
+ ).conflicts("checkoutUrl")
1943
2339
  ).action(async (options) => {
1944
2340
  const config2 = getProjectConfig();
1945
2341
  const apiHost = resolveApiHost(options, config2);
1946
2342
  const { storeHash, accessToken } = resolveCredentials(options, config2);
1947
2343
  await getTelemetry().identify(storeHash);
1948
- try {
1949
- await runChannelSiteUrlFlow({
1950
- storeHash,
1951
- accessToken,
1952
- apiHost,
1953
- projectUuid: options.projectUuid ?? config2.get("projectUuid"),
1954
- channelId: options.channelId,
1955
- hostname: options.hostname
1956
- });
1957
- } catch (error) {
1958
- if (error instanceof NoLinkedProjectError) {
1959
- consola.info(
1960
- "When you're ready to create a project, run `catalyst projects create` or re-run `catalyst channels update`."
2344
+ const touchesCheckout = options.checkoutUrl !== void 0 || options.removeCheckoutUrl === true;
2345
+ const updatesSiteUrl = options.hostname !== void 0 || !touchesCheckout;
2346
+ let channelId = options.channelId;
2347
+ if (updatesSiteUrl) {
2348
+ try {
2349
+ ({ channelId } = await runChannelSiteUrlFlow({
2350
+ storeHash,
2351
+ accessToken,
2352
+ apiHost,
2353
+ projectUuid: options.projectUuid ?? config2.get("projectUuid"),
2354
+ channelId: options.channelId,
2355
+ hostname: options.hostname
2356
+ }));
2357
+ } catch (error) {
2358
+ if (error instanceof NoLinkedProjectError) {
2359
+ consola.info(
2360
+ "When you're ready to create a project, run `catalyst projects create` or re-run `catalyst channels update`."
2361
+ );
2362
+ process.exit(0);
2363
+ return;
2364
+ }
2365
+ throw error;
2366
+ }
2367
+ }
2368
+ if (touchesCheckout) {
2369
+ const channel = channelId === void 0 ? await resolveChannel({ storeHash, accessToken, apiHost }) : { id: channelId, name: void 0 };
2370
+ const label = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
2371
+ if (options.checkoutUrl !== void 0) {
2372
+ const updated = await updateChannelCheckoutUrl(
2373
+ channel.id,
2374
+ options.checkoutUrl,
2375
+ storeHash,
2376
+ accessToken,
2377
+ apiHost
1961
2378
  );
1962
- process.exit(0);
1963
- return;
2379
+ consola.success(`Updated channel ${label} checkout URL to ${options.checkoutUrl}.`);
2380
+ reportChannelSite(updated);
2381
+ await warnOnCrossDomainCheckout(updated, { storeHash, accessToken, apiHost });
2382
+ } else {
2383
+ await deleteChannelCheckoutUrl(channel.id, storeHash, accessToken, apiHost);
2384
+ consola.success(
2385
+ `Removed the custom checkout URL from channel ${label}. Checkout now uses the shared checkout domain.`
2386
+ );
2387
+ const reverted = await getChannelSite(channel.id, storeHash, accessToken, apiHost);
2388
+ reportChannelSite(reverted);
2389
+ await warnOnCrossDomainCheckout(reverted, { storeHash, accessToken, apiHost });
1964
2390
  }
1965
- throw error;
1966
2391
  }
1967
2392
  process.exit(0);
1968
2393
  });
@@ -2120,56 +2545,107 @@ Examples:
2120
2545
  }
2121
2546
  process.exit(0);
2122
2547
  });
2123
- var channels = new Command3("channels").alias("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(create).addCommand(link).addCommand(update);
2548
+ var CHECKOUT_LABEL_WIDTH = "Storefront".length;
2549
+ var row = (label, value) => ` ${label.padEnd(CHECKOUT_LABEL_WIDTH)} ${value}`;
2550
+ function reportChannelSite(site) {
2551
+ const canonical = findChannelSiteUrl(site, "canonical");
2552
+ const checkout = findChannelSiteUrl(site, "checkout");
2553
+ consola.log(row("Storefront", findChannelSiteUrl(site, "primary") ?? site.url));
2554
+ if (canonical) {
2555
+ consola.log(row("Canonical", canonical));
2556
+ }
2557
+ consola.log(row("Checkout", checkout ?? "(none)"));
2558
+ if (!site.isCheckoutUrlCustomized) {
2559
+ consola.info(
2560
+ "This channel has no checkout URL of its own, so checkout uses the default channel's primary URL."
2561
+ );
2562
+ }
2563
+ }
2564
+ async function resolveChannelTarget(options) {
2565
+ const config2 = getProjectConfig();
2566
+ const apiHost = resolveApiHost(options, config2);
2567
+ const { storeHash, accessToken } = resolveCredentials(options, config2);
2568
+ await getTelemetry().identify(storeHash);
2569
+ const channel = await resolveChannel({
2570
+ storeHash,
2571
+ accessToken,
2572
+ apiHost,
2573
+ channelId: options.channelId
2574
+ });
2575
+ return {
2576
+ config: config2,
2577
+ apiHost,
2578
+ storeHash,
2579
+ accessToken,
2580
+ channel,
2581
+ label: channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id)
2582
+ };
2583
+ }
2584
+ var info = new Command3("info").configureHelp({ showGlobalOptions: true }).description("Show a channel's site URLs \u2014 storefront, canonical and checkout.").addHelpText(
2585
+ "after",
2586
+ `${CHECKOUT_URL_NOTES}
2587
+
2588
+ Examples:
2589
+ # Pick a channel interactively; the same as bare \`catalyst channels\`
2590
+ $ catalyst channels info
2591
+
2592
+ # Target a channel directly
2593
+ $ catalyst channels info --channel-id 123`
2594
+ ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(
2595
+ new Option3(
2596
+ "--channel-id <id>",
2597
+ "Skip the channel prompt and target this channel directly."
2598
+ ).argParser(parseChannelId)
2599
+ ).action(async (options) => {
2600
+ const { apiHost, storeHash, accessToken, channel, label } = await resolveChannelTarget(options);
2601
+ consola.start("Fetching channel site...");
2602
+ const site = await getChannelSite(channel.id, storeHash, accessToken, apiHost);
2603
+ consola.success(`Channel ${label}:`);
2604
+ reportChannelSite(site);
2605
+ const report = await warnOnCrossDomainCheckout(site, { storeHash, accessToken, apiHost });
2606
+ if (report.crossDomain && report.storefrontOnManagedZone !== true) {
2607
+ const suggested = report.suggestion ?? "<domain>";
2608
+ if (!process.stdin.isTTY) {
2609
+ consola.info(
2610
+ `Set one with \`catalyst channels update --channel-id ${channel.id} --checkout-url ${suggested}\`.`
2611
+ );
2612
+ process.exit(0);
2613
+ return;
2614
+ }
2615
+ const shouldSet = await confirm3({
2616
+ message: "Set a checkout URL for this channel now?",
2617
+ default: true
2618
+ });
2619
+ if (shouldSet) {
2620
+ await runChannelCheckoutUrlFlow({
2621
+ storeHash,
2622
+ accessToken,
2623
+ apiHost,
2624
+ // Already chosen above; don't ask again.
2625
+ channelId: channel.id,
2626
+ channelName: channel.name,
2627
+ storefrontUrl: findChannelSiteUrl(site, "primary") ?? site.url
2628
+ });
2629
+ process.exit(0);
2630
+ return;
2631
+ }
2632
+ consola.info(
2633
+ `When you're ready, run \`catalyst channels update --channel-id ${channel.id} --checkout-url <domain>\`.`
2634
+ );
2635
+ }
2636
+ process.exit(0);
2637
+ });
2638
+ var channels = new Command3("channels").alias("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(info, { isDefault: true }).addCommand(create).addCommand(link).addCommand(update);
2124
2639
 
2125
2640
  // src/cli/commands/create.ts
2126
2641
  import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as Option4 } from "@commander-js/extra-typings";
2127
- import { input as input4, select as select5 } from "@inquirer/prompts";
2642
+ import { input as input5, select as select5 } from "@inquirer/prompts";
2128
2643
  import { execSync as execSync2 } from "child_process";
2129
2644
  import { colorize as colorize6 } from "consola/utils";
2130
2645
  import { pathExistsSync } from "fs-extra/esm";
2131
2646
  import kebabCase from "lodash.kebabcase";
2132
2647
  import { join as join11 } from "path";
2133
2648
 
2134
- // src/cli/lib/detect-package-manager.ts
2135
- import { access } from "fs/promises";
2136
- import { join as join7 } from "path";
2137
- import { detectPackageManager as detectFromDir } from "nypm";
2138
- var PROJECT_LOCKFILES = [
2139
- ["pnpm-lock.yaml", "pnpm"],
2140
- ["yarn.lock", "yarn"],
2141
- ["bun.lock", "bun"],
2142
- ["bun.lockb", "bun"],
2143
- ["package-lock.json", "npm"]
2144
- ];
2145
- var detectLockfileManager = async (dir) => {
2146
- const found = await Promise.all(
2147
- PROJECT_LOCKFILES.map(
2148
- async ([file, manager]) => access(join7(dir, file)).then(() => manager).catch(() => null)
2149
- )
2150
- );
2151
- return found.find((manager) => manager !== null) ?? null;
2152
- };
2153
- var detectPackageManager = () => {
2154
- const userAgent = process.env.npm_config_user_agent ?? "";
2155
- const name = userAgent.split(" ")[0]?.split("/")[0];
2156
- if (name === "pnpm" || name === "yarn" || name === "bun") {
2157
- return name;
2158
- }
2159
- if (process.versions.bun) {
2160
- return "bun";
2161
- }
2162
- return "npm";
2163
- };
2164
- var detectProjectPackageManager = async (projectDir) => {
2165
- const detected = await detectFromDir(projectDir);
2166
- const name = detected?.name;
2167
- if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") {
2168
- return name;
2169
- }
2170
- return "npm";
2171
- };
2172
-
2173
2649
  // src/cli/lib/extract-catalyst.ts
2174
2650
  import { mkdirSync as mkdirSync2 } from "fs";
2175
2651
  import { Readable } from "stream";
@@ -2239,43 +2715,6 @@ var initGitRepo = (projectDir) => {
2239
2715
  }
2240
2716
  };
2241
2717
 
2242
- // src/cli/lib/install-dependencies.ts
2243
- import { installDependencies as installDeps } from "nypm";
2244
- import yoctoSpinner2 from "yocto-spinner";
2245
- var restoreEnv = (key, previous) => {
2246
- if (previous === void 0) {
2247
- Reflect.deleteProperty(process.env, key);
2248
- } else {
2249
- process.env[key] = previous;
2250
- }
2251
- };
2252
- var withQuietInstallEnv = async (run2) => {
2253
- var _a, _b;
2254
- const prevNodeNoWarnings = process.env.NODE_NO_WARNINGS;
2255
- const prevDownloadPrompt = process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT;
2256
- (_a = process.env).NODE_NO_WARNINGS ?? (_a.NODE_NO_WARNINGS = "1");
2257
- (_b = process.env).COREPACK_ENABLE_DOWNLOAD_PROMPT ?? (_b.COREPACK_ENABLE_DOWNLOAD_PROMPT = "0");
2258
- try {
2259
- return await run2();
2260
- } finally {
2261
- restoreEnv("NODE_NO_WARNINGS", prevNodeNoWarnings);
2262
- restoreEnv("COREPACK_ENABLE_DOWNLOAD_PROMPT", prevDownloadPrompt);
2263
- }
2264
- };
2265
- var installDependencies = async (projectDir, packageManager = "pnpm") => {
2266
- const spinner = yoctoSpinner2().start(
2267
- `Installing dependencies with ${packageManager}. This could take a minute...`
2268
- );
2269
- try {
2270
- await withQuietInstallEnv(() => installDeps({ cwd: projectDir, silent: true, packageManager }));
2271
- spinner.success("Dependencies installed successfully.");
2272
- } catch (error) {
2273
- const message = error instanceof Error ? error.message : "unknown error";
2274
- spinner.error(`Failed to install dependencies: ${message}`);
2275
- throw error;
2276
- }
2277
- };
2278
-
2279
2718
  // src/cli/lib/rewrite-core-package.ts
2280
2719
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2281
2720
  import { join as join9 } from "path";
@@ -2437,7 +2876,7 @@ async function setupProject(options) {
2437
2876
  projectDir = targetDir;
2438
2877
  return true;
2439
2878
  };
2440
- await input4({
2879
+ await input5({
2441
2880
  message: "What do you want to name your project directory?",
2442
2881
  default: "my-catalyst-app",
2443
2882
  validate: validateProjectName
@@ -2582,7 +3021,6 @@ Examples:
2582
3021
  if (useCommerceHosting && accessToken) {
2583
3022
  envVars.BIGCOMMERCE_ACCESS_TOKEN = accessToken;
2584
3023
  }
2585
- if (accessToken) envVars.CATALYST_ACCESS_TOKEN = accessToken;
2586
3024
  let commerceHostingProject;
2587
3025
  if (useCommerceHosting && storeHash && accessToken) {
2588
3026
  const apiHost = `api.${options.bigcommerceHostname}`;
@@ -2906,7 +3344,7 @@ Examples:
2906
3344
  });
2907
3345
 
2908
3346
  // src/cli/commands/deploy.ts
2909
- import { confirm as confirm3 } from "@inquirer/prompts";
3347
+ import { confirm as confirm4 } from "@inquirer/prompts";
2910
3348
  import AdmZip from "adm-zip";
2911
3349
  import { Command as Command6, Option as Option6 } from "commander";
2912
3350
  import { colorize as colorize7 } from "consola/utils";
@@ -3174,6 +3612,9 @@ Example:
3174
3612
  ).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option(
3175
3613
  "--update-site-url",
3176
3614
  "After a successful deploy, prompt to update a channel's site URL to the new hostname."
3615
+ ).option(
3616
+ "--update-checkout-url",
3617
+ "After a successful deploy, prompt to update a channel's checkout URL."
3177
3618
  ).addOption(
3178
3619
  new Option6(
3179
3620
  "--secret <value>",
@@ -3235,7 +3676,7 @@ Example:
3235
3676
  consola.success(`Linked project "${selected.name}".`);
3236
3677
  }
3237
3678
  if (!getProjectState().isTransformed) {
3238
- const shouldSetup = await confirm3({
3679
+ const shouldSetup = await confirm4({
3239
3680
  message: "Your project is not yet set up for Commerce Hosting deployments. Would you like to run the Commerce Hosting setup now?",
3240
3681
  default: true
3241
3682
  });
@@ -3250,6 +3691,12 @@ Example:
3250
3691
  await installDependencies(projectDir, packageManager);
3251
3692
  } else {
3252
3693
  await cleanupCloudflareIncompatibilities(process.cwd());
3694
+ if (options.prebuilt) {
3695
+ const projectDir = process.cwd();
3696
+ await reconcileOpenNextVersion(projectDir, await detectProjectPackageManager(projectDir), {
3697
+ canUpgrade: false
3698
+ });
3699
+ }
3253
3700
  }
3254
3701
  if (options.prebuilt) {
3255
3702
  const distDir = join13(process.cwd(), ".bigcommerce", "dist");
@@ -3300,29 +3747,44 @@ Example:
3300
3747
  accessToken,
3301
3748
  apiHost
3302
3749
  );
3303
- if (!options.updateSiteUrl) {
3304
- return;
3305
- }
3306
- try {
3307
- await runChannelSiteUrlFlow({
3308
- storeHash,
3309
- accessToken,
3310
- apiHost,
3311
- projectUuid,
3312
- preferHostname: deploymentHostname
3313
- });
3314
- } catch (error) {
3750
+ const warnChannelFlowFailed = (what, error) => {
3315
3751
  consola.warn(
3316
- `Failed to update channel site URL: ${error instanceof Error ? error.message : String(error)}`
3752
+ `Failed to update channel ${what}: ${error instanceof Error ? error.message : String(error)}`
3317
3753
  );
3318
3754
  consola.info(
3319
3755
  "Update it manually in the control panel, or re-run `catalyst auth login` if the token is missing the store_channel_settings scope."
3320
3756
  );
3757
+ };
3758
+ let resolvedChannelId;
3759
+ if (options.updateSiteUrl) {
3760
+ try {
3761
+ ({ channelId: resolvedChannelId } = await runChannelSiteUrlFlow({
3762
+ storeHash,
3763
+ accessToken,
3764
+ apiHost,
3765
+ projectUuid,
3766
+ preferHostname: deploymentHostname
3767
+ }));
3768
+ } catch (error) {
3769
+ warnChannelFlowFailed("site URL", error);
3770
+ }
3771
+ }
3772
+ if (options.updateCheckoutUrl) {
3773
+ try {
3774
+ await runChannelCheckoutUrlFlow({
3775
+ storeHash,
3776
+ accessToken,
3777
+ apiHost,
3778
+ channelId: resolvedChannelId
3779
+ });
3780
+ } catch (error) {
3781
+ warnChannelFlowFailed("checkout URL", error);
3782
+ }
3321
3783
  }
3322
3784
  });
3323
3785
 
3324
3786
  // src/cli/commands/domains.ts
3325
- import { confirm as confirm4, select as select6 } from "@inquirer/prompts";
3787
+ import { confirm as confirm5, select as select6 } from "@inquirer/prompts";
3326
3788
  import { Command as Command7, Option as Option7 } from "commander";
3327
3789
  import { colorize as colorize9 } from "consola/utils";
3328
3790
 
@@ -3652,7 +4114,7 @@ async function createDomain(domain, projectUuid, storeHash, accessToken, apiHost
3652
4114
  const result = await response.json();
3653
4115
  return createdDomainResponseSchema.parse(result).data;
3654
4116
  }
3655
- async function getDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
4117
+ async function getDomain2(domain, projectUuid, storeHash, accessToken, apiHost) {
3656
4118
  const response = await fetch(domainUrl(storeHash, projectUuid, domain, apiHost), {
3657
4119
  method: "GET",
3658
4120
  headers: authHeaders3(accessToken)
@@ -3789,7 +4251,7 @@ function formatPointingRecords(records) {
3789
4251
  if (rows.length === 0) {
3790
4252
  return null;
3791
4253
  }
3792
- return rows.map((row) => ` ${row}`).join("\n");
4254
+ return rows.map((row2) => ` ${row2}`).join("\n");
3793
4255
  }
3794
4256
  function formatOwnershipVerification(record) {
3795
4257
  const lines = [
@@ -3812,10 +4274,10 @@ async function waitForDomainVerification({
3812
4274
  timeoutMs = WAIT_TIMEOUT_MS
3813
4275
  }) {
3814
4276
  const startedAt = Date.now();
3815
- let current = await getDomain(domain, projectUuid, storeHash, accessToken, apiHost);
4277
+ let current = await getDomain2(domain, projectUuid, storeHash, accessToken, apiHost);
3816
4278
  while (current.verification_status === "pending" && Date.now() - startedAt < timeoutMs) {
3817
4279
  await sleep(intervalMs);
3818
- current = await getDomain(domain, projectUuid, storeHash, accessToken, apiHost);
4280
+ current = await getDomain2(domain, projectUuid, storeHash, accessToken, apiHost);
3819
4281
  }
3820
4282
  return current;
3821
4283
  }
@@ -3933,7 +4395,7 @@ Examples:
3933
4395
  const context = resolveDomainCommandContext(options);
3934
4396
  await getTelemetry().identify(context.storeHash);
3935
4397
  consola.start(`Fetching status for ${domain}...`);
3936
- let result = await getDomain(
4398
+ let result = await getDomain2(
3937
4399
  domain,
3938
4400
  context.projectUuid,
3939
4401
  context.storeHash,
@@ -3984,7 +4446,7 @@ Examples:
3984
4446
  throw error;
3985
4447
  }
3986
4448
  consola.success(`Domain ${domain} claimed.`);
3987
- let result = await getDomain(
4449
+ let result = await getDomain2(
3988
4450
  domain,
3989
4451
  context.projectUuid,
3990
4452
  context.storeHash,
@@ -4064,7 +4526,7 @@ Examples:
4064
4526
  context.apiHost
4065
4527
  );
4066
4528
  consola.success(`Domain ${domain} transferred.`);
4067
- let result = await getDomain(
4529
+ let result = await getDomain2(
4068
4530
  domain,
4069
4531
  newProjectUuid,
4070
4532
  context.storeHash,
@@ -4096,7 +4558,7 @@ Examples:
4096
4558
  const context = resolveDomainCommandContext(options);
4097
4559
  await getTelemetry().identify(context.storeHash);
4098
4560
  consola.start(`Fetching status for ${domain}...`);
4099
- const current = await getDomain(
4561
+ const current = await getDomain2(
4100
4562
  domain,
4101
4563
  context.projectUuid,
4102
4564
  context.storeHash,
@@ -4104,7 +4566,7 @@ Examples:
4104
4566
  context.apiHost
4105
4567
  );
4106
4568
  if (current.verification_status === "verified" && !options.force) {
4107
- const confirmed = await confirm4({
4569
+ const confirmed = await confirm5({
4108
4570
  message: `Remove active domain ${current.domain}? Traffic may stop routing to this project.`,
4109
4571
  default: false
4110
4572
  });
@@ -4608,7 +5070,7 @@ Examples:
4608
5070
  var logs = new Command9("logs").alias("log").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
4609
5071
 
4610
5072
  // src/cli/commands/projects.ts
4611
- import { confirm as confirm5, input as input5, select as select7 } from "@inquirer/prompts";
5073
+ import { confirm as confirm6, input as input6, select as select7 } from "@inquirer/prompts";
4612
5074
  import { Command as Command10 } from "commander";
4613
5075
  import { colorize as colorize11 } from "consola/utils";
4614
5076
  async function offerCommerceHostingSetup(projectUuid, credentials) {
@@ -4617,7 +5079,7 @@ async function offerCommerceHostingSetup(projectUuid, credentials) {
4617
5079
  await cleanupCloudflareIncompatibilities(projectDir);
4618
5080
  return;
4619
5081
  }
4620
- const shouldSetup = await confirm5({
5082
+ const shouldSetup = await confirm6({
4621
5083
  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?",
4622
5084
  default: true
4623
5085
  });
@@ -4696,7 +5158,7 @@ Example:
4696
5158
  }
4697
5159
  }
4698
5160
  await getTelemetry().identify(storeHash);
4699
- const newProjectName = await input5({ message: "Enter a name for the new project:" });
5161
+ const newProjectName = await input6({ message: "Enter a name for the new project:" });
4700
5162
  const data = await createProject(newProjectName, storeHash, accessToken, apiHost);
4701
5163
  consola.success(`Project "${data.name}" created successfully.`);
4702
5164
  consola.start("Writing project UUID to .bigcommerce/project.json...");
@@ -4813,7 +5275,7 @@ Examples:
4813
5275
  }
4814
5276
  if (!options.force) {
4815
5277
  const label = targetName ? `"${targetName}" (${targetUuid})` : targetUuid;
4816
- const confirmed = await confirm5({
5278
+ const confirmed = await confirm6({
4817
5279
  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.`,
4818
5280
  default: false
4819
5281
  });
@@ -4936,7 +5398,7 @@ Examples:
4936
5398
 
4937
5399
  // src/cli/commands/upgrade.ts
4938
5400
  import { Command as Command13, Option as Option10 } from "@commander-js/extra-typings";
4939
- import { confirm as confirm6 } from "@inquirer/prompts";
5401
+ import { confirm as confirm7 } from "@inquirer/prompts";
4940
5402
  import { execa as execa4 } from "execa";
4941
5403
  import {
4942
5404
  access as access3,
@@ -4952,7 +5414,7 @@ import {
4952
5414
  } from "fs/promises";
4953
5415
  import { homedir, tmpdir } from "os";
4954
5416
  import { dirname as dirname5, join as join15, relative as relative2 } from "path";
4955
- import { minVersion, satisfies, lt as semverLt, validRange, valid as validSemver2 } from "semver";
5417
+ import { minVersion as minVersion2, satisfies as satisfies2, lt as semverLt2, validRange as validRange2, valid as validSemver2 } from "semver";
4956
5418
  import yoctoSpinner4 from "yocto-spinner";
4957
5419
  import { z as z17 } from "zod";
4958
5420
  var CorePackageJson = z17.object({
@@ -5087,15 +5549,15 @@ async function normalizeWorkspaceDeps(ourPkgRaw, baseDir, theirsDir, baseVersion
5087
5549
  var migrateWorkspaceDeps = (raw, findings) => findings.reduce((acc, { name, version: version2 }) => rewriteWorkspaceSpecifier(acc, name, version2), raw);
5088
5550
  async function findStaleCli(pkgRaw) {
5089
5551
  const range = readDepSpecifiers(pkgRaw)[CATALYST_CLI_PACKAGE];
5090
- if (range === void 0 || !validRange(range)) return null;
5552
+ if (range === void 0 || !validRange2(range)) return null;
5091
5553
  const body = await fetch(`https://registry.npmjs.org/${CATALYST_CLI_PACKAGE}/latest`, {
5092
5554
  // A hanging registry would otherwise stall the CLI indefinitely.
5093
5555
  signal: AbortSignal.timeout(5e3)
5094
5556
  }).then((res) => res.ok ? res.json() : null).catch(() => null);
5095
5557
  const latest = z17.object({ version: z17.string() }).safeParse(body).data?.version;
5096
- if (latest === void 0 || !validSemver2(latest) || satisfies(latest, range)) return null;
5097
- const current = minVersion(range)?.version;
5098
- return current !== void 0 && semverLt(current, latest) ? { current, latest } : null;
5558
+ if (latest === void 0 || !validSemver2(latest) || satisfies2(latest, range)) return null;
5559
+ const current = minVersion2(range)?.version;
5560
+ return current !== void 0 && semverLt2(current, latest) ? { current, latest } : null;
5099
5561
  }
5100
5562
  async function mergeFile(oursPath, basePath, theirsPath) {
5101
5563
  const merged = await execa4(
@@ -5450,7 +5912,7 @@ async function resolveBaseRef(project, fromOption, assumeYes) {
5450
5912
  const guess = `${family}@${project.pkg.version}`;
5451
5913
  consola.warn(`No \`catalyst.ref\` found in package.json. Inferred base: ${guess}`);
5452
5914
  if (assumeYes) return guess;
5453
- const ok = await confirm6({ message: `Upgrade from ${guess}?`, default: true }).catch(() => false);
5915
+ const ok = await confirm7({ message: `Upgrade from ${guess}?`, default: true }).catch(() => false);
5454
5916
  if (!ok) {
5455
5917
  consola.info("Re-run with `--from <ref>` to set the base version explicitly.");
5456
5918
  return null;
@@ -5712,7 +6174,7 @@ to raise the GitHub API rate limit.`
5712
6174
  printWorkspaceFindings(workspaceDeps);
5713
6175
  let migratedDeps = 0;
5714
6176
  if (workspaceDeps.length) {
5715
- const accepted = stampedText !== null && (options.yes === true || Boolean(process.stdin.isTTY) && await confirm6({
6177
+ const accepted = stampedText !== null && (options.yes === true || Boolean(process.stdin.isTTY) && await confirm7({
5716
6178
  message: "Replace them with the published versions?",
5717
6179
  default: true
5718
6180
  }).catch(() => false));