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