@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 +1134 -672
- package/dist/cli.js.map +1 -1
- package/package.json +9 -3
- package/templates/open-next.config.ts +8 -1
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.
|
|
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: "^
|
|
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
|
|
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/
|
|
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 {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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/
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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
|
|
888
|
+
var projectJsonSchema = z6.looseObject({});
|
|
889
|
+
var readProjectJson = (path) => {
|
|
890
|
+
if (!existsSync2(path)) return {};
|
|
862
891
|
try {
|
|
863
|
-
|
|
892
|
+
const parsed = projectJsonSchema.safeParse(JSON.parse(readFileSync(path, "utf-8")));
|
|
893
|
+
return parsed.success ? parsed.data : {};
|
|
864
894
|
} catch {
|
|
865
|
-
return
|
|
895
|
+
return {};
|
|
866
896
|
}
|
|
867
897
|
};
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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
|
|
905
|
-
const
|
|
906
|
-
|
|
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
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
-
|
|
1055
|
-
|
|
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
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
-
|
|
1214
|
-
|
|
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 =
|
|
1359
|
-
const pkg = corePackageJsonSchema.parse(JSON.parse(
|
|
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 =
|
|
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
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
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
|
-
|
|
1854
|
+
return report;
|
|
1550
1855
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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
|
|
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
|
|
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 = (
|
|
1785
|
-
const separatorIndex =
|
|
2142
|
+
var parseEnvAssignment = (input7) => {
|
|
2143
|
+
const separatorIndex = input7.indexOf("=");
|
|
1786
2144
|
if (separatorIndex === -1) {
|
|
1787
|
-
throw new Error(`Invalid env var format: ${
|
|
2145
|
+
throw new Error(`Invalid env var format: ${input7}. Expected format: KEY=VALUE`);
|
|
1788
2146
|
}
|
|
1789
|
-
const key =
|
|
1790
|
-
const value =
|
|
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: ${
|
|
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
|
|
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 =
|
|
1850
|
-
const localPath =
|
|
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
|
-
#
|
|
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
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
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
|
-
|
|
1963
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 || !
|
|
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) ||
|
|
5097
|
-
const current =
|
|
5098
|
-
return current !== void 0 &&
|
|
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
|
|
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
|
|
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));
|