@bigcommerce/catalyst 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1441 -590
- package/dist/cli.js.map +1 -1
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/cli/lib/
|
|
4
|
-
var
|
|
5
|
-
constructor() {
|
|
6
|
-
super(
|
|
7
|
-
|
|
8
|
-
);
|
|
9
|
-
this.name = "UnauthorizedError";
|
|
3
|
+
// src/cli/lib/errors.ts
|
|
4
|
+
var UserActionableError = class extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "UserActionableError";
|
|
10
8
|
}
|
|
11
9
|
};
|
|
12
|
-
function assertAuthorized(response) {
|
|
13
|
-
if (response.status === 401) {
|
|
14
|
-
throw new UnauthorizedError();
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
10
|
|
|
18
11
|
// src/cli/lib/logger.ts
|
|
19
12
|
import { createConsola } from "consola";
|
|
20
13
|
var consola = createConsola({
|
|
21
|
-
level: "3" ? parseInt("3", 10) : 3
|
|
14
|
+
level: "3" ? parseInt("3", 10) : 3,
|
|
15
|
+
// Drop the per-line timestamp. It adds noise for a short-lived CLI and, on
|
|
16
|
+
// long lines, consola can't right-align it so it falls back to an inline
|
|
17
|
+
// `[time]` prefix that breaks copy-pasteable command suggestions.
|
|
18
|
+
formatOptions: { date: false }
|
|
22
19
|
});
|
|
23
20
|
|
|
24
21
|
// src/cli/lib/telemetry.ts
|
|
@@ -28,7 +25,7 @@ import { randomBytes, randomUUID } from "crypto";
|
|
|
28
25
|
// package.json
|
|
29
26
|
var package_default = {
|
|
30
27
|
name: "@bigcommerce/catalyst",
|
|
31
|
-
version: "1.
|
|
28
|
+
version: "1.1.0",
|
|
32
29
|
type: "module",
|
|
33
30
|
repository: {
|
|
34
31
|
type: "git",
|
|
@@ -69,6 +66,7 @@ var package_default = {
|
|
|
69
66
|
"lodash.kebabcase": "^4.1.1",
|
|
70
67
|
nypm: "^0.5.4",
|
|
71
68
|
open: "^10.1.0",
|
|
69
|
+
semver: "^7.7.4",
|
|
72
70
|
"std-env": "^3.9.0",
|
|
73
71
|
tar: "^7.5.7",
|
|
74
72
|
"yocto-spinner": "^1.0.0",
|
|
@@ -82,6 +80,7 @@ var package_default = {
|
|
|
82
80
|
"@types/fs-extra": "^11.0.4",
|
|
83
81
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
84
82
|
"@types/node": "^22.15.30",
|
|
83
|
+
"@types/semver": "^7.7.0",
|
|
85
84
|
"@vitest/coverage-v8": "^3.2.4",
|
|
86
85
|
"@vitest/ui": "^3.2.4",
|
|
87
86
|
eslint: "^8.57.1",
|
|
@@ -201,26 +200,65 @@ function getTelemetry() {
|
|
|
201
200
|
}
|
|
202
201
|
|
|
203
202
|
// src/cli/program.ts
|
|
204
|
-
import {
|
|
205
|
-
import {
|
|
206
|
-
import { colorize as colorize11 } from "consola/utils";
|
|
207
|
-
import { config } from "dotenv";
|
|
208
|
-
import { existsSync as existsSync4 } from "fs";
|
|
209
|
-
import { resolve } from "path";
|
|
203
|
+
import { Command as Command15 } from "commander";
|
|
204
|
+
import { colorize as colorize13 } from "consola/utils";
|
|
210
205
|
|
|
211
206
|
// src/cli/commands/auth.ts
|
|
212
|
-
import { Command
|
|
213
|
-
import { z as
|
|
207
|
+
import { Command } from "commander";
|
|
208
|
+
import { z as z5 } from "zod";
|
|
209
|
+
|
|
210
|
+
// src/cli/lib/auth-errors.ts
|
|
211
|
+
var UnauthorizedError = class extends UserActionableError {
|
|
212
|
+
constructor() {
|
|
213
|
+
super(
|
|
214
|
+
"Your access token is invalid or has expired.\nRun `catalyst auth login` to re-authenticate."
|
|
215
|
+
);
|
|
216
|
+
this.name = "UnauthorizedError";
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
function assertAuthorized(response) {
|
|
220
|
+
if (response.status === 401) {
|
|
221
|
+
throw new UnauthorizedError();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
214
224
|
|
|
215
225
|
// src/cli/lib/login.ts
|
|
216
226
|
import { confirm, input, password } from "@inquirer/prompts";
|
|
217
227
|
import { colorize } from "consola/utils";
|
|
218
228
|
import open from "open";
|
|
219
229
|
import yoctoSpinner from "yocto-spinner";
|
|
220
|
-
import { z as
|
|
230
|
+
import { z as z3 } from "zod";
|
|
221
231
|
|
|
222
232
|
// src/cli/lib/auth.ts
|
|
233
|
+
import { z as z2 } from "zod";
|
|
234
|
+
|
|
235
|
+
// src/cli/lib/http-errors.ts
|
|
223
236
|
import { z } from "zod";
|
|
237
|
+
var apiErrorBodySchema = z.object({
|
|
238
|
+
title: z.string().optional(),
|
|
239
|
+
detail: z.string().optional(),
|
|
240
|
+
errors: z.record(z.string(), z.string()).optional()
|
|
241
|
+
});
|
|
242
|
+
var FALLBACK_CLIENT_ERROR = "The request was rejected. Check your input \u2014 your access token may be missing a required scope, or the resource may not exist.";
|
|
243
|
+
var FALLBACK_SERVER_ERROR = "Something went wrong on our end. Please try again. If the issue persists, contact support.";
|
|
244
|
+
function extractApiErrorMessage(body) {
|
|
245
|
+
const parsed = apiErrorBodySchema.safeParse(body);
|
|
246
|
+
if (!parsed.success) return void 0;
|
|
247
|
+
const { title, detail, errors } = parsed.data;
|
|
248
|
+
const fieldErrors = errors && Object.keys(errors).length > 0 ? Object.entries(errors).map(([field, message]) => `${field}: ${message}`).join("; ") : void 0;
|
|
249
|
+
const headline = detail ?? title;
|
|
250
|
+
if (headline && fieldErrors) return `${headline} (${fieldErrors})`;
|
|
251
|
+
return headline ?? fieldErrors;
|
|
252
|
+
}
|
|
253
|
+
async function httpError(response, action) {
|
|
254
|
+
const body = await response.json().catch(() => null);
|
|
255
|
+
const isServerError = response.status >= 500;
|
|
256
|
+
const detail = extractApiErrorMessage(body) ?? (isServerError ? FALLBACK_SERVER_ERROR : FALLBACK_CLIENT_ERROR);
|
|
257
|
+
const message = `${action}: ${detail}`;
|
|
258
|
+
return isServerError ? new Error(message) : new UserActionableError(message);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/cli/lib/auth.ts
|
|
224
262
|
var DEVICE_OAUTH_CLIENT_ID = "b8063bu6hhml4e0lqh22yut63atsbyv";
|
|
225
263
|
var DEVICE_OAUTH_SCOPES = [
|
|
226
264
|
"store_v2_information",
|
|
@@ -230,18 +268,18 @@ var DEVICE_OAUTH_SCOPES = [
|
|
|
230
268
|
"store_channel_settings"
|
|
231
269
|
].join(" ");
|
|
232
270
|
var DEFAULT_LOGIN_URL = "https://login.bigcommerce.com";
|
|
233
|
-
var DeviceCodeResponseSchema =
|
|
234
|
-
device_code:
|
|
235
|
-
user_code:
|
|
236
|
-
verification_uri:
|
|
237
|
-
expires_in:
|
|
238
|
-
interval:
|
|
271
|
+
var DeviceCodeResponseSchema = z2.object({
|
|
272
|
+
device_code: z2.string(),
|
|
273
|
+
user_code: z2.string(),
|
|
274
|
+
verification_uri: z2.string(),
|
|
275
|
+
expires_in: z2.number(),
|
|
276
|
+
interval: z2.number()
|
|
239
277
|
});
|
|
240
|
-
var DeviceCodeSuccessSchema =
|
|
241
|
-
access_token:
|
|
242
|
-
store_hash:
|
|
243
|
-
context:
|
|
244
|
-
api_uri:
|
|
278
|
+
var DeviceCodeSuccessSchema = z2.object({
|
|
279
|
+
access_token: z2.string(),
|
|
280
|
+
store_hash: z2.string(),
|
|
281
|
+
context: z2.string(),
|
|
282
|
+
api_uri: z2.string()
|
|
245
283
|
});
|
|
246
284
|
async function requestDeviceCode(loginUrl) {
|
|
247
285
|
const response = await fetch(`${loginUrl}/device/token`, {
|
|
@@ -253,7 +291,7 @@ async function requestDeviceCode(loginUrl) {
|
|
|
253
291
|
})
|
|
254
292
|
});
|
|
255
293
|
if (!response.ok) {
|
|
256
|
-
throw
|
|
294
|
+
throw await httpError(response, "Failed to request device code");
|
|
257
295
|
}
|
|
258
296
|
const res = await response.json();
|
|
259
297
|
return DeviceCodeResponseSchema.parse(res);
|
|
@@ -284,6 +322,33 @@ async function waitForDeviceToken(loginUrl, deviceCode, interval) {
|
|
|
284
322
|
return waitForDeviceToken(loginUrl, deviceCode, interval);
|
|
285
323
|
}
|
|
286
324
|
|
|
325
|
+
// src/cli/lib/clipboard.ts
|
|
326
|
+
import { execa } from "execa";
|
|
327
|
+
function clipboardCommand() {
|
|
328
|
+
switch (process.platform) {
|
|
329
|
+
case "darwin":
|
|
330
|
+
return { command: "pbcopy", args: [] };
|
|
331
|
+
case "win32":
|
|
332
|
+
return { command: "clip", args: [] };
|
|
333
|
+
case "linux":
|
|
334
|
+
return process.env.WAYLAND_DISPLAY ? { command: "wl-copy", args: [] } : { command: "xclip", args: ["-selection", "clipboard"] };
|
|
335
|
+
default:
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async function copyToClipboard(text) {
|
|
340
|
+
const clipboard = clipboardCommand();
|
|
341
|
+
if (!clipboard) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
await execa(clipboard.command, clipboard.args, { input: text });
|
|
346
|
+
return true;
|
|
347
|
+
} catch {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
287
352
|
// src/cli/lib/login.ts
|
|
288
353
|
var LoginAbortedError = class extends Error {
|
|
289
354
|
constructor() {
|
|
@@ -291,9 +356,9 @@ var LoginAbortedError = class extends Error {
|
|
|
291
356
|
this.name = "LoginAbortedError";
|
|
292
357
|
}
|
|
293
358
|
};
|
|
294
|
-
var StoreProfileSchema =
|
|
295
|
-
data:
|
|
296
|
-
store_name:
|
|
359
|
+
var StoreProfileSchema = z3.object({
|
|
360
|
+
data: z3.object({
|
|
361
|
+
store_name: z3.string()
|
|
297
362
|
})
|
|
298
363
|
});
|
|
299
364
|
async function fetchStoreProfile(storeHash, accessToken, apiHost) {
|
|
@@ -319,6 +384,13 @@ async function deviceCodeLogin(loginUrl) {
|
|
|
319
384
|
consola.info(
|
|
320
385
|
`${colorize("yellow", "Your one-time code:")} ${colorize("bold", deviceCode.user_code)}`
|
|
321
386
|
);
|
|
387
|
+
if (process.stdin.isTTY) {
|
|
388
|
+
await input({ message: "Press Enter to open your browser and sign in" });
|
|
389
|
+
const copied = await copyToClipboard(deviceCode.user_code);
|
|
390
|
+
if (copied) {
|
|
391
|
+
consola.info("Copied the code to your clipboard \u2014 paste it into the sign-in page.");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
322
394
|
try {
|
|
323
395
|
await open(deviceCode.verification_uri);
|
|
324
396
|
consola.info(`Opened ${deviceCode.verification_uri} in your browser.`);
|
|
@@ -346,12 +418,12 @@ Grant these OAuth scopes: ${DEVICE_OAUTH_SCOPES}`
|
|
|
346
418
|
const storeHashInput = await input({ message: "Store hash:" });
|
|
347
419
|
const storeHash = storeHashInput.trim();
|
|
348
420
|
if (!storeHash) {
|
|
349
|
-
throw new
|
|
421
|
+
throw new UserActionableError("Store hash is required.");
|
|
350
422
|
}
|
|
351
423
|
const accessTokenInput = await password({ message: "Access token:", mask: true });
|
|
352
424
|
const accessToken = accessTokenInput.trim();
|
|
353
425
|
if (!accessToken) {
|
|
354
|
-
throw new
|
|
426
|
+
throw new UserActionableError("Access token is required.");
|
|
355
427
|
}
|
|
356
428
|
const spinner = yoctoSpinner().start("Validating credentials...");
|
|
357
429
|
try {
|
|
@@ -360,7 +432,7 @@ Grant these OAuth scopes: ${DEVICE_OAUTH_SCOPES}`
|
|
|
360
432
|
} catch (error) {
|
|
361
433
|
spinner.error("Failed to validate credentials.");
|
|
362
434
|
const message = error instanceof Error ? error.message : String(error);
|
|
363
|
-
throw new
|
|
435
|
+
throw new UserActionableError(
|
|
364
436
|
`Could not validate credentials (${message}). Double-check your store hash and access token, then try again.`
|
|
365
437
|
);
|
|
366
438
|
}
|
|
@@ -384,19 +456,19 @@ async function login(loginUrl, apiHost) {
|
|
|
384
456
|
}
|
|
385
457
|
|
|
386
458
|
// src/cli/lib/project.ts
|
|
387
|
-
import { z as
|
|
388
|
-
var InfrastructureProjectValidationError = class extends
|
|
459
|
+
import { z as z4 } from "zod";
|
|
460
|
+
var InfrastructureProjectValidationError = class extends UserActionableError {
|
|
389
461
|
constructor(message) {
|
|
390
462
|
super(message);
|
|
391
463
|
this.name = "InfrastructureProjectValidationError";
|
|
392
464
|
}
|
|
393
465
|
};
|
|
394
|
-
var fetchProjectsSchema =
|
|
395
|
-
data:
|
|
396
|
-
|
|
397
|
-
uuid:
|
|
398
|
-
name:
|
|
399
|
-
deployment_hostnames:
|
|
466
|
+
var fetchProjectsSchema = z4.object({
|
|
467
|
+
data: z4.array(
|
|
468
|
+
z4.object({
|
|
469
|
+
uuid: z4.string(),
|
|
470
|
+
name: z4.string(),
|
|
471
|
+
deployment_hostnames: z4.array(z4.string())
|
|
400
472
|
})
|
|
401
473
|
)
|
|
402
474
|
});
|
|
@@ -417,9 +489,7 @@ async function hasProjectsAccess(storeHash, accessToken, apiHost) {
|
|
|
417
489
|
assertAuthorized(response);
|
|
418
490
|
if (response.status === 200) return true;
|
|
419
491
|
if (response.status === 403) return false;
|
|
420
|
-
throw
|
|
421
|
-
`GET /v3/infrastructure/projects failed: ${response.status} ${response.statusText}`
|
|
422
|
-
);
|
|
492
|
+
throw await httpError(response, "Failed to check project access");
|
|
423
493
|
}
|
|
424
494
|
async function fetchProjects(storeHash, accessToken, apiHost) {
|
|
425
495
|
const response = await fetch(projectsUrl(storeHash, apiHost), {
|
|
@@ -428,29 +498,29 @@ async function fetchProjects(storeHash, accessToken, apiHost) {
|
|
|
428
498
|
});
|
|
429
499
|
assertAuthorized(response);
|
|
430
500
|
if (response.status === 403) {
|
|
431
|
-
throw new
|
|
501
|
+
throw new UserActionableError(
|
|
432
502
|
"Infrastructure Projects API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
|
|
433
503
|
);
|
|
434
504
|
}
|
|
435
505
|
if (!response.ok) {
|
|
436
|
-
throw
|
|
506
|
+
throw await httpError(response, "Failed to fetch projects");
|
|
437
507
|
}
|
|
438
508
|
const res = await response.json();
|
|
439
509
|
const { data } = fetchProjectsSchema.parse(res);
|
|
440
510
|
return data;
|
|
441
511
|
}
|
|
442
|
-
var createProjectSchema =
|
|
443
|
-
data:
|
|
444
|
-
uuid:
|
|
445
|
-
name:
|
|
446
|
-
date_created:
|
|
447
|
-
date_modified:
|
|
512
|
+
var createProjectSchema = z4.object({
|
|
513
|
+
data: z4.object({
|
|
514
|
+
uuid: z4.string(),
|
|
515
|
+
name: z4.string(),
|
|
516
|
+
date_created: z4.coerce.date(),
|
|
517
|
+
date_modified: z4.coerce.date()
|
|
448
518
|
})
|
|
449
519
|
});
|
|
450
|
-
var validationErrorBodySchema =
|
|
451
|
-
title:
|
|
452
|
-
detail:
|
|
453
|
-
errors:
|
|
520
|
+
var validationErrorBodySchema = z4.object({
|
|
521
|
+
title: z4.string().optional(),
|
|
522
|
+
detail: z4.string().optional(),
|
|
523
|
+
errors: z4.record(z4.string(), z4.string()).optional()
|
|
454
524
|
});
|
|
455
525
|
function extractValidationMessage(body) {
|
|
456
526
|
const parsed = validationErrorBodySchema.safeParse(body);
|
|
@@ -478,8 +548,8 @@ async function createProject(name, storeHash, accessToken, apiHost) {
|
|
|
478
548
|
const message = extractValidationMessage(body) ?? fallback;
|
|
479
549
|
throw new InfrastructureProjectValidationError(message);
|
|
480
550
|
}
|
|
481
|
-
if (response.status === 403) {
|
|
482
|
-
throw new
|
|
551
|
+
if (response.status === 403 || response.status === 404) {
|
|
552
|
+
throw new UserActionableError(
|
|
483
553
|
"Infrastructure Projects API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
|
|
484
554
|
);
|
|
485
555
|
}
|
|
@@ -487,7 +557,7 @@ async function createProject(name, storeHash, accessToken, apiHost) {
|
|
|
487
557
|
throw new Error("Failed to create project, is the name already in use?");
|
|
488
558
|
}
|
|
489
559
|
if (!response.ok) {
|
|
490
|
-
throw
|
|
560
|
+
throw await httpError(response, "Failed to create project");
|
|
491
561
|
}
|
|
492
562
|
const res = await response.json();
|
|
493
563
|
const { data } = createProjectSchema.parse(res);
|
|
@@ -500,15 +570,15 @@ async function deleteProject(projectUuid, storeHash, accessToken, apiHost) {
|
|
|
500
570
|
});
|
|
501
571
|
assertAuthorized(response);
|
|
502
572
|
if (response.status === 403) {
|
|
503
|
-
throw new
|
|
573
|
+
throw new UserActionableError(
|
|
504
574
|
"Infrastructure Projects API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
|
|
505
575
|
);
|
|
506
576
|
}
|
|
507
577
|
if (response.status === 404) {
|
|
508
|
-
throw new
|
|
578
|
+
throw new UserActionableError(`Project ${projectUuid} not found.`);
|
|
509
579
|
}
|
|
510
580
|
if (!response.ok) {
|
|
511
|
-
throw
|
|
581
|
+
throw await httpError(response, "Failed to delete project");
|
|
512
582
|
}
|
|
513
583
|
}
|
|
514
584
|
|
|
@@ -529,6 +599,7 @@ function getProjectConfig() {
|
|
|
529
599
|
},
|
|
530
600
|
storeHash: { type: "string" },
|
|
531
601
|
accessToken: { type: "string" },
|
|
602
|
+
apiHost: { type: "string" },
|
|
532
603
|
env: {
|
|
533
604
|
type: "object",
|
|
534
605
|
additionalProperties: { type: "string" },
|
|
@@ -540,6 +611,7 @@ function getProjectConfig() {
|
|
|
540
611
|
|
|
541
612
|
// src/cli/lib/shared-options.ts
|
|
542
613
|
import { Option } from "commander";
|
|
614
|
+
var DEFAULT_API_HOST = "api.bigcommerce.com";
|
|
543
615
|
var storeHashOption = () => new Option(
|
|
544
616
|
"--store-hash <hash>",
|
|
545
617
|
"BigCommerce store hash. Can be found in the URL of your store Control Panel. Read from .bigcommerce/project.json or .env when not provided."
|
|
@@ -548,12 +620,19 @@ var accessTokenOption = () => new Option(
|
|
|
548
620
|
"--access-token <token>",
|
|
549
621
|
"BigCommerce access token. Can be found after creating a store-level API account. Read from .bigcommerce/project.json or .env when not provided."
|
|
550
622
|
).env("CATALYST_ACCESS_TOKEN");
|
|
551
|
-
var apiHostOption = () => new Option(
|
|
623
|
+
var apiHostOption = () => new Option(
|
|
624
|
+
"--api-host <host>",
|
|
625
|
+
"BigCommerce API host. Read from .bigcommerce/project.json or CATALYST_API_HOST when not provided. Defaults to api.bigcommerce.com."
|
|
626
|
+
).env("CATALYST_API_HOST").hideHelp();
|
|
552
627
|
var loginUrlOption = () => new Option("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp();
|
|
553
628
|
var projectUuidOption = () => new Option(
|
|
554
629
|
"--project-uuid <uuid>",
|
|
555
630
|
"BigCommerce infrastructure project UUID. Read from .bigcommerce/project.json or .env when not provided."
|
|
556
631
|
).env("CATALYST_PROJECT_UUID");
|
|
632
|
+
var envPathOption = () => new Option(
|
|
633
|
+
"--env-path <path>",
|
|
634
|
+
"Path to an environment file to load for the build (relative to the current working directory). When omitted, .env.local and .env are auto-loaded from the current directory. Other commands never load env files."
|
|
635
|
+
);
|
|
557
636
|
var resolveProjectUuid = (options) => {
|
|
558
637
|
const config2 = getProjectConfig();
|
|
559
638
|
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
@@ -564,11 +643,12 @@ var resolveProjectUuid = (options) => {
|
|
|
564
643
|
}
|
|
565
644
|
return projectUuid;
|
|
566
645
|
};
|
|
646
|
+
var resolveApiHost = (options, config2) => options.apiHost ?? config2.get("apiHost") ?? DEFAULT_API_HOST;
|
|
567
647
|
|
|
568
648
|
// src/cli/commands/auth.ts
|
|
569
|
-
var StoreProfileSchema2 =
|
|
570
|
-
data:
|
|
571
|
-
store_name:
|
|
649
|
+
var StoreProfileSchema2 = z5.object({
|
|
650
|
+
data: z5.object({
|
|
651
|
+
store_name: z5.string()
|
|
572
652
|
})
|
|
573
653
|
});
|
|
574
654
|
async function fetchStoreProfile2(storeHash, accessToken, apiHost) {
|
|
@@ -597,21 +677,10 @@ Example:
|
|
|
597
677
|
$ catalyst auth whoami
|
|
598
678
|
|
|
599
679
|
Logged in to My Store (abc123), connected to project my-project (43eba682-0c48-11f1-9bd5-827a48b0ce1e)`
|
|
600
|
-
).addOption(
|
|
601
|
-
new Option2(
|
|
602
|
-
"--store-hash <hash>",
|
|
603
|
-
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
604
|
-
).env("CATALYST_STORE_HASH")
|
|
605
|
-
).addOption(
|
|
606
|
-
new Option2(
|
|
607
|
-
"--access-token <token>",
|
|
608
|
-
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
609
|
-
).env("CATALYST_ACCESS_TOKEN")
|
|
610
|
-
).addOption(
|
|
611
|
-
new Option2("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp()
|
|
612
|
-
).action(async (options) => {
|
|
680
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).action(async (options) => {
|
|
613
681
|
try {
|
|
614
682
|
const config2 = getProjectConfig();
|
|
683
|
+
const apiHost = resolveApiHost(options, config2);
|
|
615
684
|
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
616
685
|
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
617
686
|
if (!storeHash || !accessToken) {
|
|
@@ -622,10 +691,10 @@ Example:
|
|
|
622
691
|
process.exit(1);
|
|
623
692
|
return;
|
|
624
693
|
}
|
|
625
|
-
const store = await fetchStoreProfile2(storeHash, accessToken,
|
|
694
|
+
const store = await fetchStoreProfile2(storeHash, accessToken, apiHost);
|
|
626
695
|
const projectUuid = config2.get("projectUuid");
|
|
627
696
|
if (projectUuid) {
|
|
628
|
-
const projects = await fetchProjects(storeHash, accessToken,
|
|
697
|
+
const projects = await fetchProjects(storeHash, accessToken, apiHost);
|
|
629
698
|
const linkedProject = projects.find((p) => p.uuid === projectUuid);
|
|
630
699
|
if (linkedProject) {
|
|
631
700
|
consola.info(
|
|
@@ -665,21 +734,10 @@ Examples:
|
|
|
665
734
|
|
|
666
735
|
# Login with existing credentials (skips interactive flow)
|
|
667
736
|
$ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN>`
|
|
668
|
-
).addOption(
|
|
669
|
-
new Option2(
|
|
670
|
-
"--store-hash <hash>",
|
|
671
|
-
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
672
|
-
).env("CATALYST_STORE_HASH")
|
|
673
|
-
).addOption(
|
|
674
|
-
new Option2(
|
|
675
|
-
"--access-token <token>",
|
|
676
|
-
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
677
|
-
).env("CATALYST_ACCESS_TOKEN")
|
|
678
|
-
).addOption(
|
|
679
|
-
new Option2("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp()
|
|
680
|
-
).addOption(loginUrlOption()).action(async (options) => {
|
|
737
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).action(async (options) => {
|
|
681
738
|
try {
|
|
682
739
|
const config2 = getProjectConfig();
|
|
740
|
+
const apiHost = resolveApiHost(options, config2);
|
|
683
741
|
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
684
742
|
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
685
743
|
if (storeHash && accessToken) {
|
|
@@ -688,7 +746,7 @@ Examples:
|
|
|
688
746
|
process.exit(0);
|
|
689
747
|
return;
|
|
690
748
|
}
|
|
691
|
-
const credentials = await login(options.loginUrl,
|
|
749
|
+
const credentials = await login(options.loginUrl, apiHost);
|
|
692
750
|
config2.set("storeHash", credentials.storeHash);
|
|
693
751
|
config2.set("accessToken", credentials.accessToken);
|
|
694
752
|
consola.success(`Logged in to store ${credentials.storeHash}.`);
|
|
@@ -734,10 +792,43 @@ Example:
|
|
|
734
792
|
var auth = new Command("auth").configureHelp({ showGlobalOptions: true }).description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(login2).addCommand(logout);
|
|
735
793
|
|
|
736
794
|
// src/cli/commands/build.ts
|
|
737
|
-
import { Command as Command2, Option as
|
|
738
|
-
import { execa } from "execa";
|
|
795
|
+
import { Command as Command2, InvalidArgumentError, Option as Option2 } from "commander";
|
|
796
|
+
import { execa as execa2 } from "execa";
|
|
739
797
|
import { copyFile, cp, rm, writeFile } from "fs/promises";
|
|
740
798
|
import { join as join4 } from "path";
|
|
799
|
+
import { valid as validSemver } from "semver";
|
|
800
|
+
|
|
801
|
+
// src/cli/lib/build-env.ts
|
|
802
|
+
import { colorize as colorize2 } from "consola/utils";
|
|
803
|
+
import { config } from "dotenv";
|
|
804
|
+
import { existsSync } from "fs";
|
|
805
|
+
import { resolve } from "path";
|
|
806
|
+
var AUTO_ENV_FILES = [".env.local", ".env"];
|
|
807
|
+
function loadBuildEnv({ envPath, cwd = process.cwd() }) {
|
|
808
|
+
if (envPath !== void 0) {
|
|
809
|
+
const envFilePath = resolve(cwd, envPath);
|
|
810
|
+
const result = config({ path: envFilePath, override: true });
|
|
811
|
+
if (result.error) {
|
|
812
|
+
const errCode = "code" in result.error && typeof result.error.code === "string" ? result.error.code : void 0;
|
|
813
|
+
const message = errCode === "ENOENT" ? `Env file not found: ${envFilePath}` : `Failed to load --env-path ${envPath}: ${result.error.message}`;
|
|
814
|
+
throw new UserActionableError(message);
|
|
815
|
+
}
|
|
816
|
+
consola.log(colorize2("cyanBright", `Loaded environment variables from ${envFilePath}
|
|
817
|
+
`));
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const loaded = AUTO_ENV_FILES.filter((file) => {
|
|
821
|
+
const envFilePath = resolve(cwd, file);
|
|
822
|
+
if (!existsSync(envFilePath)) {
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
return !config({ path: envFilePath, override: false }).error;
|
|
826
|
+
});
|
|
827
|
+
if (loaded.length > 0) {
|
|
828
|
+
consola.log(colorize2("cyanBright", `Loaded environment variables from ${loaded.join(", ")}
|
|
829
|
+
`));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
741
832
|
|
|
742
833
|
// src/cli/lib/get-module-cli-path.ts
|
|
743
834
|
import { dirname, join as join2 } from "path";
|
|
@@ -749,14 +840,14 @@ function getModuleCliPath() {
|
|
|
749
840
|
}
|
|
750
841
|
|
|
751
842
|
// src/cli/lib/project-state.ts
|
|
752
|
-
import { existsSync, readFileSync } from "fs";
|
|
843
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
753
844
|
import { join as join3 } from "path";
|
|
754
|
-
import { z as
|
|
755
|
-
var projectJsonSchema =
|
|
756
|
-
projectUuid:
|
|
845
|
+
import { z as z6 } from "zod";
|
|
846
|
+
var projectJsonSchema = z6.looseObject({
|
|
847
|
+
projectUuid: z6.string().optional()
|
|
757
848
|
});
|
|
758
|
-
var packageJsonSchema =
|
|
759
|
-
dependencies:
|
|
849
|
+
var packageJsonSchema = z6.looseObject({
|
|
850
|
+
dependencies: z6.record(z6.string(), z6.string()).optional()
|
|
760
851
|
});
|
|
761
852
|
var safeReadJson = (path) => {
|
|
762
853
|
try {
|
|
@@ -770,8 +861,8 @@ function getProjectState(cwd = process.cwd()) {
|
|
|
770
861
|
safeReadJson(join3(cwd, ".bigcommerce", "project.json"))
|
|
771
862
|
);
|
|
772
863
|
const projectUuid = projectJson.success ? projectJson.data.projectUuid : void 0;
|
|
773
|
-
const hasMiddleware =
|
|
774
|
-
const hasProxy =
|
|
864
|
+
const hasMiddleware = existsSync2(join3(cwd, "middleware.ts"));
|
|
865
|
+
const hasProxy = existsSync2(join3(cwd, "proxy.ts"));
|
|
775
866
|
const pkgJson = packageJsonSchema.safeParse(safeReadJson(join3(cwd, "package.json")));
|
|
776
867
|
const hasOpenNextDep = pkgJson.success ? Boolean(pkgJson.data.dependencies?.["@opennextjs/cloudflare"]) : false;
|
|
777
868
|
const isLinked = Boolean(projectUuid);
|
|
@@ -788,6 +879,34 @@ function getProjectState(cwd = process.cwd()) {
|
|
|
788
879
|
};
|
|
789
880
|
}
|
|
790
881
|
|
|
882
|
+
// src/cli/lib/required-build-env.ts
|
|
883
|
+
var REQUIRED_BUILD_ENV_VARS = [
|
|
884
|
+
"BIGCOMMERCE_STORE_HASH",
|
|
885
|
+
"BIGCOMMERCE_STOREFRONT_TOKEN",
|
|
886
|
+
"BIGCOMMERCE_CHANNEL_ID",
|
|
887
|
+
"AUTH_SECRET"
|
|
888
|
+
];
|
|
889
|
+
var findMissingBuildEnv = (env2 = process.env) => {
|
|
890
|
+
return REQUIRED_BUILD_ENV_VARS.filter((name) => {
|
|
891
|
+
const value = env2[name];
|
|
892
|
+
return value === void 0 || value.trim() === "";
|
|
893
|
+
});
|
|
894
|
+
};
|
|
895
|
+
var assertRequiredBuildEnv = (env2 = process.env) => {
|
|
896
|
+
const missing = findMissingBuildEnv(env2);
|
|
897
|
+
if (missing.length === 0) {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
const missingList = missing.map((name) => ` - ${name}`).join("\n");
|
|
901
|
+
throw new UserActionableError(
|
|
902
|
+
`Missing required environment variable${missing.length === 1 ? "" : "s"} for the build:
|
|
903
|
+
${missingList}
|
|
904
|
+
|
|
905
|
+
These are read by the build itself, so they must be set before it runs. The build automatically loads \`.env.local\` and \`.env\` from the current directory (${process.cwd()}) \u2014 add the variables above to one of those files (\`.env.local\` takes precedence).
|
|
906
|
+
To load an env file from a different location, pass \`--env-path <path>\` (for example \`--env-path ../.env.local\` when running from a subdirectory).`
|
|
907
|
+
);
|
|
908
|
+
};
|
|
909
|
+
|
|
791
910
|
// src/cli/lib/wrangler-config.ts
|
|
792
911
|
function getCompatibilityDate(now = /* @__PURE__ */ new Date()) {
|
|
793
912
|
const date = new Date(now);
|
|
@@ -853,7 +972,17 @@ function getWranglerConfig(projectUuid) {
|
|
|
853
972
|
|
|
854
973
|
// src/cli/commands/build.ts
|
|
855
974
|
var WRANGLER_VERSION = "4.90.0";
|
|
856
|
-
|
|
975
|
+
var DIST_TAG_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
976
|
+
var parseWranglerVersion = (value) => {
|
|
977
|
+
if (!validSemver(value) && !DIST_TAG_PATTERN.test(value)) {
|
|
978
|
+
throw new InvalidArgumentError(
|
|
979
|
+
`"${value}" is not a valid Wrangler version or dist-tag (e.g. 4.90.0 or latest).`
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
return value;
|
|
983
|
+
};
|
|
984
|
+
async function buildCatalystProject(projectUuid, wranglerVersion = WRANGLER_VERSION) {
|
|
985
|
+
assertRequiredBuildEnv();
|
|
857
986
|
const coreDir = process.cwd();
|
|
858
987
|
const openNextOutDir = join4(coreDir, ".open-next");
|
|
859
988
|
const bigcommerceDistDir = join4(coreDir, ".bigcommerce", "dist");
|
|
@@ -870,7 +999,7 @@ async function buildCatalystProject(projectUuid) {
|
|
|
870
999
|
);
|
|
871
1000
|
consola.success("Templates copied");
|
|
872
1001
|
consola.start("Building project...");
|
|
873
|
-
await
|
|
1002
|
+
await execa2(
|
|
874
1003
|
"pnpm",
|
|
875
1004
|
[
|
|
876
1005
|
"exec",
|
|
@@ -885,11 +1014,11 @@ async function buildCatalystProject(projectUuid) {
|
|
|
885
1014
|
cwd: coreDir
|
|
886
1015
|
}
|
|
887
1016
|
);
|
|
888
|
-
await
|
|
1017
|
+
await execa2(
|
|
889
1018
|
"pnpm",
|
|
890
1019
|
[
|
|
891
1020
|
"dlx",
|
|
892
|
-
`wrangler@${
|
|
1021
|
+
`wrangler@${wranglerVersion}`,
|
|
893
1022
|
"deploy",
|
|
894
1023
|
"--config",
|
|
895
1024
|
join4(coreDir, ".bigcommerce", "wrangler.jsonc"),
|
|
@@ -918,18 +1047,28 @@ Examples:
|
|
|
918
1047
|
$ catalyst build
|
|
919
1048
|
|
|
920
1049
|
# Include project UUID
|
|
921
|
-
$ catalyst build --project-uuid <UUID
|
|
1050
|
+
$ catalyst build --project-uuid <UUID>
|
|
1051
|
+
|
|
1052
|
+
# Build with a specific Wrangler version
|
|
1053
|
+
$ catalyst build --wrangler-version 4.24.3`
|
|
922
1054
|
).addOption(
|
|
923
|
-
new
|
|
1055
|
+
new Option2(
|
|
924
1056
|
"--project-uuid <uuid>",
|
|
925
1057
|
"Project UUID to be included in the deployment configuration."
|
|
926
1058
|
).env("CATALYST_PROJECT_UUID")
|
|
927
|
-
).
|
|
1059
|
+
).addOption(
|
|
1060
|
+
new Option2(
|
|
1061
|
+
"--wrangler-version <version>",
|
|
1062
|
+
`Wrangler version or dist-tag to build with. Defaults to ${WRANGLER_VERSION}.`
|
|
1063
|
+
).argParser(parseWranglerVersion)
|
|
1064
|
+
).addOption(envPathOption()).action(async (options) => {
|
|
1065
|
+
loadBuildEnv({ envPath: options.envPath });
|
|
928
1066
|
const state = getProjectState();
|
|
929
1067
|
if (!state.isTransformed) {
|
|
930
1068
|
consola.info("Project is not set up for Commerce Hosting \u2014 running `next build`.");
|
|
931
1069
|
consola.info("To deploy to Commerce Hosting, run `catalyst deploy`.");
|
|
932
|
-
|
|
1070
|
+
assertRequiredBuildEnv();
|
|
1071
|
+
await execa2("pnpm", ["exec", "next", "build"], {
|
|
933
1072
|
stdio: "inherit",
|
|
934
1073
|
cwd: process.cwd()
|
|
935
1074
|
});
|
|
@@ -942,21 +1081,19 @@ Examples:
|
|
|
942
1081
|
"Project UUID is required. Please run `catalyst project create` or `catalyst project link` or this command again with --project-uuid <uuid>."
|
|
943
1082
|
);
|
|
944
1083
|
}
|
|
945
|
-
await buildCatalystProject(projectUuid);
|
|
1084
|
+
await buildCatalystProject(projectUuid, options.wranglerVersion);
|
|
946
1085
|
});
|
|
947
1086
|
|
|
948
1087
|
// src/cli/commands/channel.ts
|
|
949
|
-
import { select as
|
|
950
|
-
import { Command as Command3, InvalidArgumentError, Option as
|
|
951
|
-
import { colorize as
|
|
952
|
-
import { outputFileSync } from "fs-extra/esm";
|
|
953
|
-
import { join as join6 } from "path";
|
|
1088
|
+
import { select as select4 } from "@inquirer/prompts";
|
|
1089
|
+
import { Command as Command3, InvalidArgumentError as InvalidArgumentError2, Option as Option3 } from "commander";
|
|
1090
|
+
import { colorize as colorize5 } from "consola/utils";
|
|
954
1091
|
|
|
955
1092
|
// src/cli/lib/channel-site-flow.ts
|
|
956
1093
|
import { select as select2 } from "@inquirer/prompts";
|
|
957
1094
|
|
|
958
1095
|
// src/cli/lib/channels.ts
|
|
959
|
-
import { z as
|
|
1096
|
+
import { z as z7 } from "zod";
|
|
960
1097
|
var cliApiUrl = (origin, storeHash, path) => `${origin}/stores/${storeHash}/cli-api/v3${path}`;
|
|
961
1098
|
var channelsUrl = (storeHash, apiHost, query2 = {}) => {
|
|
962
1099
|
const params = new URLSearchParams(query2).toString();
|
|
@@ -967,14 +1104,14 @@ var authHeaders2 = (accessToken) => ({
|
|
|
967
1104
|
"X-Correlation-Id": getTelemetry().correlationId,
|
|
968
1105
|
Accept: "application/json"
|
|
969
1106
|
});
|
|
970
|
-
var envVarsSchema =
|
|
971
|
-
var channelSchema =
|
|
972
|
-
id:
|
|
973
|
-
name:
|
|
974
|
-
platform:
|
|
1107
|
+
var envVarsSchema = z7.record(z7.string(), z7.coerce.string());
|
|
1108
|
+
var channelSchema = z7.object({
|
|
1109
|
+
id: z7.number(),
|
|
1110
|
+
name: z7.string(),
|
|
1111
|
+
platform: z7.string()
|
|
975
1112
|
});
|
|
976
|
-
var channelsResponseSchema =
|
|
977
|
-
data:
|
|
1113
|
+
var channelsResponseSchema = z7.object({
|
|
1114
|
+
data: z7.array(channelSchema)
|
|
978
1115
|
});
|
|
979
1116
|
var CHANNEL_PLATFORM_ORDER = ["catalyst", "next", "bigcommerce"];
|
|
980
1117
|
function sortChannelsByPlatform(channels) {
|
|
@@ -990,30 +1127,30 @@ function sortChannelsByPlatform(channels) {
|
|
|
990
1127
|
function channelPlatformLabel(platform) {
|
|
991
1128
|
return platform === "bigcommerce" ? "Stencil" : platform.charAt(0).toUpperCase() + platform.slice(1);
|
|
992
1129
|
}
|
|
993
|
-
var initResponseSchema =
|
|
994
|
-
data:
|
|
995
|
-
storefront_api_token:
|
|
1130
|
+
var initResponseSchema = z7.object({
|
|
1131
|
+
data: z7.object({
|
|
1132
|
+
storefront_api_token: z7.string(),
|
|
996
1133
|
envVars: envVarsSchema
|
|
997
1134
|
})
|
|
998
1135
|
});
|
|
999
|
-
var createChannelResponseSchema =
|
|
1000
|
-
data:
|
|
1001
|
-
id:
|
|
1002
|
-
storefront_api_token:
|
|
1136
|
+
var createChannelResponseSchema = z7.object({
|
|
1137
|
+
data: z7.object({
|
|
1138
|
+
id: z7.number(),
|
|
1139
|
+
storefront_api_token: z7.string(),
|
|
1003
1140
|
envVars: envVarsSchema
|
|
1004
1141
|
})
|
|
1005
1142
|
});
|
|
1006
|
-
var eligibilityResponseSchema =
|
|
1007
|
-
data:
|
|
1008
|
-
eligible:
|
|
1009
|
-
message:
|
|
1143
|
+
var eligibilityResponseSchema = z7.object({
|
|
1144
|
+
data: z7.object({
|
|
1145
|
+
eligible: z7.boolean(),
|
|
1146
|
+
message: z7.string()
|
|
1010
1147
|
})
|
|
1011
1148
|
});
|
|
1012
|
-
var channelSiteSchema =
|
|
1013
|
-
data:
|
|
1014
|
-
id:
|
|
1015
|
-
url:
|
|
1016
|
-
channel_id:
|
|
1149
|
+
var channelSiteSchema = z7.object({
|
|
1150
|
+
data: z7.object({
|
|
1151
|
+
id: z7.number(),
|
|
1152
|
+
url: z7.string(),
|
|
1153
|
+
channel_id: z7.number()
|
|
1017
1154
|
})
|
|
1018
1155
|
});
|
|
1019
1156
|
async function getChannelInit(channelId, storeHash, accessToken, origin) {
|
|
@@ -1022,9 +1159,7 @@ async function getChannelInit(channelId, storeHash, accessToken, origin) {
|
|
|
1022
1159
|
headers: authHeaders2(accessToken)
|
|
1023
1160
|
});
|
|
1024
1161
|
if (!response.ok) {
|
|
1025
|
-
throw
|
|
1026
|
-
`GET /channels/${channelId}/init failed: ${response.status} ${response.statusText}`
|
|
1027
|
-
);
|
|
1162
|
+
throw await httpError(response, "Failed to initialize channel");
|
|
1028
1163
|
}
|
|
1029
1164
|
const { data } = initResponseSchema.parse(await response.json());
|
|
1030
1165
|
return { storefrontToken: data.storefront_api_token, envVars: data.envVars };
|
|
@@ -1035,9 +1170,7 @@ async function checkChannelEligibility(storeHash, accessToken, origin) {
|
|
|
1035
1170
|
headers: authHeaders2(accessToken)
|
|
1036
1171
|
});
|
|
1037
1172
|
if (!response.ok) {
|
|
1038
|
-
throw
|
|
1039
|
-
`GET /channels/catalyst/eligibility failed: ${response.status} ${response.statusText}`
|
|
1040
|
-
);
|
|
1173
|
+
throw await httpError(response, "Failed to check Catalyst eligibility");
|
|
1041
1174
|
}
|
|
1042
1175
|
return eligibilityResponseSchema.parse(await response.json()).data;
|
|
1043
1176
|
}
|
|
@@ -1055,7 +1188,7 @@ async function createChannel(name, storefrontLocale, additionalLocales, installS
|
|
|
1055
1188
|
})
|
|
1056
1189
|
});
|
|
1057
1190
|
if (!response.ok) {
|
|
1058
|
-
throw
|
|
1191
|
+
throw await httpError(response, "Failed to create channel");
|
|
1059
1192
|
}
|
|
1060
1193
|
const { data } = createChannelResponseSchema.parse(await response.json());
|
|
1061
1194
|
return {
|
|
@@ -1078,7 +1211,7 @@ async function fetchAvailableChannels(storeHash, accessToken, apiHost) {
|
|
|
1078
1211
|
);
|
|
1079
1212
|
assertAuthorized(response);
|
|
1080
1213
|
if (!response.ok) {
|
|
1081
|
-
throw
|
|
1214
|
+
throw await httpError(response, "Failed to fetch channels");
|
|
1082
1215
|
}
|
|
1083
1216
|
return channelsResponseSchema.parse(await response.json()).data;
|
|
1084
1217
|
}
|
|
@@ -1097,12 +1230,12 @@ async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken,
|
|
|
1097
1230
|
}
|
|
1098
1231
|
);
|
|
1099
1232
|
if (response.status === 401 || response.status === 403) {
|
|
1100
|
-
throw new
|
|
1233
|
+
throw new UserActionableError(
|
|
1101
1234
|
`Failed to update channel site (${response.status}). Re-run \`catalyst auth login\` to refresh your access token with the store_channel_settings scope.`
|
|
1102
1235
|
);
|
|
1103
1236
|
}
|
|
1104
1237
|
if (!response.ok) {
|
|
1105
|
-
throw
|
|
1238
|
+
throw await httpError(response, "Failed to update channel site");
|
|
1106
1239
|
}
|
|
1107
1240
|
const res = await response.json();
|
|
1108
1241
|
const { data } = channelSiteSchema.parse(res);
|
|
@@ -1111,10 +1244,10 @@ async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken,
|
|
|
1111
1244
|
|
|
1112
1245
|
// src/cli/lib/commerce-hosting.ts
|
|
1113
1246
|
import { confirm as confirm2, input as input2, select, Separator } from "@inquirer/prompts";
|
|
1114
|
-
import { colorize as
|
|
1115
|
-
import { existsSync as
|
|
1247
|
+
import { colorize as colorize3 } from "consola/utils";
|
|
1248
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
1116
1249
|
import { dirname as dirname2, join as join5 } from "path";
|
|
1117
|
-
import { z as
|
|
1250
|
+
import { z as z8 } from "zod";
|
|
1118
1251
|
|
|
1119
1252
|
// src/cli/lib/sort-package-json.ts
|
|
1120
1253
|
var FIELD_ORDER = [
|
|
@@ -1144,8 +1277,8 @@ function sortPackageJsonFields(pkg) {
|
|
|
1144
1277
|
|
|
1145
1278
|
// src/cli/lib/commerce-hosting.ts
|
|
1146
1279
|
var OPENNEXT_CLOUDFLARE_VERSION = "1.17.3";
|
|
1147
|
-
var corePackageJsonSchema =
|
|
1148
|
-
dependencies:
|
|
1280
|
+
var corePackageJsonSchema = z8.looseObject({
|
|
1281
|
+
dependencies: z8.record(z8.string(), z8.string()).optional()
|
|
1149
1282
|
});
|
|
1150
1283
|
var writeJson = (path, value) => {
|
|
1151
1284
|
mkdirSync(dirname2(path), { recursive: true });
|
|
@@ -1155,14 +1288,14 @@ var writeJson = (path, value) => {
|
|
|
1155
1288
|
var convertProxyToMiddleware = (projectDir) => {
|
|
1156
1289
|
const proxyPath = join5(projectDir, "proxy.ts");
|
|
1157
1290
|
const middlewarePath = join5(projectDir, "middleware.ts");
|
|
1158
|
-
if (!
|
|
1291
|
+
if (!existsSync3(proxyPath)) return;
|
|
1159
1292
|
const contents = readFileSync2(proxyPath, "utf-8").replace("export const proxy", "export const middleware").replace("export const config = {", "export const config = {\n runtime: 'experimental-edge',");
|
|
1160
1293
|
writeFileSync(middlewarePath, contents);
|
|
1161
1294
|
unlinkSync(proxyPath);
|
|
1162
1295
|
};
|
|
1163
1296
|
var cleanupCloudflareIncompatibilities = async (projectDir) => {
|
|
1164
1297
|
const instrumentationPath = join5(projectDir, "instrumentation.ts");
|
|
1165
|
-
if (!
|
|
1298
|
+
if (!existsSync3(instrumentationPath)) return;
|
|
1166
1299
|
if (!process.stdin.isTTY) {
|
|
1167
1300
|
consola.warn(
|
|
1168
1301
|
"instrumentation.ts is present and may be incompatible with the Cloudflare Workers bundle (the default @vercel/otel scaffolding throws at cold start under workerd). Skipping automatic cleanup in non-interactive mode \u2014 re-run interactively to remove it, or delete/gate it manually."
|
|
@@ -1182,7 +1315,7 @@ var cleanupCloudflareIncompatibilities = async (projectDir) => {
|
|
|
1182
1315
|
unlinkSync(instrumentationPath);
|
|
1183
1316
|
consola.info("Removed instrumentation.ts (incompatible with Cloudflare Workers).");
|
|
1184
1317
|
const corePackageJsonPath = join5(projectDir, "package.json");
|
|
1185
|
-
if (
|
|
1318
|
+
if (existsSync3(corePackageJsonPath)) {
|
|
1186
1319
|
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
|
|
1187
1320
|
if (pkg.dependencies && "@vercel/otel" in pkg.dependencies) {
|
|
1188
1321
|
const { "@vercel/otel": _removedVercelOtel, ...preservedDeps } = pkg.dependencies;
|
|
@@ -1243,7 +1376,7 @@ async function selectOrCreateInfrastructureProject(api, linkedProjectUuid) {
|
|
|
1243
1376
|
}
|
|
1244
1377
|
const choices = [
|
|
1245
1378
|
...existingProjects.map((p) => ({
|
|
1246
|
-
name: p.uuid === linkedProjectUuid ? `${p.name} ${
|
|
1379
|
+
name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize3("green", "[linked]")}` : p.name,
|
|
1247
1380
|
value: p.uuid,
|
|
1248
1381
|
description: p.uuid
|
|
1249
1382
|
})),
|
|
@@ -1340,7 +1473,7 @@ async function promptAndCreateCommerceHostingProject(api, takenNames, defaultNam
|
|
|
1340
1473
|
},
|
|
1341
1474
|
theme: {
|
|
1342
1475
|
style: {
|
|
1343
|
-
help: () =>
|
|
1476
|
+
help: () => colorize3(
|
|
1344
1477
|
"dim",
|
|
1345
1478
|
"(The project that hosts your storefront on Commerce \u2014 often matches your folder name.)"
|
|
1346
1479
|
)
|
|
@@ -1486,17 +1619,149 @@ async function runChannelSiteUrlFlow(options) {
|
|
|
1486
1619
|
consola.success(`Updated channel ${channelLabel} site URL to ${siteUrl}.`);
|
|
1487
1620
|
}
|
|
1488
1621
|
|
|
1622
|
+
// src/cli/lib/create-channel-flow.ts
|
|
1623
|
+
import { checkbox, input as input3, select as select3 } from "@inquirer/prompts";
|
|
1624
|
+
import { colorize as colorize4 } from "consola/utils";
|
|
1625
|
+
|
|
1626
|
+
// src/cli/lib/localization.ts
|
|
1627
|
+
import { z as z9 } from "zod";
|
|
1628
|
+
var allowedLocales = [
|
|
1629
|
+
"en",
|
|
1630
|
+
"da",
|
|
1631
|
+
"es-AR",
|
|
1632
|
+
"es-CL",
|
|
1633
|
+
"es-CO",
|
|
1634
|
+
"es-MX",
|
|
1635
|
+
"es-PE",
|
|
1636
|
+
"es-419",
|
|
1637
|
+
"es",
|
|
1638
|
+
"it",
|
|
1639
|
+
"nl",
|
|
1640
|
+
"pl",
|
|
1641
|
+
"pt",
|
|
1642
|
+
"de",
|
|
1643
|
+
"fr",
|
|
1644
|
+
"ja",
|
|
1645
|
+
"no",
|
|
1646
|
+
"pt-BR",
|
|
1647
|
+
"sv"
|
|
1648
|
+
];
|
|
1649
|
+
var AvailableLocalesSuccessSchema = z9.object({
|
|
1650
|
+
data: z9.array(
|
|
1651
|
+
z9.object({
|
|
1652
|
+
id: z9.string(),
|
|
1653
|
+
name: z9.string(),
|
|
1654
|
+
fallback: z9.string().nullable(),
|
|
1655
|
+
is_supported: z9.boolean()
|
|
1656
|
+
})
|
|
1657
|
+
)
|
|
1658
|
+
});
|
|
1659
|
+
var getAvailableLocales = async (storeHash, accessToken, apiHost) => {
|
|
1660
|
+
const response = await fetch(
|
|
1661
|
+
`https://${apiHost}/stores/${storeHash}/v3/settings/store/available-locales`,
|
|
1662
|
+
{
|
|
1663
|
+
method: "GET",
|
|
1664
|
+
headers: {
|
|
1665
|
+
"X-Auth-Token": accessToken,
|
|
1666
|
+
"X-Correlation-Id": getTelemetry().correlationId,
|
|
1667
|
+
Accept: "application/json"
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
);
|
|
1671
|
+
assertAuthorized(response);
|
|
1672
|
+
if (!response.ok) {
|
|
1673
|
+
throw await httpError(response, "Failed to fetch available locales");
|
|
1674
|
+
}
|
|
1675
|
+
return AvailableLocalesSuccessSchema.parse(await response.json()).data.filter(({ id }) => allowedLocales.includes(id)).map(({ name, id }) => ({ name: `${name} (${id})`, value: id }));
|
|
1676
|
+
};
|
|
1677
|
+
|
|
1678
|
+
// src/cli/lib/create-channel-flow.ts
|
|
1679
|
+
var ALLOWED_CHANNEL_NAME_CHARS = "letters, numbers, spaces, hyphens (-), and underscores (_)";
|
|
1680
|
+
var CHANNEL_NAME_PATTERN = /^[\p{L}\p{N} _-]+$/u;
|
|
1681
|
+
function getChannelNameError(name) {
|
|
1682
|
+
if (name.trim().length === 0) {
|
|
1683
|
+
return "Channel name cannot be empty.";
|
|
1684
|
+
}
|
|
1685
|
+
if (!CHANNEL_NAME_PATTERN.test(name)) {
|
|
1686
|
+
return `"${name}" is not a valid channel name. Channel names may contain only ${ALLOWED_CHANNEL_NAME_CHARS}.`;
|
|
1687
|
+
}
|
|
1688
|
+
return void 0;
|
|
1689
|
+
}
|
|
1690
|
+
async function runCreateChannelFlow(options) {
|
|
1691
|
+
const { storeHash, accessToken, apiHost, cliApiOrigin } = options;
|
|
1692
|
+
if (options.name !== void 0) {
|
|
1693
|
+
const nameError = getChannelNameError(options.name);
|
|
1694
|
+
if (nameError) {
|
|
1695
|
+
throw new UserActionableError(nameError);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
const name = options.name ?? await input3({
|
|
1699
|
+
message: "What would you like to name your new channel?",
|
|
1700
|
+
validate: (value) => getChannelNameError(value) ?? true
|
|
1701
|
+
});
|
|
1702
|
+
let availableLocales;
|
|
1703
|
+
const loadLocales = async () => {
|
|
1704
|
+
availableLocales ?? (availableLocales = await getAvailableLocales(storeHash, accessToken, apiHost));
|
|
1705
|
+
return availableLocales;
|
|
1706
|
+
};
|
|
1707
|
+
const storefrontLocale = options.locale ?? await select3({
|
|
1708
|
+
message: "Which default language would you like to set for your channel?",
|
|
1709
|
+
default: "en",
|
|
1710
|
+
choices: await loadLocales(),
|
|
1711
|
+
theme: {
|
|
1712
|
+
style: {
|
|
1713
|
+
help: () => colorize4("dim", "(Select locale from the list or start typing the name)")
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
let additionalLocales = options.additionalLocales;
|
|
1718
|
+
if (additionalLocales === void 0) {
|
|
1719
|
+
const shouldAddAdditionalLocales = await select3({
|
|
1720
|
+
message: "Would you like to add additional languages?",
|
|
1721
|
+
choices: [
|
|
1722
|
+
{ name: "Yes", value: true },
|
|
1723
|
+
{ name: "No", value: false }
|
|
1724
|
+
]
|
|
1725
|
+
});
|
|
1726
|
+
additionalLocales = [];
|
|
1727
|
+
if (shouldAddAdditionalLocales) {
|
|
1728
|
+
const localeChoices = (await loadLocales()).filter(({ value }) => value !== storefrontLocale).map(({ name: localeName, value }) => ({ name: localeName, value, description: value }));
|
|
1729
|
+
additionalLocales = await checkbox({
|
|
1730
|
+
message: "Which additional languages would you like to add to your channel?",
|
|
1731
|
+
choices: localeChoices,
|
|
1732
|
+
validate: (items) => items.length <= 4 || "You can only select up to 4 additional languages."
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
const shouldInstallSampleData = options.sampleData ?? await select3({
|
|
1737
|
+
message: "Would you like to install sample data?",
|
|
1738
|
+
choices: [
|
|
1739
|
+
{ name: "Yes", value: true },
|
|
1740
|
+
{ name: "No", value: false }
|
|
1741
|
+
]
|
|
1742
|
+
});
|
|
1743
|
+
return createChannel(
|
|
1744
|
+
name,
|
|
1745
|
+
storefrontLocale,
|
|
1746
|
+
additionalLocales,
|
|
1747
|
+
shouldInstallSampleData,
|
|
1748
|
+
storeHash,
|
|
1749
|
+
accessToken,
|
|
1750
|
+
cliApiOrigin
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1489
1754
|
// src/cli/lib/env-config.ts
|
|
1490
1755
|
var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1491
|
-
var parseEnvAssignment = (
|
|
1492
|
-
const separatorIndex =
|
|
1756
|
+
var parseEnvAssignment = (input6) => {
|
|
1757
|
+
const separatorIndex = input6.indexOf("=");
|
|
1493
1758
|
if (separatorIndex === -1) {
|
|
1494
|
-
throw new Error(`Invalid env var format: ${
|
|
1759
|
+
throw new Error(`Invalid env var format: ${input6}. Expected format: KEY=VALUE`);
|
|
1495
1760
|
}
|
|
1496
|
-
const key =
|
|
1497
|
-
const value =
|
|
1761
|
+
const key = input6.slice(0, separatorIndex).trim();
|
|
1762
|
+
const value = input6.slice(separatorIndex + 1).trim();
|
|
1498
1763
|
if (!key || !value) {
|
|
1499
|
-
throw new Error(`Invalid env var format: ${
|
|
1764
|
+
throw new Error(`Invalid env var format: ${input6}. Expected format: KEY=VALUE`);
|
|
1500
1765
|
}
|
|
1501
1766
|
if (!ENV_KEY_PATTERN.test(key)) {
|
|
1502
1767
|
throw new Error(
|
|
@@ -1533,22 +1798,89 @@ function resolveCredentials(options, config2) {
|
|
|
1533
1798
|
return { storeHash, accessToken };
|
|
1534
1799
|
}
|
|
1535
1800
|
|
|
1801
|
+
// src/cli/lib/write-env.ts
|
|
1802
|
+
import { outputFileSync } from "fs-extra/esm";
|
|
1803
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1804
|
+
import { join as join6 } from "path";
|
|
1805
|
+
var ACTIVE_KEY_LINE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
1806
|
+
var keyFromLine = (line) => {
|
|
1807
|
+
const match = ACTIVE_KEY_LINE.exec(line);
|
|
1808
|
+
return match ? match[1] : null;
|
|
1809
|
+
};
|
|
1810
|
+
var parseEnvValues = (contents) => {
|
|
1811
|
+
const values = /* @__PURE__ */ new Map();
|
|
1812
|
+
contents.split("\n").forEach((line) => {
|
|
1813
|
+
const match = ACTIVE_KEY_LINE.exec(line);
|
|
1814
|
+
if (match) {
|
|
1815
|
+
values.set(match[1], match[2]);
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
return values;
|
|
1819
|
+
};
|
|
1820
|
+
var writeEnv = (projectDir, envVars) => {
|
|
1821
|
+
const examplePath = join6(projectDir, ".env.example");
|
|
1822
|
+
const localPath = join6(projectDir, ".env.local");
|
|
1823
|
+
const existingLocal = existsSync4(localPath) ? parseEnvValues(readFileSync3(localPath, "utf-8")) : /* @__PURE__ */ new Map();
|
|
1824
|
+
const resolve2 = (key) => {
|
|
1825
|
+
if (key in envVars && envVars[key] !== "") {
|
|
1826
|
+
return envVars[key];
|
|
1827
|
+
}
|
|
1828
|
+
return existingLocal.get(key);
|
|
1829
|
+
};
|
|
1830
|
+
if (!existsSync4(examplePath)) {
|
|
1831
|
+
const keys = [.../* @__PURE__ */ new Set([...existingLocal.keys(), ...Object.keys(envVars)])];
|
|
1832
|
+
outputFileSync(localPath, `${keys.map((key) => `${key}=${resolve2(key) ?? ""}`).join("\n")}
|
|
1833
|
+
`);
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
const exampleLines = readFileSync3(examplePath, "utf-8").replace(/\n$/, "").split("\n");
|
|
1837
|
+
const exampleKeys = /* @__PURE__ */ new Set();
|
|
1838
|
+
const lines = exampleLines.map((line) => {
|
|
1839
|
+
const key = keyFromLine(line);
|
|
1840
|
+
if (key === null) {
|
|
1841
|
+
return line;
|
|
1842
|
+
}
|
|
1843
|
+
exampleKeys.add(key);
|
|
1844
|
+
const value = resolve2(key);
|
|
1845
|
+
return value === void 0 ? line : `${key}=${value}`;
|
|
1846
|
+
});
|
|
1847
|
+
const extraKeys = [];
|
|
1848
|
+
[...existingLocal.keys()].forEach((key) => {
|
|
1849
|
+
if (!exampleKeys.has(key)) {
|
|
1850
|
+
extraKeys.push(key);
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
Object.keys(envVars).forEach((key) => {
|
|
1854
|
+
if (!exampleKeys.has(key) && !extraKeys.includes(key)) {
|
|
1855
|
+
extraKeys.push(key);
|
|
1856
|
+
}
|
|
1857
|
+
});
|
|
1858
|
+
if (extraKeys.length > 0) {
|
|
1859
|
+
lines.push("", "# Additional variables set by the Catalyst CLI (not in .env.example).");
|
|
1860
|
+
extraKeys.forEach((key) => {
|
|
1861
|
+
lines.push(`${key}=${resolve2(key) ?? ""}`);
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
outputFileSync(localPath, `${lines.join("\n")}
|
|
1865
|
+
`);
|
|
1866
|
+
};
|
|
1867
|
+
|
|
1536
1868
|
// src/cli/commands/channel.ts
|
|
1537
1869
|
var parseChannelId = (value) => {
|
|
1538
1870
|
const parsed = Number.parseInt(value, 10);
|
|
1539
1871
|
if (Number.isNaN(parsed)) {
|
|
1540
|
-
throw new
|
|
1872
|
+
throw new InvalidArgumentError2(`"${value}" is not a valid channel ID (expected a number).`);
|
|
1541
1873
|
}
|
|
1542
1874
|
return parsed;
|
|
1543
1875
|
};
|
|
1544
|
-
async function resolveCredentialsWithLogin(options, config2) {
|
|
1876
|
+
async function resolveCredentialsWithLogin(options, config2, apiHost) {
|
|
1545
1877
|
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
1546
1878
|
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
1547
1879
|
if (storeHash && accessToken) {
|
|
1548
1880
|
return { storeHash, accessToken };
|
|
1549
1881
|
}
|
|
1550
1882
|
try {
|
|
1551
|
-
const credentials = await login(options.loginUrl,
|
|
1883
|
+
const credentials = await login(options.loginUrl, apiHost);
|
|
1552
1884
|
config2.set("storeHash", credentials.storeHash);
|
|
1553
1885
|
config2.set("accessToken", credentials.accessToken);
|
|
1554
1886
|
return credentials;
|
|
@@ -1571,24 +1903,25 @@ Examples:
|
|
|
1571
1903
|
# Skip both prompts
|
|
1572
1904
|
$ catalyst channel update --channel-id 123 --hostname my-storefront.example.com`
|
|
1573
1905
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
1574
|
-
new
|
|
1906
|
+
new Option3(
|
|
1575
1907
|
"--channel-id <id>",
|
|
1576
1908
|
"Skip the channel prompt and target this channel directly."
|
|
1577
1909
|
).argParser((value) => Number(value))
|
|
1578
1910
|
).addOption(
|
|
1579
|
-
new
|
|
1911
|
+
new Option3(
|
|
1580
1912
|
"--hostname <hostname>",
|
|
1581
1913
|
"Skip the hostname prompt and use this hostname directly. Must be one of the project's deployment_hostnames."
|
|
1582
1914
|
)
|
|
1583
1915
|
).action(async (options) => {
|
|
1584
1916
|
const config2 = getProjectConfig();
|
|
1917
|
+
const apiHost = resolveApiHost(options, config2);
|
|
1585
1918
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1586
1919
|
await getTelemetry().identify(storeHash);
|
|
1587
1920
|
try {
|
|
1588
1921
|
await runChannelSiteUrlFlow({
|
|
1589
1922
|
storeHash,
|
|
1590
1923
|
accessToken,
|
|
1591
|
-
apiHost
|
|
1924
|
+
apiHost,
|
|
1592
1925
|
projectUuid: options.projectUuid ?? config2.get("projectUuid"),
|
|
1593
1926
|
channelId: options.channelId,
|
|
1594
1927
|
hostname: options.hostname
|
|
@@ -1620,17 +1953,18 @@ Examples:
|
|
|
1620
1953
|
# Append extra environment variables to .env.local
|
|
1621
1954
|
$ catalyst channel link --channel-id 123 --env MY_FLAG=1`
|
|
1622
1955
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).addOption(
|
|
1623
|
-
new
|
|
1956
|
+
new Option3("--channel-id <id>", "Link this channel directly, skipping the picker.").argParser(
|
|
1624
1957
|
parseChannelId
|
|
1625
1958
|
)
|
|
1626
1959
|
).option(
|
|
1627
1960
|
"--env <vars...>",
|
|
1628
1961
|
"Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable)."
|
|
1629
1962
|
).addOption(
|
|
1630
|
-
new
|
|
1963
|
+
new Option3("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
1631
1964
|
).action(async (options) => {
|
|
1632
1965
|
const config2 = getProjectConfig();
|
|
1633
|
-
const
|
|
1966
|
+
const apiHost = resolveApiHost(options, config2);
|
|
1967
|
+
const credentials = await resolveCredentialsWithLogin(options, config2, apiHost);
|
|
1634
1968
|
if (!credentials) {
|
|
1635
1969
|
consola.info(
|
|
1636
1970
|
"Login aborted. Re-run `catalyst channel link` when you have your credentials ready."
|
|
@@ -1643,7 +1977,7 @@ Examples:
|
|
|
1643
1977
|
let channelId = options.channelId;
|
|
1644
1978
|
let channelName;
|
|
1645
1979
|
if (channelId === void 0) {
|
|
1646
|
-
const channels = await fetchAvailableChannels(storeHash, accessToken,
|
|
1980
|
+
const channels = await fetchAvailableChannels(storeHash, accessToken, apiHost);
|
|
1647
1981
|
if (channels.length === 0) {
|
|
1648
1982
|
consola.info(
|
|
1649
1983
|
"No storefront channels found on this store. Create one with `catalyst create` and try again."
|
|
@@ -1651,7 +1985,7 @@ Examples:
|
|
|
1651
1985
|
process.exit(0);
|
|
1652
1986
|
return;
|
|
1653
1987
|
}
|
|
1654
|
-
channelId = await
|
|
1988
|
+
channelId = await select4({
|
|
1655
1989
|
message: "Which channel would you like to link?",
|
|
1656
1990
|
choices: sortChannelsByPlatform(channels).map((c) => ({
|
|
1657
1991
|
name: c.name,
|
|
@@ -1669,32 +2003,108 @@ Examples:
|
|
|
1669
2003
|
envVars[key] = value;
|
|
1670
2004
|
});
|
|
1671
2005
|
}
|
|
1672
|
-
|
|
1673
|
-
join6(process.cwd(), ".env.local"),
|
|
1674
|
-
`${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
1675
|
-
`
|
|
1676
|
-
);
|
|
2006
|
+
writeEnv(process.cwd(), envVars);
|
|
1677
2007
|
const label = channelName ? `"${channelName}" (${channelId})` : `${channelId}`;
|
|
1678
2008
|
consola.success(
|
|
1679
|
-
`Linked to channel ${label} and wrote ${
|
|
2009
|
+
`Linked to channel ${label} and wrote ${colorize5("cyanBright", ".env.local")}.`
|
|
1680
2010
|
);
|
|
1681
2011
|
consola.log(`Next steps:
|
|
1682
2012
|
|
|
1683
|
-
${
|
|
2013
|
+
${colorize5("yellow", "pnpm run dev")}`);
|
|
2014
|
+
process.exit(0);
|
|
2015
|
+
});
|
|
2016
|
+
var create = new Command3("create").configureHelp({ showGlobalOptions: true }).description("Create a new Catalyst storefront channel on your BigCommerce store.").addHelpText(
|
|
2017
|
+
"after",
|
|
2018
|
+
`
|
|
2019
|
+
Examples:
|
|
2020
|
+
# Create a channel interactively (logs you in if needed)
|
|
2021
|
+
$ catalyst channel create
|
|
2022
|
+
|
|
2023
|
+
# Non-interactive, and link it to this project afterwards
|
|
2024
|
+
$ catalyst channel create --name "My Store" --locale en --no-sample-data --link
|
|
2025
|
+
|
|
2026
|
+
# Add additional storefront languages (max 4)
|
|
2027
|
+
$ catalyst channel create --name "My Store" --additional-locales es fr`
|
|
2028
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).option("--name <name>", "Name for the new channel. Skips the name prompt.").option(
|
|
2029
|
+
"--locale <locale>",
|
|
2030
|
+
'Default storefront locale (e.g. "en"). Skips the default-language prompt.'
|
|
2031
|
+
).option(
|
|
2032
|
+
"--additional-locales <locales...>",
|
|
2033
|
+
"Additional storefront locales, max 4 (e.g. --additional-locales es fr). Skips the additional-languages prompt."
|
|
2034
|
+
).addOption(new Option3("--sample-data", "Install sample data on the new channel.")).addOption(new Option3("--no-sample-data", "Create the channel without sample data.")).option(
|
|
2035
|
+
"--link",
|
|
2036
|
+
"Link the new channel to this project \u2014 write its credentials to .env.local without prompting."
|
|
2037
|
+
).addOption(
|
|
2038
|
+
new Option3("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
2039
|
+
).action(async (options) => {
|
|
2040
|
+
if (options.additionalLocales && options.additionalLocales.length > 4) {
|
|
2041
|
+
consola.error("You can only set up to 4 additional locales.");
|
|
2042
|
+
process.exit(1);
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
const config2 = getProjectConfig();
|
|
2046
|
+
const apiHost = resolveApiHost(options, config2);
|
|
2047
|
+
const credentials = await resolveCredentialsWithLogin(options, config2, apiHost);
|
|
2048
|
+
if (!credentials) {
|
|
2049
|
+
consola.info(
|
|
2050
|
+
"Login aborted. Re-run `catalyst channel create` when you have your credentials ready."
|
|
2051
|
+
);
|
|
2052
|
+
process.exit(0);
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
const { storeHash, accessToken } = credentials;
|
|
2056
|
+
await getTelemetry().identify(storeHash);
|
|
2057
|
+
const eligibility = await checkChannelEligibility(storeHash, accessToken, options.cliApiOrigin);
|
|
2058
|
+
if (!eligibility.eligible) {
|
|
2059
|
+
consola.warn(eligibility.message);
|
|
2060
|
+
process.exit(0);
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
const channelData = await runCreateChannelFlow({
|
|
2064
|
+
storeHash,
|
|
2065
|
+
accessToken,
|
|
2066
|
+
apiHost,
|
|
2067
|
+
cliApiOrigin: options.cliApiOrigin,
|
|
2068
|
+
name: options.name,
|
|
2069
|
+
locale: options.locale,
|
|
2070
|
+
additionalLocales: options.additionalLocales,
|
|
2071
|
+
sampleData: options.sampleData
|
|
2072
|
+
});
|
|
2073
|
+
consola.success(`Created channel ${channelData.channelId}.`);
|
|
2074
|
+
consola.warn(
|
|
2075
|
+
"A preview storefront has been deployed in your BigCommerce control panel. This preview may look different from your local environment as it may be running different code. Additionally, it may take a few minutes for the channel storefront to be accessible."
|
|
2076
|
+
);
|
|
2077
|
+
const shouldLink = options.link ? true : await select4({
|
|
2078
|
+
message: "Would you like to link this channel to your project?",
|
|
2079
|
+
choices: [
|
|
2080
|
+
{ name: "Yes", value: true },
|
|
2081
|
+
{ name: "No", value: false }
|
|
2082
|
+
]
|
|
2083
|
+
});
|
|
2084
|
+
if (shouldLink) {
|
|
2085
|
+
writeEnv(process.cwd(), channelData.envVars);
|
|
2086
|
+
consola.success(
|
|
2087
|
+
`Linked to channel ${channelData.channelId} and wrote ${colorize5("cyanBright", ".env.local")}.`
|
|
2088
|
+
);
|
|
2089
|
+
consola.log(`Next steps:
|
|
2090
|
+
|
|
2091
|
+
${colorize5("yellow", "pnpm run dev")}`);
|
|
2092
|
+
}
|
|
1684
2093
|
process.exit(0);
|
|
1685
2094
|
});
|
|
1686
|
-
var channel = new Command3("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(link).addCommand(update);
|
|
2095
|
+
var channel = new Command3("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(create).addCommand(link).addCommand(update);
|
|
1687
2096
|
|
|
1688
2097
|
// src/cli/commands/create.ts
|
|
1689
|
-
import { Command as Command4, InvalidArgumentError as
|
|
1690
|
-
import {
|
|
2098
|
+
import { Command as Command4, InvalidArgumentError as InvalidArgumentError3, Option as Option4 } from "@commander-js/extra-typings";
|
|
2099
|
+
import { input as input4, select as select5 } from "@inquirer/prompts";
|
|
1691
2100
|
import { execSync as execSync2 } from "child_process";
|
|
1692
|
-
import { colorize as
|
|
2101
|
+
import { colorize as colorize6 } from "consola/utils";
|
|
1693
2102
|
import { pathExistsSync } from "fs-extra/esm";
|
|
1694
2103
|
import kebabCase from "lodash.kebabcase";
|
|
1695
|
-
import { join as
|
|
2104
|
+
import { join as join10 } from "path";
|
|
1696
2105
|
|
|
1697
2106
|
// src/cli/lib/detect-package-manager.ts
|
|
2107
|
+
import { detectPackageManager as detectFromDir } from "nypm";
|
|
1698
2108
|
var detectPackageManager = () => {
|
|
1699
2109
|
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
1700
2110
|
const name = userAgent.split(" ")[0]?.split("/")[0];
|
|
@@ -1706,6 +2116,14 @@ var detectPackageManager = () => {
|
|
|
1706
2116
|
}
|
|
1707
2117
|
return "npm";
|
|
1708
2118
|
};
|
|
2119
|
+
var detectProjectPackageManager = async (projectDir) => {
|
|
2120
|
+
const detected = await detectFromDir(projectDir);
|
|
2121
|
+
const name = detected?.name;
|
|
2122
|
+
if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") {
|
|
2123
|
+
return name;
|
|
2124
|
+
}
|
|
2125
|
+
return "npm";
|
|
2126
|
+
};
|
|
1709
2127
|
|
|
1710
2128
|
// src/cli/lib/extract-catalyst.ts
|
|
1711
2129
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -1779,12 +2197,32 @@ var initGitRepo = (projectDir) => {
|
|
|
1779
2197
|
// src/cli/lib/install-dependencies.ts
|
|
1780
2198
|
import { installDependencies as installDeps } from "nypm";
|
|
1781
2199
|
import yoctoSpinner2 from "yocto-spinner";
|
|
2200
|
+
var restoreEnv = (key, previous) => {
|
|
2201
|
+
if (previous === void 0) {
|
|
2202
|
+
Reflect.deleteProperty(process.env, key);
|
|
2203
|
+
} else {
|
|
2204
|
+
process.env[key] = previous;
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
2207
|
+
var withQuietInstallEnv = async (run2) => {
|
|
2208
|
+
var _a, _b;
|
|
2209
|
+
const prevNodeNoWarnings = process.env.NODE_NO_WARNINGS;
|
|
2210
|
+
const prevDownloadPrompt = process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT;
|
|
2211
|
+
(_a = process.env).NODE_NO_WARNINGS ?? (_a.NODE_NO_WARNINGS = "1");
|
|
2212
|
+
(_b = process.env).COREPACK_ENABLE_DOWNLOAD_PROMPT ?? (_b.COREPACK_ENABLE_DOWNLOAD_PROMPT = "0");
|
|
2213
|
+
try {
|
|
2214
|
+
return await run2();
|
|
2215
|
+
} finally {
|
|
2216
|
+
restoreEnv("NODE_NO_WARNINGS", prevNodeNoWarnings);
|
|
2217
|
+
restoreEnv("COREPACK_ENABLE_DOWNLOAD_PROMPT", prevDownloadPrompt);
|
|
2218
|
+
}
|
|
2219
|
+
};
|
|
1782
2220
|
var installDependencies = async (projectDir, packageManager = "pnpm") => {
|
|
1783
2221
|
const spinner = yoctoSpinner2().start(
|
|
1784
2222
|
`Installing dependencies with ${packageManager}. This could take a minute...`
|
|
1785
2223
|
);
|
|
1786
2224
|
try {
|
|
1787
|
-
await installDeps({ cwd: projectDir, silent: true, packageManager });
|
|
2225
|
+
await withQuietInstallEnv(() => installDeps({ cwd: projectDir, silent: true, packageManager }));
|
|
1788
2226
|
spinner.success("Dependencies installed successfully.");
|
|
1789
2227
|
} catch (error) {
|
|
1790
2228
|
const message = error instanceof Error ? error.message : "unknown error";
|
|
@@ -1793,64 +2231,10 @@ var installDependencies = async (projectDir, packageManager = "pnpm") => {
|
|
|
1793
2231
|
}
|
|
1794
2232
|
};
|
|
1795
2233
|
|
|
1796
|
-
// src/cli/lib/localization.ts
|
|
1797
|
-
import { z as z8 } from "zod";
|
|
1798
|
-
var allowedLocales = [
|
|
1799
|
-
"en",
|
|
1800
|
-
"da",
|
|
1801
|
-
"es-AR",
|
|
1802
|
-
"es-CL",
|
|
1803
|
-
"es-CO",
|
|
1804
|
-
"es-MX",
|
|
1805
|
-
"es-PE",
|
|
1806
|
-
"es-419",
|
|
1807
|
-
"es",
|
|
1808
|
-
"it",
|
|
1809
|
-
"nl",
|
|
1810
|
-
"pl",
|
|
1811
|
-
"pt",
|
|
1812
|
-
"de",
|
|
1813
|
-
"fr",
|
|
1814
|
-
"ja",
|
|
1815
|
-
"no",
|
|
1816
|
-
"pt-BR",
|
|
1817
|
-
"sv"
|
|
1818
|
-
];
|
|
1819
|
-
var AvailableLocalesSuccessSchema = z8.object({
|
|
1820
|
-
data: z8.array(
|
|
1821
|
-
z8.object({
|
|
1822
|
-
id: z8.string(),
|
|
1823
|
-
name: z8.string(),
|
|
1824
|
-
fallback: z8.string().nullable(),
|
|
1825
|
-
is_supported: z8.boolean()
|
|
1826
|
-
})
|
|
1827
|
-
)
|
|
1828
|
-
});
|
|
1829
|
-
var getAvailableLocales = async (storeHash, accessToken, apiHost) => {
|
|
1830
|
-
const response = await fetch(
|
|
1831
|
-
`https://${apiHost}/stores/${storeHash}/v3/settings/store/available-locales`,
|
|
1832
|
-
{
|
|
1833
|
-
method: "GET",
|
|
1834
|
-
headers: {
|
|
1835
|
-
"X-Auth-Token": accessToken,
|
|
1836
|
-
"X-Correlation-Id": getTelemetry().correlationId,
|
|
1837
|
-
Accept: "application/json"
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
);
|
|
1841
|
-
assertAuthorized(response);
|
|
1842
|
-
if (!response.ok) {
|
|
1843
|
-
throw new Error(
|
|
1844
|
-
`GET /v3/settings/store/available-locales failed: ${response.status} ${response.statusText}`
|
|
1845
|
-
);
|
|
1846
|
-
}
|
|
1847
|
-
return AvailableLocalesSuccessSchema.parse(await response.json()).data.filter(({ id }) => allowedLocales.includes(id)).map(({ name, id }) => ({ name: `${name} (${id})`, value: id }));
|
|
1848
|
-
};
|
|
1849
|
-
|
|
1850
2234
|
// src/cli/lib/rewrite-core-package.ts
|
|
1851
|
-
import { readFileSync as
|
|
2235
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
1852
2236
|
import { join as join8 } from "path";
|
|
1853
|
-
import { z as
|
|
2237
|
+
import { z as z10 } from "zod";
|
|
1854
2238
|
var NATIVE_BUILD_ALLOWLIST = [
|
|
1855
2239
|
"@parcel/watcher",
|
|
1856
2240
|
"@swc/core",
|
|
@@ -1868,18 +2252,18 @@ var DEP_FIELDS = [
|
|
|
1868
2252
|
"optionalDependencies",
|
|
1869
2253
|
"peerDependencies"
|
|
1870
2254
|
];
|
|
1871
|
-
var corePackageJsonSchema2 =
|
|
1872
|
-
version:
|
|
1873
|
-
private:
|
|
1874
|
-
scripts:
|
|
1875
|
-
dependencies:
|
|
1876
|
-
devDependencies:
|
|
1877
|
-
optionalDependencies:
|
|
1878
|
-
peerDependencies:
|
|
1879
|
-
pnpm:
|
|
1880
|
-
catalyst:
|
|
2255
|
+
var corePackageJsonSchema2 = z10.looseObject({
|
|
2256
|
+
version: z10.string().optional(),
|
|
2257
|
+
private: z10.boolean().optional(),
|
|
2258
|
+
scripts: z10.record(z10.string(), z10.string()).optional(),
|
|
2259
|
+
dependencies: z10.record(z10.string(), z10.string()).optional(),
|
|
2260
|
+
devDependencies: z10.record(z10.string(), z10.string()).optional(),
|
|
2261
|
+
optionalDependencies: z10.record(z10.string(), z10.string()).optional(),
|
|
2262
|
+
peerDependencies: z10.record(z10.string(), z10.string()).optional(),
|
|
2263
|
+
pnpm: z10.looseObject({}).optional(),
|
|
2264
|
+
catalyst: z10.looseObject({}).optional()
|
|
1881
2265
|
});
|
|
1882
|
-
var registryVersionSchema =
|
|
2266
|
+
var registryVersionSchema = z10.object({ version: z10.string() });
|
|
1883
2267
|
var resolvePublishedVersion = async (pkgName) => {
|
|
1884
2268
|
const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`);
|
|
1885
2269
|
if (!response.ok) {
|
|
@@ -1892,7 +2276,7 @@ var resolvePublishedVersion = async (pkgName) => {
|
|
|
1892
2276
|
};
|
|
1893
2277
|
var rewriteCorePackage = async (projectDir, ref) => {
|
|
1894
2278
|
const packageJsonPath = join8(projectDir, "package.json");
|
|
1895
|
-
const pkg = corePackageJsonSchema2.parse(JSON.parse(
|
|
2279
|
+
const pkg = corePackageJsonSchema2.parse(JSON.parse(readFileSync4(packageJsonPath, "utf-8")));
|
|
1896
2280
|
const workspaceDeps = DEP_FIELDS.flatMap((field) => {
|
|
1897
2281
|
const deps = pkg[field];
|
|
1898
2282
|
if (!deps) return [];
|
|
@@ -1920,12 +2304,12 @@ var rewriteCorePackage = async (projectDir, ref) => {
|
|
|
1920
2304
|
};
|
|
1921
2305
|
|
|
1922
2306
|
// src/cli/lib/setup-core-project.ts
|
|
1923
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
2307
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
1924
2308
|
import { dirname as dirname3, join as join9 } from "path";
|
|
1925
|
-
import { z as
|
|
1926
|
-
var corePackageJsonSchema3 =
|
|
1927
|
-
scripts:
|
|
1928
|
-
|
|
2309
|
+
import { z as z11 } from "zod";
|
|
2310
|
+
var corePackageJsonSchema3 = z11.looseObject({
|
|
2311
|
+
scripts: z11.record(z11.string(), z11.string()).optional(),
|
|
2312
|
+
devDependencies: z11.record(z11.string(), z11.string()).optional()
|
|
1929
2313
|
});
|
|
1930
2314
|
var writeJson2 = (path, value) => {
|
|
1931
2315
|
mkdirSync3(dirname3(path), { recursive: true });
|
|
@@ -1934,31 +2318,20 @@ var writeJson2 = (path, value) => {
|
|
|
1934
2318
|
};
|
|
1935
2319
|
var setupCoreProject = (projectDir) => {
|
|
1936
2320
|
const corePackageJsonPath = join9(projectDir, "package.json");
|
|
1937
|
-
const pkg = corePackageJsonSchema3.parse(JSON.parse(
|
|
2321
|
+
const pkg = corePackageJsonSchema3.parse(JSON.parse(readFileSync5(corePackageJsonPath, "utf-8")));
|
|
1938
2322
|
pkg.scripts = {
|
|
1939
2323
|
...pkg.scripts,
|
|
1940
2324
|
build: "npm run generate && catalyst build",
|
|
1941
2325
|
start: "catalyst start",
|
|
1942
2326
|
deploy: "npm run generate && catalyst deploy"
|
|
1943
2327
|
};
|
|
1944
|
-
pkg.
|
|
1945
|
-
...pkg.
|
|
2328
|
+
pkg.devDependencies = {
|
|
2329
|
+
...pkg.devDependencies,
|
|
1946
2330
|
"@bigcommerce/catalyst": package_default.version
|
|
1947
2331
|
};
|
|
1948
2332
|
writeJson2(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1949
2333
|
};
|
|
1950
2334
|
|
|
1951
|
-
// src/cli/lib/write-env.ts
|
|
1952
|
-
import { outputFileSync as outputFileSync2 } from "fs-extra/esm";
|
|
1953
|
-
import { join as join10 } from "path";
|
|
1954
|
-
var writeEnv = (projectDir, envVars) => {
|
|
1955
|
-
outputFileSync2(
|
|
1956
|
-
join10(projectDir, ".env.local"),
|
|
1957
|
-
`${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
1958
|
-
`
|
|
1959
|
-
);
|
|
1960
|
-
};
|
|
1961
|
-
|
|
1962
2335
|
// src/cli/commands/create.ts
|
|
1963
2336
|
function getPlatformCheckCommand(command) {
|
|
1964
2337
|
const isWindows = process.platform === "win32";
|
|
@@ -1967,73 +2340,25 @@ function getPlatformCheckCommand(command) {
|
|
|
1967
2340
|
function parseChannelId2(value) {
|
|
1968
2341
|
const parsed = parseInt(value, 10);
|
|
1969
2342
|
if (Number.isNaN(parsed)) {
|
|
1970
|
-
throw new
|
|
2343
|
+
throw new InvalidArgumentError3(`"${value}" is not a valid channel ID (expected a number).`);
|
|
1971
2344
|
}
|
|
1972
2345
|
return parsed;
|
|
1973
2346
|
}
|
|
1974
2347
|
function parseEnvFlag(value, previous = {}) {
|
|
1975
2348
|
const eqIdx = value.indexOf("=");
|
|
1976
2349
|
if (eqIdx <= 0) {
|
|
1977
|
-
throw new
|
|
2350
|
+
throw new InvalidArgumentError3(`Expected KEY=VALUE, got "${value}".`);
|
|
1978
2351
|
}
|
|
1979
2352
|
const key = value.substring(0, eqIdx);
|
|
1980
2353
|
const val = value.substring(eqIdx + 1);
|
|
1981
2354
|
if (!val) {
|
|
1982
|
-
throw new
|
|
2355
|
+
throw new InvalidArgumentError3(`Expected KEY=VALUE with non-empty value, got "${value}".`);
|
|
1983
2356
|
}
|
|
1984
2357
|
return { ...previous, [key]: val };
|
|
1985
2358
|
}
|
|
1986
|
-
async function handleChannelCreation(storeHash, accessToken, apiHost, cliApiOrigin) {
|
|
1987
|
-
const newChannelName = await input3({
|
|
1988
|
-
message: "What would you like to name your new channel?"
|
|
1989
|
-
});
|
|
1990
|
-
const availableLocales = await getAvailableLocales(storeHash, accessToken, apiHost);
|
|
1991
|
-
const storefrontLocale = await select4({
|
|
1992
|
-
message: "Which default language would you like to set for your channel?",
|
|
1993
|
-
default: "en",
|
|
1994
|
-
choices: availableLocales,
|
|
1995
|
-
theme: {
|
|
1996
|
-
style: {
|
|
1997
|
-
help: () => colorize4("dim", "(Select locale from the list or start typing the name)")
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
});
|
|
2001
|
-
const shouldAddAdditionalLocales = await select4({
|
|
2002
|
-
message: "Would you like to add additional languages?",
|
|
2003
|
-
choices: [
|
|
2004
|
-
{ name: "Yes", value: true },
|
|
2005
|
-
{ name: "No", value: false }
|
|
2006
|
-
]
|
|
2007
|
-
});
|
|
2008
|
-
let additionalLocales = [];
|
|
2009
|
-
if (shouldAddAdditionalLocales) {
|
|
2010
|
-
const localeChoices = availableLocales.filter(({ value }) => value !== storefrontLocale).map(({ name, value }) => ({ name, value, description: value }));
|
|
2011
|
-
additionalLocales = await checkbox({
|
|
2012
|
-
message: "Which additional languages would you like to add to your channel?",
|
|
2013
|
-
choices: localeChoices,
|
|
2014
|
-
validate: (items) => items.length <= 4 || "You can only select up to 4 additional languages."
|
|
2015
|
-
});
|
|
2016
|
-
}
|
|
2017
|
-
const shouldInstallSampleData = await select4({
|
|
2018
|
-
message: "Would you like to install sample data?",
|
|
2019
|
-
choices: [
|
|
2020
|
-
{ name: "Yes", value: true },
|
|
2021
|
-
{ name: "No", value: false }
|
|
2022
|
-
]
|
|
2023
|
-
});
|
|
2024
|
-
return createChannel(
|
|
2025
|
-
newChannelName,
|
|
2026
|
-
storefrontLocale,
|
|
2027
|
-
additionalLocales,
|
|
2028
|
-
shouldInstallSampleData,
|
|
2029
|
-
storeHash,
|
|
2030
|
-
accessToken,
|
|
2031
|
-
cliApiOrigin
|
|
2032
|
-
);
|
|
2033
|
-
}
|
|
2034
2359
|
async function handleChannelSelection(storeHash, accessToken, apiHost) {
|
|
2035
2360
|
const channels = await fetchAvailableChannels(storeHash, accessToken, apiHost);
|
|
2036
|
-
const existingChannel = await
|
|
2361
|
+
const existingChannel = await select5({
|
|
2037
2362
|
message: "Which channel would you like to use?",
|
|
2038
2363
|
choices: sortChannelsByPlatform(channels).map((ch) => ({
|
|
2039
2364
|
name: ch.name,
|
|
@@ -2051,7 +2376,7 @@ async function setupProject(options) {
|
|
|
2051
2376
|
}
|
|
2052
2377
|
if (projectName) {
|
|
2053
2378
|
projectName = kebabCase(projectName);
|
|
2054
|
-
projectDir =
|
|
2379
|
+
projectDir = join10(options.projectDir, projectName);
|
|
2055
2380
|
if (pathExistsSync(projectDir)) {
|
|
2056
2381
|
consola.error(`${projectDir} already exists`);
|
|
2057
2382
|
process.exit(1);
|
|
@@ -2061,13 +2386,13 @@ async function setupProject(options) {
|
|
|
2061
2386
|
const validateProjectName = (i) => {
|
|
2062
2387
|
const formatted = kebabCase(i);
|
|
2063
2388
|
if (!formatted) return "Project name is required";
|
|
2064
|
-
const targetDir =
|
|
2389
|
+
const targetDir = join10(options.projectDir, formatted);
|
|
2065
2390
|
if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
|
|
2066
2391
|
projectName = formatted;
|
|
2067
2392
|
projectDir = targetDir;
|
|
2068
2393
|
return true;
|
|
2069
2394
|
};
|
|
2070
|
-
await
|
|
2395
|
+
await input4({
|
|
2071
2396
|
message: "What do you want to name your project directory?",
|
|
2072
2397
|
default: "my-catalyst-app",
|
|
2073
2398
|
validate: validateProjectName
|
|
@@ -2085,7 +2410,7 @@ function checkRequiredTools() {
|
|
|
2085
2410
|
process.exit(1);
|
|
2086
2411
|
}
|
|
2087
2412
|
}
|
|
2088
|
-
var
|
|
2413
|
+
var create2 = new Command4("create").configureHelp({ showGlobalOptions: true }).description("Scaffold and connect a Catalyst storefront to your BigCommerce store.").addHelpText(
|
|
2089
2414
|
"after",
|
|
2090
2415
|
`
|
|
2091
2416
|
Examples:
|
|
@@ -2106,7 +2431,7 @@ Examples:
|
|
|
2106
2431
|
"Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable).",
|
|
2107
2432
|
parseEnvFlag
|
|
2108
2433
|
).addOption(
|
|
2109
|
-
new
|
|
2434
|
+
new Option4(
|
|
2110
2435
|
"--hosting <mode>",
|
|
2111
2436
|
'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.'
|
|
2112
2437
|
).choices(["self-hosted", "commerce"])
|
|
@@ -2114,11 +2439,11 @@ Examples:
|
|
|
2114
2439
|
"--use-existing",
|
|
2115
2440
|
"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."
|
|
2116
2441
|
).addOption(
|
|
2117
|
-
new
|
|
2442
|
+
new Option4("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
|
|
2118
2443
|
).addOption(
|
|
2119
|
-
new
|
|
2444
|
+
new Option4("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
|
|
2120
2445
|
).addOption(
|
|
2121
|
-
new
|
|
2446
|
+
new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
2122
2447
|
).action(async (options) => {
|
|
2123
2448
|
const { ghRef, repository } = options;
|
|
2124
2449
|
if (options.useExisting && options.hosting !== "commerce") {
|
|
@@ -2172,7 +2497,7 @@ Examples:
|
|
|
2172
2497
|
}
|
|
2173
2498
|
let shouldCreateChannel;
|
|
2174
2499
|
if (eligibility.eligible) {
|
|
2175
|
-
shouldCreateChannel = await
|
|
2500
|
+
shouldCreateChannel = await select5({
|
|
2176
2501
|
message: "Would you like to create a new channel?",
|
|
2177
2502
|
choices: [
|
|
2178
2503
|
{ name: "Yes", value: true },
|
|
@@ -2181,12 +2506,12 @@ Examples:
|
|
|
2181
2506
|
});
|
|
2182
2507
|
}
|
|
2183
2508
|
if (shouldCreateChannel) {
|
|
2184
|
-
const channelData = await
|
|
2509
|
+
const channelData = await runCreateChannelFlow({
|
|
2185
2510
|
storeHash,
|
|
2186
2511
|
accessToken,
|
|
2187
2512
|
apiHost,
|
|
2188
2513
|
cliApiOrigin
|
|
2189
|
-
);
|
|
2514
|
+
});
|
|
2190
2515
|
channelId = channelData.channelId;
|
|
2191
2516
|
storefrontToken = channelData.storefrontToken;
|
|
2192
2517
|
envVars = { ...channelData.envVars };
|
|
@@ -2265,19 +2590,280 @@ Examples:
|
|
|
2265
2590
|
consola.log(
|
|
2266
2591
|
`Next steps:
|
|
2267
2592
|
|
|
2268
|
-
${steps.map((step) => ` ${
|
|
2593
|
+
${steps.map((step) => ` ${colorize6("yellow", step)}`).join("\n")}`
|
|
2269
2594
|
);
|
|
2270
2595
|
});
|
|
2271
2596
|
|
|
2597
|
+
// src/cli/commands/debug.ts
|
|
2598
|
+
import { Command as Command5, Option as Option5 } from "commander";
|
|
2599
|
+
|
|
2600
|
+
// src/cli/lib/collect-diagnostics.ts
|
|
2601
|
+
import { parse } from "dotenv";
|
|
2602
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
2603
|
+
import { release } from "os";
|
|
2604
|
+
import { dirname as dirname4, join as join11 } from "path";
|
|
2605
|
+
import { z as z12 } from "zod";
|
|
2606
|
+
var CLI_ENV_VARS = [
|
|
2607
|
+
"CATALYST_STORE_HASH",
|
|
2608
|
+
"CATALYST_ACCESS_TOKEN",
|
|
2609
|
+
"CATALYST_PROJECT_UUID",
|
|
2610
|
+
"CATALYST_API_HOST",
|
|
2611
|
+
"BIGCOMMERCE_LOGIN_URL",
|
|
2612
|
+
"CATALYST_TELEMETRY_DISABLED"
|
|
2613
|
+
];
|
|
2614
|
+
var BUILD_ENV_VARS = [
|
|
2615
|
+
"BIGCOMMERCE_STORE_HASH",
|
|
2616
|
+
"BIGCOMMERCE_STOREFRONT_TOKEN",
|
|
2617
|
+
"BIGCOMMERCE_CHANNEL_ID",
|
|
2618
|
+
"AUTH_SECRET"
|
|
2619
|
+
];
|
|
2620
|
+
var ENV_FILES = [".env.local", ".env"];
|
|
2621
|
+
var PROJECT_LOCKFILES = [
|
|
2622
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2623
|
+
["yarn.lock", "yarn"],
|
|
2624
|
+
["bun.lock", "bun"],
|
|
2625
|
+
["bun.lockb", "bun"],
|
|
2626
|
+
["package-lock.json", "npm"]
|
|
2627
|
+
];
|
|
2628
|
+
var hasValue = (value) => value !== void 0 && value.trim() !== "";
|
|
2629
|
+
var buildEnvLayers = (cwd, env2) => {
|
|
2630
|
+
const layers = [{ source: "process.env", values: env2 }];
|
|
2631
|
+
ENV_FILES.forEach((file) => {
|
|
2632
|
+
const path = join11(cwd, file);
|
|
2633
|
+
if (!existsSync5(path)) {
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
try {
|
|
2637
|
+
layers.push({ source: file, values: parse(readFileSync6(path, "utf-8")) });
|
|
2638
|
+
} catch {
|
|
2639
|
+
}
|
|
2640
|
+
});
|
|
2641
|
+
return layers;
|
|
2642
|
+
};
|
|
2643
|
+
var resolveEnvSource = (layers, ...keys) => layers.find((layer) => keys.some((key) => hasValue(layer.values[key])))?.source;
|
|
2644
|
+
var resolveEnvVars = (layers, names) => Object.fromEntries(names.map((name) => [name, resolveEnvSource(layers, name) ?? "unset"]));
|
|
2645
|
+
var resolveValue = (envSource, configValue) => {
|
|
2646
|
+
if (envSource) {
|
|
2647
|
+
return { present: true, source: envSource };
|
|
2648
|
+
}
|
|
2649
|
+
if (hasValue(configValue)) {
|
|
2650
|
+
return { present: true, source: "project.json" };
|
|
2651
|
+
}
|
|
2652
|
+
return { present: false, source: "unset" };
|
|
2653
|
+
};
|
|
2654
|
+
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
2655
|
+
var lockfileManager = (dir) => PROJECT_LOCKFILES.find(([file]) => existsSync5(join11(dir, file)))?.[1];
|
|
2656
|
+
var detectProjectPackageManager2 = (cwd) => {
|
|
2657
|
+
let dir = cwd;
|
|
2658
|
+
for (; ; ) {
|
|
2659
|
+
const match = lockfileManager(dir);
|
|
2660
|
+
if (match) {
|
|
2661
|
+
return match;
|
|
2662
|
+
}
|
|
2663
|
+
const parent = dirname4(dir);
|
|
2664
|
+
if (parent === dir) {
|
|
2665
|
+
return detectPackageManager();
|
|
2666
|
+
}
|
|
2667
|
+
dir = parent;
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
var projectJsonSchema2 = z12.looseObject({});
|
|
2671
|
+
var safeReadJson2 = (path) => {
|
|
2672
|
+
try {
|
|
2673
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
2674
|
+
} catch {
|
|
2675
|
+
return null;
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
var readProjectJson = (cwd) => {
|
|
2679
|
+
const result = projectJsonSchema2.safeParse(
|
|
2680
|
+
safeReadJson2(join11(cwd, ".bigcommerce", "project.json"))
|
|
2681
|
+
);
|
|
2682
|
+
return result.success ? result.data : null;
|
|
2683
|
+
};
|
|
2684
|
+
var storefrontPackageSchema = z12.looseObject({
|
|
2685
|
+
name: z12.string().optional(),
|
|
2686
|
+
version: z12.string().optional(),
|
|
2687
|
+
catalyst: z12.looseObject({ version: z12.string().optional() }).optional()
|
|
2688
|
+
});
|
|
2689
|
+
var readStorefrontInfo = (cwd) => {
|
|
2690
|
+
const result = storefrontPackageSchema.safeParse(safeReadJson2(join11(cwd, "package.json")));
|
|
2691
|
+
if (!result.success) {
|
|
2692
|
+
return { name: null, version: null };
|
|
2693
|
+
}
|
|
2694
|
+
return {
|
|
2695
|
+
name: result.data.name ?? null,
|
|
2696
|
+
version: result.data.catalyst?.version ?? result.data.version ?? null
|
|
2697
|
+
};
|
|
2698
|
+
};
|
|
2699
|
+
var readStoredEnvKeys = (projectJson) => {
|
|
2700
|
+
const env2 = projectJson?.env;
|
|
2701
|
+
if (typeof env2 === "object" && env2 !== null && !Array.isArray(env2)) {
|
|
2702
|
+
return Object.keys(env2).sort();
|
|
2703
|
+
}
|
|
2704
|
+
return [];
|
|
2705
|
+
};
|
|
2706
|
+
function collectDiagnostics({
|
|
2707
|
+
cwd = process.cwd(),
|
|
2708
|
+
env: env2 = process.env
|
|
2709
|
+
} = {}) {
|
|
2710
|
+
const state = getProjectState(cwd);
|
|
2711
|
+
const telemetry3 = getTelemetry();
|
|
2712
|
+
const projectJson = readProjectJson(cwd);
|
|
2713
|
+
const storefront = readStorefrontInfo(cwd);
|
|
2714
|
+
const cliEnv = [{ source: "process.env", values: env2 }];
|
|
2715
|
+
const buildEnv = buildEnvLayers(cwd, env2);
|
|
2716
|
+
return {
|
|
2717
|
+
cli: {
|
|
2718
|
+
name: package_default.name,
|
|
2719
|
+
version: package_default.version
|
|
2720
|
+
},
|
|
2721
|
+
runtime: {
|
|
2722
|
+
node: process.version,
|
|
2723
|
+
platform: process.platform,
|
|
2724
|
+
arch: process.arch,
|
|
2725
|
+
osRelease: release(),
|
|
2726
|
+
packageManager: detectProjectPackageManager2(cwd)
|
|
2727
|
+
},
|
|
2728
|
+
project: {
|
|
2729
|
+
cwd,
|
|
2730
|
+
storefrontName: storefront.name,
|
|
2731
|
+
storefrontVersion: storefront.version,
|
|
2732
|
+
projectUuid: state.projectUuid ?? null,
|
|
2733
|
+
isLinked: state.isLinked,
|
|
2734
|
+
isTransformed: state.isTransformed,
|
|
2735
|
+
isFullySetUp: state.isFullySetUp,
|
|
2736
|
+
hasMiddleware: state.hasMiddleware,
|
|
2737
|
+
hasProxy: state.hasProxy,
|
|
2738
|
+
hasOpenNextDep: state.hasOpenNextDep
|
|
2739
|
+
},
|
|
2740
|
+
config: {
|
|
2741
|
+
storeHash: resolveValue(
|
|
2742
|
+
resolveEnvSource(cliEnv, "CATALYST_STORE_HASH", "BIGCOMMERCE_STORE_HASH"),
|
|
2743
|
+
asString(projectJson?.storeHash)
|
|
2744
|
+
),
|
|
2745
|
+
accessToken: resolveValue(
|
|
2746
|
+
resolveEnvSource(cliEnv, "CATALYST_ACCESS_TOKEN"),
|
|
2747
|
+
asString(projectJson?.accessToken)
|
|
2748
|
+
),
|
|
2749
|
+
projectUuid: resolveValue(
|
|
2750
|
+
resolveEnvSource(cliEnv, "CATALYST_PROJECT_UUID"),
|
|
2751
|
+
asString(projectJson?.projectUuid)
|
|
2752
|
+
),
|
|
2753
|
+
apiHost: resolveValue(
|
|
2754
|
+
resolveEnvSource(cliEnv, "CATALYST_API_HOST"),
|
|
2755
|
+
asString(projectJson?.apiHost)
|
|
2756
|
+
),
|
|
2757
|
+
projectJsonKeys: projectJson ? Object.keys(projectJson).sort() : [],
|
|
2758
|
+
storedEnvKeys: readStoredEnvKeys(projectJson),
|
|
2759
|
+
cliEnvVars: resolveEnvVars(cliEnv, CLI_ENV_VARS),
|
|
2760
|
+
buildEnvVars: resolveEnvVars(buildEnv, BUILD_ENV_VARS)
|
|
2761
|
+
},
|
|
2762
|
+
telemetry: {
|
|
2763
|
+
enabled: telemetry3.isEnabled(),
|
|
2764
|
+
correlationId: telemetry3.correlationId
|
|
2765
|
+
},
|
|
2766
|
+
files: {
|
|
2767
|
+
".env.local": existsSync5(join11(cwd, ".env.local")),
|
|
2768
|
+
".env": existsSync5(join11(cwd, ".env")),
|
|
2769
|
+
".bigcommerce/project.json": existsSync5(join11(cwd, ".bigcommerce", "project.json")),
|
|
2770
|
+
".bigcommerce/wrangler.jsonc": existsSync5(join11(cwd, ".bigcommerce", "wrangler.jsonc")),
|
|
2771
|
+
".open-next/": existsSync5(join11(cwd, ".open-next")),
|
|
2772
|
+
"package.json": existsSync5(join11(cwd, "package.json"))
|
|
2773
|
+
}
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
// src/cli/commands/debug.ts
|
|
2778
|
+
var yesNo = (value) => value ? "yes" : "no";
|
|
2779
|
+
var presence = (value) => value ? "present" : "absent";
|
|
2780
|
+
var formatResolved = (value) => value.present ? `present (source: ${value.source})` : "not set";
|
|
2781
|
+
var formatSource = (source) => source === "unset" ? "not set" : `set (${source})`;
|
|
2782
|
+
var formatStorefront = (name, version2) => {
|
|
2783
|
+
if (!version2) {
|
|
2784
|
+
return "(unknown)";
|
|
2785
|
+
}
|
|
2786
|
+
return name ? `${name}@${version2}` : version2;
|
|
2787
|
+
};
|
|
2788
|
+
var formatList = (values) => values.length > 0 ? values.join(", ") : "(none)";
|
|
2789
|
+
var envVarLines = (vars) => Object.entries(vars).map(([name, source]) => ` ${name}: ${formatSource(source)}`);
|
|
2790
|
+
var formatReport = (d) => {
|
|
2791
|
+
const lines = [
|
|
2792
|
+
"Catalyst CLI Diagnostics",
|
|
2793
|
+
"",
|
|
2794
|
+
"CLI",
|
|
2795
|
+
` Package: ${d.cli.name}`,
|
|
2796
|
+
` Version: ${d.cli.version}`,
|
|
2797
|
+
"",
|
|
2798
|
+
"Runtime",
|
|
2799
|
+
` Node: ${d.runtime.node}`,
|
|
2800
|
+
` Platform: ${d.runtime.platform} (${d.runtime.arch})`,
|
|
2801
|
+
` OS release: ${d.runtime.osRelease}`,
|
|
2802
|
+
` Package manager: ${d.runtime.packageManager}`,
|
|
2803
|
+
"",
|
|
2804
|
+
"Project",
|
|
2805
|
+
` Directory: ${d.project.cwd}`,
|
|
2806
|
+
` Storefront: ${formatStorefront(d.project.storefrontName, d.project.storefrontVersion)}`,
|
|
2807
|
+
` Project UUID: ${d.project.projectUuid ?? "(not linked)"}`,
|
|
2808
|
+
` Linked: ${yesNo(d.project.isLinked)}`,
|
|
2809
|
+
` Transformed: ${yesNo(d.project.isTransformed)}`,
|
|
2810
|
+
` Fully set up: ${yesNo(d.project.isFullySetUp)}`,
|
|
2811
|
+
` middleware.ts: ${presence(d.project.hasMiddleware)}`,
|
|
2812
|
+
` proxy.ts: ${presence(d.project.hasProxy)}`,
|
|
2813
|
+
` OpenNext dep: ${d.project.hasOpenNextDep ? "installed" : "not installed"}`,
|
|
2814
|
+
"",
|
|
2815
|
+
"Config (resolved without secrets)",
|
|
2816
|
+
` Store hash: ${formatResolved(d.config.storeHash)}`,
|
|
2817
|
+
` Access token: ${formatResolved(d.config.accessToken)}`,
|
|
2818
|
+
` Project UUID: ${formatResolved(d.config.projectUuid)}`,
|
|
2819
|
+
` API host: ${d.config.apiHost.present ? formatResolved(d.config.apiHost) : "default (api.bigcommerce.com)"}`,
|
|
2820
|
+
` project.json keys: ${formatList(d.config.projectJsonKeys)}`,
|
|
2821
|
+
` Stored env keys: ${formatList(d.config.storedEnvKeys)}`,
|
|
2822
|
+
"",
|
|
2823
|
+
"CLI environment variables (used to run the CLI)",
|
|
2824
|
+
...envVarLines(d.config.cliEnvVars),
|
|
2825
|
+
"",
|
|
2826
|
+
"Build environment variables (used to build the Next.js app)",
|
|
2827
|
+
...envVarLines(d.config.buildEnvVars),
|
|
2828
|
+
"",
|
|
2829
|
+
"Telemetry",
|
|
2830
|
+
` Enabled: ${yesNo(d.telemetry.enabled)}`,
|
|
2831
|
+
` Correlation ID: ${d.telemetry.correlationId}`,
|
|
2832
|
+
"",
|
|
2833
|
+
"Files",
|
|
2834
|
+
...Object.entries(d.files).map(([name, exists]) => ` ${name}: ${presence(exists)}`)
|
|
2835
|
+
];
|
|
2836
|
+
return lines.join("\n");
|
|
2837
|
+
};
|
|
2838
|
+
var debug = new Command5("debug").configureHelp({ showGlobalOptions: true }).description(
|
|
2839
|
+
"Print a diagnostic report (CLI, runtime, project, and config state) to include when filing a bug report. Never prints secret values \u2014 credentials and env vars are reported by presence only."
|
|
2840
|
+
).addHelpText(
|
|
2841
|
+
"after",
|
|
2842
|
+
`
|
|
2843
|
+
Examples:
|
|
2844
|
+
# Print a human-readable report
|
|
2845
|
+
$ catalyst debug
|
|
2846
|
+
|
|
2847
|
+
# Print machine-readable JSON (useful for copy/paste or piping)
|
|
2848
|
+
$ catalyst debug --json`
|
|
2849
|
+
).addOption(new Option5("--json", "Output the report as JSON.")).action((options) => {
|
|
2850
|
+
const diagnostics = collectDiagnostics();
|
|
2851
|
+
if (options.json) {
|
|
2852
|
+
consola.log(JSON.stringify(diagnostics, null, 2));
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
consola.log(formatReport(diagnostics));
|
|
2856
|
+
});
|
|
2857
|
+
|
|
2272
2858
|
// src/cli/commands/deploy.ts
|
|
2273
2859
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
2274
2860
|
import AdmZip from "adm-zip";
|
|
2275
|
-
import { Command as
|
|
2276
|
-
import { colorize as
|
|
2861
|
+
import { Command as Command6, Option as Option6 } from "commander";
|
|
2862
|
+
import { colorize as colorize7 } from "consola/utils";
|
|
2277
2863
|
import { access, readdir, readFile } from "fs/promises";
|
|
2278
2864
|
import { join as join12 } from "path";
|
|
2279
2865
|
import yoctoSpinner3 from "yocto-spinner";
|
|
2280
|
-
import { z as
|
|
2866
|
+
import { z as z13 } from "zod";
|
|
2281
2867
|
|
|
2282
2868
|
// src/cli/lib/deployment-errors.ts
|
|
2283
2869
|
var ERROR_MESSAGES = {
|
|
@@ -2296,7 +2882,7 @@ function getDeploymentErrorMessage(code) {
|
|
|
2296
2882
|
}
|
|
2297
2883
|
|
|
2298
2884
|
// src/cli/commands/deploy.ts
|
|
2299
|
-
var stepsEnum =
|
|
2885
|
+
var stepsEnum = z13.enum([
|
|
2300
2886
|
"initializing",
|
|
2301
2887
|
"downloading",
|
|
2302
2888
|
"unzipping",
|
|
@@ -2314,35 +2900,35 @@ var STEPS = {
|
|
|
2314
2900
|
finalizing: "Finalizing...",
|
|
2315
2901
|
complete: "Complete"
|
|
2316
2902
|
};
|
|
2317
|
-
var UploadSignatureSchema =
|
|
2318
|
-
data:
|
|
2319
|
-
upload_url:
|
|
2320
|
-
upload_uuid:
|
|
2903
|
+
var UploadSignatureSchema = z13.object({
|
|
2904
|
+
data: z13.object({
|
|
2905
|
+
upload_url: z13.url(),
|
|
2906
|
+
upload_uuid: z13.string()
|
|
2321
2907
|
})
|
|
2322
2908
|
});
|
|
2323
|
-
var CreateDeploymentSchema =
|
|
2324
|
-
data:
|
|
2325
|
-
deployment_uuid:
|
|
2909
|
+
var CreateDeploymentSchema = z13.object({
|
|
2910
|
+
data: z13.object({
|
|
2911
|
+
deployment_uuid: z13.uuid()
|
|
2326
2912
|
})
|
|
2327
2913
|
});
|
|
2328
|
-
var ProjectSchema =
|
|
2329
|
-
data:
|
|
2330
|
-
|
|
2331
|
-
uuid:
|
|
2332
|
-
name:
|
|
2914
|
+
var ProjectSchema = z13.object({
|
|
2915
|
+
data: z13.array(
|
|
2916
|
+
z13.object({
|
|
2917
|
+
uuid: z13.uuid(),
|
|
2918
|
+
name: z13.string()
|
|
2333
2919
|
})
|
|
2334
2920
|
)
|
|
2335
2921
|
});
|
|
2336
|
-
var DeploymentStatusSchema =
|
|
2337
|
-
deployment_uuid:
|
|
2338
|
-
deployment_status:
|
|
2339
|
-
event:
|
|
2922
|
+
var DeploymentStatusSchema = z13.object({
|
|
2923
|
+
deployment_uuid: z13.uuid(),
|
|
2924
|
+
deployment_status: z13.enum(["queued", "in_progress", "failed", "completed"]),
|
|
2925
|
+
event: z13.object({
|
|
2340
2926
|
step: stepsEnum,
|
|
2341
|
-
progress:
|
|
2927
|
+
progress: z13.number()
|
|
2342
2928
|
}).nullable(),
|
|
2343
|
-
deployment_hostnames:
|
|
2344
|
-
error:
|
|
2345
|
-
code:
|
|
2929
|
+
deployment_hostnames: z13.array(z13.string()).optional(),
|
|
2930
|
+
error: z13.object({
|
|
2931
|
+
code: z13.number()
|
|
2346
2932
|
}).optional()
|
|
2347
2933
|
});
|
|
2348
2934
|
var generateBundleZip = async () => {
|
|
@@ -2381,7 +2967,7 @@ var generateUploadSignature = async (storeHash, accessToken, apiHost) => {
|
|
|
2381
2967
|
);
|
|
2382
2968
|
assertAuthorized(response);
|
|
2383
2969
|
if (!response.ok) {
|
|
2384
|
-
throw
|
|
2970
|
+
throw await httpError(response, "Failed to generate upload signature");
|
|
2385
2971
|
}
|
|
2386
2972
|
const res = await response.json();
|
|
2387
2973
|
const { data } = UploadSignatureSchema.parse(res);
|
|
@@ -2400,7 +2986,7 @@ var uploadBundleZip = async (uploadUrl) => {
|
|
|
2400
2986
|
body: fileBuffer
|
|
2401
2987
|
});
|
|
2402
2988
|
if (!response.ok) {
|
|
2403
|
-
throw
|
|
2989
|
+
throw await httpError(response, "Failed to upload bundle");
|
|
2404
2990
|
}
|
|
2405
2991
|
consola.success("Bundle uploaded successfully.");
|
|
2406
2992
|
return true;
|
|
@@ -2436,7 +3022,7 @@ var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, a
|
|
|
2436
3022
|
);
|
|
2437
3023
|
assertAuthorized(response);
|
|
2438
3024
|
if (!response.ok) {
|
|
2439
|
-
throw
|
|
3025
|
+
throw await httpError(response, "Failed to create deployment");
|
|
2440
3026
|
}
|
|
2441
3027
|
const res = await response.json();
|
|
2442
3028
|
const { data } = CreateDeploymentSchema.parse(res);
|
|
@@ -2460,7 +3046,7 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
2460
3046
|
);
|
|
2461
3047
|
assertAuthorized(response);
|
|
2462
3048
|
if (!response.ok) {
|
|
2463
|
-
throw
|
|
3049
|
+
throw await httpError(response, "Failed to open deployment event stream");
|
|
2464
3050
|
}
|
|
2465
3051
|
const reader = response.body?.getReader();
|
|
2466
3052
|
if (!reader) {
|
|
@@ -2501,7 +3087,7 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
2501
3087
|
spinner.success("Deployment completed successfully.");
|
|
2502
3088
|
if (deploymentHostname) {
|
|
2503
3089
|
consola.success(
|
|
2504
|
-
`View your deployment at: ${
|
|
3090
|
+
`View your deployment at: ${colorize7("blue", `https://${deploymentHostname}`)}`
|
|
2505
3091
|
);
|
|
2506
3092
|
}
|
|
2507
3093
|
return deploymentHostname;
|
|
@@ -2521,13 +3107,13 @@ var fetchProject = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
|
2521
3107
|
);
|
|
2522
3108
|
assertAuthorized(response);
|
|
2523
3109
|
if (!response.ok) {
|
|
2524
|
-
throw
|
|
3110
|
+
throw await httpError(response, "Failed to fetch projects");
|
|
2525
3111
|
}
|
|
2526
3112
|
const res = await response.json();
|
|
2527
3113
|
const { data } = ProjectSchema.parse(res);
|
|
2528
3114
|
return data.find((project2) => project2.uuid === projectUuid);
|
|
2529
3115
|
};
|
|
2530
|
-
var deploy = new
|
|
3116
|
+
var deploy = new Command6("deploy").configureHelp({ showGlobalOptions: true }).description("Deploy your application to Cloudflare.").addHelpText(
|
|
2531
3117
|
"after",
|
|
2532
3118
|
`
|
|
2533
3119
|
Environment variables saved with \`catalyst env add\` are sent automatically on every deploy.
|
|
@@ -2548,8 +3134,14 @@ Example:
|
|
|
2548
3134
|
).option("--dry-run", "Run the command to generate the bundle without uploading or deploying.").option(
|
|
2549
3135
|
"--prebuilt",
|
|
2550
3136
|
"Skip the build step. Requires .bigcommerce/dist/ to already contain build output."
|
|
2551
|
-
).
|
|
3137
|
+
).addOption(
|
|
3138
|
+
new Option6(
|
|
3139
|
+
"--wrangler-version <version>",
|
|
3140
|
+
`Wrangler version or dist-tag to build with. Ignored with --prebuilt. Defaults to ${WRANGLER_VERSION}.`
|
|
3141
|
+
).argParser(parseWranglerVersion)
|
|
3142
|
+
).addOption(envPathOption()).action(async (options) => {
|
|
2552
3143
|
const config2 = getProjectConfig();
|
|
3144
|
+
const apiHost = resolveApiHost(options, config2);
|
|
2553
3145
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
2554
3146
|
const telemetry3 = getTelemetry();
|
|
2555
3147
|
await telemetry3.identify(storeHash);
|
|
@@ -2560,7 +3152,7 @@ Example:
|
|
|
2560
3152
|
return await selectOrCreateInfrastructureProject({
|
|
2561
3153
|
storeHash,
|
|
2562
3154
|
accessToken,
|
|
2563
|
-
apiHost
|
|
3155
|
+
apiHost
|
|
2564
3156
|
});
|
|
2565
3157
|
} catch (error) {
|
|
2566
3158
|
if (error instanceof NoLinkedProjectError) {
|
|
@@ -2573,12 +3165,7 @@ Example:
|
|
|
2573
3165
|
}
|
|
2574
3166
|
};
|
|
2575
3167
|
if (linkedProjectUuid) {
|
|
2576
|
-
const existing = await fetchProject(
|
|
2577
|
-
linkedProjectUuid,
|
|
2578
|
-
storeHash,
|
|
2579
|
-
accessToken,
|
|
2580
|
-
options.apiHost
|
|
2581
|
-
);
|
|
3168
|
+
const existing = await fetchProject(linkedProjectUuid, storeHash, accessToken, apiHost);
|
|
2582
3169
|
if (existing) {
|
|
2583
3170
|
projectUuid = linkedProjectUuid;
|
|
2584
3171
|
} else {
|
|
@@ -2609,7 +3196,8 @@ Example:
|
|
|
2609
3196
|
const projectDir = process.cwd();
|
|
2610
3197
|
await setupCommerceHosting({ projectDir, projectUuid, storeHash, accessToken });
|
|
2611
3198
|
consola.success("Commerce Hosting setup complete.");
|
|
2612
|
-
await
|
|
3199
|
+
const packageManager = await detectProjectPackageManager(projectDir);
|
|
3200
|
+
await installDependencies(projectDir, packageManager);
|
|
2613
3201
|
} else {
|
|
2614
3202
|
await cleanupCloudflareIncompatibilities(process.cwd());
|
|
2615
3203
|
}
|
|
@@ -2630,7 +3218,8 @@ Example:
|
|
|
2630
3218
|
}
|
|
2631
3219
|
consola.info("Using existing build output (--prebuilt).");
|
|
2632
3220
|
} else {
|
|
2633
|
-
|
|
3221
|
+
loadBuildEnv({ envPath: options.envPath });
|
|
3222
|
+
await buildCatalystProject(projectUuid, options.wranglerVersion);
|
|
2634
3223
|
}
|
|
2635
3224
|
await generateBundleZip();
|
|
2636
3225
|
if (options.dryRun) {
|
|
@@ -2641,7 +3230,7 @@ Example:
|
|
|
2641
3230
|
consola.info("- Create deployment");
|
|
2642
3231
|
process.exit(0);
|
|
2643
3232
|
}
|
|
2644
|
-
const uploadSignature = await generateUploadSignature(storeHash, accessToken,
|
|
3233
|
+
const uploadSignature = await generateUploadSignature(storeHash, accessToken, apiHost);
|
|
2645
3234
|
await uploadBundleZip(uploadSignature.upload_url);
|
|
2646
3235
|
const flagSecrets = parseEnvironmentVariables(options.secret) ?? [];
|
|
2647
3236
|
const persistedSecrets = toDeploymentSecrets(getStoredEnv(config2));
|
|
@@ -2652,14 +3241,14 @@ Example:
|
|
|
2652
3241
|
uploadSignature.upload_uuid,
|
|
2653
3242
|
storeHash,
|
|
2654
3243
|
accessToken,
|
|
2655
|
-
|
|
3244
|
+
apiHost,
|
|
2656
3245
|
environmentVariables
|
|
2657
3246
|
);
|
|
2658
3247
|
const deploymentHostname = await getDeploymentStatus(
|
|
2659
3248
|
deploymentUuid,
|
|
2660
3249
|
storeHash,
|
|
2661
3250
|
accessToken,
|
|
2662
|
-
|
|
3251
|
+
apiHost
|
|
2663
3252
|
);
|
|
2664
3253
|
if (!options.updateSiteUrl) {
|
|
2665
3254
|
return;
|
|
@@ -2668,7 +3257,7 @@ Example:
|
|
|
2668
3257
|
await runChannelSiteUrlFlow({
|
|
2669
3258
|
storeHash,
|
|
2670
3259
|
accessToken,
|
|
2671
|
-
apiHost
|
|
3260
|
+
apiHost,
|
|
2672
3261
|
projectUuid,
|
|
2673
3262
|
preferHostname: deploymentHostname
|
|
2674
3263
|
});
|
|
@@ -2683,36 +3272,36 @@ Example:
|
|
|
2683
3272
|
});
|
|
2684
3273
|
|
|
2685
3274
|
// src/cli/commands/domains.ts
|
|
2686
|
-
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
2687
|
-
import { Command as
|
|
2688
|
-
import { colorize as
|
|
3275
|
+
import { confirm as confirm4, select as select6 } from "@inquirer/prompts";
|
|
3276
|
+
import { Command as Command7, Option as Option7 } from "commander";
|
|
3277
|
+
import { colorize as colorize9 } from "consola/utils";
|
|
2689
3278
|
|
|
2690
3279
|
// src/cli/lib/domains.ts
|
|
2691
|
-
import { z as
|
|
3280
|
+
import { z as z15 } from "zod";
|
|
2692
3281
|
|
|
2693
3282
|
// src/cli/lib/observability.ts
|
|
2694
|
-
import { colorize as
|
|
2695
|
-
import { z as
|
|
3283
|
+
import { colorize as colorize8 } from "consola/utils";
|
|
3284
|
+
import { z as z14 } from "zod";
|
|
2696
3285
|
var LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
2697
3286
|
var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2698
|
-
var logEntrySchema =
|
|
2699
|
-
timestamp:
|
|
2700
|
-
level:
|
|
2701
|
-
messages:
|
|
2702
|
-
is_exception:
|
|
2703
|
-
exception_name:
|
|
2704
|
-
request:
|
|
2705
|
-
method:
|
|
2706
|
-
url:
|
|
2707
|
-
status_code:
|
|
3287
|
+
var logEntrySchema = z14.object({
|
|
3288
|
+
timestamp: z14.string().nullable().optional(),
|
|
3289
|
+
level: z14.string().optional(),
|
|
3290
|
+
messages: z14.array(z14.unknown()).optional(),
|
|
3291
|
+
is_exception: z14.boolean().optional(),
|
|
3292
|
+
exception_name: z14.string().optional(),
|
|
3293
|
+
request: z14.object({
|
|
3294
|
+
method: z14.string().optional(),
|
|
3295
|
+
url: z14.string().optional(),
|
|
3296
|
+
status_code: z14.number().optional()
|
|
2708
3297
|
}).nullable().optional()
|
|
2709
3298
|
});
|
|
2710
|
-
var queryLogsSchema =
|
|
2711
|
-
data:
|
|
3299
|
+
var queryLogsSchema = z14.object({
|
|
3300
|
+
data: z14.array(logEntrySchema)
|
|
2712
3301
|
});
|
|
2713
|
-
var v3ErrorSchema =
|
|
2714
|
-
title:
|
|
2715
|
-
errors:
|
|
3302
|
+
var v3ErrorSchema = z14.object({
|
|
3303
|
+
title: z14.string().optional(),
|
|
3304
|
+
errors: z14.record(z14.string(), z14.string()).optional()
|
|
2716
3305
|
});
|
|
2717
3306
|
function formatV3Error(body) {
|
|
2718
3307
|
const parsed = v3ErrorSchema.safeParse(body);
|
|
@@ -2749,7 +3338,7 @@ function resolveTimeWindow(opts, nowMs = Date.now()) {
|
|
|
2749
3338
|
if (end) {
|
|
2750
3339
|
endMs = parseTimeInput(end);
|
|
2751
3340
|
if (endMs === null) {
|
|
2752
|
-
throw new
|
|
3341
|
+
throw new UserActionableError(
|
|
2753
3342
|
`Invalid --end value "${end}". Provide an ISO-8601 timestamp or a Unix epoch (seconds).`
|
|
2754
3343
|
);
|
|
2755
3344
|
}
|
|
@@ -2760,7 +3349,7 @@ function resolveTimeWindow(opts, nowMs = Date.now()) {
|
|
|
2760
3349
|
if (since) {
|
|
2761
3350
|
const durationMs = parseDuration(since);
|
|
2762
3351
|
if (durationMs === null) {
|
|
2763
|
-
throw new
|
|
3352
|
+
throw new UserActionableError(
|
|
2764
3353
|
`Invalid --since value "${since}". Provide a duration like 30m, 6h, or 2d (units: s, m, h, d).`
|
|
2765
3354
|
);
|
|
2766
3355
|
}
|
|
@@ -2769,18 +3358,20 @@ function resolveTimeWindow(opts, nowMs = Date.now()) {
|
|
|
2769
3358
|
} else if (start2) {
|
|
2770
3359
|
startMs = parseTimeInput(start2);
|
|
2771
3360
|
if (startMs === null) {
|
|
2772
|
-
throw new
|
|
3361
|
+
throw new UserActionableError(
|
|
2773
3362
|
`Invalid --start value "${start2}". Provide an ISO-8601 timestamp or a Unix epoch (seconds).`
|
|
2774
3363
|
);
|
|
2775
3364
|
}
|
|
2776
3365
|
} else {
|
|
2777
|
-
throw new
|
|
3366
|
+
throw new UserActionableError(
|
|
3367
|
+
"Provide a time window with --since <duration> or --start <time>."
|
|
3368
|
+
);
|
|
2778
3369
|
}
|
|
2779
3370
|
if (startMs > endMs) {
|
|
2780
|
-
throw new
|
|
3371
|
+
throw new UserActionableError("Invalid time window: --start must be before or equal to --end.");
|
|
2781
3372
|
}
|
|
2782
3373
|
if (endMs - startMs > SEVEN_DAYS_MS) {
|
|
2783
|
-
throw new
|
|
3374
|
+
throw new UserActionableError("Invalid time window: the range must not exceed 7 days.");
|
|
2784
3375
|
}
|
|
2785
3376
|
return { start: start2, end };
|
|
2786
3377
|
}
|
|
@@ -2800,8 +3391,8 @@ function formatLogEntry(entry, format = "default") {
|
|
|
2800
3391
|
const message = (entry.messages ?? []).map(stringifyMessage).join(" ").replace(/\s+/g, " ").trim();
|
|
2801
3392
|
if (format === "short") return message;
|
|
2802
3393
|
const level = entry.level ?? "unknown";
|
|
2803
|
-
const coloredLevel =
|
|
2804
|
-
const exceptionStr = entry.is_exception && entry.exception_name && !isRedundantExceptionName(entry.exception_name, message) ? ` ${
|
|
3394
|
+
const coloredLevel = colorize8(LEVEL_COLORS[level.toLowerCase()] ?? "white", level.toUpperCase());
|
|
3395
|
+
const exceptionStr = entry.is_exception && entry.exception_name && !isRedundantExceptionName(entry.exception_name, message) ? ` ${colorize8("red", `[${entry.exception_name}]`)}` : "";
|
|
2805
3396
|
let requestStr = "";
|
|
2806
3397
|
if (format === "request" && entry.request) {
|
|
2807
3398
|
const parts = [];
|
|
@@ -2834,36 +3425,73 @@ async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
|
|
|
2834
3425
|
);
|
|
2835
3426
|
assertAuthorized(response);
|
|
2836
3427
|
if (response.status === 403) {
|
|
2837
|
-
throw new
|
|
3428
|
+
throw new UserActionableError(
|
|
2838
3429
|
"Infrastructure Logs API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it."
|
|
2839
3430
|
);
|
|
2840
3431
|
}
|
|
2841
3432
|
if (response.status === 404) {
|
|
2842
|
-
throw new
|
|
3433
|
+
throw new UserActionableError("Project not found. Check the project UUID.");
|
|
2843
3434
|
}
|
|
2844
3435
|
if (response.status === 400 || response.status === 422) {
|
|
2845
3436
|
const body = await response.json().catch(() => null);
|
|
2846
|
-
throw new
|
|
3437
|
+
throw new UserActionableError(formatV3Error(body) ?? "Invalid log query.");
|
|
2847
3438
|
}
|
|
2848
3439
|
if (!response.ok) {
|
|
2849
|
-
throw
|
|
3440
|
+
throw await httpError(response, "Failed to fetch logs");
|
|
2850
3441
|
}
|
|
2851
3442
|
const res = await response.json();
|
|
2852
3443
|
return queryLogsSchema.parse(res);
|
|
2853
3444
|
}
|
|
2854
3445
|
|
|
2855
3446
|
// src/cli/lib/domains.ts
|
|
2856
|
-
var domainStatusSchema =
|
|
2857
|
-
var domainSchema =
|
|
2858
|
-
domain:
|
|
2859
|
-
project_uuid:
|
|
3447
|
+
var domainStatusSchema = z15.enum(["pending", "verified", "failed", "unknown"]);
|
|
3448
|
+
var domainSchema = z15.object({
|
|
3449
|
+
domain: z15.string(),
|
|
3450
|
+
project_uuid: z15.string(),
|
|
2860
3451
|
verification_status: domainStatusSchema
|
|
2861
3452
|
});
|
|
2862
|
-
var
|
|
3453
|
+
var ownershipVerificationSchema = z15.object({
|
|
3454
|
+
type: z15.string(),
|
|
3455
|
+
name: z15.string(),
|
|
3456
|
+
value: z15.string().optional()
|
|
3457
|
+
});
|
|
3458
|
+
var ownershipVerificationMetaSchema = z15.object({
|
|
3459
|
+
meta: z15.object({
|
|
3460
|
+
ownership_verification: ownershipVerificationSchema
|
|
3461
|
+
})
|
|
3462
|
+
});
|
|
3463
|
+
var DomainOwnershipVerificationError = class extends UserActionableError {
|
|
3464
|
+
constructor(message, ownershipVerification) {
|
|
3465
|
+
super(message);
|
|
3466
|
+
this.name = "DomainOwnershipVerificationError";
|
|
3467
|
+
this.ownershipVerification = ownershipVerification;
|
|
3468
|
+
}
|
|
3469
|
+
};
|
|
3470
|
+
function parseOwnershipVerification(body) {
|
|
3471
|
+
const parsed = ownershipVerificationMetaSchema.safeParse(body);
|
|
3472
|
+
return parsed.success ? parsed.data.meta.ownership_verification : void 0;
|
|
3473
|
+
}
|
|
3474
|
+
var boundProjectMetaSchema = z15.object({
|
|
3475
|
+
meta: z15.object({
|
|
3476
|
+
project_uuid: z15.string()
|
|
3477
|
+
})
|
|
3478
|
+
});
|
|
3479
|
+
var DomainBoundToProjectError = class extends UserActionableError {
|
|
3480
|
+
constructor(message, projectUuid) {
|
|
3481
|
+
super(message);
|
|
3482
|
+
this.name = "DomainBoundToProjectError";
|
|
3483
|
+
this.projectUuid = projectUuid;
|
|
3484
|
+
}
|
|
3485
|
+
};
|
|
3486
|
+
function parseBoundProjectUuid(body) {
|
|
3487
|
+
const parsed = boundProjectMetaSchema.safeParse(body);
|
|
3488
|
+
return parsed.success ? parsed.data.meta.project_uuid : void 0;
|
|
3489
|
+
}
|
|
3490
|
+
var domainResponseSchema = z15.object({
|
|
2863
3491
|
data: domainSchema
|
|
2864
3492
|
});
|
|
2865
|
-
var domainListResponseSchema =
|
|
2866
|
-
data:
|
|
3493
|
+
var domainListResponseSchema = z15.object({
|
|
3494
|
+
data: z15.array(domainSchema)
|
|
2867
3495
|
});
|
|
2868
3496
|
var DOMAINS_API_NOT_ENABLED = "Infrastructure Domains API not enabled. If you are part of the beta, contact support@bigcommerce.com to enable it.";
|
|
2869
3497
|
function domainsUrl(storeHash, projectUuid, apiHost) {
|
|
@@ -2872,6 +3500,12 @@ function domainsUrl(storeHash, projectUuid, apiHost) {
|
|
|
2872
3500
|
function domainUrl(storeHash, projectUuid, domain, apiHost) {
|
|
2873
3501
|
return `${domainsUrl(storeHash, projectUuid, apiHost)}/${encodeURIComponent(domain)}`;
|
|
2874
3502
|
}
|
|
3503
|
+
function domainClaimUrl(storeHash, projectUuid, domain, apiHost) {
|
|
3504
|
+
return `${domainUrl(storeHash, projectUuid, domain, apiHost)}/claim`;
|
|
3505
|
+
}
|
|
3506
|
+
function domainTransferUrl(storeHash, projectUuid, domain, apiHost) {
|
|
3507
|
+
return `${domainUrl(storeHash, projectUuid, domain, apiHost)}/transfer`;
|
|
3508
|
+
}
|
|
2875
3509
|
function authHeaders3(accessToken) {
|
|
2876
3510
|
return {
|
|
2877
3511
|
Accept: "application/json",
|
|
@@ -2882,8 +3516,7 @@ function authHeaders3(accessToken) {
|
|
|
2882
3516
|
function formatResponseStatus(response, message) {
|
|
2883
3517
|
return [response.status, response.statusText || message].filter(Boolean).join(" ");
|
|
2884
3518
|
}
|
|
2885
|
-
|
|
2886
|
-
const body = await response.json().catch(() => null);
|
|
3519
|
+
function buildErrorMessage(response, body, action) {
|
|
2887
3520
|
const message = formatV3Error(body);
|
|
2888
3521
|
if (response.status >= 500) {
|
|
2889
3522
|
const status = formatResponseStatus(response, message);
|
|
@@ -2894,11 +3527,25 @@ async function getErrorMessage(response, action) {
|
|
|
2894
3527
|
async function assertDomainResponse(response, action) {
|
|
2895
3528
|
assertAuthorized(response);
|
|
2896
3529
|
if (response.status === 403) {
|
|
2897
|
-
throw new
|
|
3530
|
+
throw new UserActionableError(DOMAINS_API_NOT_ENABLED);
|
|
2898
3531
|
}
|
|
2899
|
-
if (
|
|
2900
|
-
|
|
3532
|
+
if (response.ok) {
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
const body = await response.json().catch(() => null);
|
|
3536
|
+
const message = buildErrorMessage(response, body, action);
|
|
3537
|
+
const ownershipVerification = parseOwnershipVerification(body);
|
|
3538
|
+
if (ownershipVerification) {
|
|
3539
|
+
throw new DomainOwnershipVerificationError(message, ownershipVerification);
|
|
3540
|
+
}
|
|
3541
|
+
const boundProjectUuid = parseBoundProjectUuid(body);
|
|
3542
|
+
if (boundProjectUuid) {
|
|
3543
|
+
throw new DomainBoundToProjectError(message, boundProjectUuid);
|
|
2901
3544
|
}
|
|
3545
|
+
if (response.status >= 500) {
|
|
3546
|
+
throw new Error(message);
|
|
3547
|
+
}
|
|
3548
|
+
throw new UserActionableError(message);
|
|
2902
3549
|
}
|
|
2903
3550
|
async function createDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
|
|
2904
3551
|
const response = await fetch(domainsUrl(storeHash, projectUuid, apiHost), {
|
|
@@ -2922,6 +3569,18 @@ async function getDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
|
|
|
2922
3569
|
const result = await response.json();
|
|
2923
3570
|
return domainResponseSchema.parse(result).data;
|
|
2924
3571
|
}
|
|
3572
|
+
async function findDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
|
|
3573
|
+
const response = await fetch(domainUrl(storeHash, projectUuid, domain, apiHost), {
|
|
3574
|
+
method: "GET",
|
|
3575
|
+
headers: authHeaders3(accessToken)
|
|
3576
|
+
});
|
|
3577
|
+
if (response.status === 404) {
|
|
3578
|
+
return null;
|
|
3579
|
+
}
|
|
3580
|
+
await assertDomainResponse(response, "Failed to fetch domain");
|
|
3581
|
+
const result = await response.json();
|
|
3582
|
+
return domainResponseSchema.parse(result).data;
|
|
3583
|
+
}
|
|
2925
3584
|
async function listDomains(projectUuid, storeHash, accessToken, apiHost, filters = {}) {
|
|
2926
3585
|
const search = new URLSearchParams();
|
|
2927
3586
|
if (filters.domains && filters.domains.length > 0) {
|
|
@@ -2947,6 +3606,24 @@ async function deleteDomain(domain, projectUuid, storeHash, accessToken, apiHost
|
|
|
2947
3606
|
});
|
|
2948
3607
|
await assertDomainResponse(response, `Failed to remove domain: ${response.statusText}`);
|
|
2949
3608
|
}
|
|
3609
|
+
async function claimDomain(domain, projectUuid, storeHash, accessToken, apiHost) {
|
|
3610
|
+
const response = await fetch(domainClaimUrl(storeHash, projectUuid, domain, apiHost), {
|
|
3611
|
+
method: "POST",
|
|
3612
|
+
headers: authHeaders3(accessToken)
|
|
3613
|
+
});
|
|
3614
|
+
await assertDomainResponse(response, "Failed to claim domain");
|
|
3615
|
+
}
|
|
3616
|
+
async function transferDomain(domain, projectUuid, newProjectUuid, storeHash, accessToken, apiHost) {
|
|
3617
|
+
const response = await fetch(domainTransferUrl(storeHash, projectUuid, domain, apiHost), {
|
|
3618
|
+
method: "POST",
|
|
3619
|
+
headers: {
|
|
3620
|
+
...authHeaders3(accessToken),
|
|
3621
|
+
"Content-Type": "application/json"
|
|
3622
|
+
},
|
|
3623
|
+
body: JSON.stringify({ new_project_uuid: newProjectUuid })
|
|
3624
|
+
});
|
|
3625
|
+
await assertDomainResponse(response, "Failed to transfer domain");
|
|
3626
|
+
}
|
|
2950
3627
|
|
|
2951
3628
|
// src/cli/commands/domains.ts
|
|
2952
3629
|
var WAIT_INTERVAL_MS = 5e3;
|
|
@@ -2967,21 +3644,58 @@ var STATUS_LABELS = {
|
|
|
2967
3644
|
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2968
3645
|
function resolveDomainCommandContext(options) {
|
|
2969
3646
|
const config2 = getProjectConfig();
|
|
3647
|
+
const apiHost = resolveApiHost(options, config2);
|
|
2970
3648
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
2971
3649
|
const projectUuid = resolveProjectUuid(options);
|
|
2972
3650
|
return {
|
|
2973
3651
|
projectUuid,
|
|
2974
3652
|
storeHash,
|
|
2975
3653
|
accessToken,
|
|
2976
|
-
apiHost
|
|
3654
|
+
apiHost
|
|
2977
3655
|
};
|
|
2978
3656
|
}
|
|
3657
|
+
async function resolveDestinationProject(domain, context, toProjectUuid) {
|
|
3658
|
+
if (toProjectUuid) {
|
|
3659
|
+
if (toProjectUuid === context.projectUuid) {
|
|
3660
|
+
throw new UserActionableError("The destination project must differ from the source project.");
|
|
3661
|
+
}
|
|
3662
|
+
return toProjectUuid;
|
|
3663
|
+
}
|
|
3664
|
+
consola.start("Fetching projects...");
|
|
3665
|
+
const projects = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
|
|
3666
|
+
consola.success("Projects fetched.");
|
|
3667
|
+
const destinations = projects.filter((project2) => project2.uuid !== context.projectUuid);
|
|
3668
|
+
if (destinations.length === 0) {
|
|
3669
|
+
throw new UserActionableError(
|
|
3670
|
+
"No other projects to transfer to. Create another project with `catalyst project create` first."
|
|
3671
|
+
);
|
|
3672
|
+
}
|
|
3673
|
+
return select6({
|
|
3674
|
+
message: `Select the project to transfer ${domain} to (Press <enter> to select).`,
|
|
3675
|
+
choices: destinations.map((project2) => ({
|
|
3676
|
+
name: project2.name,
|
|
3677
|
+
value: project2.uuid,
|
|
3678
|
+
description: project2.uuid
|
|
3679
|
+
}))
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
2979
3682
|
function formatDomainStatus(status) {
|
|
2980
|
-
return
|
|
3683
|
+
return colorize9(STATUS_COLORS[status], STATUS_LABELS[status]);
|
|
2981
3684
|
}
|
|
2982
3685
|
function formatDomain(domain) {
|
|
2983
3686
|
return `${domain.domain} ${formatDomainStatus(domain.verification_status)}`;
|
|
2984
3687
|
}
|
|
3688
|
+
function formatOwnershipVerification(record) {
|
|
3689
|
+
const lines = [
|
|
3690
|
+
"Add this DNS record to verify ownership:",
|
|
3691
|
+
` Type: ${record.type}`,
|
|
3692
|
+
` Name: ${record.name}`
|
|
3693
|
+
];
|
|
3694
|
+
if (record.value) {
|
|
3695
|
+
lines.push(` Value: ${record.value}`);
|
|
3696
|
+
}
|
|
3697
|
+
return lines.join("\n");
|
|
3698
|
+
}
|
|
2985
3699
|
async function waitForDomainVerification({
|
|
2986
3700
|
domain,
|
|
2987
3701
|
projectUuid,
|
|
@@ -2999,7 +3713,7 @@ async function waitForDomainVerification({
|
|
|
2999
3713
|
}
|
|
3000
3714
|
return current;
|
|
3001
3715
|
}
|
|
3002
|
-
var add = new
|
|
3716
|
+
var add = new Command7("add").configureHelp({ showGlobalOptions: true }).description("Add a custom domain to the current Native Hosting project.").argument("<domain>", "Custom domain to add.").addHelpText(
|
|
3003
3717
|
"after",
|
|
3004
3718
|
`
|
|
3005
3719
|
Examples:
|
|
@@ -3011,13 +3725,35 @@ Examples:
|
|
|
3011
3725
|
const context = resolveDomainCommandContext(options);
|
|
3012
3726
|
await getTelemetry().identify(context.storeHash);
|
|
3013
3727
|
consola.start(`Adding domain ${domain}...`);
|
|
3014
|
-
let result
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3728
|
+
let result;
|
|
3729
|
+
try {
|
|
3730
|
+
result = await createDomain(
|
|
3731
|
+
domain,
|
|
3732
|
+
context.projectUuid,
|
|
3733
|
+
context.storeHash,
|
|
3734
|
+
context.accessToken,
|
|
3735
|
+
context.apiHost
|
|
3736
|
+
);
|
|
3737
|
+
} catch (error) {
|
|
3738
|
+
if (error instanceof DomainOwnershipVerificationError) {
|
|
3739
|
+
consola.warn(`${domain} is already in use on another store.`);
|
|
3740
|
+
consola.log(formatOwnershipVerification(error.ownershipVerification));
|
|
3741
|
+
consola.info("Once the record is live, claim it with:");
|
|
3742
|
+
consola.log(` catalyst domains claim ${domain}`);
|
|
3743
|
+
process.exit(1);
|
|
3744
|
+
return;
|
|
3745
|
+
}
|
|
3746
|
+
if (error instanceof DomainBoundToProjectError) {
|
|
3747
|
+
consola.warn(`${domain} is already bound to another project in this store.`);
|
|
3748
|
+
consola.info("To move it to this project, run:");
|
|
3749
|
+
consola.log(
|
|
3750
|
+
` catalyst domains transfer ${domain} --project-uuid ${error.projectUuid} --to-project-uuid ${context.projectUuid}`
|
|
3751
|
+
);
|
|
3752
|
+
process.exit(1);
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
throw error;
|
|
3756
|
+
}
|
|
3021
3757
|
consola.success(`Domain ${result.domain} added.`);
|
|
3022
3758
|
if (options.wait && result.verification_status === "pending") {
|
|
3023
3759
|
consola.start(`Waiting for ${result.domain} to verify...`);
|
|
@@ -3026,7 +3762,7 @@ Examples:
|
|
|
3026
3762
|
consola.log(formatDomain(result));
|
|
3027
3763
|
process.exit(0);
|
|
3028
3764
|
});
|
|
3029
|
-
var list = new
|
|
3765
|
+
var list = new Command7("list").configureHelp({ showGlobalOptions: true }).description("List custom domains for the current Native Hosting project.").addHelpText(
|
|
3030
3766
|
"after",
|
|
3031
3767
|
`
|
|
3032
3768
|
Examples:
|
|
@@ -3061,7 +3797,7 @@ Examples:
|
|
|
3061
3797
|
result.forEach((item) => consola.log(formatDomain(item)));
|
|
3062
3798
|
process.exit(0);
|
|
3063
3799
|
});
|
|
3064
|
-
var showStatus = new
|
|
3800
|
+
var showStatus = new Command7("status").configureHelp({ showGlobalOptions: true }).description("Show the status of a custom domain on the current Native Hosting project.").argument("<domain>", "Custom domain to check.").addHelpText(
|
|
3065
3801
|
"after",
|
|
3066
3802
|
`
|
|
3067
3803
|
Examples:
|
|
@@ -3088,7 +3824,143 @@ Examples:
|
|
|
3088
3824
|
consola.log(formatDomain(result));
|
|
3089
3825
|
process.exit(0);
|
|
3090
3826
|
});
|
|
3091
|
-
var
|
|
3827
|
+
var claim = new Command7("claim").configureHelp({ showGlobalOptions: true }).description("Claim a custom domain that is currently in use on another store.").argument("<domain>", "Custom domain to claim.").addHelpText(
|
|
3828
|
+
"after",
|
|
3829
|
+
`
|
|
3830
|
+
Claiming requires publishing the ownership-verification TXT record returned when
|
|
3831
|
+
you try to add a domain that is already in use on another store. Publish the
|
|
3832
|
+
record with your DNS provider, then run this command to complete the claim.
|
|
3833
|
+
|
|
3834
|
+
Examples:
|
|
3835
|
+
$ catalyst domains claim www.example.com
|
|
3836
|
+
|
|
3837
|
+
# Wait until the claimed domain leaves pending verification
|
|
3838
|
+
$ catalyst domains claim www.example.com --wait`
|
|
3839
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--wait", "Poll until domain verification completes or times out.").action(async (domain, options) => {
|
|
3840
|
+
const context = resolveDomainCommandContext(options);
|
|
3841
|
+
await getTelemetry().identify(context.storeHash);
|
|
3842
|
+
consola.start(`Claiming domain ${domain}...`);
|
|
3843
|
+
try {
|
|
3844
|
+
await claimDomain(
|
|
3845
|
+
domain,
|
|
3846
|
+
context.projectUuid,
|
|
3847
|
+
context.storeHash,
|
|
3848
|
+
context.accessToken,
|
|
3849
|
+
context.apiHost
|
|
3850
|
+
);
|
|
3851
|
+
} catch (error) {
|
|
3852
|
+
if (error instanceof DomainOwnershipVerificationError) {
|
|
3853
|
+
consola.warn(`Ownership of ${domain} could not be verified yet.`);
|
|
3854
|
+
consola.log(formatOwnershipVerification(error.ownershipVerification));
|
|
3855
|
+
consola.info("Once the record is live, run the claim again:");
|
|
3856
|
+
consola.log(` catalyst domains claim ${domain}`);
|
|
3857
|
+
process.exit(1);
|
|
3858
|
+
return;
|
|
3859
|
+
}
|
|
3860
|
+
throw error;
|
|
3861
|
+
}
|
|
3862
|
+
consola.success(`Domain ${domain} claimed.`);
|
|
3863
|
+
let result = await getDomain(
|
|
3864
|
+
domain,
|
|
3865
|
+
context.projectUuid,
|
|
3866
|
+
context.storeHash,
|
|
3867
|
+
context.accessToken,
|
|
3868
|
+
context.apiHost
|
|
3869
|
+
);
|
|
3870
|
+
if (options.wait && result.verification_status === "pending") {
|
|
3871
|
+
consola.start(`Waiting for ${result.domain} to verify...`);
|
|
3872
|
+
result = await waitForDomainVerification({ domain: result.domain, ...context });
|
|
3873
|
+
}
|
|
3874
|
+
consola.log(formatDomain(result));
|
|
3875
|
+
process.exit(0);
|
|
3876
|
+
});
|
|
3877
|
+
var transfer = new Command7("transfer").configureHelp({ showGlobalOptions: true }).description("Transfer a custom domain to another project in the same store.").argument("<domain>", "Custom domain to transfer.").addHelpText(
|
|
3878
|
+
"after",
|
|
3879
|
+
`
|
|
3880
|
+
The domain is transferred from the current project to the destination project.
|
|
3881
|
+
Omit --to-project-uuid to pick the destination from a list of your projects.
|
|
3882
|
+
|
|
3883
|
+
Examples:
|
|
3884
|
+
# Pick the destination project interactively
|
|
3885
|
+
$ catalyst domains transfer www.example.com
|
|
3886
|
+
|
|
3887
|
+
# Transfer to a specific project
|
|
3888
|
+
$ catalyst domains transfer www.example.com --to-project-uuid <uuid>`
|
|
3889
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--to-project-uuid <uuid>", "Destination project UUID. Prompts to choose one if omitted.").option("--wait", "Poll until domain verification completes or times out.").action(async (domain, options) => {
|
|
3890
|
+
const context = resolveDomainCommandContext(options);
|
|
3891
|
+
await getTelemetry().identify(context.storeHash);
|
|
3892
|
+
consola.start(`Checking ${domain} on the current project...`);
|
|
3893
|
+
const owned = await findDomain(
|
|
3894
|
+
domain,
|
|
3895
|
+
context.projectUuid,
|
|
3896
|
+
context.storeHash,
|
|
3897
|
+
context.accessToken,
|
|
3898
|
+
context.apiHost
|
|
3899
|
+
);
|
|
3900
|
+
if (!owned) {
|
|
3901
|
+
consola.start(`Locating the project that owns ${domain}...`);
|
|
3902
|
+
const projects = await fetchProjects(context.storeHash, context.accessToken, context.apiHost);
|
|
3903
|
+
const candidates = projects.filter((project2) => project2.uuid !== context.projectUuid);
|
|
3904
|
+
const matches = await Promise.all(
|
|
3905
|
+
candidates.map(async (project2) => ({
|
|
3906
|
+
project: project2,
|
|
3907
|
+
found: await findDomain(
|
|
3908
|
+
domain,
|
|
3909
|
+
project2.uuid,
|
|
3910
|
+
context.storeHash,
|
|
3911
|
+
context.accessToken,
|
|
3912
|
+
context.apiHost
|
|
3913
|
+
)
|
|
3914
|
+
}))
|
|
3915
|
+
);
|
|
3916
|
+
const owner = matches.find((match) => match.found)?.project;
|
|
3917
|
+
if (!owner) {
|
|
3918
|
+
throw new UserActionableError(
|
|
3919
|
+
`${domain} isn't on any project in this store. If it's in use on another store, claim it with \`catalyst domains claim ${domain}\`; otherwise double-check the domain name.`
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
const toSuffix = options.toProjectUuid ? ` --to-project-uuid ${options.toProjectUuid}` : "";
|
|
3923
|
+
consola.warn(
|
|
3924
|
+
`${domain} is on project "${owner.name}" (${owner.uuid}), not the current project.`
|
|
3925
|
+
);
|
|
3926
|
+
consola.info("Re-run the transfer from that project:");
|
|
3927
|
+
consola.log(` catalyst domains transfer ${domain} --project-uuid ${owner.uuid}${toSuffix}`);
|
|
3928
|
+
process.exit(1);
|
|
3929
|
+
return;
|
|
3930
|
+
}
|
|
3931
|
+
consola.success(`${domain} found on the current project.`);
|
|
3932
|
+
const newProjectUuid = await resolveDestinationProject(domain, context, options.toProjectUuid);
|
|
3933
|
+
consola.start(`Transferring domain ${domain}...`);
|
|
3934
|
+
await transferDomain(
|
|
3935
|
+
domain,
|
|
3936
|
+
context.projectUuid,
|
|
3937
|
+
newProjectUuid,
|
|
3938
|
+
context.storeHash,
|
|
3939
|
+
context.accessToken,
|
|
3940
|
+
context.apiHost
|
|
3941
|
+
);
|
|
3942
|
+
consola.success(`Domain ${domain} transferred.`);
|
|
3943
|
+
let result = await getDomain(
|
|
3944
|
+
domain,
|
|
3945
|
+
newProjectUuid,
|
|
3946
|
+
context.storeHash,
|
|
3947
|
+
context.accessToken,
|
|
3948
|
+
context.apiHost
|
|
3949
|
+
);
|
|
3950
|
+
if (options.wait && result.verification_status === "pending") {
|
|
3951
|
+
consola.start(`Waiting for ${result.domain} to verify...`);
|
|
3952
|
+
result = await waitForDomainVerification({
|
|
3953
|
+
domain: result.domain,
|
|
3954
|
+
projectUuid: newProjectUuid,
|
|
3955
|
+
storeHash: context.storeHash,
|
|
3956
|
+
accessToken: context.accessToken,
|
|
3957
|
+
apiHost: context.apiHost
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
consola.log(formatDomain(result));
|
|
3961
|
+
process.exit(0);
|
|
3962
|
+
});
|
|
3963
|
+
var remove = new Command7("remove").configureHelp({ showGlobalOptions: true }).description("Remove a custom domain from the current Native Hosting project.").argument("<domain>", "Custom domain to remove.").addHelpText(
|
|
3092
3964
|
"after",
|
|
3093
3965
|
`
|
|
3094
3966
|
Examples:
|
|
@@ -3129,12 +4001,12 @@ Examples:
|
|
|
3129
4001
|
consola.success(`Domain ${current.domain} removed.`);
|
|
3130
4002
|
process.exit(0);
|
|
3131
4003
|
});
|
|
3132
|
-
var domains = new
|
|
4004
|
+
var domains = new Command7("domains").configureHelp({ showGlobalOptions: true }).description("Manage custom domains for the current Native Hosting project.").addCommand(add).addCommand(list).addCommand(showStatus).addCommand(claim).addCommand(transfer).addCommand(remove);
|
|
3133
4005
|
|
|
3134
4006
|
// src/cli/commands/env.ts
|
|
3135
|
-
import { Command as
|
|
4007
|
+
import { Command as Command8 } from "commander";
|
|
3136
4008
|
var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
3137
|
-
var add2 = new
|
|
4009
|
+
var add2 = new Command8("add").configureHelp({ showGlobalOptions: true }).description(
|
|
3138
4010
|
"Add or update one or more deployment environment variables. Stored in .bigcommerce/project.json and sent as secrets on every `catalyst deploy`."
|
|
3139
4011
|
).argument("<vars...>", "One or more environment variables in KEY=VALUE format.").addHelpText(
|
|
3140
4012
|
"after",
|
|
@@ -3156,7 +4028,7 @@ Example:
|
|
|
3156
4028
|
keys.forEach((key) => consola.log(` ${key}=${MASK}`));
|
|
3157
4029
|
process.exit(0);
|
|
3158
4030
|
});
|
|
3159
|
-
var remove2 = new
|
|
4031
|
+
var remove2 = new Command8("remove").configureHelp({ showGlobalOptions: true }).description("Remove one or more stored deployment environment variables.").argument("<keys...>", "One or more environment variable names to remove.").addHelpText(
|
|
3160
4032
|
"after",
|
|
3161
4033
|
`
|
|
3162
4034
|
Example:
|
|
@@ -3182,7 +4054,7 @@ Example:
|
|
|
3182
4054
|
}
|
|
3183
4055
|
process.exit(0);
|
|
3184
4056
|
});
|
|
3185
|
-
var list2 = new
|
|
4057
|
+
var list2 = new Command8("list").configureHelp({ showGlobalOptions: true }).description("List stored deployment environment variables (values are masked).").addHelpText(
|
|
3186
4058
|
"after",
|
|
3187
4059
|
`
|
|
3188
4060
|
Example:
|
|
@@ -3199,14 +4071,14 @@ Example:
|
|
|
3199
4071
|
keys.forEach((key) => consola.log(`${key}=${MASK}`));
|
|
3200
4072
|
process.exit(0);
|
|
3201
4073
|
});
|
|
3202
|
-
var env = new
|
|
4074
|
+
var env = new Command8("env").configureHelp({ showGlobalOptions: true }).description(
|
|
3203
4075
|
"Manage persistent deployment environment variables. These are sent as secrets on every `catalyst deploy`, so you no longer need to pass `--secret` each time."
|
|
3204
4076
|
).addCommand(add2).addCommand(remove2).addCommand(list2);
|
|
3205
4077
|
|
|
3206
4078
|
// src/cli/commands/logs.ts
|
|
3207
|
-
import { Command as
|
|
3208
|
-
import { colorize as
|
|
3209
|
-
import { z as
|
|
4079
|
+
import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as Option8 } from "commander";
|
|
4080
|
+
import { colorize as colorize10 } from "consola/utils";
|
|
4081
|
+
import { z as z16 } from "zod";
|
|
3210
4082
|
var telemetry = new Telemetry();
|
|
3211
4083
|
var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
|
|
3212
4084
|
var MAX_RETRIES = 5;
|
|
@@ -3217,23 +4089,23 @@ var LEVEL_COLORS2 = {
|
|
|
3217
4089
|
ERROR: "red",
|
|
3218
4090
|
DEBUG: "gray"
|
|
3219
4091
|
};
|
|
3220
|
-
var LogEventSchema =
|
|
3221
|
-
uuid:
|
|
3222
|
-
project_uuid:
|
|
3223
|
-
request:
|
|
3224
|
-
method:
|
|
3225
|
-
url:
|
|
3226
|
-
status_code:
|
|
4092
|
+
var LogEventSchema = z16.object({
|
|
4093
|
+
uuid: z16.string(),
|
|
4094
|
+
project_uuid: z16.string(),
|
|
4095
|
+
request: z16.object({
|
|
4096
|
+
method: z16.string(),
|
|
4097
|
+
url: z16.string(),
|
|
4098
|
+
status_code: z16.number()
|
|
3227
4099
|
}),
|
|
3228
|
-
logs:
|
|
3229
|
-
|
|
3230
|
-
timestamp:
|
|
3231
|
-
level:
|
|
3232
|
-
messages:
|
|
4100
|
+
logs: z16.array(
|
|
4101
|
+
z16.object({
|
|
4102
|
+
timestamp: z16.string(),
|
|
4103
|
+
level: z16.string(),
|
|
4104
|
+
messages: z16.array(z16.unknown())
|
|
3233
4105
|
})
|
|
3234
4106
|
),
|
|
3235
|
-
exceptions:
|
|
3236
|
-
timestamp:
|
|
4107
|
+
exceptions: z16.array(z16.unknown()),
|
|
4108
|
+
timestamp: z16.string()
|
|
3237
4109
|
}).loose();
|
|
3238
4110
|
var StreamError = class extends Error {
|
|
3239
4111
|
constructor(message, fatal) {
|
|
@@ -3247,7 +4119,7 @@ var formatLogEvent = (event, format) => {
|
|
|
3247
4119
|
logEntries.forEach((entry) => {
|
|
3248
4120
|
const msg = formatMessages(entry.messages);
|
|
3249
4121
|
const level = entry.level.toUpperCase();
|
|
3250
|
-
const coloredLevel =
|
|
4122
|
+
const coloredLevel = colorize10(LEVEL_COLORS2[level] ?? "white", level);
|
|
3251
4123
|
switch (format) {
|
|
3252
4124
|
case "short":
|
|
3253
4125
|
consola.log(msg);
|
|
@@ -3318,10 +4190,8 @@ var openLogStream = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
|
3318
4190
|
throw new StreamError(new UnauthorizedError().message, true);
|
|
3319
4191
|
}
|
|
3320
4192
|
if (!response.ok) {
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
isFatalStatusCode(response.status)
|
|
3324
|
-
);
|
|
4193
|
+
const error = await httpError(response, "Failed to open log stream");
|
|
4194
|
+
throw new StreamError(error.message, isFatalStatusCode(response.status));
|
|
3325
4195
|
}
|
|
3326
4196
|
const reader = response.body?.getReader();
|
|
3327
4197
|
if (!reader) {
|
|
@@ -3390,7 +4260,7 @@ var tailLogs = async (projectUuid, storeHash, accessToken, apiHost, format, conn
|
|
|
3390
4260
|
}
|
|
3391
4261
|
}
|
|
3392
4262
|
};
|
|
3393
|
-
var tail = new
|
|
4263
|
+
var tail = new Command9("tail").configureHelp({ showGlobalOptions: true }).description("Tail live logs from your deployed application.").addHelpText(
|
|
3394
4264
|
"after",
|
|
3395
4265
|
`
|
|
3396
4266
|
Examples:
|
|
@@ -3406,10 +4276,11 @@ Examples:
|
|
|
3406
4276
|
).action(async (options) => {
|
|
3407
4277
|
try {
|
|
3408
4278
|
const config2 = getProjectConfig();
|
|
4279
|
+
const apiHost = resolveApiHost(options, config2);
|
|
3409
4280
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
3410
4281
|
await telemetry.identify(storeHash);
|
|
3411
4282
|
const projectUuid = resolveProjectUuid(options);
|
|
3412
|
-
await tailLogs(projectUuid, storeHash, accessToken,
|
|
4283
|
+
await tailLogs(projectUuid, storeHash, accessToken, apiHost, options.format);
|
|
3413
4284
|
} catch (error) {
|
|
3414
4285
|
consola.error(error);
|
|
3415
4286
|
process.exit(1);
|
|
@@ -3418,11 +4289,11 @@ Examples:
|
|
|
3418
4289
|
var parseIntInRange = (flag, min, max) => (value) => {
|
|
3419
4290
|
const parsed = Number(value);
|
|
3420
4291
|
if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
|
|
3421
|
-
throw new
|
|
4292
|
+
throw new InvalidArgumentError4(`${flag} must be an integer between ${min} and ${max}.`);
|
|
3422
4293
|
}
|
|
3423
4294
|
return parsed;
|
|
3424
4295
|
};
|
|
3425
|
-
var query = new
|
|
4296
|
+
var query = new Command9("query").configureHelp({ showGlobalOptions: true }).description("Query historical logs from your deployed application.").addHelpText(
|
|
3426
4297
|
"after",
|
|
3427
4298
|
`
|
|
3428
4299
|
Specify a time window with \`--since\` (relative to now) or \`--start\`/\`--end\`
|
|
@@ -3465,11 +4336,12 @@ Examples:
|
|
|
3465
4336
|
).action(async (options) => {
|
|
3466
4337
|
try {
|
|
3467
4338
|
const config2 = getProjectConfig();
|
|
4339
|
+
const apiHost = resolveApiHost(options, config2);
|
|
3468
4340
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
3469
4341
|
await telemetry.identify(storeHash);
|
|
3470
4342
|
const projectUuid = resolveProjectUuid(options);
|
|
3471
4343
|
const { start: start2, end } = resolveTimeWindow(options);
|
|
3472
|
-
const result = await queryLogs(projectUuid, storeHash, accessToken,
|
|
4344
|
+
const result = await queryLogs(projectUuid, storeHash, accessToken, apiHost, {
|
|
3473
4345
|
start: start2,
|
|
3474
4346
|
end,
|
|
3475
4347
|
method: options.method,
|
|
@@ -3508,12 +4380,12 @@ Examples:
|
|
|
3508
4380
|
process.exit(1);
|
|
3509
4381
|
}
|
|
3510
4382
|
});
|
|
3511
|
-
var logs = new
|
|
4383
|
+
var logs = new Command9("logs").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
|
|
3512
4384
|
|
|
3513
4385
|
// src/cli/commands/project.ts
|
|
3514
|
-
import { confirm as confirm5, input as
|
|
3515
|
-
import { Command as
|
|
3516
|
-
import { colorize as
|
|
4386
|
+
import { confirm as confirm5, input as input5, select as select7 } from "@inquirer/prompts";
|
|
4387
|
+
import { Command as Command10 } from "commander";
|
|
4388
|
+
import { colorize as colorize11 } from "consola/utils";
|
|
3517
4389
|
async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
3518
4390
|
const projectDir = process.cwd();
|
|
3519
4391
|
if (getProjectState().isTransformed) {
|
|
@@ -3532,19 +4404,21 @@ async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
|
3532
4404
|
accessToken: credentials?.accessToken
|
|
3533
4405
|
});
|
|
3534
4406
|
consola.success("Commerce Hosting setup complete.");
|
|
3535
|
-
await
|
|
4407
|
+
const packageManager = await detectProjectPackageManager(projectDir);
|
|
4408
|
+
await installDependencies(projectDir, packageManager);
|
|
3536
4409
|
}
|
|
3537
|
-
var list3 = new
|
|
4410
|
+
var list3 = new Command10("list").configureHelp({ showGlobalOptions: true }).description("List BigCommerce infrastructure projects for your store.").addHelpText(
|
|
3538
4411
|
"after",
|
|
3539
4412
|
`
|
|
3540
4413
|
Example:
|
|
3541
4414
|
$ catalyst project list`
|
|
3542
4415
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).action(async (options) => {
|
|
3543
4416
|
const config2 = getProjectConfig();
|
|
4417
|
+
const apiHost = resolveApiHost(options, config2);
|
|
3544
4418
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
3545
4419
|
await getTelemetry().identify(storeHash);
|
|
3546
4420
|
consola.start("Fetching projects...");
|
|
3547
|
-
const projects = await fetchProjects(storeHash, accessToken,
|
|
4421
|
+
const projects = await fetchProjects(storeHash, accessToken, apiHost);
|
|
3548
4422
|
consola.success("Projects fetched.");
|
|
3549
4423
|
if (projects.length === 0) {
|
|
3550
4424
|
consola.info("No projects found.");
|
|
@@ -3553,16 +4427,16 @@ Example:
|
|
|
3553
4427
|
}
|
|
3554
4428
|
const linkedProjectUuid = config2.get("projectUuid");
|
|
3555
4429
|
const projectList = projects.map((p) => {
|
|
3556
|
-
const marker = p.uuid === linkedProjectUuid ? ` ${
|
|
4430
|
+
const marker = p.uuid === linkedProjectUuid ? ` ${colorize11("green", "[linked]")}` : "";
|
|
3557
4431
|
const hostnames = p.deployment_hostnames.length === 0 ? [" (not deployed)"] : p.deployment_hostnames.map(
|
|
3558
|
-
(hostname) => ` ${
|
|
4432
|
+
(hostname) => ` ${colorize11("blue", `https://${hostname}`)}`
|
|
3559
4433
|
);
|
|
3560
4434
|
return [`${p.name} (${p.uuid})${marker}`, ...hostnames].join("\n");
|
|
3561
4435
|
});
|
|
3562
4436
|
consola.log(projectList.join("\n\n"));
|
|
3563
4437
|
process.exit(0);
|
|
3564
4438
|
});
|
|
3565
|
-
var
|
|
4439
|
+
var create3 = new Command10("create").configureHelp({ showGlobalOptions: true }).description(
|
|
3566
4440
|
"Create a new BigCommerce infrastructure project and link it to your local Catalyst project. Walks you through logging in if you are not authenticated yet."
|
|
3567
4441
|
).addHelpText(
|
|
3568
4442
|
"after",
|
|
@@ -3571,6 +4445,7 @@ Example:
|
|
|
3571
4445
|
$ catalyst project create`
|
|
3572
4446
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).action(async (options) => {
|
|
3573
4447
|
const config2 = getProjectConfig();
|
|
4448
|
+
const apiHost = resolveApiHost(options, config2);
|
|
3574
4449
|
let storeHash = options.storeHash ?? config2.get("storeHash");
|
|
3575
4450
|
let accessToken = options.accessToken ?? config2.get("accessToken");
|
|
3576
4451
|
if (!storeHash || !accessToken) {
|
|
@@ -3578,7 +4453,7 @@ Example:
|
|
|
3578
4453
|
"You're not logged in yet. Let's get you authenticated before creating a project."
|
|
3579
4454
|
);
|
|
3580
4455
|
try {
|
|
3581
|
-
const credentials = await login(options.loginUrl,
|
|
4456
|
+
const credentials = await login(options.loginUrl, apiHost);
|
|
3582
4457
|
storeHash = credentials.storeHash;
|
|
3583
4458
|
accessToken = credentials.accessToken;
|
|
3584
4459
|
config2.set("storeHash", storeHash);
|
|
@@ -3596,8 +4471,8 @@ Example:
|
|
|
3596
4471
|
}
|
|
3597
4472
|
}
|
|
3598
4473
|
await getTelemetry().identify(storeHash);
|
|
3599
|
-
const newProjectName = await
|
|
3600
|
-
const data = await createProject(newProjectName, storeHash, accessToken,
|
|
4474
|
+
const newProjectName = await input5({ message: "Enter a name for the new project:" });
|
|
4475
|
+
const data = await createProject(newProjectName, storeHash, accessToken, apiHost);
|
|
3601
4476
|
consola.success(`Project "${data.name}" created successfully.`);
|
|
3602
4477
|
consola.start("Writing project UUID to .bigcommerce/project.json...");
|
|
3603
4478
|
config2.set("projectUuid", data.uuid);
|
|
@@ -3606,7 +4481,7 @@ Example:
|
|
|
3606
4481
|
consola.success("Project UUID written to .bigcommerce/project.json.");
|
|
3607
4482
|
process.exit(0);
|
|
3608
4483
|
});
|
|
3609
|
-
var link2 = new
|
|
4484
|
+
var link2 = new Command10("link").configureHelp({ showGlobalOptions: true }).description(
|
|
3610
4485
|
"Link your local Catalyst project to a BigCommerce infrastructure project. You can provide a project UUID directly, or fetch and select from available projects using your store credentials."
|
|
3611
4486
|
).addHelpText(
|
|
3612
4487
|
"after",
|
|
@@ -3619,6 +4494,7 @@ Examples:
|
|
|
3619
4494
|
$ catalyst project link --project-uuid <UUID>`
|
|
3620
4495
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).action(async (options) => {
|
|
3621
4496
|
const config2 = getProjectConfig();
|
|
4497
|
+
const apiHost = resolveApiHost(options, config2);
|
|
3622
4498
|
const writeProjectConfig = (uuid, credentials) => {
|
|
3623
4499
|
consola.start("Writing project UUID to .bigcommerce/project.json...");
|
|
3624
4500
|
config2.set("projectUuid", uuid);
|
|
@@ -3639,7 +4515,7 @@ Examples:
|
|
|
3639
4515
|
let selected;
|
|
3640
4516
|
try {
|
|
3641
4517
|
selected = await selectOrCreateInfrastructureProject(
|
|
3642
|
-
{ storeHash, accessToken, apiHost
|
|
4518
|
+
{ storeHash, accessToken, apiHost },
|
|
3643
4519
|
config2.get("projectUuid")
|
|
3644
4520
|
);
|
|
3645
4521
|
} catch (error) {
|
|
@@ -3656,7 +4532,7 @@ Examples:
|
|
|
3656
4532
|
await offerCommerceHostingSetup(selected.uuid, { storeHash, accessToken });
|
|
3657
4533
|
process.exit(0);
|
|
3658
4534
|
});
|
|
3659
|
-
var del = new
|
|
4535
|
+
var del = new Command10("delete").configureHelp({ showGlobalOptions: true }).description(
|
|
3660
4536
|
"Permanently delete a BigCommerce infrastructure project. This action is irreversible."
|
|
3661
4537
|
).addHelpText(
|
|
3662
4538
|
"after",
|
|
@@ -3672,13 +4548,14 @@ Examples:
|
|
|
3672
4548
|
$ catalyst project delete --project-uuid <UUID> --force`
|
|
3673
4549
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--force", "Skip the confirmation prompt before deleting.").action(async (options) => {
|
|
3674
4550
|
const config2 = getProjectConfig();
|
|
4551
|
+
const apiHost = resolveApiHost(options, config2);
|
|
3675
4552
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
3676
4553
|
await getTelemetry().identify(storeHash);
|
|
3677
4554
|
let targetUuid = options.projectUuid;
|
|
3678
4555
|
let targetName;
|
|
3679
4556
|
if (!targetUuid) {
|
|
3680
4557
|
consola.start("Fetching projects...");
|
|
3681
|
-
const projects = await fetchProjects(storeHash, accessToken,
|
|
4558
|
+
const projects = await fetchProjects(storeHash, accessToken, apiHost);
|
|
3682
4559
|
consola.success("Projects fetched.");
|
|
3683
4560
|
if (projects.length === 0) {
|
|
3684
4561
|
consola.info("No projects found.");
|
|
@@ -3686,11 +4563,11 @@ Examples:
|
|
|
3686
4563
|
return;
|
|
3687
4564
|
}
|
|
3688
4565
|
const linkedProjectUuid = config2.get("projectUuid");
|
|
3689
|
-
const selected = await
|
|
4566
|
+
const selected = await select7({
|
|
3690
4567
|
message: "Select a project to delete (Press <enter> to select).",
|
|
3691
4568
|
choices: [
|
|
3692
4569
|
...projects.map((p) => ({
|
|
3693
|
-
name: p.uuid === linkedProjectUuid ? `${p.name} ${
|
|
4570
|
+
name: p.uuid === linkedProjectUuid ? `${p.name} ${colorize11("green", "[linked]")}` : p.name,
|
|
3694
4571
|
value: p.uuid,
|
|
3695
4572
|
description: p.uuid
|
|
3696
4573
|
})),
|
|
@@ -3722,7 +4599,7 @@ Examples:
|
|
|
3722
4599
|
}
|
|
3723
4600
|
}
|
|
3724
4601
|
consola.start(`Deleting project ${targetUuid}...`);
|
|
3725
|
-
await deleteProject(targetUuid, storeHash, accessToken,
|
|
4602
|
+
await deleteProject(targetUuid, storeHash, accessToken, apiHost);
|
|
3726
4603
|
consola.success(`Project ${targetUuid} deleted.`);
|
|
3727
4604
|
if (config2.get("projectUuid") === targetUuid) {
|
|
3728
4605
|
config2.delete("projectUuid");
|
|
@@ -3730,14 +4607,14 @@ Examples:
|
|
|
3730
4607
|
}
|
|
3731
4608
|
process.exit(0);
|
|
3732
4609
|
});
|
|
3733
|
-
var project = new
|
|
4610
|
+
var project = new Command10("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure project.").addCommand(create3).addCommand(list3).addCommand(link2).addCommand(del);
|
|
3734
4611
|
|
|
3735
4612
|
// src/cli/commands/start.ts
|
|
3736
|
-
import { Command as
|
|
3737
|
-
import { execa as
|
|
3738
|
-
import { existsSync as
|
|
4613
|
+
import { Command as Command11 } from "commander";
|
|
4614
|
+
import { execa as execa3 } from "execa";
|
|
4615
|
+
import { existsSync as existsSync6, lstatSync, symlinkSync } from "fs";
|
|
3739
4616
|
import { join as join13, relative } from "path";
|
|
3740
|
-
var start = new
|
|
4617
|
+
var start = new Command11("start").configureHelp({ showGlobalOptions: true }).description(
|
|
3741
4618
|
"Start a local preview of your Catalyst storefront using the OpenNext Cloudflare adapter."
|
|
3742
4619
|
).addHelpText(
|
|
3743
4620
|
"after",
|
|
@@ -3748,7 +4625,7 @@ Example:
|
|
|
3748
4625
|
const state = getProjectState();
|
|
3749
4626
|
if (!state.isTransformed) {
|
|
3750
4627
|
consola.info("Project is not set up for Commerce Hosting \u2014 running `next start`.");
|
|
3751
|
-
await
|
|
4628
|
+
await execa3("pnpm", ["exec", "next", "start"], {
|
|
3752
4629
|
stdio: "inherit",
|
|
3753
4630
|
cwd: process.cwd()
|
|
3754
4631
|
});
|
|
@@ -3756,8 +4633,8 @@ Example:
|
|
|
3756
4633
|
}
|
|
3757
4634
|
const envLocal = join13(process.cwd(), ".env.local");
|
|
3758
4635
|
const devVars = join13(process.cwd(), ".bigcommerce", ".dev.vars");
|
|
3759
|
-
if (
|
|
3760
|
-
if (!
|
|
4636
|
+
if (existsSync6(envLocal)) {
|
|
4637
|
+
if (!existsSync6(devVars)) {
|
|
3761
4638
|
const target = relative(join13(process.cwd(), ".bigcommerce"), envLocal);
|
|
3762
4639
|
symlinkSync(target, devVars);
|
|
3763
4640
|
consola.success("Symlinked .bigcommerce/.dev.vars \u2192 .env.local");
|
|
@@ -3771,7 +4648,7 @@ Example:
|
|
|
3771
4648
|
"No .env.local file found. The preview server may fail without environment variables.\nCreate a .env.local file in your project root with the required variables."
|
|
3772
4649
|
);
|
|
3773
4650
|
}
|
|
3774
|
-
await
|
|
4651
|
+
await execa3(
|
|
3775
4652
|
"pnpm",
|
|
3776
4653
|
[
|
|
3777
4654
|
"exec",
|
|
@@ -3788,11 +4665,11 @@ Example:
|
|
|
3788
4665
|
});
|
|
3789
4666
|
|
|
3790
4667
|
// src/cli/commands/telemetry.ts
|
|
3791
|
-
import { Argument, Command as
|
|
3792
|
-
import { colorize as
|
|
4668
|
+
import { Argument, Command as Command12, Option as Option9 } from "commander";
|
|
4669
|
+
import { colorize as colorize12 } from "consola/utils";
|
|
3793
4670
|
var telemetryService = getTelemetry();
|
|
3794
4671
|
var isEnabled = telemetryService.isEnabled();
|
|
3795
|
-
var telemetry2 = new
|
|
4672
|
+
var telemetry2 = new Command12("telemetry").configureHelp({ showGlobalOptions: true }).description(
|
|
3796
4673
|
"View or change CLI telemetry collection status. Enabling telemetry helps BigCommerce support diagnose and troubleshoot errors you encounter when using the CLI."
|
|
3797
4674
|
).addHelpText(
|
|
3798
4675
|
"after",
|
|
@@ -3823,7 +4700,7 @@ Examples:
|
|
|
3823
4700
|
consola.info("Catalyst CLI Telemetry\n");
|
|
3824
4701
|
}
|
|
3825
4702
|
consola.info(
|
|
3826
|
-
`Status: ${
|
|
4703
|
+
`Status: ${colorize12("bold", isEnabled ? colorize12("green", "Enabled") : colorize12("red", "Disabled"))}`
|
|
3827
4704
|
);
|
|
3828
4705
|
if (!isEnabled) {
|
|
3829
4706
|
consola.info(
|
|
@@ -3833,9 +4710,9 @@ Examples:
|
|
|
3833
4710
|
});
|
|
3834
4711
|
|
|
3835
4712
|
// src/cli/commands/upgrade.ts
|
|
3836
|
-
import { Command as
|
|
4713
|
+
import { Command as Command13, Option as Option10 } from "@commander-js/extra-typings";
|
|
3837
4714
|
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
3838
|
-
import { execa as
|
|
4715
|
+
import { execa as execa4 } from "execa";
|
|
3839
4716
|
import {
|
|
3840
4717
|
access as access2,
|
|
3841
4718
|
copyFile as copyFile2,
|
|
@@ -3849,13 +4726,13 @@ import {
|
|
|
3849
4726
|
writeFile as writeFile2
|
|
3850
4727
|
} from "fs/promises";
|
|
3851
4728
|
import { homedir, tmpdir } from "os";
|
|
3852
|
-
import { dirname as
|
|
4729
|
+
import { dirname as dirname5, join as join14, relative as relative2 } from "path";
|
|
3853
4730
|
import yoctoSpinner4 from "yocto-spinner";
|
|
3854
|
-
import { z as
|
|
3855
|
-
var CorePackageJson =
|
|
3856
|
-
name:
|
|
3857
|
-
version:
|
|
3858
|
-
catalyst:
|
|
4731
|
+
import { z as z17 } from "zod";
|
|
4732
|
+
var CorePackageJson = z17.object({
|
|
4733
|
+
name: z17.string().optional(),
|
|
4734
|
+
version: z17.string(),
|
|
4735
|
+
catalyst: z17.object({ version: z17.string(), ref: z17.string() }).optional()
|
|
3859
4736
|
});
|
|
3860
4737
|
var DEFAULT_REPOSITORY = "bigcommerce/catalyst";
|
|
3861
4738
|
var MOVING_TAGS = /* @__PURE__ */ new Set(["latest", "canary", "alpha"]);
|
|
@@ -3898,7 +4775,7 @@ async function isBinary(p) {
|
|
|
3898
4775
|
return buf.subarray(0, 8e3).includes(0);
|
|
3899
4776
|
}
|
|
3900
4777
|
async function copyInto(src, dest) {
|
|
3901
|
-
await mkdir(
|
|
4778
|
+
await mkdir(dirname5(dest), { recursive: true });
|
|
3902
4779
|
await copyFile2(src, dest);
|
|
3903
4780
|
}
|
|
3904
4781
|
function parseRef(ref) {
|
|
@@ -3912,7 +4789,7 @@ async function readResolvedVersion(coreDir) {
|
|
|
3912
4789
|
return pkg.catalyst?.version ?? pkg.version;
|
|
3913
4790
|
}
|
|
3914
4791
|
async function mergeFile(oursPath, basePath, theirsPath) {
|
|
3915
|
-
const merged = await
|
|
4792
|
+
const merged = await execa4(
|
|
3916
4793
|
"git",
|
|
3917
4794
|
[
|
|
3918
4795
|
"merge-file",
|
|
@@ -3995,7 +4872,7 @@ async function mergeCorePerFile(baseDir, theirsDir, catalystRoot, emptyFile) {
|
|
|
3995
4872
|
}
|
|
3996
4873
|
async function gitSupportsMergeTree() {
|
|
3997
4874
|
try {
|
|
3998
|
-
const { stdout } = await
|
|
4875
|
+
const { stdout } = await execa4("git", ["--version"]);
|
|
3999
4876
|
const match = /(\d+)\.(\d+)/.exec(stdout);
|
|
4000
4877
|
if (!match) return false;
|
|
4001
4878
|
const [major, minor] = [Number(match[1]), Number(match[2])];
|
|
@@ -4013,8 +4890,8 @@ async function resolveStrategy(strategy) {
|
|
|
4013
4890
|
async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
|
|
4014
4891
|
const scratch = await mkdtemp(join14(tmpdir(), "catalyst-merge-"));
|
|
4015
4892
|
try {
|
|
4016
|
-
await
|
|
4017
|
-
await
|
|
4893
|
+
await execa4("git", ["init", "-q", scratch]);
|
|
4894
|
+
await execa4("git", ["config", "core.autocrlf", "false"], {
|
|
4018
4895
|
env: { ...process.env, GIT_DIR: join14(scratch, ".git") }
|
|
4019
4896
|
});
|
|
4020
4897
|
const env2 = {
|
|
@@ -4031,10 +4908,10 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
|
|
|
4031
4908
|
GIT_WORK_TREE: workTree,
|
|
4032
4909
|
GIT_INDEX_FILE: join14(scratch, `index.${tag}`)
|
|
4033
4910
|
};
|
|
4034
|
-
await
|
|
4035
|
-
return (await
|
|
4911
|
+
await execa4("git", ["add", "-A"], { env: sideEnv });
|
|
4912
|
+
return (await execa4("git", ["write-tree"], { env: sideEnv })).stdout.trim();
|
|
4036
4913
|
};
|
|
4037
|
-
const commitTree = (tree, parent) =>
|
|
4914
|
+
const commitTree = (tree, parent) => execa4("git", ["commit-tree", tree, "-m", "x", ...parent ? ["-p", parent] : []], {
|
|
4038
4915
|
env: env2
|
|
4039
4916
|
}).then((r) => r.stdout.trim());
|
|
4040
4917
|
const baseTree = await writeTree(baseDir, "base");
|
|
@@ -4046,10 +4923,10 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
|
|
|
4046
4923
|
commitTree(theirsTree, baseCommit)
|
|
4047
4924
|
]);
|
|
4048
4925
|
await Promise.all([
|
|
4049
|
-
|
|
4050
|
-
|
|
4926
|
+
execa4("git", ["branch", "ours", oursCommit], { env: env2 }),
|
|
4927
|
+
execa4("git", ["branch", "theirs", theirsCommit], { env: env2 })
|
|
4051
4928
|
]);
|
|
4052
|
-
const merge = await
|
|
4929
|
+
const merge = await execa4(
|
|
4053
4930
|
"git",
|
|
4054
4931
|
[
|
|
4055
4932
|
"merge-tree",
|
|
@@ -4075,9 +4952,9 @@ async function mergeCoreTree(baseDir, theirsDir, catalystRoot) {
|
|
|
4075
4952
|
GIT_WORK_TREE: catalystRoot,
|
|
4076
4953
|
GIT_INDEX_FILE: join14(scratch, "index.out")
|
|
4077
4954
|
};
|
|
4078
|
-
await
|
|
4079
|
-
await
|
|
4080
|
-
const diff = await
|
|
4955
|
+
await execa4("git", ["read-tree", mergedTree], { env: outEnv });
|
|
4956
|
+
await execa4("git", ["checkout-index", "-a", "-f"], { env: outEnv });
|
|
4957
|
+
const diff = await execa4(
|
|
4081
4958
|
"git",
|
|
4082
4959
|
["diff", "-z", "--no-renames", "--name-status", oursTree, mergedTree],
|
|
4083
4960
|
{ env: env2 }
|
|
@@ -4112,19 +4989,19 @@ async function applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stam
|
|
|
4112
4989
|
if (stampedPkg) clean.push(toRepoPath(pkgRel));
|
|
4113
4990
|
const toStage = [...new Set(clean)].filter((path) => !conflictedRepoPaths.has(path));
|
|
4114
4991
|
if (toStage.length) {
|
|
4115
|
-
await
|
|
4992
|
+
await execa4("git", ["add", "-A", "--", ...toStage], { cwd: gitRoot });
|
|
4116
4993
|
}
|
|
4117
4994
|
if (stillConflicted.length === 0) return;
|
|
4118
4995
|
const OID = /^[0-9a-f]{40,64}$/;
|
|
4119
|
-
const gitFormat = (await
|
|
4996
|
+
const gitFormat = (await execa4("git", ["rev-parse", "--show-object-format"], { cwd: gitRoot, reject: false })).stdout.trim();
|
|
4120
4997
|
const NULL_OID = gitFormat === "sha256" ? "0".repeat(64) : "0".repeat(40);
|
|
4121
4998
|
const hashBlob = async (file) => {
|
|
4122
4999
|
if (!await pathExists(file)) return null;
|
|
4123
|
-
const oid = (await
|
|
5000
|
+
const oid = (await execa4("git", ["hash-object", "-w", "--", file], { cwd: gitRoot })).stdout.trim();
|
|
4124
5001
|
return OID.test(oid) ? oid : null;
|
|
4125
5002
|
};
|
|
4126
5003
|
const headEntry = async (repoPath) => {
|
|
4127
|
-
const { stdout } = await
|
|
5004
|
+
const { stdout } = await execa4("git", ["ls-tree", "HEAD", "--", repoPath], {
|
|
4128
5005
|
cwd: gitRoot,
|
|
4129
5006
|
reject: false
|
|
4130
5007
|
});
|
|
@@ -4147,7 +5024,7 @@ async function applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stam
|
|
|
4147
5024
|
].join("\n");
|
|
4148
5025
|
})
|
|
4149
5026
|
);
|
|
4150
|
-
await
|
|
5027
|
+
await execa4("git", ["update-index", "--index-info"], {
|
|
4151
5028
|
cwd: gitRoot,
|
|
4152
5029
|
input: `${records.join("\n")}
|
|
4153
5030
|
`
|
|
@@ -4156,7 +5033,7 @@ async function applyIndexState(gitRoot, relDir, baseDir, theirsDir, result, stam
|
|
|
4156
5033
|
async function resolveProject2(cwd) {
|
|
4157
5034
|
let gitRoot;
|
|
4158
5035
|
try {
|
|
4159
|
-
gitRoot = (await
|
|
5036
|
+
gitRoot = (await execa4("git", ["rev-parse", "--show-toplevel"], { cwd })).stdout.trim();
|
|
4160
5037
|
} catch {
|
|
4161
5038
|
return null;
|
|
4162
5039
|
}
|
|
@@ -4212,7 +5089,7 @@ async function downloadCore(repository, ref, destDir, token) {
|
|
|
4212
5089
|
const rawDir = `${destDir}.raw`;
|
|
4213
5090
|
await writeFile2(tarballPath, Buffer.from(await res.arrayBuffer()));
|
|
4214
5091
|
await mkdir(rawDir, { recursive: true });
|
|
4215
|
-
await
|
|
5092
|
+
await execa4("tar", ["-xzf", tarballPath, "-C", rawDir, "--strip-components=1"]);
|
|
4216
5093
|
const coreDir = join14(rawDir, "core");
|
|
4217
5094
|
const sourceDir = await pathExists(coreDir) ? coreDir : rawDir;
|
|
4218
5095
|
await rename(sourceDir, destDir);
|
|
@@ -4261,7 +5138,7 @@ Resolve them, then: git add ${relDir} && git commit`
|
|
|
4261
5138
|
consola.success("Staged all changes \u2014 review with `git diff --cached`, then commit.");
|
|
4262
5139
|
}
|
|
4263
5140
|
}
|
|
4264
|
-
var upgrade = new
|
|
5141
|
+
var upgrade = new Command13("upgrade").configureHelp({ showGlobalOptions: true }).aliases([
|
|
4265
5142
|
"up",
|
|
4266
5143
|
// These are hidden aliases
|
|
4267
5144
|
"upgrayedd",
|
|
@@ -4329,7 +5206,7 @@ to raise the GitHub API rate limit.`
|
|
|
4329
5206
|
}
|
|
4330
5207
|
if (!options.dryRun) {
|
|
4331
5208
|
const statusArgs = relDir === "." ? [] : ["--", relDir];
|
|
4332
|
-
const status = await
|
|
5209
|
+
const status = await execa4("git", ["status", "--porcelain", ...statusArgs], {
|
|
4333
5210
|
cwd: gitRoot,
|
|
4334
5211
|
reject: false
|
|
4335
5212
|
});
|
|
@@ -4346,11 +5223,11 @@ to raise the GitHub API rate limit.`
|
|
|
4346
5223
|
if (!project2.pkg.catalyst?.ref && !options.from && !options.dryRun) {
|
|
4347
5224
|
const { version: baseVersion } = parseRef(baseRef);
|
|
4348
5225
|
const rawPkg = await readFile2(pkgPath, "utf-8");
|
|
4349
|
-
const parsedPkg =
|
|
5226
|
+
const parsedPkg = z17.record(z17.string(), z17.unknown()).parse(JSON.parse(rawPkg));
|
|
4350
5227
|
parsedPkg.catalyst = { version: baseVersion, ref: baseRef };
|
|
4351
5228
|
await writeFile2(pkgPath, `${JSON.stringify(parsedPkg, null, 2)}
|
|
4352
5229
|
`);
|
|
4353
|
-
await
|
|
5230
|
+
await execa4("git", ["add", "--", pkgPath], { cwd: gitRoot });
|
|
4354
5231
|
consola.success(`Added catalyst.ref \u2192 ${baseRef}`);
|
|
4355
5232
|
}
|
|
4356
5233
|
const token = process.env.GITHUB_TOKEN;
|
|
@@ -4384,7 +5261,7 @@ to raise the GitHub API rate limit.`
|
|
|
4384
5261
|
const newRef = MOVING_TAGS.has(upstreamTagVersion) ? `${basePackage}@${resolvedVersion}` : upstreamRef;
|
|
4385
5262
|
if (options.dryRun) {
|
|
4386
5263
|
const diffSpinner = yoctoSpinner4().start("Generating diff...");
|
|
4387
|
-
const diff = await
|
|
5264
|
+
const diff = await execa4(
|
|
4388
5265
|
"git",
|
|
4389
5266
|
["diff", "--no-index", "--binary", "--diff-algorithm=histogram", "base", "theirs"],
|
|
4390
5267
|
{ cwd: tmpDir, reject: false, stripFinalNewline: false }
|
|
@@ -4419,7 +5296,7 @@ to raise the GitHub API rate limit.`
|
|
|
4419
5296
|
const patchedRaw = await readFile2(pkgPath, "utf-8");
|
|
4420
5297
|
let stampedPkg = false;
|
|
4421
5298
|
try {
|
|
4422
|
-
const patchedPkg =
|
|
5299
|
+
const patchedPkg = z17.record(z17.string(), z17.unknown()).parse(JSON.parse(patchedRaw));
|
|
4423
5300
|
patchedPkg.catalyst = { version: resolvedVersion, ref: newRef };
|
|
4424
5301
|
await writeFile2(pkgPath, `${JSON.stringify(patchedPkg, null, 2)}
|
|
4425
5302
|
`);
|
|
@@ -4455,7 +5332,7 @@ to raise the GitHub API rate limit.`
|
|
|
4455
5332
|
`Couldn't auto-stage the changes (${err instanceof Error ? err.message : String(err)}). Your files are merged on disk \u2014 run \`git add ${relDir}\` yourself.`
|
|
4456
5333
|
);
|
|
4457
5334
|
}
|
|
4458
|
-
const gitVersion = await
|
|
5335
|
+
const gitVersion = await execa4("git", ["--version"]).then((r) => r.stdout.trim()).catch(() => "unknown");
|
|
4459
5336
|
await getTelemetry().track("upgrade", {
|
|
4460
5337
|
strategy,
|
|
4461
5338
|
gitVersion,
|
|
@@ -4473,8 +5350,8 @@ to raise the GitHub API rate limit.`
|
|
|
4473
5350
|
});
|
|
4474
5351
|
|
|
4475
5352
|
// src/cli/commands/version.ts
|
|
4476
|
-
import { Command as
|
|
4477
|
-
var version = new
|
|
5353
|
+
import { Command as Command14 } from "commander";
|
|
5354
|
+
var version = new Command14("version").configureHelp({ showGlobalOptions: true }).description("Display detailed version information.").addHelpText(
|
|
4478
5355
|
"after",
|
|
4479
5356
|
`
|
|
4480
5357
|
Example:
|
|
@@ -4535,41 +5412,15 @@ var telemetryPostHook = async () => {
|
|
|
4535
5412
|
};
|
|
4536
5413
|
|
|
4537
5414
|
// src/cli/program.ts
|
|
4538
|
-
var defaultEnvPath = resolve(process.cwd(), ".env.local");
|
|
4539
|
-
if (existsSync4(defaultEnvPath)) {
|
|
4540
|
-
config({ path: defaultEnvPath });
|
|
4541
|
-
}
|
|
4542
5415
|
if (!process.env.CATALYST_STORE_HASH && process.env.BIGCOMMERCE_STORE_HASH) {
|
|
4543
5416
|
process.env.CATALYST_STORE_HASH = process.env.BIGCOMMERCE_STORE_HASH;
|
|
4544
5417
|
}
|
|
4545
|
-
var program = new
|
|
4546
|
-
consola.log(
|
|
5418
|
+
var program = new Command15();
|
|
5419
|
+
consola.log(colorize13("cyanBright", `\u25E2 ${package_default.name} v${package_default.version}
|
|
4547
5420
|
`));
|
|
4548
5421
|
program.name(package_default.name).version(package_default.version).summary("CLI tool for Catalyst development").description(
|
|
4549
|
-
"CLI tool for Catalyst development.\n\nConfiguration priority: flags > env
|
|
4550
|
-
).configureHelp({ showGlobalOptions: true }).
|
|
4551
|
-
new Option11(
|
|
4552
|
-
"--env-path <path>",
|
|
4553
|
-
"Path to environment file to load (relative to current working directory)"
|
|
4554
|
-
// We are using argParser, because commander loads in environment variables before executing hooks.
|
|
4555
|
-
).argParser((value) => {
|
|
4556
|
-
if (value) {
|
|
4557
|
-
const envFilePath = resolve(process.cwd(), value);
|
|
4558
|
-
const result = config({
|
|
4559
|
-
path: envFilePath,
|
|
4560
|
-
override: true
|
|
4561
|
-
});
|
|
4562
|
-
if (result.error) {
|
|
4563
|
-
const errCode = "code" in result.error && typeof result.error.code === "string" ? result.error.code : void 0;
|
|
4564
|
-
const message = errCode === "ENOENT" ? `Env file not found: ${envFilePath}` : `Failed to load --env-path ${value}: ${result.error.message}`;
|
|
4565
|
-
throw new Error(message);
|
|
4566
|
-
}
|
|
4567
|
-
consola.log(colorize11("cyanBright", `Loaded environment variables from ${envFilePath}
|
|
4568
|
-
`));
|
|
4569
|
-
}
|
|
4570
|
-
return value;
|
|
4571
|
-
})
|
|
4572
|
-
).addCommand(version).addCommand(create).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(project).addCommand(env).addCommand(channel).addCommand(auth).addCommand(upgrade).addCommand(telemetry2).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
|
|
5422
|
+
"CLI tool for Catalyst development.\n\nConfiguration priority: flags > process.env > .bigcommerce/project.json. `build` and `deploy` additionally load env files (--env-path, or an auto-loaded .env.local/.env) for the build.\n\nCATALYST_STORE_HASH falls back to BIGCOMMERCE_STORE_HASH if unset.\n\nRun `catalyst <command> --help` for details on a specific command."
|
|
5423
|
+
).configureHelp({ showGlobalOptions: true }).addCommand(version).addCommand(create2).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(project).addCommand(env).addCommand(channel).addCommand(auth).addCommand(upgrade).addCommand(telemetry2).addCommand(debug).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
|
|
4573
5424
|
|
|
4574
5425
|
// src/cli/index.ts
|
|
4575
5426
|
var handleFatalError = async (error) => {
|
|
@@ -4586,7 +5437,7 @@ var handleFatalError = async (error) => {
|
|
|
4586
5437
|
await telemetry3.analytics.closeAndFlush();
|
|
4587
5438
|
} catch {
|
|
4588
5439
|
}
|
|
4589
|
-
if (error instanceof
|
|
5440
|
+
if (error instanceof UserActionableError) {
|
|
4590
5441
|
consola.error(errorMessage);
|
|
4591
5442
|
process.exit(1);
|
|
4592
5443
|
}
|