@bigcommerce/catalyst 1.4.1 → 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 +318 -58
- 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",
|
|
@@ -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
|
});
|
|
@@ -1550,6 +1558,8 @@ async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERS
|
|
|
1550
1558
|
"pnpm",
|
|
1551
1559
|
[
|
|
1552
1560
|
"dlx",
|
|
1561
|
+
"--allow-build=esbuild",
|
|
1562
|
+
"--allow-build=workerd",
|
|
1553
1563
|
`wrangler@${wranglerVersion}`,
|
|
1554
1564
|
"deploy",
|
|
1555
1565
|
"--config",
|
|
@@ -1617,12 +1627,17 @@ Examples:
|
|
|
1617
1627
|
});
|
|
1618
1628
|
|
|
1619
1629
|
// src/cli/commands/channels.ts
|
|
1620
|
-
import { confirm as
|
|
1630
|
+
import { confirm as confirm4, select as select4 } from "@inquirer/prompts";
|
|
1621
1631
|
import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option4 } from "commander";
|
|
1622
1632
|
import { colorize as colorize5 } from "consola/utils";
|
|
1623
1633
|
|
|
1634
|
+
// src/cli/lib/can-prompt.ts
|
|
1635
|
+
function canPrompt() {
|
|
1636
|
+
return Boolean(process.stdin.isTTY) && !process.env.CI;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1624
1639
|
// src/cli/lib/channel-checkout-url-flow.ts
|
|
1625
|
-
import { input as input3 } from "@inquirer/prompts";
|
|
1640
|
+
import { confirm as confirm3, input as input3 } from "@inquirer/prompts";
|
|
1626
1641
|
|
|
1627
1642
|
// src/cli/lib/channel-site-flow.ts
|
|
1628
1643
|
import { select as select2 } from "@inquirer/prompts";
|
|
@@ -1837,7 +1852,6 @@ async function deleteChannelCheckoutUrl(channelId, storeHash, accessToken, apiHo
|
|
|
1837
1852
|
// src/cli/lib/checkout-url.ts
|
|
1838
1853
|
import { getDomain } from "tldts";
|
|
1839
1854
|
var normalizeHostname = (hostname) => hostname.toLowerCase().replace(/\.$/, "");
|
|
1840
|
-
var parentDomain = (hostname) => hostname.split(".").slice(1).join(".");
|
|
1841
1855
|
var isSubdomainOf = (hostname, parent) => hostname.endsWith(`.${parent}`);
|
|
1842
1856
|
function hostnameOf(url) {
|
|
1843
1857
|
try {
|
|
@@ -1850,11 +1864,45 @@ function sharesMainDomain(a, b) {
|
|
|
1850
1864
|
const first = getDomain(normalizeHostname(a));
|
|
1851
1865
|
return first !== null && first === getDomain(normalizeHostname(b));
|
|
1852
1866
|
}
|
|
1853
|
-
|
|
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 } = {}) {
|
|
1854
1901
|
const host = hostnameOf(storefrontUrl);
|
|
1855
1902
|
if (!host) return void 0;
|
|
1856
|
-
const
|
|
1857
|
-
return `https
|
|
1903
|
+
const normalized = normalizeHostname(host);
|
|
1904
|
+
if (managedZone) return `https://${MANAGED_ZONE_CHECKOUT_PREFIX}${normalized}`;
|
|
1905
|
+
return `https://checkout.${normalized.replace(/^www\./, "")}`;
|
|
1858
1906
|
}
|
|
1859
1907
|
function normalizeCheckoutUrl(value) {
|
|
1860
1908
|
const withScheme = /^[a-z][a-z\d+.-]*:\/\//i.test(value) ? value : `https://${value}`;
|
|
@@ -1873,18 +1921,24 @@ function normalizeCheckoutUrl(value) {
|
|
|
1873
1921
|
}
|
|
1874
1922
|
return parsed.origin;
|
|
1875
1923
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
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));
|
|
1886
1940
|
}
|
|
1887
|
-
|
|
1941
|
+
function warnOnCrossDomainCheckout(site) {
|
|
1888
1942
|
const storefrontUrl = findChannelSiteUrl(site, "primary") ?? site.url;
|
|
1889
1943
|
const storefrontHost = hostnameOf(storefrontUrl);
|
|
1890
1944
|
const checkoutUrl = findChannelSiteUrl(site, "checkout");
|
|
@@ -1906,22 +1960,23 @@ async function warnOnCrossDomainCheckout(site, context) {
|
|
|
1906
1960
|
consola.info(
|
|
1907
1961
|
"Shopper sessions and carts may not carry into checkout in browsers that restrict cross-domain cookies."
|
|
1908
1962
|
);
|
|
1909
|
-
const storefrontOnManagedZone =
|
|
1910
|
-
const report = {
|
|
1911
|
-
|
|
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) {
|
|
1912
1970
|
consola.info(
|
|
1913
|
-
|
|
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:`
|
|
1914
1972
|
);
|
|
1915
|
-
|
|
1916
|
-
}
|
|
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\`.`
|
|
1973
|
+
consola.log(
|
|
1974
|
+
` catalyst channels update --channel-id ${site.channelId} --checkout-url ${report.suggestion ?? "<domain>"}`
|
|
1920
1975
|
);
|
|
1921
1976
|
return report;
|
|
1922
1977
|
}
|
|
1923
1978
|
consola.info(
|
|
1924
|
-
|
|
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.`
|
|
1925
1980
|
);
|
|
1926
1981
|
return report;
|
|
1927
1982
|
}
|
|
@@ -1995,15 +2050,26 @@ async function runChannelSiteUrlFlow(options) {
|
|
|
1995
2050
|
const channel = await resolveChannel(options);
|
|
1996
2051
|
const hostname = await resolveHostname(project, options);
|
|
1997
2052
|
const siteUrl = hostname.startsWith("https://") ? hostname : `https://${hostname}`;
|
|
1998
|
-
|
|
2053
|
+
const channelLabel = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
|
|
2054
|
+
const current = await getChannelSite(
|
|
1999
2055
|
channel.id,
|
|
2000
|
-
siteUrl,
|
|
2001
2056
|
options.storeHash,
|
|
2002
2057
|
options.accessToken,
|
|
2003
2058
|
options.apiHost
|
|
2004
|
-
);
|
|
2005
|
-
|
|
2006
|
-
|
|
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 };
|
|
2007
2073
|
try {
|
|
2008
2074
|
const site = await getChannelSite(
|
|
2009
2075
|
channel.id,
|
|
@@ -2011,11 +2077,7 @@ async function runChannelSiteUrlFlow(options) {
|
|
|
2011
2077
|
options.accessToken,
|
|
2012
2078
|
options.apiHost
|
|
2013
2079
|
);
|
|
2014
|
-
const report =
|
|
2015
|
-
storeHash: options.storeHash,
|
|
2016
|
-
accessToken: options.accessToken,
|
|
2017
|
-
apiHost: options.apiHost
|
|
2018
|
-
});
|
|
2080
|
+
const report = warnOnCrossDomainCheckout(site);
|
|
2019
2081
|
if (report.crossDomain && report.storefrontOnManagedZone !== true) {
|
|
2020
2082
|
consola.info("Set a checkout URL for this channel with:");
|
|
2021
2083
|
consola.log(
|
|
@@ -2024,13 +2086,22 @@ async function runChannelSiteUrlFlow(options) {
|
|
|
2024
2086
|
}
|
|
2025
2087
|
} catch {
|
|
2026
2088
|
}
|
|
2027
|
-
return { channelId: channel.id };
|
|
2089
|
+
return { channelId: channel.id, hostname };
|
|
2028
2090
|
}
|
|
2029
2091
|
|
|
2030
2092
|
// src/cli/lib/channel-checkout-url-flow.ts
|
|
2031
2093
|
async function runChannelCheckoutUrlFlow(options) {
|
|
2032
2094
|
const channel = options.channelId !== void 0 ? { id: options.channelId, name: options.channelName } : await resolveChannel(options);
|
|
2033
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
|
+
}
|
|
2034
2105
|
let storefrontUrl = options.storefrontUrl;
|
|
2035
2106
|
if (!storefrontUrl) {
|
|
2036
2107
|
const site = await getChannelSite(
|
|
@@ -2048,6 +2119,16 @@ async function runChannelCheckoutUrlFlow(options) {
|
|
|
2048
2119
|
);
|
|
2049
2120
|
}
|
|
2050
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
|
+
}
|
|
2051
2132
|
const answer = options.url ?? await input3({
|
|
2052
2133
|
message: "Checkout URL for this channel (must share a main domain with the storefront)",
|
|
2053
2134
|
default: suggestCheckoutUrl(storefrontUrl)
|
|
@@ -2062,6 +2143,78 @@ async function runChannelCheckoutUrlFlow(options) {
|
|
|
2062
2143
|
);
|
|
2063
2144
|
consola.success(`Updated channel ${label} checkout URL to ${checkoutUrl}.`);
|
|
2064
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
|
+
}
|
|
2065
2218
|
|
|
2066
2219
|
// src/cli/lib/create-channel-flow.ts
|
|
2067
2220
|
import { checkbox, input as input4, select as select3 } from "@inquirer/prompts";
|
|
@@ -2404,16 +2557,19 @@ Examples:
|
|
|
2404
2557
|
await getTelemetry().identify(storeHash);
|
|
2405
2558
|
const touchesCheckout = options.checkoutUrl !== void 0 || options.removeCheckoutUrl === true;
|
|
2406
2559
|
const updatesSiteUrl = options.hostname !== void 0 || !touchesCheckout;
|
|
2560
|
+
const offersCheckout = updatesSiteUrl && !touchesCheckout && canPrompt();
|
|
2407
2561
|
let channelId = options.channelId;
|
|
2562
|
+
let siteHostname;
|
|
2408
2563
|
if (updatesSiteUrl) {
|
|
2409
2564
|
try {
|
|
2410
|
-
({ channelId } = await runChannelSiteUrlFlow({
|
|
2565
|
+
({ channelId, hostname: siteHostname } = await runChannelSiteUrlFlow({
|
|
2411
2566
|
storeHash,
|
|
2412
2567
|
accessToken,
|
|
2413
2568
|
apiHost,
|
|
2414
2569
|
projectUuid: options.projectUuid ?? config2.get("projectUuid"),
|
|
2415
2570
|
channelId: options.channelId,
|
|
2416
|
-
hostname: options.hostname
|
|
2571
|
+
hostname: options.hostname,
|
|
2572
|
+
diagnoseCheckout: !offersCheckout
|
|
2417
2573
|
}));
|
|
2418
2574
|
} catch (error) {
|
|
2419
2575
|
if (error instanceof NoLinkedProjectError) {
|
|
@@ -2426,6 +2582,22 @@ Examples:
|
|
|
2426
2582
|
throw error;
|
|
2427
2583
|
}
|
|
2428
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
|
+
}
|
|
2429
2601
|
if (touchesCheckout) {
|
|
2430
2602
|
const channel = channelId === void 0 ? await resolveChannel({ storeHash, accessToken, apiHost }) : { id: channelId, name: void 0 };
|
|
2431
2603
|
const label = channel.name ? `"${channel.name}" (${channel.id})` : String(channel.id);
|
|
@@ -2439,7 +2611,7 @@ Examples:
|
|
|
2439
2611
|
);
|
|
2440
2612
|
consola.success(`Updated channel ${label} checkout URL to ${options.checkoutUrl}.`);
|
|
2441
2613
|
reportChannelSite(updated);
|
|
2442
|
-
|
|
2614
|
+
warnOnCrossDomainCheckout(updated);
|
|
2443
2615
|
} else {
|
|
2444
2616
|
await deleteChannelCheckoutUrl(channel.id, storeHash, accessToken, apiHost);
|
|
2445
2617
|
consola.success(
|
|
@@ -2447,7 +2619,7 @@ Examples:
|
|
|
2447
2619
|
);
|
|
2448
2620
|
const reverted = await getChannelSite(channel.id, storeHash, accessToken, apiHost);
|
|
2449
2621
|
reportChannelSite(reverted);
|
|
2450
|
-
|
|
2622
|
+
warnOnCrossDomainCheckout(reverted);
|
|
2451
2623
|
}
|
|
2452
2624
|
}
|
|
2453
2625
|
process.exit(0);
|
|
@@ -2666,7 +2838,7 @@ Examples:
|
|
|
2666
2838
|
const site = await getChannelSite(channel.id, storeHash, accessToken, apiHost);
|
|
2667
2839
|
consola.success(`Channel ${label}:`);
|
|
2668
2840
|
reportChannelSite(site);
|
|
2669
|
-
const report =
|
|
2841
|
+
const report = warnOnCrossDomainCheckout(site);
|
|
2670
2842
|
if (report.crossDomain && report.storefrontOnManagedZone !== true) {
|
|
2671
2843
|
const suggested = report.suggestion ?? "<domain>";
|
|
2672
2844
|
if (!process.stdin.isTTY) {
|
|
@@ -2676,7 +2848,7 @@ Examples:
|
|
|
2676
2848
|
process.exit(0);
|
|
2677
2849
|
return;
|
|
2678
2850
|
}
|
|
2679
|
-
const shouldSet = await
|
|
2851
|
+
const shouldSet = await confirm4({
|
|
2680
2852
|
message: "Set a checkout URL for this channel now?",
|
|
2681
2853
|
default: true
|
|
2682
2854
|
});
|
|
@@ -3408,7 +3580,7 @@ Examples:
|
|
|
3408
3580
|
});
|
|
3409
3581
|
|
|
3410
3582
|
// src/cli/commands/deploy.ts
|
|
3411
|
-
import { confirm as
|
|
3583
|
+
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
3412
3584
|
import AdmZip from "adm-zip";
|
|
3413
3585
|
import { Command as Command6, Option as Option7 } from "commander";
|
|
3414
3586
|
import { colorize as colorize7 } from "consola/utils";
|
|
@@ -3417,6 +3589,68 @@ import { join as join13 } from "path";
|
|
|
3417
3589
|
import yoctoSpinner3 from "yocto-spinner";
|
|
3418
3590
|
import { z as z13 } from "zod";
|
|
3419
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
|
+
|
|
3420
3654
|
// src/cli/lib/deployment-errors.ts
|
|
3421
3655
|
var ERROR_MESSAGES = {
|
|
3422
3656
|
10: "Something went wrong on our end. Please try again. If the issue persists, contact support.",
|
|
@@ -3543,6 +3777,11 @@ var uploadBundleZip = async (uploadUrl) => {
|
|
|
3543
3777
|
consola.success("Bundle uploaded successfully.");
|
|
3544
3778
|
return true;
|
|
3545
3779
|
};
|
|
3780
|
+
var NATIVE_HOSTING_ENV = {
|
|
3781
|
+
type: "plain_text",
|
|
3782
|
+
key: "CATALYST_HOSTING",
|
|
3783
|
+
value: "native"
|
|
3784
|
+
};
|
|
3546
3785
|
var parseEnvironmentVariables = (secretOption) => {
|
|
3547
3786
|
return secretOption?.map((envVar) => {
|
|
3548
3787
|
const { key, value } = parseEnvAssignment(envVar);
|
|
@@ -3671,6 +3910,10 @@ var deploy = new Command6("deploy").configureHelp({ showGlobalOptions: true }).d
|
|
|
3671
3910
|
Environment variables saved with \`catalyst env add\` are sent automatically on every deploy.
|
|
3672
3911
|
Use \`--secret\` to set or override a variable for a single run.
|
|
3673
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
|
+
|
|
3674
3917
|
Example:
|
|
3675
3918
|
$ catalyst deploy --secret BIGCOMMERCE_STORE_HASH=<YOUR_STORE_HASH> --secret BIGCOMMERCE_STOREFRONT_TOKEN=<YOUR_STOREFRONT_TOKEN>`
|
|
3676
3919
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option(
|
|
@@ -3740,7 +3983,7 @@ Example:
|
|
|
3740
3983
|
consola.success(`Linked project "${selected.name}".`);
|
|
3741
3984
|
}
|
|
3742
3985
|
if (!getProjectState().isTransformed) {
|
|
3743
|
-
const shouldSetup = await
|
|
3986
|
+
const shouldSetup = await confirm6({
|
|
3744
3987
|
message: "Your project is not yet set up for Commerce Hosting deployments. Would you like to run the Commerce Hosting setup now?",
|
|
3745
3988
|
default: true
|
|
3746
3989
|
});
|
|
@@ -3796,7 +4039,7 @@ Example:
|
|
|
3796
4039
|
const flagSecrets = parseEnvironmentVariables(options.secret) ?? [];
|
|
3797
4040
|
const persistedSecrets = toDeploymentSecrets(getStoredEnv(config2));
|
|
3798
4041
|
const mergedSecrets = mergeDeploymentSecrets(persistedSecrets, flagSecrets);
|
|
3799
|
-
const environmentVariables = mergedSecrets
|
|
4042
|
+
const environmentVariables = [...mergedSecrets, NATIVE_HOSTING_ENV];
|
|
3800
4043
|
const { deployment_uuid: deploymentUuid } = await createDeployment(
|
|
3801
4044
|
projectUuid,
|
|
3802
4045
|
uploadSignature.upload_uuid,
|
|
@@ -3820,9 +4063,10 @@ Example:
|
|
|
3820
4063
|
);
|
|
3821
4064
|
};
|
|
3822
4065
|
let resolvedChannelId;
|
|
4066
|
+
let siteHostname;
|
|
3823
4067
|
if (options.updateSiteUrl) {
|
|
3824
4068
|
try {
|
|
3825
|
-
({ channelId: resolvedChannelId } = await runChannelSiteUrlFlow({
|
|
4069
|
+
({ channelId: resolvedChannelId, hostname: siteHostname } = await runChannelSiteUrlFlow({
|
|
3826
4070
|
storeHash,
|
|
3827
4071
|
accessToken,
|
|
3828
4072
|
apiHost,
|
|
@@ -3839,16 +4083,32 @@ Example:
|
|
|
3839
4083
|
storeHash,
|
|
3840
4084
|
accessToken,
|
|
3841
4085
|
apiHost,
|
|
3842
|
-
channelId: resolvedChannelId
|
|
4086
|
+
channelId: resolvedChannelId,
|
|
4087
|
+
storefrontHostname: siteHostname
|
|
3843
4088
|
});
|
|
3844
4089
|
} catch (error) {
|
|
3845
4090
|
warnChannelFlowFailed("checkout URL", error);
|
|
3846
4091
|
}
|
|
3847
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
|
+
}
|
|
3848
4108
|
});
|
|
3849
4109
|
|
|
3850
4110
|
// src/cli/commands/domains.ts
|
|
3851
|
-
import { confirm as
|
|
4111
|
+
import { confirm as confirm7, select as select6 } from "@inquirer/prompts";
|
|
3852
4112
|
import { Command as Command7, Option as Option8 } from "commander";
|
|
3853
4113
|
import { colorize as colorize9 } from "consola/utils";
|
|
3854
4114
|
|
|
@@ -4630,7 +4890,7 @@ Examples:
|
|
|
4630
4890
|
context.apiHost
|
|
4631
4891
|
);
|
|
4632
4892
|
if (current.verification_status === "verified" && !options.force) {
|
|
4633
|
-
const confirmed = await
|
|
4893
|
+
const confirmed = await confirm7({
|
|
4634
4894
|
message: `Remove active domain ${current.domain}? Traffic may stop routing to this project.`,
|
|
4635
4895
|
default: false
|
|
4636
4896
|
});
|
|
@@ -5134,7 +5394,7 @@ Examples:
|
|
|
5134
5394
|
var logs = new Command9("logs").alias("log").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
|
|
5135
5395
|
|
|
5136
5396
|
// src/cli/commands/projects.ts
|
|
5137
|
-
import { confirm as
|
|
5397
|
+
import { confirm as confirm8, input as input6, select as select7 } from "@inquirer/prompts";
|
|
5138
5398
|
import { Command as Command10 } from "commander";
|
|
5139
5399
|
import { colorize as colorize11 } from "consola/utils";
|
|
5140
5400
|
async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
@@ -5143,7 +5403,7 @@ async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
|
5143
5403
|
await cleanupCloudflareIncompatibilities(projectDir);
|
|
5144
5404
|
return;
|
|
5145
5405
|
}
|
|
5146
|
-
const shouldSetup = await
|
|
5406
|
+
const shouldSetup = await confirm8({
|
|
5147
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?",
|
|
5148
5408
|
default: true
|
|
5149
5409
|
});
|
|
@@ -5339,7 +5599,7 @@ Examples:
|
|
|
5339
5599
|
}
|
|
5340
5600
|
if (!options.force) {
|
|
5341
5601
|
const label = targetName ? `"${targetName}" (${targetUuid})` : targetUuid;
|
|
5342
|
-
const confirmed = await
|
|
5602
|
+
const confirmed = await confirm8({
|
|
5343
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.`,
|
|
5344
5604
|
default: false
|
|
5345
5605
|
});
|
|
@@ -5462,7 +5722,7 @@ Examples:
|
|
|
5462
5722
|
|
|
5463
5723
|
// src/cli/commands/upgrade.ts
|
|
5464
5724
|
import { Command as Command13, Option as Option11 } from "@commander-js/extra-typings";
|
|
5465
|
-
import { confirm as
|
|
5725
|
+
import { confirm as confirm9 } from "@inquirer/prompts";
|
|
5466
5726
|
import { execa as execa4 } from "execa";
|
|
5467
5727
|
import {
|
|
5468
5728
|
access as access3,
|
|
@@ -6042,7 +6302,7 @@ async function resolveBaseRef(project, fromOption, assumeYes) {
|
|
|
6042
6302
|
const guess = `${family}@${project.pkg.version}`;
|
|
6043
6303
|
consola.warn(`No \`catalyst.ref\` found in package.json. Inferred base: ${guess}`);
|
|
6044
6304
|
if (assumeYes) return guess;
|
|
6045
|
-
const ok = await
|
|
6305
|
+
const ok = await confirm9({ message: `Upgrade from ${guess}?`, default: true }).catch(() => false);
|
|
6046
6306
|
if (!ok) {
|
|
6047
6307
|
consola.info("Re-run with `--from <ref>` to set the base version explicitly.");
|
|
6048
6308
|
return null;
|
|
@@ -6344,7 +6604,7 @@ to raise the GitHub API rate limit.`
|
|
|
6344
6604
|
printWorkspaceFindings(workspaceDeps);
|
|
6345
6605
|
let migratedDeps = 0;
|
|
6346
6606
|
if (workspaceDeps.length) {
|
|
6347
|
-
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({
|
|
6348
6608
|
message: "Replace them with the published versions?",
|
|
6349
6609
|
default: true
|
|
6350
6610
|
}).catch(() => false));
|