@bigcommerce/catalyst 1.2.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/README.md +1 -1
- package/dist/cli.js +1648 -866
- package/dist/cli.js.map +1 -1
- package/package.json +9 -3
- package/templates/open-next.config.ts +67 -6
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
|
|
|
@@ -594,9 +600,9 @@ async function deleteProject(projectUuid, storeHash, accessToken, apiHost) {
|
|
|
594
600
|
// src/cli/lib/project-config.ts
|
|
595
601
|
import Conf2 from "conf";
|
|
596
602
|
import { join } from "path";
|
|
597
|
-
function getProjectConfig() {
|
|
603
|
+
function getProjectConfig(cwd = process.cwd()) {
|
|
598
604
|
return new Conf2({
|
|
599
|
-
cwd: join(
|
|
605
|
+
cwd: join(cwd, ".bigcommerce"),
|
|
600
606
|
projectSuffix: "",
|
|
601
607
|
configName: "project",
|
|
602
608
|
schema: {
|
|
@@ -647,7 +653,7 @@ var resolveProjectUuid = (options) => {
|
|
|
647
653
|
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
648
654
|
if (!projectUuid) {
|
|
649
655
|
throw new Error(
|
|
650
|
-
"Project UUID is required. Please run either `catalyst
|
|
656
|
+
"Project UUID is required. Please run either `catalyst projects link` or `catalyst projects create` or this command again with --project-uuid <uuid>."
|
|
651
657
|
);
|
|
652
658
|
}
|
|
653
659
|
return projectUuid;
|
|
@@ -703,8 +709,8 @@ Example:
|
|
|
703
709
|
const store = await fetchStoreProfile2(storeHash, accessToken, apiHost);
|
|
704
710
|
const projectUuid = config2.get("projectUuid");
|
|
705
711
|
if (projectUuid) {
|
|
706
|
-
const
|
|
707
|
-
const linkedProject =
|
|
712
|
+
const projects2 = await fetchProjects(storeHash, accessToken, apiHost);
|
|
713
|
+
const linkedProject = projects2.find((p) => p.uuid === projectUuid);
|
|
708
714
|
if (linkedProject) {
|
|
709
715
|
consola.info(
|
|
710
716
|
`Logged in to ${store.store_name} (${storeHash}), connected to project ${linkedProject.name} (${projectUuid})`
|
|
@@ -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,223 +845,679 @@ 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 execa2(
|
|
1027
|
-
"pnpm",
|
|
1028
|
-
[
|
|
1029
|
-
"dlx",
|
|
1030
|
-
`wrangler@${wranglerVersion}`,
|
|
1031
|
-
"deploy",
|
|
1032
|
-
"--config",
|
|
1033
|
-
join4(coreDir, ".bigcommerce", "wrangler.jsonc"),
|
|
1034
|
-
"--keep-vars",
|
|
1035
|
-
"--outdir",
|
|
1036
|
-
bigcommerceDistDir,
|
|
1037
|
-
"--dry-run"
|
|
1038
|
-
],
|
|
1039
|
-
{
|
|
1040
|
-
stdout: ["pipe", "inherit"],
|
|
1041
|
-
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.");
|
|
1042
940
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
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
|
|
1048
978
|
});
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
979
|
+
if (!shouldUpgrade) {
|
|
980
|
+
consola.info(`Keeping @opennextjs/cloudflare ${current}. Update it later with ${manualStep}.`);
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
pkg.dependencies = {
|
|
984
|
+
...pkg.dependencies,
|
|
985
|
+
"@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
|
|
986
|
+
};
|
|
987
|
+
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
988
|
+
consola.info(`Updated @opennextjs/cloudflare to ${OPENNEXT_CLOUDFLARE_VERSION}.`);
|
|
989
|
+
return true;
|
|
990
|
+
};
|
|
991
|
+
var setupCommerceHosting = async ({
|
|
992
|
+
projectDir,
|
|
993
|
+
projectUuid,
|
|
994
|
+
storeHash,
|
|
995
|
+
accessToken
|
|
996
|
+
}) => {
|
|
997
|
+
await cleanupCloudflareIncompatibilities(projectDir);
|
|
998
|
+
const corePackageJsonPath = join2(projectDir, "package.json");
|
|
999
|
+
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync(corePackageJsonPath, "utf-8")));
|
|
1000
|
+
pkg.dependencies = {
|
|
1001
|
+
...pkg.dependencies,
|
|
1002
|
+
"@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
|
|
1003
|
+
};
|
|
1004
|
+
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1005
|
+
const projectJsonPath = join2(projectDir, ".bigcommerce", "project.json");
|
|
1006
|
+
const projectJson = {
|
|
1007
|
+
...readProjectJson(projectJsonPath),
|
|
1008
|
+
projectUuid,
|
|
1009
|
+
framework: "catalyst"
|
|
1010
|
+
};
|
|
1011
|
+
if (storeHash) projectJson.storeHash = storeHash;
|
|
1012
|
+
if (accessToken) projectJson.accessToken = accessToken;
|
|
1013
|
+
writeJson(projectJsonPath, projectJson);
|
|
1014
|
+
convertProxyToMiddleware(projectDir);
|
|
1015
|
+
};
|
|
1016
|
+
var NoLinkedProjectError = class extends Error {
|
|
1017
|
+
constructor() {
|
|
1018
|
+
super("No infrastructure project linked: user declined to create one.");
|
|
1019
|
+
this.name = "NoLinkedProjectError";
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
async function promptForNewProjectName(api) {
|
|
1023
|
+
const newProjectName = await input2({ message: "Enter a name for the new project:" });
|
|
1024
|
+
const data = await createProject(newProjectName, api.storeHash, api.accessToken, api.apiHost);
|
|
1025
|
+
consola.success(`Project "${data.name}" created successfully.`);
|
|
1026
|
+
return { uuid: data.uuid, name: data.name, deployment_hostnames: [] };
|
|
1027
|
+
}
|
|
1028
|
+
async function selectOrCreateInfrastructureProject(api, linkedProjectUuid) {
|
|
1029
|
+
consola.start("Fetching projects...");
|
|
1030
|
+
const existingProjects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
|
|
1031
|
+
consola.success("Projects fetched.");
|
|
1032
|
+
if (existingProjects.length === 0) {
|
|
1033
|
+
const shouldCreate = await confirm2({
|
|
1034
|
+
message: "There are not any hosting projects that you can link to yet. Would you like to create one?",
|
|
1035
|
+
default: true
|
|
1036
|
+
});
|
|
1037
|
+
if (!shouldCreate) {
|
|
1038
|
+
throw new NoLinkedProjectError();
|
|
1039
|
+
}
|
|
1040
|
+
return promptForNewProjectName(api);
|
|
1041
|
+
}
|
|
1042
|
+
const choices = [
|
|
1043
|
+
...existingProjects.map((p) => ({
|
|
1044
|
+
name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize3("green", "[linked]")}` : p.name,
|
|
1045
|
+
value: p.uuid,
|
|
1046
|
+
description: p.uuid
|
|
1047
|
+
})),
|
|
1048
|
+
{
|
|
1049
|
+
name: "Create a new project",
|
|
1050
|
+
value: "create",
|
|
1051
|
+
description: "Create a new hosting project for this Catalyst storefront."
|
|
1052
|
+
}
|
|
1053
|
+
];
|
|
1054
|
+
const selected = await select({
|
|
1055
|
+
message: "Select a project or create a new project (Press <enter> to select).",
|
|
1056
|
+
choices
|
|
1057
|
+
});
|
|
1058
|
+
if (selected === "create") {
|
|
1059
|
+
return promptForNewProjectName(api);
|
|
1060
|
+
}
|
|
1061
|
+
const matched = existingProjects.find((p) => p.uuid === selected);
|
|
1062
|
+
if (!matched) {
|
|
1063
|
+
throw new Error(`Selected project ${String(selected)} not found in fetched list.`);
|
|
1064
|
+
}
|
|
1065
|
+
return matched;
|
|
1066
|
+
}
|
|
1067
|
+
async function promptForCommerceHostingProject(api, defaultName, autoUseDefaultName, useExistingOnCollision) {
|
|
1068
|
+
const existingProjects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
|
|
1069
|
+
const takenNames = existingProjects.map((project) => project.name);
|
|
1070
|
+
if (autoUseDefaultName) {
|
|
1071
|
+
return autoCreateCommerceHostingProject(
|
|
1072
|
+
api,
|
|
1073
|
+
defaultName,
|
|
1074
|
+
existingProjects,
|
|
1075
|
+
useExistingOnCollision
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
const conflict = existingProjects.find(
|
|
1079
|
+
(project) => project.name.toLowerCase() === defaultName.toLowerCase()
|
|
1080
|
+
);
|
|
1081
|
+
if (!conflict) {
|
|
1082
|
+
return autoCreateCommerceHostingProject(
|
|
1083
|
+
api,
|
|
1084
|
+
defaultName,
|
|
1085
|
+
existingProjects,
|
|
1086
|
+
useExistingOnCollision
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
const hasOtherProjects = existingProjects.length > 1;
|
|
1090
|
+
const choices = [
|
|
1091
|
+
{ name: `Use "${conflict.name}"`, value: "use-named" }
|
|
1092
|
+
];
|
|
1093
|
+
if (hasOtherProjects) {
|
|
1094
|
+
choices.push({ name: "Select from my projects", value: "select-from-list" });
|
|
1095
|
+
}
|
|
1096
|
+
choices.push({ name: "Create a new project", value: "create" });
|
|
1097
|
+
const action = await select({
|
|
1098
|
+
message: hasOtherProjects ? `It looks like you already have an existing Commerce Hosting project named "${conflict.name}". Would you like to use it, select from your projects, or create a new one?` : `It looks like you already have an existing Commerce Hosting project named "${conflict.name}". Would you like to use it, or create a new one?`,
|
|
1099
|
+
choices
|
|
1100
|
+
});
|
|
1101
|
+
if (action === "use-named") {
|
|
1102
|
+
consola.success(`Using existing Commerce Hosting project "${conflict.name}"`);
|
|
1103
|
+
return conflict;
|
|
1104
|
+
}
|
|
1105
|
+
if (action === "create") {
|
|
1106
|
+
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1107
|
+
}
|
|
1108
|
+
const selected = await select({
|
|
1109
|
+
message: "Which Commerce Hosting project would you like to use?",
|
|
1110
|
+
choices: [
|
|
1111
|
+
...existingProjects.map((project) => ({
|
|
1112
|
+
name: project.name,
|
|
1113
|
+
value: project,
|
|
1114
|
+
description: project.uuid
|
|
1115
|
+
})),
|
|
1116
|
+
new Separator(),
|
|
1117
|
+
{ name: "Create a new project", value: "create-new" }
|
|
1118
|
+
]
|
|
1119
|
+
});
|
|
1120
|
+
if (selected === "create-new") {
|
|
1121
|
+
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1122
|
+
}
|
|
1123
|
+
consola.success(`Using existing Commerce Hosting project "${selected.name}"`);
|
|
1124
|
+
return selected;
|
|
1125
|
+
}
|
|
1126
|
+
async function promptAndCreateCommerceHostingProject(api, takenNames, defaultName) {
|
|
1127
|
+
const projectName = await input2({
|
|
1128
|
+
message: "What would you like to name your Commerce Hosting project?",
|
|
1129
|
+
default: defaultName,
|
|
1130
|
+
validate: (value) => {
|
|
1131
|
+
const trimmed = value.trim();
|
|
1132
|
+
if (!trimmed) return "Project name is required";
|
|
1133
|
+
const conflict = takenNames.find((taken) => taken.toLowerCase() === trimmed.toLowerCase());
|
|
1134
|
+
if (conflict) {
|
|
1135
|
+
return `A Commerce Hosting project named "${conflict}" already exists`;
|
|
1136
|
+
}
|
|
1137
|
+
return true;
|
|
1138
|
+
},
|
|
1139
|
+
theme: {
|
|
1140
|
+
style: {
|
|
1141
|
+
help: () => colorize3(
|
|
1142
|
+
"dim",
|
|
1143
|
+
"(The project that hosts your storefront on Commerce \u2014 often matches your folder name.)"
|
|
1144
|
+
)
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
try {
|
|
1149
|
+
const created = await createProject(
|
|
1150
|
+
projectName.trim(),
|
|
1151
|
+
api.storeHash,
|
|
1152
|
+
api.accessToken,
|
|
1153
|
+
api.apiHost
|
|
1154
|
+
);
|
|
1155
|
+
consola.success(`Commerce Hosting project "${created.name}" created successfully`);
|
|
1156
|
+
return { uuid: created.uuid, name: created.name, deployment_hostnames: [] };
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
if (error instanceof InfrastructureProjectValidationError) {
|
|
1159
|
+
consola.error(error.message);
|
|
1160
|
+
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1161
|
+
}
|
|
1162
|
+
throw error;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
async function resolveCollisionChoice(existingName, useExistingOnCollision) {
|
|
1166
|
+
if (useExistingOnCollision === true) return true;
|
|
1167
|
+
if (!process.stdin.isTTY) return false;
|
|
1168
|
+
return select({
|
|
1169
|
+
message: `A Commerce Hosting project named "${existingName}" already exists. Use the existing project?`,
|
|
1170
|
+
choices: [
|
|
1171
|
+
{ name: "Yes", value: true },
|
|
1172
|
+
{ name: "No", value: false }
|
|
1173
|
+
]
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
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
|
|
1059
1521
|
$ catalyst build --project-uuid <UUID>
|
|
1060
1522
|
|
|
1061
1523
|
# Build with a specific Wrangler version
|
|
@@ -1087,22 +1549,25 @@ Examples:
|
|
|
1087
1549
|
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
1088
1550
|
if (!projectUuid) {
|
|
1089
1551
|
throw new Error(
|
|
1090
|
-
"Project UUID is required. Please run `catalyst
|
|
1552
|
+
"Project UUID is required. Please run `catalyst projects create` or `catalyst projects link` or this command again with --project-uuid <uuid>."
|
|
1091
1553
|
);
|
|
1092
1554
|
}
|
|
1093
1555
|
await buildCatalystProject(projectUuid, options.wranglerVersion);
|
|
1094
1556
|
});
|
|
1095
1557
|
|
|
1096
|
-
// src/cli/commands/
|
|
1097
|
-
import { select as select4 } from "@inquirer/prompts";
|
|
1558
|
+
// src/cli/commands/channels.ts
|
|
1559
|
+
import { confirm as confirm3, select as select4 } from "@inquirer/prompts";
|
|
1098
1560
|
import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option3 } from "commander";
|
|
1099
1561
|
import { colorize as colorize5 } from "consola/utils";
|
|
1100
1562
|
|
|
1563
|
+
// src/cli/lib/channel-checkout-url-flow.ts
|
|
1564
|
+
import { input as input3 } from "@inquirer/prompts";
|
|
1565
|
+
|
|
1101
1566
|
// src/cli/lib/channel-site-flow.ts
|
|
1102
1567
|
import { select as select2 } from "@inquirer/prompts";
|
|
1103
1568
|
|
|
1104
1569
|
// src/cli/lib/channels.ts
|
|
1105
|
-
import { z as
|
|
1570
|
+
import { z as z8 } from "zod";
|
|
1106
1571
|
var cliApiUrl = (origin, storeHash, path) => `${origin}/stores/${storeHash}/cli-api/v3${path}`;
|
|
1107
1572
|
var channelsUrl = (storeHash, apiHost, query2 = {}) => {
|
|
1108
1573
|
const params = new URLSearchParams(query2).toString();
|
|
@@ -1113,18 +1578,18 @@ var authHeaders2 = (accessToken) => ({
|
|
|
1113
1578
|
"X-Correlation-Id": getTelemetry().correlationId,
|
|
1114
1579
|
Accept: "application/json"
|
|
1115
1580
|
});
|
|
1116
|
-
var envVarsSchema =
|
|
1117
|
-
var channelSchema =
|
|
1118
|
-
id:
|
|
1119
|
-
name:
|
|
1120
|
-
platform:
|
|
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()
|
|
1121
1586
|
});
|
|
1122
|
-
var channelsResponseSchema =
|
|
1123
|
-
data:
|
|
1587
|
+
var channelsResponseSchema = z8.object({
|
|
1588
|
+
data: z8.array(channelSchema)
|
|
1124
1589
|
});
|
|
1125
1590
|
var CHANNEL_PLATFORM_ORDER = ["catalyst", "next", "bigcommerce"];
|
|
1126
|
-
function sortChannelsByPlatform(
|
|
1127
|
-
return [...
|
|
1591
|
+
function sortChannelsByPlatform(channels2) {
|
|
1592
|
+
return [...channels2].sort((a, b) => {
|
|
1128
1593
|
const aIndex = CHANNEL_PLATFORM_ORDER.indexOf(a.platform);
|
|
1129
1594
|
const bIndex = CHANNEL_PLATFORM_ORDER.indexOf(b.platform);
|
|
1130
1595
|
if (aIndex === -1 && bIndex === -1) return 0;
|
|
@@ -1136,32 +1601,46 @@ function sortChannelsByPlatform(channels) {
|
|
|
1136
1601
|
function channelPlatformLabel(platform) {
|
|
1137
1602
|
return platform === "bigcommerce" ? "Stencil" : platform.charAt(0).toUpperCase() + platform.slice(1);
|
|
1138
1603
|
}
|
|
1139
|
-
var initResponseSchema =
|
|
1140
|
-
data:
|
|
1141
|
-
storefront_api_token:
|
|
1604
|
+
var initResponseSchema = z8.object({
|
|
1605
|
+
data: z8.object({
|
|
1606
|
+
storefront_api_token: z8.string(),
|
|
1142
1607
|
envVars: envVarsSchema
|
|
1143
1608
|
})
|
|
1144
1609
|
});
|
|
1145
|
-
var createChannelResponseSchema =
|
|
1146
|
-
data:
|
|
1147
|
-
id:
|
|
1148
|
-
storefront_api_token:
|
|
1610
|
+
var createChannelResponseSchema = z8.object({
|
|
1611
|
+
data: z8.object({
|
|
1612
|
+
id: z8.number(),
|
|
1613
|
+
storefront_api_token: z8.string(),
|
|
1149
1614
|
envVars: envVarsSchema
|
|
1150
1615
|
})
|
|
1151
1616
|
});
|
|
1152
|
-
var eligibilityResponseSchema =
|
|
1153
|
-
data:
|
|
1154
|
-
eligible:
|
|
1155
|
-
message:
|
|
1617
|
+
var eligibilityResponseSchema = z8.object({
|
|
1618
|
+
data: z8.object({
|
|
1619
|
+
eligible: z8.boolean(),
|
|
1620
|
+
message: z8.string()
|
|
1156
1621
|
})
|
|
1157
1622
|
});
|
|
1158
|
-
var
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
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()
|
|
1163
1635
|
})
|
|
1164
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
|
+
}
|
|
1165
1644
|
async function getChannelInit(channelId, storeHash, accessToken, origin) {
|
|
1166
1645
|
const response = await fetch(cliApiUrl(origin, storeHash, `/channels/${channelId}/init`), {
|
|
1167
1646
|
method: "GET",
|
|
@@ -1194,358 +1673,196 @@ async function createChannel(name, storefrontLocale, additionalLocales, installS
|
|
|
1194
1673
|
devOrigin: "http://localhost:3000",
|
|
1195
1674
|
storefrontLanguage: storefrontLocale,
|
|
1196
1675
|
additionalLocales
|
|
1197
|
-
})
|
|
1198
|
-
});
|
|
1199
|
-
if (!response.ok) {
|
|
1200
|
-
throw await httpError(response, "Failed to create channel");
|
|
1201
|
-
}
|
|
1202
|
-
const { data } = createChannelResponseSchema.parse(await response.json());
|
|
1203
|
-
return {
|
|
1204
|
-
channelId: data.id,
|
|
1205
|
-
storefrontToken: data.storefront_api_token,
|
|
1206
|
-
envVars: data.envVars
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
async function fetchAvailableChannels(storeHash, accessToken, apiHost) {
|
|
1210
|
-
const response = await fetch(
|
|
1211
|
-
channelsUrl(storeHash, apiHost, { available: "true", type: "storefront" }),
|
|
1212
|
-
{
|
|
1213
|
-
method: "GET",
|
|
1214
|
-
headers: {
|
|
1215
|
-
"X-Auth-Token": accessToken,
|
|
1216
|
-
"X-Correlation-Id": getTelemetry().correlationId,
|
|
1217
|
-
Accept: "application/json"
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
);
|
|
1221
|
-
assertAuthorized(response);
|
|
1222
|
-
if (!response.ok) {
|
|
1223
|
-
throw await httpError(response, "Failed to fetch channels");
|
|
1224
|
-
}
|
|
1225
|
-
return channelsResponseSchema.parse(await response.json()).data;
|
|
1226
|
-
}
|
|
1227
|
-
async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken, apiHost) {
|
|
1228
|
-
const response = await fetch(
|
|
1229
|
-
`https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site`,
|
|
1230
|
-
{
|
|
1231
|
-
method: "PUT",
|
|
1232
|
-
headers: {
|
|
1233
|
-
"X-Auth-Token": accessToken,
|
|
1234
|
-
"Content-Type": "application/json",
|
|
1235
|
-
Accept: "application/json",
|
|
1236
|
-
"X-Correlation-Id": getTelemetry().correlationId
|
|
1237
|
-
},
|
|
1238
|
-
body: JSON.stringify({ url: siteUrl })
|
|
1239
|
-
}
|
|
1240
|
-
);
|
|
1241
|
-
if (response.status === 401 || response.status === 403) {
|
|
1242
|
-
throw new UserActionableError(
|
|
1243
|
-
`Failed to update channel site (${response.status}). Re-run \`catalyst auth login\` to refresh your access token with the store_channel_settings scope.`
|
|
1244
|
-
);
|
|
1245
|
-
}
|
|
1246
|
-
if (!response.ok) {
|
|
1247
|
-
throw await httpError(response, "Failed to update channel site");
|
|
1248
|
-
}
|
|
1249
|
-
const res = await response.json();
|
|
1250
|
-
const { data } = channelSiteSchema.parse(res);
|
|
1251
|
-
return { id: data.id, url: data.url, channelId: data.channel_id };
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
// src/cli/lib/commerce-hosting.ts
|
|
1255
|
-
import { confirm as confirm2, input as input2, select, Separator } from "@inquirer/prompts";
|
|
1256
|
-
import { colorize as colorize3 } from "consola/utils";
|
|
1257
|
-
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
1258
|
-
import { dirname as dirname2, join as join5 } from "path";
|
|
1259
|
-
import { z as z8 } from "zod";
|
|
1260
|
-
|
|
1261
|
-
// src/cli/lib/sort-package-json.ts
|
|
1262
|
-
var FIELD_ORDER = [
|
|
1263
|
-
"name",
|
|
1264
|
-
"description",
|
|
1265
|
-
"version",
|
|
1266
|
-
"private",
|
|
1267
|
-
"engines",
|
|
1268
|
-
"scripts",
|
|
1269
|
-
"dependencies",
|
|
1270
|
-
"devDependencies"
|
|
1271
|
-
];
|
|
1272
|
-
function sortPackageJsonFields(pkg) {
|
|
1273
|
-
const ordered = {};
|
|
1274
|
-
FIELD_ORDER.forEach((field) => {
|
|
1275
|
-
if (field in pkg) {
|
|
1276
|
-
ordered[field] = pkg[field];
|
|
1277
|
-
}
|
|
1278
|
-
});
|
|
1279
|
-
Object.keys(pkg).forEach((key) => {
|
|
1280
|
-
if (!(key in ordered)) {
|
|
1281
|
-
ordered[key] = pkg[key];
|
|
1282
|
-
}
|
|
1283
|
-
});
|
|
1284
|
-
return ordered;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
// src/cli/lib/commerce-hosting.ts
|
|
1288
|
-
var OPENNEXT_CLOUDFLARE_VERSION = "1.17.3";
|
|
1289
|
-
var corePackageJsonSchema = z8.looseObject({
|
|
1290
|
-
dependencies: z8.record(z8.string(), z8.string()).optional()
|
|
1291
|
-
});
|
|
1292
|
-
var writeJson = (path, value) => {
|
|
1293
|
-
mkdirSync(dirname2(path), { recursive: true });
|
|
1294
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
1295
|
-
`);
|
|
1296
|
-
};
|
|
1297
|
-
var convertProxyToMiddleware = (projectDir) => {
|
|
1298
|
-
const proxyPath = join5(projectDir, "proxy.ts");
|
|
1299
|
-
const middlewarePath = join5(projectDir, "middleware.ts");
|
|
1300
|
-
if (!existsSync3(proxyPath)) return;
|
|
1301
|
-
const contents = readFileSync2(proxyPath, "utf-8").replace("export const proxy", "export const middleware").replace("export const config = {", "export const config = {\n runtime: 'experimental-edge',");
|
|
1302
|
-
writeFileSync(middlewarePath, contents);
|
|
1303
|
-
unlinkSync(proxyPath);
|
|
1304
|
-
};
|
|
1305
|
-
var cleanupCloudflareIncompatibilities = async (projectDir) => {
|
|
1306
|
-
const instrumentationPath = join5(projectDir, "instrumentation.ts");
|
|
1307
|
-
if (!existsSync3(instrumentationPath)) return;
|
|
1308
|
-
if (!process.stdin.isTTY) {
|
|
1309
|
-
consola.warn(
|
|
1310
|
-
"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."
|
|
1311
|
-
);
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
const shouldRemove = await confirm2({
|
|
1315
|
-
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?',
|
|
1316
|
-
default: true
|
|
1317
|
-
});
|
|
1318
|
-
if (!shouldRemove) {
|
|
1319
|
-
consola.info(
|
|
1320
|
-
'Leaving instrumentation.ts in place. The Cloudflare worker will continue to log "Failed to prepare server" at cold start until this is resolved manually.'
|
|
1321
|
-
);
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
unlinkSync(instrumentationPath);
|
|
1325
|
-
consola.info("Removed instrumentation.ts (incompatible with Cloudflare Workers).");
|
|
1326
|
-
const corePackageJsonPath = join5(projectDir, "package.json");
|
|
1327
|
-
if (existsSync3(corePackageJsonPath)) {
|
|
1328
|
-
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
|
|
1329
|
-
if (pkg.dependencies && "@vercel/otel" in pkg.dependencies) {
|
|
1330
|
-
const { "@vercel/otel": _removedVercelOtel, ...preservedDeps } = pkg.dependencies;
|
|
1331
|
-
pkg.dependencies = preservedDeps;
|
|
1332
|
-
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1333
|
-
consola.info("Dropped @vercel/otel from package.json.");
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
};
|
|
1337
|
-
var setupCommerceHosting = async ({
|
|
1338
|
-
projectDir,
|
|
1339
|
-
projectUuid,
|
|
1340
|
-
storeHash,
|
|
1341
|
-
accessToken
|
|
1342
|
-
}) => {
|
|
1343
|
-
await cleanupCloudflareIncompatibilities(projectDir);
|
|
1344
|
-
const corePackageJsonPath = join5(projectDir, "package.json");
|
|
1345
|
-
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
|
|
1346
|
-
pkg.dependencies = {
|
|
1347
|
-
...pkg.dependencies,
|
|
1348
|
-
"@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
|
|
1349
|
-
};
|
|
1350
|
-
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1351
|
-
const projectJson = {
|
|
1352
|
-
projectUuid,
|
|
1353
|
-
framework: "catalyst"
|
|
1354
|
-
};
|
|
1355
|
-
if (storeHash) projectJson.storeHash = storeHash;
|
|
1356
|
-
if (accessToken) projectJson.accessToken = accessToken;
|
|
1357
|
-
writeJson(join5(projectDir, ".bigcommerce", "project.json"), projectJson);
|
|
1358
|
-
convertProxyToMiddleware(projectDir);
|
|
1359
|
-
};
|
|
1360
|
-
var NoLinkedProjectError = class extends Error {
|
|
1361
|
-
constructor() {
|
|
1362
|
-
super("No infrastructure project linked: user declined to create one.");
|
|
1363
|
-
this.name = "NoLinkedProjectError";
|
|
1364
|
-
}
|
|
1365
|
-
};
|
|
1366
|
-
async function promptForNewProjectName(api) {
|
|
1367
|
-
const newProjectName = await input2({ message: "Enter a name for the new project:" });
|
|
1368
|
-
const data = await createProject(newProjectName, api.storeHash, api.accessToken, api.apiHost);
|
|
1369
|
-
consola.success(`Project "${data.name}" created successfully.`);
|
|
1370
|
-
return { uuid: data.uuid, name: data.name, deployment_hostnames: [] };
|
|
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
|
+
};
|
|
1371
1687
|
}
|
|
1372
|
-
async function
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
throw new NoLinkedProjectError();
|
|
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
|
+
}
|
|
1383
1698
|
}
|
|
1384
|
-
|
|
1699
|
+
);
|
|
1700
|
+
assertAuthorized(response);
|
|
1701
|
+
if (!response.ok) {
|
|
1702
|
+
throw await httpError(response, "Failed to fetch channels");
|
|
1385
1703
|
}
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
})),
|
|
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`,
|
|
1392
1709
|
{
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
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 })
|
|
1396
1718
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
});
|
|
1402
|
-
if (selected === "create") {
|
|
1403
|
-
return promptForNewProjectName(api);
|
|
1404
|
-
}
|
|
1405
|
-
const matched = existingProjects.find((p) => p.uuid === selected);
|
|
1406
|
-
if (!matched) {
|
|
1407
|
-
throw new Error(`Selected project ${String(selected)} not found in fetched list.`);
|
|
1719
|
+
);
|
|
1720
|
+
assertChannelSettingsAuthorized(response, "Failed to update channel site");
|
|
1721
|
+
if (!response.ok) {
|
|
1722
|
+
throw await httpError(response, "Failed to update channel site");
|
|
1408
1723
|
}
|
|
1409
|
-
|
|
1724
|
+
const res = await response.json();
|
|
1725
|
+
const { data } = channelSiteSchema.parse(res);
|
|
1726
|
+
return { id: data.id, url: data.url, channelId: data.channel_id };
|
|
1410
1727
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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) }
|
|
1424
1743
|
);
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
defaultName,
|
|
1429
|
-
existingProjects,
|
|
1430
|
-
useExistingOnCollision
|
|
1431
|
-
);
|
|
1432
|
-
}
|
|
1433
|
-
const hasOtherProjects = existingProjects.length > 1;
|
|
1434
|
-
const choices = [
|
|
1435
|
-
{ name: `Use "${conflict.name}"`, value: "use-named" }
|
|
1436
|
-
];
|
|
1437
|
-
if (hasOtherProjects) {
|
|
1438
|
-
choices.push({ name: "Select from my projects", value: "select-from-list" });
|
|
1744
|
+
assertChannelSettingsAuthorized(response, "Failed to fetch channel site");
|
|
1745
|
+
if (!response.ok) {
|
|
1746
|
+
throw await httpError(response, "Failed to fetch channel site");
|
|
1439
1747
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
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 })
|
|
1444
1757
|
});
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1449
|
-
if (action === "create") {
|
|
1450
|
-
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1758
|
+
assertChannelSettingsAuthorized(response, "Failed to update channel checkout URL");
|
|
1759
|
+
if (!response.ok) {
|
|
1760
|
+
throw await httpError(response, "Failed to update channel checkout URL");
|
|
1451
1761
|
}
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
})),
|
|
1460
|
-
new Separator(),
|
|
1461
|
-
{ name: "Create a new project", value: "create-new" }
|
|
1462
|
-
]
|
|
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)
|
|
1463
1769
|
});
|
|
1464
|
-
|
|
1465
|
-
|
|
1770
|
+
assertChannelSettingsAuthorized(response, "Failed to remove channel checkout URL");
|
|
1771
|
+
if (!response.ok) {
|
|
1772
|
+
throw await httpError(response, "Failed to remove channel checkout URL");
|
|
1466
1773
|
}
|
|
1467
|
-
consola.success(`Using existing Commerce Hosting project "${selected.name}"`);
|
|
1468
|
-
return selected;
|
|
1469
1774
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
const conflict = takenNames.find((taken) => taken.toLowerCase() === trimmed.toLowerCase());
|
|
1478
|
-
if (conflict) {
|
|
1479
|
-
return `A Commerce Hosting project named "${conflict}" already exists`;
|
|
1480
|
-
}
|
|
1481
|
-
return true;
|
|
1482
|
-
},
|
|
1483
|
-
theme: {
|
|
1484
|
-
style: {
|
|
1485
|
-
help: () => colorize3(
|
|
1486
|
-
"dim",
|
|
1487
|
-
"(The project that hosts your storefront on Commerce \u2014 often matches your folder name.)"
|
|
1488
|
-
)
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
});
|
|
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) {
|
|
1492
1782
|
try {
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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(":", "")}.`
|
|
1498
1811
|
);
|
|
1499
|
-
consola.success(`Commerce Hosting project "${created.name}" created successfully`);
|
|
1500
|
-
return { uuid: created.uuid, name: created.name, deployment_hostnames: [] };
|
|
1501
|
-
} catch (error) {
|
|
1502
|
-
if (error instanceof InfrastructureProjectValidationError) {
|
|
1503
|
-
consola.error(error.message);
|
|
1504
|
-
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1505
|
-
}
|
|
1506
|
-
throw error;
|
|
1507
1812
|
}
|
|
1813
|
+
return parsed.origin;
|
|
1508
1814
|
}
|
|
1509
|
-
async function
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
}
|
|
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
|
+
}
|
|
1519
1825
|
}
|
|
1520
|
-
async function
|
|
1521
|
-
const
|
|
1522
|
-
|
|
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."
|
|
1523
1847
|
);
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
}
|
|
1530
|
-
consola.error(
|
|
1531
|
-
"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.`
|
|
1532
1853
|
);
|
|
1533
|
-
|
|
1854
|
+
return report;
|
|
1534
1855
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
if (error instanceof InfrastructureProjectValidationError) {
|
|
1541
|
-
consola.error(
|
|
1542
|
-
`Failed to create Commerce Hosting project "${name}": ${error.message}
|
|
1543
|
-
Re-run with a different --project-name.`
|
|
1544
|
-
);
|
|
1545
|
-
process.exit(1);
|
|
1546
|
-
}
|
|
1547
|
-
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;
|
|
1548
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;
|
|
1549
1866
|
}
|
|
1550
1867
|
|
|
1551
1868
|
// src/cli/lib/channel-site-flow.ts
|
|
@@ -1556,8 +1873,8 @@ async function resolveProject(options) {
|
|
|
1556
1873
|
apiHost: options.apiHost
|
|
1557
1874
|
};
|
|
1558
1875
|
if (options.projectUuid) {
|
|
1559
|
-
const
|
|
1560
|
-
const matched =
|
|
1876
|
+
const projects2 = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
|
|
1877
|
+
const matched = projects2.find((p) => p.uuid === options.projectUuid);
|
|
1561
1878
|
if (matched) return matched;
|
|
1562
1879
|
consola.warn(
|
|
1563
1880
|
`Project ${options.projectUuid} not found on this store. Pick another to continue.`
|
|
@@ -1570,13 +1887,13 @@ async function resolveChannel(options) {
|
|
|
1570
1887
|
return { id: options.channelId };
|
|
1571
1888
|
}
|
|
1572
1889
|
consola.start("Fetching channels...");
|
|
1573
|
-
const
|
|
1890
|
+
const channels2 = await fetchAvailableChannels(
|
|
1574
1891
|
options.storeHash,
|
|
1575
1892
|
options.accessToken,
|
|
1576
1893
|
options.apiHost
|
|
1577
1894
|
);
|
|
1578
1895
|
consola.success("Channels fetched.");
|
|
1579
|
-
const catalystChannels =
|
|
1896
|
+
const catalystChannels = channels2.filter((c) => c.platform === "catalyst");
|
|
1580
1897
|
if (catalystChannels.length === 0) {
|
|
1581
1898
|
throw new Error(
|
|
1582
1899
|
"No Catalyst channels found on this store. Create one with `catalyst create` and try again."
|
|
@@ -1593,19 +1910,19 @@ async function resolveChannel(options) {
|
|
|
1593
1910
|
const matched = catalystChannels.find((c) => c.id === id);
|
|
1594
1911
|
return { id, name: matched?.name };
|
|
1595
1912
|
}
|
|
1596
|
-
async function resolveHostname(
|
|
1913
|
+
async function resolveHostname(project, options) {
|
|
1597
1914
|
if (options.hostname) {
|
|
1598
1915
|
return options.hostname;
|
|
1599
1916
|
}
|
|
1600
|
-
if (
|
|
1917
|
+
if (project.deployment_hostnames.length === 0) {
|
|
1601
1918
|
throw new Error(
|
|
1602
|
-
`Project "${
|
|
1919
|
+
`Project "${project.name}" has no deployment hostnames yet. Run \`catalyst deploy\` first to create one.`
|
|
1603
1920
|
);
|
|
1604
1921
|
}
|
|
1605
1922
|
const ordered = options.preferHostname ? [
|
|
1606
|
-
...
|
|
1607
|
-
...
|
|
1608
|
-
] :
|
|
1923
|
+
...project.deployment_hostnames.filter((h) => h === options.preferHostname),
|
|
1924
|
+
...project.deployment_hostnames.filter((h) => h !== options.preferHostname)
|
|
1925
|
+
] : project.deployment_hostnames;
|
|
1609
1926
|
const selected = await select2({
|
|
1610
1927
|
message: "Select the hostname to point the channel at.",
|
|
1611
1928
|
choices: ordered.map((h) => ({ name: h, value: h }))
|
|
@@ -1613,23 +1930,80 @@ async function resolveHostname(project2, options) {
|
|
|
1613
1930
|
return selected;
|
|
1614
1931
|
}
|
|
1615
1932
|
async function runChannelSiteUrlFlow(options) {
|
|
1616
|
-
const
|
|
1617
|
-
const
|
|
1618
|
-
const hostname = await resolveHostname(
|
|
1933
|
+
const project = await resolveProject(options);
|
|
1934
|
+
const channel = await resolveChannel(options);
|
|
1935
|
+
const hostname = await resolveHostname(project, options);
|
|
1619
1936
|
const siteUrl = hostname.startsWith("https://") ? hostname : `https://${hostname}`;
|
|
1620
1937
|
await updateChannelSiteUrl(
|
|
1621
|
-
|
|
1938
|
+
channel.id,
|
|
1622
1939
|
siteUrl,
|
|
1623
1940
|
options.storeHash,
|
|
1624
1941
|
options.accessToken,
|
|
1625
1942
|
options.apiHost
|
|
1626
1943
|
);
|
|
1627
|
-
const channelLabel =
|
|
1944
|
+
const channelLabel = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
|
|
1628
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}.`);
|
|
1629
2003
|
}
|
|
1630
2004
|
|
|
1631
2005
|
// src/cli/lib/create-channel-flow.ts
|
|
1632
|
-
import { checkbox, input as
|
|
2006
|
+
import { checkbox, input as input4, select as select3 } from "@inquirer/prompts";
|
|
1633
2007
|
import { colorize as colorize4 } from "consola/utils";
|
|
1634
2008
|
|
|
1635
2009
|
// src/cli/lib/localization.ts
|
|
@@ -1704,7 +2078,7 @@ async function runCreateChannelFlow(options) {
|
|
|
1704
2078
|
throw new UserActionableError(nameError);
|
|
1705
2079
|
}
|
|
1706
2080
|
}
|
|
1707
|
-
const name = options.name ?? await
|
|
2081
|
+
const name = options.name ?? await input4({
|
|
1708
2082
|
message: "What would you like to name your new channel?",
|
|
1709
2083
|
validate: (value) => getChannelNameError(value) ?? true
|
|
1710
2084
|
});
|
|
@@ -1749,7 +2123,8 @@ async function runCreateChannelFlow(options) {
|
|
|
1749
2123
|
{ name: "No", value: false }
|
|
1750
2124
|
]
|
|
1751
2125
|
});
|
|
1752
|
-
|
|
2126
|
+
consola.start("Creating channel...");
|
|
2127
|
+
const channel = await createChannel(
|
|
1753
2128
|
name,
|
|
1754
2129
|
storefrontLocale,
|
|
1755
2130
|
additionalLocales,
|
|
@@ -1758,19 +2133,21 @@ async function runCreateChannelFlow(options) {
|
|
|
1758
2133
|
accessToken,
|
|
1759
2134
|
cliApiOrigin
|
|
1760
2135
|
);
|
|
2136
|
+
consola.success("Channel created.");
|
|
2137
|
+
return channel;
|
|
1761
2138
|
}
|
|
1762
2139
|
|
|
1763
2140
|
// src/cli/lib/env-config.ts
|
|
1764
2141
|
var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1765
|
-
var parseEnvAssignment = (
|
|
1766
|
-
const separatorIndex =
|
|
2142
|
+
var parseEnvAssignment = (input7) => {
|
|
2143
|
+
const separatorIndex = input7.indexOf("=");
|
|
1767
2144
|
if (separatorIndex === -1) {
|
|
1768
|
-
throw new Error(`Invalid env var format: ${
|
|
2145
|
+
throw new Error(`Invalid env var format: ${input7}. Expected format: KEY=VALUE`);
|
|
1769
2146
|
}
|
|
1770
|
-
const key =
|
|
1771
|
-
const value =
|
|
2147
|
+
const key = input7.slice(0, separatorIndex).trim();
|
|
2148
|
+
const value = input7.slice(separatorIndex + 1).trim();
|
|
1772
2149
|
if (!key || !value) {
|
|
1773
|
-
throw new Error(`Invalid env var format: ${
|
|
2150
|
+
throw new Error(`Invalid env var format: ${input7}. Expected format: KEY=VALUE`);
|
|
1774
2151
|
}
|
|
1775
2152
|
if (!ENV_KEY_PATTERN.test(key)) {
|
|
1776
2153
|
throw new Error(
|
|
@@ -1810,7 +2187,7 @@ function resolveCredentials(options, config2) {
|
|
|
1810
2187
|
// src/cli/lib/write-env.ts
|
|
1811
2188
|
import { outputFileSync } from "fs-extra/esm";
|
|
1812
2189
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1813
|
-
import { join as
|
|
2190
|
+
import { join as join7 } from "path";
|
|
1814
2191
|
var ACTIVE_KEY_LINE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
1815
2192
|
var keyFromLine = (line) => {
|
|
1816
2193
|
const match = ACTIVE_KEY_LINE.exec(line);
|
|
@@ -1827,8 +2204,8 @@ var parseEnvValues = (contents) => {
|
|
|
1827
2204
|
return values;
|
|
1828
2205
|
};
|
|
1829
2206
|
var writeEnv = (projectDir, envVars) => {
|
|
1830
|
-
const examplePath =
|
|
1831
|
-
const localPath =
|
|
2207
|
+
const examplePath = join7(projectDir, ".env.example");
|
|
2208
|
+
const localPath = join7(projectDir, ".env.local");
|
|
1832
2209
|
const existingLocal = existsSync4(localPath) ? parseEnvValues(readFileSync3(localPath, "utf-8")) : /* @__PURE__ */ new Map();
|
|
1833
2210
|
const resolve2 = (key) => {
|
|
1834
2211
|
if (key in envVars && envVars[key] !== "") {
|
|
@@ -1874,7 +2251,7 @@ var writeEnv = (projectDir, envVars) => {
|
|
|
1874
2251
|
`);
|
|
1875
2252
|
};
|
|
1876
2253
|
|
|
1877
|
-
// src/cli/commands/
|
|
2254
|
+
// src/cli/commands/channels.ts
|
|
1878
2255
|
var parseChannelId = (value) => {
|
|
1879
2256
|
const parsed = Number.parseInt(value, 10);
|
|
1880
2257
|
if (Number.isNaN(parsed)) {
|
|
@@ -1882,6 +2259,22 @@ var parseChannelId = (value) => {
|
|
|
1882
2259
|
}
|
|
1883
2260
|
return parsed;
|
|
1884
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.`;
|
|
1885
2278
|
async function resolveCredentialsWithLogin(options, config2, apiHost) {
|
|
1886
2279
|
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
1887
2280
|
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
@@ -1900,17 +2293,29 @@ async function resolveCredentialsWithLogin(options, config2, apiHost) {
|
|
|
1900
2293
|
throw error;
|
|
1901
2294
|
}
|
|
1902
2295
|
}
|
|
1903
|
-
var update = new Command3("update").configureHelp({ showGlobalOptions: true }).description(
|
|
1904
|
-
"Update a BigCommerce channel's site URL to point at one of your project's deployment hostnames."
|
|
1905
|
-
).addHelpText(
|
|
2296
|
+
var update = new Command3("update").configureHelp({ showGlobalOptions: true }).description("Update a BigCommerce channel's storefront and checkout URLs.").addHelpText(
|
|
1906
2297
|
"after",
|
|
1907
|
-
|
|
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
|
+
|
|
1908
2304
|
Examples:
|
|
1909
2305
|
# Pick a channel and hostname interactively
|
|
1910
|
-
$ catalyst
|
|
2306
|
+
$ catalyst channels update
|
|
2307
|
+
|
|
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
|
|
1911
2316
|
|
|
1912
|
-
#
|
|
1913
|
-
$ catalyst
|
|
2317
|
+
# Fall back to the shared checkout domain
|
|
2318
|
+
$ catalyst channels update --channel-id 123 --remove-checkout-url`
|
|
1914
2319
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
1915
2320
|
new Option3(
|
|
1916
2321
|
"--channel-id <id>",
|
|
@@ -1921,29 +2326,68 @@ Examples:
|
|
|
1921
2326
|
"--hostname <hostname>",
|
|
1922
2327
|
"Skip the hostname prompt and use this hostname directly. Must be one of the project's deployment_hostnames."
|
|
1923
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")
|
|
1924
2339
|
).action(async (options) => {
|
|
1925
2340
|
const config2 = getProjectConfig();
|
|
1926
2341
|
const apiHost = resolveApiHost(options, config2);
|
|
1927
2342
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1928
2343
|
await getTelemetry().identify(storeHash);
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
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
|
|
1942
2378
|
);
|
|
1943
|
-
|
|
1944
|
-
|
|
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 });
|
|
1945
2390
|
}
|
|
1946
|
-
throw error;
|
|
1947
2391
|
}
|
|
1948
2392
|
process.exit(0);
|
|
1949
2393
|
});
|
|
@@ -1954,13 +2398,13 @@ var link = new Command3("link").configureHelp({ showGlobalOptions: true }).descr
|
|
|
1954
2398
|
`
|
|
1955
2399
|
Examples:
|
|
1956
2400
|
# Pick a channel interactively (logs you in if needed)
|
|
1957
|
-
$ catalyst
|
|
2401
|
+
$ catalyst channels link
|
|
1958
2402
|
|
|
1959
2403
|
# Non-interactive
|
|
1960
|
-
$ catalyst
|
|
2404
|
+
$ catalyst channels link --store-hash <hash> --access-token <token> --channel-id 123
|
|
1961
2405
|
|
|
1962
2406
|
# Append extra environment variables to .env.local
|
|
1963
|
-
$ catalyst
|
|
2407
|
+
$ catalyst channels link --channel-id 123 --env MY_FLAG=1`
|
|
1964
2408
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
|
|
1965
2409
|
new Option3("--channel-id <id>", "Link this channel directly, skipping the picker.").argParser(
|
|
1966
2410
|
parseChannelId
|
|
@@ -1976,7 +2420,7 @@ Examples:
|
|
|
1976
2420
|
const credentials = await resolveCredentialsWithLogin(options, config2, apiHost);
|
|
1977
2421
|
if (!credentials) {
|
|
1978
2422
|
consola.info(
|
|
1979
|
-
"Login aborted. Re-run `catalyst
|
|
2423
|
+
"Login aborted. Re-run `catalyst channels link` when you have your credentials ready."
|
|
1980
2424
|
);
|
|
1981
2425
|
process.exit(0);
|
|
1982
2426
|
return;
|
|
@@ -1986,8 +2430,8 @@ Examples:
|
|
|
1986
2430
|
let channelId = options.channelId;
|
|
1987
2431
|
let channelName;
|
|
1988
2432
|
if (channelId === void 0) {
|
|
1989
|
-
const
|
|
1990
|
-
if (
|
|
2433
|
+
const availableChannels = await fetchAvailableChannels(storeHash, accessToken, apiHost);
|
|
2434
|
+
if (availableChannels.length === 0) {
|
|
1991
2435
|
consola.info(
|
|
1992
2436
|
"No storefront channels found on this store. Create one with `catalyst create` and try again."
|
|
1993
2437
|
);
|
|
@@ -1996,13 +2440,13 @@ Examples:
|
|
|
1996
2440
|
}
|
|
1997
2441
|
channelId = await select4({
|
|
1998
2442
|
message: "Which channel would you like to link?",
|
|
1999
|
-
choices: sortChannelsByPlatform(
|
|
2443
|
+
choices: sortChannelsByPlatform(availableChannels).map((c) => ({
|
|
2000
2444
|
name: c.name,
|
|
2001
2445
|
value: c.id,
|
|
2002
2446
|
description: channelPlatformLabel(c.platform)
|
|
2003
2447
|
}))
|
|
2004
2448
|
});
|
|
2005
|
-
channelName =
|
|
2449
|
+
channelName = availableChannels.find((c) => c.id === channelId)?.name;
|
|
2006
2450
|
}
|
|
2007
2451
|
const initData = await getChannelInit(channelId, storeHash, accessToken, options.cliApiOrigin);
|
|
2008
2452
|
const envVars = { ...initData.envVars };
|
|
@@ -2027,13 +2471,13 @@ var create = new Command3("create").configureHelp({ showGlobalOptions: true }).d
|
|
|
2027
2471
|
`
|
|
2028
2472
|
Examples:
|
|
2029
2473
|
# Create a channel interactively (logs you in if needed)
|
|
2030
|
-
$ catalyst
|
|
2474
|
+
$ catalyst channels create
|
|
2031
2475
|
|
|
2032
2476
|
# Non-interactive, and link it to this project afterwards
|
|
2033
|
-
$ catalyst
|
|
2477
|
+
$ catalyst channels create --name "My Store" --locale en --no-sample-data --link
|
|
2034
2478
|
|
|
2035
2479
|
# Add additional storefront languages (max 4)
|
|
2036
|
-
$ catalyst
|
|
2480
|
+
$ catalyst channels create --name "My Store" --additional-locales es fr`
|
|
2037
2481
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).option("--name <name>", "Name for the new channel. Skips the name prompt.").option(
|
|
2038
2482
|
"--locale <locale>",
|
|
2039
2483
|
'Default storefront locale (e.g. "en"). Skips the default-language prompt.'
|
|
@@ -2056,7 +2500,7 @@ Examples:
|
|
|
2056
2500
|
const credentials = await resolveCredentialsWithLogin(options, config2, apiHost);
|
|
2057
2501
|
if (!credentials) {
|
|
2058
2502
|
consola.info(
|
|
2059
|
-
"Login aborted. Re-run `catalyst
|
|
2503
|
+
"Login aborted. Re-run `catalyst channels create` when you have your credentials ready."
|
|
2060
2504
|
);
|
|
2061
2505
|
process.exit(0);
|
|
2062
2506
|
return;
|
|
@@ -2101,38 +2545,106 @@ Examples:
|
|
|
2101
2545
|
}
|
|
2102
2546
|
process.exit(0);
|
|
2103
2547
|
});
|
|
2104
|
-
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);
|
|
2105
2639
|
|
|
2106
2640
|
// src/cli/commands/create.ts
|
|
2107
2641
|
import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as Option4 } from "@commander-js/extra-typings";
|
|
2108
|
-
import { input as
|
|
2642
|
+
import { input as input5, select as select5 } from "@inquirer/prompts";
|
|
2109
2643
|
import { execSync as execSync2 } from "child_process";
|
|
2110
2644
|
import { colorize as colorize6 } from "consola/utils";
|
|
2111
2645
|
import { pathExistsSync } from "fs-extra/esm";
|
|
2112
2646
|
import kebabCase from "lodash.kebabcase";
|
|
2113
|
-
import { join as
|
|
2114
|
-
|
|
2115
|
-
// src/cli/lib/detect-package-manager.ts
|
|
2116
|
-
import { detectPackageManager as detectFromDir } from "nypm";
|
|
2117
|
-
var detectPackageManager = () => {
|
|
2118
|
-
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
2119
|
-
const name = userAgent.split(" ")[0]?.split("/")[0];
|
|
2120
|
-
if (name === "pnpm" || name === "yarn" || name === "bun") {
|
|
2121
|
-
return name;
|
|
2122
|
-
}
|
|
2123
|
-
if (process.versions.bun) {
|
|
2124
|
-
return "bun";
|
|
2125
|
-
}
|
|
2126
|
-
return "npm";
|
|
2127
|
-
};
|
|
2128
|
-
var detectProjectPackageManager = async (projectDir) => {
|
|
2129
|
-
const detected = await detectFromDir(projectDir);
|
|
2130
|
-
const name = detected?.name;
|
|
2131
|
-
if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") {
|
|
2132
|
-
return name;
|
|
2133
|
-
}
|
|
2134
|
-
return "npm";
|
|
2135
|
-
};
|
|
2647
|
+
import { join as join11 } from "path";
|
|
2136
2648
|
|
|
2137
2649
|
// src/cli/lib/extract-catalyst.ts
|
|
2138
2650
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -2189,7 +2701,7 @@ var extractCatalyst = async ({
|
|
|
2189
2701
|
// src/cli/lib/init-git-repo.ts
|
|
2190
2702
|
import { execSync } from "child_process";
|
|
2191
2703
|
import { rmSync } from "fs";
|
|
2192
|
-
import { join as
|
|
2704
|
+
import { join as join8 } from "path";
|
|
2193
2705
|
var run = (command, cwd) => execSync(command, { cwd, stdio: "ignore" });
|
|
2194
2706
|
var initGitRepo = (projectDir) => {
|
|
2195
2707
|
try {
|
|
@@ -2198,51 +2710,14 @@ var initGitRepo = (projectDir) => {
|
|
|
2198
2710
|
run('git commit -m "Initial commit from Catalyst"', projectDir);
|
|
2199
2711
|
consola.success("Initialized a git repository.");
|
|
2200
2712
|
} catch {
|
|
2201
|
-
rmSync(
|
|
2713
|
+
rmSync(join8(projectDir, ".git"), { recursive: true, force: true });
|
|
2202
2714
|
consola.warn("Could not initialize a git repository. Skipping.");
|
|
2203
2715
|
}
|
|
2204
2716
|
};
|
|
2205
2717
|
|
|
2206
|
-
// src/cli/lib/install-dependencies.ts
|
|
2207
|
-
import { installDependencies as installDeps } from "nypm";
|
|
2208
|
-
import yoctoSpinner2 from "yocto-spinner";
|
|
2209
|
-
var restoreEnv = (key, previous) => {
|
|
2210
|
-
if (previous === void 0) {
|
|
2211
|
-
Reflect.deleteProperty(process.env, key);
|
|
2212
|
-
} else {
|
|
2213
|
-
process.env[key] = previous;
|
|
2214
|
-
}
|
|
2215
|
-
};
|
|
2216
|
-
var withQuietInstallEnv = async (run2) => {
|
|
2217
|
-
var _a, _b;
|
|
2218
|
-
const prevNodeNoWarnings = process.env.NODE_NO_WARNINGS;
|
|
2219
|
-
const prevDownloadPrompt = process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT;
|
|
2220
|
-
(_a = process.env).NODE_NO_WARNINGS ?? (_a.NODE_NO_WARNINGS = "1");
|
|
2221
|
-
(_b = process.env).COREPACK_ENABLE_DOWNLOAD_PROMPT ?? (_b.COREPACK_ENABLE_DOWNLOAD_PROMPT = "0");
|
|
2222
|
-
try {
|
|
2223
|
-
return await run2();
|
|
2224
|
-
} finally {
|
|
2225
|
-
restoreEnv("NODE_NO_WARNINGS", prevNodeNoWarnings);
|
|
2226
|
-
restoreEnv("COREPACK_ENABLE_DOWNLOAD_PROMPT", prevDownloadPrompt);
|
|
2227
|
-
}
|
|
2228
|
-
};
|
|
2229
|
-
var installDependencies = async (projectDir, packageManager = "pnpm") => {
|
|
2230
|
-
const spinner = yoctoSpinner2().start(
|
|
2231
|
-
`Installing dependencies with ${packageManager}. This could take a minute...`
|
|
2232
|
-
);
|
|
2233
|
-
try {
|
|
2234
|
-
await withQuietInstallEnv(() => installDeps({ cwd: projectDir, silent: true, packageManager }));
|
|
2235
|
-
spinner.success("Dependencies installed successfully.");
|
|
2236
|
-
} catch (error) {
|
|
2237
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
2238
|
-
spinner.error(`Failed to install dependencies: ${message}`);
|
|
2239
|
-
throw error;
|
|
2240
|
-
}
|
|
2241
|
-
};
|
|
2242
|
-
|
|
2243
2718
|
// src/cli/lib/rewrite-core-package.ts
|
|
2244
2719
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
2245
|
-
import { join as
|
|
2720
|
+
import { join as join9 } from "path";
|
|
2246
2721
|
import { z as z10 } from "zod";
|
|
2247
2722
|
var NATIVE_BUILD_ALLOWLIST = [
|
|
2248
2723
|
"@parcel/watcher",
|
|
@@ -2284,7 +2759,7 @@ var resolvePublishedVersion = async (pkgName) => {
|
|
|
2284
2759
|
return `^${version2}`;
|
|
2285
2760
|
};
|
|
2286
2761
|
var rewriteCorePackage = async (projectDir, ref) => {
|
|
2287
|
-
const packageJsonPath =
|
|
2762
|
+
const packageJsonPath = join9(projectDir, "package.json");
|
|
2288
2763
|
const pkg = corePackageJsonSchema2.parse(JSON.parse(readFileSync4(packageJsonPath, "utf-8")));
|
|
2289
2764
|
const workspaceDeps = DEP_FIELDS.flatMap((field) => {
|
|
2290
2765
|
const deps = pkg[field];
|
|
@@ -2309,12 +2784,12 @@ var rewriteCorePackage = async (projectDir, ref) => {
|
|
|
2309
2784
|
pkg.catalyst = { ...pkg.catalyst, version: pkg.version, ref };
|
|
2310
2785
|
writeFileSync2(packageJsonPath, `${JSON.stringify(sortPackageJsonFields(pkg), null, 2)}
|
|
2311
2786
|
`);
|
|
2312
|
-
writeFileSync2(
|
|
2787
|
+
writeFileSync2(join9(projectDir, ".npmrc"), "legacy-peer-deps=true\n");
|
|
2313
2788
|
};
|
|
2314
2789
|
|
|
2315
2790
|
// src/cli/lib/setup-core-project.ts
|
|
2316
2791
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
2317
|
-
import { dirname as dirname3, join as
|
|
2792
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
2318
2793
|
import { z as z11 } from "zod";
|
|
2319
2794
|
var corePackageJsonSchema3 = z11.looseObject({
|
|
2320
2795
|
scripts: z11.record(z11.string(), z11.string()).optional(),
|
|
@@ -2326,7 +2801,7 @@ var writeJson2 = (path, value) => {
|
|
|
2326
2801
|
`);
|
|
2327
2802
|
};
|
|
2328
2803
|
var setupCoreProject = (projectDir) => {
|
|
2329
|
-
const corePackageJsonPath =
|
|
2804
|
+
const corePackageJsonPath = join10(projectDir, "package.json");
|
|
2330
2805
|
const pkg = corePackageJsonSchema3.parse(JSON.parse(readFileSync5(corePackageJsonPath, "utf-8")));
|
|
2331
2806
|
pkg.scripts = {
|
|
2332
2807
|
...pkg.scripts,
|
|
@@ -2366,10 +2841,10 @@ function parseEnvFlag(value, previous = {}) {
|
|
|
2366
2841
|
return { ...previous, [key]: val };
|
|
2367
2842
|
}
|
|
2368
2843
|
async function handleChannelSelection(storeHash, accessToken, apiHost) {
|
|
2369
|
-
const
|
|
2844
|
+
const channels2 = await fetchAvailableChannels(storeHash, accessToken, apiHost);
|
|
2370
2845
|
const existingChannel = await select5({
|
|
2371
2846
|
message: "Which channel would you like to use?",
|
|
2372
|
-
choices: sortChannelsByPlatform(
|
|
2847
|
+
choices: sortChannelsByPlatform(channels2).map((ch) => ({
|
|
2373
2848
|
name: ch.name,
|
|
2374
2849
|
value: ch,
|
|
2375
2850
|
description: `Channel Platform: ${channelPlatformLabel(ch.platform)}`
|
|
@@ -2385,7 +2860,7 @@ async function setupProject(options) {
|
|
|
2385
2860
|
}
|
|
2386
2861
|
if (projectName) {
|
|
2387
2862
|
projectName = kebabCase(projectName);
|
|
2388
|
-
projectDir =
|
|
2863
|
+
projectDir = join11(options.projectDir, projectName);
|
|
2389
2864
|
if (pathExistsSync(projectDir)) {
|
|
2390
2865
|
consola.error(`${projectDir} already exists`);
|
|
2391
2866
|
process.exit(1);
|
|
@@ -2395,13 +2870,13 @@ async function setupProject(options) {
|
|
|
2395
2870
|
const validateProjectName = (i) => {
|
|
2396
2871
|
const formatted = kebabCase(i);
|
|
2397
2872
|
if (!formatted) return "Project name is required";
|
|
2398
|
-
const targetDir =
|
|
2873
|
+
const targetDir = join11(options.projectDir, formatted);
|
|
2399
2874
|
if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
|
|
2400
2875
|
projectName = formatted;
|
|
2401
2876
|
projectDir = targetDir;
|
|
2402
2877
|
return true;
|
|
2403
2878
|
};
|
|
2404
|
-
await
|
|
2879
|
+
await input5({
|
|
2405
2880
|
message: "What do you want to name your project directory?",
|
|
2406
2881
|
default: "my-catalyst-app",
|
|
2407
2882
|
validate: validateProjectName
|
|
@@ -2546,7 +3021,6 @@ Examples:
|
|
|
2546
3021
|
if (useCommerceHosting && accessToken) {
|
|
2547
3022
|
envVars.BIGCOMMERCE_ACCESS_TOKEN = accessToken;
|
|
2548
3023
|
}
|
|
2549
|
-
if (accessToken) envVars.CATALYST_ACCESS_TOKEN = accessToken;
|
|
2550
3024
|
let commerceHostingProject;
|
|
2551
3025
|
if (useCommerceHosting && storeHash && accessToken) {
|
|
2552
3026
|
const apiHost = `api.${options.bigcommerceHostname}`;
|
|
@@ -2578,6 +3052,11 @@ Examples:
|
|
|
2578
3052
|
accessToken
|
|
2579
3053
|
});
|
|
2580
3054
|
}
|
|
3055
|
+
if (storeHash && accessToken) {
|
|
3056
|
+
const projectConfig = getProjectConfig(projectDir);
|
|
3057
|
+
projectConfig.set("storeHash", storeHash);
|
|
3058
|
+
projectConfig.set("accessToken", accessToken);
|
|
3059
|
+
}
|
|
2581
3060
|
writeEnv(projectDir, envVars);
|
|
2582
3061
|
await installDependencies(projectDir, packageManager);
|
|
2583
3062
|
initGitRepo(projectDir);
|
|
@@ -2610,7 +3089,7 @@ import { Command as Command5, Option as Option5 } from "commander";
|
|
|
2610
3089
|
import { parse } from "dotenv";
|
|
2611
3090
|
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
2612
3091
|
import { release } from "os";
|
|
2613
|
-
import { dirname as dirname4, join as
|
|
3092
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
2614
3093
|
import { z as z12 } from "zod";
|
|
2615
3094
|
var CLI_ENV_VARS = [
|
|
2616
3095
|
"CATALYST_STORE_HASH",
|
|
@@ -2627,7 +3106,7 @@ var BUILD_ENV_VARS = [
|
|
|
2627
3106
|
"AUTH_SECRET"
|
|
2628
3107
|
];
|
|
2629
3108
|
var ENV_FILES = [".env.local", ".env"];
|
|
2630
|
-
var
|
|
3109
|
+
var PROJECT_LOCKFILES2 = [
|
|
2631
3110
|
["pnpm-lock.yaml", "pnpm"],
|
|
2632
3111
|
["yarn.lock", "yarn"],
|
|
2633
3112
|
["bun.lock", "bun"],
|
|
@@ -2638,7 +3117,7 @@ var hasValue = (value) => value !== void 0 && value.trim() !== "";
|
|
|
2638
3117
|
var buildEnvLayers = (cwd, env2) => {
|
|
2639
3118
|
const layers = [{ source: "process.env", values: env2 }];
|
|
2640
3119
|
ENV_FILES.forEach((file) => {
|
|
2641
|
-
const path =
|
|
3120
|
+
const path = join12(cwd, file);
|
|
2642
3121
|
if (!existsSync5(path)) {
|
|
2643
3122
|
return;
|
|
2644
3123
|
}
|
|
@@ -2661,7 +3140,7 @@ var resolveValue = (envSource, configValue) => {
|
|
|
2661
3140
|
return { present: false, source: "unset" };
|
|
2662
3141
|
};
|
|
2663
3142
|
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
2664
|
-
var lockfileManager = (dir) =>
|
|
3143
|
+
var lockfileManager = (dir) => PROJECT_LOCKFILES2.find(([file]) => existsSync5(join12(dir, file)))?.[1];
|
|
2665
3144
|
var detectProjectPackageManager2 = (cwd) => {
|
|
2666
3145
|
let dir = cwd;
|
|
2667
3146
|
for (; ; ) {
|
|
@@ -2676,7 +3155,7 @@ var detectProjectPackageManager2 = (cwd) => {
|
|
|
2676
3155
|
dir = parent;
|
|
2677
3156
|
}
|
|
2678
3157
|
};
|
|
2679
|
-
var
|
|
3158
|
+
var projectJsonSchema3 = z12.looseObject({});
|
|
2680
3159
|
var safeReadJson2 = (path) => {
|
|
2681
3160
|
try {
|
|
2682
3161
|
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
@@ -2684,9 +3163,9 @@ var safeReadJson2 = (path) => {
|
|
|
2684
3163
|
return null;
|
|
2685
3164
|
}
|
|
2686
3165
|
};
|
|
2687
|
-
var
|
|
2688
|
-
const result =
|
|
2689
|
-
safeReadJson2(
|
|
3166
|
+
var readProjectJson2 = (cwd) => {
|
|
3167
|
+
const result = projectJsonSchema3.safeParse(
|
|
3168
|
+
safeReadJson2(join12(cwd, ".bigcommerce", "project.json"))
|
|
2690
3169
|
);
|
|
2691
3170
|
return result.success ? result.data : null;
|
|
2692
3171
|
};
|
|
@@ -2696,7 +3175,7 @@ var storefrontPackageSchema = z12.looseObject({
|
|
|
2696
3175
|
catalyst: z12.looseObject({ version: z12.string().optional() }).optional()
|
|
2697
3176
|
});
|
|
2698
3177
|
var readStorefrontInfo = (cwd) => {
|
|
2699
|
-
const result = storefrontPackageSchema.safeParse(safeReadJson2(
|
|
3178
|
+
const result = storefrontPackageSchema.safeParse(safeReadJson2(join12(cwd, "package.json")));
|
|
2700
3179
|
if (!result.success) {
|
|
2701
3180
|
return { name: null, version: null };
|
|
2702
3181
|
}
|
|
@@ -2718,7 +3197,7 @@ function collectDiagnostics({
|
|
|
2718
3197
|
} = {}) {
|
|
2719
3198
|
const state = getProjectState(cwd);
|
|
2720
3199
|
const telemetry2 = getTelemetry();
|
|
2721
|
-
const projectJson =
|
|
3200
|
+
const projectJson = readProjectJson2(cwd);
|
|
2722
3201
|
const storefront = readStorefrontInfo(cwd);
|
|
2723
3202
|
const cliEnv = [{ source: "process.env", values: env2 }];
|
|
2724
3203
|
const buildEnv = buildEnvLayers(cwd, env2);
|
|
@@ -2773,12 +3252,12 @@ function collectDiagnostics({
|
|
|
2773
3252
|
correlationId: telemetry2.correlationId
|
|
2774
3253
|
},
|
|
2775
3254
|
files: {
|
|
2776
|
-
".env.local": existsSync5(
|
|
2777
|
-
".env": existsSync5(
|
|
2778
|
-
".bigcommerce/project.json": existsSync5(
|
|
2779
|
-
".bigcommerce/wrangler.jsonc": existsSync5(
|
|
2780
|
-
".open-next/": existsSync5(
|
|
2781
|
-
"package.json": existsSync5(
|
|
3255
|
+
".env.local": existsSync5(join12(cwd, ".env.local")),
|
|
3256
|
+
".env": existsSync5(join12(cwd, ".env")),
|
|
3257
|
+
".bigcommerce/project.json": existsSync5(join12(cwd, ".bigcommerce", "project.json")),
|
|
3258
|
+
".bigcommerce/wrangler.jsonc": existsSync5(join12(cwd, ".bigcommerce", "wrangler.jsonc")),
|
|
3259
|
+
".open-next/": existsSync5(join12(cwd, ".open-next")),
|
|
3260
|
+
"package.json": existsSync5(join12(cwd, "package.json"))
|
|
2782
3261
|
}
|
|
2783
3262
|
};
|
|
2784
3263
|
}
|
|
@@ -2865,12 +3344,12 @@ Examples:
|
|
|
2865
3344
|
});
|
|
2866
3345
|
|
|
2867
3346
|
// src/cli/commands/deploy.ts
|
|
2868
|
-
import { confirm as
|
|
3347
|
+
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
2869
3348
|
import AdmZip from "adm-zip";
|
|
2870
3349
|
import { Command as Command6, Option as Option6 } from "commander";
|
|
2871
3350
|
import { colorize as colorize7 } from "consola/utils";
|
|
2872
|
-
import { access, readdir, readFile } from "fs/promises";
|
|
2873
|
-
import { join as
|
|
3351
|
+
import { access as access2, readdir, readFile } from "fs/promises";
|
|
3352
|
+
import { join as join13 } from "path";
|
|
2874
3353
|
import yoctoSpinner3 from "yocto-spinner";
|
|
2875
3354
|
import { z as z13 } from "zod";
|
|
2876
3355
|
|
|
@@ -2942,10 +3421,10 @@ var DeploymentStatusSchema = z13.object({
|
|
|
2942
3421
|
});
|
|
2943
3422
|
var generateBundleZip = async () => {
|
|
2944
3423
|
consola.info("Generating bundle...");
|
|
2945
|
-
const bigcommerceDir =
|
|
2946
|
-
const distDir =
|
|
3424
|
+
const bigcommerceDir = join13(process.cwd(), ".bigcommerce");
|
|
3425
|
+
const distDir = join13(process.cwd(), ".bigcommerce", "dist");
|
|
2947
3426
|
try {
|
|
2948
|
-
await
|
|
3427
|
+
await access2(distDir);
|
|
2949
3428
|
} catch {
|
|
2950
3429
|
throw new Error(`Dist directory not found: ${distDir}`);
|
|
2951
3430
|
}
|
|
@@ -2953,7 +3432,7 @@ var generateBundleZip = async () => {
|
|
|
2953
3432
|
if (buildDirContents.length === 0) {
|
|
2954
3433
|
throw new Error(`Dist directory is empty: ${distDir}`);
|
|
2955
3434
|
}
|
|
2956
|
-
const outputZip =
|
|
3435
|
+
const outputZip = join13(bigcommerceDir, "bundle.zip");
|
|
2957
3436
|
const zip = new AdmZip();
|
|
2958
3437
|
zip.addLocalFolder(distDir, "output");
|
|
2959
3438
|
zip.writeZip(outputZip);
|
|
@@ -2985,7 +3464,7 @@ var generateUploadSignature = async (storeHash, accessToken, apiHost) => {
|
|
|
2985
3464
|
};
|
|
2986
3465
|
var uploadBundleZip = async (uploadUrl) => {
|
|
2987
3466
|
consola.info("Uploading bundle...");
|
|
2988
|
-
const zipPath =
|
|
3467
|
+
const zipPath = join13(process.cwd(), ".bigcommerce", "bundle.zip");
|
|
2989
3468
|
const fileBuffer = await readFile(zipPath);
|
|
2990
3469
|
const response = await fetch(uploadUrl, {
|
|
2991
3470
|
method: "PUT",
|
|
@@ -3120,7 +3599,7 @@ var fetchProject = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
|
3120
3599
|
}
|
|
3121
3600
|
const res = await response.json();
|
|
3122
3601
|
const { data } = ProjectSchema.parse(res);
|
|
3123
|
-
return data.find((
|
|
3602
|
+
return data.find((project) => project.uuid === projectUuid);
|
|
3124
3603
|
};
|
|
3125
3604
|
var deploy = new Command6("deploy").configureHelp({ showGlobalOptions: true }).description("Deploy your application to Cloudflare.").addHelpText(
|
|
3126
3605
|
"after",
|
|
@@ -3133,6 +3612,9 @@ Example:
|
|
|
3133
3612
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option(
|
|
3134
3613
|
"--update-site-url",
|
|
3135
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."
|
|
3136
3618
|
).addOption(
|
|
3137
3619
|
new Option6(
|
|
3138
3620
|
"--secret <value>",
|
|
@@ -3166,7 +3648,7 @@ Example:
|
|
|
3166
3648
|
} catch (error) {
|
|
3167
3649
|
if (error instanceof NoLinkedProjectError) {
|
|
3168
3650
|
consola.info(
|
|
3169
|
-
"When you're ready to create a project, run `catalyst
|
|
3651
|
+
"When you're ready to create a project, run `catalyst projects create` or re-run `catalyst deploy`."
|
|
3170
3652
|
);
|
|
3171
3653
|
process.exit(0);
|
|
3172
3654
|
}
|
|
@@ -3194,7 +3676,7 @@ Example:
|
|
|
3194
3676
|
consola.success(`Linked project "${selected.name}".`);
|
|
3195
3677
|
}
|
|
3196
3678
|
if (!getProjectState().isTransformed) {
|
|
3197
|
-
const shouldSetup = await
|
|
3679
|
+
const shouldSetup = await confirm4({
|
|
3198
3680
|
message: "Your project is not yet set up for Commerce Hosting deployments. Would you like to run the Commerce Hosting setup now?",
|
|
3199
3681
|
default: true
|
|
3200
3682
|
});
|
|
@@ -3209,11 +3691,17 @@ Example:
|
|
|
3209
3691
|
await installDependencies(projectDir, packageManager);
|
|
3210
3692
|
} else {
|
|
3211
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
|
+
}
|
|
3212
3700
|
}
|
|
3213
3701
|
if (options.prebuilt) {
|
|
3214
|
-
const distDir =
|
|
3702
|
+
const distDir = join13(process.cwd(), ".bigcommerce", "dist");
|
|
3215
3703
|
try {
|
|
3216
|
-
await
|
|
3704
|
+
await access2(distDir);
|
|
3217
3705
|
} catch {
|
|
3218
3706
|
throw new Error(
|
|
3219
3707
|
"No build output found at .bigcommerce/dist/. Run `catalyst build` first or remove `--prebuilt` to build automatically."
|
|
@@ -3259,29 +3747,44 @@ Example:
|
|
|
3259
3747
|
accessToken,
|
|
3260
3748
|
apiHost
|
|
3261
3749
|
);
|
|
3262
|
-
|
|
3263
|
-
return;
|
|
3264
|
-
}
|
|
3265
|
-
try {
|
|
3266
|
-
await runChannelSiteUrlFlow({
|
|
3267
|
-
storeHash,
|
|
3268
|
-
accessToken,
|
|
3269
|
-
apiHost,
|
|
3270
|
-
projectUuid,
|
|
3271
|
-
preferHostname: deploymentHostname
|
|
3272
|
-
});
|
|
3273
|
-
} catch (error) {
|
|
3750
|
+
const warnChannelFlowFailed = (what, error) => {
|
|
3274
3751
|
consola.warn(
|
|
3275
|
-
`Failed to update channel
|
|
3752
|
+
`Failed to update channel ${what}: ${error instanceof Error ? error.message : String(error)}`
|
|
3276
3753
|
);
|
|
3277
3754
|
consola.info(
|
|
3278
3755
|
"Update it manually in the control panel, or re-run `catalyst auth login` if the token is missing the store_channel_settings scope."
|
|
3279
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
|
+
}
|
|
3280
3783
|
}
|
|
3281
3784
|
});
|
|
3282
3785
|
|
|
3283
3786
|
// src/cli/commands/domains.ts
|
|
3284
|
-
import { confirm as
|
|
3787
|
+
import { confirm as confirm5, select as select6 } from "@inquirer/prompts";
|
|
3285
3788
|
import { Command as Command7, Option as Option7 } from "commander";
|
|
3286
3789
|
import { colorize as colorize9 } from "consola/utils";
|
|
3287
3790
|
|
|
@@ -3295,6 +3798,7 @@ var LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
|
3295
3798
|
var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3296
3799
|
var logEntrySchema = z14.object({
|
|
3297
3800
|
timestamp: z14.string().nullable().optional(),
|
|
3801
|
+
entry_type: z14.string().optional(),
|
|
3298
3802
|
level: z14.string().optional(),
|
|
3299
3803
|
messages: z14.array(z14.unknown()).optional(),
|
|
3300
3804
|
is_exception: z14.boolean().optional(),
|
|
@@ -3411,8 +3915,16 @@ function isRedundantExceptionName(name, message) {
|
|
|
3411
3915
|
return parts.length > 0 && parts.every((part) => message.includes(part));
|
|
3412
3916
|
}
|
|
3413
3917
|
var stringifyMessage = (message) => typeof message === "string" ? message : JSON.stringify(message);
|
|
3918
|
+
var joinMessages = (messages) => (messages ?? []).map(stringifyMessage).join(" ").replace(/\s+/g, " ").trim();
|
|
3919
|
+
function isRequestLogEntry(entry) {
|
|
3920
|
+
if (entry.entry_type != null) return entry.entry_type === "request";
|
|
3921
|
+
const method = entry.request?.method;
|
|
3922
|
+
const url = entry.request?.url;
|
|
3923
|
+
if (!method || !url) return false;
|
|
3924
|
+
return joinMessages(entry.messages) === `${method} ${url}`;
|
|
3925
|
+
}
|
|
3414
3926
|
function formatLogEntry(entry, format = "default") {
|
|
3415
|
-
const message = (entry.messages
|
|
3927
|
+
const message = joinMessages(entry.messages);
|
|
3416
3928
|
if (format === "short") return message;
|
|
3417
3929
|
const level = entry.level ?? "unknown";
|
|
3418
3930
|
const coloredLevel = colorize8(LEVEL_COLORS[level.toLowerCase()] ?? "white", level.toUpperCase());
|
|
@@ -3425,12 +3937,16 @@ function formatLogEntry(entry, format = "default") {
|
|
|
3425
3937
|
const status = entry.request.status_code ? ` (${entry.request.status_code})` : "";
|
|
3426
3938
|
if (parts.length > 0) requestStr = ` ${parts.join(" ")}${status}`;
|
|
3427
3939
|
}
|
|
3428
|
-
|
|
3940
|
+
if (format === "request" && isRequestLogEntry(entry)) {
|
|
3941
|
+
return `[${entry.timestamp ?? UNKNOWN_TIME}]${requestStr}`;
|
|
3942
|
+
}
|
|
3943
|
+
return `[${entry.timestamp ?? UNKNOWN_TIME}]${requestStr} [${coloredLevel}]${exceptionStr} ${message}`;
|
|
3429
3944
|
}
|
|
3430
3945
|
async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
|
|
3431
3946
|
const search = new URLSearchParams();
|
|
3432
3947
|
search.set("start", params.start);
|
|
3433
3948
|
search.set("end", params.end);
|
|
3949
|
+
if (params.entryType) search.set("entry_type", params.entryType);
|
|
3434
3950
|
if (params.method) search.set("method", params.method);
|
|
3435
3951
|
if (params.statusCode != null) search.set("status_code", String(params.statusCode));
|
|
3436
3952
|
if (params.urlLike) search.set("url:like", params.urlLike);
|
|
@@ -3473,11 +3989,18 @@ async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
|
|
|
3473
3989
|
|
|
3474
3990
|
// src/cli/lib/domains.ts
|
|
3475
3991
|
var domainStatusSchema = z15.enum(["pending", "verified", "failed", "unknown"]);
|
|
3992
|
+
var pointingRecordsSchema = z15.object({
|
|
3993
|
+
a_record_value: z15.string().nullish(),
|
|
3994
|
+
cname_record_value: z15.string().nullish()
|
|
3995
|
+
});
|
|
3476
3996
|
var domainSchema = z15.object({
|
|
3477
3997
|
domain: z15.string(),
|
|
3478
3998
|
project_uuid: z15.string(),
|
|
3479
3999
|
verification_status: domainStatusSchema
|
|
3480
4000
|
});
|
|
4001
|
+
var createdDomainSchema = domainSchema.extend({
|
|
4002
|
+
pointing_records: pointingRecordsSchema.nullish()
|
|
4003
|
+
});
|
|
3481
4004
|
var ownershipVerificationSchema = z15.object({
|
|
3482
4005
|
type: z15.string(),
|
|
3483
4006
|
name: z15.string(),
|
|
@@ -3518,6 +4041,9 @@ function parseBoundProjectUuid(body) {
|
|
|
3518
4041
|
var domainResponseSchema = z15.object({
|
|
3519
4042
|
data: domainSchema
|
|
3520
4043
|
});
|
|
4044
|
+
var createdDomainResponseSchema = z15.object({
|
|
4045
|
+
data: createdDomainSchema
|
|
4046
|
+
});
|
|
3521
4047
|
var domainListResponseSchema = z15.object({
|
|
3522
4048
|
data: z15.array(domainSchema)
|
|
3523
4049
|
});
|
|
@@ -3586,9 +4112,9 @@ async function createDomain(domain, projectUuid, storeHash, accessToken, apiHost
|
|
|
3586
4112
|
});
|
|
3587
4113
|
await assertDomainResponse(response, "Failed to add domain");
|
|
3588
4114
|
const result = await response.json();
|
|
3589
|
-
return
|
|
4115
|
+
return createdDomainResponseSchema.parse(result).data;
|
|
3590
4116
|
}
|
|
3591
|
-
async function
|
|
4117
|
+
async function getDomain2(domain, projectUuid, storeHash, accessToken, apiHost) {
|
|
3592
4118
|
const response = await fetch(domainUrl(storeHash, projectUuid, domain, apiHost), {
|
|
3593
4119
|
method: "GET",
|
|
3594
4120
|
headers: authHeaders3(accessToken)
|
|
@@ -3657,6 +4183,7 @@ async function transferDomain(domain, projectUuid, newProjectUuid, storeHash, ac
|
|
|
3657
4183
|
var WAIT_INTERVAL_MS = 5e3;
|
|
3658
4184
|
var WAIT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3659
4185
|
var DOMAIN_STATUS_FILTERS = ["pending", "verified", "failed"];
|
|
4186
|
+
var RECORD_TYPE_WIDTH = "CNAME".length;
|
|
3660
4187
|
var STATUS_COLORS = {
|
|
3661
4188
|
pending: "yellow",
|
|
3662
4189
|
verified: "green",
|
|
@@ -3690,20 +4217,20 @@ async function resolveDestinationProject(domain, context, toProjectUuid) {
|
|
|
3690
4217
|
return toProjectUuid;
|
|
3691
4218
|
}
|
|
3692
4219
|
consola.start("Fetching projects...");
|
|
3693
|
-
const
|
|
4220
|
+
const projects2 = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
|
|
3694
4221
|
consola.success("Projects fetched.");
|
|
3695
|
-
const destinations =
|
|
4222
|
+
const destinations = projects2.filter((project) => project.uuid !== context.projectUuid);
|
|
3696
4223
|
if (destinations.length === 0) {
|
|
3697
4224
|
throw new UserActionableError(
|
|
3698
|
-
"No other projects to transfer to. Create another project with `catalyst
|
|
4225
|
+
"No other projects to transfer to. Create another project with `catalyst projects create` first."
|
|
3699
4226
|
);
|
|
3700
4227
|
}
|
|
3701
4228
|
return select6({
|
|
3702
4229
|
message: `Select the project to transfer ${domain} to (Press <enter> to select).`,
|
|
3703
|
-
choices: destinations.map((
|
|
3704
|
-
name:
|
|
3705
|
-
value:
|
|
3706
|
-
description:
|
|
4230
|
+
choices: destinations.map((project) => ({
|
|
4231
|
+
name: project.name,
|
|
4232
|
+
value: project.uuid,
|
|
4233
|
+
description: project.uuid
|
|
3707
4234
|
}))
|
|
3708
4235
|
});
|
|
3709
4236
|
}
|
|
@@ -3713,6 +4240,19 @@ function formatDomainStatus(status) {
|
|
|
3713
4240
|
function formatDomain(domain) {
|
|
3714
4241
|
return `${domain.domain} ${formatDomainStatus(domain.verification_status)}`;
|
|
3715
4242
|
}
|
|
4243
|
+
function formatPointingRecords(records) {
|
|
4244
|
+
const rows = [];
|
|
4245
|
+
if (records?.a_record_value) {
|
|
4246
|
+
rows.push(`${"A".padEnd(RECORD_TYPE_WIDTH)} ${records.a_record_value}`);
|
|
4247
|
+
}
|
|
4248
|
+
if (records?.cname_record_value) {
|
|
4249
|
+
rows.push(`${"CNAME".padEnd(RECORD_TYPE_WIDTH)} ${records.cname_record_value}`);
|
|
4250
|
+
}
|
|
4251
|
+
if (rows.length === 0) {
|
|
4252
|
+
return null;
|
|
4253
|
+
}
|
|
4254
|
+
return rows.map((row2) => ` ${row2}`).join("\n");
|
|
4255
|
+
}
|
|
3716
4256
|
function formatOwnershipVerification(record) {
|
|
3717
4257
|
const lines = [
|
|
3718
4258
|
"Add this DNS record to verify ownership:",
|
|
@@ -3734,16 +4274,20 @@ async function waitForDomainVerification({
|
|
|
3734
4274
|
timeoutMs = WAIT_TIMEOUT_MS
|
|
3735
4275
|
}) {
|
|
3736
4276
|
const startedAt = Date.now();
|
|
3737
|
-
let current = await
|
|
4277
|
+
let current = await getDomain2(domain, projectUuid, storeHash, accessToken, apiHost);
|
|
3738
4278
|
while (current.verification_status === "pending" && Date.now() - startedAt < timeoutMs) {
|
|
3739
4279
|
await sleep(intervalMs);
|
|
3740
|
-
current = await
|
|
4280
|
+
current = await getDomain2(domain, projectUuid, storeHash, accessToken, apiHost);
|
|
3741
4281
|
}
|
|
3742
4282
|
return current;
|
|
3743
4283
|
}
|
|
3744
4284
|
var add = new Command7("add").configureHelp({ showGlobalOptions: true }).description("Add a custom domain to the current Native Hosting project.").argument("<domain>", "Custom domain to add.").addHelpText(
|
|
3745
4285
|
"after",
|
|
3746
4286
|
`
|
|
4287
|
+
The DNS records that point the domain at this project are printed once the domain
|
|
4288
|
+
is added. They are returned only by this command, so save them: publish the CNAME
|
|
4289
|
+
for a subdomain, or the A record for an apex domain.
|
|
4290
|
+
|
|
3747
4291
|
Examples:
|
|
3748
4292
|
$ catalyst domains add www.example.com
|
|
3749
4293
|
|
|
@@ -3753,9 +4297,9 @@ Examples:
|
|
|
3753
4297
|
const context = resolveDomainCommandContext(options);
|
|
3754
4298
|
await getTelemetry().identify(context.storeHash);
|
|
3755
4299
|
consola.start(`Adding domain ${domain}...`);
|
|
3756
|
-
let
|
|
4300
|
+
let created;
|
|
3757
4301
|
try {
|
|
3758
|
-
|
|
4302
|
+
created = await createDomain(
|
|
3759
4303
|
domain,
|
|
3760
4304
|
context.projectUuid,
|
|
3761
4305
|
context.storeHash,
|
|
@@ -3782,12 +4326,26 @@ Examples:
|
|
|
3782
4326
|
}
|
|
3783
4327
|
throw error;
|
|
3784
4328
|
}
|
|
3785
|
-
consola.success(`Domain ${
|
|
4329
|
+
consola.success(`Domain ${created.domain} added.`);
|
|
4330
|
+
const records = formatPointingRecords(created.pointing_records);
|
|
4331
|
+
let result = created;
|
|
3786
4332
|
if (options.wait && result.verification_status === "pending") {
|
|
3787
4333
|
consola.start(`Waiting for ${result.domain} to verify...`);
|
|
3788
4334
|
result = await waitForDomainVerification({ domain: result.domain, ...context });
|
|
3789
4335
|
}
|
|
3790
4336
|
consola.log(formatDomain(result));
|
|
4337
|
+
if (records) {
|
|
4338
|
+
consola.info(`Point ${created.domain} at this project with one of these DNS records:`);
|
|
4339
|
+
consola.log(records);
|
|
4340
|
+
consola.info(
|
|
4341
|
+
"Use the CNAME for a subdomain, or the A record for an apex domain. Save them now \u2014 only `domains add` returns them."
|
|
4342
|
+
);
|
|
4343
|
+
}
|
|
4344
|
+
if (result.verification_status === "pending") {
|
|
4345
|
+
consola.info(
|
|
4346
|
+
`Run \`catalyst domains status ${result.domain}\` to check verification progress.`
|
|
4347
|
+
);
|
|
4348
|
+
}
|
|
3791
4349
|
process.exit(0);
|
|
3792
4350
|
});
|
|
3793
4351
|
var list = new Command7("list").configureHelp({ showGlobalOptions: true }).description("List custom domains for the current Native Hosting project.").addHelpText(
|
|
@@ -3837,7 +4395,7 @@ Examples:
|
|
|
3837
4395
|
const context = resolveDomainCommandContext(options);
|
|
3838
4396
|
await getTelemetry().identify(context.storeHash);
|
|
3839
4397
|
consola.start(`Fetching status for ${domain}...`);
|
|
3840
|
-
let result = await
|
|
4398
|
+
let result = await getDomain2(
|
|
3841
4399
|
domain,
|
|
3842
4400
|
context.projectUuid,
|
|
3843
4401
|
context.storeHash,
|
|
@@ -3888,7 +4446,7 @@ Examples:
|
|
|
3888
4446
|
throw error;
|
|
3889
4447
|
}
|
|
3890
4448
|
consola.success(`Domain ${domain} claimed.`);
|
|
3891
|
-
let result = await
|
|
4449
|
+
let result = await getDomain2(
|
|
3892
4450
|
domain,
|
|
3893
4451
|
context.projectUuid,
|
|
3894
4452
|
context.storeHash,
|
|
@@ -3927,14 +4485,14 @@ Examples:
|
|
|
3927
4485
|
);
|
|
3928
4486
|
if (!owned) {
|
|
3929
4487
|
consola.start(`Locating the project that owns ${domain}...`);
|
|
3930
|
-
const
|
|
3931
|
-
const candidates =
|
|
4488
|
+
const projects2 = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
|
|
4489
|
+
const candidates = projects2.filter((project) => project.uuid !== context.projectUuid);
|
|
3932
4490
|
const matches = await Promise.all(
|
|
3933
|
-
candidates.map(async (
|
|
3934
|
-
project
|
|
4491
|
+
candidates.map(async (project) => ({
|
|
4492
|
+
project,
|
|
3935
4493
|
found: await findDomain(
|
|
3936
4494
|
domain,
|
|
3937
|
-
|
|
4495
|
+
project.uuid,
|
|
3938
4496
|
context.storeHash,
|
|
3939
4497
|
context.accessToken,
|
|
3940
4498
|
context.apiHost
|
|
@@ -3968,7 +4526,7 @@ Examples:
|
|
|
3968
4526
|
context.apiHost
|
|
3969
4527
|
);
|
|
3970
4528
|
consola.success(`Domain ${domain} transferred.`);
|
|
3971
|
-
let result = await
|
|
4529
|
+
let result = await getDomain2(
|
|
3972
4530
|
domain,
|
|
3973
4531
|
newProjectUuid,
|
|
3974
4532
|
context.storeHash,
|
|
@@ -4000,7 +4558,7 @@ Examples:
|
|
|
4000
4558
|
const context = resolveDomainCommandContext(options);
|
|
4001
4559
|
await getTelemetry().identify(context.storeHash);
|
|
4002
4560
|
consola.start(`Fetching status for ${domain}...`);
|
|
4003
|
-
const current = await
|
|
4561
|
+
const current = await getDomain2(
|
|
4004
4562
|
domain,
|
|
4005
4563
|
context.projectUuid,
|
|
4006
4564
|
context.storeHash,
|
|
@@ -4008,7 +4566,7 @@ Examples:
|
|
|
4008
4566
|
context.apiHost
|
|
4009
4567
|
);
|
|
4010
4568
|
if (current.verification_status === "verified" && !options.force) {
|
|
4011
|
-
const confirmed = await
|
|
4569
|
+
const confirmed = await confirm5({
|
|
4012
4570
|
message: `Remove active domain ${current.domain}? Traffic may stop routing to this project.`,
|
|
4013
4571
|
default: false
|
|
4014
4572
|
});
|
|
@@ -4029,7 +4587,7 @@ Examples:
|
|
|
4029
4587
|
consola.success(`Domain ${current.domain} removed.`);
|
|
4030
4588
|
process.exit(0);
|
|
4031
4589
|
});
|
|
4032
|
-
var domains = new Command7("domains").configureHelp({ showGlobalOptions: true }).description("Manage custom domains for the current Native Hosting project.").addCommand(add).addCommand(list).addCommand(showStatus).addCommand(claim).addCommand(transfer).addCommand(remove);
|
|
4590
|
+
var domains = new Command7("domains").alias("domain").configureHelp({ showGlobalOptions: true }).description("Manage custom domains for the current Native Hosting project.").addCommand(add).addCommand(list).addCommand(showStatus).addCommand(claim).addCommand(transfer).addCommand(remove);
|
|
4033
4591
|
|
|
4034
4592
|
// src/cli/commands/env.ts
|
|
4035
4593
|
import { Command as Command8 } from "commander";
|
|
@@ -4141,8 +4699,19 @@ var StreamError = class extends Error {
|
|
|
4141
4699
|
}
|
|
4142
4700
|
};
|
|
4143
4701
|
var formatMessages = (messages) => messages.map((m) => typeof m === "string" ? m : JSON.stringify(m)).join(" ");
|
|
4702
|
+
var formatRequestLine = (event, entry) => {
|
|
4703
|
+
const { request } = event;
|
|
4704
|
+
const msg = entry ? formatMessages(entry.messages) : "";
|
|
4705
|
+
const msgPart = msg ? ` ${msg}` : "";
|
|
4706
|
+
const level = msg ? entry?.level.toUpperCase() : void 0;
|
|
4707
|
+
const levelPart = level ? ` [${colorize10(LEVEL_COLORS2[level] ?? "white", level)}]` : "";
|
|
4708
|
+
return `[${entry?.timestamp ?? event.timestamp}] ${request.method} ${request.url} (${request.status_code})${levelPart}${msgPart}`;
|
|
4709
|
+
};
|
|
4144
4710
|
var formatLogEvent = (event, format) => {
|
|
4145
|
-
const {
|
|
4711
|
+
const { logs: logEntries, exceptions } = event;
|
|
4712
|
+
if (format === "request" && logEntries.length === 0) {
|
|
4713
|
+
consola.log(formatRequestLine(event));
|
|
4714
|
+
}
|
|
4146
4715
|
logEntries.forEach((entry) => {
|
|
4147
4716
|
const msg = formatMessages(entry.messages);
|
|
4148
4717
|
const level = entry.level.toUpperCase();
|
|
@@ -4152,9 +4721,7 @@ var formatLogEvent = (event, format) => {
|
|
|
4152
4721
|
consola.log(msg);
|
|
4153
4722
|
break;
|
|
4154
4723
|
case "request":
|
|
4155
|
-
consola.log(
|
|
4156
|
-
`[${entry.timestamp}] [${coloredLevel}] ${request.method} ${request.url} (${request.status_code}) ${msg}`
|
|
4157
|
-
);
|
|
4724
|
+
consola.log(formatRequestLine(event, entry));
|
|
4158
4725
|
break;
|
|
4159
4726
|
default:
|
|
4160
4727
|
consola.log(`[${entry.timestamp}] [${coloredLevel}] ${msg}`);
|
|
@@ -4290,6 +4857,19 @@ var tailLogs = async (projectUuid, storeHash, accessToken, apiHost, format, conn
|
|
|
4290
4857
|
var tail = new Command9("tail").configureHelp({ showGlobalOptions: true }).description("Tail live logs from your deployed application.").addHelpText(
|
|
4291
4858
|
"after",
|
|
4292
4859
|
`
|
|
4860
|
+
Formats:
|
|
4861
|
+
default One line per log message: timestamp, level, and message.
|
|
4862
|
+
short Message text only.
|
|
4863
|
+
request One line per log message: timestamp, request method, URL, status
|
|
4864
|
+
code, level, and message. Requests that logged no messages are
|
|
4865
|
+
printed too, without the level and message.
|
|
4866
|
+
pretty Indented JSON of the whole event.
|
|
4867
|
+
json Raw JSON, one event per line (useful for piping to other tools).
|
|
4868
|
+
|
|
4869
|
+
The \`default\` and \`short\` formats only print requests that produced a log
|
|
4870
|
+
message. Use \`--format request\` (or \`json\`/\`pretty\`) to see every request,
|
|
4871
|
+
including ones with no message body.
|
|
4872
|
+
|
|
4293
4873
|
Examples:
|
|
4294
4874
|
$ catalyst logs tail
|
|
4295
4875
|
|
|
@@ -4349,6 +4929,8 @@ function printPaginationHints(pagination, start2, end, options) {
|
|
|
4349
4929
|
...options.urlLike ? [`--url-like ${quoteArg(options.urlLike)}`] : [],
|
|
4350
4930
|
...options.levelMin ? [`--level-min ${options.levelMin}`] : [],
|
|
4351
4931
|
...options.limit != null ? [`--limit ${options.limit}`] : [],
|
|
4932
|
+
// The entry_type filter derives from --format (default/short request only
|
|
4933
|
+
// application logs), so echoing --format is enough to reproduce it.
|
|
4352
4934
|
...options.format !== "default" ? [`--format ${options.format}`] : []
|
|
4353
4935
|
];
|
|
4354
4936
|
const command = `${invocationPrefix()} logs query ${baseFlags.join(" ")}`;
|
|
@@ -4375,6 +4957,10 @@ entries with \`--before\`. Cursors are only valid with the same window and
|
|
|
4375
4957
|
filters they came from. With \`--format json\`, the pagination cursors are
|
|
4376
4958
|
emitted as a final \`{"meta": ...}\` line after the entries.
|
|
4377
4959
|
|
|
4960
|
+
The \`default\` and \`short\` formats show only application log output,
|
|
4961
|
+
excluding Cloudflare's auto-generated per-request rows. Use
|
|
4962
|
+
\`--format request\` (or \`json\`/\`pretty\`) to include them.
|
|
4963
|
+
|
|
4378
4964
|
Examples:
|
|
4379
4965
|
# Last hour of logs
|
|
4380
4966
|
$ catalyst logs query --since 1h
|
|
@@ -4432,9 +5018,11 @@ Examples:
|
|
|
4432
5018
|
await getTelemetry().identify(storeHash);
|
|
4433
5019
|
const projectUuid = resolveProjectUuid(options);
|
|
4434
5020
|
const { start: start2, end } = resolveTimeWindow(options);
|
|
5021
|
+
const hideRequestRows = options.format === "default" || options.format === "short";
|
|
4435
5022
|
const result = await queryLogs(projectUuid, storeHash, accessToken, apiHost, {
|
|
4436
5023
|
start: start2,
|
|
4437
5024
|
end,
|
|
5025
|
+
entryType: hideRequestRows ? "log" : void 0,
|
|
4438
5026
|
method: options.method,
|
|
4439
5027
|
statusCode: options.statusCode,
|
|
4440
5028
|
urlLike: options.urlLike,
|
|
@@ -4466,9 +5054,12 @@ Examples:
|
|
|
4466
5054
|
return;
|
|
4467
5055
|
}
|
|
4468
5056
|
const lineFormat = options.format;
|
|
4469
|
-
ordered.
|
|
5057
|
+
const visible = hideRequestRows ? ordered.filter((entry) => !isRequestLogEntry(entry)) : ordered;
|
|
5058
|
+
const hiddenCount = ordered.length - visible.length;
|
|
5059
|
+
visible.forEach((entry) => consola.log(formatLogEntry(entry, lineFormat)));
|
|
5060
|
+
const shownPart = `${visible.length} ${visible.length === 1 ? "entry" : "entries"} shown`;
|
|
4470
5061
|
consola.info(
|
|
4471
|
-
`${
|
|
5062
|
+
hiddenCount > 0 ? `${shownPart}, ${hiddenCount} request ${hiddenCount === 1 ? "row" : "rows"} hidden (oldest first, times in UTC). Use --format request to see request rows.` : `${shownPart} (oldest first, times in UTC).`
|
|
4472
5063
|
);
|
|
4473
5064
|
printPaginationHints(result.meta?.cursor_pagination, start2, end, options);
|
|
4474
5065
|
} catch (error) {
|
|
@@ -4476,10 +5067,10 @@ Examples:
|
|
|
4476
5067
|
process.exit(1);
|
|
4477
5068
|
}
|
|
4478
5069
|
});
|
|
4479
|
-
var logs = new Command9("logs").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
|
|
5070
|
+
var logs = new Command9("logs").alias("log").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
|
|
4480
5071
|
|
|
4481
|
-
// src/cli/commands/
|
|
4482
|
-
import { confirm as
|
|
5072
|
+
// src/cli/commands/projects.ts
|
|
5073
|
+
import { confirm as confirm6, input as input6, select as select7 } from "@inquirer/prompts";
|
|
4483
5074
|
import { Command as Command10 } from "commander";
|
|
4484
5075
|
import { colorize as colorize11 } from "consola/utils";
|
|
4485
5076
|
async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
@@ -4488,7 +5079,7 @@ async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
|
4488
5079
|
await cleanupCloudflareIncompatibilities(projectDir);
|
|
4489
5080
|
return;
|
|
4490
5081
|
}
|
|
4491
|
-
const shouldSetup = await
|
|
5082
|
+
const shouldSetup = await confirm6({
|
|
4492
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?",
|
|
4493
5084
|
default: true
|
|
4494
5085
|
});
|
|
@@ -4507,22 +5098,22 @@ var list3 = new Command10("list").configureHelp({ showGlobalOptions: true }).des
|
|
|
4507
5098
|
"after",
|
|
4508
5099
|
`
|
|
4509
5100
|
Example:
|
|
4510
|
-
$ catalyst
|
|
5101
|
+
$ catalyst projects list`
|
|
4511
5102
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).action(async (options) => {
|
|
4512
5103
|
const config2 = getProjectConfig();
|
|
4513
5104
|
const apiHost = resolveApiHost(options, config2);
|
|
4514
5105
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
4515
5106
|
await getTelemetry().identify(storeHash);
|
|
4516
5107
|
consola.start("Fetching projects...");
|
|
4517
|
-
const
|
|
5108
|
+
const storeProjects = await fetchProjects(storeHash, accessToken, apiHost);
|
|
4518
5109
|
consola.success("Projects fetched.");
|
|
4519
|
-
if (
|
|
5110
|
+
if (storeProjects.length === 0) {
|
|
4520
5111
|
consola.info("No projects found.");
|
|
4521
5112
|
process.exit(0);
|
|
4522
5113
|
return;
|
|
4523
5114
|
}
|
|
4524
5115
|
const linkedProjectUuid = config2.get("projectUuid");
|
|
4525
|
-
const projectList =
|
|
5116
|
+
const projectList = storeProjects.map((p) => {
|
|
4526
5117
|
const marker = p.uuid === linkedProjectUuid ? ` ${colorize11("green", "[linked]")}` : "";
|
|
4527
5118
|
const hostnames = p.deployment_hostnames.length === 0 ? [" (not deployed)"] : p.deployment_hostnames.map(
|
|
4528
5119
|
(hostname) => ` ${colorize11("blue", `https://${hostname}`)}`
|
|
@@ -4538,7 +5129,7 @@ var create3 = new Command10("create").configureHelp({ showGlobalOptions: true })
|
|
|
4538
5129
|
"after",
|
|
4539
5130
|
`
|
|
4540
5131
|
Example:
|
|
4541
|
-
$ catalyst
|
|
5132
|
+
$ catalyst projects create`
|
|
4542
5133
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).action(async (options) => {
|
|
4543
5134
|
const config2 = getProjectConfig();
|
|
4544
5135
|
const apiHost = resolveApiHost(options, config2);
|
|
@@ -4558,7 +5149,7 @@ Example:
|
|
|
4558
5149
|
} catch (error) {
|
|
4559
5150
|
if (error instanceof LoginAbortedError) {
|
|
4560
5151
|
consola.info(
|
|
4561
|
-
"Login aborted. Re-run `catalyst
|
|
5152
|
+
"Login aborted. Re-run `catalyst projects create` when you have your credentials ready."
|
|
4562
5153
|
);
|
|
4563
5154
|
process.exit(0);
|
|
4564
5155
|
return;
|
|
@@ -4567,7 +5158,7 @@ Example:
|
|
|
4567
5158
|
}
|
|
4568
5159
|
}
|
|
4569
5160
|
await getTelemetry().identify(storeHash);
|
|
4570
|
-
const newProjectName = await
|
|
5161
|
+
const newProjectName = await input6({ message: "Enter a name for the new project:" });
|
|
4571
5162
|
const data = await createProject(newProjectName, storeHash, accessToken, apiHost);
|
|
4572
5163
|
consola.success(`Project "${data.name}" created successfully.`);
|
|
4573
5164
|
consola.start("Writing project UUID to .bigcommerce/project.json...");
|
|
@@ -4584,10 +5175,10 @@ var link2 = new Command10("link").configureHelp({ showGlobalOptions: true }).des
|
|
|
4584
5175
|
`
|
|
4585
5176
|
Examples:
|
|
4586
5177
|
# Link interactively (prompts to select or create)
|
|
4587
|
-
$ catalyst
|
|
5178
|
+
$ catalyst projects link
|
|
4588
5179
|
|
|
4589
5180
|
# Link using a project UUID directly
|
|
4590
|
-
$ catalyst
|
|
5181
|
+
$ catalyst projects link --project-uuid <UUID>`
|
|
4591
5182
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).action(async (options) => {
|
|
4592
5183
|
const config2 = getProjectConfig();
|
|
4593
5184
|
const apiHost = resolveApiHost(options, config2);
|
|
@@ -4617,7 +5208,7 @@ Examples:
|
|
|
4617
5208
|
} catch (error) {
|
|
4618
5209
|
if (error instanceof NoLinkedProjectError) {
|
|
4619
5210
|
consola.info(
|
|
4620
|
-
"When you're ready to create a project, run `catalyst
|
|
5211
|
+
"When you're ready to create a project, run `catalyst projects create` or re-run `catalyst projects link`."
|
|
4621
5212
|
);
|
|
4622
5213
|
process.exit(0);
|
|
4623
5214
|
throw error;
|
|
@@ -4635,13 +5226,13 @@ var del = new Command10("delete").configureHelp({ showGlobalOptions: true }).des
|
|
|
4635
5226
|
`
|
|
4636
5227
|
Examples:
|
|
4637
5228
|
# Select a project to delete interactively
|
|
4638
|
-
$ catalyst
|
|
5229
|
+
$ catalyst projects delete
|
|
4639
5230
|
|
|
4640
5231
|
# Delete a specific project (still prompts for confirmation)
|
|
4641
|
-
$ catalyst
|
|
5232
|
+
$ catalyst projects delete --project-uuid <UUID>
|
|
4642
5233
|
|
|
4643
5234
|
# Skip the confirmation prompt
|
|
4644
|
-
$ catalyst
|
|
5235
|
+
$ catalyst projects delete --project-uuid <UUID> --force`
|
|
4645
5236
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--force", "Skip the confirmation prompt before deleting.").action(async (options) => {
|
|
4646
5237
|
const config2 = getProjectConfig();
|
|
4647
5238
|
const apiHost = resolveApiHost(options, config2);
|
|
@@ -4651,9 +5242,9 @@ Examples:
|
|
|
4651
5242
|
let targetName;
|
|
4652
5243
|
if (!targetUuid) {
|
|
4653
5244
|
consola.start("Fetching projects...");
|
|
4654
|
-
const
|
|
5245
|
+
const storeProjects = await fetchProjects(storeHash, accessToken, apiHost);
|
|
4655
5246
|
consola.success("Projects fetched.");
|
|
4656
|
-
if (
|
|
5247
|
+
if (storeProjects.length === 0) {
|
|
4657
5248
|
consola.info("No projects found.");
|
|
4658
5249
|
process.exit(0);
|
|
4659
5250
|
return;
|
|
@@ -4662,7 +5253,7 @@ Examples:
|
|
|
4662
5253
|
const selected = await select7({
|
|
4663
5254
|
message: "Select a project to delete (Press <enter> to select).",
|
|
4664
5255
|
choices: [
|
|
4665
|
-
...
|
|
5256
|
+
...storeProjects.map((p) => ({
|
|
4666
5257
|
name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize11("green", "[linked]")}` : p.name,
|
|
4667
5258
|
value: p.uuid,
|
|
4668
5259
|
description: p.uuid
|
|
@@ -4675,7 +5266,7 @@ Examples:
|
|
|
4675
5266
|
process.exit(0);
|
|
4676
5267
|
return;
|
|
4677
5268
|
}
|
|
4678
|
-
const matched =
|
|
5269
|
+
const matched = storeProjects.find((p) => p.uuid === selected);
|
|
4679
5270
|
if (!matched) {
|
|
4680
5271
|
throw new Error(`Selected project ${String(selected)} not found in fetched list.`);
|
|
4681
5272
|
}
|
|
@@ -4684,7 +5275,7 @@ Examples:
|
|
|
4684
5275
|
}
|
|
4685
5276
|
if (!options.force) {
|
|
4686
5277
|
const label = targetName ? `"${targetName}" (${targetUuid})` : targetUuid;
|
|
4687
|
-
const confirmed = await
|
|
5278
|
+
const confirmed = await confirm6({
|
|
4688
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.`,
|
|
4689
5280
|
default: false
|
|
4690
5281
|
});
|
|
@@ -4703,13 +5294,13 @@ Examples:
|
|
|
4703
5294
|
}
|
|
4704
5295
|
process.exit(0);
|
|
4705
5296
|
});
|
|
4706
|
-
var
|
|
5297
|
+
var projects = new Command10("projects").alias("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure projects.").addCommand(create3).addCommand(list3).addCommand(link2).addCommand(del);
|
|
4707
5298
|
|
|
4708
5299
|
// src/cli/commands/start.ts
|
|
4709
5300
|
import { Command as Command11 } from "commander";
|
|
4710
5301
|
import { execa as execa3 } from "execa";
|
|
4711
5302
|
import { existsSync as existsSync6, lstatSync, symlinkSync } from "fs";
|
|
4712
|
-
import { join as
|
|
5303
|
+
import { join as join14, relative } from "path";
|
|
4713
5304
|
var start = new Command11("start").configureHelp({ showGlobalOptions: true }).description(
|
|
4714
5305
|
"Start a local preview of your Catalyst storefront using the OpenNext Cloudflare adapter."
|
|
4715
5306
|
).addHelpText(
|
|
@@ -4727,11 +5318,11 @@ Example:
|
|
|
4727
5318
|
});
|
|
4728
5319
|
return;
|
|
4729
5320
|
}
|
|
4730
|
-
const envLocal =
|
|
4731
|
-
const devVars =
|
|
5321
|
+
const envLocal = join14(process.cwd(), ".env.local");
|
|
5322
|
+
const devVars = join14(process.cwd(), ".bigcommerce", ".dev.vars");
|
|
4732
5323
|
if (existsSync6(envLocal)) {
|
|
4733
5324
|
if (!existsSync6(devVars)) {
|
|
4734
|
-
const target = relative(
|
|
5325
|
+
const target = relative(join14(process.cwd(), ".bigcommerce"), envLocal);
|
|
4735
5326
|
symlinkSync(target, devVars);
|
|
4736
5327
|
consola.success("Symlinked .bigcommerce/.dev.vars \u2192 .env.local");
|
|
4737
5328
|
} else if (!lstatSync(devVars).isSymbolicLink()) {
|
|
@@ -4751,7 +5342,7 @@ Example:
|
|
|
4751
5342
|
"opennextjs-cloudflare",
|
|
4752
5343
|
"preview",
|
|
4753
5344
|
"--config",
|
|
4754
|
-
|
|
5345
|
+
join14(".bigcommerce", "wrangler.jsonc")
|
|
4755
5346
|
],
|
|
4756
5347
|
{
|
|
4757
5348
|
stdio: "inherit",
|
|
@@ -4807,10 +5398,10 @@ Examples:
|
|
|
4807
5398
|
|
|
4808
5399
|
// src/cli/commands/upgrade.ts
|
|
4809
5400
|
import { Command as Command13, Option as Option10 } from "@commander-js/extra-typings";
|
|
4810
|
-
import { confirm as
|
|
5401
|
+
import { confirm as confirm7 } from "@inquirer/prompts";
|
|
4811
5402
|
import { execa as execa4 } from "execa";
|
|
4812
5403
|
import {
|
|
4813
|
-
access as
|
|
5404
|
+
access as access3,
|
|
4814
5405
|
copyFile as copyFile2,
|
|
4815
5406
|
cp as cp2,
|
|
4816
5407
|
mkdir,
|
|
@@ -4822,7 +5413,8 @@ import {
|
|
|
4822
5413
|
writeFile as writeFile2
|
|
4823
5414
|
} from "fs/promises";
|
|
4824
5415
|
import { homedir, tmpdir } from "os";
|
|
4825
|
-
import { dirname as dirname5, join as
|
|
5416
|
+
import { dirname as dirname5, join as join15, relative as relative2 } from "path";
|
|
5417
|
+
import { minVersion as minVersion2, satisfies as satisfies2, lt as semverLt2, validRange as validRange2, valid as validSemver2 } from "semver";
|
|
4826
5418
|
import yoctoSpinner4 from "yocto-spinner";
|
|
4827
5419
|
import { z as z17 } from "zod";
|
|
4828
5420
|
var CorePackageJson = z17.object({
|
|
@@ -4830,6 +5422,20 @@ var CorePackageJson = z17.object({
|
|
|
4830
5422
|
version: z17.string(),
|
|
4831
5423
|
catalyst: z17.object({ version: z17.string(), ref: z17.string() }).optional()
|
|
4832
5424
|
});
|
|
5425
|
+
var DEP_FIELDS2 = [
|
|
5426
|
+
"dependencies",
|
|
5427
|
+
"devDependencies",
|
|
5428
|
+
"optionalDependencies",
|
|
5429
|
+
"peerDependencies"
|
|
5430
|
+
];
|
|
5431
|
+
var DepsPackageJson = z17.looseObject({
|
|
5432
|
+
dependencies: z17.record(z17.string(), z17.string()).optional(),
|
|
5433
|
+
devDependencies: z17.record(z17.string(), z17.string()).optional(),
|
|
5434
|
+
optionalDependencies: z17.record(z17.string(), z17.string()).optional(),
|
|
5435
|
+
peerDependencies: z17.record(z17.string(), z17.string()).optional()
|
|
5436
|
+
});
|
|
5437
|
+
var WorkspacePackageJson = z17.object({ name: z17.string(), version: z17.string() });
|
|
5438
|
+
var CATALYST_CLI_PACKAGE = "@bigcommerce/catalyst";
|
|
4833
5439
|
var DEFAULT_REPOSITORY = "bigcommerce/catalyst";
|
|
4834
5440
|
var MOVING_TAGS = /* @__PURE__ */ new Set(["latest", "canary", "alpha"]);
|
|
4835
5441
|
var KNOWN_FAMILIES = /* @__PURE__ */ new Set([
|
|
@@ -4838,14 +5444,14 @@ var KNOWN_FAMILIES = /* @__PURE__ */ new Set([
|
|
|
4838
5444
|
"@bigcommerce/catalyst-b2b-makeswift",
|
|
4839
5445
|
"@bigcommerce/catalyst-b2b"
|
|
4840
5446
|
]);
|
|
4841
|
-
var CACHE_DIR =
|
|
4842
|
-
var pathExists = (p) =>
|
|
5447
|
+
var CACHE_DIR = join15(homedir(), ".cache", "catalyst-cli", "cores");
|
|
5448
|
+
var pathExists = (p) => access3(p).then(() => true).catch(() => false);
|
|
4843
5449
|
async function listFiles(dir, prefix = "") {
|
|
4844
5450
|
const entries = await readdir2(dir, { withFileTypes: true }).catch(() => []);
|
|
4845
5451
|
const nested = await Promise.all(
|
|
4846
5452
|
entries.map((entry) => {
|
|
4847
5453
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
4848
|
-
return entry.isDirectory() ? listFiles(
|
|
5454
|
+
return entry.isDirectory() ? listFiles(join15(dir, entry.name), rel) : Promise.resolve([rel]);
|
|
4849
5455
|
})
|
|
4850
5456
|
);
|
|
4851
5457
|
return nested.flat();
|
|
@@ -4861,7 +5467,7 @@ async function computeBaseSimilarity(baseDir, destDir) {
|
|
|
4861
5467
|
const baseFiles = await listFiles(baseDir);
|
|
4862
5468
|
if (baseFiles.length === 0) return 0;
|
|
4863
5469
|
const matches = await Promise.all(
|
|
4864
|
-
baseFiles.map((rel) => filesEqual(
|
|
5470
|
+
baseFiles.map((rel) => filesEqual(join15(baseDir, rel), join15(destDir, rel)))
|
|
4865
5471
|
);
|
|
4866
5472
|
return matches.filter(Boolean).length / baseFiles.length;
|
|
4867
5473
|
}
|
|
@@ -4880,10 +5486,79 @@ function parseRef(ref) {
|
|
|
4880
5486
|
return { packageName: ref.slice(0, lastAt), version: ref.slice(lastAt + 1) };
|
|
4881
5487
|
}
|
|
4882
5488
|
async function readResolvedVersion(coreDir) {
|
|
4883
|
-
const raw = await readFile2(
|
|
5489
|
+
const raw = await readFile2(join15(coreDir, "package.json"), "utf-8");
|
|
4884
5490
|
const pkg = CorePackageJson.parse(JSON.parse(raw));
|
|
4885
5491
|
return pkg.catalyst?.version ?? pkg.version;
|
|
4886
5492
|
}
|
|
5493
|
+
var readDepSpecifiers = (raw) => {
|
|
5494
|
+
let parsed;
|
|
5495
|
+
try {
|
|
5496
|
+
parsed = DepsPackageJson.parse(JSON.parse(raw));
|
|
5497
|
+
} catch {
|
|
5498
|
+
return {};
|
|
5499
|
+
}
|
|
5500
|
+
return Object.fromEntries(DEP_FIELDS2.flatMap((field) => Object.entries(parsed[field] ?? {})));
|
|
5501
|
+
};
|
|
5502
|
+
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5503
|
+
var rewriteWorkspaceSpecifier = (raw, name, version2) => raw.replace(
|
|
5504
|
+
new RegExp(`("${escapeRegExp(name)}"\\s*:\\s*)"workspace:[^"]*"`, "g"),
|
|
5505
|
+
(_match, key) => `${key}"^${version2}"`
|
|
5506
|
+
);
|
|
5507
|
+
async function normalizeWorkspaceDeps(ourPkgRaw, baseDir, theirsDir, baseVersions, targetVersions) {
|
|
5508
|
+
const basePath = join15(baseDir, "package.json");
|
|
5509
|
+
const theirsPath = join15(theirsDir, "package.json");
|
|
5510
|
+
const [originalBase, originalTheirs] = await Promise.all([
|
|
5511
|
+
readFile2(basePath, "utf-8").catch(() => null),
|
|
5512
|
+
readFile2(theirsPath, "utf-8").catch(() => null)
|
|
5513
|
+
]);
|
|
5514
|
+
if (originalBase === null || originalTheirs === null) return { workspace: [], bumped: [] };
|
|
5515
|
+
const ourSpecifiers = readDepSpecifiers(ourPkgRaw);
|
|
5516
|
+
const names = new Set(
|
|
5517
|
+
[
|
|
5518
|
+
...Object.entries(readDepSpecifiers(originalBase)),
|
|
5519
|
+
...Object.entries(readDepSpecifiers(originalTheirs))
|
|
5520
|
+
].filter(([, range]) => range?.startsWith("workspace:")).map(([name]) => name)
|
|
5521
|
+
);
|
|
5522
|
+
const decisions = [...names].map((name) => ({
|
|
5523
|
+
name,
|
|
5524
|
+
ours: ourSpecifiers[name],
|
|
5525
|
+
baseVersion: baseVersions[name],
|
|
5526
|
+
targetVersion: targetVersions[name]
|
|
5527
|
+
}));
|
|
5528
|
+
const workspace = decisions.flatMap(
|
|
5529
|
+
({ name, ours, targetVersion }) => ours?.startsWith("workspace:") && targetVersion !== void 0 ? [{ name, ours, version: targetVersion }] : []
|
|
5530
|
+
);
|
|
5531
|
+
const normalizable = decisions.filter(({ ours }) => !ours?.startsWith("workspace:"));
|
|
5532
|
+
const baseRaw = normalizable.reduce(
|
|
5533
|
+
(acc, { name, baseVersion }) => baseVersion === void 0 ? acc : rewriteWorkspaceSpecifier(acc, name, baseVersion),
|
|
5534
|
+
originalBase
|
|
5535
|
+
);
|
|
5536
|
+
const theirsRaw = normalizable.reduce(
|
|
5537
|
+
(acc, { name, targetVersion }) => targetVersion === void 0 ? acc : rewriteWorkspaceSpecifier(acc, name, targetVersion),
|
|
5538
|
+
originalTheirs
|
|
5539
|
+
);
|
|
5540
|
+
const bumped = normalizable.filter(
|
|
5541
|
+
({ baseVersion, targetVersion }) => baseVersion !== void 0 && targetVersion !== void 0 && baseVersion !== targetVersion
|
|
5542
|
+
).map(({ name }) => name);
|
|
5543
|
+
await Promise.all([
|
|
5544
|
+
baseRaw === originalBase ? null : writeFile2(basePath, baseRaw),
|
|
5545
|
+
theirsRaw === originalTheirs ? null : writeFile2(theirsPath, theirsRaw)
|
|
5546
|
+
]);
|
|
5547
|
+
return { workspace, bumped };
|
|
5548
|
+
}
|
|
5549
|
+
var migrateWorkspaceDeps = (raw, findings) => findings.reduce((acc, { name, version: version2 }) => rewriteWorkspaceSpecifier(acc, name, version2), raw);
|
|
5550
|
+
async function findStaleCli(pkgRaw) {
|
|
5551
|
+
const range = readDepSpecifiers(pkgRaw)[CATALYST_CLI_PACKAGE];
|
|
5552
|
+
if (range === void 0 || !validRange2(range)) return null;
|
|
5553
|
+
const body = await fetch(`https://registry.npmjs.org/${CATALYST_CLI_PACKAGE}/latest`, {
|
|
5554
|
+
// A hanging registry would otherwise stall the CLI indefinitely.
|
|
5555
|
+
signal: AbortSignal.timeout(5e3)
|
|
5556
|
+
}).then((res) => res.ok ? res.json() : null).catch(() => null);
|
|
5557
|
+
const latest = z17.object({ version: z17.string() }).safeParse(body).data?.version;
|
|
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;
|
|
5561
|
+
}
|
|
4887
5562
|
async function mergeFile(oursPath, basePath, theirsPath) {
|
|
4888
5563
|
const merged = await execa4(
|
|
4889
5564
|
"git",
|
|
@@ -4907,9 +5582,9 @@ async function mergeFile(oursPath, basePath, theirsPath) {
|
|
|
4907
5582
|
return (merged.exitCode ?? 0) > 0;
|
|
4908
5583
|
}
|
|
4909
5584
|
async function mergeModified(rel, baseDir, theirsDir, catalystRoot) {
|
|
4910
|
-
const basePath =
|
|
4911
|
-
const theirsPath =
|
|
4912
|
-
const oursPath =
|
|
5585
|
+
const basePath = join15(baseDir, rel);
|
|
5586
|
+
const theirsPath = join15(theirsDir, rel);
|
|
5587
|
+
const oursPath = join15(catalystRoot, rel);
|
|
4913
5588
|
if (await filesEqual(basePath, theirsPath)) return null;
|
|
4914
5589
|
if (!await pathExists(oursPath)) {
|
|
4915
5590
|
await copyInto(theirsPath, oursPath);
|
|
@@ -4925,8 +5600,8 @@ async function mergeModified(rel, baseDir, theirsDir, catalystRoot) {
|
|
|
4925
5600
|
return await mergeFile(oursPath, basePath, theirsPath) ? "conflicted" : "applied";
|
|
4926
5601
|
}
|
|
4927
5602
|
async function mergeAdded(rel, theirsDir, catalystRoot, emptyFile) {
|
|
4928
|
-
const theirsPath =
|
|
4929
|
-
const oursPath =
|
|
5603
|
+
const theirsPath = join15(theirsDir, rel);
|
|
5604
|
+
const oursPath = join15(catalystRoot, rel);
|
|
4930
5605
|
if (!await pathExists(oursPath)) {
|
|
4931
5606
|
await copyInto(theirsPath, oursPath);
|
|
4932
5607
|
return "added";
|
|
@@ -4937,8 +5612,8 @@ async function mergeAdded(rel, theirsDir, catalystRoot, emptyFile) {
|
|
|
4937
5612
|
return hadConflict ? "conflicted" : "applied";
|
|
4938
5613
|
}
|
|
4939
5614
|
async function mergeDeleted(rel, baseDir, catalystRoot) {
|
|
4940
|
-
const basePath =
|
|
4941
|
-
const oursPath =
|
|
5615
|
+
const basePath = join15(baseDir, rel);
|
|
5616
|
+
const oursPath = join15(catalystRoot, rel);
|
|
4942
5617
|
if (!await pathExists(oursPath)) return null;
|
|
4943
5618
|
if (await filesEqual(oursPath, basePath)) {
|
|
4944
5619
|
await rm2(oursPath, { force: true });
|
|
@@ -4984,15 +5659,15 @@ async function resolveStrategy(strategy) {
|
|
|
4984
5659
|
return "per-file";
|
|
4985
5660
|
}
|
|
4986
5661
|
async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
|
|
4987
|
-
const scratch = await mkdtemp(
|
|
5662
|
+
const scratch = await mkdtemp(join15(tmpdir(), "catalyst-merge-"));
|
|
4988
5663
|
try {
|
|
4989
5664
|
await execa4("git", ["init", "-q", scratch]);
|
|
4990
5665
|
await execa4("git", ["config", "core.autocrlf", "false"], {
|
|
4991
|
-
env: { ...process.env, GIT_DIR:
|
|
5666
|
+
env: { ...process.env, GIT_DIR: join15(scratch, ".git") }
|
|
4992
5667
|
});
|
|
4993
5668
|
const env2 = {
|
|
4994
5669
|
...process.env,
|
|
4995
|
-
GIT_DIR:
|
|
5670
|
+
GIT_DIR: join15(scratch, ".git"),
|
|
4996
5671
|
GIT_AUTHOR_NAME: "catalyst",
|
|
4997
5672
|
GIT_AUTHOR_EMAIL: "catalyst@bigcommerce.com",
|
|
4998
5673
|
GIT_COMMITTER_NAME: "catalyst",
|
|
@@ -5002,7 +5677,7 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
|
|
|
5002
5677
|
const sideEnv = {
|
|
5003
5678
|
...env2,
|
|
5004
5679
|
GIT_WORK_TREE: workTree,
|
|
5005
|
-
GIT_INDEX_FILE:
|
|
5680
|
+
GIT_INDEX_FILE: join15(scratch, `index.${tag}`)
|
|
5006
5681
|
};
|
|
5007
5682
|
await execa4("git", ["add", "-A"], { env: sideEnv });
|
|
5008
5683
|
return (await execa4("git", ["write-tree"], { env: sideEnv })).stdout.trim();
|
|
@@ -5046,7 +5721,7 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
|
|
|
5046
5721
|
const outEnv = {
|
|
5047
5722
|
...env2,
|
|
5048
5723
|
GIT_WORK_TREE: catalystRoot,
|
|
5049
|
-
GIT_INDEX_FILE:
|
|
5724
|
+
GIT_INDEX_FILE: join15(scratch, "index.out")
|
|
5050
5725
|
};
|
|
5051
5726
|
await execa4("git", ["read-tree", mergedTree], { env: outEnv });
|
|
5052
5727
|
await execa4("git", ["checkout-index", "-a", "-f"], { env: outEnv });
|
|
@@ -5065,7 +5740,7 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
|
|
|
5065
5740
|
}
|
|
5066
5741
|
}
|
|
5067
5742
|
const deleted = changes.filter((change) => change.status === "D").map((change) => change.path);
|
|
5068
|
-
await Promise.all(deleted.map((path) => rm2(
|
|
5743
|
+
await Promise.all(deleted.map((path) => rm2(join15(catalystRoot, path), { force: true })));
|
|
5069
5744
|
return {
|
|
5070
5745
|
applied: changes.filter((change) => change.status !== "A" && change.status !== "D").map((change) => change.path),
|
|
5071
5746
|
added: changes.filter((change) => change.status === "A").map((change) => change.path),
|
|
@@ -5108,9 +5783,9 @@ async function applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stam
|
|
|
5108
5783
|
stillConflicted.map(async (rel) => {
|
|
5109
5784
|
const repoPath = toRepoPath(rel);
|
|
5110
5785
|
const [base, ours, theirs] = await Promise.all([
|
|
5111
|
-
hashBlob(
|
|
5786
|
+
hashBlob(join15(baseDir, rel)),
|
|
5112
5787
|
headEntry(repoPath),
|
|
5113
|
-
hashBlob(
|
|
5788
|
+
hashBlob(join15(theirsDir, rel))
|
|
5114
5789
|
]);
|
|
5115
5790
|
return [
|
|
5116
5791
|
`0 ${NULL_OID} ${repoPath}`,
|
|
@@ -5133,10 +5808,10 @@ async function resolveProject2(cwd) {
|
|
|
5133
5808
|
} catch {
|
|
5134
5809
|
return null;
|
|
5135
5810
|
}
|
|
5136
|
-
const candidateDirs = [.../* @__PURE__ */ new Set([
|
|
5811
|
+
const candidateDirs = [.../* @__PURE__ */ new Set([join15(gitRoot, "core"), gitRoot, join15(cwd, "core"), cwd])];
|
|
5137
5812
|
const parsed = (await Promise.all(
|
|
5138
5813
|
candidateDirs.map(async (dir) => {
|
|
5139
|
-
const pkgPath =
|
|
5814
|
+
const pkgPath = join15(dir, "package.json");
|
|
5140
5815
|
const raw = await readFile2(pkgPath, "utf-8").catch(() => null);
|
|
5141
5816
|
if (!raw) return null;
|
|
5142
5817
|
let pkgJson;
|
|
@@ -5160,13 +5835,44 @@ async function resolveProject2(cwd) {
|
|
|
5160
5835
|
pkg: chosen.pkg
|
|
5161
5836
|
};
|
|
5162
5837
|
}
|
|
5838
|
+
var readWorkspaceVersions = async (repoDir) => {
|
|
5839
|
+
const packagesDir = join15(repoDir, "packages");
|
|
5840
|
+
const entries = await readdir2(packagesDir, { withFileTypes: true }).catch(() => []);
|
|
5841
|
+
const manifests = await Promise.all(
|
|
5842
|
+
entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
5843
|
+
const raw = await readFile2(join15(packagesDir, entry.name, "package.json"), "utf-8").catch(
|
|
5844
|
+
() => null
|
|
5845
|
+
);
|
|
5846
|
+
if (raw === null) return null;
|
|
5847
|
+
try {
|
|
5848
|
+
return WorkspacePackageJson.parse(JSON.parse(raw));
|
|
5849
|
+
} catch {
|
|
5850
|
+
return null;
|
|
5851
|
+
}
|
|
5852
|
+
})
|
|
5853
|
+
);
|
|
5854
|
+
return Object.fromEntries(
|
|
5855
|
+
manifests.filter((pkg) => pkg !== null).map(({ name, version: version2 }) => [name, version2])
|
|
5856
|
+
);
|
|
5857
|
+
};
|
|
5858
|
+
var readCachedVersions = async (path) => {
|
|
5859
|
+
const raw = await readFile2(path, "utf-8").catch(() => null);
|
|
5860
|
+
if (raw === null) return null;
|
|
5861
|
+
try {
|
|
5862
|
+
return z17.record(z17.string(), z17.string()).parse(JSON.parse(raw));
|
|
5863
|
+
} catch {
|
|
5864
|
+
return null;
|
|
5865
|
+
}
|
|
5866
|
+
};
|
|
5163
5867
|
async function downloadCore(repository, ref, destDir, token) {
|
|
5164
5868
|
const cacheable = !MOVING_TAGS.has(parseRef(ref).version);
|
|
5165
5869
|
const cacheKey = `${repository.replace(/[^a-zA-Z0-9._-]/g, "_")}__${ref.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
5166
|
-
const cachePath =
|
|
5167
|
-
|
|
5870
|
+
const cachePath = join15(CACHE_DIR, cacheKey);
|
|
5871
|
+
const versionsPath = `${cachePath}.versions.json`;
|
|
5872
|
+
const cachedVersions = cacheable && await pathExists(join15(cachePath, "package.json")) ? await readCachedVersions(versionsPath) : null;
|
|
5873
|
+
if (cachedVersions) {
|
|
5168
5874
|
await cp2(cachePath, destDir, { recursive: true });
|
|
5169
|
-
return;
|
|
5875
|
+
return cachedVersions;
|
|
5170
5876
|
}
|
|
5171
5877
|
const url = `https://api.github.com/repos/${repository}/tarball/${encodeURIComponent(ref)}`;
|
|
5172
5878
|
const headers = {
|
|
@@ -5186,32 +5892,52 @@ async function downloadCore(repository, ref, destDir, token) {
|
|
|
5186
5892
|
await writeFile2(tarballPath, Buffer.from(await res.arrayBuffer()));
|
|
5187
5893
|
await mkdir(rawDir, { recursive: true });
|
|
5188
5894
|
await execa4("tar", ["-xzf", tarballPath, "-C", rawDir, "--strip-components=1"]);
|
|
5189
|
-
const coreDir =
|
|
5895
|
+
const coreDir = join15(rawDir, "core");
|
|
5190
5896
|
const sourceDir = await pathExists(coreDir) ? coreDir : rawDir;
|
|
5897
|
+
const workspaceVersions = await readWorkspaceVersions(rawDir);
|
|
5191
5898
|
await rename(sourceDir, destDir);
|
|
5192
5899
|
await rm2(tarballPath, { force: true });
|
|
5193
5900
|
await rm2(rawDir, { recursive: true, force: true });
|
|
5194
5901
|
if (cacheable) {
|
|
5195
5902
|
await mkdir(CACHE_DIR, { recursive: true });
|
|
5196
|
-
await cp2(destDir, cachePath, { recursive: true }).catch(() => {
|
|
5903
|
+
await cp2(destDir, cachePath, { recursive: true }).then(() => writeFile2(versionsPath, JSON.stringify(workspaceVersions))).catch(() => {
|
|
5197
5904
|
});
|
|
5198
5905
|
}
|
|
5906
|
+
return workspaceVersions;
|
|
5199
5907
|
}
|
|
5200
|
-
async function resolveBaseRef(
|
|
5201
|
-
if (
|
|
5908
|
+
async function resolveBaseRef(project, fromOption, assumeYes) {
|
|
5909
|
+
if (project.pkg.catalyst?.ref) return project.pkg.catalyst.ref;
|
|
5202
5910
|
if (fromOption) return fromOption;
|
|
5203
|
-
const family =
|
|
5204
|
-
const guess = `${family}@${
|
|
5911
|
+
const family = project.pkg.name && KNOWN_FAMILIES.has(project.pkg.name) ? project.pkg.name : "@bigcommerce/catalyst-core";
|
|
5912
|
+
const guess = `${family}@${project.pkg.version}`;
|
|
5205
5913
|
consola.warn(`No \`catalyst.ref\` found in package.json. Inferred base: ${guess}`);
|
|
5206
5914
|
if (assumeYes) return guess;
|
|
5207
|
-
const ok = await
|
|
5915
|
+
const ok = await confirm7({ message: `Upgrade from ${guess}?`, default: true }).catch(() => false);
|
|
5208
5916
|
if (!ok) {
|
|
5209
5917
|
consola.info("Re-run with `--from <ref>` to set the base version explicitly.");
|
|
5210
5918
|
return null;
|
|
5211
5919
|
}
|
|
5212
5920
|
return guess;
|
|
5213
5921
|
}
|
|
5214
|
-
function
|
|
5922
|
+
function printWorkspaceFindings(findings) {
|
|
5923
|
+
if (findings.length === 0) return;
|
|
5924
|
+
const pad = Math.max(...findings.map((finding) => finding.name.length));
|
|
5925
|
+
const rows = findings.map((finding) => ` ${finding.name.padEnd(pad)} ${finding.ours} \u2192 ^${finding.version}`).join("\n");
|
|
5926
|
+
consola.warn(
|
|
5927
|
+
`Catalyst dependencies still using the workspace protocol (${findings.length}):
|
|
5928
|
+
${rows}
|
|
5929
|
+
|
|
5930
|
+
They resolve to your local packages/ copies, which \`catalyst upgrade\` does not update \u2014 so they never receive upgrades.`
|
|
5931
|
+
);
|
|
5932
|
+
}
|
|
5933
|
+
function printSummary({
|
|
5934
|
+
result,
|
|
5935
|
+
relDir,
|
|
5936
|
+
stampedPkg,
|
|
5937
|
+
migratedDeps,
|
|
5938
|
+
packageManager,
|
|
5939
|
+
staleCli
|
|
5940
|
+
}) {
|
|
5215
5941
|
const unresolved = result.conflicted.filter((f) => !(stampedPkg && f === "package.json"));
|
|
5216
5942
|
const parts = [
|
|
5217
5943
|
`${result.applied.length} updated`,
|
|
@@ -5221,18 +5947,31 @@ function printSummary(result, relDir, stampedPkg) {
|
|
|
5221
5947
|
if (unresolved.length) parts.push(`${unresolved.length} with conflicts`);
|
|
5222
5948
|
consola.log(`
|
|
5223
5949
|
${parts.join(", ")}`);
|
|
5950
|
+
const pkgConflicted = unresolved.includes("package.json");
|
|
5951
|
+
const depsChanged = migratedDeps > 0 || [...result.applied, ...result.added, ...result.conflicted].includes("package.json");
|
|
5952
|
+
if (depsChanged && !pkgConflicted) {
|
|
5953
|
+
consola.info(
|
|
5954
|
+
`Dependencies changed \u2014 run \`${packageManager} install\` to update your lockfile.`
|
|
5955
|
+
);
|
|
5956
|
+
}
|
|
5224
5957
|
if (unresolved.length) {
|
|
5225
5958
|
const files = unresolved.map((f) => ` ${f}`).join("\n");
|
|
5959
|
+
const install = depsChanged && pkgConflicted ? ` && ${packageManager} install` : "";
|
|
5226
5960
|
consola.warn(
|
|
5227
5961
|
`
|
|
5228
5962
|
Staged the clean changes. ${unresolved.length} file(s) need conflict resolution (the <<<ours/===/theirs>>> markers):
|
|
5229
5963
|
${files}
|
|
5230
5964
|
|
|
5231
|
-
Resolve them, then: git add ${relDir} && git commit`
|
|
5965
|
+
Resolve them, then: git add ${relDir} && git commit${install}`
|
|
5232
5966
|
);
|
|
5233
5967
|
} else {
|
|
5234
5968
|
consola.success("Staged all changes \u2014 review with `git diff --cached`, then commit.");
|
|
5235
5969
|
}
|
|
5970
|
+
if (staleCli) {
|
|
5971
|
+
consola.info(
|
|
5972
|
+
`Your Catalyst CLI is behind (${staleCli.current} \u2192 ${staleCli.latest}). Update it with \`${packageManager} add -D ${CATALYST_CLI_PACKAGE}@${staleCli.latest}\`.`
|
|
5973
|
+
);
|
|
5974
|
+
}
|
|
5236
5975
|
}
|
|
5237
5976
|
var upgrade = new Command13("upgrade").configureHelp({ showGlobalOptions: true }).aliases([
|
|
5238
5977
|
"up",
|
|
@@ -5273,15 +6012,15 @@ Conflicts are written as standard <<<ours/===/theirs>>> markers \u2014 the upgra
|
|
|
5273
6012
|
never aborts. Versions are git tags on the repository (not npm). Set GITHUB_TOKEN
|
|
5274
6013
|
to raise the GitHub API rate limit.`
|
|
5275
6014
|
).action(async (version2, options) => {
|
|
5276
|
-
const
|
|
5277
|
-
if (!
|
|
6015
|
+
const project = await resolveProject2(process.cwd());
|
|
6016
|
+
if (!project) {
|
|
5278
6017
|
consola.error(
|
|
5279
6018
|
"Run `catalyst upgrade` from inside a Catalyst git repository (flat core/ repo or a project containing core/)."
|
|
5280
6019
|
);
|
|
5281
6020
|
process.exit(1);
|
|
5282
6021
|
}
|
|
5283
|
-
const { gitRoot, catalystRoot, relDir, pkgPath } =
|
|
5284
|
-
const baseRef = await resolveBaseRef(
|
|
6022
|
+
const { gitRoot, catalystRoot, relDir, pkgPath } = project;
|
|
6023
|
+
const baseRef = await resolveBaseRef(project, options.from, options.yes ?? false);
|
|
5285
6024
|
if (!baseRef) process.exit(1);
|
|
5286
6025
|
let basePackage;
|
|
5287
6026
|
let upstreamRef;
|
|
@@ -5316,7 +6055,7 @@ to raise the GitHub API rate limit.`
|
|
|
5316
6055
|
process.exit(1);
|
|
5317
6056
|
}
|
|
5318
6057
|
}
|
|
5319
|
-
if (!
|
|
6058
|
+
if (!project.pkg.catalyst?.ref && !options.from && !options.dryRun) {
|
|
5320
6059
|
const { version: baseVersion } = parseRef(baseRef);
|
|
5321
6060
|
const rawPkg = await readFile2(pkgPath, "utf-8");
|
|
5322
6061
|
const parsedPkg = z17.record(z17.string(), z17.unknown()).parse(JSON.parse(rawPkg));
|
|
@@ -5327,15 +6066,18 @@ to raise the GitHub API rate limit.`
|
|
|
5327
6066
|
consola.success(`Added catalyst.ref \u2192 ${baseRef}`);
|
|
5328
6067
|
}
|
|
5329
6068
|
const token = process.env.GITHUB_TOKEN;
|
|
5330
|
-
const tmpDir = await mkdtemp(
|
|
6069
|
+
const tmpDir = await mkdtemp(join15(tmpdir(), "catalyst-upgrade-"));
|
|
5331
6070
|
try {
|
|
5332
|
-
const baseDir =
|
|
5333
|
-
const theirsDir =
|
|
5334
|
-
const emptyFile =
|
|
6071
|
+
const baseDir = join15(tmpDir, "base");
|
|
6072
|
+
const theirsDir = join15(tmpDir, "theirs");
|
|
6073
|
+
const emptyFile = join15(tmpDir, ".empty");
|
|
5335
6074
|
await writeFile2(emptyFile, "");
|
|
6075
|
+
const staleCliCheck = findStaleCli(project.rawContent).catch(() => null);
|
|
5336
6076
|
const downloadSpinner = yoctoSpinner4().start(`Downloading ${baseRef} and ${upstreamRef}...`);
|
|
6077
|
+
let baseVersions;
|
|
6078
|
+
let targetVersions;
|
|
5337
6079
|
try {
|
|
5338
|
-
await Promise.all([
|
|
6080
|
+
[baseVersions, targetVersions] = await Promise.all([
|
|
5339
6081
|
downloadCore(options.repository, baseRef, baseDir, token),
|
|
5340
6082
|
downloadCore(options.repository, upstreamRef, theirsDir, token)
|
|
5341
6083
|
]);
|
|
@@ -5344,7 +6086,7 @@ to raise the GitHub API rate limit.`
|
|
|
5344
6086
|
downloadSpinner.error("Download failed");
|
|
5345
6087
|
throw err;
|
|
5346
6088
|
}
|
|
5347
|
-
if (!
|
|
6089
|
+
if (!project.pkg.catalyst?.ref && !options.from) {
|
|
5348
6090
|
const similarity = await computeBaseSimilarity(baseDir, catalystRoot);
|
|
5349
6091
|
const pct = Math.round(similarity * 100);
|
|
5350
6092
|
if (similarity < 0.5) {
|
|
@@ -5355,6 +6097,13 @@ to raise the GitHub API rate limit.`
|
|
|
5355
6097
|
}
|
|
5356
6098
|
const resolvedVersion = MOVING_TAGS.has(upstreamTagVersion) ? await readResolvedVersion(theirsDir) : upstreamTagVersion;
|
|
5357
6099
|
const newRef = MOVING_TAGS.has(upstreamTagVersion) ? `${basePackage}@${resolvedVersion}` : upstreamRef;
|
|
6100
|
+
const { workspace: workspaceDeps, bumped } = await normalizeWorkspaceDeps(
|
|
6101
|
+
project.rawContent,
|
|
6102
|
+
baseDir,
|
|
6103
|
+
theirsDir,
|
|
6104
|
+
baseVersions,
|
|
6105
|
+
targetVersions
|
|
6106
|
+
);
|
|
5358
6107
|
if (options.dryRun) {
|
|
5359
6108
|
const diffSpinner = yoctoSpinner4().start("Generating diff...");
|
|
5360
6109
|
const diff = await execa4(
|
|
@@ -5374,6 +6123,7 @@ to raise the GitHub API rate limit.`
|
|
|
5374
6123
|
diffSpinner.success(`Diff ready \u2014 ${fileCount} file(s) affected.`);
|
|
5375
6124
|
consola.log("\nDiff preview (--dry-run, not applied):\n");
|
|
5376
6125
|
consola.log(diff.stdout);
|
|
6126
|
+
printWorkspaceFindings(workspaceDeps);
|
|
5377
6127
|
return;
|
|
5378
6128
|
}
|
|
5379
6129
|
const strategy = await resolveStrategy(options.strategy);
|
|
@@ -5390,13 +6140,13 @@ to raise the GitHub API rate limit.`
|
|
|
5390
6140
|
mergeSpinner.success("Merged cleanly.");
|
|
5391
6141
|
}
|
|
5392
6142
|
const patchedRaw = await readFile2(pkgPath, "utf-8");
|
|
5393
|
-
let
|
|
6143
|
+
let stampedText = null;
|
|
5394
6144
|
try {
|
|
5395
6145
|
const patchedPkg = z17.record(z17.string(), z17.unknown()).parse(JSON.parse(patchedRaw));
|
|
5396
6146
|
patchedPkg.catalyst = { version: resolvedVersion, ref: newRef };
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
6147
|
+
stampedText = `${JSON.stringify(patchedPkg, null, 2)}
|
|
6148
|
+
`;
|
|
6149
|
+
await writeFile2(pkgPath, stampedText);
|
|
5400
6150
|
consola.success(`catalyst.ref updated \u2192 ${newRef}`);
|
|
5401
6151
|
} catch {
|
|
5402
6152
|
const catalystReplacement = JSON.stringify(
|
|
@@ -5421,14 +6171,35 @@ to raise the GitHub API rate limit.`
|
|
|
5421
6171
|
);
|
|
5422
6172
|
}
|
|
5423
6173
|
}
|
|
6174
|
+
printWorkspaceFindings(workspaceDeps);
|
|
6175
|
+
let migratedDeps = 0;
|
|
6176
|
+
if (workspaceDeps.length) {
|
|
6177
|
+
const accepted = stampedText !== null && (options.yes === true || Boolean(process.stdin.isTTY) && await confirm7({
|
|
6178
|
+
message: "Replace them with the published versions?",
|
|
6179
|
+
default: true
|
|
6180
|
+
}).catch(() => false));
|
|
6181
|
+
if (accepted && stampedText !== null) {
|
|
6182
|
+
await writeFile2(pkgPath, migrateWorkspaceDeps(stampedText, workspaceDeps));
|
|
6183
|
+
migratedDeps = workspaceDeps.length;
|
|
6184
|
+
consola.success(
|
|
6185
|
+
`Migrated ${migratedDeps} dependenc${migratedDeps === 1 ? "y" : "ies"} to published versions.`
|
|
6186
|
+
);
|
|
6187
|
+
} else {
|
|
6188
|
+
consola.info(
|
|
6189
|
+
"Keeping the workspace references. Switching abandons any local packages/ copy you've customized; otherwise apply the versions above in package.json and reinstall."
|
|
6190
|
+
);
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
5424
6193
|
try {
|
|
5425
|
-
await applyIndexState(gitRoot, relDir, baseDir, theirsDir, result,
|
|
6194
|
+
await applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stampedText !== null);
|
|
5426
6195
|
} catch (err) {
|
|
5427
6196
|
consola.warn(
|
|
5428
6197
|
`Couldn't auto-stage the changes (${err instanceof Error ? err.message : String(err)}). Your files are merged on disk \u2014 run \`git add ${relDir}\` yourself.`
|
|
5429
6198
|
);
|
|
5430
6199
|
}
|
|
5431
6200
|
const gitVersion = await execa4("git", ["--version"]).then((r) => r.stdout.trim()).catch(() => "unknown");
|
|
6201
|
+
const packageManager = await detectLockfileManager(gitRoot) ?? "pnpm";
|
|
6202
|
+
const staleCli = await staleCliCheck;
|
|
5432
6203
|
await getTelemetry().track("upgrade", {
|
|
5433
6204
|
strategy,
|
|
5434
6205
|
gitVersion,
|
|
@@ -5437,9 +6208,20 @@ to raise the GitHub API rate limit.`
|
|
|
5437
6208
|
added: result.added.length,
|
|
5438
6209
|
deleted: result.deleted.length,
|
|
5439
6210
|
conflicts: result.conflicted.length,
|
|
5440
|
-
hasConflicts: result.conflicted.length > 0
|
|
6211
|
+
hasConflicts: result.conflicted.length > 0,
|
|
6212
|
+
workspaceDepsFound: workspaceDeps.length,
|
|
6213
|
+
workspaceDepsMigrated: migratedDeps,
|
|
6214
|
+
catalystDepsBumped: bumped.length,
|
|
6215
|
+
cliOutdated: staleCli !== null
|
|
6216
|
+
});
|
|
6217
|
+
printSummary({
|
|
6218
|
+
result,
|
|
6219
|
+
relDir,
|
|
6220
|
+
stampedPkg: stampedText !== null,
|
|
6221
|
+
migratedDeps,
|
|
6222
|
+
packageManager,
|
|
6223
|
+
staleCli
|
|
5441
6224
|
});
|
|
5442
|
-
printSummary(result, relDir, stampedPkg);
|
|
5443
6225
|
} finally {
|
|
5444
6226
|
await rm2(tmpDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 });
|
|
5445
6227
|
}
|
|
@@ -5516,7 +6298,7 @@ consola.log(colorize13("cyanBright", `\u25E2 ${package_default.name} v${package_
|
|
|
5516
6298
|
`));
|
|
5517
6299
|
program.name(package_default.name).version(package_default.version).summary("CLI tool for Catalyst development").description(
|
|
5518
6300
|
"CLI tool for Catalyst development.\n\nConfiguration priority: flags > process.env > .bigcommerce/project.json. `build` and `deploy` additionally load env files (--env-path, or an auto-loaded .env.local/.env) for the build.\n\nCATALYST_STORE_HASH falls back to BIGCOMMERCE_STORE_HASH if unset.\n\nRun `catalyst <command> --help` for details on a specific command."
|
|
5519
|
-
).configureHelp({ showGlobalOptions: true }).addCommand(version).addCommand(create2).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(
|
|
6301
|
+
).configureHelp({ showGlobalOptions: true }).addCommand(version).addCommand(create2).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(projects).addCommand(env).addCommand(channels).addCommand(auth).addCommand(upgrade).addCommand(telemetry).addCommand(debug).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
|
|
5520
6302
|
|
|
5521
6303
|
// src/cli/index.ts
|
|
5522
6304
|
var handleFatalError = async (error) => {
|