@bigcommerce/catalyst 1.4.0 → 1.4.2
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 +540 -109
- package/dist/cli.js.map +1 -1
- package/package.json +1 -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.4.
|
|
28
|
+
version: "1.4.2",
|
|
29
29
|
type: "module",
|
|
30
30
|
repository: {
|
|
31
31
|
type: "git",
|
|
@@ -215,7 +215,7 @@ import { Command as Command15 } from "commander";
|
|
|
215
215
|
import { colorize as colorize13 } from "consola/utils";
|
|
216
216
|
|
|
217
217
|
// src/cli/commands/auth.ts
|
|
218
|
-
import { Command } from "commander";
|
|
218
|
+
import { Command, Option as Option2 } from "commander";
|
|
219
219
|
import { z as z5 } from "zod";
|
|
220
220
|
|
|
221
221
|
// src/cli/lib/auth-errors.ts
|
|
@@ -619,6 +619,14 @@ function getProjectConfig(cwd = process.cwd()) {
|
|
|
619
619
|
type: "object",
|
|
620
620
|
additionalProperties: { type: "string" },
|
|
621
621
|
default: {}
|
|
622
|
+
},
|
|
623
|
+
declinedSiteUrlChannels: {
|
|
624
|
+
type: "array",
|
|
625
|
+
items: { type: "number" }
|
|
626
|
+
},
|
|
627
|
+
declinedCheckoutUrlChannels: {
|
|
628
|
+
type: "array",
|
|
629
|
+
items: { type: "number" }
|
|
622
630
|
}
|
|
623
631
|
}
|
|
624
632
|
});
|
|
@@ -685,6 +693,19 @@ async function fetchStoreProfile2(storeHash, accessToken, apiHost) {
|
|
|
685
693
|
}
|
|
686
694
|
return result.data.data;
|
|
687
695
|
}
|
|
696
|
+
async function checkCredentials(storeHash, accessToken, apiHost) {
|
|
697
|
+
try {
|
|
698
|
+
const { store_name: storeName } = await fetchStoreProfile2(storeHash, accessToken, apiHost);
|
|
699
|
+
return { valid: true, storeName };
|
|
700
|
+
} catch (error) {
|
|
701
|
+
if (error instanceof UnauthorizedError) return { valid: false, reason: "unauthorized" };
|
|
702
|
+
return {
|
|
703
|
+
valid: false,
|
|
704
|
+
reason: "unverifiable",
|
|
705
|
+
message: error instanceof Error ? error.message : String(error)
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
}
|
|
688
709
|
var whoami = new Command("whoami").configureHelp({ showGlobalOptions: true }).description("Verify stored credentials and display store/project info.").addHelpText(
|
|
689
710
|
"after",
|
|
690
711
|
`
|
|
@@ -739,7 +760,7 @@ Example:
|
|
|
739
760
|
}
|
|
740
761
|
});
|
|
741
762
|
var login2 = new Command("login").configureHelp({ showGlobalOptions: true }).description(
|
|
742
|
-
"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
|
|
763
|
+
"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."
|
|
743
764
|
).addHelpText(
|
|
744
765
|
"after",
|
|
745
766
|
`
|
|
@@ -748,19 +769,67 @@ Examples:
|
|
|
748
769
|
$ catalyst auth login
|
|
749
770
|
|
|
750
771
|
# Login with existing credentials (skips interactive flow)
|
|
751
|
-
$ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN
|
|
752
|
-
|
|
772
|
+
$ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN>
|
|
773
|
+
|
|
774
|
+
# Re-authenticate without checking the stored credentials first
|
|
775
|
+
$ catalyst auth login --force`
|
|
776
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
|
|
777
|
+
new Option2(
|
|
778
|
+
"--force",
|
|
779
|
+
"Skip the check on any credentials already stored and re-authenticate regardless."
|
|
780
|
+
)
|
|
781
|
+
).action(async (options, command) => {
|
|
753
782
|
try {
|
|
754
783
|
const config2 = getProjectConfig();
|
|
755
784
|
const apiHost = resolveApiHost(options, config2);
|
|
756
785
|
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
757
786
|
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
787
|
+
const suppliedOnCli = command.getOptionValueSource("storeHash") === "cli" && command.getOptionValueSource("accessToken") === "cli";
|
|
788
|
+
if (suppliedOnCli && storeHash && accessToken) {
|
|
789
|
+
if (!options.force) {
|
|
790
|
+
const status = await checkCredentials(storeHash, accessToken, apiHost);
|
|
791
|
+
if (!status.valid && status.reason === "unauthorized") {
|
|
792
|
+
consola.error(`The credentials provided for store ${storeHash} were rejected.`);
|
|
793
|
+
consola.info(
|
|
794
|
+
"Check the store hash and access token, or run `catalyst auth login` without them to authenticate in the browser."
|
|
795
|
+
);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (!status.valid) {
|
|
800
|
+
consola.warn(`Couldn't verify the credentials provided: ${status.message}`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
config2.set("storeHash", storeHash);
|
|
804
|
+
config2.set("accessToken", accessToken);
|
|
805
|
+
consola.success(`Logged in to store ${storeHash}.`);
|
|
761
806
|
process.exit(0);
|
|
762
807
|
return;
|
|
763
808
|
}
|
|
809
|
+
if (storeHash && accessToken && !options.force) {
|
|
810
|
+
const status = await checkCredentials(storeHash, accessToken, apiHost);
|
|
811
|
+
if (status.valid) {
|
|
812
|
+
consola.info(`Already logged in to ${status.storeName} (${storeHash}).`);
|
|
813
|
+
consola.info(
|
|
814
|
+
"Run `catalyst auth logout` first to re-authenticate, or `catalyst auth login --force` to skip this check."
|
|
815
|
+
);
|
|
816
|
+
process.exit(0);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
if (status.reason === "unverifiable") {
|
|
820
|
+
consola.warn(
|
|
821
|
+
`Found credentials for store ${storeHash} but couldn't verify them: ${status.message}`
|
|
822
|
+
);
|
|
823
|
+
consola.info(
|
|
824
|
+
"Keeping them. Re-run once the API is reachable, or `catalyst auth login --force` to re-authenticate regardless."
|
|
825
|
+
);
|
|
826
|
+
process.exit(0);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
consola.warn(
|
|
830
|
+
`Stored credentials for store ${storeHash} are no longer valid \u2014 re-authenticating.`
|
|
831
|
+
);
|
|
832
|
+
}
|
|
764
833
|
const credentials = await login(options.loginUrl, apiHost);
|
|
765
834
|
config2.set("storeHash", credentials.storeHash);
|
|
766
835
|
config2.set("accessToken", credentials.accessToken);
|
|
@@ -807,7 +876,7 @@ Example:
|
|
|
807
876
|
var auth = new Command("auth").configureHelp({ showGlobalOptions: true }).description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(login2).addCommand(logout);
|
|
808
877
|
|
|
809
878
|
// src/cli/commands/build.ts
|
|
810
|
-
import { Command as Command2, InvalidArgumentError, Option as
|
|
879
|
+
import { Command as Command2, InvalidArgumentError, Option as Option3 } from "commander";
|
|
811
880
|
import { execa as execa2 } from "execa";
|
|
812
881
|
import { copyFile, cp, rm, writeFile } from "fs/promises";
|
|
813
882
|
import { join as join6 } from "path";
|
|
@@ -918,7 +987,7 @@ var cleanupCloudflareIncompatibilities = async (projectDir) => {
|
|
|
918
987
|
return;
|
|
919
988
|
}
|
|
920
989
|
const shouldRemove = await confirm2({
|
|
921
|
-
message: 'Catalyst found instrumentation.ts, which is incompatible with the Cloudflare Workers bundle when it uses @vercel/otel (causes "Failed to prepare server" at cold start)
|
|
990
|
+
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)',
|
|
922
991
|
default: true
|
|
923
992
|
});
|
|
924
993
|
if (!shouldRemove) {
|
|
@@ -1489,6 +1558,8 @@ async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERS
|
|
|
1489
1558
|
"pnpm",
|
|
1490
1559
|
[
|
|
1491
1560
|
"dlx",
|
|
1561
|
+
"--allow-build=esbuild",
|
|
1562
|
+
"--allow-build=workerd",
|
|
1492
1563
|
`wrangler@${wranglerVersion}`,
|
|
1493
1564
|
"deploy",
|
|
1494
1565
|
"--config",
|
|
@@ -1523,12 +1594,12 @@ Examples:
|
|
|
1523
1594
|
# Build with a specific Wrangler version
|
|
1524
1595
|
$ catalyst build --wrangler-version 4.24.3`
|
|
1525
1596
|
).addOption(
|
|
1526
|
-
new
|
|
1597
|
+
new Option3(
|
|
1527
1598
|
"--project-uuid <uuid>",
|
|
1528
1599
|
"Project UUID to be included in the deployment configuration."
|
|
1529
1600
|
).env("CATALYST_PROJECT_UUID")
|
|
1530
1601
|
).addOption(
|
|
1531
|
-
new
|
|
1602
|
+
new Option3(
|
|
1532
1603
|
"--wrangler-version <version>",
|
|
1533
1604
|
`Wrangler version or dist-tag to build with. Defaults to ${WRANGLER_VERSION}.`
|
|
1534
1605
|
).argParser(parseWranglerVersion)
|
|
@@ -1556,12 +1627,17 @@ Examples:
|
|
|
1556
1627
|
});
|
|
1557
1628
|
|
|
1558
1629
|
// src/cli/commands/channels.ts
|
|
1559
|
-
import { confirm as
|
|
1560
|
-
import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as
|
|
1630
|
+
import { confirm as confirm4, select as select4 } from "@inquirer/prompts";
|
|
1631
|
+
import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option4 } from "commander";
|
|
1561
1632
|
import { colorize as colorize5 } from "consola/utils";
|
|
1562
1633
|
|
|
1634
|
+
// src/cli/lib/can-prompt.ts
|
|
1635
|
+
function canPrompt() {
|
|
1636
|
+
return Boolean(process.stdin.isTTY) && !process.env.CI;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1563
1639
|
// src/cli/lib/channel-checkout-url-flow.ts
|
|
1564
|
-
import { input as input3 } from "@inquirer/prompts";
|
|
1640
|
+
import { confirm as confirm3, input as input3 } from "@inquirer/prompts";
|
|
1565
1641
|
|
|
1566
1642
|
// src/cli/lib/channel-site-flow.ts
|
|
1567
1643
|
import { select as select2 } from "@inquirer/prompts";
|
|
@@ -1776,7 +1852,6 @@ async function deleteChannelCheckoutUrl(channelId, storeHash, accessToken, apiHo
|
|
|
1776
1852
|
// src/cli/lib/checkout-url.ts
|
|
1777
1853
|
import { getDomain } from "tldts";
|
|
1778
1854
|
var normalizeHostname = (hostname) => hostname.toLowerCase().replace(/\.$/, "");
|
|
1779
|
-
var parentDomain = (hostname) => hostname.split(".").slice(1).join(".");
|
|
1780
1855
|
var isSubdomainOf = (hostname, parent) => hostname.endsWith(`.${parent}`);
|
|
1781
1856
|
function hostnameOf(url) {
|
|
1782
1857
|
try {
|
|
@@ -1789,11 +1864,45 @@ function sharesMainDomain(a, b) {
|
|
|
1789
1864
|
const first = getDomain(normalizeHostname(a));
|
|
1790
1865
|
return first !== null && first === getDomain(normalizeHostname(b));
|
|
1791
1866
|
}
|
|
1792
|
-
|
|
1867
|
+
var MANAGED_ZONE_CHECKOUT_PREFIX = "c.";
|
|
1868
|
+
var MAX_HOSTNAME_LENGTH = 64;
|
|
1869
|
+
var CHECKOUT_HOSTNAME_READY_TIMEOUT_MS = 6 * 60 * 1e3;
|
|
1870
|
+
var CHECKOUT_HOSTNAME_POLL_INTERVAL_MS = 10 * 1e3;
|
|
1871
|
+
function managedCheckoutHostname(storefrontHostname) {
|
|
1872
|
+
const hostname = MANAGED_ZONE_CHECKOUT_PREFIX + normalizeHostname(storefrontHostname);
|
|
1873
|
+
return hostname.length <= MAX_HOSTNAME_LENGTH ? hostname : void 0;
|
|
1874
|
+
}
|
|
1875
|
+
async function checkoutHostnameIsServing(hostname) {
|
|
1876
|
+
try {
|
|
1877
|
+
await fetch(`https://${hostname}/`, {
|
|
1878
|
+
method: "HEAD",
|
|
1879
|
+
redirect: "manual",
|
|
1880
|
+
signal: AbortSignal.timeout(1e4)
|
|
1881
|
+
});
|
|
1882
|
+
return true;
|
|
1883
|
+
} catch {
|
|
1884
|
+
return false;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
async function waitForCheckoutHostname(hostname, { timeoutMs = CHECKOUT_HOSTNAME_READY_TIMEOUT_MS, onWait } = {}) {
|
|
1888
|
+
const deadline = Date.now() + timeoutMs;
|
|
1889
|
+
let waited = false;
|
|
1890
|
+
for (; ; ) {
|
|
1891
|
+
if (await checkoutHostnameIsServing(hostname)) return true;
|
|
1892
|
+
if (Date.now() + CHECKOUT_HOSTNAME_POLL_INTERVAL_MS >= deadline) return false;
|
|
1893
|
+
if (!waited) {
|
|
1894
|
+
waited = true;
|
|
1895
|
+
onWait?.();
|
|
1896
|
+
}
|
|
1897
|
+
await new Promise((resolve2) => setTimeout(resolve2, CHECKOUT_HOSTNAME_POLL_INTERVAL_MS));
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
function suggestCheckoutUrl(storefrontUrl, { managedZone = false } = {}) {
|
|
1793
1901
|
const host = hostnameOf(storefrontUrl);
|
|
1794
1902
|
if (!host) return void 0;
|
|
1795
|
-
const
|
|
1796
|
-
return `https
|
|
1903
|
+
const normalized = normalizeHostname(host);
|
|
1904
|
+
if (managedZone) return `https://${MANAGED_ZONE_CHECKOUT_PREFIX}${normalized}`;
|
|
1905
|
+
return `https://checkout.${normalized.replace(/^www\./, "")}`;
|
|
1797
1906
|
}
|
|
1798
1907
|
function normalizeCheckoutUrl(value) {
|
|
1799
1908
|
const withScheme = /^[a-z][a-z\d+.-]*:\/\//i.test(value) ? value : `https://${value}`;
|
|
@@ -1812,18 +1921,24 @@ function normalizeCheckoutUrl(value) {
|
|
|
1812
1921
|
}
|
|
1813
1922
|
return parsed.origin;
|
|
1814
1923
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1924
|
+
var NATIVE_HOSTING_ZONES = [
|
|
1925
|
+
"catalyst-sandbox.store",
|
|
1926
|
+
"catalyst-sandbox-dev.store",
|
|
1927
|
+
"catalyst-sandbox-staging.store",
|
|
1928
|
+
"catalyst-sandbox-integration.store",
|
|
1929
|
+
"ignition-demo.store"
|
|
1930
|
+
];
|
|
1931
|
+
function isManagedHostingHostname(hostname) {
|
|
1932
|
+
const host = normalizeHostname(hostname);
|
|
1933
|
+
return NATIVE_HOSTING_ZONES.some((zone) => isSubdomainOf(host, zone));
|
|
1934
|
+
}
|
|
1935
|
+
function isCrossDomainCheckout(site) {
|
|
1936
|
+
const storefrontHost = hostnameOf(findChannelSiteUrl(site, "primary") ?? site.url);
|
|
1937
|
+
const checkoutUrl = findChannelSiteUrl(site, "checkout");
|
|
1938
|
+
const checkoutHost = checkoutUrl ? hostnameOf(checkoutUrl) : void 0;
|
|
1939
|
+
return Boolean(storefrontHost && checkoutHost && !sharesMainDomain(storefrontHost, checkoutHost));
|
|
1825
1940
|
}
|
|
1826
|
-
|
|
1941
|
+
function warnOnCrossDomainCheckout(site) {
|
|
1827
1942
|
const storefrontUrl = findChannelSiteUrl(site, "primary") ?? site.url;
|
|
1828
1943
|
const storefrontHost = hostnameOf(storefrontUrl);
|
|
1829
1944
|
const checkoutUrl = findChannelSiteUrl(site, "checkout");
|
|
@@ -1845,22 +1960,23 @@ async function warnOnCrossDomainCheckout(site, context) {
|
|
|
1845
1960
|
consola.info(
|
|
1846
1961
|
"Shopper sessions and carts may not carry into checkout in browsers that restrict cross-domain cookies."
|
|
1847
1962
|
);
|
|
1848
|
-
const storefrontOnManagedZone =
|
|
1849
|
-
const report = {
|
|
1850
|
-
|
|
1963
|
+
const storefrontOnManagedZone = isManagedHostingHostname(storefrontHost);
|
|
1964
|
+
const report = {
|
|
1965
|
+
crossDomain: true,
|
|
1966
|
+
storefrontOnManagedZone,
|
|
1967
|
+
suggestion: storefrontOnManagedZone ? suggestCheckoutUrl(storefrontUrl, { managedZone: true }) : suggestion
|
|
1968
|
+
};
|
|
1969
|
+
if (storefrontOnManagedZone) {
|
|
1851
1970
|
consola.info(
|
|
1852
|
-
|
|
1971
|
+
`${storefrontHost} is an auto-generated deployment hostname, so its checkout hostname is ${MANAGED_ZONE_CHECKOUT_PREFIX}${storefrontHost}. BigCommerce provisions it when it's set as the checkout URL, and its certificate takes a few minutes to issue. Set it with:`
|
|
1853
1972
|
);
|
|
1854
|
-
|
|
1855
|
-
}
|
|
1856
|
-
if (storefrontOnManagedZone === true) {
|
|
1857
|
-
consola.info(
|
|
1858
|
-
`${storefrontHost} is an auto-generated deployment hostname, and a checkout subdomain of one cannot be issued a certificate \u2014 so no checkout URL can be set for this channel until its storefront is on a custom domain. Add one with \`catalyst domains add\`.`
|
|
1973
|
+
consola.log(
|
|
1974
|
+
` catalyst channels update --channel-id ${site.channelId} --checkout-url ${report.suggestion ?? "<domain>"}`
|
|
1859
1975
|
);
|
|
1860
1976
|
return report;
|
|
1861
1977
|
}
|
|
1862
1978
|
consola.info(
|
|
1863
|
-
|
|
1979
|
+
`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.`
|
|
1864
1980
|
);
|
|
1865
1981
|
return report;
|
|
1866
1982
|
}
|
|
@@ -1900,7 +2016,7 @@ async function resolveChannel(options) {
|
|
|
1900
2016
|
);
|
|
1901
2017
|
}
|
|
1902
2018
|
const id = await select2({
|
|
1903
|
-
message: "Select the channel to update.",
|
|
2019
|
+
message: options.message ?? "Select the channel to update.",
|
|
1904
2020
|
choices: catalystChannels.map((c) => ({
|
|
1905
2021
|
name: c.name,
|
|
1906
2022
|
value: c.id,
|
|
@@ -1934,15 +2050,26 @@ async function runChannelSiteUrlFlow(options) {
|
|
|
1934
2050
|
const channel = await resolveChannel(options);
|
|
1935
2051
|
const hostname = await resolveHostname(project, options);
|
|
1936
2052
|
const siteUrl = hostname.startsWith("https://") ? hostname : `https://${hostname}`;
|
|
1937
|
-
|
|
2053
|
+
const channelLabel = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
|
|
2054
|
+
const current = await getChannelSite(
|
|
1938
2055
|
channel.id,
|
|
1939
|
-
siteUrl,
|
|
1940
2056
|
options.storeHash,
|
|
1941
2057
|
options.accessToken,
|
|
1942
2058
|
options.apiHost
|
|
1943
|
-
);
|
|
1944
|
-
|
|
1945
|
-
|
|
2059
|
+
).catch(() => void 0);
|
|
2060
|
+
if (current && (findChannelSiteUrl(current, "primary") ?? current.url).replace(/\/$/, "") === siteUrl) {
|
|
2061
|
+
consola.info(`Channel ${channelLabel} site URL is already ${siteUrl}.`);
|
|
2062
|
+
} else {
|
|
2063
|
+
await updateChannelSiteUrl(
|
|
2064
|
+
channel.id,
|
|
2065
|
+
siteUrl,
|
|
2066
|
+
options.storeHash,
|
|
2067
|
+
options.accessToken,
|
|
2068
|
+
options.apiHost
|
|
2069
|
+
);
|
|
2070
|
+
consola.success(`Updated channel ${channelLabel} site URL to ${siteUrl}.`);
|
|
2071
|
+
}
|
|
2072
|
+
if (options.diagnoseCheckout === false) return { channelId: channel.id, hostname };
|
|
1946
2073
|
try {
|
|
1947
2074
|
const site = await getChannelSite(
|
|
1948
2075
|
channel.id,
|
|
@@ -1950,11 +2077,7 @@ async function runChannelSiteUrlFlow(options) {
|
|
|
1950
2077
|
options.accessToken,
|
|
1951
2078
|
options.apiHost
|
|
1952
2079
|
);
|
|
1953
|
-
const report =
|
|
1954
|
-
storeHash: options.storeHash,
|
|
1955
|
-
accessToken: options.accessToken,
|
|
1956
|
-
apiHost: options.apiHost
|
|
1957
|
-
});
|
|
2080
|
+
const report = warnOnCrossDomainCheckout(site);
|
|
1958
2081
|
if (report.crossDomain && report.storefrontOnManagedZone !== true) {
|
|
1959
2082
|
consola.info("Set a checkout URL for this channel with:");
|
|
1960
2083
|
consola.log(
|
|
@@ -1963,13 +2086,22 @@ async function runChannelSiteUrlFlow(options) {
|
|
|
1963
2086
|
}
|
|
1964
2087
|
} catch {
|
|
1965
2088
|
}
|
|
1966
|
-
return { channelId: channel.id };
|
|
2089
|
+
return { channelId: channel.id, hostname };
|
|
1967
2090
|
}
|
|
1968
2091
|
|
|
1969
2092
|
// src/cli/lib/channel-checkout-url-flow.ts
|
|
1970
2093
|
async function runChannelCheckoutUrlFlow(options) {
|
|
1971
2094
|
const channel = options.channelId !== void 0 ? { id: options.channelId, name: options.channelName } : await resolveChannel(options);
|
|
1972
2095
|
const label = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
|
|
2096
|
+
if (options.storefrontHostname !== void 0 && await setManagedCheckoutUrl({
|
|
2097
|
+
...options,
|
|
2098
|
+
channelId: channel.id,
|
|
2099
|
+
label,
|
|
2100
|
+
storefrontHostname: options.storefrontHostname,
|
|
2101
|
+
timeoutMs: options.certificateTimeoutMs
|
|
2102
|
+
})) {
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
1973
2105
|
let storefrontUrl = options.storefrontUrl;
|
|
1974
2106
|
if (!storefrontUrl) {
|
|
1975
2107
|
const site = await getChannelSite(
|
|
@@ -1987,6 +2119,16 @@ async function runChannelCheckoutUrlFlow(options) {
|
|
|
1987
2119
|
);
|
|
1988
2120
|
}
|
|
1989
2121
|
}
|
|
2122
|
+
const storefrontHostname = hostnameOf(storefrontUrl);
|
|
2123
|
+
if (options.url === void 0 && options.storefrontHostname === void 0 && storefrontHostname !== void 0 && await setManagedCheckoutUrl({
|
|
2124
|
+
...options,
|
|
2125
|
+
channelId: channel.id,
|
|
2126
|
+
label,
|
|
2127
|
+
storefrontHostname,
|
|
2128
|
+
timeoutMs: options.certificateTimeoutMs
|
|
2129
|
+
})) {
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
1990
2132
|
const answer = options.url ?? await input3({
|
|
1991
2133
|
message: "Checkout URL for this channel (must share a main domain with the storefront)",
|
|
1992
2134
|
default: suggestCheckoutUrl(storefrontUrl)
|
|
@@ -2001,6 +2143,78 @@ async function runChannelCheckoutUrlFlow(options) {
|
|
|
2001
2143
|
);
|
|
2002
2144
|
consola.success(`Updated channel ${label} checkout URL to ${checkoutUrl}.`);
|
|
2003
2145
|
}
|
|
2146
|
+
async function setManagedCheckoutUrl(options) {
|
|
2147
|
+
const { storeHash, accessToken, apiHost, channelId, label, storefrontHostname } = options;
|
|
2148
|
+
if (!isManagedHostingHostname(storefrontHostname)) return false;
|
|
2149
|
+
const hostname = managedCheckoutHostname(storefrontHostname);
|
|
2150
|
+
if (!hostname) {
|
|
2151
|
+
consola.warn(
|
|
2152
|
+
`${storefrontHostname} is too long to take a checkout prefix, so it has no checkout hostname. Checkout keeps its current URL.`
|
|
2153
|
+
);
|
|
2154
|
+
return true;
|
|
2155
|
+
}
|
|
2156
|
+
const checkoutUrl = `https://${hostname}`;
|
|
2157
|
+
const site = await getChannelSite(channelId, storeHash, accessToken, apiHost);
|
|
2158
|
+
if (site.isCheckoutUrlCustomized) {
|
|
2159
|
+
const alreadySet = site.urls.some(
|
|
2160
|
+
(entry) => entry.type === "checkout" && entry.url.replace(/\/$/, "") === checkoutUrl
|
|
2161
|
+
);
|
|
2162
|
+
if (!alreadySet) {
|
|
2163
|
+
consola.info(
|
|
2164
|
+
`Channel ${label} already has a custom checkout URL, so it was left alone. To replace it, run \`catalyst channels update --channel-id ${channelId} --checkout-url ${checkoutUrl}\`.`
|
|
2165
|
+
);
|
|
2166
|
+
return true;
|
|
2167
|
+
}
|
|
2168
|
+
} else {
|
|
2169
|
+
await updateChannelCheckoutUrl(channelId, checkoutUrl, storeHash, accessToken, apiHost);
|
|
2170
|
+
consola.success(`Updated channel ${label} checkout URL to ${checkoutUrl}.`);
|
|
2171
|
+
}
|
|
2172
|
+
const ready = await waitForCheckoutHostname(hostname, {
|
|
2173
|
+
timeoutMs: options.timeoutMs,
|
|
2174
|
+
onWait: () => consola.info(
|
|
2175
|
+
`Waiting for ${hostname} to be issued a certificate. This usually takes a minute or two...`
|
|
2176
|
+
)
|
|
2177
|
+
});
|
|
2178
|
+
if (ready) {
|
|
2179
|
+
consola.success(`${hostname} is serving checkout.`);
|
|
2180
|
+
return true;
|
|
2181
|
+
}
|
|
2182
|
+
await deleteChannelCheckoutUrl(channelId, storeHash, accessToken, apiHost);
|
|
2183
|
+
consola.warn(
|
|
2184
|
+
`${hostname} wasn't issued a certificate in time, so the checkout URL was removed and checkout falls back to the default channel's. Re-run with \`--update-checkout-url\` to try again.`
|
|
2185
|
+
);
|
|
2186
|
+
return true;
|
|
2187
|
+
}
|
|
2188
|
+
async function offerManagedCheckoutUrl(options) {
|
|
2189
|
+
const { storeHash, accessToken, apiHost, channelId, storefrontHostname, config: config2 } = options;
|
|
2190
|
+
const declined = config2.get("declinedCheckoutUrlChannels") ?? [];
|
|
2191
|
+
if (!isManagedHostingHostname(storefrontHostname)) return false;
|
|
2192
|
+
if (options.respectOptOut && declined.includes(channelId)) return false;
|
|
2193
|
+
const site = await getChannelSite(channelId, storeHash, accessToken, apiHost);
|
|
2194
|
+
if (site.isCheckoutUrlCustomized || !isCrossDomainCheckout(site)) return false;
|
|
2195
|
+
const shouldMove = await confirm3({
|
|
2196
|
+
message: `Checkout is on ${findChannelSiteUrl(site, "checkout") ?? "another domain"}. Move it to https://${MANAGED_ZONE_CHECKOUT_PREFIX}${storefrontHostname} so shoppers stay on this domain through payment?`,
|
|
2197
|
+
default: options.defaultAnswer
|
|
2198
|
+
});
|
|
2199
|
+
if (!shouldMove) {
|
|
2200
|
+
if (!declined.includes(channelId)) {
|
|
2201
|
+
config2.set("declinedCheckoutUrlChannels", [...declined, channelId]);
|
|
2202
|
+
}
|
|
2203
|
+
warnOnCrossDomainCheckout(site);
|
|
2204
|
+
consola.info(
|
|
2205
|
+
`Won't ask again after deploys for channel ${channelId}. To set it later, run \`catalyst channels update --channel-id ${channelId} --checkout-url https://${MANAGED_ZONE_CHECKOUT_PREFIX}${storefrontHostname}\`.`
|
|
2206
|
+
);
|
|
2207
|
+
return true;
|
|
2208
|
+
}
|
|
2209
|
+
await runChannelCheckoutUrlFlow({
|
|
2210
|
+
storeHash,
|
|
2211
|
+
accessToken,
|
|
2212
|
+
apiHost,
|
|
2213
|
+
channelId,
|
|
2214
|
+
storefrontHostname
|
|
2215
|
+
});
|
|
2216
|
+
return true;
|
|
2217
|
+
}
|
|
2004
2218
|
|
|
2005
2219
|
// src/cli/lib/create-channel-flow.ts
|
|
2006
2220
|
import { checkbox, input as input4, select as select3 } from "@inquirer/prompts";
|
|
@@ -2317,22 +2531,22 @@ Examples:
|
|
|
2317
2531
|
# Fall back to the shared checkout domain
|
|
2318
2532
|
$ catalyst channels update --channel-id 123 --remove-checkout-url`
|
|
2319
2533
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
2320
|
-
new
|
|
2534
|
+
new Option4(
|
|
2321
2535
|
"--channel-id <id>",
|
|
2322
2536
|
"Skip the channel prompt and target this channel directly."
|
|
2323
2537
|
).argParser((value) => Number(value))
|
|
2324
2538
|
).addOption(
|
|
2325
|
-
new
|
|
2539
|
+
new Option4(
|
|
2326
2540
|
"--hostname <hostname>",
|
|
2327
2541
|
"Skip the hostname prompt and use this hostname directly. Must be one of the project's deployment_hostnames."
|
|
2328
2542
|
)
|
|
2329
2543
|
).addOption(
|
|
2330
|
-
new
|
|
2544
|
+
new Option4(
|
|
2331
2545
|
"--checkout-url <url>",
|
|
2332
2546
|
"Set the channel's checkout URL. Must share a main domain with the storefront URL."
|
|
2333
2547
|
).argParser(parseCheckoutUrl)
|
|
2334
2548
|
).addOption(
|
|
2335
|
-
new
|
|
2549
|
+
new Option4(
|
|
2336
2550
|
"--remove-checkout-url",
|
|
2337
2551
|
"Remove the channel's custom checkout URL so checkout falls back to the shared checkout domain."
|
|
2338
2552
|
).conflicts("checkoutUrl")
|
|
@@ -2343,16 +2557,19 @@ Examples:
|
|
|
2343
2557
|
await getTelemetry().identify(storeHash);
|
|
2344
2558
|
const touchesCheckout = options.checkoutUrl !== void 0 || options.removeCheckoutUrl === true;
|
|
2345
2559
|
const updatesSiteUrl = options.hostname !== void 0 || !touchesCheckout;
|
|
2560
|
+
const offersCheckout = updatesSiteUrl && !touchesCheckout && canPrompt();
|
|
2346
2561
|
let channelId = options.channelId;
|
|
2562
|
+
let siteHostname;
|
|
2347
2563
|
if (updatesSiteUrl) {
|
|
2348
2564
|
try {
|
|
2349
|
-
({ channelId } = await runChannelSiteUrlFlow({
|
|
2565
|
+
({ channelId, hostname: siteHostname } = await runChannelSiteUrlFlow({
|
|
2350
2566
|
storeHash,
|
|
2351
2567
|
accessToken,
|
|
2352
2568
|
apiHost,
|
|
2353
2569
|
projectUuid: options.projectUuid ?? config2.get("projectUuid"),
|
|
2354
2570
|
channelId: options.channelId,
|
|
2355
|
-
hostname: options.hostname
|
|
2571
|
+
hostname: options.hostname,
|
|
2572
|
+
diagnoseCheckout: !offersCheckout
|
|
2356
2573
|
}));
|
|
2357
2574
|
} catch (error) {
|
|
2358
2575
|
if (error instanceof NoLinkedProjectError) {
|
|
@@ -2365,6 +2582,22 @@ Examples:
|
|
|
2365
2582
|
throw error;
|
|
2366
2583
|
}
|
|
2367
2584
|
}
|
|
2585
|
+
if (offersCheckout && channelId !== void 0 && siteHostname !== void 0) {
|
|
2586
|
+
const offered = await offerManagedCheckoutUrl({
|
|
2587
|
+
storeHash,
|
|
2588
|
+
accessToken,
|
|
2589
|
+
apiHost,
|
|
2590
|
+
channelId,
|
|
2591
|
+
storefrontHostname: siteHostname,
|
|
2592
|
+
config: config2,
|
|
2593
|
+
// Explicit command, so an earlier decline doesn't silence it.
|
|
2594
|
+
respectOptOut: false,
|
|
2595
|
+
defaultAnswer: true
|
|
2596
|
+
});
|
|
2597
|
+
if (!offered) {
|
|
2598
|
+
warnOnCrossDomainCheckout(await getChannelSite(channelId, storeHash, accessToken, apiHost));
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2368
2601
|
if (touchesCheckout) {
|
|
2369
2602
|
const channel = channelId === void 0 ? await resolveChannel({ storeHash, accessToken, apiHost }) : { id: channelId, name: void 0 };
|
|
2370
2603
|
const label = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
|
|
@@ -2378,7 +2611,7 @@ Examples:
|
|
|
2378
2611
|
);
|
|
2379
2612
|
consola.success(`Updated channel ${label} checkout URL to ${options.checkoutUrl}.`);
|
|
2380
2613
|
reportChannelSite(updated);
|
|
2381
|
-
|
|
2614
|
+
warnOnCrossDomainCheckout(updated);
|
|
2382
2615
|
} else {
|
|
2383
2616
|
await deleteChannelCheckoutUrl(channel.id, storeHash, accessToken, apiHost);
|
|
2384
2617
|
consola.success(
|
|
@@ -2386,7 +2619,7 @@ Examples:
|
|
|
2386
2619
|
);
|
|
2387
2620
|
const reverted = await getChannelSite(channel.id, storeHash, accessToken, apiHost);
|
|
2388
2621
|
reportChannelSite(reverted);
|
|
2389
|
-
|
|
2622
|
+
warnOnCrossDomainCheckout(reverted);
|
|
2390
2623
|
}
|
|
2391
2624
|
}
|
|
2392
2625
|
process.exit(0);
|
|
@@ -2406,14 +2639,14 @@ Examples:
|
|
|
2406
2639
|
# Append extra environment variables to .env.local
|
|
2407
2640
|
$ catalyst channels link --channel-id 123 --env MY_FLAG=1`
|
|
2408
2641
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
|
|
2409
|
-
new
|
|
2642
|
+
new Option4("--channel-id <id>", "Link this channel directly, skipping the picker.").argParser(
|
|
2410
2643
|
parseChannelId
|
|
2411
2644
|
)
|
|
2412
2645
|
).option(
|
|
2413
2646
|
"--env <vars...>",
|
|
2414
2647
|
"Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable)."
|
|
2415
2648
|
).addOption(
|
|
2416
|
-
new
|
|
2649
|
+
new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
2417
2650
|
).action(async (options) => {
|
|
2418
2651
|
const config2 = getProjectConfig();
|
|
2419
2652
|
const apiHost = resolveApiHost(options, config2);
|
|
@@ -2484,11 +2717,11 @@ Examples:
|
|
|
2484
2717
|
).option(
|
|
2485
2718
|
"--additional-locales <locales...>",
|
|
2486
2719
|
"Additional storefront locales, max 4 (e.g. --additional-locales es fr). Skips the additional-languages prompt."
|
|
2487
|
-
).addOption(new
|
|
2720
|
+
).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(
|
|
2488
2721
|
"--link",
|
|
2489
2722
|
"Link the new channel to this project \u2014 write its credentials to .env.local without prompting."
|
|
2490
2723
|
).addOption(
|
|
2491
|
-
new
|
|
2724
|
+
new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
2492
2725
|
).action(async (options) => {
|
|
2493
2726
|
if (options.additionalLocales && options.additionalLocales.length > 4) {
|
|
2494
2727
|
consola.error("You can only set up to 4 additional locales.");
|
|
@@ -2570,7 +2803,10 @@ async function resolveChannelTarget(options) {
|
|
|
2570
2803
|
storeHash,
|
|
2571
2804
|
accessToken,
|
|
2572
2805
|
apiHost,
|
|
2573
|
-
channelId: options.channelId
|
|
2806
|
+
channelId: options.channelId,
|
|
2807
|
+
// Read-only: these commands report on a channel, so the picker must not
|
|
2808
|
+
// say "to update" — the shared default is written for `channels update`.
|
|
2809
|
+
message: "Select a channel."
|
|
2574
2810
|
});
|
|
2575
2811
|
return {
|
|
2576
2812
|
config: config2,
|
|
@@ -2592,7 +2828,7 @@ Examples:
|
|
|
2592
2828
|
# Target a channel directly
|
|
2593
2829
|
$ catalyst channels info --channel-id 123`
|
|
2594
2830
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(
|
|
2595
|
-
new
|
|
2831
|
+
new Option4(
|
|
2596
2832
|
"--channel-id <id>",
|
|
2597
2833
|
"Skip the channel prompt and target this channel directly."
|
|
2598
2834
|
).argParser(parseChannelId)
|
|
@@ -2602,7 +2838,7 @@ Examples:
|
|
|
2602
2838
|
const site = await getChannelSite(channel.id, storeHash, accessToken, apiHost);
|
|
2603
2839
|
consola.success(`Channel ${label}:`);
|
|
2604
2840
|
reportChannelSite(site);
|
|
2605
|
-
const report =
|
|
2841
|
+
const report = warnOnCrossDomainCheckout(site);
|
|
2606
2842
|
if (report.crossDomain && report.storefrontOnManagedZone !== true) {
|
|
2607
2843
|
const suggested = report.suggestion ?? "<domain>";
|
|
2608
2844
|
if (!process.stdin.isTTY) {
|
|
@@ -2612,7 +2848,7 @@ Examples:
|
|
|
2612
2848
|
process.exit(0);
|
|
2613
2849
|
return;
|
|
2614
2850
|
}
|
|
2615
|
-
const shouldSet = await
|
|
2851
|
+
const shouldSet = await confirm4({
|
|
2616
2852
|
message: "Set a checkout URL for this channel now?",
|
|
2617
2853
|
default: true
|
|
2618
2854
|
});
|
|
@@ -2638,7 +2874,7 @@ Examples:
|
|
|
2638
2874
|
var channels = new Command3("channels").alias("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(info, { isDefault: true }).addCommand(create).addCommand(link).addCommand(update);
|
|
2639
2875
|
|
|
2640
2876
|
// src/cli/commands/create.ts
|
|
2641
|
-
import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as
|
|
2877
|
+
import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as Option5 } from "@commander-js/extra-typings";
|
|
2642
2878
|
import { input as input5, select as select5 } from "@inquirer/prompts";
|
|
2643
2879
|
import { execSync as execSync2 } from "child_process";
|
|
2644
2880
|
import { colorize as colorize6 } from "consola/utils";
|
|
@@ -2915,7 +3151,7 @@ Examples:
|
|
|
2915
3151
|
"Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable).",
|
|
2916
3152
|
parseEnvFlag
|
|
2917
3153
|
).addOption(
|
|
2918
|
-
new
|
|
3154
|
+
new Option5(
|
|
2919
3155
|
"--hosting <mode>",
|
|
2920
3156
|
'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.'
|
|
2921
3157
|
).choices(["self-hosted", "commerce"])
|
|
@@ -2923,11 +3159,11 @@ Examples:
|
|
|
2923
3159
|
"--use-existing",
|
|
2924
3160
|
"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."
|
|
2925
3161
|
).addOption(
|
|
2926
|
-
new
|
|
3162
|
+
new Option5("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
|
|
2927
3163
|
).addOption(
|
|
2928
|
-
new
|
|
3164
|
+
new Option5("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
|
|
2929
3165
|
).addOption(
|
|
2930
|
-
new
|
|
3166
|
+
new Option5("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
2931
3167
|
).action(async (options) => {
|
|
2932
3168
|
const { ghRef, repository } = options;
|
|
2933
3169
|
if (options.useExisting && options.hosting !== "commerce") {
|
|
@@ -3083,7 +3319,7 @@ ${steps.map((step) => ` ${colorize6("yellow", step)}`).join("\n")}`
|
|
|
3083
3319
|
});
|
|
3084
3320
|
|
|
3085
3321
|
// src/cli/commands/debug.ts
|
|
3086
|
-
import { Command as Command5, Option as
|
|
3322
|
+
import { Command as Command5, Option as Option6 } from "commander";
|
|
3087
3323
|
|
|
3088
3324
|
// src/cli/lib/collect-diagnostics.ts
|
|
3089
3325
|
import { parse } from "dotenv";
|
|
@@ -3334,7 +3570,7 @@ Examples:
|
|
|
3334
3570
|
|
|
3335
3571
|
# Print machine-readable JSON (useful for copy/paste or piping)
|
|
3336
3572
|
$ catalyst debug --json`
|
|
3337
|
-
).addOption(new
|
|
3573
|
+
).addOption(new Option6("--json", "Output the report as JSON.")).action((options) => {
|
|
3338
3574
|
const diagnostics = collectDiagnostics();
|
|
3339
3575
|
if (options.json) {
|
|
3340
3576
|
consola.log(JSON.stringify(diagnostics, null, 2));
|
|
@@ -3344,15 +3580,77 @@ Examples:
|
|
|
3344
3580
|
});
|
|
3345
3581
|
|
|
3346
3582
|
// src/cli/commands/deploy.ts
|
|
3347
|
-
import { confirm as
|
|
3583
|
+
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
3348
3584
|
import AdmZip from "adm-zip";
|
|
3349
|
-
import { Command as Command6, Option as
|
|
3585
|
+
import { Command as Command6, Option as Option7 } from "commander";
|
|
3350
3586
|
import { colorize as colorize7 } from "consola/utils";
|
|
3351
3587
|
import { access as access2, readdir, readFile } from "fs/promises";
|
|
3352
3588
|
import { join as join13 } from "path";
|
|
3353
3589
|
import yoctoSpinner3 from "yocto-spinner";
|
|
3354
3590
|
import { z as z13 } from "zod";
|
|
3355
3591
|
|
|
3592
|
+
// src/cli/lib/deploy-channel-urls.ts
|
|
3593
|
+
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
3594
|
+
function deployedChannelId(secrets) {
|
|
3595
|
+
const raw = secrets.find((secret) => secret.key === "BIGCOMMERCE_CHANNEL_ID")?.value ?? process.env.BIGCOMMERCE_CHANNEL_ID;
|
|
3596
|
+
const id = Number(raw);
|
|
3597
|
+
return Number.isInteger(id) && id > 0 ? id : void 0;
|
|
3598
|
+
}
|
|
3599
|
+
async function offerChannelUrlUpdates(options) {
|
|
3600
|
+
const { storeHash, accessToken, apiHost, projectUuid, config: config2, channelId } = options;
|
|
3601
|
+
if (!canPrompt() || channelId === void 0) return;
|
|
3602
|
+
const [site, projects2] = await Promise.all([
|
|
3603
|
+
getChannelSite(channelId, storeHash, accessToken, apiHost),
|
|
3604
|
+
fetchProjects(storeHash, accessToken, apiHost)
|
|
3605
|
+
]);
|
|
3606
|
+
const deploymentHostnames = projects2.find((project) => project.uuid === projectUuid)?.deployment_hostnames ?? [];
|
|
3607
|
+
const storefrontUrl = (findChannelSiteUrl(site, "primary") ?? site.url).replace(/\/$/, "");
|
|
3608
|
+
let storefrontHostname = deploymentHostnames.find(
|
|
3609
|
+
(deployed) => storefrontUrl === `https://${deployed}`
|
|
3610
|
+
);
|
|
3611
|
+
storefrontHostname ?? (storefrontHostname = await offerSiteUrl({ ...options, channelId }, storefrontUrl));
|
|
3612
|
+
if (storefrontHostname === void 0) return;
|
|
3613
|
+
await offerManagedCheckoutUrl({
|
|
3614
|
+
storeHash,
|
|
3615
|
+
accessToken,
|
|
3616
|
+
apiHost,
|
|
3617
|
+
channelId,
|
|
3618
|
+
storefrontHostname,
|
|
3619
|
+
config: config2,
|
|
3620
|
+
respectOptOut: true,
|
|
3621
|
+
defaultAnswer: false
|
|
3622
|
+
});
|
|
3623
|
+
}
|
|
3624
|
+
async function offerSiteUrl(options, storefrontUrl) {
|
|
3625
|
+
const { storeHash, accessToken, apiHost, projectUuid, config: config2, channelId } = options;
|
|
3626
|
+
const declined = config2.get("declinedSiteUrlChannels") ?? [];
|
|
3627
|
+
if (declined.includes(channelId)) return void 0;
|
|
3628
|
+
const shouldUpdate = await confirm5({
|
|
3629
|
+
message: `Channel ${channelId}'s site URL is ${storefrontUrl}. Point it at this deployment?`,
|
|
3630
|
+
// Unasked-for after a deploy, so Enter mustn't change a live channel.
|
|
3631
|
+
default: false
|
|
3632
|
+
});
|
|
3633
|
+
if (!shouldUpdate) {
|
|
3634
|
+
config2.set("declinedSiteUrlChannels", [...declined, channelId]);
|
|
3635
|
+
consola.info(
|
|
3636
|
+
`Won't ask again for channel ${channelId}. To change it later, run \`catalyst channels update --channel-id ${channelId}\`.`
|
|
3637
|
+
);
|
|
3638
|
+
return void 0;
|
|
3639
|
+
}
|
|
3640
|
+
const { hostname } = await runChannelSiteUrlFlow({
|
|
3641
|
+
storeHash,
|
|
3642
|
+
accessToken,
|
|
3643
|
+
apiHost,
|
|
3644
|
+
projectUuid,
|
|
3645
|
+
// The deploy knows both; the hostname picker only shows if it reported none.
|
|
3646
|
+
channelId,
|
|
3647
|
+
hostname: options.deploymentHostname,
|
|
3648
|
+
// On the managed zone the checkout offer follows; elsewhere, warn instead.
|
|
3649
|
+
diagnoseCheckout: !isManagedHostingHostname(options.deploymentHostname ?? "")
|
|
3650
|
+
});
|
|
3651
|
+
return hostname;
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3356
3654
|
// src/cli/lib/deployment-errors.ts
|
|
3357
3655
|
var ERROR_MESSAGES = {
|
|
3358
3656
|
10: "Something went wrong on our end. Please try again. If the issue persists, contact support.",
|
|
@@ -3479,6 +3777,11 @@ var uploadBundleZip = async (uploadUrl) => {
|
|
|
3479
3777
|
consola.success("Bundle uploaded successfully.");
|
|
3480
3778
|
return true;
|
|
3481
3779
|
};
|
|
3780
|
+
var NATIVE_HOSTING_ENV = {
|
|
3781
|
+
type: "plain_text",
|
|
3782
|
+
key: "CATALYST_HOSTING",
|
|
3783
|
+
value: "native"
|
|
3784
|
+
};
|
|
3482
3785
|
var parseEnvironmentVariables = (secretOption) => {
|
|
3483
3786
|
return secretOption?.map((envVar) => {
|
|
3484
3787
|
const { key, value } = parseEnvAssignment(envVar);
|
|
@@ -3607,6 +3910,10 @@ var deploy = new Command6("deploy").configureHelp({ showGlobalOptions: true }).d
|
|
|
3607
3910
|
Environment variables saved with \`catalyst env add\` are sent automatically on every deploy.
|
|
3608
3911
|
Use \`--secret\` to set or override a variable for a single run.
|
|
3609
3912
|
|
|
3913
|
+
Without \`--update-site-url\` or \`--update-checkout-url\`, an interactive deploy offers to
|
|
3914
|
+
point the channel's site URL at the deployment and to move its checkout onto the same domain.
|
|
3915
|
+
Each is checked on every deploy; declining either is saved in .bigcommerce/project.json.
|
|
3916
|
+
|
|
3610
3917
|
Example:
|
|
3611
3918
|
$ catalyst deploy --secret BIGCOMMERCE_STORE_HASH=<YOUR_STORE_HASH> --secret BIGCOMMERCE_STOREFRONT_TOKEN=<YOUR_STOREFRONT_TOKEN>`
|
|
3612
3919
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option(
|
|
@@ -3616,7 +3923,7 @@ Example:
|
|
|
3616
3923
|
"--update-checkout-url",
|
|
3617
3924
|
"After a successful deploy, prompt to update a channel's checkout URL."
|
|
3618
3925
|
).addOption(
|
|
3619
|
-
new
|
|
3926
|
+
new Option7(
|
|
3620
3927
|
"--secret <value>",
|
|
3621
3928
|
"Secret to set for this deployment (repeatable). Overrides a stored value for the same key. Format: --secret KEY=VALUE"
|
|
3622
3929
|
).argParser((value, previous = []) => {
|
|
@@ -3626,7 +3933,7 @@ Example:
|
|
|
3626
3933
|
"--prebuilt",
|
|
3627
3934
|
"Skip the build step. Requires .bigcommerce/dist/ to already contain build output."
|
|
3628
3935
|
).addOption(
|
|
3629
|
-
new
|
|
3936
|
+
new Option7(
|
|
3630
3937
|
"--wrangler-version <version>",
|
|
3631
3938
|
`Wrangler version or dist-tag to build with. Ignored with --prebuilt. Defaults to ${WRANGLER_VERSION}.`
|
|
3632
3939
|
).argParser(parseWranglerVersion)
|
|
@@ -3676,7 +3983,7 @@ Example:
|
|
|
3676
3983
|
consola.success(`Linked project "${selected.name}".`);
|
|
3677
3984
|
}
|
|
3678
3985
|
if (!getProjectState().isTransformed) {
|
|
3679
|
-
const shouldSetup = await
|
|
3986
|
+
const shouldSetup = await confirm6({
|
|
3680
3987
|
message: "Your project is not yet set up for Commerce Hosting deployments. Would you like to run the Commerce Hosting setup now?",
|
|
3681
3988
|
default: true
|
|
3682
3989
|
});
|
|
@@ -3732,7 +4039,7 @@ Example:
|
|
|
3732
4039
|
const flagSecrets = parseEnvironmentVariables(options.secret) ?? [];
|
|
3733
4040
|
const persistedSecrets = toDeploymentSecrets(getStoredEnv(config2));
|
|
3734
4041
|
const mergedSecrets = mergeDeploymentSecrets(persistedSecrets, flagSecrets);
|
|
3735
|
-
const environmentVariables = mergedSecrets
|
|
4042
|
+
const environmentVariables = [...mergedSecrets, NATIVE_HOSTING_ENV];
|
|
3736
4043
|
const { deployment_uuid: deploymentUuid } = await createDeployment(
|
|
3737
4044
|
projectUuid,
|
|
3738
4045
|
uploadSignature.upload_uuid,
|
|
@@ -3756,9 +4063,10 @@ Example:
|
|
|
3756
4063
|
);
|
|
3757
4064
|
};
|
|
3758
4065
|
let resolvedChannelId;
|
|
4066
|
+
let siteHostname;
|
|
3759
4067
|
if (options.updateSiteUrl) {
|
|
3760
4068
|
try {
|
|
3761
|
-
({ channelId: resolvedChannelId } = await runChannelSiteUrlFlow({
|
|
4069
|
+
({ channelId: resolvedChannelId, hostname: siteHostname } = await runChannelSiteUrlFlow({
|
|
3762
4070
|
storeHash,
|
|
3763
4071
|
accessToken,
|
|
3764
4072
|
apiHost,
|
|
@@ -3775,17 +4083,33 @@ Example:
|
|
|
3775
4083
|
storeHash,
|
|
3776
4084
|
accessToken,
|
|
3777
4085
|
apiHost,
|
|
3778
|
-
channelId: resolvedChannelId
|
|
4086
|
+
channelId: resolvedChannelId,
|
|
4087
|
+
storefrontHostname: siteHostname
|
|
3779
4088
|
});
|
|
3780
4089
|
} catch (error) {
|
|
3781
4090
|
warnChannelFlowFailed("checkout URL", error);
|
|
3782
4091
|
}
|
|
3783
4092
|
}
|
|
4093
|
+
if (!options.updateSiteUrl && !options.updateCheckoutUrl) {
|
|
4094
|
+
try {
|
|
4095
|
+
await offerChannelUrlUpdates({
|
|
4096
|
+
storeHash,
|
|
4097
|
+
accessToken,
|
|
4098
|
+
apiHost,
|
|
4099
|
+
projectUuid,
|
|
4100
|
+
config: config2,
|
|
4101
|
+
deploymentHostname,
|
|
4102
|
+
channelId: deployedChannelId(mergedSecrets)
|
|
4103
|
+
});
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
warnChannelFlowFailed("URLs", error);
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
3784
4108
|
});
|
|
3785
4109
|
|
|
3786
4110
|
// src/cli/commands/domains.ts
|
|
3787
|
-
import { confirm as
|
|
3788
|
-
import { Command as Command7, Option as
|
|
4111
|
+
import { confirm as confirm7, select as select6 } from "@inquirer/prompts";
|
|
4112
|
+
import { Command as Command7, Option as Option8 } from "commander";
|
|
3789
4113
|
import { colorize as colorize9 } from "consola/utils";
|
|
3790
4114
|
|
|
3791
4115
|
// src/cli/lib/domains.ts
|
|
@@ -4357,7 +4681,7 @@ Examples:
|
|
|
4357
4681
|
# Show pending domains only
|
|
4358
4682
|
$ catalyst domains list --status pending`
|
|
4359
4683
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--domain <domain>", "Only show a specific domain.").addOption(
|
|
4360
|
-
new
|
|
4684
|
+
new Option8("--status <status>", "Filter by verification status.").choices(
|
|
4361
4685
|
DOMAIN_STATUS_FILTERS
|
|
4362
4686
|
)
|
|
4363
4687
|
).action(async (options) => {
|
|
@@ -4566,7 +4890,7 @@ Examples:
|
|
|
4566
4890
|
context.apiHost
|
|
4567
4891
|
);
|
|
4568
4892
|
if (current.verification_status === "verified" && !options.force) {
|
|
4569
|
-
const confirmed = await
|
|
4893
|
+
const confirmed = await confirm7({
|
|
4570
4894
|
message: `Remove active domain ${current.domain}? Traffic may stop routing to this project.`,
|
|
4571
4895
|
default: false
|
|
4572
4896
|
});
|
|
@@ -4662,7 +4986,7 @@ var env = new Command8("env").configureHelp({ showGlobalOptions: true }).descrip
|
|
|
4662
4986
|
).addCommand(add2).addCommand(remove2).addCommand(list2);
|
|
4663
4987
|
|
|
4664
4988
|
// src/cli/commands/logs.ts
|
|
4665
|
-
import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as
|
|
4989
|
+
import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as Option9 } from "commander";
|
|
4666
4990
|
import { colorize as colorize10 } from "consola/utils";
|
|
4667
4991
|
import { z as z16 } from "zod";
|
|
4668
4992
|
var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
|
|
@@ -4879,7 +5203,7 @@ Examples:
|
|
|
4879
5203
|
# Tail logs as raw JSON (useful for piping to other tools)
|
|
4880
5204
|
$ catalyst logs tail --format json`
|
|
4881
5205
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
4882
|
-
new
|
|
5206
|
+
new Option9("--format <format>", "Output format for log events.").choices(["json", "pretty", "default", "short", "request"]).default("default")
|
|
4883
5207
|
).action(async (options) => {
|
|
4884
5208
|
try {
|
|
4885
5209
|
const config2 = getProjectConfig();
|
|
@@ -4980,7 +5304,7 @@ Examples:
|
|
|
4980
5304
|
# Raw JSON (NDJSON) for piping to other tools
|
|
4981
5305
|
$ catalyst logs query --since 2d --format json`
|
|
4982
5306
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
4983
|
-
new
|
|
5307
|
+
new Option9(
|
|
4984
5308
|
"--since <duration>",
|
|
4985
5309
|
"Relative window ending at --end (default: now), e.g. 30m, 6h, 2d (units: s, m, h, d)."
|
|
4986
5310
|
).conflicts("start")
|
|
@@ -4988,28 +5312,28 @@ Examples:
|
|
|
4988
5312
|
"--end <time>",
|
|
4989
5313
|
"Window end: ISO-8601 timestamp or Unix epoch (seconds). Defaults to now. The window must not exceed 7 days."
|
|
4990
5314
|
).option("--method <method>", "Filter by request HTTP method (case-insensitive).").addOption(
|
|
4991
|
-
new
|
|
5315
|
+
new Option9("--status-code <code>", "Filter by response status code (100-599).").argParser(
|
|
4992
5316
|
parseIntInRange("--status-code", 100, 599)
|
|
4993
5317
|
)
|
|
4994
5318
|
).option("--url-like <substring>", "Filter by URL substring (case-sensitive).").addOption(
|
|
4995
|
-
new
|
|
5319
|
+
new Option9("--level-min <level>", "Minimum log level.").choices(Array.from(LOG_LEVELS))
|
|
4996
5320
|
).addOption(
|
|
4997
|
-
new
|
|
5321
|
+
new Option9(
|
|
4998
5322
|
"--limit <count>",
|
|
4999
5323
|
"Maximum entries per page (1-500). Defaults to the API default (100)."
|
|
5000
5324
|
).argParser(parseIntInRange("--limit", 1, 500))
|
|
5001
5325
|
).addOption(
|
|
5002
|
-
new
|
|
5326
|
+
new Option9(
|
|
5003
5327
|
"--after <cursor>",
|
|
5004
5328
|
"Return the page after (older than) this cursor, from a previous page's end_cursor."
|
|
5005
5329
|
).conflicts("before")
|
|
5006
5330
|
).addOption(
|
|
5007
|
-
new
|
|
5331
|
+
new Option9(
|
|
5008
5332
|
"--before <cursor>",
|
|
5009
5333
|
"Return the page before (newer than) this cursor, from a previous page's start_cursor."
|
|
5010
5334
|
)
|
|
5011
5335
|
).addOption(
|
|
5012
|
-
new
|
|
5336
|
+
new Option9("--format <format>", "Output format for log entries.").choices(["json", "pretty", "default", "short", "request"]).default("default")
|
|
5013
5337
|
).action(async (options) => {
|
|
5014
5338
|
try {
|
|
5015
5339
|
const config2 = getProjectConfig();
|
|
@@ -5070,7 +5394,7 @@ Examples:
|
|
|
5070
5394
|
var logs = new Command9("logs").alias("log").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
|
|
5071
5395
|
|
|
5072
5396
|
// src/cli/commands/projects.ts
|
|
5073
|
-
import { confirm as
|
|
5397
|
+
import { confirm as confirm8, input as input6, select as select7 } from "@inquirer/prompts";
|
|
5074
5398
|
import { Command as Command10 } from "commander";
|
|
5075
5399
|
import { colorize as colorize11 } from "consola/utils";
|
|
5076
5400
|
async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
@@ -5079,7 +5403,7 @@ async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
|
5079
5403
|
await cleanupCloudflareIncompatibilities(projectDir);
|
|
5080
5404
|
return;
|
|
5081
5405
|
}
|
|
5082
|
-
const shouldSetup = await
|
|
5406
|
+
const shouldSetup = await confirm8({
|
|
5083
5407
|
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?",
|
|
5084
5408
|
default: true
|
|
5085
5409
|
});
|
|
@@ -5275,7 +5599,7 @@ Examples:
|
|
|
5275
5599
|
}
|
|
5276
5600
|
if (!options.force) {
|
|
5277
5601
|
const label = targetName ? `"${targetName}" (${targetUuid})` : targetUuid;
|
|
5278
|
-
const confirmed = await
|
|
5602
|
+
const confirmed = await confirm8({
|
|
5279
5603
|
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.`,
|
|
5280
5604
|
default: false
|
|
5281
5605
|
});
|
|
@@ -5352,7 +5676,7 @@ Example:
|
|
|
5352
5676
|
});
|
|
5353
5677
|
|
|
5354
5678
|
// src/cli/commands/telemetry.ts
|
|
5355
|
-
import { Argument, Command as Command12, Option as
|
|
5679
|
+
import { Argument, Command as Command12, Option as Option10 } from "commander";
|
|
5356
5680
|
import { colorize as colorize12 } from "consola/utils";
|
|
5357
5681
|
var telemetryService = getTelemetry();
|
|
5358
5682
|
var isEnabled = telemetryService.isEnabled();
|
|
@@ -5370,7 +5694,7 @@ Examples:
|
|
|
5370
5694
|
|
|
5371
5695
|
# Disable telemetry collection
|
|
5372
5696
|
$ catalyst telemetry --disable`
|
|
5373
|
-
).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new
|
|
5697
|
+
).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) => {
|
|
5374
5698
|
if (options.enable || arg === "enable") {
|
|
5375
5699
|
telemetryService.setEnabled(true);
|
|
5376
5700
|
isEnabled = true;
|
|
@@ -5397,8 +5721,8 @@ Examples:
|
|
|
5397
5721
|
});
|
|
5398
5722
|
|
|
5399
5723
|
// src/cli/commands/upgrade.ts
|
|
5400
|
-
import { Command as Command13, Option as
|
|
5401
|
-
import { confirm as
|
|
5724
|
+
import { Command as Command13, Option as Option11 } from "@commander-js/extra-typings";
|
|
5725
|
+
import { confirm as confirm9 } from "@inquirer/prompts";
|
|
5402
5726
|
import { execa as execa4 } from "execa";
|
|
5403
5727
|
import {
|
|
5404
5728
|
access as access3,
|
|
@@ -5559,6 +5883,72 @@ async function findStaleCli(pkgRaw) {
|
|
|
5559
5883
|
const current = minVersion2(range)?.version;
|
|
5560
5884
|
return current !== void 0 && semverLt2(current, latest) ? { current, latest } : null;
|
|
5561
5885
|
}
|
|
5886
|
+
var CLI_MANAGED_SCRIPTS = ["build", "start", "deploy"];
|
|
5887
|
+
var ScriptsPackageJson = z17.looseObject({
|
|
5888
|
+
scripts: z17.record(z17.string(), z17.string()).optional()
|
|
5889
|
+
});
|
|
5890
|
+
var readScripts = (raw) => {
|
|
5891
|
+
try {
|
|
5892
|
+
return ScriptsPackageJson.parse(JSON.parse(raw)).scripts ?? {};
|
|
5893
|
+
} catch {
|
|
5894
|
+
return {};
|
|
5895
|
+
}
|
|
5896
|
+
};
|
|
5897
|
+
var SCRIPTS_BLOCK = /"scripts"\s*:\s*\{(?:[^{}"]|"(?:[^"\\]|\\.)*")*\}/;
|
|
5898
|
+
var rewriteScriptCommand = (raw, name, command) => {
|
|
5899
|
+
const block = SCRIPTS_BLOCK.exec(raw);
|
|
5900
|
+
if (!block) return raw;
|
|
5901
|
+
const splice = (replacement) => raw.slice(0, block.index) + replacement + raw.slice(block.index + block[0].length);
|
|
5902
|
+
const updated = block[0].replace(
|
|
5903
|
+
new RegExp(`("${escapeRegExp(name)}"\\s*:\\s*)"(?:[^"\\\\]|\\\\.)*"`),
|
|
5904
|
+
(_match, key) => `${key}${JSON.stringify(command)}`
|
|
5905
|
+
);
|
|
5906
|
+
if (updated !== block[0]) return splice(updated);
|
|
5907
|
+
const closeIdx = block[0].length - 1;
|
|
5908
|
+
const head = block[0].slice(0, closeIdx).replace(/\s+$/, "");
|
|
5909
|
+
const closeIndent = /\n([ \t]*)$/.exec(block[0].slice(0, closeIdx))?.[1] ?? "";
|
|
5910
|
+
const entryIndent = /\n([ \t]*)"/.exec(block[0])?.[1] ?? `${closeIndent} `;
|
|
5911
|
+
const separator = head.endsWith("{") ? "" : ",";
|
|
5912
|
+
return splice(
|
|
5913
|
+
`${head}${separator}
|
|
5914
|
+
${entryIndent}${JSON.stringify(name)}: ${JSON.stringify(command)}
|
|
5915
|
+
${closeIndent}}`
|
|
5916
|
+
);
|
|
5917
|
+
};
|
|
5918
|
+
async function normalizeManagedScripts(ourPkgRaw, baseDir, theirsDir) {
|
|
5919
|
+
const basePath = join15(baseDir, "package.json");
|
|
5920
|
+
const theirsPath = join15(theirsDir, "package.json");
|
|
5921
|
+
const [originalBase, originalTheirs] = await Promise.all([
|
|
5922
|
+
readFile2(basePath, "utf-8").catch(() => null),
|
|
5923
|
+
readFile2(theirsPath, "utf-8").catch(() => null)
|
|
5924
|
+
]);
|
|
5925
|
+
if (originalBase === null || originalTheirs === null) return { pinned: [], upstreamChanged: [] };
|
|
5926
|
+
const ourScripts = readScripts(ourPkgRaw);
|
|
5927
|
+
const baseScripts = readScripts(originalBase);
|
|
5928
|
+
const theirsScripts = readScripts(originalTheirs);
|
|
5929
|
+
const decisions = CLI_MANAGED_SCRIPTS.flatMap((name) => {
|
|
5930
|
+
const ours = ourScripts[name];
|
|
5931
|
+
return ours !== void 0 && ours !== baseScripts[name] ? [{ name, ours }] : [];
|
|
5932
|
+
});
|
|
5933
|
+
const upstreamChanged = decisions.flatMap(({ name }) => {
|
|
5934
|
+
const base = baseScripts[name] ?? null;
|
|
5935
|
+
const theirs = theirsScripts[name] ?? null;
|
|
5936
|
+
return base !== theirs ? [{ name, base, theirs }] : [];
|
|
5937
|
+
});
|
|
5938
|
+
const baseRaw = decisions.reduce(
|
|
5939
|
+
(acc, { name, ours }) => rewriteScriptCommand(acc, name, ours),
|
|
5940
|
+
originalBase
|
|
5941
|
+
);
|
|
5942
|
+
const theirsRaw = decisions.reduce(
|
|
5943
|
+
(acc, { name, ours }) => rewriteScriptCommand(acc, name, ours),
|
|
5944
|
+
originalTheirs
|
|
5945
|
+
);
|
|
5946
|
+
await Promise.all([
|
|
5947
|
+
baseRaw === originalBase ? null : writeFile2(basePath, baseRaw),
|
|
5948
|
+
theirsRaw === originalTheirs ? null : writeFile2(theirsPath, theirsRaw)
|
|
5949
|
+
]);
|
|
5950
|
+
return { pinned: decisions.map(({ name }) => name), upstreamChanged };
|
|
5951
|
+
}
|
|
5562
5952
|
async function mergeFile(oursPath, basePath, theirsPath) {
|
|
5563
5953
|
const merged = await execa4(
|
|
5564
5954
|
"git",
|
|
@@ -5912,7 +6302,7 @@ async function resolveBaseRef(project, fromOption, assumeYes) {
|
|
|
5912
6302
|
const guess = `${family}@${project.pkg.version}`;
|
|
5913
6303
|
consola.warn(`No \`catalyst.ref\` found in package.json. Inferred base: ${guess}`);
|
|
5914
6304
|
if (assumeYes) return guess;
|
|
5915
|
-
const ok = await
|
|
6305
|
+
const ok = await confirm9({ message: `Upgrade from ${guess}?`, default: true }).catch(() => false);
|
|
5916
6306
|
if (!ok) {
|
|
5917
6307
|
consola.info("Re-run with `--from <ref>` to set the base version explicitly.");
|
|
5918
6308
|
return null;
|
|
@@ -5930,6 +6320,20 @@ ${rows}
|
|
|
5930
6320
|
They resolve to your local packages/ copies, which \`catalyst upgrade\` does not update \u2014 so they never receive upgrades.`
|
|
5931
6321
|
);
|
|
5932
6322
|
}
|
|
6323
|
+
function printManagedScriptFindings(findings) {
|
|
6324
|
+
if (findings.length === 0) return;
|
|
6325
|
+
const pad = Math.max(...findings.map((finding) => finding.name.length));
|
|
6326
|
+
const show = (command) => command ?? "(absent)";
|
|
6327
|
+
const rows = findings.map(
|
|
6328
|
+
(finding) => ` ${finding.name.padEnd(pad)} ${show(finding.base)} \u2192 ${show(finding.theirs)}`
|
|
6329
|
+
).join("\n");
|
|
6330
|
+
consola.warn(
|
|
6331
|
+
`Core's own version of ${findings.length} npm script${findings.length === 1 ? "" : "s"} the CLI manages for you changed:
|
|
6332
|
+
${rows}
|
|
6333
|
+
|
|
6334
|
+
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.`
|
|
6335
|
+
);
|
|
6336
|
+
}
|
|
5933
6337
|
function printSummary({
|
|
5934
6338
|
result,
|
|
5935
6339
|
relDir,
|
|
@@ -5973,23 +6377,42 @@ Resolve them, then: git add ${relDir} && git commit${install}`
|
|
|
5973
6377
|
);
|
|
5974
6378
|
}
|
|
5975
6379
|
}
|
|
6380
|
+
async function reconcileNativeHosting(catalystRoot, result) {
|
|
6381
|
+
const state = getProjectState(catalystRoot);
|
|
6382
|
+
if (!state.isLinked && !state.isTransformed) return;
|
|
6383
|
+
const instrumentationRel = "instrumentation.ts";
|
|
6384
|
+
const instrumentationPath = join15(catalystRoot, instrumentationRel);
|
|
6385
|
+
const hadInstrumentation = await pathExists(instrumentationPath);
|
|
6386
|
+
try {
|
|
6387
|
+
await cleanupCloudflareIncompatibilities(catalystRoot);
|
|
6388
|
+
} catch (err) {
|
|
6389
|
+
consola.warn(
|
|
6390
|
+
`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.`
|
|
6391
|
+
);
|
|
6392
|
+
}
|
|
6393
|
+
if (hadInstrumentation && !await pathExists(instrumentationPath)) {
|
|
6394
|
+
result.conflicted = result.conflicted.filter((rel) => rel !== instrumentationRel);
|
|
6395
|
+
result.applied = result.applied.filter((rel) => rel !== instrumentationRel);
|
|
6396
|
+
result.added = result.added.filter((rel) => rel !== instrumentationRel);
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
5976
6399
|
var upgrade = new Command13("upgrade").configureHelp({ showGlobalOptions: true }).aliases([
|
|
5977
6400
|
"up",
|
|
5978
6401
|
// These are hidden aliases
|
|
5979
6402
|
"upgrayedd",
|
|
5980
6403
|
"ugrad"
|
|
5981
6404
|
]).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(
|
|
5982
|
-
new
|
|
6405
|
+
new Option11(
|
|
5983
6406
|
"--ref <ref>",
|
|
5984
6407
|
"Full git tag to upgrade to, e.g. an integration family like @bigcommerce/catalyst-makeswift@1.7.0. Overrides [version]."
|
|
5985
6408
|
)
|
|
5986
|
-
).addOption(new
|
|
5987
|
-
new
|
|
6409
|
+
).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(
|
|
6410
|
+
new Option11(
|
|
5988
6411
|
"--strategy <strategy>",
|
|
5989
6412
|
"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)"
|
|
5990
6413
|
).choices(["auto", "tree", "per-file"]).default("auto").hideHelp()
|
|
5991
6414
|
).addOption(
|
|
5992
|
-
new
|
|
6415
|
+
new Option11("--repository <owner/repo>", "GitHub repository to pull versions from").default(
|
|
5993
6416
|
DEFAULT_REPOSITORY
|
|
5994
6417
|
)
|
|
5995
6418
|
).addHelpText(
|
|
@@ -6104,6 +6527,11 @@ to raise the GitHub API rate limit.`
|
|
|
6104
6527
|
baseVersions,
|
|
6105
6528
|
targetVersions
|
|
6106
6529
|
);
|
|
6530
|
+
const { upstreamChanged: managedScripts } = await normalizeManagedScripts(
|
|
6531
|
+
project.rawContent,
|
|
6532
|
+
baseDir,
|
|
6533
|
+
theirsDir
|
|
6534
|
+
);
|
|
6107
6535
|
if (options.dryRun) {
|
|
6108
6536
|
const diffSpinner = yoctoSpinner4().start("Generating diff...");
|
|
6109
6537
|
const diff = await execa4(
|
|
@@ -6124,6 +6552,7 @@ to raise the GitHub API rate limit.`
|
|
|
6124
6552
|
consola.log("\nDiff preview (--dry-run, not applied):\n");
|
|
6125
6553
|
consola.log(diff.stdout);
|
|
6126
6554
|
printWorkspaceFindings(workspaceDeps);
|
|
6555
|
+
printManagedScriptFindings(managedScripts);
|
|
6127
6556
|
return;
|
|
6128
6557
|
}
|
|
6129
6558
|
const strategy = await resolveStrategy(options.strategy);
|
|
@@ -6171,10 +6600,11 @@ to raise the GitHub API rate limit.`
|
|
|
6171
6600
|
);
|
|
6172
6601
|
}
|
|
6173
6602
|
}
|
|
6603
|
+
printManagedScriptFindings(managedScripts);
|
|
6174
6604
|
printWorkspaceFindings(workspaceDeps);
|
|
6175
6605
|
let migratedDeps = 0;
|
|
6176
6606
|
if (workspaceDeps.length) {
|
|
6177
|
-
const accepted = stampedText !== null && (options.yes === true || Boolean(process.stdin.isTTY) && await
|
|
6607
|
+
const accepted = stampedText !== null && (options.yes === true || Boolean(process.stdin.isTTY) && await confirm9({
|
|
6178
6608
|
message: "Replace them with the published versions?",
|
|
6179
6609
|
default: true
|
|
6180
6610
|
}).catch(() => false));
|
|
@@ -6190,6 +6620,7 @@ to raise the GitHub API rate limit.`
|
|
|
6190
6620
|
);
|
|
6191
6621
|
}
|
|
6192
6622
|
}
|
|
6623
|
+
await reconcileNativeHosting(catalystRoot, result);
|
|
6193
6624
|
try {
|
|
6194
6625
|
await applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stampedText !== null);
|
|
6195
6626
|
} catch (err) {
|