@bigcommerce/catalyst 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1358 -725
- package/dist/cli.js.map +1 -1
- package/package.json +9 -3
- package/templates/open-next.config.ts +8 -1
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ import { randomBytes, randomUUID } from "crypto";
|
|
|
25
25
|
// package.json
|
|
26
26
|
var package_default = {
|
|
27
27
|
name: "@bigcommerce/catalyst",
|
|
28
|
-
version: "1.
|
|
28
|
+
version: "1.4.1",
|
|
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
|
|
|
@@ -209,7 +215,7 @@ import { Command as Command15 } from "commander";
|
|
|
209
215
|
import { colorize as colorize13 } from "consola/utils";
|
|
210
216
|
|
|
211
217
|
// src/cli/commands/auth.ts
|
|
212
|
-
import { Command } from "commander";
|
|
218
|
+
import { Command, Option as Option2 } from "commander";
|
|
213
219
|
import { z as z5 } from "zod";
|
|
214
220
|
|
|
215
221
|
// src/cli/lib/auth-errors.ts
|
|
@@ -679,6 +685,19 @@ async function fetchStoreProfile2(storeHash, accessToken, apiHost) {
|
|
|
679
685
|
}
|
|
680
686
|
return result.data.data;
|
|
681
687
|
}
|
|
688
|
+
async function checkCredentials(storeHash, accessToken, apiHost) {
|
|
689
|
+
try {
|
|
690
|
+
const { store_name: storeName } = await fetchStoreProfile2(storeHash, accessToken, apiHost);
|
|
691
|
+
return { valid: true, storeName };
|
|
692
|
+
} catch (error) {
|
|
693
|
+
if (error instanceof UnauthorizedError) return { valid: false, reason: "unauthorized" };
|
|
694
|
+
return {
|
|
695
|
+
valid: false,
|
|
696
|
+
reason: "unverifiable",
|
|
697
|
+
message: error instanceof Error ? error.message : String(error)
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
}
|
|
682
701
|
var whoami = new Command("whoami").configureHelp({ showGlobalOptions: true }).description("Verify stored credentials and display store/project info.").addHelpText(
|
|
683
702
|
"after",
|
|
684
703
|
`
|
|
@@ -733,7 +752,7 @@ Example:
|
|
|
733
752
|
}
|
|
734
753
|
});
|
|
735
754
|
var login2 = new Command("login").configureHelp({ showGlobalOptions: true }).description(
|
|
736
|
-
"Authenticate via browser using the OAuth device code flow. Falls back to an interactive store hash + access token prompt if the browser flow is unavailable. If already logged in, displays
|
|
755
|
+
"Authenticate via browser using the OAuth device code flow. Falls back to an interactive store hash + access token prompt if the browser flow is unavailable. Passing --store-hash and --access-token verifies and stores those credentials instead. If already logged in with credentials that still work, displays them and suggests `catalyst auth logout`; if the stored token has expired or been revoked, re-authenticates in place."
|
|
737
756
|
).addHelpText(
|
|
738
757
|
"after",
|
|
739
758
|
`
|
|
@@ -742,19 +761,67 @@ Examples:
|
|
|
742
761
|
$ catalyst auth login
|
|
743
762
|
|
|
744
763
|
# Login with existing credentials (skips interactive flow)
|
|
745
|
-
$ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN
|
|
746
|
-
|
|
764
|
+
$ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN>
|
|
765
|
+
|
|
766
|
+
# Re-authenticate without checking the stored credentials first
|
|
767
|
+
$ catalyst auth login --force`
|
|
768
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
|
|
769
|
+
new Option2(
|
|
770
|
+
"--force",
|
|
771
|
+
"Skip the check on any credentials already stored and re-authenticate regardless."
|
|
772
|
+
)
|
|
773
|
+
).action(async (options, command) => {
|
|
747
774
|
try {
|
|
748
775
|
const config2 = getProjectConfig();
|
|
749
776
|
const apiHost = resolveApiHost(options, config2);
|
|
750
777
|
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
751
778
|
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
779
|
+
const suppliedOnCli = command.getOptionValueSource("storeHash") === "cli" && command.getOptionValueSource("accessToken") === "cli";
|
|
780
|
+
if (suppliedOnCli && storeHash && accessToken) {
|
|
781
|
+
if (!options.force) {
|
|
782
|
+
const status = await checkCredentials(storeHash, accessToken, apiHost);
|
|
783
|
+
if (!status.valid && status.reason === "unauthorized") {
|
|
784
|
+
consola.error(`The credentials provided for store ${storeHash} were rejected.`);
|
|
785
|
+
consola.info(
|
|
786
|
+
"Check the store hash and access token, or run `catalyst auth login` without them to authenticate in the browser."
|
|
787
|
+
);
|
|
788
|
+
process.exit(1);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (!status.valid) {
|
|
792
|
+
consola.warn(`Couldn't verify the credentials provided: ${status.message}`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
config2.set("storeHash", storeHash);
|
|
796
|
+
config2.set("accessToken", accessToken);
|
|
797
|
+
consola.success(`Logged in to store ${storeHash}.`);
|
|
755
798
|
process.exit(0);
|
|
756
799
|
return;
|
|
757
800
|
}
|
|
801
|
+
if (storeHash && accessToken && !options.force) {
|
|
802
|
+
const status = await checkCredentials(storeHash, accessToken, apiHost);
|
|
803
|
+
if (status.valid) {
|
|
804
|
+
consola.info(`Already logged in to ${status.storeName} (${storeHash}).`);
|
|
805
|
+
consola.info(
|
|
806
|
+
"Run `catalyst auth logout` first to re-authenticate, or `catalyst auth login --force` to skip this check."
|
|
807
|
+
);
|
|
808
|
+
process.exit(0);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (status.reason === "unverifiable") {
|
|
812
|
+
consola.warn(
|
|
813
|
+
`Found credentials for store ${storeHash} but couldn't verify them: ${status.message}`
|
|
814
|
+
);
|
|
815
|
+
consola.info(
|
|
816
|
+
"Keeping them. Re-run once the API is reachable, or `catalyst auth login --force` to re-authenticate regardless."
|
|
817
|
+
);
|
|
818
|
+
process.exit(0);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
consola.warn(
|
|
822
|
+
`Stored credentials for store ${storeHash} are no longer valid \u2014 re-authenticating.`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
758
825
|
const credentials = await login(options.loginUrl, apiHost);
|
|
759
826
|
config2.set("storeHash", credentials.storeHash);
|
|
760
827
|
config2.set("accessToken", credentials.accessToken);
|
|
@@ -801,10 +868,10 @@ Example:
|
|
|
801
868
|
var auth = new Command("auth").configureHelp({ showGlobalOptions: true }).description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(login2).addCommand(logout);
|
|
802
869
|
|
|
803
870
|
// src/cli/commands/build.ts
|
|
804
|
-
import { Command as Command2, InvalidArgumentError, Option as
|
|
871
|
+
import { Command as Command2, InvalidArgumentError, Option as Option3 } from "commander";
|
|
805
872
|
import { execa as execa2 } from "execa";
|
|
806
873
|
import { copyFile, cp, rm, writeFile } from "fs/promises";
|
|
807
|
-
import { join as
|
|
874
|
+
import { join as join6 } from "path";
|
|
808
875
|
import { valid as validSemver } from "semver";
|
|
809
876
|
|
|
810
877
|
// src/cli/lib/build-env.ts
|
|
@@ -839,173 +906,625 @@ function loadBuildEnv({ envPath, cwd = process.cwd() }) {
|
|
|
839
906
|
}
|
|
840
907
|
}
|
|
841
908
|
|
|
842
|
-
// src/cli/lib/
|
|
909
|
+
// src/cli/lib/commerce-hosting.ts
|
|
910
|
+
import { confirm as confirm2, input as input2, select, Separator } from "@inquirer/prompts";
|
|
911
|
+
import { colorize as colorize3 } from "consola/utils";
|
|
912
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
843
913
|
import { dirname, join as join2 } from "path";
|
|
844
|
-
import {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
914
|
+
import { minVersion, satisfies, lt as semverLt, validRange } from "semver";
|
|
915
|
+
import { z as z6 } from "zod";
|
|
916
|
+
|
|
917
|
+
// src/cli/lib/sort-package-json.ts
|
|
918
|
+
var FIELD_ORDER = [
|
|
919
|
+
"name",
|
|
920
|
+
"description",
|
|
921
|
+
"version",
|
|
922
|
+
"private",
|
|
923
|
+
"engines",
|
|
924
|
+
"scripts",
|
|
925
|
+
"dependencies",
|
|
926
|
+
"devDependencies"
|
|
927
|
+
];
|
|
928
|
+
function sortPackageJsonFields(pkg) {
|
|
929
|
+
const ordered = {};
|
|
930
|
+
FIELD_ORDER.forEach((field) => {
|
|
931
|
+
if (field in pkg) {
|
|
932
|
+
ordered[field] = pkg[field];
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
Object.keys(pkg).forEach((key) => {
|
|
936
|
+
if (!(key in ordered)) {
|
|
937
|
+
ordered[key] = pkg[key];
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
return ordered;
|
|
849
941
|
}
|
|
850
942
|
|
|
851
|
-
// src/cli/lib/
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
var projectJsonSchema = z6.looseObject({
|
|
856
|
-
projectUuid: z6.string().optional()
|
|
857
|
-
});
|
|
858
|
-
var packageJsonSchema = z6.looseObject({
|
|
943
|
+
// src/cli/lib/commerce-hosting.ts
|
|
944
|
+
var OPENNEXT_CLOUDFLARE_VERSION = "1.20.6";
|
|
945
|
+
var OPENNEXT_REQUIRED_NEXT_RANGE = ">=15.5.24 <16 || >=16.3.3";
|
|
946
|
+
var corePackageJsonSchema = z6.looseObject({
|
|
859
947
|
dependencies: z6.record(z6.string(), z6.string()).optional()
|
|
860
948
|
});
|
|
861
|
-
var
|
|
949
|
+
var projectJsonSchema = z6.looseObject({});
|
|
950
|
+
var readProjectJson = (path) => {
|
|
951
|
+
if (!existsSync2(path)) return {};
|
|
862
952
|
try {
|
|
863
|
-
|
|
953
|
+
const parsed = projectJsonSchema.safeParse(JSON.parse(readFileSync(path, "utf-8")));
|
|
954
|
+
return parsed.success ? parsed.data : {};
|
|
864
955
|
} catch {
|
|
865
|
-
return
|
|
956
|
+
return {};
|
|
866
957
|
}
|
|
867
958
|
};
|
|
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
|
-
});
|
|
959
|
+
var writeJson = (path, value) => {
|
|
960
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
961
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
962
|
+
`);
|
|
903
963
|
};
|
|
904
|
-
var
|
|
905
|
-
const
|
|
906
|
-
|
|
964
|
+
var convertProxyToMiddleware = (projectDir) => {
|
|
965
|
+
const proxyPath = join2(projectDir, "proxy.ts");
|
|
966
|
+
const middlewarePath = join2(projectDir, "middleware.ts");
|
|
967
|
+
if (!existsSync2(proxyPath)) return;
|
|
968
|
+
const contents = readFileSync(proxyPath, "utf-8").replace("export const proxy", "export const middleware").replace("export const config = {", "export const config = {\n runtime: 'experimental-edge',");
|
|
969
|
+
writeFileSync(middlewarePath, contents);
|
|
970
|
+
unlinkSync(proxyPath);
|
|
971
|
+
};
|
|
972
|
+
var cleanupCloudflareIncompatibilities = async (projectDir) => {
|
|
973
|
+
const instrumentationPath = join2(projectDir, "instrumentation.ts");
|
|
974
|
+
if (!existsSync2(instrumentationPath)) return;
|
|
975
|
+
if (!process.stdin.isTTY) {
|
|
976
|
+
consola.warn(
|
|
977
|
+
"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."
|
|
978
|
+
);
|
|
907
979
|
return;
|
|
908
980
|
}
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
981
|
+
const shouldRemove = await confirm2({
|
|
982
|
+
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).\nRemove it and drop @vercel/otel from package.json? (required for native hosting)',
|
|
983
|
+
default: true
|
|
984
|
+
});
|
|
985
|
+
if (!shouldRemove) {
|
|
986
|
+
consola.info(
|
|
987
|
+
'Leaving instrumentation.ts in place. The Cloudflare worker will continue to log "Failed to prepare server" at cold start until this is resolved manually.'
|
|
988
|
+
);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
unlinkSync(instrumentationPath);
|
|
992
|
+
consola.info("Removed instrumentation.ts (incompatible with Cloudflare Workers).");
|
|
993
|
+
const corePackageJsonPath = join2(projectDir, "package.json");
|
|
994
|
+
if (existsSync2(corePackageJsonPath)) {
|
|
995
|
+
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync(corePackageJsonPath, "utf-8")));
|
|
996
|
+
if (pkg.dependencies && "@vercel/otel" in pkg.dependencies) {
|
|
997
|
+
const { "@vercel/otel": _removedVercelOtel, ...preservedDeps } = pkg.dependencies;
|
|
998
|
+
pkg.dependencies = preservedDeps;
|
|
999
|
+
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1000
|
+
consola.info("Dropped @vercel/otel from package.json.");
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
917
1003
|
};
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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).`
|
|
1004
|
+
var reconcileOpenNextVersion = async (projectDir, packageManager, { canUpgrade }) => {
|
|
1005
|
+
const corePackageJsonPath = join2(projectDir, "package.json");
|
|
1006
|
+
if (!existsSync2(corePackageJsonPath)) return false;
|
|
1007
|
+
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync(corePackageJsonPath, "utf-8")));
|
|
1008
|
+
const range = pkg.dependencies?.["@opennextjs/cloudflare"];
|
|
1009
|
+
if (range === void 0) return false;
|
|
1010
|
+
if (!validRange(range)) return false;
|
|
1011
|
+
if (satisfies(OPENNEXT_CLOUDFLARE_VERSION, range)) return false;
|
|
1012
|
+
const current = minVersion(range)?.version;
|
|
1013
|
+
if (current === void 0 || !semverLt(current, OPENNEXT_CLOUDFLARE_VERSION)) return false;
|
|
1014
|
+
const manualStep = `\`${packageManager} add @opennextjs/cloudflare@${OPENNEXT_CLOUDFLARE_VERSION}\``;
|
|
1015
|
+
const behind = `This project pins @opennextjs/cloudflare ${current}, but this CLI targets ${OPENNEXT_CLOUDFLARE_VERSION}.`;
|
|
1016
|
+
const nextRange = pkg.dependencies?.next;
|
|
1017
|
+
const nextVersion = nextRange !== void 0 && validRange(nextRange) ? minVersion(nextRange) : null;
|
|
1018
|
+
if (nextVersion !== null && !satisfies(nextVersion.version, OPENNEXT_REQUIRED_NEXT_RANGE)) {
|
|
1019
|
+
consola.warn(
|
|
1020
|
+
`${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.`
|
|
989
1021
|
);
|
|
1022
|
+
return false;
|
|
990
1023
|
}
|
|
991
|
-
|
|
1024
|
+
if (!canUpgrade) {
|
|
1025
|
+
consola.warn(
|
|
1026
|
+
`${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.`
|
|
1027
|
+
);
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
if (!process.stdin.isTTY) {
|
|
1031
|
+
consola.warn(
|
|
1032
|
+
`${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.`
|
|
1033
|
+
);
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
const shouldUpgrade = await confirm2({
|
|
1037
|
+
message: `${behind} Adapter fixes released since then are not applied. Update it to ${OPENNEXT_CLOUDFLARE_VERSION} and reinstall now?`,
|
|
1038
|
+
default: true
|
|
1039
|
+
});
|
|
1040
|
+
if (!shouldUpgrade) {
|
|
1041
|
+
consola.info(`Keeping @opennextjs/cloudflare ${current}. Update it later with ${manualStep}.`);
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
pkg.dependencies = {
|
|
1045
|
+
...pkg.dependencies,
|
|
1046
|
+
"@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
|
|
1047
|
+
};
|
|
1048
|
+
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1049
|
+
consola.info(`Updated @opennextjs/cloudflare to ${OPENNEXT_CLOUDFLARE_VERSION}.`);
|
|
1050
|
+
return true;
|
|
992
1051
|
};
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
await
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1052
|
+
var setupCommerceHosting = async ({
|
|
1053
|
+
projectDir,
|
|
1054
|
+
projectUuid,
|
|
1055
|
+
storeHash,
|
|
1056
|
+
accessToken
|
|
1057
|
+
}) => {
|
|
1058
|
+
await cleanupCloudflareIncompatibilities(projectDir);
|
|
1059
|
+
const corePackageJsonPath = join2(projectDir, "package.json");
|
|
1060
|
+
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync(corePackageJsonPath, "utf-8")));
|
|
1061
|
+
pkg.dependencies = {
|
|
1062
|
+
...pkg.dependencies,
|
|
1063
|
+
"@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
|
|
1064
|
+
};
|
|
1065
|
+
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1066
|
+
const projectJsonPath = join2(projectDir, ".bigcommerce", "project.json");
|
|
1067
|
+
const projectJson = {
|
|
1068
|
+
...readProjectJson(projectJsonPath),
|
|
1069
|
+
projectUuid,
|
|
1070
|
+
framework: "catalyst"
|
|
1071
|
+
};
|
|
1072
|
+
if (storeHash) projectJson.storeHash = storeHash;
|
|
1073
|
+
if (accessToken) projectJson.accessToken = accessToken;
|
|
1074
|
+
writeJson(projectJsonPath, projectJson);
|
|
1075
|
+
convertProxyToMiddleware(projectDir);
|
|
1076
|
+
};
|
|
1077
|
+
var NoLinkedProjectError = class extends Error {
|
|
1078
|
+
constructor() {
|
|
1079
|
+
super("No infrastructure project linked: user declined to create one.");
|
|
1080
|
+
this.name = "NoLinkedProjectError";
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
async function promptForNewProjectName(api) {
|
|
1084
|
+
const newProjectName = await input2({ message: "Enter a name for the new project:" });
|
|
1085
|
+
const data = await createProject(newProjectName, api.storeHash, api.accessToken, api.apiHost);
|
|
1086
|
+
consola.success(`Project "${data.name}" created successfully.`);
|
|
1087
|
+
return { uuid: data.uuid, name: data.name, deployment_hostnames: [] };
|
|
1088
|
+
}
|
|
1089
|
+
async function selectOrCreateInfrastructureProject(api, linkedProjectUuid) {
|
|
1090
|
+
consola.start("Fetching projects...");
|
|
1091
|
+
const existingProjects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
|
|
1092
|
+
consola.success("Projects fetched.");
|
|
1093
|
+
if (existingProjects.length === 0) {
|
|
1094
|
+
const shouldCreate = await confirm2({
|
|
1095
|
+
message: "There are not any hosting projects that you can link to yet. Would you like to create one?",
|
|
1096
|
+
default: true
|
|
1097
|
+
});
|
|
1098
|
+
if (!shouldCreate) {
|
|
1099
|
+
throw new NoLinkedProjectError();
|
|
1100
|
+
}
|
|
1101
|
+
return promptForNewProjectName(api);
|
|
1102
|
+
}
|
|
1103
|
+
const choices = [
|
|
1104
|
+
...existingProjects.map((p) => ({
|
|
1105
|
+
name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize3("green", "[linked]")}` : p.name,
|
|
1106
|
+
value: p.uuid,
|
|
1107
|
+
description: p.uuid
|
|
1108
|
+
})),
|
|
1109
|
+
{
|
|
1110
|
+
name: "Create a new project",
|
|
1111
|
+
value: "create",
|
|
1112
|
+
description: "Create a new hosting project for this Catalyst storefront."
|
|
1113
|
+
}
|
|
1114
|
+
];
|
|
1115
|
+
const selected = await select({
|
|
1116
|
+
message: "Select a project or create a new project (Press <enter> to select).",
|
|
1117
|
+
choices
|
|
1118
|
+
});
|
|
1119
|
+
if (selected === "create") {
|
|
1120
|
+
return promptForNewProjectName(api);
|
|
1121
|
+
}
|
|
1122
|
+
const matched = existingProjects.find((p) => p.uuid === selected);
|
|
1123
|
+
if (!matched) {
|
|
1124
|
+
throw new Error(`Selected project ${String(selected)} not found in fetched list.`);
|
|
1125
|
+
}
|
|
1126
|
+
return matched;
|
|
1127
|
+
}
|
|
1128
|
+
async function promptForCommerceHostingProject(api, defaultName, autoUseDefaultName, useExistingOnCollision) {
|
|
1129
|
+
const existingProjects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
|
|
1130
|
+
const takenNames = existingProjects.map((project) => project.name);
|
|
1131
|
+
if (autoUseDefaultName) {
|
|
1132
|
+
return autoCreateCommerceHostingProject(
|
|
1133
|
+
api,
|
|
1134
|
+
defaultName,
|
|
1135
|
+
existingProjects,
|
|
1136
|
+
useExistingOnCollision
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
const conflict = existingProjects.find(
|
|
1140
|
+
(project) => project.name.toLowerCase() === defaultName.toLowerCase()
|
|
1141
|
+
);
|
|
1142
|
+
if (!conflict) {
|
|
1143
|
+
return autoCreateCommerceHostingProject(
|
|
1144
|
+
api,
|
|
1145
|
+
defaultName,
|
|
1146
|
+
existingProjects,
|
|
1147
|
+
useExistingOnCollision
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
const hasOtherProjects = existingProjects.length > 1;
|
|
1151
|
+
const choices = [
|
|
1152
|
+
{ name: `Use "${conflict.name}"`, value: "use-named" }
|
|
1153
|
+
];
|
|
1154
|
+
if (hasOtherProjects) {
|
|
1155
|
+
choices.push({ name: "Select from my projects", value: "select-from-list" });
|
|
1156
|
+
}
|
|
1157
|
+
choices.push({ name: "Create a new project", value: "create" });
|
|
1158
|
+
const action = await select({
|
|
1159
|
+
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?`,
|
|
1160
|
+
choices
|
|
1161
|
+
});
|
|
1162
|
+
if (action === "use-named") {
|
|
1163
|
+
consola.success(`Using existing Commerce Hosting project "${conflict.name}"`);
|
|
1164
|
+
return conflict;
|
|
1165
|
+
}
|
|
1166
|
+
if (action === "create") {
|
|
1167
|
+
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1168
|
+
}
|
|
1169
|
+
const selected = await select({
|
|
1170
|
+
message: "Which Commerce Hosting project would you like to use?",
|
|
1171
|
+
choices: [
|
|
1172
|
+
...existingProjects.map((project) => ({
|
|
1173
|
+
name: project.name,
|
|
1174
|
+
value: project,
|
|
1175
|
+
description: project.uuid
|
|
1176
|
+
})),
|
|
1177
|
+
new Separator(),
|
|
1178
|
+
{ name: "Create a new project", value: "create-new" }
|
|
1179
|
+
]
|
|
1180
|
+
});
|
|
1181
|
+
if (selected === "create-new") {
|
|
1182
|
+
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1183
|
+
}
|
|
1184
|
+
consola.success(`Using existing Commerce Hosting project "${selected.name}"`);
|
|
1185
|
+
return selected;
|
|
1186
|
+
}
|
|
1187
|
+
async function promptAndCreateCommerceHostingProject(api, takenNames, defaultName) {
|
|
1188
|
+
const projectName = await input2({
|
|
1189
|
+
message: "What would you like to name your Commerce Hosting project?",
|
|
1190
|
+
default: defaultName,
|
|
1191
|
+
validate: (value) => {
|
|
1192
|
+
const trimmed = value.trim();
|
|
1193
|
+
if (!trimmed) return "Project name is required";
|
|
1194
|
+
const conflict = takenNames.find((taken) => taken.toLowerCase() === trimmed.toLowerCase());
|
|
1195
|
+
if (conflict) {
|
|
1196
|
+
return `A Commerce Hosting project named "${conflict}" already exists`;
|
|
1197
|
+
}
|
|
1198
|
+
return true;
|
|
1199
|
+
},
|
|
1200
|
+
theme: {
|
|
1201
|
+
style: {
|
|
1202
|
+
help: () => colorize3(
|
|
1203
|
+
"dim",
|
|
1204
|
+
"(The project that hosts your storefront on Commerce \u2014 often matches your folder name.)"
|
|
1205
|
+
)
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
try {
|
|
1210
|
+
const created = await createProject(
|
|
1211
|
+
projectName.trim(),
|
|
1212
|
+
api.storeHash,
|
|
1213
|
+
api.accessToken,
|
|
1214
|
+
api.apiHost
|
|
1215
|
+
);
|
|
1216
|
+
consola.success(`Commerce Hosting project "${created.name}" created successfully`);
|
|
1217
|
+
return { uuid: created.uuid, name: created.name, deployment_hostnames: [] };
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
if (error instanceof InfrastructureProjectValidationError) {
|
|
1220
|
+
consola.error(error.message);
|
|
1221
|
+
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1222
|
+
}
|
|
1223
|
+
throw error;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
async function resolveCollisionChoice(existingName, useExistingOnCollision) {
|
|
1227
|
+
if (useExistingOnCollision === true) return true;
|
|
1228
|
+
if (!process.stdin.isTTY) return false;
|
|
1229
|
+
return select({
|
|
1230
|
+
message: `A Commerce Hosting project named "${existingName}" already exists. Use the existing project?`,
|
|
1231
|
+
choices: [
|
|
1232
|
+
{ name: "Yes", value: true },
|
|
1233
|
+
{ name: "No", value: false }
|
|
1234
|
+
]
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
async function autoCreateCommerceHostingProject(api, name, existingProjects, useExistingOnCollision) {
|
|
1238
|
+
const existing = existingProjects.find(
|
|
1239
|
+
(project) => project.name.toLowerCase() === name.toLowerCase()
|
|
1240
|
+
);
|
|
1241
|
+
if (existing) {
|
|
1242
|
+
const shouldUseExisting = await resolveCollisionChoice(existing.name, useExistingOnCollision);
|
|
1243
|
+
if (shouldUseExisting) {
|
|
1244
|
+
consola.success(`Using existing Commerce Hosting project "${existing.name}"`);
|
|
1245
|
+
return existing;
|
|
1246
|
+
}
|
|
1247
|
+
consola.error(
|
|
1248
|
+
"Not reusing the existing project. Re-run with a different --project-name, or pass --use-existing to reuse it."
|
|
1249
|
+
);
|
|
1250
|
+
process.exit(1);
|
|
1251
|
+
}
|
|
1252
|
+
try {
|
|
1253
|
+
const created = await createProject(name, api.storeHash, api.accessToken, api.apiHost);
|
|
1254
|
+
consola.success(`Commerce Hosting project "${created.name}" created successfully`);
|
|
1255
|
+
return { uuid: created.uuid, name: created.name, deployment_hostnames: [] };
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
if (error instanceof InfrastructureProjectValidationError) {
|
|
1258
|
+
consola.error(
|
|
1259
|
+
`Failed to create Commerce Hosting project "${name}": ${error.message}
|
|
1260
|
+
Re-run with a different --project-name.`
|
|
1261
|
+
);
|
|
1262
|
+
process.exit(1);
|
|
1263
|
+
}
|
|
1264
|
+
throw error;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// src/cli/lib/detect-package-manager.ts
|
|
1269
|
+
import { access } from "fs/promises";
|
|
1270
|
+
import { join as join3 } from "path";
|
|
1271
|
+
import { detectPackageManager as detectFromDir } from "nypm";
|
|
1272
|
+
var PROJECT_LOCKFILES = [
|
|
1273
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
1274
|
+
["yarn.lock", "yarn"],
|
|
1275
|
+
["bun.lock", "bun"],
|
|
1276
|
+
["bun.lockb", "bun"],
|
|
1277
|
+
["package-lock.json", "npm"]
|
|
1278
|
+
];
|
|
1279
|
+
var detectLockfileManager = async (dir) => {
|
|
1280
|
+
const found = await Promise.all(
|
|
1281
|
+
PROJECT_LOCKFILES.map(
|
|
1282
|
+
async ([file, manager]) => access(join3(dir, file)).then(() => manager).catch(() => null)
|
|
1283
|
+
)
|
|
1284
|
+
);
|
|
1285
|
+
return found.find((manager) => manager !== null) ?? null;
|
|
1286
|
+
};
|
|
1287
|
+
var detectPackageManager = () => {
|
|
1288
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
1289
|
+
const name = userAgent.split(" ")[0]?.split("/")[0];
|
|
1290
|
+
if (name === "pnpm" || name === "yarn" || name === "bun") {
|
|
1291
|
+
return name;
|
|
1292
|
+
}
|
|
1293
|
+
if (process.versions.bun) {
|
|
1294
|
+
return "bun";
|
|
1295
|
+
}
|
|
1296
|
+
return "npm";
|
|
1297
|
+
};
|
|
1298
|
+
var detectProjectPackageManager = async (projectDir) => {
|
|
1299
|
+
const detected = await detectFromDir(projectDir);
|
|
1300
|
+
const name = detected?.name;
|
|
1301
|
+
if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") {
|
|
1302
|
+
return name;
|
|
1303
|
+
}
|
|
1304
|
+
return "npm";
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
// src/cli/lib/get-module-cli-path.ts
|
|
1308
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
1309
|
+
import { fileURLToPath } from "url";
|
|
1310
|
+
function getModuleCliPath() {
|
|
1311
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1312
|
+
const __dirname = dirname2(__filename);
|
|
1313
|
+
return join4(__dirname, "..");
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// src/cli/lib/install-dependencies.ts
|
|
1317
|
+
import { installDependencies as installDeps } from "nypm";
|
|
1318
|
+
import yoctoSpinner2 from "yocto-spinner";
|
|
1319
|
+
var restoreEnv = (key, previous) => {
|
|
1320
|
+
if (previous === void 0) {
|
|
1321
|
+
Reflect.deleteProperty(process.env, key);
|
|
1322
|
+
} else {
|
|
1323
|
+
process.env[key] = previous;
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
var withQuietInstallEnv = async (run2) => {
|
|
1327
|
+
var _a, _b;
|
|
1328
|
+
const prevNodeNoWarnings = process.env.NODE_NO_WARNINGS;
|
|
1329
|
+
const prevDownloadPrompt = process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT;
|
|
1330
|
+
(_a = process.env).NODE_NO_WARNINGS ?? (_a.NODE_NO_WARNINGS = "1");
|
|
1331
|
+
(_b = process.env).COREPACK_ENABLE_DOWNLOAD_PROMPT ?? (_b.COREPACK_ENABLE_DOWNLOAD_PROMPT = "0");
|
|
1332
|
+
try {
|
|
1333
|
+
return await run2();
|
|
1334
|
+
} finally {
|
|
1335
|
+
restoreEnv("NODE_NO_WARNINGS", prevNodeNoWarnings);
|
|
1336
|
+
restoreEnv("COREPACK_ENABLE_DOWNLOAD_PROMPT", prevDownloadPrompt);
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
var installDependencies = async (projectDir, packageManager = "pnpm") => {
|
|
1340
|
+
const spinner = yoctoSpinner2().start(
|
|
1341
|
+
`Installing dependencies with ${packageManager}. This could take a minute...`
|
|
1342
|
+
);
|
|
1343
|
+
try {
|
|
1344
|
+
await withQuietInstallEnv(() => installDeps({ cwd: projectDir, silent: true, packageManager }));
|
|
1345
|
+
spinner.success("Dependencies installed successfully.");
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
1348
|
+
spinner.error(`Failed to install dependencies: ${message}`);
|
|
1349
|
+
throw error;
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
|
|
1353
|
+
// src/cli/lib/project-state.ts
|
|
1354
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1355
|
+
import { join as join5 } from "path";
|
|
1356
|
+
import { z as z7 } from "zod";
|
|
1357
|
+
var projectJsonSchema2 = z7.looseObject({
|
|
1358
|
+
projectUuid: z7.string().optional()
|
|
1359
|
+
});
|
|
1360
|
+
var packageJsonSchema = z7.looseObject({
|
|
1361
|
+
dependencies: z7.record(z7.string(), z7.string()).optional()
|
|
1362
|
+
});
|
|
1363
|
+
var safeReadJson = (path) => {
|
|
1364
|
+
try {
|
|
1365
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
1366
|
+
} catch {
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
function getProjectState(cwd = process.cwd()) {
|
|
1371
|
+
const projectJson = projectJsonSchema2.safeParse(
|
|
1372
|
+
safeReadJson(join5(cwd, ".bigcommerce", "project.json"))
|
|
1373
|
+
);
|
|
1374
|
+
const projectUuid = projectJson.success ? projectJson.data.projectUuid : void 0;
|
|
1375
|
+
const hasMiddleware = existsSync3(join5(cwd, "middleware.ts"));
|
|
1376
|
+
const hasProxy = existsSync3(join5(cwd, "proxy.ts"));
|
|
1377
|
+
const pkgJson = packageJsonSchema.safeParse(safeReadJson(join5(cwd, "package.json")));
|
|
1378
|
+
const hasOpenNextDep = pkgJson.success ? Boolean(pkgJson.data.dependencies?.["@opennextjs/cloudflare"]) : false;
|
|
1379
|
+
const isLinked = Boolean(projectUuid);
|
|
1380
|
+
const isTransformed = hasMiddleware && !hasProxy && hasOpenNextDep;
|
|
1381
|
+
const isFullySetUp = isLinked && isTransformed;
|
|
1382
|
+
return {
|
|
1383
|
+
projectUuid,
|
|
1384
|
+
hasMiddleware,
|
|
1385
|
+
hasProxy,
|
|
1386
|
+
hasOpenNextDep,
|
|
1387
|
+
isLinked,
|
|
1388
|
+
isTransformed,
|
|
1389
|
+
isFullySetUp
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// src/cli/lib/required-build-env.ts
|
|
1394
|
+
var REQUIRED_BUILD_ENV_VARS = [
|
|
1395
|
+
"BIGCOMMERCE_STORE_HASH",
|
|
1396
|
+
"BIGCOMMERCE_STOREFRONT_TOKEN",
|
|
1397
|
+
"BIGCOMMERCE_CHANNEL_ID",
|
|
1398
|
+
"AUTH_SECRET"
|
|
1399
|
+
];
|
|
1400
|
+
var findMissingBuildEnv = (env2 = process.env) => {
|
|
1401
|
+
return REQUIRED_BUILD_ENV_VARS.filter((name) => {
|
|
1402
|
+
const value = env2[name];
|
|
1403
|
+
return value === void 0 || value.trim() === "";
|
|
1404
|
+
});
|
|
1405
|
+
};
|
|
1406
|
+
var assertRequiredBuildEnv = (env2 = process.env) => {
|
|
1407
|
+
const missing = findMissingBuildEnv(env2);
|
|
1408
|
+
if (missing.length === 0) {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
const missingList = missing.map((name) => ` - ${name}`).join("\n");
|
|
1412
|
+
throw new UserActionableError(
|
|
1413
|
+
`Missing required environment variable${missing.length === 1 ? "" : "s"} for the build:
|
|
1414
|
+
${missingList}
|
|
1415
|
+
|
|
1416
|
+
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).
|
|
1417
|
+
To load an env file from a different location, pass \`--env-path <path>\` (for example \`--env-path ../.env.local\` when running from a subdirectory).`
|
|
1418
|
+
);
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1421
|
+
// src/cli/lib/wrangler-config.ts
|
|
1422
|
+
function getCompatibilityDate(now = /* @__PURE__ */ new Date()) {
|
|
1423
|
+
const date = new Date(now);
|
|
1424
|
+
date.setUTCMonth(date.getUTCMonth() - 1);
|
|
1425
|
+
return date.toISOString().slice(0, 10);
|
|
1426
|
+
}
|
|
1427
|
+
var LOCAL_ROUTES_KV_NAMESPACE_ID = "catalyst-routes-kv-local";
|
|
1428
|
+
function getWranglerConfig(projectUuid) {
|
|
1429
|
+
return {
|
|
1430
|
+
$schema: "node_modules/wrangler/config-schema.json",
|
|
1431
|
+
main: "../.open-next/worker.js",
|
|
1432
|
+
name: `project-${projectUuid}`,
|
|
1433
|
+
compatibility_date: getCompatibilityDate(),
|
|
1434
|
+
compatibility_flags: ["nodejs_compat", "global_fetch_strictly_public"],
|
|
1435
|
+
observability: {
|
|
1436
|
+
enabled: true,
|
|
1437
|
+
head_sampling_rate: 0.05,
|
|
1438
|
+
logs: {
|
|
1439
|
+
enabled: true,
|
|
1440
|
+
head_sampling_rate: 1,
|
|
1441
|
+
invocation_logs: false
|
|
1442
|
+
}
|
|
1443
|
+
},
|
|
1444
|
+
assets: {
|
|
1445
|
+
directory: "../.open-next/assets",
|
|
1446
|
+
binding: "ASSETS"
|
|
1447
|
+
},
|
|
1448
|
+
services: [
|
|
1449
|
+
{
|
|
1450
|
+
binding: "WORKER_SELF_REFERENCE",
|
|
1451
|
+
service: `project-${projectUuid}`
|
|
1452
|
+
}
|
|
1453
|
+
],
|
|
1454
|
+
r2_buckets: [
|
|
1455
|
+
{
|
|
1456
|
+
binding: "NEXT_INC_CACHE_R2_BUCKET",
|
|
1457
|
+
bucket_name: `project-${projectUuid}`
|
|
1458
|
+
}
|
|
1459
|
+
],
|
|
1460
|
+
kv_namespaces: [
|
|
1461
|
+
{
|
|
1462
|
+
binding: "CATALYST_ROUTES_KV",
|
|
1463
|
+
id: LOCAL_ROUTES_KV_NAMESPACE_ID
|
|
1464
|
+
}
|
|
1465
|
+
],
|
|
1466
|
+
durable_objects: {
|
|
1467
|
+
bindings: [
|
|
1468
|
+
{
|
|
1469
|
+
name: "NEXT_CACHE_DO_QUEUE",
|
|
1470
|
+
class_name: "DOQueueHandler"
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "NEXT_TAG_CACHE_DO_SHARDED",
|
|
1474
|
+
class_name: "DOShardedTagCache"
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
name: "NEXT_CACHE_DO_PURGE",
|
|
1478
|
+
class_name: "BucketCachePurge"
|
|
1479
|
+
}
|
|
1480
|
+
]
|
|
1481
|
+
},
|
|
1482
|
+
migrations: [
|
|
1483
|
+
{
|
|
1484
|
+
tag: "v1",
|
|
1485
|
+
new_sqlite_classes: ["DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"]
|
|
1486
|
+
}
|
|
1487
|
+
]
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// src/cli/commands/build.ts
|
|
1492
|
+
var WRANGLER_VERSION = "4.128.0";
|
|
1493
|
+
var DIST_TAG_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
1494
|
+
var parseWranglerVersion = (value) => {
|
|
1495
|
+
if (!validSemver(value) && !DIST_TAG_PATTERN.test(value)) {
|
|
1496
|
+
throw new InvalidArgumentError(
|
|
1497
|
+
`"${value}" is not a valid Wrangler version or dist-tag (e.g. 4.90.0 or latest).`
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
return value;
|
|
1501
|
+
};
|
|
1502
|
+
async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERSION) {
|
|
1503
|
+
assertRequiredBuildEnv();
|
|
1504
|
+
const coreDir = process.cwd();
|
|
1505
|
+
const projectPackageManager = await detectProjectPackageManager(coreDir);
|
|
1506
|
+
if (await reconcileOpenNextVersion(coreDir, projectPackageManager, { canUpgrade: true })) {
|
|
1507
|
+
await installDependencies(coreDir, projectPackageManager);
|
|
1508
|
+
}
|
|
1509
|
+
const openNextOutDir = join6(coreDir, ".open-next");
|
|
1510
|
+
const bigcommerceDistDir = join6(coreDir, ".bigcommerce", "dist");
|
|
1511
|
+
consola.start("Generating GraphQL types...");
|
|
1512
|
+
await execa2("node", [join6(coreDir, "scripts", "generate.cjs")], {
|
|
1513
|
+
stdio: "inherit",
|
|
1514
|
+
cwd: coreDir
|
|
1515
|
+
});
|
|
1516
|
+
consola.success("GraphQL types generated");
|
|
1517
|
+
const wranglerConfig = getWranglerConfig(projectUuid);
|
|
1518
|
+
await rm(bigcommerceDistDir, { recursive: true, force: true });
|
|
1519
|
+
consola.start("Copying templates...");
|
|
1520
|
+
await copyFile(
|
|
1521
|
+
join6(getModuleCliPath(), "templates", "open-next.config.ts"),
|
|
1522
|
+
join6(coreDir, ".bigcommerce", "open-next.config.ts")
|
|
1523
|
+
);
|
|
1524
|
+
await writeFile(
|
|
1525
|
+
join6(coreDir, ".bigcommerce", "wrangler.jsonc"),
|
|
1526
|
+
JSON.stringify(wranglerConfig, null, 2)
|
|
1527
|
+
);
|
|
1009
1528
|
consola.success("Templates copied");
|
|
1010
1529
|
consola.start("Building project...");
|
|
1011
1530
|
await execa2(
|
|
@@ -1016,7 +1535,7 @@ async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERS
|
|
|
1016
1535
|
"build",
|
|
1017
1536
|
"--skipWranglerConfigCheck",
|
|
1018
1537
|
"--openNextConfigPath",
|
|
1019
|
-
|
|
1538
|
+
join6(coreDir, ".bigcommerce", "open-next.config.ts")
|
|
1020
1539
|
],
|
|
1021
1540
|
{
|
|
1022
1541
|
stdout: ["pipe", "inherit"],
|
|
@@ -1024,8 +1543,8 @@ async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERS
|
|
|
1024
1543
|
}
|
|
1025
1544
|
);
|
|
1026
1545
|
await copyFile(
|
|
1027
|
-
|
|
1028
|
-
|
|
1546
|
+
join6(getModuleCliPath(), "templates", "public_headers"),
|
|
1547
|
+
join6(openNextOutDir, "assets", "_headers")
|
|
1029
1548
|
);
|
|
1030
1549
|
await execa2(
|
|
1031
1550
|
"pnpm",
|
|
@@ -1034,7 +1553,7 @@ async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERS
|
|
|
1034
1553
|
`wrangler@${wranglerVersion}`,
|
|
1035
1554
|
"deploy",
|
|
1036
1555
|
"--config",
|
|
1037
|
-
|
|
1556
|
+
join6(coreDir, ".bigcommerce", "wrangler.jsonc"),
|
|
1038
1557
|
"--keep-vars",
|
|
1039
1558
|
"--outdir",
|
|
1040
1559
|
bigcommerceDistDir,
|
|
@@ -1046,7 +1565,7 @@ async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERS
|
|
|
1046
1565
|
}
|
|
1047
1566
|
);
|
|
1048
1567
|
consola.success("Project built");
|
|
1049
|
-
await cp(
|
|
1568
|
+
await cp(join6(openNextOutDir, "assets"), join6(bigcommerceDistDir, "assets"), {
|
|
1050
1569
|
recursive: true,
|
|
1051
1570
|
force: true
|
|
1052
1571
|
});
|
|
@@ -1065,12 +1584,12 @@ Examples:
|
|
|
1065
1584
|
# Build with a specific Wrangler version
|
|
1066
1585
|
$ catalyst build --wrangler-version 4.24.3`
|
|
1067
1586
|
).addOption(
|
|
1068
|
-
new
|
|
1587
|
+
new Option3(
|
|
1069
1588
|
"--project-uuid <uuid>",
|
|
1070
1589
|
"Project UUID to be included in the deployment configuration."
|
|
1071
1590
|
).env("CATALYST_PROJECT_UUID")
|
|
1072
1591
|
).addOption(
|
|
1073
|
-
new
|
|
1592
|
+
new Option3(
|
|
1074
1593
|
"--wrangler-version <version>",
|
|
1075
1594
|
`Wrangler version or dist-tag to build with. Defaults to ${WRANGLER_VERSION}.`
|
|
1076
1595
|
).argParser(parseWranglerVersion)
|
|
@@ -1098,15 +1617,18 @@ Examples:
|
|
|
1098
1617
|
});
|
|
1099
1618
|
|
|
1100
1619
|
// src/cli/commands/channels.ts
|
|
1101
|
-
import { select as select4 } from "@inquirer/prompts";
|
|
1102
|
-
import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as
|
|
1620
|
+
import { confirm as confirm3, select as select4 } from "@inquirer/prompts";
|
|
1621
|
+
import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option4 } from "commander";
|
|
1103
1622
|
import { colorize as colorize5 } from "consola/utils";
|
|
1104
1623
|
|
|
1624
|
+
// src/cli/lib/channel-checkout-url-flow.ts
|
|
1625
|
+
import { input as input3 } from "@inquirer/prompts";
|
|
1626
|
+
|
|
1105
1627
|
// src/cli/lib/channel-site-flow.ts
|
|
1106
1628
|
import { select as select2 } from "@inquirer/prompts";
|
|
1107
1629
|
|
|
1108
1630
|
// src/cli/lib/channels.ts
|
|
1109
|
-
import { z as
|
|
1631
|
+
import { z as z8 } from "zod";
|
|
1110
1632
|
var cliApiUrl = (origin, storeHash, path) => `${origin}/stores/${storeHash}/cli-api/v3${path}`;
|
|
1111
1633
|
var channelsUrl = (storeHash, apiHost, query2 = {}) => {
|
|
1112
1634
|
const params = new URLSearchParams(query2).toString();
|
|
@@ -1117,14 +1639,14 @@ var authHeaders2 = (accessToken) => ({
|
|
|
1117
1639
|
"X-Correlation-Id": getTelemetry().correlationId,
|
|
1118
1640
|
Accept: "application/json"
|
|
1119
1641
|
});
|
|
1120
|
-
var envVarsSchema =
|
|
1121
|
-
var channelSchema =
|
|
1122
|
-
id:
|
|
1123
|
-
name:
|
|
1124
|
-
platform:
|
|
1642
|
+
var envVarsSchema = z8.record(z8.string(), z8.coerce.string());
|
|
1643
|
+
var channelSchema = z8.object({
|
|
1644
|
+
id: z8.number(),
|
|
1645
|
+
name: z8.string(),
|
|
1646
|
+
platform: z8.string()
|
|
1125
1647
|
});
|
|
1126
|
-
var channelsResponseSchema =
|
|
1127
|
-
data:
|
|
1648
|
+
var channelsResponseSchema = z8.object({
|
|
1649
|
+
data: z8.array(channelSchema)
|
|
1128
1650
|
});
|
|
1129
1651
|
var CHANNEL_PLATFORM_ORDER = ["catalyst", "next", "bigcommerce"];
|
|
1130
1652
|
function sortChannelsByPlatform(channels2) {
|
|
@@ -1140,32 +1662,46 @@ function sortChannelsByPlatform(channels2) {
|
|
|
1140
1662
|
function channelPlatformLabel(platform) {
|
|
1141
1663
|
return platform === "bigcommerce" ? "Stencil" : platform.charAt(0).toUpperCase() + platform.slice(1);
|
|
1142
1664
|
}
|
|
1143
|
-
var initResponseSchema =
|
|
1144
|
-
data:
|
|
1145
|
-
storefront_api_token:
|
|
1665
|
+
var initResponseSchema = z8.object({
|
|
1666
|
+
data: z8.object({
|
|
1667
|
+
storefront_api_token: z8.string(),
|
|
1146
1668
|
envVars: envVarsSchema
|
|
1147
1669
|
})
|
|
1148
1670
|
});
|
|
1149
|
-
var createChannelResponseSchema =
|
|
1150
|
-
data:
|
|
1151
|
-
id:
|
|
1152
|
-
storefront_api_token:
|
|
1671
|
+
var createChannelResponseSchema = z8.object({
|
|
1672
|
+
data: z8.object({
|
|
1673
|
+
id: z8.number(),
|
|
1674
|
+
storefront_api_token: z8.string(),
|
|
1153
1675
|
envVars: envVarsSchema
|
|
1154
1676
|
})
|
|
1155
1677
|
});
|
|
1156
|
-
var eligibilityResponseSchema =
|
|
1157
|
-
data:
|
|
1158
|
-
eligible:
|
|
1159
|
-
message:
|
|
1678
|
+
var eligibilityResponseSchema = z8.object({
|
|
1679
|
+
data: z8.object({
|
|
1680
|
+
eligible: z8.boolean(),
|
|
1681
|
+
message: z8.string()
|
|
1160
1682
|
})
|
|
1161
1683
|
});
|
|
1162
|
-
var
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1684
|
+
var siteUrlSchema = z8.object({
|
|
1685
|
+
url: z8.string(),
|
|
1686
|
+
type: z8.string()
|
|
1687
|
+
});
|
|
1688
|
+
var channelSiteSchema = z8.object({
|
|
1689
|
+
data: z8.object({
|
|
1690
|
+
id: z8.number(),
|
|
1691
|
+
url: z8.string(),
|
|
1692
|
+
channel_id: z8.number(),
|
|
1693
|
+
ssl_status: z8.string().nullable().optional(),
|
|
1694
|
+
is_checkout_url_customized: z8.boolean().optional(),
|
|
1695
|
+
urls: z8.array(siteUrlSchema).optional()
|
|
1167
1696
|
})
|
|
1168
1697
|
});
|
|
1698
|
+
function assertChannelSettingsAuthorized(response, action) {
|
|
1699
|
+
if (response.status === 401 || response.status === 403) {
|
|
1700
|
+
throw new UserActionableError(
|
|
1701
|
+
`${action} (${response.status}). Re-run \`catalyst auth login\` to refresh your access token with the store_channel_settings and store_sites scopes.`
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1169
1705
|
async function getChannelInit(channelId, storeHash, accessToken, origin) {
|
|
1170
1706
|
const response = await fetch(cliApiUrl(origin, storeHash, `/channels/${channelId}/init`), {
|
|
1171
1707
|
method: "GET",
|
|
@@ -1214,354 +1750,180 @@ async function fetchAvailableChannels(storeHash, accessToken, apiHost) {
|
|
|
1214
1750
|
const response = await fetch(
|
|
1215
1751
|
channelsUrl(storeHash, apiHost, { available: "true", type: "storefront" }),
|
|
1216
1752
|
{
|
|
1217
|
-
method: "GET",
|
|
1218
|
-
headers: {
|
|
1219
|
-
"X-Auth-Token": accessToken,
|
|
1220
|
-
"X-Correlation-Id": getTelemetry().correlationId,
|
|
1221
|
-
Accept: "application/json"
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
);
|
|
1225
|
-
assertAuthorized(response);
|
|
1226
|
-
if (!response.ok) {
|
|
1227
|
-
throw await httpError(response, "Failed to fetch channels");
|
|
1228
|
-
}
|
|
1229
|
-
return channelsResponseSchema.parse(await response.json()).data;
|
|
1230
|
-
}
|
|
1231
|
-
async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken, apiHost) {
|
|
1232
|
-
const response = await fetch(
|
|
1233
|
-
`https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site`,
|
|
1234
|
-
{
|
|
1235
|
-
method: "PUT",
|
|
1236
|
-
headers: {
|
|
1237
|
-
"X-Auth-Token": accessToken,
|
|
1238
|
-
"Content-Type": "application/json",
|
|
1239
|
-
Accept: "application/json",
|
|
1240
|
-
"X-Correlation-Id": getTelemetry().correlationId
|
|
1241
|
-
},
|
|
1242
|
-
body: JSON.stringify({ url: siteUrl })
|
|
1243
|
-
}
|
|
1244
|
-
);
|
|
1245
|
-
if (response.status === 401 || response.status === 403) {
|
|
1246
|
-
throw new UserActionableError(
|
|
1247
|
-
`Failed to update channel site (${response.status}). Re-run \`catalyst auth login\` to refresh your access token with the store_channel_settings scope.`
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
if (!response.ok) {
|
|
1251
|
-
throw await httpError(response, "Failed to update channel site");
|
|
1252
|
-
}
|
|
1253
|
-
const res = await response.json();
|
|
1254
|
-
const { data } = channelSiteSchema.parse(res);
|
|
1255
|
-
return { id: data.id, url: data.url, channelId: data.channel_id };
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
// src/cli/lib/commerce-hosting.ts
|
|
1259
|
-
import { confirm as confirm2, input as input2, select, Separator } from "@inquirer/prompts";
|
|
1260
|
-
import { colorize as colorize3 } from "consola/utils";
|
|
1261
|
-
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
1262
|
-
import { dirname as dirname2, join as join5 } from "path";
|
|
1263
|
-
import { z as z8 } from "zod";
|
|
1264
|
-
|
|
1265
|
-
// src/cli/lib/sort-package-json.ts
|
|
1266
|
-
var FIELD_ORDER = [
|
|
1267
|
-
"name",
|
|
1268
|
-
"description",
|
|
1269
|
-
"version",
|
|
1270
|
-
"private",
|
|
1271
|
-
"engines",
|
|
1272
|
-
"scripts",
|
|
1273
|
-
"dependencies",
|
|
1274
|
-
"devDependencies"
|
|
1275
|
-
];
|
|
1276
|
-
function sortPackageJsonFields(pkg) {
|
|
1277
|
-
const ordered = {};
|
|
1278
|
-
FIELD_ORDER.forEach((field) => {
|
|
1279
|
-
if (field in pkg) {
|
|
1280
|
-
ordered[field] = pkg[field];
|
|
1281
|
-
}
|
|
1282
|
-
});
|
|
1283
|
-
Object.keys(pkg).forEach((key) => {
|
|
1284
|
-
if (!(key in ordered)) {
|
|
1285
|
-
ordered[key] = pkg[key];
|
|
1286
|
-
}
|
|
1287
|
-
});
|
|
1288
|
-
return ordered;
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
// src/cli/lib/commerce-hosting.ts
|
|
1292
|
-
var OPENNEXT_CLOUDFLARE_VERSION = "1.17.3";
|
|
1293
|
-
var corePackageJsonSchema = z8.looseObject({
|
|
1294
|
-
dependencies: z8.record(z8.string(), z8.string()).optional()
|
|
1295
|
-
});
|
|
1296
|
-
var projectJsonSchema2 = z8.looseObject({});
|
|
1297
|
-
var readProjectJson = (path) => {
|
|
1298
|
-
if (!existsSync3(path)) return {};
|
|
1299
|
-
try {
|
|
1300
|
-
const parsed = projectJsonSchema2.safeParse(JSON.parse(readFileSync2(path, "utf-8")));
|
|
1301
|
-
return parsed.success ? parsed.data : {};
|
|
1302
|
-
} catch {
|
|
1303
|
-
return {};
|
|
1304
|
-
}
|
|
1305
|
-
};
|
|
1306
|
-
var writeJson = (path, value) => {
|
|
1307
|
-
mkdirSync(dirname2(path), { recursive: true });
|
|
1308
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
1309
|
-
`);
|
|
1310
|
-
};
|
|
1311
|
-
var convertProxyToMiddleware = (projectDir) => {
|
|
1312
|
-
const proxyPath = join5(projectDir, "proxy.ts");
|
|
1313
|
-
const middlewarePath = join5(projectDir, "middleware.ts");
|
|
1314
|
-
if (!existsSync3(proxyPath)) return;
|
|
1315
|
-
const contents = readFileSync2(proxyPath, "utf-8").replace("export const proxy", "export const middleware").replace("export const config = {", "export const config = {\n runtime: 'experimental-edge',");
|
|
1316
|
-
writeFileSync(middlewarePath, contents);
|
|
1317
|
-
unlinkSync(proxyPath);
|
|
1318
|
-
};
|
|
1319
|
-
var cleanupCloudflareIncompatibilities = async (projectDir) => {
|
|
1320
|
-
const instrumentationPath = join5(projectDir, "instrumentation.ts");
|
|
1321
|
-
if (!existsSync3(instrumentationPath)) return;
|
|
1322
|
-
if (!process.stdin.isTTY) {
|
|
1323
|
-
consola.warn(
|
|
1324
|
-
"instrumentation.ts is present and may be incompatible with the Cloudflare Workers bundle (the default @vercel/otel scaffolding throws at cold start under workerd). Skipping automatic cleanup in non-interactive mode \u2014 re-run interactively to remove it, or delete/gate it manually."
|
|
1325
|
-
);
|
|
1326
|
-
return;
|
|
1327
|
-
}
|
|
1328
|
-
const shouldRemove = await confirm2({
|
|
1329
|
-
message: 'Catalyst found instrumentation.ts, which is incompatible with the Cloudflare Workers bundle when it uses @vercel/otel (causes "Failed to prepare server" at cold start). Remove it and drop @vercel/otel from package.json?',
|
|
1330
|
-
default: true
|
|
1331
|
-
});
|
|
1332
|
-
if (!shouldRemove) {
|
|
1333
|
-
consola.info(
|
|
1334
|
-
'Leaving instrumentation.ts in place. The Cloudflare worker will continue to log "Failed to prepare server" at cold start until this is resolved manually.'
|
|
1335
|
-
);
|
|
1336
|
-
return;
|
|
1337
|
-
}
|
|
1338
|
-
unlinkSync(instrumentationPath);
|
|
1339
|
-
consola.info("Removed instrumentation.ts (incompatible with Cloudflare Workers).");
|
|
1340
|
-
const corePackageJsonPath = join5(projectDir, "package.json");
|
|
1341
|
-
if (existsSync3(corePackageJsonPath)) {
|
|
1342
|
-
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
|
|
1343
|
-
if (pkg.dependencies && "@vercel/otel" in pkg.dependencies) {
|
|
1344
|
-
const { "@vercel/otel": _removedVercelOtel, ...preservedDeps } = pkg.dependencies;
|
|
1345
|
-
pkg.dependencies = preservedDeps;
|
|
1346
|
-
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1347
|
-
consola.info("Dropped @vercel/otel from package.json.");
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
};
|
|
1351
|
-
var setupCommerceHosting = async ({
|
|
1352
|
-
projectDir,
|
|
1353
|
-
projectUuid,
|
|
1354
|
-
storeHash,
|
|
1355
|
-
accessToken
|
|
1356
|
-
}) => {
|
|
1357
|
-
await cleanupCloudflareIncompatibilities(projectDir);
|
|
1358
|
-
const corePackageJsonPath = join5(projectDir, "package.json");
|
|
1359
|
-
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
|
|
1360
|
-
pkg.dependencies = {
|
|
1361
|
-
...pkg.dependencies,
|
|
1362
|
-
"@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
|
|
1363
|
-
};
|
|
1364
|
-
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1365
|
-
const projectJsonPath = join5(projectDir, ".bigcommerce", "project.json");
|
|
1366
|
-
const projectJson = {
|
|
1367
|
-
...readProjectJson(projectJsonPath),
|
|
1368
|
-
projectUuid,
|
|
1369
|
-
framework: "catalyst"
|
|
1370
|
-
};
|
|
1371
|
-
if (storeHash) projectJson.storeHash = storeHash;
|
|
1372
|
-
if (accessToken) projectJson.accessToken = accessToken;
|
|
1373
|
-
writeJson(projectJsonPath, projectJson);
|
|
1374
|
-
convertProxyToMiddleware(projectDir);
|
|
1375
|
-
};
|
|
1376
|
-
var NoLinkedProjectError = class extends Error {
|
|
1377
|
-
constructor() {
|
|
1378
|
-
super("No infrastructure project linked: user declined to create one.");
|
|
1379
|
-
this.name = "NoLinkedProjectError";
|
|
1380
|
-
}
|
|
1381
|
-
};
|
|
1382
|
-
async function promptForNewProjectName(api) {
|
|
1383
|
-
const newProjectName = await input2({ message: "Enter a name for the new project:" });
|
|
1384
|
-
const data = await createProject(newProjectName, api.storeHash, api.accessToken, api.apiHost);
|
|
1385
|
-
consola.success(`Project "${data.name}" created successfully.`);
|
|
1386
|
-
return { uuid: data.uuid, name: data.name, deployment_hostnames: [] };
|
|
1387
|
-
}
|
|
1388
|
-
async function selectOrCreateInfrastructureProject(api, linkedProjectUuid) {
|
|
1389
|
-
consola.start("Fetching projects...");
|
|
1390
|
-
const existingProjects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
|
|
1391
|
-
consola.success("Projects fetched.");
|
|
1392
|
-
if (existingProjects.length === 0) {
|
|
1393
|
-
const shouldCreate = await confirm2({
|
|
1394
|
-
message: "There are not any hosting projects that you can link to yet. Would you like to create one?",
|
|
1395
|
-
default: true
|
|
1396
|
-
});
|
|
1397
|
-
if (!shouldCreate) {
|
|
1398
|
-
throw new NoLinkedProjectError();
|
|
1753
|
+
method: "GET",
|
|
1754
|
+
headers: {
|
|
1755
|
+
"X-Auth-Token": accessToken,
|
|
1756
|
+
"X-Correlation-Id": getTelemetry().correlationId,
|
|
1757
|
+
Accept: "application/json"
|
|
1758
|
+
}
|
|
1399
1759
|
}
|
|
1400
|
-
|
|
1760
|
+
);
|
|
1761
|
+
assertAuthorized(response);
|
|
1762
|
+
if (!response.ok) {
|
|
1763
|
+
throw await httpError(response, "Failed to fetch channels");
|
|
1401
1764
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
})),
|
|
1765
|
+
return channelsResponseSchema.parse(await response.json()).data;
|
|
1766
|
+
}
|
|
1767
|
+
async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken, apiHost) {
|
|
1768
|
+
const response = await fetch(
|
|
1769
|
+
`https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site`,
|
|
1408
1770
|
{
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1771
|
+
method: "PUT",
|
|
1772
|
+
headers: {
|
|
1773
|
+
"X-Auth-Token": accessToken,
|
|
1774
|
+
"Content-Type": "application/json",
|
|
1775
|
+
Accept: "application/json",
|
|
1776
|
+
"X-Correlation-Id": getTelemetry().correlationId
|
|
1777
|
+
},
|
|
1778
|
+
body: JSON.stringify({ url: siteUrl })
|
|
1412
1779
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
});
|
|
1418
|
-
if (selected === "create") {
|
|
1419
|
-
return promptForNewProjectName(api);
|
|
1420
|
-
}
|
|
1421
|
-
const matched = existingProjects.find((p) => p.uuid === selected);
|
|
1422
|
-
if (!matched) {
|
|
1423
|
-
throw new Error(`Selected project ${String(selected)} not found in fetched list.`);
|
|
1780
|
+
);
|
|
1781
|
+
assertChannelSettingsAuthorized(response, "Failed to update channel site");
|
|
1782
|
+
if (!response.ok) {
|
|
1783
|
+
throw await httpError(response, "Failed to update channel site");
|
|
1424
1784
|
}
|
|
1425
|
-
|
|
1785
|
+
const res = await response.json();
|
|
1786
|
+
const { data } = channelSiteSchema.parse(res);
|
|
1787
|
+
return { id: data.id, url: data.url, channelId: data.channel_id };
|
|
1426
1788
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1789
|
+
var toChannelSiteDetails = (data) => ({
|
|
1790
|
+
id: data.id,
|
|
1791
|
+
url: data.url,
|
|
1792
|
+
channelId: data.channel_id,
|
|
1793
|
+
sslStatus: data.ssl_status ?? null,
|
|
1794
|
+
isCheckoutUrlCustomized: data.is_checkout_url_customized ?? false,
|
|
1795
|
+
urls: data.urls ?? []
|
|
1796
|
+
});
|
|
1797
|
+
function findChannelSiteUrl(site, type) {
|
|
1798
|
+
return site.urls.find((entry) => entry.type === type)?.url;
|
|
1799
|
+
}
|
|
1800
|
+
async function getChannelSite(channelId, storeHash, accessToken, apiHost) {
|
|
1801
|
+
const response = await fetch(
|
|
1802
|
+
`https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site`,
|
|
1803
|
+
{ method: "GET", headers: authHeaders2(accessToken) }
|
|
1440
1804
|
);
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
defaultName,
|
|
1445
|
-
existingProjects,
|
|
1446
|
-
useExistingOnCollision
|
|
1447
|
-
);
|
|
1448
|
-
}
|
|
1449
|
-
const hasOtherProjects = existingProjects.length > 1;
|
|
1450
|
-
const choices = [
|
|
1451
|
-
{ name: `Use "${conflict.name}"`, value: "use-named" }
|
|
1452
|
-
];
|
|
1453
|
-
if (hasOtherProjects) {
|
|
1454
|
-
choices.push({ name: "Select from my projects", value: "select-from-list" });
|
|
1805
|
+
assertChannelSettingsAuthorized(response, "Failed to fetch channel site");
|
|
1806
|
+
if (!response.ok) {
|
|
1807
|
+
throw await httpError(response, "Failed to fetch channel site");
|
|
1455
1808
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1809
|
+
const res = await response.json();
|
|
1810
|
+
return toChannelSiteDetails(channelSiteSchema.parse(res).data);
|
|
1811
|
+
}
|
|
1812
|
+
var checkoutUrlPath = (storeHash, apiHost, channelId) => `https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site/checkout-url`;
|
|
1813
|
+
async function updateChannelCheckoutUrl(channelId, checkoutUrl, storeHash, accessToken, apiHost) {
|
|
1814
|
+
const response = await fetch(checkoutUrlPath(storeHash, apiHost, channelId), {
|
|
1815
|
+
method: "PUT",
|
|
1816
|
+
headers: { ...authHeaders2(accessToken), "Content-Type": "application/json" },
|
|
1817
|
+
body: JSON.stringify({ url: checkoutUrl })
|
|
1460
1818
|
});
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
}
|
|
1465
|
-
if (action === "create") {
|
|
1466
|
-
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1819
|
+
assertChannelSettingsAuthorized(response, "Failed to update channel checkout URL");
|
|
1820
|
+
if (!response.ok) {
|
|
1821
|
+
throw await httpError(response, "Failed to update channel checkout URL");
|
|
1467
1822
|
}
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
})),
|
|
1476
|
-
new Separator(),
|
|
1477
|
-
{ name: "Create a new project", value: "create-new" }
|
|
1478
|
-
]
|
|
1823
|
+
const res = await response.json();
|
|
1824
|
+
return toChannelSiteDetails(channelSiteSchema.parse(res).data);
|
|
1825
|
+
}
|
|
1826
|
+
async function deleteChannelCheckoutUrl(channelId, storeHash, accessToken, apiHost) {
|
|
1827
|
+
const response = await fetch(checkoutUrlPath(storeHash, apiHost, channelId), {
|
|
1828
|
+
method: "DELETE",
|
|
1829
|
+
headers: authHeaders2(accessToken)
|
|
1479
1830
|
});
|
|
1480
|
-
|
|
1481
|
-
|
|
1831
|
+
assertChannelSettingsAuthorized(response, "Failed to remove channel checkout URL");
|
|
1832
|
+
if (!response.ok) {
|
|
1833
|
+
throw await httpError(response, "Failed to remove channel checkout URL");
|
|
1482
1834
|
}
|
|
1483
|
-
consola.success(`Using existing Commerce Hosting project "${selected.name}"`);
|
|
1484
|
-
return selected;
|
|
1485
1835
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
const conflict = takenNames.find((taken) => taken.toLowerCase() === trimmed.toLowerCase());
|
|
1494
|
-
if (conflict) {
|
|
1495
|
-
return `A Commerce Hosting project named "${conflict}" already exists`;
|
|
1496
|
-
}
|
|
1497
|
-
return true;
|
|
1498
|
-
},
|
|
1499
|
-
theme: {
|
|
1500
|
-
style: {
|
|
1501
|
-
help: () => colorize3(
|
|
1502
|
-
"dim",
|
|
1503
|
-
"(The project that hosts your storefront on Commerce \u2014 often matches your folder name.)"
|
|
1504
|
-
)
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
});
|
|
1836
|
+
|
|
1837
|
+
// src/cli/lib/checkout-url.ts
|
|
1838
|
+
import { getDomain } from "tldts";
|
|
1839
|
+
var normalizeHostname = (hostname) => hostname.toLowerCase().replace(/\.$/, "");
|
|
1840
|
+
var parentDomain = (hostname) => hostname.split(".").slice(1).join(".");
|
|
1841
|
+
var isSubdomainOf = (hostname, parent) => hostname.endsWith(`.${parent}`);
|
|
1842
|
+
function hostnameOf(url) {
|
|
1508
1843
|
try {
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1844
|
+
return new URL(url).hostname;
|
|
1845
|
+
} catch {
|
|
1846
|
+
return void 0;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
function sharesMainDomain(a, b) {
|
|
1850
|
+
const first = getDomain(normalizeHostname(a));
|
|
1851
|
+
return first !== null && first === getDomain(normalizeHostname(b));
|
|
1852
|
+
}
|
|
1853
|
+
function suggestCheckoutUrl(storefrontUrl) {
|
|
1854
|
+
const host = hostnameOf(storefrontUrl);
|
|
1855
|
+
if (!host) return void 0;
|
|
1856
|
+
const base = normalizeHostname(host).replace(/^www\./, "");
|
|
1857
|
+
return `https://checkout.${base}`;
|
|
1858
|
+
}
|
|
1859
|
+
function normalizeCheckoutUrl(value) {
|
|
1860
|
+
const withScheme = /^[a-z][a-z\d+.-]*:\/\//i.test(value) ? value : `https://${value}`;
|
|
1861
|
+
let parsed;
|
|
1862
|
+
try {
|
|
1863
|
+
parsed = new URL(withScheme);
|
|
1864
|
+
} catch {
|
|
1865
|
+
throw new UserActionableError(
|
|
1866
|
+
`"${value}" is not a valid URL. Pass a hostname or an https URL, e.g. https://checkout.example.com.`
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
if (parsed.protocol !== "https:") {
|
|
1870
|
+
throw new UserActionableError(
|
|
1871
|
+
`The checkout URL must use https, but "${value}" uses ${parsed.protocol.replace(":", "")}.`
|
|
1514
1872
|
);
|
|
1515
|
-
consola.success(`Commerce Hosting project "${created.name}" created successfully`);
|
|
1516
|
-
return { uuid: created.uuid, name: created.name, deployment_hostnames: [] };
|
|
1517
|
-
} catch (error) {
|
|
1518
|
-
if (error instanceof InfrastructureProjectValidationError) {
|
|
1519
|
-
consola.error(error.message);
|
|
1520
|
-
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1521
|
-
}
|
|
1522
|
-
throw error;
|
|
1523
1873
|
}
|
|
1874
|
+
return parsed.origin;
|
|
1524
1875
|
}
|
|
1525
|
-
async function
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
}
|
|
1876
|
+
async function isManagedHostingHostname(hostname, context) {
|
|
1877
|
+
try {
|
|
1878
|
+
const projects2 = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
|
|
1879
|
+
const zones = projects2.flatMap((project) => project.deployment_hostnames).map((deploymentHostname) => parentDomain(normalizeHostname(deploymentHostname))).filter((zone) => zone.includes("."));
|
|
1880
|
+
if (zones.length === 0) return void 0;
|
|
1881
|
+
const host = normalizeHostname(hostname);
|
|
1882
|
+
return zones.some((zone) => host === zone || isSubdomainOf(host, zone));
|
|
1883
|
+
} catch {
|
|
1884
|
+
return void 0;
|
|
1885
|
+
}
|
|
1535
1886
|
}
|
|
1536
|
-
async function
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1887
|
+
async function warnOnCrossDomainCheckout(site, context) {
|
|
1888
|
+
const storefrontUrl = findChannelSiteUrl(site, "primary") ?? site.url;
|
|
1889
|
+
const storefrontHost = hostnameOf(storefrontUrl);
|
|
1890
|
+
const checkoutUrl = findChannelSiteUrl(site, "checkout");
|
|
1891
|
+
if (!storefrontHost) return { crossDomain: false };
|
|
1892
|
+
const suggestion = suggestCheckoutUrl(storefrontUrl);
|
|
1893
|
+
const checkoutHost = checkoutUrl ? hostnameOf(checkoutUrl) : void 0;
|
|
1894
|
+
if (!checkoutHost || sharesMainDomain(storefrontHost, checkoutHost)) {
|
|
1895
|
+
return { crossDomain: false, suggestion };
|
|
1896
|
+
}
|
|
1897
|
+
if (site.isCheckoutUrlCustomized) {
|
|
1898
|
+
consola.warn(
|
|
1899
|
+
`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.`
|
|
1900
|
+
);
|
|
1901
|
+
} else {
|
|
1902
|
+
consola.warn(
|
|
1903
|
+
`Checkout is on the default channel's domain (${checkoutHost}), not this channel's storefront (${storefrontHost}).`
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
consola.info(
|
|
1907
|
+
"Shopper sessions and carts may not carry into checkout in browsers that restrict cross-domain cookies."
|
|
1539
1908
|
);
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
}
|
|
1546
|
-
consola.error(
|
|
1547
|
-
"Not reusing the existing project. Re-run with a different --project-name, or pass --use-existing to reuse it."
|
|
1909
|
+
const storefrontOnManagedZone = await isManagedHostingHostname(storefrontHost, context);
|
|
1910
|
+
const report = { crossDomain: true, storefrontOnManagedZone, suggestion };
|
|
1911
|
+
if (storefrontOnManagedZone === false) {
|
|
1912
|
+
consola.info(
|
|
1913
|
+
`To put checkout on this channel's own domain, point ${suggestion?.replace("https://", "") ?? "your checkout subdomain"} at BigCommerce and provision a certificate for it there.`
|
|
1548
1914
|
);
|
|
1549
|
-
|
|
1915
|
+
return report;
|
|
1550
1916
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
if (error instanceof InfrastructureProjectValidationError) {
|
|
1557
|
-
consola.error(
|
|
1558
|
-
`Failed to create Commerce Hosting project "${name}": ${error.message}
|
|
1559
|
-
Re-run with a different --project-name.`
|
|
1560
|
-
);
|
|
1561
|
-
process.exit(1);
|
|
1562
|
-
}
|
|
1563
|
-
throw error;
|
|
1917
|
+
if (storefrontOnManagedZone === true) {
|
|
1918
|
+
consola.info(
|
|
1919
|
+
`${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\`.`
|
|
1920
|
+
);
|
|
1921
|
+
return report;
|
|
1564
1922
|
}
|
|
1923
|
+
consola.info(
|
|
1924
|
+
"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."
|
|
1925
|
+
);
|
|
1926
|
+
return report;
|
|
1565
1927
|
}
|
|
1566
1928
|
|
|
1567
1929
|
// src/cli/lib/channel-site-flow.ts
|
|
@@ -1599,7 +1961,7 @@ async function resolveChannel(options) {
|
|
|
1599
1961
|
);
|
|
1600
1962
|
}
|
|
1601
1963
|
const id = await select2({
|
|
1602
|
-
message: "Select the channel to update.",
|
|
1964
|
+
message: options.message ?? "Select the channel to update.",
|
|
1603
1965
|
choices: catalystChannels.map((c) => ({
|
|
1604
1966
|
name: c.name,
|
|
1605
1967
|
value: c.id,
|
|
@@ -1618,34 +1980,91 @@ async function resolveHostname(project, options) {
|
|
|
1618
1980
|
`Project "${project.name}" has no deployment hostnames yet. Run \`catalyst deploy\` first to create one.`
|
|
1619
1981
|
);
|
|
1620
1982
|
}
|
|
1621
|
-
const ordered = options.preferHostname ? [
|
|
1622
|
-
...project.deployment_hostnames.filter((h) => h === options.preferHostname),
|
|
1623
|
-
...project.deployment_hostnames.filter((h) => h !== options.preferHostname)
|
|
1624
|
-
] : project.deployment_hostnames;
|
|
1625
|
-
const selected = await select2({
|
|
1626
|
-
message: "Select the hostname to point the channel at.",
|
|
1627
|
-
choices: ordered.map((h) => ({ name: h, value: h }))
|
|
1983
|
+
const ordered = options.preferHostname ? [
|
|
1984
|
+
...project.deployment_hostnames.filter((h) => h === options.preferHostname),
|
|
1985
|
+
...project.deployment_hostnames.filter((h) => h !== options.preferHostname)
|
|
1986
|
+
] : project.deployment_hostnames;
|
|
1987
|
+
const selected = await select2({
|
|
1988
|
+
message: "Select the hostname to point the channel at.",
|
|
1989
|
+
choices: ordered.map((h) => ({ name: h, value: h }))
|
|
1990
|
+
});
|
|
1991
|
+
return selected;
|
|
1992
|
+
}
|
|
1993
|
+
async function runChannelSiteUrlFlow(options) {
|
|
1994
|
+
const project = await resolveProject(options);
|
|
1995
|
+
const channel = await resolveChannel(options);
|
|
1996
|
+
const hostname = await resolveHostname(project, options);
|
|
1997
|
+
const siteUrl = hostname.startsWith("https://") ? hostname : `https://${hostname}`;
|
|
1998
|
+
await updateChannelSiteUrl(
|
|
1999
|
+
channel.id,
|
|
2000
|
+
siteUrl,
|
|
2001
|
+
options.storeHash,
|
|
2002
|
+
options.accessToken,
|
|
2003
|
+
options.apiHost
|
|
2004
|
+
);
|
|
2005
|
+
const channelLabel = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
|
|
2006
|
+
consola.success(`Updated channel ${channelLabel} site URL to ${siteUrl}.`);
|
|
2007
|
+
try {
|
|
2008
|
+
const site = await getChannelSite(
|
|
2009
|
+
channel.id,
|
|
2010
|
+
options.storeHash,
|
|
2011
|
+
options.accessToken,
|
|
2012
|
+
options.apiHost
|
|
2013
|
+
);
|
|
2014
|
+
const report = await warnOnCrossDomainCheckout(site, {
|
|
2015
|
+
storeHash: options.storeHash,
|
|
2016
|
+
accessToken: options.accessToken,
|
|
2017
|
+
apiHost: options.apiHost
|
|
2018
|
+
});
|
|
2019
|
+
if (report.crossDomain && report.storefrontOnManagedZone !== true) {
|
|
2020
|
+
consola.info("Set a checkout URL for this channel with:");
|
|
2021
|
+
consola.log(
|
|
2022
|
+
` catalyst channels update --channel-id ${channel.id} --checkout-url ${report.suggestion ?? "<domain>"}`
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
} catch {
|
|
2026
|
+
}
|
|
2027
|
+
return { channelId: channel.id };
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
// src/cli/lib/channel-checkout-url-flow.ts
|
|
2031
|
+
async function runChannelCheckoutUrlFlow(options) {
|
|
2032
|
+
const channel = options.channelId !== void 0 ? { id: options.channelId, name: options.channelName } : await resolveChannel(options);
|
|
2033
|
+
const label = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
|
|
2034
|
+
let storefrontUrl = options.storefrontUrl;
|
|
2035
|
+
if (!storefrontUrl) {
|
|
2036
|
+
const site = await getChannelSite(
|
|
2037
|
+
channel.id,
|
|
2038
|
+
options.storeHash,
|
|
2039
|
+
options.accessToken,
|
|
2040
|
+
options.apiHost
|
|
2041
|
+
);
|
|
2042
|
+
storefrontUrl = findChannelSiteUrl(site, "primary") ?? site.url;
|
|
2043
|
+
const currentCheckoutUrl = findChannelSiteUrl(site, "checkout");
|
|
2044
|
+
consola.info(`Channel ${label} storefront is ${storefrontUrl}.`);
|
|
2045
|
+
if (currentCheckoutUrl) {
|
|
2046
|
+
consola.info(
|
|
2047
|
+
`Checkout is currently ${currentCheckoutUrl}${site.isCheckoutUrlCustomized ? "" : ", inherited from the default channel"}.`
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
const answer = options.url ?? await input3({
|
|
2052
|
+
message: "Checkout URL for this channel (must share a main domain with the storefront)",
|
|
2053
|
+
default: suggestCheckoutUrl(storefrontUrl)
|
|
1628
2054
|
});
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
async function runChannelSiteUrlFlow(options) {
|
|
1632
|
-
const project = await resolveProject(options);
|
|
1633
|
-
const channel = await resolveChannel(options);
|
|
1634
|
-
const hostname = await resolveHostname(project, options);
|
|
1635
|
-
const siteUrl = hostname.startsWith("https://") ? hostname : `https://${hostname}`;
|
|
1636
|
-
await updateChannelSiteUrl(
|
|
2055
|
+
const checkoutUrl = normalizeCheckoutUrl(answer);
|
|
2056
|
+
await updateChannelCheckoutUrl(
|
|
1637
2057
|
channel.id,
|
|
1638
|
-
|
|
2058
|
+
checkoutUrl,
|
|
1639
2059
|
options.storeHash,
|
|
1640
2060
|
options.accessToken,
|
|
1641
2061
|
options.apiHost
|
|
1642
2062
|
);
|
|
1643
|
-
|
|
1644
|
-
consola.success(`Updated channel ${channelLabel} site URL to ${siteUrl}.`);
|
|
2063
|
+
consola.success(`Updated channel ${label} checkout URL to ${checkoutUrl}.`);
|
|
1645
2064
|
}
|
|
1646
2065
|
|
|
1647
2066
|
// src/cli/lib/create-channel-flow.ts
|
|
1648
|
-
import { checkbox, input as
|
|
2067
|
+
import { checkbox, input as input4, select as select3 } from "@inquirer/prompts";
|
|
1649
2068
|
import { colorize as colorize4 } from "consola/utils";
|
|
1650
2069
|
|
|
1651
2070
|
// src/cli/lib/localization.ts
|
|
@@ -1720,7 +2139,7 @@ async function runCreateChannelFlow(options) {
|
|
|
1720
2139
|
throw new UserActionableError(nameError);
|
|
1721
2140
|
}
|
|
1722
2141
|
}
|
|
1723
|
-
const name = options.name ?? await
|
|
2142
|
+
const name = options.name ?? await input4({
|
|
1724
2143
|
message: "What would you like to name your new channel?",
|
|
1725
2144
|
validate: (value) => getChannelNameError(value) ?? true
|
|
1726
2145
|
});
|
|
@@ -1781,15 +2200,15 @@ async function runCreateChannelFlow(options) {
|
|
|
1781
2200
|
|
|
1782
2201
|
// src/cli/lib/env-config.ts
|
|
1783
2202
|
var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1784
|
-
var parseEnvAssignment = (
|
|
1785
|
-
const separatorIndex =
|
|
2203
|
+
var parseEnvAssignment = (input7) => {
|
|
2204
|
+
const separatorIndex = input7.indexOf("=");
|
|
1786
2205
|
if (separatorIndex === -1) {
|
|
1787
|
-
throw new Error(`Invalid env var format: ${
|
|
2206
|
+
throw new Error(`Invalid env var format: ${input7}. Expected format: KEY=VALUE`);
|
|
1788
2207
|
}
|
|
1789
|
-
const key =
|
|
1790
|
-
const value =
|
|
2208
|
+
const key = input7.slice(0, separatorIndex).trim();
|
|
2209
|
+
const value = input7.slice(separatorIndex + 1).trim();
|
|
1791
2210
|
if (!key || !value) {
|
|
1792
|
-
throw new Error(`Invalid env var format: ${
|
|
2211
|
+
throw new Error(`Invalid env var format: ${input7}. Expected format: KEY=VALUE`);
|
|
1793
2212
|
}
|
|
1794
2213
|
if (!ENV_KEY_PATTERN.test(key)) {
|
|
1795
2214
|
throw new Error(
|
|
@@ -1829,7 +2248,7 @@ function resolveCredentials(options, config2) {
|
|
|
1829
2248
|
// src/cli/lib/write-env.ts
|
|
1830
2249
|
import { outputFileSync } from "fs-extra/esm";
|
|
1831
2250
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1832
|
-
import { join as
|
|
2251
|
+
import { join as join7 } from "path";
|
|
1833
2252
|
var ACTIVE_KEY_LINE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
1834
2253
|
var keyFromLine = (line) => {
|
|
1835
2254
|
const match = ACTIVE_KEY_LINE.exec(line);
|
|
@@ -1846,8 +2265,8 @@ var parseEnvValues = (contents) => {
|
|
|
1846
2265
|
return values;
|
|
1847
2266
|
};
|
|
1848
2267
|
var writeEnv = (projectDir, envVars) => {
|
|
1849
|
-
const examplePath =
|
|
1850
|
-
const localPath =
|
|
2268
|
+
const examplePath = join7(projectDir, ".env.example");
|
|
2269
|
+
const localPath = join7(projectDir, ".env.local");
|
|
1851
2270
|
const existingLocal = existsSync4(localPath) ? parseEnvValues(readFileSync3(localPath, "utf-8")) : /* @__PURE__ */ new Map();
|
|
1852
2271
|
const resolve2 = (key) => {
|
|
1853
2272
|
if (key in envVars && envVars[key] !== "") {
|
|
@@ -1901,6 +2320,22 @@ var parseChannelId = (value) => {
|
|
|
1901
2320
|
}
|
|
1902
2321
|
return parsed;
|
|
1903
2322
|
};
|
|
2323
|
+
var parseCheckoutUrl = (value) => {
|
|
2324
|
+
try {
|
|
2325
|
+
return normalizeCheckoutUrl(value);
|
|
2326
|
+
} catch (error) {
|
|
2327
|
+
throw new InvalidArgumentError2(error instanceof Error ? error.message : String(error));
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
var CHECKOUT_URL_NOTES = `
|
|
2331
|
+
Checkout is hosted by BigCommerce, so a checkout URL must be a domain you have
|
|
2332
|
+
pointed at BigCommerce with a certificate provisioned there \u2014 not one added with
|
|
2333
|
+
\`catalyst domains add\`, which only routes traffic to this project.
|
|
2334
|
+
|
|
2335
|
+
BigCommerce also requires the checkout URL to share a main domain with the
|
|
2336
|
+
channel's storefront URL, so that sessions carry between the two. This means a
|
|
2337
|
+
custom checkout URL is only possible on a custom storefront domain; channels on
|
|
2338
|
+
an auto-generated deployment hostname use the shared checkout domain.`;
|
|
1904
2339
|
async function resolveCredentialsWithLogin(options, config2, apiHost) {
|
|
1905
2340
|
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
1906
2341
|
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
@@ -1919,50 +2354,101 @@ async function resolveCredentialsWithLogin(options, config2, apiHost) {
|
|
|
1919
2354
|
throw error;
|
|
1920
2355
|
}
|
|
1921
2356
|
}
|
|
1922
|
-
var update = new Command3("update").configureHelp({ showGlobalOptions: true }).description(
|
|
1923
|
-
"Update a BigCommerce channel's site URL to point at one of your project's deployment hostnames."
|
|
1924
|
-
).addHelpText(
|
|
2357
|
+
var update = new Command3("update").configureHelp({ showGlobalOptions: true }).description("Update a BigCommerce channel's storefront and checkout URLs.").addHelpText(
|
|
1925
2358
|
"after",
|
|
1926
|
-
|
|
2359
|
+
`${CHECKOUT_URL_NOTES}
|
|
2360
|
+
|
|
2361
|
+
With no flags, this updates the storefront URL and prompts for the hostname.
|
|
2362
|
+
Pass a checkout flag on its own to change only the checkout URL, leaving the
|
|
2363
|
+
storefront URL alone.
|
|
2364
|
+
|
|
1927
2365
|
Examples:
|
|
1928
2366
|
# Pick a channel and hostname interactively
|
|
1929
2367
|
$ catalyst channels update
|
|
1930
2368
|
|
|
1931
|
-
#
|
|
1932
|
-
$ catalyst channels update --channel-id 123 --hostname my-storefront.example.com
|
|
2369
|
+
# Point the storefront at a deployment hostname
|
|
2370
|
+
$ catalyst channels update --channel-id 123 --hostname my-storefront.example.com
|
|
2371
|
+
|
|
2372
|
+
# Set both at once
|
|
2373
|
+
$ catalyst channels update --channel-id 123 --hostname my-storefront.example.com --checkout-url checkout.example.com
|
|
2374
|
+
|
|
2375
|
+
# Change only the checkout URL
|
|
2376
|
+
$ catalyst channels update --channel-id 123 --checkout-url checkout.example.com
|
|
2377
|
+
|
|
2378
|
+
# Fall back to the shared checkout domain
|
|
2379
|
+
$ catalyst channels update --channel-id 123 --remove-checkout-url`
|
|
1933
2380
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
1934
|
-
new
|
|
2381
|
+
new Option4(
|
|
1935
2382
|
"--channel-id <id>",
|
|
1936
2383
|
"Skip the channel prompt and target this channel directly."
|
|
1937
2384
|
).argParser((value) => Number(value))
|
|
1938
2385
|
).addOption(
|
|
1939
|
-
new
|
|
2386
|
+
new Option4(
|
|
1940
2387
|
"--hostname <hostname>",
|
|
1941
2388
|
"Skip the hostname prompt and use this hostname directly. Must be one of the project's deployment_hostnames."
|
|
1942
2389
|
)
|
|
2390
|
+
).addOption(
|
|
2391
|
+
new Option4(
|
|
2392
|
+
"--checkout-url <url>",
|
|
2393
|
+
"Set the channel's checkout URL. Must share a main domain with the storefront URL."
|
|
2394
|
+
).argParser(parseCheckoutUrl)
|
|
2395
|
+
).addOption(
|
|
2396
|
+
new Option4(
|
|
2397
|
+
"--remove-checkout-url",
|
|
2398
|
+
"Remove the channel's custom checkout URL so checkout falls back to the shared checkout domain."
|
|
2399
|
+
).conflicts("checkoutUrl")
|
|
1943
2400
|
).action(async (options) => {
|
|
1944
2401
|
const config2 = getProjectConfig();
|
|
1945
2402
|
const apiHost = resolveApiHost(options, config2);
|
|
1946
2403
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1947
2404
|
await getTelemetry().identify(storeHash);
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2405
|
+
const touchesCheckout = options.checkoutUrl !== void 0 || options.removeCheckoutUrl === true;
|
|
2406
|
+
const updatesSiteUrl = options.hostname !== void 0 || !touchesCheckout;
|
|
2407
|
+
let channelId = options.channelId;
|
|
2408
|
+
if (updatesSiteUrl) {
|
|
2409
|
+
try {
|
|
2410
|
+
({ channelId } = await runChannelSiteUrlFlow({
|
|
2411
|
+
storeHash,
|
|
2412
|
+
accessToken,
|
|
2413
|
+
apiHost,
|
|
2414
|
+
projectUuid: options.projectUuid ?? config2.get("projectUuid"),
|
|
2415
|
+
channelId: options.channelId,
|
|
2416
|
+
hostname: options.hostname
|
|
2417
|
+
}));
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
if (error instanceof NoLinkedProjectError) {
|
|
2420
|
+
consola.info(
|
|
2421
|
+
"When you're ready to create a project, run `catalyst projects create` or re-run `catalyst channels update`."
|
|
2422
|
+
);
|
|
2423
|
+
process.exit(0);
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
throw error;
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
if (touchesCheckout) {
|
|
2430
|
+
const channel = channelId === void 0 ? await resolveChannel({ storeHash, accessToken, apiHost }) : { id: channelId, name: void 0 };
|
|
2431
|
+
const label = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
|
|
2432
|
+
if (options.checkoutUrl !== void 0) {
|
|
2433
|
+
const updated = await updateChannelCheckoutUrl(
|
|
2434
|
+
channel.id,
|
|
2435
|
+
options.checkoutUrl,
|
|
2436
|
+
storeHash,
|
|
2437
|
+
accessToken,
|
|
2438
|
+
apiHost
|
|
1961
2439
|
);
|
|
1962
|
-
|
|
1963
|
-
|
|
2440
|
+
consola.success(`Updated channel ${label} checkout URL to ${options.checkoutUrl}.`);
|
|
2441
|
+
reportChannelSite(updated);
|
|
2442
|
+
await warnOnCrossDomainCheckout(updated, { storeHash, accessToken, apiHost });
|
|
2443
|
+
} else {
|
|
2444
|
+
await deleteChannelCheckoutUrl(channel.id, storeHash, accessToken, apiHost);
|
|
2445
|
+
consola.success(
|
|
2446
|
+
`Removed the custom checkout URL from channel ${label}. Checkout now uses the shared checkout domain.`
|
|
2447
|
+
);
|
|
2448
|
+
const reverted = await getChannelSite(channel.id, storeHash, accessToken, apiHost);
|
|
2449
|
+
reportChannelSite(reverted);
|
|
2450
|
+
await warnOnCrossDomainCheckout(reverted, { storeHash, accessToken, apiHost });
|
|
1964
2451
|
}
|
|
1965
|
-
throw error;
|
|
1966
2452
|
}
|
|
1967
2453
|
process.exit(0);
|
|
1968
2454
|
});
|
|
@@ -1981,14 +2467,14 @@ Examples:
|
|
|
1981
2467
|
# Append extra environment variables to .env.local
|
|
1982
2468
|
$ catalyst channels link --channel-id 123 --env MY_FLAG=1`
|
|
1983
2469
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
|
|
1984
|
-
new
|
|
2470
|
+
new Option4("--channel-id <id>", "Link this channel directly, skipping the picker.").argParser(
|
|
1985
2471
|
parseChannelId
|
|
1986
2472
|
)
|
|
1987
2473
|
).option(
|
|
1988
2474
|
"--env <vars...>",
|
|
1989
2475
|
"Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable)."
|
|
1990
2476
|
).addOption(
|
|
1991
|
-
new
|
|
2477
|
+
new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
1992
2478
|
).action(async (options) => {
|
|
1993
2479
|
const config2 = getProjectConfig();
|
|
1994
2480
|
const apiHost = resolveApiHost(options, config2);
|
|
@@ -2059,11 +2545,11 @@ Examples:
|
|
|
2059
2545
|
).option(
|
|
2060
2546
|
"--additional-locales <locales...>",
|
|
2061
2547
|
"Additional storefront locales, max 4 (e.g. --additional-locales es fr). Skips the additional-languages prompt."
|
|
2062
|
-
).addOption(new
|
|
2548
|
+
).addOption(new Option4("--sample-data", "Install sample data on the new channel.")).addOption(new Option4("--no-sample-data", "Create the channel without sample data.")).option(
|
|
2063
2549
|
"--link",
|
|
2064
2550
|
"Link the new channel to this project \u2014 write its credentials to .env.local without prompting."
|
|
2065
2551
|
).addOption(
|
|
2066
|
-
new
|
|
2552
|
+
new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
2067
2553
|
).action(async (options) => {
|
|
2068
2554
|
if (options.additionalLocales && options.additionalLocales.length > 4) {
|
|
2069
2555
|
consola.error("You can only set up to 4 additional locales.");
|
|
@@ -2120,56 +2606,110 @@ Examples:
|
|
|
2120
2606
|
}
|
|
2121
2607
|
process.exit(0);
|
|
2122
2608
|
});
|
|
2123
|
-
var
|
|
2609
|
+
var CHECKOUT_LABEL_WIDTH = "Storefront".length;
|
|
2610
|
+
var row = (label, value) => ` ${label.padEnd(CHECKOUT_LABEL_WIDTH)} ${value}`;
|
|
2611
|
+
function reportChannelSite(site) {
|
|
2612
|
+
const canonical = findChannelSiteUrl(site, "canonical");
|
|
2613
|
+
const checkout = findChannelSiteUrl(site, "checkout");
|
|
2614
|
+
consola.log(row("Storefront", findChannelSiteUrl(site, "primary") ?? site.url));
|
|
2615
|
+
if (canonical) {
|
|
2616
|
+
consola.log(row("Canonical", canonical));
|
|
2617
|
+
}
|
|
2618
|
+
consola.log(row("Checkout", checkout ?? "(none)"));
|
|
2619
|
+
if (!site.isCheckoutUrlCustomized) {
|
|
2620
|
+
consola.info(
|
|
2621
|
+
"This channel has no checkout URL of its own, so checkout uses the default channel's primary URL."
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
async function resolveChannelTarget(options) {
|
|
2626
|
+
const config2 = getProjectConfig();
|
|
2627
|
+
const apiHost = resolveApiHost(options, config2);
|
|
2628
|
+
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
2629
|
+
await getTelemetry().identify(storeHash);
|
|
2630
|
+
const channel = await resolveChannel({
|
|
2631
|
+
storeHash,
|
|
2632
|
+
accessToken,
|
|
2633
|
+
apiHost,
|
|
2634
|
+
channelId: options.channelId,
|
|
2635
|
+
// Read-only: these commands report on a channel, so the picker must not
|
|
2636
|
+
// say "to update" — the shared default is written for `channels update`.
|
|
2637
|
+
message: "Select a channel."
|
|
2638
|
+
});
|
|
2639
|
+
return {
|
|
2640
|
+
config: config2,
|
|
2641
|
+
apiHost,
|
|
2642
|
+
storeHash,
|
|
2643
|
+
accessToken,
|
|
2644
|
+
channel,
|
|
2645
|
+
label: channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id)
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
var info = new Command3("info").configureHelp({ showGlobalOptions: true }).description("Show a channel's site URLs \u2014 storefront, canonical and checkout.").addHelpText(
|
|
2649
|
+
"after",
|
|
2650
|
+
`${CHECKOUT_URL_NOTES}
|
|
2651
|
+
|
|
2652
|
+
Examples:
|
|
2653
|
+
# Pick a channel interactively; the same as bare \`catalyst channels\`
|
|
2654
|
+
$ catalyst channels info
|
|
2655
|
+
|
|
2656
|
+
# Target a channel directly
|
|
2657
|
+
$ catalyst channels info --channel-id 123`
|
|
2658
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(
|
|
2659
|
+
new Option4(
|
|
2660
|
+
"--channel-id <id>",
|
|
2661
|
+
"Skip the channel prompt and target this channel directly."
|
|
2662
|
+
).argParser(parseChannelId)
|
|
2663
|
+
).action(async (options) => {
|
|
2664
|
+
const { apiHost, storeHash, accessToken, channel, label } = await resolveChannelTarget(options);
|
|
2665
|
+
consola.start("Fetching channel site...");
|
|
2666
|
+
const site = await getChannelSite(channel.id, storeHash, accessToken, apiHost);
|
|
2667
|
+
consola.success(`Channel ${label}:`);
|
|
2668
|
+
reportChannelSite(site);
|
|
2669
|
+
const report = await warnOnCrossDomainCheckout(site, { storeHash, accessToken, apiHost });
|
|
2670
|
+
if (report.crossDomain && report.storefrontOnManagedZone !== true) {
|
|
2671
|
+
const suggested = report.suggestion ?? "<domain>";
|
|
2672
|
+
if (!process.stdin.isTTY) {
|
|
2673
|
+
consola.info(
|
|
2674
|
+
`Set one with \`catalyst channels update --channel-id ${channel.id} --checkout-url ${suggested}\`.`
|
|
2675
|
+
);
|
|
2676
|
+
process.exit(0);
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
const shouldSet = await confirm3({
|
|
2680
|
+
message: "Set a checkout URL for this channel now?",
|
|
2681
|
+
default: true
|
|
2682
|
+
});
|
|
2683
|
+
if (shouldSet) {
|
|
2684
|
+
await runChannelCheckoutUrlFlow({
|
|
2685
|
+
storeHash,
|
|
2686
|
+
accessToken,
|
|
2687
|
+
apiHost,
|
|
2688
|
+
// Already chosen above; don't ask again.
|
|
2689
|
+
channelId: channel.id,
|
|
2690
|
+
channelName: channel.name,
|
|
2691
|
+
storefrontUrl: findChannelSiteUrl(site, "primary") ?? site.url
|
|
2692
|
+
});
|
|
2693
|
+
process.exit(0);
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
consola.info(
|
|
2697
|
+
`When you're ready, run \`catalyst channels update --channel-id ${channel.id} --checkout-url <domain>\`.`
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
process.exit(0);
|
|
2701
|
+
});
|
|
2702
|
+
var channels = new Command3("channels").alias("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(info, { isDefault: true }).addCommand(create).addCommand(link).addCommand(update);
|
|
2124
2703
|
|
|
2125
2704
|
// src/cli/commands/create.ts
|
|
2126
|
-
import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as
|
|
2127
|
-
import { input as
|
|
2705
|
+
import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as Option5 } from "@commander-js/extra-typings";
|
|
2706
|
+
import { input as input5, select as select5 } from "@inquirer/prompts";
|
|
2128
2707
|
import { execSync as execSync2 } from "child_process";
|
|
2129
2708
|
import { colorize as colorize6 } from "consola/utils";
|
|
2130
2709
|
import { pathExistsSync } from "fs-extra/esm";
|
|
2131
2710
|
import kebabCase from "lodash.kebabcase";
|
|
2132
2711
|
import { join as join11 } from "path";
|
|
2133
2712
|
|
|
2134
|
-
// src/cli/lib/detect-package-manager.ts
|
|
2135
|
-
import { access } from "fs/promises";
|
|
2136
|
-
import { join as join7 } from "path";
|
|
2137
|
-
import { detectPackageManager as detectFromDir } from "nypm";
|
|
2138
|
-
var PROJECT_LOCKFILES = [
|
|
2139
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2140
|
-
["yarn.lock", "yarn"],
|
|
2141
|
-
["bun.lock", "bun"],
|
|
2142
|
-
["bun.lockb", "bun"],
|
|
2143
|
-
["package-lock.json", "npm"]
|
|
2144
|
-
];
|
|
2145
|
-
var detectLockfileManager = async (dir) => {
|
|
2146
|
-
const found = await Promise.all(
|
|
2147
|
-
PROJECT_LOCKFILES.map(
|
|
2148
|
-
async ([file, manager]) => access(join7(dir, file)).then(() => manager).catch(() => null)
|
|
2149
|
-
)
|
|
2150
|
-
);
|
|
2151
|
-
return found.find((manager) => manager !== null) ?? null;
|
|
2152
|
-
};
|
|
2153
|
-
var detectPackageManager = () => {
|
|
2154
|
-
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
2155
|
-
const name = userAgent.split(" ")[0]?.split("/")[0];
|
|
2156
|
-
if (name === "pnpm" || name === "yarn" || name === "bun") {
|
|
2157
|
-
return name;
|
|
2158
|
-
}
|
|
2159
|
-
if (process.versions.bun) {
|
|
2160
|
-
return "bun";
|
|
2161
|
-
}
|
|
2162
|
-
return "npm";
|
|
2163
|
-
};
|
|
2164
|
-
var detectProjectPackageManager = async (projectDir) => {
|
|
2165
|
-
const detected = await detectFromDir(projectDir);
|
|
2166
|
-
const name = detected?.name;
|
|
2167
|
-
if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") {
|
|
2168
|
-
return name;
|
|
2169
|
-
}
|
|
2170
|
-
return "npm";
|
|
2171
|
-
};
|
|
2172
|
-
|
|
2173
2713
|
// src/cli/lib/extract-catalyst.ts
|
|
2174
2714
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
2175
2715
|
import { Readable } from "stream";
|
|
@@ -2239,43 +2779,6 @@ var initGitRepo = (projectDir) => {
|
|
|
2239
2779
|
}
|
|
2240
2780
|
};
|
|
2241
2781
|
|
|
2242
|
-
// src/cli/lib/install-dependencies.ts
|
|
2243
|
-
import { installDependencies as installDeps } from "nypm";
|
|
2244
|
-
import yoctoSpinner2 from "yocto-spinner";
|
|
2245
|
-
var restoreEnv = (key, previous) => {
|
|
2246
|
-
if (previous === void 0) {
|
|
2247
|
-
Reflect.deleteProperty(process.env, key);
|
|
2248
|
-
} else {
|
|
2249
|
-
process.env[key] = previous;
|
|
2250
|
-
}
|
|
2251
|
-
};
|
|
2252
|
-
var withQuietInstallEnv = async (run2) => {
|
|
2253
|
-
var _a, _b;
|
|
2254
|
-
const prevNodeNoWarnings = process.env.NODE_NO_WARNINGS;
|
|
2255
|
-
const prevDownloadPrompt = process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT;
|
|
2256
|
-
(_a = process.env).NODE_NO_WARNINGS ?? (_a.NODE_NO_WARNINGS = "1");
|
|
2257
|
-
(_b = process.env).COREPACK_ENABLE_DOWNLOAD_PROMPT ?? (_b.COREPACK_ENABLE_DOWNLOAD_PROMPT = "0");
|
|
2258
|
-
try {
|
|
2259
|
-
return await run2();
|
|
2260
|
-
} finally {
|
|
2261
|
-
restoreEnv("NODE_NO_WARNINGS", prevNodeNoWarnings);
|
|
2262
|
-
restoreEnv("COREPACK_ENABLE_DOWNLOAD_PROMPT", prevDownloadPrompt);
|
|
2263
|
-
}
|
|
2264
|
-
};
|
|
2265
|
-
var installDependencies = async (projectDir, packageManager = "pnpm") => {
|
|
2266
|
-
const spinner = yoctoSpinner2().start(
|
|
2267
|
-
`Installing dependencies with ${packageManager}. This could take a minute...`
|
|
2268
|
-
);
|
|
2269
|
-
try {
|
|
2270
|
-
await withQuietInstallEnv(() => installDeps({ cwd: projectDir, silent: true, packageManager }));
|
|
2271
|
-
spinner.success("Dependencies installed successfully.");
|
|
2272
|
-
} catch (error) {
|
|
2273
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
2274
|
-
spinner.error(`Failed to install dependencies: ${message}`);
|
|
2275
|
-
throw error;
|
|
2276
|
-
}
|
|
2277
|
-
};
|
|
2278
|
-
|
|
2279
2782
|
// src/cli/lib/rewrite-core-package.ts
|
|
2280
2783
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
2281
2784
|
import { join as join9 } from "path";
|
|
@@ -2437,7 +2940,7 @@ async function setupProject(options) {
|
|
|
2437
2940
|
projectDir = targetDir;
|
|
2438
2941
|
return true;
|
|
2439
2942
|
};
|
|
2440
|
-
await
|
|
2943
|
+
await input5({
|
|
2441
2944
|
message: "What do you want to name your project directory?",
|
|
2442
2945
|
default: "my-catalyst-app",
|
|
2443
2946
|
validate: validateProjectName
|
|
@@ -2476,7 +2979,7 @@ Examples:
|
|
|
2476
2979
|
"Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable).",
|
|
2477
2980
|
parseEnvFlag
|
|
2478
2981
|
).addOption(
|
|
2479
|
-
new
|
|
2982
|
+
new Option5(
|
|
2480
2983
|
"--hosting <mode>",
|
|
2481
2984
|
'Hosting mode: "self-hosted" (default) or "commerce" to set up Commerce Hosting at create time. When omitted, scaffolding is hosting-agnostic; run `catalyst deploy` later to opt in.'
|
|
2482
2985
|
).choices(["self-hosted", "commerce"])
|
|
@@ -2484,11 +2987,11 @@ Examples:
|
|
|
2484
2987
|
"--use-existing",
|
|
2485
2988
|
"Only used with --hosting commerce and --project-name. When the named project already exists on the store, reuse it instead of prompting. Has no effect without --hosting commerce."
|
|
2486
2989
|
).addOption(
|
|
2487
|
-
new
|
|
2990
|
+
new Option5("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
|
|
2488
2991
|
).addOption(
|
|
2489
|
-
new
|
|
2992
|
+
new Option5("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
|
|
2490
2993
|
).addOption(
|
|
2491
|
-
new
|
|
2994
|
+
new Option5("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
2492
2995
|
).action(async (options) => {
|
|
2493
2996
|
const { ghRef, repository } = options;
|
|
2494
2997
|
if (options.useExisting && options.hosting !== "commerce") {
|
|
@@ -2582,7 +3085,6 @@ Examples:
|
|
|
2582
3085
|
if (useCommerceHosting && accessToken) {
|
|
2583
3086
|
envVars.BIGCOMMERCE_ACCESS_TOKEN = accessToken;
|
|
2584
3087
|
}
|
|
2585
|
-
if (accessToken) envVars.CATALYST_ACCESS_TOKEN = accessToken;
|
|
2586
3088
|
let commerceHostingProject;
|
|
2587
3089
|
if (useCommerceHosting && storeHash && accessToken) {
|
|
2588
3090
|
const apiHost = `api.${options.bigcommerceHostname}`;
|
|
@@ -2645,7 +3147,7 @@ ${steps.map((step) => ` ${colorize6("yellow", step)}`).join("\n")}`
|
|
|
2645
3147
|
});
|
|
2646
3148
|
|
|
2647
3149
|
// src/cli/commands/debug.ts
|
|
2648
|
-
import { Command as Command5, Option as
|
|
3150
|
+
import { Command as Command5, Option as Option6 } from "commander";
|
|
2649
3151
|
|
|
2650
3152
|
// src/cli/lib/collect-diagnostics.ts
|
|
2651
3153
|
import { parse } from "dotenv";
|
|
@@ -2896,7 +3398,7 @@ Examples:
|
|
|
2896
3398
|
|
|
2897
3399
|
# Print machine-readable JSON (useful for copy/paste or piping)
|
|
2898
3400
|
$ catalyst debug --json`
|
|
2899
|
-
).addOption(new
|
|
3401
|
+
).addOption(new Option6("--json", "Output the report as JSON.")).action((options) => {
|
|
2900
3402
|
const diagnostics = collectDiagnostics();
|
|
2901
3403
|
if (options.json) {
|
|
2902
3404
|
consola.log(JSON.stringify(diagnostics, null, 2));
|
|
@@ -2906,9 +3408,9 @@ Examples:
|
|
|
2906
3408
|
});
|
|
2907
3409
|
|
|
2908
3410
|
// src/cli/commands/deploy.ts
|
|
2909
|
-
import { confirm as
|
|
3411
|
+
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
2910
3412
|
import AdmZip from "adm-zip";
|
|
2911
|
-
import { Command as Command6, Option as
|
|
3413
|
+
import { Command as Command6, Option as Option7 } from "commander";
|
|
2912
3414
|
import { colorize as colorize7 } from "consola/utils";
|
|
2913
3415
|
import { access as access2, readdir, readFile } from "fs/promises";
|
|
2914
3416
|
import { join as join13 } from "path";
|
|
@@ -3174,8 +3676,11 @@ Example:
|
|
|
3174
3676
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option(
|
|
3175
3677
|
"--update-site-url",
|
|
3176
3678
|
"After a successful deploy, prompt to update a channel's site URL to the new hostname."
|
|
3679
|
+
).option(
|
|
3680
|
+
"--update-checkout-url",
|
|
3681
|
+
"After a successful deploy, prompt to update a channel's checkout URL."
|
|
3177
3682
|
).addOption(
|
|
3178
|
-
new
|
|
3683
|
+
new Option7(
|
|
3179
3684
|
"--secret <value>",
|
|
3180
3685
|
"Secret to set for this deployment (repeatable). Overrides a stored value for the same key. Format: --secret KEY=VALUE"
|
|
3181
3686
|
).argParser((value, previous = []) => {
|
|
@@ -3185,7 +3690,7 @@ Example:
|
|
|
3185
3690
|
"--prebuilt",
|
|
3186
3691
|
"Skip the build step. Requires .bigcommerce/dist/ to already contain build output."
|
|
3187
3692
|
).addOption(
|
|
3188
|
-
new
|
|
3693
|
+
new Option7(
|
|
3189
3694
|
"--wrangler-version <version>",
|
|
3190
3695
|
`Wrangler version or dist-tag to build with. Ignored with --prebuilt. Defaults to ${WRANGLER_VERSION}.`
|
|
3191
3696
|
).argParser(parseWranglerVersion)
|
|
@@ -3235,7 +3740,7 @@ Example:
|
|
|
3235
3740
|
consola.success(`Linked project "${selected.name}".`);
|
|
3236
3741
|
}
|
|
3237
3742
|
if (!getProjectState().isTransformed) {
|
|
3238
|
-
const shouldSetup = await
|
|
3743
|
+
const shouldSetup = await confirm4({
|
|
3239
3744
|
message: "Your project is not yet set up for Commerce Hosting deployments. Would you like to run the Commerce Hosting setup now?",
|
|
3240
3745
|
default: true
|
|
3241
3746
|
});
|
|
@@ -3250,6 +3755,12 @@ Example:
|
|
|
3250
3755
|
await installDependencies(projectDir, packageManager);
|
|
3251
3756
|
} else {
|
|
3252
3757
|
await cleanupCloudflareIncompatibilities(process.cwd());
|
|
3758
|
+
if (options.prebuilt) {
|
|
3759
|
+
const projectDir = process.cwd();
|
|
3760
|
+
await reconcileOpenNextVersion(projectDir, await detectProjectPackageManager(projectDir), {
|
|
3761
|
+
canUpgrade: false
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3253
3764
|
}
|
|
3254
3765
|
if (options.prebuilt) {
|
|
3255
3766
|
const distDir = join13(process.cwd(), ".bigcommerce", "dist");
|
|
@@ -3300,30 +3811,45 @@ Example:
|
|
|
3300
3811
|
accessToken,
|
|
3301
3812
|
apiHost
|
|
3302
3813
|
);
|
|
3303
|
-
|
|
3304
|
-
return;
|
|
3305
|
-
}
|
|
3306
|
-
try {
|
|
3307
|
-
await runChannelSiteUrlFlow({
|
|
3308
|
-
storeHash,
|
|
3309
|
-
accessToken,
|
|
3310
|
-
apiHost,
|
|
3311
|
-
projectUuid,
|
|
3312
|
-
preferHostname: deploymentHostname
|
|
3313
|
-
});
|
|
3314
|
-
} catch (error) {
|
|
3814
|
+
const warnChannelFlowFailed = (what, error) => {
|
|
3315
3815
|
consola.warn(
|
|
3316
|
-
`Failed to update channel
|
|
3816
|
+
`Failed to update channel ${what}: ${error instanceof Error ? error.message : String(error)}`
|
|
3317
3817
|
);
|
|
3318
3818
|
consola.info(
|
|
3319
3819
|
"Update it manually in the control panel, or re-run `catalyst auth login` if the token is missing the store_channel_settings scope."
|
|
3320
3820
|
);
|
|
3821
|
+
};
|
|
3822
|
+
let resolvedChannelId;
|
|
3823
|
+
if (options.updateSiteUrl) {
|
|
3824
|
+
try {
|
|
3825
|
+
({ channelId: resolvedChannelId } = await runChannelSiteUrlFlow({
|
|
3826
|
+
storeHash,
|
|
3827
|
+
accessToken,
|
|
3828
|
+
apiHost,
|
|
3829
|
+
projectUuid,
|
|
3830
|
+
preferHostname: deploymentHostname
|
|
3831
|
+
}));
|
|
3832
|
+
} catch (error) {
|
|
3833
|
+
warnChannelFlowFailed("site URL", error);
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
if (options.updateCheckoutUrl) {
|
|
3837
|
+
try {
|
|
3838
|
+
await runChannelCheckoutUrlFlow({
|
|
3839
|
+
storeHash,
|
|
3840
|
+
accessToken,
|
|
3841
|
+
apiHost,
|
|
3842
|
+
channelId: resolvedChannelId
|
|
3843
|
+
});
|
|
3844
|
+
} catch (error) {
|
|
3845
|
+
warnChannelFlowFailed("checkout URL", error);
|
|
3846
|
+
}
|
|
3321
3847
|
}
|
|
3322
3848
|
});
|
|
3323
3849
|
|
|
3324
3850
|
// src/cli/commands/domains.ts
|
|
3325
|
-
import { confirm as
|
|
3326
|
-
import { Command as Command7, Option as
|
|
3851
|
+
import { confirm as confirm5, select as select6 } from "@inquirer/prompts";
|
|
3852
|
+
import { Command as Command7, Option as Option8 } from "commander";
|
|
3327
3853
|
import { colorize as colorize9 } from "consola/utils";
|
|
3328
3854
|
|
|
3329
3855
|
// src/cli/lib/domains.ts
|
|
@@ -3652,7 +4178,7 @@ async function createDomain(domain, projectUuid, storeHash, accessToken, apiHost
|
|
|
3652
4178
|
const result = await response.json();
|
|
3653
4179
|
return createdDomainResponseSchema.parse(result).data;
|
|
3654
4180
|
}
|
|
3655
|
-
async function
|
|
4181
|
+
async function getDomain2(domain, projectUuid, storeHash, accessToken, apiHost) {
|
|
3656
4182
|
const response = await fetch(domainUrl(storeHash, projectUuid, domain, apiHost), {
|
|
3657
4183
|
method: "GET",
|
|
3658
4184
|
headers: authHeaders3(accessToken)
|
|
@@ -3789,7 +4315,7 @@ function formatPointingRecords(records) {
|
|
|
3789
4315
|
if (rows.length === 0) {
|
|
3790
4316
|
return null;
|
|
3791
4317
|
}
|
|
3792
|
-
return rows.map((
|
|
4318
|
+
return rows.map((row2) => ` ${row2}`).join("\n");
|
|
3793
4319
|
}
|
|
3794
4320
|
function formatOwnershipVerification(record) {
|
|
3795
4321
|
const lines = [
|
|
@@ -3812,10 +4338,10 @@ async function waitForDomainVerification({
|
|
|
3812
4338
|
timeoutMs = WAIT_TIMEOUT_MS
|
|
3813
4339
|
}) {
|
|
3814
4340
|
const startedAt = Date.now();
|
|
3815
|
-
let current = await
|
|
4341
|
+
let current = await getDomain2(domain, projectUuid, storeHash, accessToken, apiHost);
|
|
3816
4342
|
while (current.verification_status === "pending" && Date.now() - startedAt < timeoutMs) {
|
|
3817
4343
|
await sleep(intervalMs);
|
|
3818
|
-
current = await
|
|
4344
|
+
current = await getDomain2(domain, projectUuid, storeHash, accessToken, apiHost);
|
|
3819
4345
|
}
|
|
3820
4346
|
return current;
|
|
3821
4347
|
}
|
|
@@ -3895,7 +4421,7 @@ Examples:
|
|
|
3895
4421
|
# Show pending domains only
|
|
3896
4422
|
$ catalyst domains list --status pending`
|
|
3897
4423
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--domain <domain>", "Only show a specific domain.").addOption(
|
|
3898
|
-
new
|
|
4424
|
+
new Option8("--status <status>", "Filter by verification status.").choices(
|
|
3899
4425
|
DOMAIN_STATUS_FILTERS
|
|
3900
4426
|
)
|
|
3901
4427
|
).action(async (options) => {
|
|
@@ -3933,7 +4459,7 @@ Examples:
|
|
|
3933
4459
|
const context = resolveDomainCommandContext(options);
|
|
3934
4460
|
await getTelemetry().identify(context.storeHash);
|
|
3935
4461
|
consola.start(`Fetching status for ${domain}...`);
|
|
3936
|
-
let result = await
|
|
4462
|
+
let result = await getDomain2(
|
|
3937
4463
|
domain,
|
|
3938
4464
|
context.projectUuid,
|
|
3939
4465
|
context.storeHash,
|
|
@@ -3984,7 +4510,7 @@ Examples:
|
|
|
3984
4510
|
throw error;
|
|
3985
4511
|
}
|
|
3986
4512
|
consola.success(`Domain ${domain} claimed.`);
|
|
3987
|
-
let result = await
|
|
4513
|
+
let result = await getDomain2(
|
|
3988
4514
|
domain,
|
|
3989
4515
|
context.projectUuid,
|
|
3990
4516
|
context.storeHash,
|
|
@@ -4064,7 +4590,7 @@ Examples:
|
|
|
4064
4590
|
context.apiHost
|
|
4065
4591
|
);
|
|
4066
4592
|
consola.success(`Domain ${domain} transferred.`);
|
|
4067
|
-
let result = await
|
|
4593
|
+
let result = await getDomain2(
|
|
4068
4594
|
domain,
|
|
4069
4595
|
newProjectUuid,
|
|
4070
4596
|
context.storeHash,
|
|
@@ -4096,7 +4622,7 @@ Examples:
|
|
|
4096
4622
|
const context = resolveDomainCommandContext(options);
|
|
4097
4623
|
await getTelemetry().identify(context.storeHash);
|
|
4098
4624
|
consola.start(`Fetching status for ${domain}...`);
|
|
4099
|
-
const current = await
|
|
4625
|
+
const current = await getDomain2(
|
|
4100
4626
|
domain,
|
|
4101
4627
|
context.projectUuid,
|
|
4102
4628
|
context.storeHash,
|
|
@@ -4104,7 +4630,7 @@ Examples:
|
|
|
4104
4630
|
context.apiHost
|
|
4105
4631
|
);
|
|
4106
4632
|
if (current.verification_status === "verified" && !options.force) {
|
|
4107
|
-
const confirmed = await
|
|
4633
|
+
const confirmed = await confirm5({
|
|
4108
4634
|
message: `Remove active domain ${current.domain}? Traffic may stop routing to this project.`,
|
|
4109
4635
|
default: false
|
|
4110
4636
|
});
|
|
@@ -4200,7 +4726,7 @@ var env = new Command8("env").configureHelp({ showGlobalOptions: true }).descrip
|
|
|
4200
4726
|
).addCommand(add2).addCommand(remove2).addCommand(list2);
|
|
4201
4727
|
|
|
4202
4728
|
// src/cli/commands/logs.ts
|
|
4203
|
-
import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as
|
|
4729
|
+
import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as Option9 } from "commander";
|
|
4204
4730
|
import { colorize as colorize10 } from "consola/utils";
|
|
4205
4731
|
import { z as z16 } from "zod";
|
|
4206
4732
|
var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
|
|
@@ -4417,7 +4943,7 @@ Examples:
|
|
|
4417
4943
|
# Tail logs as raw JSON (useful for piping to other tools)
|
|
4418
4944
|
$ catalyst logs tail --format json`
|
|
4419
4945
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
4420
|
-
new
|
|
4946
|
+
new Option9("--format <format>", "Output format for log events.").choices(["json", "pretty", "default", "short", "request"]).default("default")
|
|
4421
4947
|
).action(async (options) => {
|
|
4422
4948
|
try {
|
|
4423
4949
|
const config2 = getProjectConfig();
|
|
@@ -4518,7 +5044,7 @@ Examples:
|
|
|
4518
5044
|
# Raw JSON (NDJSON) for piping to other tools
|
|
4519
5045
|
$ catalyst logs query --since 2d --format json`
|
|
4520
5046
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
4521
|
-
new
|
|
5047
|
+
new Option9(
|
|
4522
5048
|
"--since <duration>",
|
|
4523
5049
|
"Relative window ending at --end (default: now), e.g. 30m, 6h, 2d (units: s, m, h, d)."
|
|
4524
5050
|
).conflicts("start")
|
|
@@ -4526,28 +5052,28 @@ Examples:
|
|
|
4526
5052
|
"--end <time>",
|
|
4527
5053
|
"Window end: ISO-8601 timestamp or Unix epoch (seconds). Defaults to now. The window must not exceed 7 days."
|
|
4528
5054
|
).option("--method <method>", "Filter by request HTTP method (case-insensitive).").addOption(
|
|
4529
|
-
new
|
|
5055
|
+
new Option9("--status-code <code>", "Filter by response status code (100-599).").argParser(
|
|
4530
5056
|
parseIntInRange("--status-code", 100, 599)
|
|
4531
5057
|
)
|
|
4532
5058
|
).option("--url-like <substring>", "Filter by URL substring (case-sensitive).").addOption(
|
|
4533
|
-
new
|
|
5059
|
+
new Option9("--level-min <level>", "Minimum log level.").choices(Array.from(LOG_LEVELS))
|
|
4534
5060
|
).addOption(
|
|
4535
|
-
new
|
|
5061
|
+
new Option9(
|
|
4536
5062
|
"--limit <count>",
|
|
4537
5063
|
"Maximum entries per page (1-500). Defaults to the API default (100)."
|
|
4538
5064
|
).argParser(parseIntInRange("--limit", 1, 500))
|
|
4539
5065
|
).addOption(
|
|
4540
|
-
new
|
|
5066
|
+
new Option9(
|
|
4541
5067
|
"--after <cursor>",
|
|
4542
5068
|
"Return the page after (older than) this cursor, from a previous page's end_cursor."
|
|
4543
5069
|
).conflicts("before")
|
|
4544
5070
|
).addOption(
|
|
4545
|
-
new
|
|
5071
|
+
new Option9(
|
|
4546
5072
|
"--before <cursor>",
|
|
4547
5073
|
"Return the page before (newer than) this cursor, from a previous page's start_cursor."
|
|
4548
5074
|
)
|
|
4549
5075
|
).addOption(
|
|
4550
|
-
new
|
|
5076
|
+
new Option9("--format <format>", "Output format for log entries.").choices(["json", "pretty", "default", "short", "request"]).default("default")
|
|
4551
5077
|
).action(async (options) => {
|
|
4552
5078
|
try {
|
|
4553
5079
|
const config2 = getProjectConfig();
|
|
@@ -4608,7 +5134,7 @@ Examples:
|
|
|
4608
5134
|
var logs = new Command9("logs").alias("log").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
|
|
4609
5135
|
|
|
4610
5136
|
// src/cli/commands/projects.ts
|
|
4611
|
-
import { confirm as
|
|
5137
|
+
import { confirm as confirm6, input as input6, select as select7 } from "@inquirer/prompts";
|
|
4612
5138
|
import { Command as Command10 } from "commander";
|
|
4613
5139
|
import { colorize as colorize11 } from "consola/utils";
|
|
4614
5140
|
async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
@@ -4617,7 +5143,7 @@ async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
|
4617
5143
|
await cleanupCloudflareIncompatibilities(projectDir);
|
|
4618
5144
|
return;
|
|
4619
5145
|
}
|
|
4620
|
-
const shouldSetup = await
|
|
5146
|
+
const shouldSetup = await confirm6({
|
|
4621
5147
|
message: "Your project has been linked, but is not fully set up for Commerce Hosting deployments yet. Would you like to run the setup now?",
|
|
4622
5148
|
default: true
|
|
4623
5149
|
});
|
|
@@ -4696,7 +5222,7 @@ Example:
|
|
|
4696
5222
|
}
|
|
4697
5223
|
}
|
|
4698
5224
|
await getTelemetry().identify(storeHash);
|
|
4699
|
-
const newProjectName = await
|
|
5225
|
+
const newProjectName = await input6({ message: "Enter a name for the new project:" });
|
|
4700
5226
|
const data = await createProject(newProjectName, storeHash, accessToken, apiHost);
|
|
4701
5227
|
consola.success(`Project "${data.name}" created successfully.`);
|
|
4702
5228
|
consola.start("Writing project UUID to .bigcommerce/project.json...");
|
|
@@ -4813,7 +5339,7 @@ Examples:
|
|
|
4813
5339
|
}
|
|
4814
5340
|
if (!options.force) {
|
|
4815
5341
|
const label = targetName ? `"${targetName}" (${targetUuid})` : targetUuid;
|
|
4816
|
-
const confirmed = await
|
|
5342
|
+
const confirmed = await confirm6({
|
|
4817
5343
|
message: `Are you sure you want to delete project ${label}? This action is irreversible and will permanently destroy the project and all of its data.`,
|
|
4818
5344
|
default: false
|
|
4819
5345
|
});
|
|
@@ -4890,7 +5416,7 @@ Example:
|
|
|
4890
5416
|
});
|
|
4891
5417
|
|
|
4892
5418
|
// src/cli/commands/telemetry.ts
|
|
4893
|
-
import { Argument, Command as Command12, Option as
|
|
5419
|
+
import { Argument, Command as Command12, Option as Option10 } from "commander";
|
|
4894
5420
|
import { colorize as colorize12 } from "consola/utils";
|
|
4895
5421
|
var telemetryService = getTelemetry();
|
|
4896
5422
|
var isEnabled = telemetryService.isEnabled();
|
|
@@ -4908,7 +5434,7 @@ Examples:
|
|
|
4908
5434
|
|
|
4909
5435
|
# Disable telemetry collection
|
|
4910
5436
|
$ catalyst telemetry --disable`
|
|
4911
|
-
).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new
|
|
5437
|
+
).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new Option10("--enable", `Enables CLI telemetry collection.`).conflicts("disable")).option("--disable", `Disables CLI telemetry collection.`).action((arg, options) => {
|
|
4912
5438
|
if (options.enable || arg === "enable") {
|
|
4913
5439
|
telemetryService.setEnabled(true);
|
|
4914
5440
|
isEnabled = true;
|
|
@@ -4935,8 +5461,8 @@ Examples:
|
|
|
4935
5461
|
});
|
|
4936
5462
|
|
|
4937
5463
|
// src/cli/commands/upgrade.ts
|
|
4938
|
-
import { Command as Command13, Option as
|
|
4939
|
-
import { confirm as
|
|
5464
|
+
import { Command as Command13, Option as Option11 } from "@commander-js/extra-typings";
|
|
5465
|
+
import { confirm as confirm7 } from "@inquirer/prompts";
|
|
4940
5466
|
import { execa as execa4 } from "execa";
|
|
4941
5467
|
import {
|
|
4942
5468
|
access as access3,
|
|
@@ -4952,7 +5478,7 @@ import {
|
|
|
4952
5478
|
} from "fs/promises";
|
|
4953
5479
|
import { homedir, tmpdir } from "os";
|
|
4954
5480
|
import { dirname as dirname5, join as join15, relative as relative2 } from "path";
|
|
4955
|
-
import { minVersion, satisfies, lt as
|
|
5481
|
+
import { minVersion as minVersion2, satisfies as satisfies2, lt as semverLt2, validRange as validRange2, valid as validSemver2 } from "semver";
|
|
4956
5482
|
import yoctoSpinner4 from "yocto-spinner";
|
|
4957
5483
|
import { z as z17 } from "zod";
|
|
4958
5484
|
var CorePackageJson = z17.object({
|
|
@@ -5087,15 +5613,81 @@ async function normalizeWorkspaceDeps(ourPkgRaw, baseDir, theirsDir, baseVersion
|
|
|
5087
5613
|
var migrateWorkspaceDeps = (raw, findings) => findings.reduce((acc, { name, version: version2 }) => rewriteWorkspaceSpecifier(acc, name, version2), raw);
|
|
5088
5614
|
async function findStaleCli(pkgRaw) {
|
|
5089
5615
|
const range = readDepSpecifiers(pkgRaw)[CATALYST_CLI_PACKAGE];
|
|
5090
|
-
if (range === void 0 || !
|
|
5616
|
+
if (range === void 0 || !validRange2(range)) return null;
|
|
5091
5617
|
const body = await fetch(`https://registry.npmjs.org/${CATALYST_CLI_PACKAGE}/latest`, {
|
|
5092
5618
|
// A hanging registry would otherwise stall the CLI indefinitely.
|
|
5093
5619
|
signal: AbortSignal.timeout(5e3)
|
|
5094
5620
|
}).then((res) => res.ok ? res.json() : null).catch(() => null);
|
|
5095
5621
|
const latest = z17.object({ version: z17.string() }).safeParse(body).data?.version;
|
|
5096
|
-
if (latest === void 0 || !validSemver2(latest) ||
|
|
5097
|
-
const current =
|
|
5098
|
-
return current !== void 0 &&
|
|
5622
|
+
if (latest === void 0 || !validSemver2(latest) || satisfies2(latest, range)) return null;
|
|
5623
|
+
const current = minVersion2(range)?.version;
|
|
5624
|
+
return current !== void 0 && semverLt2(current, latest) ? { current, latest } : null;
|
|
5625
|
+
}
|
|
5626
|
+
var CLI_MANAGED_SCRIPTS = ["build", "start", "deploy"];
|
|
5627
|
+
var ScriptsPackageJson = z17.looseObject({
|
|
5628
|
+
scripts: z17.record(z17.string(), z17.string()).optional()
|
|
5629
|
+
});
|
|
5630
|
+
var readScripts = (raw) => {
|
|
5631
|
+
try {
|
|
5632
|
+
return ScriptsPackageJson.parse(JSON.parse(raw)).scripts ?? {};
|
|
5633
|
+
} catch {
|
|
5634
|
+
return {};
|
|
5635
|
+
}
|
|
5636
|
+
};
|
|
5637
|
+
var SCRIPTS_BLOCK = /"scripts"\s*:\s*\{(?:[^{}"]|"(?:[^"\\]|\\.)*")*\}/;
|
|
5638
|
+
var rewriteScriptCommand = (raw, name, command) => {
|
|
5639
|
+
const block = SCRIPTS_BLOCK.exec(raw);
|
|
5640
|
+
if (!block) return raw;
|
|
5641
|
+
const splice = (replacement) => raw.slice(0, block.index) + replacement + raw.slice(block.index + block[0].length);
|
|
5642
|
+
const updated = block[0].replace(
|
|
5643
|
+
new RegExp(`("${escapeRegExp(name)}"\\s*:\\s*)"(?:[^"\\\\]|\\\\.)*"`),
|
|
5644
|
+
(_match, key) => `${key}${JSON.stringify(command)}`
|
|
5645
|
+
);
|
|
5646
|
+
if (updated !== block[0]) return splice(updated);
|
|
5647
|
+
const closeIdx = block[0].length - 1;
|
|
5648
|
+
const head = block[0].slice(0, closeIdx).replace(/\s+$/, "");
|
|
5649
|
+
const closeIndent = /\n([ \t]*)$/.exec(block[0].slice(0, closeIdx))?.[1] ?? "";
|
|
5650
|
+
const entryIndent = /\n([ \t]*)"/.exec(block[0])?.[1] ?? `${closeIndent} `;
|
|
5651
|
+
const separator = head.endsWith("{") ? "" : ",";
|
|
5652
|
+
return splice(
|
|
5653
|
+
`${head}${separator}
|
|
5654
|
+
${entryIndent}${JSON.stringify(name)}: ${JSON.stringify(command)}
|
|
5655
|
+
${closeIndent}}`
|
|
5656
|
+
);
|
|
5657
|
+
};
|
|
5658
|
+
async function normalizeManagedScripts(ourPkgRaw, baseDir, theirsDir) {
|
|
5659
|
+
const basePath = join15(baseDir, "package.json");
|
|
5660
|
+
const theirsPath = join15(theirsDir, "package.json");
|
|
5661
|
+
const [originalBase, originalTheirs] = await Promise.all([
|
|
5662
|
+
readFile2(basePath, "utf-8").catch(() => null),
|
|
5663
|
+
readFile2(theirsPath, "utf-8").catch(() => null)
|
|
5664
|
+
]);
|
|
5665
|
+
if (originalBase === null || originalTheirs === null) return { pinned: [], upstreamChanged: [] };
|
|
5666
|
+
const ourScripts = readScripts(ourPkgRaw);
|
|
5667
|
+
const baseScripts = readScripts(originalBase);
|
|
5668
|
+
const theirsScripts = readScripts(originalTheirs);
|
|
5669
|
+
const decisions = CLI_MANAGED_SCRIPTS.flatMap((name) => {
|
|
5670
|
+
const ours = ourScripts[name];
|
|
5671
|
+
return ours !== void 0 && ours !== baseScripts[name] ? [{ name, ours }] : [];
|
|
5672
|
+
});
|
|
5673
|
+
const upstreamChanged = decisions.flatMap(({ name }) => {
|
|
5674
|
+
const base = baseScripts[name] ?? null;
|
|
5675
|
+
const theirs = theirsScripts[name] ?? null;
|
|
5676
|
+
return base !== theirs ? [{ name, base, theirs }] : [];
|
|
5677
|
+
});
|
|
5678
|
+
const baseRaw = decisions.reduce(
|
|
5679
|
+
(acc, { name, ours }) => rewriteScriptCommand(acc, name, ours),
|
|
5680
|
+
originalBase
|
|
5681
|
+
);
|
|
5682
|
+
const theirsRaw = decisions.reduce(
|
|
5683
|
+
(acc, { name, ours }) => rewriteScriptCommand(acc, name, ours),
|
|
5684
|
+
originalTheirs
|
|
5685
|
+
);
|
|
5686
|
+
await Promise.all([
|
|
5687
|
+
baseRaw === originalBase ? null : writeFile2(basePath, baseRaw),
|
|
5688
|
+
theirsRaw === originalTheirs ? null : writeFile2(theirsPath, theirsRaw)
|
|
5689
|
+
]);
|
|
5690
|
+
return { pinned: decisions.map(({ name }) => name), upstreamChanged };
|
|
5099
5691
|
}
|
|
5100
5692
|
async function mergeFile(oursPath, basePath, theirsPath) {
|
|
5101
5693
|
const merged = await execa4(
|
|
@@ -5450,7 +6042,7 @@ async function resolveBaseRef(project, fromOption, assumeYes) {
|
|
|
5450
6042
|
const guess = `${family}@${project.pkg.version}`;
|
|
5451
6043
|
consola.warn(`No \`catalyst.ref\` found in package.json. Inferred base: ${guess}`);
|
|
5452
6044
|
if (assumeYes) return guess;
|
|
5453
|
-
const ok = await
|
|
6045
|
+
const ok = await confirm7({ message: `Upgrade from ${guess}?`, default: true }).catch(() => false);
|
|
5454
6046
|
if (!ok) {
|
|
5455
6047
|
consola.info("Re-run with `--from <ref>` to set the base version explicitly.");
|
|
5456
6048
|
return null;
|
|
@@ -5468,6 +6060,20 @@ ${rows}
|
|
|
5468
6060
|
They resolve to your local packages/ copies, which \`catalyst upgrade\` does not update \u2014 so they never receive upgrades.`
|
|
5469
6061
|
);
|
|
5470
6062
|
}
|
|
6063
|
+
function printManagedScriptFindings(findings) {
|
|
6064
|
+
if (findings.length === 0) return;
|
|
6065
|
+
const pad = Math.max(...findings.map((finding) => finding.name.length));
|
|
6066
|
+
const show = (command) => command ?? "(absent)";
|
|
6067
|
+
const rows = findings.map(
|
|
6068
|
+
(finding) => ` ${finding.name.padEnd(pad)} ${show(finding.base)} \u2192 ${show(finding.theirs)}`
|
|
6069
|
+
).join("\n");
|
|
6070
|
+
consola.warn(
|
|
6071
|
+
`Core's own version of ${findings.length} npm script${findings.length === 1 ? "" : "s"} the CLI manages for you changed:
|
|
6072
|
+
${rows}
|
|
6073
|
+
|
|
6074
|
+
Your project keeps its \`catalyst\`-backed command, which is almost always what you want \u2014 \`catalyst build\`/\`start\`/\`deploy\` wrap the equivalent \`next\` command. Apply the change by hand only if you have opted out of the CLI scripts.`
|
|
6075
|
+
);
|
|
6076
|
+
}
|
|
5471
6077
|
function printSummary({
|
|
5472
6078
|
result,
|
|
5473
6079
|
relDir,
|
|
@@ -5511,23 +6117,42 @@ Resolve them, then: git add ${relDir} && git commit${install}`
|
|
|
5511
6117
|
);
|
|
5512
6118
|
}
|
|
5513
6119
|
}
|
|
6120
|
+
async function reconcileNativeHosting(catalystRoot, result) {
|
|
6121
|
+
const state = getProjectState(catalystRoot);
|
|
6122
|
+
if (!state.isLinked && !state.isTransformed) return;
|
|
6123
|
+
const instrumentationRel = "instrumentation.ts";
|
|
6124
|
+
const instrumentationPath = join15(catalystRoot, instrumentationRel);
|
|
6125
|
+
const hadInstrumentation = await pathExists(instrumentationPath);
|
|
6126
|
+
try {
|
|
6127
|
+
await cleanupCloudflareIncompatibilities(catalystRoot);
|
|
6128
|
+
} catch (err) {
|
|
6129
|
+
consola.warn(
|
|
6130
|
+
`Couldn't fully reconcile native-hosting files (${err instanceof Error ? err.message : String(err)}). Remove @vercel/otel from package.json by hand if the merge brought it back.`
|
|
6131
|
+
);
|
|
6132
|
+
}
|
|
6133
|
+
if (hadInstrumentation && !await pathExists(instrumentationPath)) {
|
|
6134
|
+
result.conflicted = result.conflicted.filter((rel) => rel !== instrumentationRel);
|
|
6135
|
+
result.applied = result.applied.filter((rel) => rel !== instrumentationRel);
|
|
6136
|
+
result.added = result.added.filter((rel) => rel !== instrumentationRel);
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
5514
6139
|
var upgrade = new Command13("upgrade").configureHelp({ showGlobalOptions: true }).aliases([
|
|
5515
6140
|
"up",
|
|
5516
6141
|
// These are hidden aliases
|
|
5517
6142
|
"upgrayedd",
|
|
5518
6143
|
"ugrad"
|
|
5519
6144
|
]).description("Upgrade your Catalyst project to a newer version by applying a 3-way merge.").argument("[version]", "Target version to upgrade to (default: latest)").addOption(
|
|
5520
|
-
new
|
|
6145
|
+
new Option11(
|
|
5521
6146
|
"--ref <ref>",
|
|
5522
6147
|
"Full git tag to upgrade to, e.g. an integration family like @bigcommerce/catalyst-makeswift@1.7.0. Overrides [version]."
|
|
5523
6148
|
)
|
|
5524
|
-
).addOption(new
|
|
5525
|
-
new
|
|
6149
|
+
).addOption(new Option11("--from <ref>", "Base ref to upgrade from (when catalyst.ref is missing)")).option("--dry-run", "Generate and display the diff without applying it").option("--yes", "Skip confirmation prompts (e.g. inferred base ref)").addOption(
|
|
6150
|
+
new Option11(
|
|
5526
6151
|
"--strategy <strategy>",
|
|
5527
6152
|
"Merge engine: tree (git merge-tree, full fidelity), per-file (git merge-file, no-history fallback), or auto (tree when git >= 2.38, else per-file)"
|
|
5528
6153
|
).choices(["auto", "tree", "per-file"]).default("auto").hideHelp()
|
|
5529
6154
|
).addOption(
|
|
5530
|
-
new
|
|
6155
|
+
new Option11("--repository <owner/repo>", "GitHub repository to pull versions from").default(
|
|
5531
6156
|
DEFAULT_REPOSITORY
|
|
5532
6157
|
)
|
|
5533
6158
|
).addHelpText(
|
|
@@ -5642,6 +6267,11 @@ to raise the GitHub API rate limit.`
|
|
|
5642
6267
|
baseVersions,
|
|
5643
6268
|
targetVersions
|
|
5644
6269
|
);
|
|
6270
|
+
const { upstreamChanged: managedScripts } = await normalizeManagedScripts(
|
|
6271
|
+
project.rawContent,
|
|
6272
|
+
baseDir,
|
|
6273
|
+
theirsDir
|
|
6274
|
+
);
|
|
5645
6275
|
if (options.dryRun) {
|
|
5646
6276
|
const diffSpinner = yoctoSpinner4().start("Generating diff...");
|
|
5647
6277
|
const diff = await execa4(
|
|
@@ -5662,6 +6292,7 @@ to raise the GitHub API rate limit.`
|
|
|
5662
6292
|
consola.log("\nDiff preview (--dry-run, not applied):\n");
|
|
5663
6293
|
consola.log(diff.stdout);
|
|
5664
6294
|
printWorkspaceFindings(workspaceDeps);
|
|
6295
|
+
printManagedScriptFindings(managedScripts);
|
|
5665
6296
|
return;
|
|
5666
6297
|
}
|
|
5667
6298
|
const strategy = await resolveStrategy(options.strategy);
|
|
@@ -5709,10 +6340,11 @@ to raise the GitHub API rate limit.`
|
|
|
5709
6340
|
);
|
|
5710
6341
|
}
|
|
5711
6342
|
}
|
|
6343
|
+
printManagedScriptFindings(managedScripts);
|
|
5712
6344
|
printWorkspaceFindings(workspaceDeps);
|
|
5713
6345
|
let migratedDeps = 0;
|
|
5714
6346
|
if (workspaceDeps.length) {
|
|
5715
|
-
const accepted = stampedText !== null && (options.yes === true || Boolean(process.stdin.isTTY) && await
|
|
6347
|
+
const accepted = stampedText !== null && (options.yes === true || Boolean(process.stdin.isTTY) && await confirm7({
|
|
5716
6348
|
message: "Replace them with the published versions?",
|
|
5717
6349
|
default: true
|
|
5718
6350
|
}).catch(() => false));
|
|
@@ -5728,6 +6360,7 @@ to raise the GitHub API rate limit.`
|
|
|
5728
6360
|
);
|
|
5729
6361
|
}
|
|
5730
6362
|
}
|
|
6363
|
+
await reconcileNativeHosting(catalystRoot, result);
|
|
5731
6364
|
try {
|
|
5732
6365
|
await applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stampedText !== null);
|
|
5733
6366
|
} catch (err) {
|