@bigcommerce/catalyst 1.0.0-alpha.2 → 1.0.0-alpha.4
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 +1661 -311
- package/dist/cli.js.map +1 -1
- package/package.json +11 -2
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { randomBytes, randomUUID } from "crypto";
|
|
|
13
13
|
// package.json
|
|
14
14
|
var package_default = {
|
|
15
15
|
name: "@bigcommerce/catalyst",
|
|
16
|
-
version: "1.0.0-alpha.
|
|
16
|
+
version: "1.0.0-alpha.4",
|
|
17
17
|
type: "module",
|
|
18
18
|
bin: {
|
|
19
19
|
catalyst: "dist/cli.js"
|
|
@@ -35,22 +35,31 @@ var package_default = {
|
|
|
35
35
|
node: "^20.0.0 || ^22.0.0 || ^24.0.0"
|
|
36
36
|
},
|
|
37
37
|
dependencies: {
|
|
38
|
+
"@commander-js/extra-typings": "^14.0.0",
|
|
39
|
+
"@inquirer/prompts": "^7.5.3",
|
|
38
40
|
"@segment/analytics-node": "^2.2.1",
|
|
39
41
|
"adm-zip": "^0.5.16",
|
|
40
42
|
commander: "^14.0.0",
|
|
41
43
|
conf: "^13.1.0",
|
|
42
44
|
consola: "^3.4.2",
|
|
45
|
+
"cross-spawn": "^7.0.6",
|
|
43
46
|
dotenv: "^16.5.0",
|
|
44
47
|
execa: "^9.6.0",
|
|
48
|
+
"fs-extra": "^11.3.0",
|
|
49
|
+
"lodash.kebabcase": "^4.1.1",
|
|
50
|
+
nypm: "^0.5.4",
|
|
45
51
|
open: "^10.1.0",
|
|
52
|
+
"std-env": "^3.9.0",
|
|
46
53
|
"yocto-spinner": "^1.0.0",
|
|
47
54
|
zod: "^4.0.5"
|
|
48
55
|
},
|
|
49
56
|
devDependencies: {
|
|
50
57
|
"@bigcommerce/eslint-config": "^2.11.0",
|
|
51
58
|
"@bigcommerce/eslint-config-catalyst": "workspace:^",
|
|
52
|
-
"@commander-js/extra-typings": "^14.0.0",
|
|
53
59
|
"@types/adm-zip": "^0.5.7",
|
|
60
|
+
"@types/cross-spawn": "^6.0.6",
|
|
61
|
+
"@types/fs-extra": "^11.0.4",
|
|
62
|
+
"@types/lodash.kebabcase": "^4.1.9",
|
|
54
63
|
"@types/node": "^22.15.30",
|
|
55
64
|
"@vitest/coverage-v8": "^3.2.4",
|
|
56
65
|
"@vitest/ui": "^3.2.4",
|
|
@@ -66,23 +75,15 @@ var package_default = {
|
|
|
66
75
|
}
|
|
67
76
|
};
|
|
68
77
|
|
|
69
|
-
// src/cli/lib/
|
|
78
|
+
// src/cli/lib/user-config.ts
|
|
70
79
|
import Conf from "conf";
|
|
71
|
-
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
var userConfigInstance;
|
|
81
|
+
function getUserConfig() {
|
|
82
|
+
userConfigInstance ?? (userConfigInstance = new Conf({
|
|
83
|
+
projectName: "catalyst-cli",
|
|
75
84
|
projectSuffix: "",
|
|
76
|
-
configName: "
|
|
85
|
+
configName: "config",
|
|
77
86
|
schema: {
|
|
78
|
-
projectUuid: { type: "string", format: "uuid" },
|
|
79
|
-
framework: {
|
|
80
|
-
type: "string",
|
|
81
|
-
enum: ["catalyst"],
|
|
82
|
-
default: "catalyst"
|
|
83
|
-
},
|
|
84
|
-
storeHash: { type: "string" },
|
|
85
|
-
accessToken: { type: "string" },
|
|
86
87
|
telemetry: {
|
|
87
88
|
type: "object",
|
|
88
89
|
properties: {
|
|
@@ -91,7 +92,8 @@ function getProjectConfig() {
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
|
-
});
|
|
95
|
+
}));
|
|
96
|
+
return userConfigInstance;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
// src/cli/lib/telemetry.ts
|
|
@@ -104,14 +106,14 @@ var Telemetry = class {
|
|
|
104
106
|
this.projectVersion = package_default.version;
|
|
105
107
|
this.setEnabled = (_enabled) => {
|
|
106
108
|
const enabled = Boolean(_enabled);
|
|
107
|
-
this.
|
|
109
|
+
this.userConfig.set("telemetry.enabled", enabled);
|
|
108
110
|
};
|
|
109
111
|
this.CATALYST_TELEMETRY_DISABLED = process.env.CATALYST_TELEMETRY_DISABLED;
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
112
|
+
this.userConfig = getUserConfig();
|
|
113
|
+
this.correlationId = randomUUID();
|
|
112
114
|
this.startTime = Date.now();
|
|
113
115
|
this.analytics = new Analytics({
|
|
114
|
-
writeKey: "
|
|
116
|
+
writeKey: "OCkxLj3KTc1N3ebEbnFBqbhxfE3tVb3Z"
|
|
115
117
|
});
|
|
116
118
|
}
|
|
117
119
|
durationMs() {
|
|
@@ -126,7 +128,7 @@ var Telemetry = class {
|
|
|
126
128
|
anonymousId: this.getAnonymousId(),
|
|
127
129
|
properties: {
|
|
128
130
|
...payload,
|
|
129
|
-
|
|
131
|
+
correlationId: this.correlationId,
|
|
130
132
|
nodeVersion: process.version,
|
|
131
133
|
platform: process.platform,
|
|
132
134
|
arch: process.arch,
|
|
@@ -159,15 +161,15 @@ var Telemetry = class {
|
|
|
159
161
|
});
|
|
160
162
|
}
|
|
161
163
|
isEnabled() {
|
|
162
|
-
return !this.CATALYST_TELEMETRY_DISABLED && this.
|
|
164
|
+
return !this.CATALYST_TELEMETRY_DISABLED && this.userConfig.get(TELEMETRY_KEY_ENABLED, true);
|
|
163
165
|
}
|
|
164
166
|
getAnonymousId() {
|
|
165
|
-
const val = this.
|
|
167
|
+
const val = this.userConfig.get(TELEMETRY_KEY_ID);
|
|
166
168
|
if (val) {
|
|
167
169
|
return val;
|
|
168
170
|
}
|
|
169
171
|
const generated = randomBytes(32).toString("hex");
|
|
170
|
-
this.
|
|
172
|
+
this.userConfig.set(TELEMETRY_KEY_ID, generated);
|
|
171
173
|
return generated;
|
|
172
174
|
}
|
|
173
175
|
};
|
|
@@ -179,9 +181,10 @@ function getTelemetry() {
|
|
|
179
181
|
|
|
180
182
|
// src/cli/program.ts
|
|
181
183
|
import { Option as Option8 } from "@commander-js/extra-typings";
|
|
182
|
-
import { Command as
|
|
183
|
-
import { colorize as
|
|
184
|
+
import { Command as Command10 } from "commander";
|
|
185
|
+
import { colorize as colorize9 } from "consola/utils";
|
|
184
186
|
import { config } from "dotenv";
|
|
187
|
+
import { existsSync as existsSync5 } from "fs";
|
|
185
188
|
import { resolve } from "path";
|
|
186
189
|
|
|
187
190
|
// src/cli/commands/auth.ts
|
|
@@ -193,7 +196,7 @@ import { z as z3 } from "zod";
|
|
|
193
196
|
|
|
194
197
|
// src/cli/lib/auth.ts
|
|
195
198
|
import { z } from "zod";
|
|
196
|
-
var DEVICE_OAUTH_CLIENT_ID = "
|
|
199
|
+
var DEVICE_OAUTH_CLIENT_ID = "b8063bu6hhml4e0lqh22yut63atsbyv";
|
|
197
200
|
var DEVICE_OAUTH_SCOPES = [
|
|
198
201
|
"store_v2_information",
|
|
199
202
|
"store_infrastructure_deployments_manage",
|
|
@@ -257,6 +260,12 @@ async function waitForDeviceToken(loginUrl, deviceCode, interval) {
|
|
|
257
260
|
|
|
258
261
|
// src/cli/lib/project.ts
|
|
259
262
|
import { z as z2 } from "zod";
|
|
263
|
+
var InfrastructureProjectValidationError = class extends Error {
|
|
264
|
+
constructor(message) {
|
|
265
|
+
super(message);
|
|
266
|
+
this.name = "InfrastructureProjectValidationError";
|
|
267
|
+
}
|
|
268
|
+
};
|
|
260
269
|
var fetchProjectsSchema = z2.object({
|
|
261
270
|
data: z2.array(
|
|
262
271
|
z2.object({
|
|
@@ -265,17 +274,31 @@ var fetchProjectsSchema = z2.object({
|
|
|
265
274
|
})
|
|
266
275
|
)
|
|
267
276
|
});
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
277
|
+
function projectsUrl(storeHash, apiHost) {
|
|
278
|
+
return `https://${apiHost}/stores/${storeHash}/v3/infrastructure/projects`;
|
|
279
|
+
}
|
|
280
|
+
function authHeaders(accessToken) {
|
|
281
|
+
return {
|
|
282
|
+
"X-Auth-Token": accessToken,
|
|
283
|
+
"X-Correlation-Id": getTelemetry().correlationId
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async function hasProjectsAccess(storeHash, accessToken, apiHost) {
|
|
287
|
+
const response = await fetch(projectsUrl(storeHash, apiHost), {
|
|
288
|
+
method: "GET",
|
|
289
|
+
headers: authHeaders(accessToken)
|
|
290
|
+
});
|
|
291
|
+
if (response.status === 200) return true;
|
|
292
|
+
if (response.status === 403) return false;
|
|
293
|
+
throw new Error(
|
|
294
|
+
`GET /v3/infrastructure/projects failed: ${response.status} ${response.statusText}`
|
|
278
295
|
);
|
|
296
|
+
}
|
|
297
|
+
async function fetchProjects(storeHash, accessToken, apiHost) {
|
|
298
|
+
const response = await fetch(projectsUrl(storeHash, apiHost), {
|
|
299
|
+
method: "GET",
|
|
300
|
+
headers: authHeaders(accessToken)
|
|
301
|
+
});
|
|
279
302
|
if (response.status === 403) {
|
|
280
303
|
throw new Error(
|
|
281
304
|
"Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
|
|
@@ -296,32 +319,43 @@ var createProjectSchema = z2.object({
|
|
|
296
319
|
date_modified: z2.coerce.date()
|
|
297
320
|
})
|
|
298
321
|
});
|
|
322
|
+
var validationErrorBodySchema = z2.object({
|
|
323
|
+
title: z2.string().optional(),
|
|
324
|
+
detail: z2.string().optional(),
|
|
325
|
+
errors: z2.record(z2.string(), z2.string()).optional()
|
|
326
|
+
});
|
|
327
|
+
function extractValidationMessage(body) {
|
|
328
|
+
const parsed = validationErrorBodySchema.safeParse(body);
|
|
329
|
+
if (!parsed.success) return null;
|
|
330
|
+
const { title, detail, errors } = parsed.data;
|
|
331
|
+
if (errors && Object.keys(errors).length > 0) {
|
|
332
|
+
return Object.values(errors).join("; ");
|
|
333
|
+
}
|
|
334
|
+
return detail ?? title ?? null;
|
|
335
|
+
}
|
|
299
336
|
async function createProject(name, storeHash, accessToken, apiHost) {
|
|
300
|
-
const response = await fetch(
|
|
301
|
-
|
|
302
|
-
{
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
throw new Error("Failed to create project, is the name already in use?");
|
|
337
|
+
const response = await fetch(projectsUrl(storeHash, apiHost), {
|
|
338
|
+
method: "POST",
|
|
339
|
+
headers: {
|
|
340
|
+
...authHeaders(accessToken),
|
|
341
|
+
Accept: "application/json",
|
|
342
|
+
"Content-Type": "application/json"
|
|
343
|
+
},
|
|
344
|
+
body: JSON.stringify({ name })
|
|
345
|
+
});
|
|
346
|
+
if (response.status === 400 || response.status === 422) {
|
|
347
|
+
const body = await response.json().catch(() => null);
|
|
348
|
+
const fallback = response.status === 422 ? "The project name you entered doesn't meet the requirements. It must be 3\u201332 characters long and use only letters, numbers, hyphens (-), underscores (_), and periods (.)" : response.statusText;
|
|
349
|
+
const message = extractValidationMessage(body) ?? fallback;
|
|
350
|
+
throw new InfrastructureProjectValidationError(message);
|
|
315
351
|
}
|
|
316
352
|
if (response.status === 403) {
|
|
317
353
|
throw new Error(
|
|
318
354
|
"Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
|
|
319
355
|
);
|
|
320
356
|
}
|
|
321
|
-
if (response.status ===
|
|
322
|
-
throw new Error(
|
|
323
|
-
"The project name you entered doesn't meet the requirements. It must be 3\u201332 characters long and use only letters, numbers, hyphens (-), underscores (_), and periods (.)"
|
|
324
|
-
);
|
|
357
|
+
if (response.status === 502) {
|
|
358
|
+
throw new Error("Failed to create project, is the name already in use?");
|
|
325
359
|
}
|
|
326
360
|
if (!response.ok) {
|
|
327
361
|
throw new Error(`Failed to create project: ${response.statusText}`);
|
|
@@ -330,6 +364,44 @@ async function createProject(name, storeHash, accessToken, apiHost) {
|
|
|
330
364
|
const { data } = createProjectSchema.parse(res);
|
|
331
365
|
return data;
|
|
332
366
|
}
|
|
367
|
+
async function deleteProject(projectUuid, storeHash, accessToken, apiHost) {
|
|
368
|
+
const response = await fetch(`${projectsUrl(storeHash, apiHost)}/${projectUuid}`, {
|
|
369
|
+
method: "DELETE",
|
|
370
|
+
headers: authHeaders(accessToken)
|
|
371
|
+
});
|
|
372
|
+
if (response.status === 403) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
"Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (response.status === 404) {
|
|
378
|
+
throw new Error(`Project ${projectUuid} not found.`);
|
|
379
|
+
}
|
|
380
|
+
if (!response.ok) {
|
|
381
|
+
throw new Error(`Failed to delete project: ${response.statusText}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/cli/lib/project-config.ts
|
|
386
|
+
import Conf2 from "conf";
|
|
387
|
+
import { join } from "path";
|
|
388
|
+
function getProjectConfig() {
|
|
389
|
+
return new Conf2({
|
|
390
|
+
cwd: join(process.cwd(), ".bigcommerce"),
|
|
391
|
+
projectSuffix: "",
|
|
392
|
+
configName: "project",
|
|
393
|
+
schema: {
|
|
394
|
+
projectUuid: { type: "string", format: "uuid" },
|
|
395
|
+
framework: {
|
|
396
|
+
type: "string",
|
|
397
|
+
enum: ["catalyst"],
|
|
398
|
+
default: "catalyst"
|
|
399
|
+
},
|
|
400
|
+
storeHash: { type: "string" },
|
|
401
|
+
accessToken: { type: "string" }
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
333
405
|
|
|
334
406
|
// src/cli/commands/auth.ts
|
|
335
407
|
var StoreProfileSchema = z3.object({
|
|
@@ -355,7 +427,14 @@ async function fetchStoreProfile(storeHash, accessToken, apiHost) {
|
|
|
355
427
|
}
|
|
356
428
|
return result.data.data;
|
|
357
429
|
}
|
|
358
|
-
var whoami = new Command("whoami").description("Verify stored credentials and display store/project info.").
|
|
430
|
+
var whoami = new Command("whoami").configureHelp({ showGlobalOptions: true }).description("Verify stored credentials and display store/project info.").addHelpText(
|
|
431
|
+
"after",
|
|
432
|
+
`
|
|
433
|
+
Example:
|
|
434
|
+
$ catalyst auth whoami
|
|
435
|
+
|
|
436
|
+
Logged in to My Store (abc123), connected to project my-project (43eba682-0c48-11f1-9bd5-827a48b0ce1e)`
|
|
437
|
+
).addOption(
|
|
359
438
|
new Option(
|
|
360
439
|
"--store-hash <hash>",
|
|
361
440
|
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
@@ -366,7 +445,7 @@ var whoami = new Command("whoami").description("Verify stored credentials and di
|
|
|
366
445
|
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
367
446
|
).env("CATALYST_ACCESS_TOKEN")
|
|
368
447
|
).addOption(
|
|
369
|
-
new Option("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com")
|
|
448
|
+
new Option("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp()
|
|
370
449
|
).action(async (options) => {
|
|
371
450
|
try {
|
|
372
451
|
const config2 = getProjectConfig();
|
|
@@ -408,7 +487,18 @@ var whoami = new Command("whoami").description("Verify stored credentials and di
|
|
|
408
487
|
process.exit(1);
|
|
409
488
|
}
|
|
410
489
|
});
|
|
411
|
-
var login = new Command("login").
|
|
490
|
+
var login = new Command("login").configureHelp({ showGlobalOptions: true }).description(
|
|
491
|
+
"Authenticate via browser using the OAuth device code flow. If already logged in, displays current credentials and suggests running `catalyst auth logout` to re-authenticate."
|
|
492
|
+
).addHelpText(
|
|
493
|
+
"after",
|
|
494
|
+
`
|
|
495
|
+
Examples:
|
|
496
|
+
# Login via browser (recommended)
|
|
497
|
+
$ catalyst auth login
|
|
498
|
+
|
|
499
|
+
# Login with existing credentials (skips browser flow)
|
|
500
|
+
$ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN>`
|
|
501
|
+
).addOption(
|
|
412
502
|
new Option(
|
|
413
503
|
"--store-hash <hash>",
|
|
414
504
|
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
@@ -419,7 +509,7 @@ var login = new Command("login").description("Authenticate via browser using the
|
|
|
419
509
|
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
420
510
|
).env("CATALYST_ACCESS_TOKEN")
|
|
421
511
|
).addOption(
|
|
422
|
-
new Option("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL)
|
|
512
|
+
new Option("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
|
|
423
513
|
).action(async (options) => {
|
|
424
514
|
try {
|
|
425
515
|
const config2 = getProjectConfig();
|
|
@@ -460,7 +550,12 @@ var login = new Command("login").description("Authenticate via browser using the
|
|
|
460
550
|
process.exit(1);
|
|
461
551
|
}
|
|
462
552
|
});
|
|
463
|
-
var logout = new Command("logout").description("Remove stored credentials for the current project.").
|
|
553
|
+
var logout = new Command("logout").configureHelp({ showGlobalOptions: true }).description("Remove stored credentials for the current project.").addHelpText(
|
|
554
|
+
"after",
|
|
555
|
+
`
|
|
556
|
+
Example:
|
|
557
|
+
$ catalyst auth logout`
|
|
558
|
+
).action(() => {
|
|
464
559
|
try {
|
|
465
560
|
const config2 = getProjectConfig();
|
|
466
561
|
const storeHash = config2.get("storeHash");
|
|
@@ -480,13 +575,13 @@ var logout = new Command("logout").description("Remove stored credentials for th
|
|
|
480
575
|
process.exit(1);
|
|
481
576
|
}
|
|
482
577
|
});
|
|
483
|
-
var auth = new Command("auth").description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(login).addCommand(logout);
|
|
578
|
+
var auth = new Command("auth").configureHelp({ showGlobalOptions: true }).description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(login).addCommand(logout);
|
|
484
579
|
|
|
485
580
|
// src/cli/commands/build.ts
|
|
486
581
|
import { Command as Command2, Option as Option2 } from "commander";
|
|
487
582
|
import { execa } from "execa";
|
|
488
583
|
import { copyFile, cp, writeFile } from "fs/promises";
|
|
489
|
-
import { join as
|
|
584
|
+
import { join as join4 } from "path";
|
|
490
585
|
|
|
491
586
|
// src/cli/lib/get-module-cli-path.ts
|
|
492
587
|
import { dirname, join as join2 } from "path";
|
|
@@ -497,6 +592,46 @@ function getModuleCliPath() {
|
|
|
497
592
|
return join2(__dirname, "..");
|
|
498
593
|
}
|
|
499
594
|
|
|
595
|
+
// src/cli/lib/project-state.ts
|
|
596
|
+
import { existsSync, readFileSync } from "fs";
|
|
597
|
+
import { join as join3 } from "path";
|
|
598
|
+
import { z as z4 } from "zod";
|
|
599
|
+
var projectJsonSchema = z4.looseObject({
|
|
600
|
+
projectUuid: z4.string().optional()
|
|
601
|
+
});
|
|
602
|
+
var packageJsonSchema = z4.looseObject({
|
|
603
|
+
dependencies: z4.record(z4.string(), z4.string()).optional()
|
|
604
|
+
});
|
|
605
|
+
var safeReadJson = (path) => {
|
|
606
|
+
try {
|
|
607
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
608
|
+
} catch {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
function getProjectState(cwd = process.cwd()) {
|
|
613
|
+
const projectJson = projectJsonSchema.safeParse(
|
|
614
|
+
safeReadJson(join3(cwd, ".bigcommerce", "project.json"))
|
|
615
|
+
);
|
|
616
|
+
const projectUuid = projectJson.success ? projectJson.data.projectUuid : void 0;
|
|
617
|
+
const hasMiddleware = existsSync(join3(cwd, "middleware.ts"));
|
|
618
|
+
const hasProxy = existsSync(join3(cwd, "proxy.ts"));
|
|
619
|
+
const pkgJson = packageJsonSchema.safeParse(safeReadJson(join3(cwd, "package.json")));
|
|
620
|
+
const hasOpenNextDep = pkgJson.success ? Boolean(pkgJson.data.dependencies?.["@opennextjs/cloudflare"]) : false;
|
|
621
|
+
const isLinked = Boolean(projectUuid);
|
|
622
|
+
const isTransformed = hasMiddleware && !hasProxy && hasOpenNextDep;
|
|
623
|
+
const isFullySetUp = isLinked && isTransformed;
|
|
624
|
+
return {
|
|
625
|
+
projectUuid,
|
|
626
|
+
hasMiddleware,
|
|
627
|
+
hasProxy,
|
|
628
|
+
hasOpenNextDep,
|
|
629
|
+
isLinked,
|
|
630
|
+
isTransformed,
|
|
631
|
+
isFullySetUp
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
500
635
|
// src/cli/lib/wrangler-config.ts
|
|
501
636
|
function getWranglerConfig(projectUuid) {
|
|
502
637
|
return {
|
|
@@ -556,19 +691,19 @@ function getWranglerConfig(projectUuid) {
|
|
|
556
691
|
}
|
|
557
692
|
|
|
558
693
|
// src/cli/commands/build.ts
|
|
559
|
-
var WRANGLER_VERSION = "4.
|
|
694
|
+
var WRANGLER_VERSION = "4.90.0";
|
|
560
695
|
async function buildCatalystProject(projectUuid) {
|
|
561
696
|
const coreDir = process.cwd();
|
|
562
|
-
const openNextOutDir =
|
|
563
|
-
const bigcommerceDistDir =
|
|
697
|
+
const openNextOutDir = join4(coreDir, ".open-next");
|
|
698
|
+
const bigcommerceDistDir = join4(coreDir, ".bigcommerce", "dist");
|
|
564
699
|
const wranglerConfig = getWranglerConfig(projectUuid);
|
|
565
700
|
consola.start("Copying templates...");
|
|
566
701
|
await copyFile(
|
|
567
|
-
|
|
568
|
-
|
|
702
|
+
join4(getModuleCliPath(), "templates", "open-next.config.ts"),
|
|
703
|
+
join4(coreDir, ".bigcommerce", "open-next.config.ts")
|
|
569
704
|
);
|
|
570
705
|
await writeFile(
|
|
571
|
-
|
|
706
|
+
join4(coreDir, ".bigcommerce", "wrangler.jsonc"),
|
|
572
707
|
JSON.stringify(wranglerConfig, null, 2)
|
|
573
708
|
);
|
|
574
709
|
consola.success("Templates copied");
|
|
@@ -581,7 +716,7 @@ async function buildCatalystProject(projectUuid) {
|
|
|
581
716
|
"build",
|
|
582
717
|
"--skipWranglerConfigCheck",
|
|
583
718
|
"--openNextConfigPath",
|
|
584
|
-
|
|
719
|
+
join4(coreDir, ".bigcommerce", "open-next.config.ts")
|
|
585
720
|
],
|
|
586
721
|
{
|
|
587
722
|
stdout: ["pipe", "inherit"],
|
|
@@ -595,7 +730,7 @@ async function buildCatalystProject(projectUuid) {
|
|
|
595
730
|
`wrangler@${WRANGLER_VERSION}`,
|
|
596
731
|
"deploy",
|
|
597
732
|
"--config",
|
|
598
|
-
|
|
733
|
+
join4(coreDir, ".bigcommerce", "wrangler.jsonc"),
|
|
599
734
|
"--keep-vars",
|
|
600
735
|
"--outdir",
|
|
601
736
|
bigcommerceDistDir,
|
|
@@ -606,35 +741,1073 @@ async function buildCatalystProject(projectUuid) {
|
|
|
606
741
|
cwd: coreDir
|
|
607
742
|
}
|
|
608
743
|
);
|
|
609
|
-
consola.success("Project built");
|
|
610
|
-
await cp(
|
|
611
|
-
recursive: true,
|
|
612
|
-
force: true
|
|
744
|
+
consola.success("Project built");
|
|
745
|
+
await cp(join4(openNextOutDir, "assets"), join4(bigcommerceDistDir, "assets"), {
|
|
746
|
+
recursive: true,
|
|
747
|
+
force: true
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
var build = new Command2("build").configureHelp({ showGlobalOptions: true }).description(
|
|
751
|
+
"Build your Catalyst project using the OpenNext/Cloudflare build pipeline. Also runs a Wrangler dry-run to generate deployment artifacts."
|
|
752
|
+
).addHelpText(
|
|
753
|
+
"after",
|
|
754
|
+
`
|
|
755
|
+
Examples:
|
|
756
|
+
$ catalyst build
|
|
757
|
+
|
|
758
|
+
# Include project UUID
|
|
759
|
+
$ catalyst build --project-uuid <UUID>`
|
|
760
|
+
).addOption(
|
|
761
|
+
new Option2(
|
|
762
|
+
"--project-uuid <uuid>",
|
|
763
|
+
"Project UUID to be included in the deployment configuration."
|
|
764
|
+
).env("CATALYST_PROJECT_UUID")
|
|
765
|
+
).action(async (options) => {
|
|
766
|
+
const state = getProjectState();
|
|
767
|
+
if (!state.isTransformed) {
|
|
768
|
+
consola.info("Project is not set up for Commerce Hosting \u2014 running `next build`.");
|
|
769
|
+
consola.info("To deploy to Commerce Hosting, run `catalyst deploy`.");
|
|
770
|
+
await execa("pnpm", ["exec", "next", "build"], {
|
|
771
|
+
stdio: "inherit",
|
|
772
|
+
cwd: process.cwd()
|
|
773
|
+
});
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const config2 = getProjectConfig();
|
|
777
|
+
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
778
|
+
if (!projectUuid) {
|
|
779
|
+
throw new Error(
|
|
780
|
+
"Project UUID is required. Please run `catalyst project create` or `catalyst project link` or this command again with --project-uuid <uuid>."
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
await buildCatalystProject(projectUuid);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// src/cli/commands/create.ts
|
|
787
|
+
import { Command as Command3, InvalidArgumentError, Option as Option4 } from "@commander-js/extra-typings";
|
|
788
|
+
import { input as input2, select as select2 } from "@inquirer/prompts";
|
|
789
|
+
import { execSync as execSync4 } from "child_process";
|
|
790
|
+
import { colorize as colorize4 } from "consola/utils";
|
|
791
|
+
import { pathExistsSync } from "fs-extra/esm";
|
|
792
|
+
import kebabCase from "lodash.kebabcase";
|
|
793
|
+
import { join as join9 } from "path";
|
|
794
|
+
|
|
795
|
+
// src/cli/lib/build-workspace-packages.ts
|
|
796
|
+
import { execSync } from "child_process";
|
|
797
|
+
import { existsSync as existsSync2 } from "fs";
|
|
798
|
+
import { join as join5 } from "path";
|
|
799
|
+
import yoctoSpinner2 from "yocto-spinner";
|
|
800
|
+
var WORKSPACE_PACKAGES = ["packages/catalyst", "packages/client"];
|
|
801
|
+
var buildWorkspacePackages = (projectDir) => {
|
|
802
|
+
const hasCore = existsSync2(join5(projectDir, "core"));
|
|
803
|
+
const hasAllWorkspacePackages = WORKSPACE_PACKAGES.every(
|
|
804
|
+
(pkg) => existsSync2(join5(projectDir, pkg))
|
|
805
|
+
);
|
|
806
|
+
if (!hasCore || !hasAllWorkspacePackages) return;
|
|
807
|
+
WORKSPACE_PACKAGES.forEach((pkg) => {
|
|
808
|
+
const spinner = yoctoSpinner2().start(`Building ${pkg}...`);
|
|
809
|
+
try {
|
|
810
|
+
execSync("pnpm build", { cwd: join5(projectDir, pkg), stdio: "ignore" });
|
|
811
|
+
spinner.success(`Built ${pkg}.`);
|
|
812
|
+
} catch (error) {
|
|
813
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
814
|
+
spinner.error(`Failed to build ${pkg}: ${message}`);
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// src/cli/lib/channels.ts
|
|
821
|
+
import { z as z5 } from "zod";
|
|
822
|
+
var cliApiUrl = (origin, storeHash, path) => `${origin}/stores/${storeHash}/cli-api/v3${path}`;
|
|
823
|
+
var channelsUrl = (storeHash, apiHost, query2 = {}) => {
|
|
824
|
+
const params = new URLSearchParams(query2).toString();
|
|
825
|
+
return `https://${apiHost}/stores/${storeHash}/v3/channels${params ? `?${params}` : ""}`;
|
|
826
|
+
};
|
|
827
|
+
var authHeaders2 = (accessToken) => ({
|
|
828
|
+
"X-Auth-Token": accessToken,
|
|
829
|
+
"X-Correlation-Id": getTelemetry().correlationId,
|
|
830
|
+
Accept: "application/json"
|
|
831
|
+
});
|
|
832
|
+
var envVarsSchema = z5.record(z5.string(), z5.coerce.string());
|
|
833
|
+
var channelSchema = z5.object({
|
|
834
|
+
id: z5.number(),
|
|
835
|
+
name: z5.string(),
|
|
836
|
+
platform: z5.string()
|
|
837
|
+
});
|
|
838
|
+
var channelsResponseSchema = z5.object({
|
|
839
|
+
data: z5.array(channelSchema)
|
|
840
|
+
});
|
|
841
|
+
var initResponseSchema = z5.object({
|
|
842
|
+
data: z5.object({
|
|
843
|
+
storefront_api_token: z5.string(),
|
|
844
|
+
envVars: envVarsSchema
|
|
845
|
+
})
|
|
846
|
+
});
|
|
847
|
+
var createChannelResponseSchema = z5.object({
|
|
848
|
+
data: z5.object({
|
|
849
|
+
id: z5.number(),
|
|
850
|
+
storefront_api_token: z5.string(),
|
|
851
|
+
envVars: envVarsSchema
|
|
852
|
+
})
|
|
853
|
+
});
|
|
854
|
+
var eligibilityResponseSchema = z5.object({
|
|
855
|
+
data: z5.object({
|
|
856
|
+
eligible: z5.boolean(),
|
|
857
|
+
message: z5.string()
|
|
858
|
+
})
|
|
859
|
+
});
|
|
860
|
+
async function getChannelInit(channelId, storeHash, accessToken, origin) {
|
|
861
|
+
const response = await fetch(cliApiUrl(origin, storeHash, `/channels/${channelId}/init`), {
|
|
862
|
+
method: "GET",
|
|
863
|
+
headers: authHeaders2(accessToken)
|
|
864
|
+
});
|
|
865
|
+
if (!response.ok) {
|
|
866
|
+
throw new Error(
|
|
867
|
+
`GET /channels/${channelId}/init failed: ${response.status} ${response.statusText}`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
const { data } = initResponseSchema.parse(await response.json());
|
|
871
|
+
return { storefrontToken: data.storefront_api_token, envVars: data.envVars };
|
|
872
|
+
}
|
|
873
|
+
async function checkChannelEligibility(storeHash, accessToken, origin) {
|
|
874
|
+
const response = await fetch(cliApiUrl(origin, storeHash, "/channels/catalyst/eligibility"), {
|
|
875
|
+
method: "GET",
|
|
876
|
+
headers: authHeaders2(accessToken)
|
|
877
|
+
});
|
|
878
|
+
if (!response.ok) {
|
|
879
|
+
throw new Error(
|
|
880
|
+
`GET /channels/catalyst/eligibility failed: ${response.status} ${response.statusText}`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
return eligibilityResponseSchema.parse(await response.json()).data;
|
|
884
|
+
}
|
|
885
|
+
async function createChannel(name, storefrontLocale, additionalLocales, installSampleData, storeHash, accessToken, origin) {
|
|
886
|
+
const response = await fetch(cliApiUrl(origin, storeHash, "/channels/catalyst"), {
|
|
887
|
+
method: "POST",
|
|
888
|
+
headers: { ...authHeaders2(accessToken), "Content-Type": "application/json" },
|
|
889
|
+
body: JSON.stringify({
|
|
890
|
+
name,
|
|
891
|
+
initialData: { type: installSampleData ? "sample" : "none" },
|
|
892
|
+
deployStorefront: true,
|
|
893
|
+
devOrigin: "http://localhost:3000",
|
|
894
|
+
storefrontLanguage: storefrontLocale,
|
|
895
|
+
additionalLocales
|
|
896
|
+
})
|
|
897
|
+
});
|
|
898
|
+
if (!response.ok) {
|
|
899
|
+
throw new Error(`POST /channels/catalyst failed: ${response.status} ${response.statusText}`);
|
|
900
|
+
}
|
|
901
|
+
const { data } = createChannelResponseSchema.parse(await response.json());
|
|
902
|
+
return {
|
|
903
|
+
channelId: data.id,
|
|
904
|
+
storefrontToken: data.storefront_api_token,
|
|
905
|
+
envVars: data.envVars
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
async function fetchAvailableChannels(storeHash, accessToken, apiHost) {
|
|
909
|
+
const response = await fetch(
|
|
910
|
+
channelsUrl(storeHash, apiHost, { available: "true", type: "storefront" }),
|
|
911
|
+
{
|
|
912
|
+
method: "GET",
|
|
913
|
+
headers: {
|
|
914
|
+
"X-Auth-Token": accessToken,
|
|
915
|
+
"X-Correlation-Id": getTelemetry().correlationId,
|
|
916
|
+
Accept: "application/json"
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
);
|
|
920
|
+
if (!response.ok) {
|
|
921
|
+
throw new Error(`GET /v3/channels failed: ${response.status} ${response.statusText}`);
|
|
922
|
+
}
|
|
923
|
+
return channelsResponseSchema.parse(await response.json()).data;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/cli/lib/clone-catalyst.ts
|
|
927
|
+
import { execSync as execSync3 } from "child_process";
|
|
928
|
+
|
|
929
|
+
// src/cli/lib/checkout-ref.ts
|
|
930
|
+
import { sync as spawnSync } from "cross-spawn";
|
|
931
|
+
|
|
932
|
+
// src/cli/lib/is-exec-exception.ts
|
|
933
|
+
function isExecException(error) {
|
|
934
|
+
return typeof error === "object" && error !== null && "stdout" in error && "stderr" in error;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// src/cli/lib/checkout-ref.ts
|
|
938
|
+
function checkoutRef(repoDir, ref) {
|
|
939
|
+
try {
|
|
940
|
+
const spawn = spawnSync("git", ["checkout", ref, "--"], {
|
|
941
|
+
cwd: repoDir,
|
|
942
|
+
encoding: "utf8",
|
|
943
|
+
shell: false
|
|
944
|
+
});
|
|
945
|
+
const stderr = spawn.stderr.trim();
|
|
946
|
+
if (spawn.status !== 0 && stderr) {
|
|
947
|
+
throw new Error(stderr);
|
|
948
|
+
}
|
|
949
|
+
consola.success(`Checked out ref ${ref} successfully.`);
|
|
950
|
+
} catch (error) {
|
|
951
|
+
if (isExecException(error)) {
|
|
952
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
953
|
+
if (stderr.includes(`fatal: reference is not a tree: ${ref}`) || stderr.includes(`fatal: ambiguous argument '${ref}'`) || stderr.includes(`unknown revision or path not in the working tree`)) {
|
|
954
|
+
consola.error(`Ref '${ref}' not found in the repository.`);
|
|
955
|
+
} else {
|
|
956
|
+
consola.error(`Error checking out ref '${ref}':`, stderr.trim());
|
|
957
|
+
}
|
|
958
|
+
} else if (error instanceof Error) {
|
|
959
|
+
consola.error(`Error checking out ref '${ref}':`, error.message);
|
|
960
|
+
} else {
|
|
961
|
+
consola.error(`Unknown error occurred while checking out ref '${ref}'.`);
|
|
962
|
+
}
|
|
963
|
+
consola.warn(`Falling back to the default branch.`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/cli/lib/has-github-ssh.ts
|
|
968
|
+
import { execSync as execSync2 } from "child_process";
|
|
969
|
+
function hasGitHubSSH() {
|
|
970
|
+
try {
|
|
971
|
+
const output = execSync2("ssh -T git@github.com", {
|
|
972
|
+
encoding: "utf8",
|
|
973
|
+
stdio: "pipe"
|
|
974
|
+
}).toString();
|
|
975
|
+
return output.includes("successfully authenticated");
|
|
976
|
+
} catch (error) {
|
|
977
|
+
if (isExecException(error)) {
|
|
978
|
+
const stdout = error.stdout ? error.stdout.toString() : "";
|
|
979
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
980
|
+
const combinedOutput = stdout + stderr;
|
|
981
|
+
if (combinedOutput.includes("successfully authenticated")) {
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/cli/lib/reset-branch-to-ref.ts
|
|
990
|
+
import { sync as spawnSync2 } from "cross-spawn";
|
|
991
|
+
function resetBranchToRef(projectDir, ghRef) {
|
|
992
|
+
const spawn = spawnSync2("git", ["reset", "--hard", ghRef, "--"], {
|
|
993
|
+
cwd: projectDir,
|
|
994
|
+
encoding: "utf8",
|
|
995
|
+
shell: false
|
|
996
|
+
});
|
|
997
|
+
const stderr = spawn.stderr.trim();
|
|
998
|
+
if (spawn.status !== 0 && stderr) {
|
|
999
|
+
throw new Error(stderr);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/cli/lib/clone-catalyst.ts
|
|
1004
|
+
var cloneCatalyst = ({
|
|
1005
|
+
repository,
|
|
1006
|
+
projectName,
|
|
1007
|
+
projectDir,
|
|
1008
|
+
ghRef,
|
|
1009
|
+
resetMain = false
|
|
1010
|
+
}) => {
|
|
1011
|
+
const useSSH = hasGitHubSSH();
|
|
1012
|
+
consola.info(`Cloning ${repository} using ${useSSH ? "SSH" : "HTTPS"}...`);
|
|
1013
|
+
const cloneCommand = `git clone ${useSSH ? `git@github.com:${repository}` : `https://github.com/${repository}`}.git${projectName ? ` ${projectName}` : ""}`;
|
|
1014
|
+
execSync3(cloneCommand, { stdio: "inherit" });
|
|
1015
|
+
execSync3("git remote rename origin upstream", { cwd: projectDir, stdio: "inherit" });
|
|
1016
|
+
if (ghRef) {
|
|
1017
|
+
if (resetMain) {
|
|
1018
|
+
execSync3("git checkout -b main", { cwd: projectDir, stdio: "inherit" });
|
|
1019
|
+
resetBranchToRef(projectDir, ghRef);
|
|
1020
|
+
consola.success(`Reset main to ${ghRef} successfully.`);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
checkoutRef(projectDir, ghRef);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// src/cli/lib/commerce-hosting.ts
|
|
1028
|
+
import { input, select, Separator } from "@inquirer/prompts";
|
|
1029
|
+
import { colorize as colorize2 } from "consola/utils";
|
|
1030
|
+
import {
|
|
1031
|
+
existsSync as existsSync3,
|
|
1032
|
+
lstatSync,
|
|
1033
|
+
mkdirSync,
|
|
1034
|
+
readFileSync as readFileSync2,
|
|
1035
|
+
symlinkSync,
|
|
1036
|
+
unlinkSync,
|
|
1037
|
+
writeFileSync
|
|
1038
|
+
} from "fs";
|
|
1039
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
1040
|
+
import { z as z6 } from "zod";
|
|
1041
|
+
|
|
1042
|
+
// src/cli/lib/sort-package-json.ts
|
|
1043
|
+
var FIELD_ORDER = [
|
|
1044
|
+
"name",
|
|
1045
|
+
"description",
|
|
1046
|
+
"version",
|
|
1047
|
+
"private",
|
|
1048
|
+
"engines",
|
|
1049
|
+
"scripts",
|
|
1050
|
+
"dependencies",
|
|
1051
|
+
"devDependencies"
|
|
1052
|
+
];
|
|
1053
|
+
function sortPackageJsonFields(pkg) {
|
|
1054
|
+
const ordered = {};
|
|
1055
|
+
FIELD_ORDER.forEach((field) => {
|
|
1056
|
+
if (field in pkg) {
|
|
1057
|
+
ordered[field] = pkg[field];
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
Object.keys(pkg).forEach((key) => {
|
|
1061
|
+
if (!(key in ordered)) {
|
|
1062
|
+
ordered[key] = pkg[key];
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
return ordered;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// src/cli/lib/commerce-hosting.ts
|
|
1069
|
+
var OPENNEXT_CLOUDFLARE_VERSION = "1.17.3";
|
|
1070
|
+
var corePackageJsonSchema = z6.looseObject({
|
|
1071
|
+
dependencies: z6.record(z6.string(), z6.string()).optional()
|
|
1072
|
+
});
|
|
1073
|
+
var writeJson = (path, value) => {
|
|
1074
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
1075
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
1076
|
+
`);
|
|
1077
|
+
};
|
|
1078
|
+
var symlinkRootEnvToCore = (projectDir) => {
|
|
1079
|
+
const coreEnvPath = join6(projectDir, "core", ".env.local");
|
|
1080
|
+
if (lstatSync(coreEnvPath, { throwIfNoEntry: false })) return;
|
|
1081
|
+
try {
|
|
1082
|
+
symlinkSync("../.env.local", coreEnvPath);
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
1085
|
+
consola.warn(
|
|
1086
|
+
`Could not create symlink at core/.env.local: ${message}
|
|
1087
|
+
On Windows, enable Developer Mode or run as administrator to allow symlinks.
|
|
1088
|
+
You will need to keep .env.local and core/.env.local in sync manually.`
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
var convertProxyToMiddleware = (projectDir) => {
|
|
1093
|
+
const proxyPath = join6(projectDir, "core", "proxy.ts");
|
|
1094
|
+
const middlewarePath = join6(projectDir, "core", "middleware.ts");
|
|
1095
|
+
if (!existsSync3(proxyPath)) return;
|
|
1096
|
+
const contents = readFileSync2(proxyPath, "utf-8").replace("export const proxy", "export const middleware").replace("export const config = {", "export const config = {\n runtime: 'experimental-edge',");
|
|
1097
|
+
writeFileSync(middlewarePath, contents);
|
|
1098
|
+
unlinkSync(proxyPath);
|
|
1099
|
+
};
|
|
1100
|
+
var setupCommerceHosting = ({
|
|
1101
|
+
projectDir,
|
|
1102
|
+
projectUuid,
|
|
1103
|
+
storeHash,
|
|
1104
|
+
accessToken
|
|
1105
|
+
}) => {
|
|
1106
|
+
const corePackageJsonPath = join6(projectDir, "core", "package.json");
|
|
1107
|
+
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
|
|
1108
|
+
pkg.dependencies = {
|
|
1109
|
+
...pkg.dependencies,
|
|
1110
|
+
"@opennextjs/cloudflare": OPENNEXT_CLOUDFLARE_VERSION
|
|
1111
|
+
};
|
|
1112
|
+
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1113
|
+
const projectJson = {
|
|
1114
|
+
projectUuid,
|
|
1115
|
+
framework: "catalyst"
|
|
1116
|
+
};
|
|
1117
|
+
if (storeHash) projectJson.storeHash = storeHash;
|
|
1118
|
+
if (accessToken) projectJson.accessToken = accessToken;
|
|
1119
|
+
writeJson(join6(projectDir, "core", ".bigcommerce", "project.json"), projectJson);
|
|
1120
|
+
symlinkRootEnvToCore(projectDir);
|
|
1121
|
+
convertProxyToMiddleware(projectDir);
|
|
1122
|
+
};
|
|
1123
|
+
var NoLinkedProjectError = class extends Error {
|
|
1124
|
+
constructor() {
|
|
1125
|
+
super("No infrastructure project linked: user declined to create one.");
|
|
1126
|
+
this.name = "NoLinkedProjectError";
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
async function promptForNewProjectName(api) {
|
|
1130
|
+
const newProjectName = await consola.prompt("Enter a name for the new project:", {
|
|
1131
|
+
type: "text"
|
|
1132
|
+
});
|
|
1133
|
+
const data = await createProject(
|
|
1134
|
+
String(newProjectName),
|
|
1135
|
+
api.storeHash,
|
|
1136
|
+
api.accessToken,
|
|
1137
|
+
api.apiHost
|
|
1138
|
+
);
|
|
1139
|
+
consola.success(`Project "${data.name}" created successfully.`);
|
|
1140
|
+
return { uuid: data.uuid, name: data.name };
|
|
1141
|
+
}
|
|
1142
|
+
async function selectOrCreateInfrastructureProject(api, linkedProjectUuid) {
|
|
1143
|
+
consola.start("Fetching projects...");
|
|
1144
|
+
const existingProjects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
|
|
1145
|
+
consola.success("Projects fetched.");
|
|
1146
|
+
if (existingProjects.length === 0) {
|
|
1147
|
+
const shouldCreate = await consola.prompt(
|
|
1148
|
+
"There are not any hosting projects that you can link to yet. Would you like to create one?",
|
|
1149
|
+
{ type: "confirm", initial: true }
|
|
1150
|
+
);
|
|
1151
|
+
if (!shouldCreate) {
|
|
1152
|
+
throw new NoLinkedProjectError();
|
|
1153
|
+
}
|
|
1154
|
+
return promptForNewProjectName(api);
|
|
1155
|
+
}
|
|
1156
|
+
const promptOptions = [
|
|
1157
|
+
...existingProjects.map((p) => ({
|
|
1158
|
+
label: p.uuid === linkedProjectUuid ? `${p.name} ${colorize2("green", "[linked]")}` : p.name,
|
|
1159
|
+
value: p.uuid,
|
|
1160
|
+
hint: p.uuid
|
|
1161
|
+
})),
|
|
1162
|
+
{
|
|
1163
|
+
label: "Create a new project",
|
|
1164
|
+
value: "create",
|
|
1165
|
+
hint: "Create a new hosting project for this Catalyst storefront."
|
|
1166
|
+
}
|
|
1167
|
+
];
|
|
1168
|
+
const selected = await consola.prompt(
|
|
1169
|
+
"Select a project or create a new project (Press <enter> to select).",
|
|
1170
|
+
{ type: "select", options: promptOptions, cancel: "reject" }
|
|
1171
|
+
);
|
|
1172
|
+
if (selected === "create") {
|
|
1173
|
+
return promptForNewProjectName(api);
|
|
1174
|
+
}
|
|
1175
|
+
const matched = existingProjects.find((p) => p.uuid === selected);
|
|
1176
|
+
if (!matched) {
|
|
1177
|
+
throw new Error(`Selected project ${String(selected)} not found in fetched list.`);
|
|
1178
|
+
}
|
|
1179
|
+
return matched;
|
|
1180
|
+
}
|
|
1181
|
+
async function promptForCommerceHostingProject(api, defaultName, autoUseDefaultName, useExistingOnCollision) {
|
|
1182
|
+
const existingProjects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
|
|
1183
|
+
const takenNames = existingProjects.map((project2) => project2.name);
|
|
1184
|
+
if (autoUseDefaultName) {
|
|
1185
|
+
return autoCreateCommerceHostingProject(
|
|
1186
|
+
api,
|
|
1187
|
+
defaultName,
|
|
1188
|
+
existingProjects,
|
|
1189
|
+
useExistingOnCollision
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
const conflict = existingProjects.find(
|
|
1193
|
+
(project2) => project2.name.toLowerCase() === defaultName.toLowerCase()
|
|
1194
|
+
);
|
|
1195
|
+
if (!conflict) {
|
|
1196
|
+
return autoCreateCommerceHostingProject(
|
|
1197
|
+
api,
|
|
1198
|
+
defaultName,
|
|
1199
|
+
existingProjects,
|
|
1200
|
+
useExistingOnCollision
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
const hasOtherProjects = existingProjects.length > 1;
|
|
1204
|
+
const choices = [
|
|
1205
|
+
{ name: `Use "${conflict.name}"`, value: "use-named" }
|
|
1206
|
+
];
|
|
1207
|
+
if (hasOtherProjects) {
|
|
1208
|
+
choices.push({ name: "Select from my projects", value: "select-from-list" });
|
|
1209
|
+
}
|
|
1210
|
+
choices.push({ name: "Create a new project", value: "create" });
|
|
1211
|
+
const action = await select({
|
|
1212
|
+
message: hasOtherProjects ? `It looks like you already have an existing Commerce Hosting project named "${conflict.name}". Would you like to use it, select from your projects, or create a new one?` : `It looks like you already have an existing Commerce Hosting project named "${conflict.name}". Would you like to use it, or create a new one?`,
|
|
1213
|
+
choices
|
|
1214
|
+
});
|
|
1215
|
+
if (action === "use-named") {
|
|
1216
|
+
consola.success(`Using existing Commerce Hosting project "${conflict.name}"`);
|
|
1217
|
+
return conflict;
|
|
1218
|
+
}
|
|
1219
|
+
if (action === "create") {
|
|
1220
|
+
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1221
|
+
}
|
|
1222
|
+
const selected = await select({
|
|
1223
|
+
message: "Which Commerce Hosting project would you like to use?",
|
|
1224
|
+
choices: [
|
|
1225
|
+
...existingProjects.map((project2) => ({
|
|
1226
|
+
name: project2.name,
|
|
1227
|
+
value: project2,
|
|
1228
|
+
description: project2.uuid
|
|
1229
|
+
})),
|
|
1230
|
+
new Separator(),
|
|
1231
|
+
{ name: "Create a new project", value: "create-new" }
|
|
1232
|
+
]
|
|
1233
|
+
});
|
|
1234
|
+
if (selected === "create-new") {
|
|
1235
|
+
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1236
|
+
}
|
|
1237
|
+
consola.success(`Using existing Commerce Hosting project "${selected.name}"`);
|
|
1238
|
+
return selected;
|
|
1239
|
+
}
|
|
1240
|
+
async function promptAndCreateCommerceHostingProject(api, takenNames, defaultName) {
|
|
1241
|
+
const projectName = await input({
|
|
1242
|
+
message: "What would you like to name your Commerce Hosting project?",
|
|
1243
|
+
default: defaultName,
|
|
1244
|
+
validate: (value) => {
|
|
1245
|
+
const trimmed = value.trim();
|
|
1246
|
+
if (!trimmed) return "Project name is required";
|
|
1247
|
+
const conflict = takenNames.find((taken) => taken.toLowerCase() === trimmed.toLowerCase());
|
|
1248
|
+
if (conflict) {
|
|
1249
|
+
return `A Commerce Hosting project named "${conflict}" already exists`;
|
|
1250
|
+
}
|
|
1251
|
+
return true;
|
|
1252
|
+
},
|
|
1253
|
+
theme: {
|
|
1254
|
+
style: {
|
|
1255
|
+
help: () => colorize2(
|
|
1256
|
+
"dim",
|
|
1257
|
+
"(The project that hosts your storefront on Commerce \u2014 often matches your folder name.)"
|
|
1258
|
+
)
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
try {
|
|
1263
|
+
const created = await createProject(
|
|
1264
|
+
projectName.trim(),
|
|
1265
|
+
api.storeHash,
|
|
1266
|
+
api.accessToken,
|
|
1267
|
+
api.apiHost
|
|
1268
|
+
);
|
|
1269
|
+
consola.success(`Commerce Hosting project "${created.name}" created successfully`);
|
|
1270
|
+
return { uuid: created.uuid, name: created.name };
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
if (error instanceof InfrastructureProjectValidationError) {
|
|
1273
|
+
consola.error(error.message);
|
|
1274
|
+
return promptAndCreateCommerceHostingProject(api, takenNames, defaultName);
|
|
1275
|
+
}
|
|
1276
|
+
throw error;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
async function resolveCollisionChoice(existingName, useExistingOnCollision) {
|
|
1280
|
+
if (useExistingOnCollision === true) return true;
|
|
1281
|
+
if (!process.stdin.isTTY) return false;
|
|
1282
|
+
return select({
|
|
1283
|
+
message: `A Commerce Hosting project named "${existingName}" already exists. Use the existing project?`,
|
|
1284
|
+
choices: [
|
|
1285
|
+
{ name: "Yes", value: true },
|
|
1286
|
+
{ name: "No", value: false }
|
|
1287
|
+
]
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
async function autoCreateCommerceHostingProject(api, name, existingProjects, useExistingOnCollision) {
|
|
1291
|
+
const existing = existingProjects.find(
|
|
1292
|
+
(project2) => project2.name.toLowerCase() === name.toLowerCase()
|
|
1293
|
+
);
|
|
1294
|
+
if (existing) {
|
|
1295
|
+
const shouldUseExisting = await resolveCollisionChoice(existing.name, useExistingOnCollision);
|
|
1296
|
+
if (shouldUseExisting) {
|
|
1297
|
+
consola.success(`Using existing Commerce Hosting project "${existing.name}"`);
|
|
1298
|
+
return existing;
|
|
1299
|
+
}
|
|
1300
|
+
consola.error(
|
|
1301
|
+
"Not reusing the existing project. Re-run with a different --project-name, or pass --use-existing to reuse it."
|
|
1302
|
+
);
|
|
1303
|
+
process.exit(1);
|
|
1304
|
+
}
|
|
1305
|
+
try {
|
|
1306
|
+
const created = await createProject(name, api.storeHash, api.accessToken, api.apiHost);
|
|
1307
|
+
consola.success(`Commerce Hosting project "${created.name}" created successfully`);
|
|
1308
|
+
return { uuid: created.uuid, name: created.name };
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
if (error instanceof InfrastructureProjectValidationError) {
|
|
1311
|
+
consola.error(
|
|
1312
|
+
`Failed to create Commerce Hosting project "${name}": ${error.message}
|
|
1313
|
+
Re-run with a different --project-name.`
|
|
1314
|
+
);
|
|
1315
|
+
process.exit(1);
|
|
1316
|
+
}
|
|
1317
|
+
throw error;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// src/cli/lib/install-dependencies.ts
|
|
1322
|
+
import { installDependencies as installDeps } from "nypm";
|
|
1323
|
+
import yoctoSpinner3 from "yocto-spinner";
|
|
1324
|
+
var installDependencies = async (projectDir) => {
|
|
1325
|
+
const spinner = yoctoSpinner3().start("Installing dependencies. This could take a minute...");
|
|
1326
|
+
try {
|
|
1327
|
+
await installDeps({ cwd: projectDir, silent: true, packageManager: "pnpm" });
|
|
1328
|
+
spinner.success("Dependencies installed successfully.");
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
1331
|
+
spinner.error(`Failed to install dependencies: ${message}`);
|
|
1332
|
+
throw error;
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
// src/cli/lib/localization.ts
|
|
1337
|
+
import { z as z7 } from "zod";
|
|
1338
|
+
var allowedLocales = [
|
|
1339
|
+
"en",
|
|
1340
|
+
"da",
|
|
1341
|
+
"es-AR",
|
|
1342
|
+
"es-CL",
|
|
1343
|
+
"es-CO",
|
|
1344
|
+
"es-MX",
|
|
1345
|
+
"es-PE",
|
|
1346
|
+
"es-419",
|
|
1347
|
+
"es",
|
|
1348
|
+
"it",
|
|
1349
|
+
"nl",
|
|
1350
|
+
"pl",
|
|
1351
|
+
"pt",
|
|
1352
|
+
"de",
|
|
1353
|
+
"fr",
|
|
1354
|
+
"ja",
|
|
1355
|
+
"no",
|
|
1356
|
+
"pt-BR",
|
|
1357
|
+
"sv"
|
|
1358
|
+
];
|
|
1359
|
+
var AvailableLocalesSuccessSchema = z7.object({
|
|
1360
|
+
data: z7.array(
|
|
1361
|
+
z7.object({
|
|
1362
|
+
id: z7.string(),
|
|
1363
|
+
name: z7.string(),
|
|
1364
|
+
fallback: z7.string().nullable(),
|
|
1365
|
+
is_supported: z7.boolean()
|
|
1366
|
+
})
|
|
1367
|
+
)
|
|
1368
|
+
});
|
|
1369
|
+
var getAvailableLocales = async (storeHash, accessToken, apiHost) => {
|
|
1370
|
+
const response = await fetch(
|
|
1371
|
+
`https://${apiHost}/stores/${storeHash}/v3/settings/store/available-locales`,
|
|
1372
|
+
{
|
|
1373
|
+
method: "GET",
|
|
1374
|
+
headers: {
|
|
1375
|
+
"X-Auth-Token": accessToken,
|
|
1376
|
+
"X-Correlation-Id": getTelemetry().correlationId,
|
|
1377
|
+
Accept: "application/json"
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
);
|
|
1381
|
+
if (!response.ok) {
|
|
1382
|
+
throw new Error(
|
|
1383
|
+
`GET /v3/settings/store/available-locales failed: ${response.status} ${response.statusText}`
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
return AvailableLocalesSuccessSchema.parse(await response.json()).data.filter(({ id }) => allowedLocales.includes(id)).map(({ name, id }) => ({ name: `${name} (${id})`, value: id }));
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
// src/cli/lib/login.ts
|
|
1390
|
+
import { colorize as colorize3 } from "consola/utils";
|
|
1391
|
+
import open2 from "open";
|
|
1392
|
+
import yoctoSpinner4 from "yocto-spinner";
|
|
1393
|
+
async function login2(loginUrl) {
|
|
1394
|
+
const deviceCode = await requestDeviceCode(loginUrl);
|
|
1395
|
+
consola.info(
|
|
1396
|
+
`${colorize3("yellow", "Your one-time code:")} ${colorize3("bold", deviceCode.user_code)}`
|
|
1397
|
+
);
|
|
1398
|
+
try {
|
|
1399
|
+
await open2(deviceCode.verification_uri);
|
|
1400
|
+
consola.info(`Opened ${deviceCode.verification_uri} in your browser.`);
|
|
1401
|
+
} catch {
|
|
1402
|
+
consola.info(`Open ${deviceCode.verification_uri} in your browser and enter the code above.`);
|
|
1403
|
+
}
|
|
1404
|
+
const spinner = yoctoSpinner4().start("Waiting for authentication...");
|
|
1405
|
+
const credentials = await waitForDeviceToken(
|
|
1406
|
+
loginUrl,
|
|
1407
|
+
deviceCode.device_code,
|
|
1408
|
+
deviceCode.interval
|
|
1409
|
+
);
|
|
1410
|
+
spinner.success("Authentication complete.");
|
|
1411
|
+
return {
|
|
1412
|
+
storeHash: credentials.store_hash,
|
|
1413
|
+
accessToken: credentials.access_token
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
// src/cli/lib/setup-core-project.ts
|
|
1418
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1419
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
1420
|
+
import { z as z8 } from "zod";
|
|
1421
|
+
var corePackageJsonSchema2 = z8.looseObject({
|
|
1422
|
+
scripts: z8.record(z8.string(), z8.string()).optional(),
|
|
1423
|
+
dependencies: z8.record(z8.string(), z8.string()).optional()
|
|
1424
|
+
});
|
|
1425
|
+
var writeJson2 = (path, value) => {
|
|
1426
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
1427
|
+
writeFileSync2(path, `${JSON.stringify(value, null, 2)}
|
|
1428
|
+
`);
|
|
1429
|
+
};
|
|
1430
|
+
var setupCoreProject = (projectDir) => {
|
|
1431
|
+
const corePackageJsonPath = join7(projectDir, "core", "package.json");
|
|
1432
|
+
const pkg = corePackageJsonSchema2.parse(JSON.parse(readFileSync3(corePackageJsonPath, "utf-8")));
|
|
1433
|
+
pkg.scripts = {
|
|
1434
|
+
...pkg.scripts,
|
|
1435
|
+
build: "npm run generate && catalyst build",
|
|
1436
|
+
start: "catalyst start",
|
|
1437
|
+
deploy: "npm run generate && catalyst deploy"
|
|
1438
|
+
};
|
|
1439
|
+
pkg.dependencies = {
|
|
1440
|
+
...pkg.dependencies,
|
|
1441
|
+
"@bigcommerce/catalyst": package_default.version
|
|
1442
|
+
};
|
|
1443
|
+
writeJson2(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
// src/cli/lib/shared-options.ts
|
|
1447
|
+
import { Option as Option3 } from "commander";
|
|
1448
|
+
var storeHashOption = () => new Option3(
|
|
1449
|
+
"--store-hash <hash>",
|
|
1450
|
+
"BigCommerce store hash. Can be found in the URL of your store Control Panel. Read from .bigcommerce/project.json or .env when not provided."
|
|
1451
|
+
).env("CATALYST_STORE_HASH");
|
|
1452
|
+
var accessTokenOption = () => new Option3(
|
|
1453
|
+
"--access-token <token>",
|
|
1454
|
+
"BigCommerce access token. Can be found after creating a store-level API account. Read from .bigcommerce/project.json or .env when not provided."
|
|
1455
|
+
).env("CATALYST_ACCESS_TOKEN");
|
|
1456
|
+
var apiHostOption = () => new Option3("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp();
|
|
1457
|
+
var projectUuidOption = () => new Option3(
|
|
1458
|
+
"--project-uuid <uuid>",
|
|
1459
|
+
"BigCommerce infrastructure project UUID. Read from .bigcommerce/project.json or .env when not provided."
|
|
1460
|
+
).env("CATALYST_PROJECT_UUID");
|
|
1461
|
+
var resolveProjectUuid = (options) => {
|
|
1462
|
+
const config2 = getProjectConfig();
|
|
1463
|
+
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
1464
|
+
if (!projectUuid) {
|
|
1465
|
+
throw new Error(
|
|
1466
|
+
"Project UUID is required. Please run either `catalyst project link` or `catalyst project create` or this command again with --project-uuid <uuid>."
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
return projectUuid;
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
// src/cli/lib/write-env.ts
|
|
1473
|
+
import { outputFileSync } from "fs-extra/esm";
|
|
1474
|
+
import { join as join8 } from "path";
|
|
1475
|
+
var writeEnv = (projectDir, envVars) => {
|
|
1476
|
+
outputFileSync(
|
|
1477
|
+
join8(projectDir, "core", ".env.local"),
|
|
1478
|
+
`${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
1479
|
+
`
|
|
1480
|
+
);
|
|
1481
|
+
};
|
|
1482
|
+
|
|
1483
|
+
// src/cli/commands/create.ts
|
|
1484
|
+
function getPlatformCheckCommand(command) {
|
|
1485
|
+
const isWindows = process.platform === "win32";
|
|
1486
|
+
return isWindows ? `where.exe ${command}` : `which ${command}`;
|
|
1487
|
+
}
|
|
1488
|
+
function parseChannelId(value) {
|
|
1489
|
+
const parsed = parseInt(value, 10);
|
|
1490
|
+
if (Number.isNaN(parsed)) {
|
|
1491
|
+
throw new InvalidArgumentError(`"${value}" is not a valid channel ID (expected a number).`);
|
|
1492
|
+
}
|
|
1493
|
+
return parsed;
|
|
1494
|
+
}
|
|
1495
|
+
function parseEnvFlag(value, previous = {}) {
|
|
1496
|
+
const eqIdx = value.indexOf("=");
|
|
1497
|
+
if (eqIdx <= 0) {
|
|
1498
|
+
throw new InvalidArgumentError(`Expected KEY=VALUE, got "${value}".`);
|
|
1499
|
+
}
|
|
1500
|
+
const key = value.substring(0, eqIdx);
|
|
1501
|
+
const val = value.substring(eqIdx + 1);
|
|
1502
|
+
if (!val) {
|
|
1503
|
+
throw new InvalidArgumentError(`Expected KEY=VALUE with non-empty value, got "${value}".`);
|
|
1504
|
+
}
|
|
1505
|
+
return { ...previous, [key]: val };
|
|
1506
|
+
}
|
|
1507
|
+
async function handleChannelCreation(storeHash, accessToken, apiHost, cliApiOrigin) {
|
|
1508
|
+
const newChannelName = await input2({
|
|
1509
|
+
message: "What would you like to name your new channel?"
|
|
1510
|
+
});
|
|
1511
|
+
const availableLocales = await getAvailableLocales(storeHash, accessToken, apiHost);
|
|
1512
|
+
const storefrontLocale = await select2({
|
|
1513
|
+
message: "Which default language would you like to set for your channel?",
|
|
1514
|
+
default: "en",
|
|
1515
|
+
choices: availableLocales,
|
|
1516
|
+
theme: {
|
|
1517
|
+
style: {
|
|
1518
|
+
help: () => colorize4("dim", "(Select locale from the list or start typing the name)")
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
const shouldAddAdditionalLocales = await select2({
|
|
1523
|
+
message: "Would you like to add additional languages?",
|
|
1524
|
+
choices: [
|
|
1525
|
+
{ name: "Yes", value: true },
|
|
1526
|
+
{ name: "No", value: false }
|
|
1527
|
+
]
|
|
1528
|
+
});
|
|
1529
|
+
let additionalLocales = [];
|
|
1530
|
+
if (shouldAddAdditionalLocales) {
|
|
1531
|
+
const localeOptions = availableLocales.filter(({ value }) => value !== storefrontLocale).map(({ name, value }) => ({ label: name, value, hint: value }));
|
|
1532
|
+
const pickLocales = async () => {
|
|
1533
|
+
const selected = await consola.prompt(
|
|
1534
|
+
"Which additional languages would you like to add to your channel?",
|
|
1535
|
+
{ type: "multiselect", options: localeOptions, cancel: "reject" }
|
|
1536
|
+
);
|
|
1537
|
+
if (selected.length > 4) {
|
|
1538
|
+
consola.warn("You can only select up to 4 additional languages. Please try again.");
|
|
1539
|
+
return pickLocales();
|
|
1540
|
+
}
|
|
1541
|
+
return selected;
|
|
1542
|
+
};
|
|
1543
|
+
additionalLocales = await pickLocales();
|
|
1544
|
+
}
|
|
1545
|
+
const shouldInstallSampleData = await select2({
|
|
1546
|
+
message: "Would you like to install sample data?",
|
|
1547
|
+
choices: [
|
|
1548
|
+
{ name: "Yes", value: true },
|
|
1549
|
+
{ name: "No", value: false }
|
|
1550
|
+
]
|
|
1551
|
+
});
|
|
1552
|
+
return createChannel(
|
|
1553
|
+
newChannelName,
|
|
1554
|
+
storefrontLocale,
|
|
1555
|
+
additionalLocales,
|
|
1556
|
+
shouldInstallSampleData,
|
|
1557
|
+
storeHash,
|
|
1558
|
+
accessToken,
|
|
1559
|
+
cliApiOrigin
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
async function handleChannelSelection(storeHash, accessToken, apiHost) {
|
|
1563
|
+
const channelSortOrder = ["catalyst", "next", "bigcommerce"];
|
|
1564
|
+
const channels = await fetchAvailableChannels(storeHash, accessToken, apiHost);
|
|
1565
|
+
const existingChannel = await select2({
|
|
1566
|
+
message: "Which channel would you like to use?",
|
|
1567
|
+
choices: channels.sort((a, b) => {
|
|
1568
|
+
const aIndex = channelSortOrder.indexOf(a.platform);
|
|
1569
|
+
const bIndex = channelSortOrder.indexOf(b.platform);
|
|
1570
|
+
if (aIndex === -1 && bIndex === -1) {
|
|
1571
|
+
return 0;
|
|
1572
|
+
}
|
|
1573
|
+
if (aIndex === -1) return 1;
|
|
1574
|
+
if (bIndex === -1) return -1;
|
|
1575
|
+
return aIndex - bIndex;
|
|
1576
|
+
}).map((ch) => ({
|
|
1577
|
+
name: ch.name,
|
|
1578
|
+
value: ch,
|
|
1579
|
+
description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
|
|
1580
|
+
}))
|
|
613
1581
|
});
|
|
1582
|
+
return existingChannel.id;
|
|
614
1583
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1584
|
+
async function setupProject(options) {
|
|
1585
|
+
let { projectName, projectDir } = options;
|
|
1586
|
+
if (!pathExistsSync(projectDir)) {
|
|
1587
|
+
consola.error(`--project-dir ${projectDir} is not a valid path`);
|
|
1588
|
+
process.exit(1);
|
|
1589
|
+
}
|
|
1590
|
+
if (projectName) {
|
|
1591
|
+
projectName = kebabCase(projectName);
|
|
1592
|
+
projectDir = join9(options.projectDir, projectName);
|
|
1593
|
+
if (pathExistsSync(projectDir)) {
|
|
1594
|
+
consola.error(`${projectDir} already exists`);
|
|
1595
|
+
process.exit(1);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (!projectName) {
|
|
1599
|
+
const validateProjectName = (i) => {
|
|
1600
|
+
const formatted = kebabCase(i);
|
|
1601
|
+
if (!formatted) return "Project name is required";
|
|
1602
|
+
const targetDir = join9(options.projectDir, formatted);
|
|
1603
|
+
if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
|
|
1604
|
+
projectName = formatted;
|
|
1605
|
+
projectDir = targetDir;
|
|
1606
|
+
return true;
|
|
1607
|
+
};
|
|
1608
|
+
await input2({
|
|
1609
|
+
message: "What do you want to name your project directory?",
|
|
1610
|
+
default: "my-catalyst-app",
|
|
1611
|
+
validate: validateProjectName
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
if (!projectName) throw new Error("Something went wrong, projectName is not defined");
|
|
1615
|
+
if (!projectDir) throw new Error("Something went wrong, projectDir is not defined");
|
|
1616
|
+
return { projectName, projectDir };
|
|
1617
|
+
}
|
|
1618
|
+
function checkRequiredTools() {
|
|
1619
|
+
try {
|
|
1620
|
+
execSync4(getPlatformCheckCommand("git"), { stdio: "ignore" });
|
|
1621
|
+
} catch {
|
|
1622
|
+
consola.error("git is required to create a Catalyst project");
|
|
1623
|
+
process.exit(1);
|
|
1624
|
+
}
|
|
1625
|
+
try {
|
|
1626
|
+
execSync4(getPlatformCheckCommand("pnpm"), { stdio: "ignore" });
|
|
1627
|
+
} catch {
|
|
1628
|
+
consola.error(
|
|
1629
|
+
"pnpm is required to create a Catalyst project. Enable it by running `corepack enable pnpm`."
|
|
1630
|
+
);
|
|
1631
|
+
process.exit(1);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
var create = new Command3("create").configureHelp({ showGlobalOptions: true }).description("Scaffold and connect a Catalyst storefront to your BigCommerce store.").addHelpText(
|
|
1635
|
+
"after",
|
|
1636
|
+
`
|
|
1637
|
+
Examples:
|
|
1638
|
+
# Interactive scaffold (default \u2014 self-hosted, no hosting prompt)
|
|
1639
|
+
$ catalyst create
|
|
1640
|
+
|
|
1641
|
+
# Non-interactive: skip the project-name prompt
|
|
1642
|
+
$ catalyst create --project-name my-store
|
|
1643
|
+
|
|
1644
|
+
# Eagerly set up Commerce Hosting at create time
|
|
1645
|
+
$ catalyst create --project-name my-store --hosting commerce`
|
|
1646
|
+
).option("--project-name <name>", "Name of your Catalyst project").option("--project-dir <dir>", "Directory in which to create your project", process.cwd()).addOption(storeHashOption()).addOption(accessTokenOption()).option("--channel-id <id>", "BigCommerce channel ID", parseChannelId).option("--storefront-token <token>", "BigCommerce storefront token").option(
|
|
1647
|
+
"--gh-ref <ref>",
|
|
1648
|
+
"Clone a specific ref from the source repository",
|
|
1649
|
+
"@bigcommerce/catalyst-core@latest"
|
|
1650
|
+
).option("--reset-main", "Reset the main branch to the gh-ref").option("--repository <repository>", "GitHub repository to clone from", "bigcommerce/catalyst").option(
|
|
1651
|
+
"--env <vars...>",
|
|
1652
|
+
"Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable).",
|
|
1653
|
+
parseEnvFlag
|
|
1654
|
+
).addOption(
|
|
1655
|
+
new Option4(
|
|
1656
|
+
"--hosting <mode>",
|
|
1657
|
+
'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.'
|
|
1658
|
+
).choices(["self-hosted", "commerce"])
|
|
1659
|
+
).option(
|
|
1660
|
+
"--use-existing",
|
|
1661
|
+
"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."
|
|
1662
|
+
).addOption(
|
|
1663
|
+
new Option4("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
|
|
1664
|
+
).addOption(
|
|
1665
|
+
new Option4("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
|
|
1666
|
+
).addOption(
|
|
1667
|
+
new Option4("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
620
1668
|
).action(async (options) => {
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
1669
|
+
const { ghRef, repository } = options;
|
|
1670
|
+
if (options.useExisting && options.hosting !== "commerce") {
|
|
1671
|
+
consola.warn("--use-existing has no effect without --hosting commerce. Ignoring.");
|
|
1672
|
+
}
|
|
1673
|
+
checkRequiredTools();
|
|
1674
|
+
const { projectName, projectDir } = await setupProject({
|
|
1675
|
+
projectName: options.projectName,
|
|
1676
|
+
projectDir: options.projectDir
|
|
1677
|
+
});
|
|
1678
|
+
let storeHash = options.storeHash;
|
|
1679
|
+
let accessToken = options.accessToken;
|
|
1680
|
+
let channelId = options.channelId;
|
|
1681
|
+
let storefrontToken = options.storefrontToken;
|
|
1682
|
+
let envVars = {};
|
|
1683
|
+
if (!storeHash || !accessToken) {
|
|
1684
|
+
const credentials = await login2(options.loginUrl);
|
|
1685
|
+
storeHash = credentials.storeHash;
|
|
1686
|
+
accessToken = credentials.accessToken;
|
|
1687
|
+
}
|
|
1688
|
+
const useCommerceHosting = options.hosting === "commerce";
|
|
1689
|
+
await getTelemetry().identify(storeHash);
|
|
1690
|
+
if (storeHash && channelId && storefrontToken) {
|
|
1691
|
+
envVars.BIGCOMMERCE_STORE_HASH = storeHash;
|
|
1692
|
+
envVars.BIGCOMMERCE_CHANNEL_ID = channelId.toString();
|
|
1693
|
+
envVars.BIGCOMMERCE_STOREFRONT_TOKEN = storefrontToken;
|
|
1694
|
+
}
|
|
1695
|
+
if (storeHash && accessToken && (!channelId || !storefrontToken)) {
|
|
1696
|
+
const apiHost = `api.${options.bigcommerceHostname}`;
|
|
1697
|
+
const cliApiOrigin = options.cliApiOrigin;
|
|
1698
|
+
if (channelId && !storefrontToken) {
|
|
1699
|
+
const initData = await getChannelInit(channelId, storeHash, accessToken, cliApiOrigin);
|
|
1700
|
+
envVars = { ...initData.envVars };
|
|
1701
|
+
storefrontToken = initData.storefrontToken;
|
|
1702
|
+
} else if (!channelId) {
|
|
1703
|
+
const eligibility = await checkChannelEligibility(storeHash, accessToken, cliApiOrigin);
|
|
1704
|
+
if (!eligibility.eligible) {
|
|
1705
|
+
consola.warn(eligibility.message);
|
|
1706
|
+
}
|
|
1707
|
+
let shouldCreateChannel;
|
|
1708
|
+
if (eligibility.eligible) {
|
|
1709
|
+
shouldCreateChannel = await select2({
|
|
1710
|
+
message: "Would you like to create a new channel?",
|
|
1711
|
+
choices: [
|
|
1712
|
+
{ name: "Yes", value: true },
|
|
1713
|
+
{ name: "No", value: false }
|
|
1714
|
+
]
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
if (shouldCreateChannel) {
|
|
1718
|
+
const channelData = await handleChannelCreation(
|
|
1719
|
+
storeHash,
|
|
1720
|
+
accessToken,
|
|
1721
|
+
apiHost,
|
|
1722
|
+
cliApiOrigin
|
|
1723
|
+
);
|
|
1724
|
+
channelId = channelData.channelId;
|
|
1725
|
+
storefrontToken = channelData.storefrontToken;
|
|
1726
|
+
envVars = { ...channelData.envVars };
|
|
1727
|
+
consola.success("Channel created successfully.");
|
|
1728
|
+
consola.warn(
|
|
1729
|
+
"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."
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
if (!shouldCreateChannel) {
|
|
1733
|
+
channelId = await handleChannelSelection(storeHash, accessToken, apiHost);
|
|
1734
|
+
const initData = await getChannelInit(channelId, storeHash, accessToken, cliApiOrigin);
|
|
1735
|
+
envVars = { ...initData.envVars };
|
|
1736
|
+
storefrontToken = initData.storefrontToken;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
if (options.env) {
|
|
1741
|
+
Object.assign(envVars, options.env);
|
|
1742
|
+
}
|
|
1743
|
+
if (options.storeHash) envVars.BIGCOMMERCE_STORE_HASH = options.storeHash;
|
|
1744
|
+
if (options.channelId) envVars.BIGCOMMERCE_CHANNEL_ID = options.channelId.toString();
|
|
1745
|
+
if (options.storefrontToken) envVars.BIGCOMMERCE_STOREFRONT_TOKEN = options.storefrontToken;
|
|
1746
|
+
if (useCommerceHosting && accessToken) {
|
|
1747
|
+
envVars.BIGCOMMERCE_ACCESS_TOKEN = accessToken;
|
|
1748
|
+
}
|
|
1749
|
+
if (accessToken) envVars.CATALYST_ACCESS_TOKEN = accessToken;
|
|
1750
|
+
let commerceHostingProject;
|
|
1751
|
+
if (useCommerceHosting && storeHash && accessToken) {
|
|
1752
|
+
const apiHost = `api.${options.bigcommerceHostname}`;
|
|
1753
|
+
const hasAccess = await hasProjectsAccess(storeHash, accessToken, apiHost);
|
|
1754
|
+
if (!hasAccess) {
|
|
1755
|
+
consola.error(
|
|
1756
|
+
"This store does not have access to the Infrastructure Projects API. Contact support@bigcommerce.com to enable it."
|
|
1757
|
+
);
|
|
1758
|
+
process.exit(1);
|
|
1759
|
+
}
|
|
1760
|
+
commerceHostingProject = await promptForCommerceHostingProject(
|
|
1761
|
+
{ storeHash, accessToken, apiHost },
|
|
1762
|
+
projectName,
|
|
1763
|
+
!!options.projectName,
|
|
1764
|
+
options.useExisting
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
consola.info(`Creating '${projectName}' at '${projectDir}'`);
|
|
1768
|
+
try {
|
|
1769
|
+
cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
|
|
1770
|
+
setupCoreProject(projectDir);
|
|
1771
|
+
if (useCommerceHosting && commerceHostingProject && storeHash && accessToken) {
|
|
1772
|
+
setupCommerceHosting({
|
|
1773
|
+
projectDir,
|
|
1774
|
+
projectUuid: commerceHostingProject.uuid,
|
|
1775
|
+
storeHash,
|
|
1776
|
+
accessToken
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
writeEnv(projectDir, envVars);
|
|
1780
|
+
await installDependencies(projectDir);
|
|
1781
|
+
buildWorkspacePackages(projectDir);
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
if (pathExistsSync(projectDir)) {
|
|
1784
|
+
consola.warn(
|
|
1785
|
+
`Setup failed before completion. '${projectDir}' may be in a partial state \u2014 review and delete it before re-running 'catalyst create'.`
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
throw error;
|
|
1789
|
+
}
|
|
1790
|
+
consola.success(`Created '${projectName}' at '${projectDir}'`);
|
|
1791
|
+
consola.info("Next steps:");
|
|
1792
|
+
consola.info(colorize4("yellow", ` cd ${projectName}/core && pnpm run dev`));
|
|
1793
|
+
if (useCommerceHosting) {
|
|
1794
|
+
consola.info(
|
|
1795
|
+
colorize4(
|
|
1796
|
+
"yellow",
|
|
1797
|
+
` Run 'cd ${projectName}/core && pnpm run deploy' when ready to deploy to Commerce Hosting.`
|
|
1798
|
+
)
|
|
626
1799
|
);
|
|
627
1800
|
}
|
|
628
|
-
await buildCatalystProject(projectUuid);
|
|
629
1801
|
});
|
|
630
1802
|
|
|
631
1803
|
// src/cli/commands/deploy.ts
|
|
632
1804
|
import AdmZip from "adm-zip";
|
|
633
|
-
import { Command as
|
|
1805
|
+
import { Command as Command4, Option as Option5 } from "commander";
|
|
1806
|
+
import { colorize as colorize5 } from "consola/utils";
|
|
634
1807
|
import { access, readdir, readFile } from "fs/promises";
|
|
635
|
-
import { join as
|
|
636
|
-
import
|
|
637
|
-
import { z as
|
|
1808
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
1809
|
+
import yoctoSpinner5 from "yocto-spinner";
|
|
1810
|
+
import { z as z9 } from "zod";
|
|
638
1811
|
|
|
639
1812
|
// src/cli/lib/deployment-errors.ts
|
|
640
1813
|
var ERROR_MESSAGES = {
|
|
@@ -668,7 +1841,7 @@ function resolveCredentials(options, config2) {
|
|
|
668
1841
|
}
|
|
669
1842
|
|
|
670
1843
|
// src/cli/commands/deploy.ts
|
|
671
|
-
var stepsEnum =
|
|
1844
|
+
var stepsEnum = z9.enum([
|
|
672
1845
|
"initializing",
|
|
673
1846
|
"downloading",
|
|
674
1847
|
"unzipping",
|
|
@@ -686,41 +1859,41 @@ var STEPS = {
|
|
|
686
1859
|
finalizing: "Finalizing...",
|
|
687
1860
|
complete: "Complete"
|
|
688
1861
|
};
|
|
689
|
-
var UploadSignatureSchema =
|
|
690
|
-
data:
|
|
691
|
-
upload_url:
|
|
692
|
-
upload_uuid:
|
|
1862
|
+
var UploadSignatureSchema = z9.object({
|
|
1863
|
+
data: z9.object({
|
|
1864
|
+
upload_url: z9.url(),
|
|
1865
|
+
upload_uuid: z9.string()
|
|
693
1866
|
})
|
|
694
1867
|
});
|
|
695
|
-
var CreateDeploymentSchema =
|
|
696
|
-
data:
|
|
697
|
-
deployment_uuid:
|
|
1868
|
+
var CreateDeploymentSchema = z9.object({
|
|
1869
|
+
data: z9.object({
|
|
1870
|
+
deployment_uuid: z9.uuid()
|
|
698
1871
|
})
|
|
699
1872
|
});
|
|
700
|
-
var ProjectSchema =
|
|
701
|
-
data:
|
|
702
|
-
|
|
703
|
-
uuid:
|
|
704
|
-
name:
|
|
1873
|
+
var ProjectSchema = z9.object({
|
|
1874
|
+
data: z9.array(
|
|
1875
|
+
z9.object({
|
|
1876
|
+
uuid: z9.uuid(),
|
|
1877
|
+
name: z9.string()
|
|
705
1878
|
})
|
|
706
1879
|
)
|
|
707
1880
|
});
|
|
708
|
-
var DeploymentStatusSchema =
|
|
709
|
-
deployment_uuid:
|
|
710
|
-
deployment_status:
|
|
711
|
-
event:
|
|
1881
|
+
var DeploymentStatusSchema = z9.object({
|
|
1882
|
+
deployment_uuid: z9.uuid(),
|
|
1883
|
+
deployment_status: z9.enum(["queued", "in_progress", "failed", "completed"]),
|
|
1884
|
+
event: z9.object({
|
|
712
1885
|
step: stepsEnum,
|
|
713
|
-
progress:
|
|
1886
|
+
progress: z9.number()
|
|
714
1887
|
}).nullable(),
|
|
715
|
-
deployment_url:
|
|
716
|
-
error:
|
|
717
|
-
code:
|
|
1888
|
+
deployment_url: z9.string().nullable(),
|
|
1889
|
+
error: z9.object({
|
|
1890
|
+
code: z9.number()
|
|
718
1891
|
}).optional()
|
|
719
1892
|
});
|
|
720
1893
|
var generateBundleZip = async () => {
|
|
721
1894
|
consola.info("Generating bundle...");
|
|
722
|
-
const bigcommerceDir =
|
|
723
|
-
const distDir =
|
|
1895
|
+
const bigcommerceDir = join10(process.cwd(), ".bigcommerce");
|
|
1896
|
+
const distDir = join10(process.cwd(), ".bigcommerce", "dist");
|
|
724
1897
|
try {
|
|
725
1898
|
await access(distDir);
|
|
726
1899
|
} catch {
|
|
@@ -730,7 +1903,7 @@ var generateBundleZip = async () => {
|
|
|
730
1903
|
if (buildDirContents.length === 0) {
|
|
731
1904
|
throw new Error(`Dist directory is empty: ${distDir}`);
|
|
732
1905
|
}
|
|
733
|
-
const outputZip =
|
|
1906
|
+
const outputZip = join10(bigcommerceDir, "bundle.zip");
|
|
734
1907
|
const zip = new AdmZip();
|
|
735
1908
|
zip.addLocalFolder(distDir, "output");
|
|
736
1909
|
zip.writeZip(outputZip);
|
|
@@ -746,7 +1919,7 @@ var generateUploadSignature = async (storeHash, accessToken, apiHost) => {
|
|
|
746
1919
|
"X-Auth-Token": accessToken,
|
|
747
1920
|
"Content-Type": "application/json",
|
|
748
1921
|
Accept: "application/json",
|
|
749
|
-
"X-Correlation-Id": getTelemetry().
|
|
1922
|
+
"X-Correlation-Id": getTelemetry().correlationId
|
|
750
1923
|
},
|
|
751
1924
|
body: JSON.stringify({})
|
|
752
1925
|
}
|
|
@@ -761,7 +1934,7 @@ var generateUploadSignature = async (storeHash, accessToken, apiHost) => {
|
|
|
761
1934
|
};
|
|
762
1935
|
var uploadBundleZip = async (uploadUrl) => {
|
|
763
1936
|
consola.info("Uploading bundle...");
|
|
764
|
-
const zipPath =
|
|
1937
|
+
const zipPath = join10(process.cwd(), ".bigcommerce", "bundle.zip");
|
|
765
1938
|
const fileBuffer = await readFile(zipPath);
|
|
766
1939
|
const response = await fetch(uploadUrl, {
|
|
767
1940
|
method: "PUT",
|
|
@@ -789,6 +1962,30 @@ var parseEnvironmentVariables = (secretOption) => {
|
|
|
789
1962
|
};
|
|
790
1963
|
});
|
|
791
1964
|
};
|
|
1965
|
+
var AUTO_DETECT_SECRETS = [
|
|
1966
|
+
{ key: "BIGCOMMERCE_STORE_HASH", warnIfMissing: true },
|
|
1967
|
+
{ key: "BIGCOMMERCE_CHANNEL_ID", warnIfMissing: true },
|
|
1968
|
+
{ key: "BIGCOMMERCE_STOREFRONT_TOKEN", warnIfMissing: true },
|
|
1969
|
+
{ key: "BIGCOMMERCE_API_HOST", warnIfMissing: false },
|
|
1970
|
+
{ key: "BIGCOMMERCE_GRAPHQL_API_DOMAIN", warnIfMissing: false },
|
|
1971
|
+
{ key: "AUTH_SECRET", warnIfMissing: false }
|
|
1972
|
+
];
|
|
1973
|
+
var autoDetectSecrets = (environmentVariables) => {
|
|
1974
|
+
const secrets = environmentVariables ?? [];
|
|
1975
|
+
const existingKeys = new Set(secrets.map((s) => s.key));
|
|
1976
|
+
AUTO_DETECT_SECRETS.forEach(({ key, warnIfMissing }) => {
|
|
1977
|
+
if (existingKeys.has(key)) {
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
const value = process.env[key];
|
|
1981
|
+
if (value) {
|
|
1982
|
+
secrets.push({ type: "secret", key, value });
|
|
1983
|
+
} else if (warnIfMissing) {
|
|
1984
|
+
consola.warn(`${key} is not set in the environment and was not provided via --secret.`);
|
|
1985
|
+
}
|
|
1986
|
+
});
|
|
1987
|
+
return secrets;
|
|
1988
|
+
};
|
|
792
1989
|
var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, apiHost, environmentVariables) => {
|
|
793
1990
|
consola.info("Creating deployment...");
|
|
794
1991
|
const response = await fetch(
|
|
@@ -799,7 +1996,7 @@ var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, a
|
|
|
799
1996
|
"X-Auth-Token": accessToken,
|
|
800
1997
|
"Content-Type": "application/json",
|
|
801
1998
|
Accept: "application/json",
|
|
802
|
-
"X-Correlation-Id": getTelemetry().
|
|
1999
|
+
"X-Correlation-Id": getTelemetry().correlationId
|
|
803
2000
|
},
|
|
804
2001
|
body: JSON.stringify({
|
|
805
2002
|
project_uuid: projectUuid,
|
|
@@ -818,7 +2015,7 @@ var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, a
|
|
|
818
2015
|
};
|
|
819
2016
|
var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost) => {
|
|
820
2017
|
consola.info("Fetching deployment status...");
|
|
821
|
-
const spinner =
|
|
2018
|
+
const spinner = yoctoSpinner5().start("Fetching...");
|
|
822
2019
|
const response = await fetch(
|
|
823
2020
|
`https://${apiHost}/stores/${storeHash}/v3/infrastructure/deployments/${deploymentUuid}/events`,
|
|
824
2021
|
{
|
|
@@ -827,7 +2024,7 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
827
2024
|
"X-Auth-Token": accessToken,
|
|
828
2025
|
Accept: "text/event-stream",
|
|
829
2026
|
Connection: "keep-alive",
|
|
830
|
-
"X-Correlation-Id": getTelemetry().
|
|
2027
|
+
"X-Correlation-Id": getTelemetry().correlationId
|
|
831
2028
|
}
|
|
832
2029
|
}
|
|
833
2030
|
);
|
|
@@ -864,37 +2061,45 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
864
2061
|
spinner.text = STEPS[data.event.step];
|
|
865
2062
|
}
|
|
866
2063
|
if (data.deployment_url) {
|
|
867
|
-
console.log(data.deployment_url);
|
|
868
2064
|
deploymentUrl = data.deployment_url;
|
|
869
2065
|
}
|
|
870
2066
|
});
|
|
871
2067
|
}
|
|
872
2068
|
done = streamDone;
|
|
873
2069
|
}
|
|
874
|
-
spinner.success("Deployment completed successfully
|
|
2070
|
+
spinner.success("Deployment completed successfully.");
|
|
875
2071
|
if (deploymentUrl) {
|
|
876
|
-
|
|
2072
|
+
const url = deploymentUrl.startsWith("https://") ? deploymentUrl : `https://${deploymentUrl}`;
|
|
2073
|
+
consola.success(`View your deployment at: ${colorize5("blue", url)}`);
|
|
877
2074
|
}
|
|
878
2075
|
};
|
|
879
|
-
var
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
)
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
2076
|
+
var fetchProject = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
2077
|
+
const response = await fetch(
|
|
2078
|
+
`https://${apiHost}/stores/${storeHash}/v3/infrastructure/projects`,
|
|
2079
|
+
{
|
|
2080
|
+
method: "GET",
|
|
2081
|
+
headers: {
|
|
2082
|
+
"X-Auth-Token": accessToken,
|
|
2083
|
+
"Content-Type": "application/json",
|
|
2084
|
+
Accept: "application/json",
|
|
2085
|
+
"X-Correlation-Id": getTelemetry().correlationId
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
);
|
|
2089
|
+
if (!response.ok) {
|
|
2090
|
+
throw new Error(`Failed to fetch projects: ${response.status} ${response.statusText}`);
|
|
2091
|
+
}
|
|
2092
|
+
const res = await response.json();
|
|
2093
|
+
const { data } = ProjectSchema.parse(res);
|
|
2094
|
+
return data.find((project2) => project2.uuid === projectUuid);
|
|
2095
|
+
};
|
|
2096
|
+
var deploy = new Command4("deploy").configureHelp({ showGlobalOptions: true }).description("Deploy your application to Cloudflare.").addHelpText(
|
|
2097
|
+
"after",
|
|
2098
|
+
`
|
|
2099
|
+
Example:
|
|
2100
|
+
$ catalyst deploy --secret BIGCOMMERCE_STORE_HASH=<YOUR_STORE_HASH> --secret BIGCOMMERCE_STOREFRONT_TOKEN=<YOUR_STOREFRONT_TOKEN>`
|
|
2101
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
2102
|
+
new Option5(
|
|
898
2103
|
"--secret <value>",
|
|
899
2104
|
"Secret to set for the deployment (repeatable). Format: --secret KEY=VALUE"
|
|
900
2105
|
).argParser((value, previous = []) => {
|
|
@@ -907,15 +2112,67 @@ var deploy = new Command3("deploy").description("Deploy your application to Clou
|
|
|
907
2112
|
const config2 = getProjectConfig();
|
|
908
2113
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
909
2114
|
const telemetry3 = getTelemetry();
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
2115
|
+
await telemetry3.identify(storeHash);
|
|
2116
|
+
const linkedProjectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
2117
|
+
let projectUuid;
|
|
2118
|
+
const promptForProject = async () => {
|
|
2119
|
+
try {
|
|
2120
|
+
return await selectOrCreateInfrastructureProject({
|
|
2121
|
+
storeHash,
|
|
2122
|
+
accessToken,
|
|
2123
|
+
apiHost: options.apiHost
|
|
2124
|
+
});
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
if (error instanceof NoLinkedProjectError) {
|
|
2127
|
+
consola.info(
|
|
2128
|
+
"When you're ready to create a project, run `catalyst project create` or re-run `catalyst deploy`."
|
|
2129
|
+
);
|
|
2130
|
+
process.exit(0);
|
|
2131
|
+
}
|
|
2132
|
+
throw error;
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
if (linkedProjectUuid) {
|
|
2136
|
+
const existing = await fetchProject(
|
|
2137
|
+
linkedProjectUuid,
|
|
2138
|
+
storeHash,
|
|
2139
|
+
accessToken,
|
|
2140
|
+
options.apiHost
|
|
914
2141
|
);
|
|
2142
|
+
if (existing) {
|
|
2143
|
+
projectUuid = linkedProjectUuid;
|
|
2144
|
+
} else {
|
|
2145
|
+
consola.warn(
|
|
2146
|
+
`The linked project (${linkedProjectUuid}) no longer exists on this store. It may have been deleted.`
|
|
2147
|
+
);
|
|
2148
|
+
const selected = await promptForProject();
|
|
2149
|
+
projectUuid = selected.uuid;
|
|
2150
|
+
config2.set("projectUuid", projectUuid);
|
|
2151
|
+
consola.success(`Linked project "${selected.name}".`);
|
|
2152
|
+
}
|
|
2153
|
+
} else {
|
|
2154
|
+
consola.info("No project is currently linked.");
|
|
2155
|
+
const selected = await promptForProject();
|
|
2156
|
+
projectUuid = selected.uuid;
|
|
2157
|
+
config2.set("projectUuid", projectUuid);
|
|
2158
|
+
consola.success(`Linked project "${selected.name}".`);
|
|
2159
|
+
}
|
|
2160
|
+
if (!getProjectState().isTransformed) {
|
|
2161
|
+
const shouldSetup = await consola.prompt(
|
|
2162
|
+
"Your project is not yet set up for Commerce Hosting deployments. Would you like to run the Commerce Hosting setup now?",
|
|
2163
|
+
{ type: "confirm", initial: true }
|
|
2164
|
+
);
|
|
2165
|
+
if (!shouldSetup) {
|
|
2166
|
+
consola.info("When you're ready to deploy, re-run `catalyst deploy` to complete setup.");
|
|
2167
|
+
process.exit(0);
|
|
2168
|
+
}
|
|
2169
|
+
const projectDir = dirname4(process.cwd());
|
|
2170
|
+
setupCommerceHosting({ projectDir, projectUuid, storeHash, accessToken });
|
|
2171
|
+
consola.success("Commerce Hosting setup complete.");
|
|
2172
|
+
await installDependencies(projectDir);
|
|
915
2173
|
}
|
|
916
|
-
await telemetry3.identify(storeHash);
|
|
917
2174
|
if (options.prebuilt) {
|
|
918
|
-
const distDir =
|
|
2175
|
+
const distDir = join10(process.cwd(), ".bigcommerce", "dist");
|
|
919
2176
|
try {
|
|
920
2177
|
await access(distDir);
|
|
921
2178
|
} catch {
|
|
@@ -944,7 +2201,7 @@ var deploy = new Command3("deploy").description("Deploy your application to Clou
|
|
|
944
2201
|
}
|
|
945
2202
|
const uploadSignature = await generateUploadSignature(storeHash, accessToken, options.apiHost);
|
|
946
2203
|
await uploadBundleZip(uploadSignature.upload_url);
|
|
947
|
-
const environmentVariables = parseEnvironmentVariables(options.secret);
|
|
2204
|
+
const environmentVariables = autoDetectSecrets(parseEnvironmentVariables(options.secret));
|
|
948
2205
|
const { deployment_uuid: deploymentUuid } = await createDeployment(
|
|
949
2206
|
projectUuid,
|
|
950
2207
|
uploadSignature.upload_uuid,
|
|
@@ -957,37 +2214,9 @@ var deploy = new Command3("deploy").description("Deploy your application to Clou
|
|
|
957
2214
|
});
|
|
958
2215
|
|
|
959
2216
|
// src/cli/commands/logs.ts
|
|
960
|
-
import { Command as
|
|
961
|
-
import { colorize as
|
|
962
|
-
import { z as
|
|
963
|
-
|
|
964
|
-
// src/cli/lib/shared-options.ts
|
|
965
|
-
import { Option as Option4 } from "commander";
|
|
966
|
-
var storeHashOption = () => new Option4(
|
|
967
|
-
"--store-hash <hash>",
|
|
968
|
-
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
969
|
-
).env("BIGCOMMERCE_STORE_HASH");
|
|
970
|
-
var accessTokenOption = () => new Option4(
|
|
971
|
-
"--access-token <token>",
|
|
972
|
-
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
973
|
-
).env("BIGCOMMERCE_ACCESS_TOKEN");
|
|
974
|
-
var apiHostOption = () => new Option4("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com");
|
|
975
|
-
var projectUuidOption = () => new Option4(
|
|
976
|
-
"--project-uuid <uuid>",
|
|
977
|
-
"BigCommerce infrastructure project UUID. Can be found via the BigCommerce API (GET /v3/infrastructure/projects)."
|
|
978
|
-
).env("BIGCOMMERCE_PROJECT_UUID");
|
|
979
|
-
var resolveProjectUuid = (options) => {
|
|
980
|
-
const config2 = getProjectConfig();
|
|
981
|
-
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
982
|
-
if (!projectUuid) {
|
|
983
|
-
throw new Error(
|
|
984
|
-
"Project UUID is required. Please run either `catalyst project link` or `catalyst project create` or this command again with --project-uuid <uuid>."
|
|
985
|
-
);
|
|
986
|
-
}
|
|
987
|
-
return projectUuid;
|
|
988
|
-
};
|
|
989
|
-
|
|
990
|
-
// src/cli/commands/logs.ts
|
|
2217
|
+
import { Command as Command5, Option as Option6 } from "commander";
|
|
2218
|
+
import { colorize as colorize6 } from "consola/utils";
|
|
2219
|
+
import { z as z10 } from "zod";
|
|
991
2220
|
var telemetry = new Telemetry();
|
|
992
2221
|
var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
|
|
993
2222
|
var MAX_RETRIES = 5;
|
|
@@ -998,23 +2227,23 @@ var LEVEL_COLORS = {
|
|
|
998
2227
|
ERROR: "red",
|
|
999
2228
|
DEBUG: "gray"
|
|
1000
2229
|
};
|
|
1001
|
-
var LogEventSchema =
|
|
1002
|
-
uuid:
|
|
1003
|
-
project_uuid:
|
|
1004
|
-
request:
|
|
1005
|
-
method:
|
|
1006
|
-
url:
|
|
1007
|
-
status_code:
|
|
2230
|
+
var LogEventSchema = z10.object({
|
|
2231
|
+
uuid: z10.string(),
|
|
2232
|
+
project_uuid: z10.string(),
|
|
2233
|
+
request: z10.object({
|
|
2234
|
+
method: z10.string(),
|
|
2235
|
+
url: z10.string(),
|
|
2236
|
+
status_code: z10.number()
|
|
1008
2237
|
}),
|
|
1009
|
-
logs:
|
|
1010
|
-
|
|
1011
|
-
timestamp:
|
|
1012
|
-
level:
|
|
1013
|
-
messages:
|
|
2238
|
+
logs: z10.array(
|
|
2239
|
+
z10.object({
|
|
2240
|
+
timestamp: z10.string(),
|
|
2241
|
+
level: z10.string(),
|
|
2242
|
+
messages: z10.array(z10.unknown())
|
|
1014
2243
|
})
|
|
1015
2244
|
),
|
|
1016
|
-
exceptions:
|
|
1017
|
-
timestamp:
|
|
2245
|
+
exceptions: z10.array(z10.unknown()),
|
|
2246
|
+
timestamp: z10.string()
|
|
1018
2247
|
}).loose();
|
|
1019
2248
|
var StreamError = class extends Error {
|
|
1020
2249
|
constructor(message, fatal) {
|
|
@@ -1028,7 +2257,7 @@ var formatLogEvent = (event, format) => {
|
|
|
1028
2257
|
logEntries.forEach((entry) => {
|
|
1029
2258
|
const msg = formatMessages(entry.messages);
|
|
1030
2259
|
const level = entry.level.toUpperCase();
|
|
1031
|
-
const coloredLevel =
|
|
2260
|
+
const coloredLevel = colorize6(LEVEL_COLORS[level] ?? "white", level);
|
|
1032
2261
|
switch (format) {
|
|
1033
2262
|
case "short":
|
|
1034
2263
|
consola.log(msg);
|
|
@@ -1136,45 +2365,71 @@ var tailLogs = async (projectUuid, storeHash, accessToken, apiHost, format, conn
|
|
|
1136
2365
|
}
|
|
1137
2366
|
}
|
|
1138
2367
|
};
|
|
1139
|
-
var tail = new
|
|
1140
|
-
|
|
2368
|
+
var tail = new Command5("tail").configureHelp({ showGlobalOptions: true }).description("Tail live logs from your deployed application.").addHelpText(
|
|
2369
|
+
"after",
|
|
2370
|
+
`
|
|
2371
|
+
Examples:
|
|
2372
|
+
$ catalyst logs tail
|
|
2373
|
+
|
|
2374
|
+
# Tail logs with request format
|
|
2375
|
+
$ catalyst logs tail --format request
|
|
2376
|
+
|
|
2377
|
+
# Tail logs as raw JSON (useful for piping to other tools)
|
|
2378
|
+
$ catalyst logs tail --format json`
|
|
2379
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
2380
|
+
new Option6("--format <format>", "Output format for log events.").choices(["json", "pretty", "default", "short", "request"]).default("default")
|
|
1141
2381
|
).action(async (options) => {
|
|
1142
2382
|
try {
|
|
1143
|
-
|
|
2383
|
+
const config2 = getProjectConfig();
|
|
2384
|
+
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
2385
|
+
await telemetry.identify(storeHash);
|
|
1144
2386
|
const projectUuid = resolveProjectUuid(options);
|
|
1145
|
-
await tailLogs(
|
|
1146
|
-
projectUuid,
|
|
1147
|
-
options.storeHash,
|
|
1148
|
-
options.accessToken,
|
|
1149
|
-
options.apiHost,
|
|
1150
|
-
options.format
|
|
1151
|
-
);
|
|
2387
|
+
await tailLogs(projectUuid, storeHash, accessToken, options.apiHost, options.format);
|
|
1152
2388
|
} catch (error) {
|
|
1153
2389
|
consola.error(error);
|
|
1154
2390
|
process.exit(1);
|
|
1155
2391
|
}
|
|
1156
2392
|
});
|
|
1157
|
-
var query = new
|
|
2393
|
+
var query = new Command5("query").configureHelp({ showGlobalOptions: true }).description("Query historical logs from your deployed application.").addHelpText(
|
|
2394
|
+
"after",
|
|
2395
|
+
`
|
|
2396
|
+
Example:
|
|
2397
|
+
$ catalyst logs query`
|
|
2398
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).action((options) => {
|
|
2399
|
+
const config2 = getProjectConfig();
|
|
2400
|
+
resolveCredentials(options, config2);
|
|
1158
2401
|
consola.error("The query command is not yet implemented.");
|
|
1159
2402
|
process.exit(1);
|
|
1160
2403
|
});
|
|
1161
|
-
var logs = new
|
|
2404
|
+
var logs = new Command5("logs").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
|
|
1162
2405
|
|
|
1163
2406
|
// src/cli/commands/project.ts
|
|
1164
|
-
import { Command as
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
2407
|
+
import { Command as Command6 } from "commander";
|
|
2408
|
+
import { colorize as colorize7 } from "consola/utils";
|
|
2409
|
+
import { dirname as dirname5 } from "path";
|
|
2410
|
+
async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
2411
|
+
if (getProjectState().isTransformed) return;
|
|
2412
|
+
const shouldSetup = await consola.prompt(
|
|
2413
|
+
"Your project has been linked, but is not fully set up for Commerce Hosting deployments yet. Would you like to run the setup now?",
|
|
2414
|
+
{ type: "confirm", initial: true }
|
|
2415
|
+
);
|
|
2416
|
+
if (!shouldSetup) return;
|
|
2417
|
+
const projectDir = dirname5(process.cwd());
|
|
2418
|
+
setupCommerceHosting({
|
|
2419
|
+
projectDir,
|
|
2420
|
+
projectUuid,
|
|
2421
|
+
storeHash: credentials?.storeHash,
|
|
2422
|
+
accessToken: credentials?.accessToken
|
|
2423
|
+
});
|
|
2424
|
+
consola.success("Commerce Hosting setup complete.");
|
|
2425
|
+
await installDependencies(projectDir);
|
|
2426
|
+
}
|
|
2427
|
+
var list = new Command6("list").configureHelp({ showGlobalOptions: true }).description("List BigCommerce infrastructure projects for your store.").addHelpText(
|
|
2428
|
+
"after",
|
|
2429
|
+
`
|
|
2430
|
+
Example:
|
|
2431
|
+
$ catalyst project list`
|
|
2432
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).action(async (options) => {
|
|
1178
2433
|
const config2 = getProjectConfig();
|
|
1179
2434
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1180
2435
|
await getTelemetry().identify(storeHash);
|
|
@@ -1186,26 +2441,21 @@ var list = new Command5("list").description("List BigCommerce infrastructure pro
|
|
|
1186
2441
|
process.exit(0);
|
|
1187
2442
|
return;
|
|
1188
2443
|
}
|
|
2444
|
+
const linkedProjectUuid = config2.get("projectUuid");
|
|
1189
2445
|
projects.forEach((p) => {
|
|
1190
|
-
|
|
2446
|
+
const marker = p.uuid === linkedProjectUuid ? ` ${colorize7("green", "[linked]")}` : "";
|
|
2447
|
+
consola.log(`${p.name} (${p.uuid})${marker}`);
|
|
1191
2448
|
});
|
|
1192
2449
|
process.exit(0);
|
|
1193
2450
|
});
|
|
1194
|
-
var
|
|
2451
|
+
var create2 = new Command6("create").configureHelp({ showGlobalOptions: true }).description(
|
|
1195
2452
|
"Create a new BigCommerce infrastructure project and link it to your local Catalyst project."
|
|
1196
|
-
).
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
).addOption(
|
|
1202
|
-
new Option6(
|
|
1203
|
-
"--access-token <token>",
|
|
1204
|
-
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
1205
|
-
).env("CATALYST_ACCESS_TOKEN")
|
|
1206
|
-
).addOption(
|
|
1207
|
-
new Option6("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com")
|
|
1208
|
-
).action(async (options) => {
|
|
2453
|
+
).addHelpText(
|
|
2454
|
+
"after",
|
|
2455
|
+
`
|
|
2456
|
+
Example:
|
|
2457
|
+
$ catalyst project create`
|
|
2458
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).action(async (options) => {
|
|
1209
2459
|
const config2 = getProjectConfig();
|
|
1210
2460
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1211
2461
|
await getTelemetry().identify(storeHash);
|
|
@@ -1221,24 +2471,18 @@ var create = new Command5("create").description(
|
|
|
1221
2471
|
consola.success("Project UUID written to .bigcommerce/project.json.");
|
|
1222
2472
|
process.exit(0);
|
|
1223
2473
|
});
|
|
1224
|
-
var link = new
|
|
2474
|
+
var link = new Command6("link").configureHelp({ showGlobalOptions: true }).description(
|
|
1225
2475
|
"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."
|
|
1226
|
-
).
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
).addOption(
|
|
1237
|
-
new Option6("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com")
|
|
1238
|
-
).option(
|
|
1239
|
-
"--project-uuid <uuid>",
|
|
1240
|
-
"BigCommerce infrastructure project UUID. Can be found via the BigCommerce API (GET /v3/infrastructure/projects). Use this to link directly without fetching projects."
|
|
1241
|
-
).action(async (options) => {
|
|
2476
|
+
).addHelpText(
|
|
2477
|
+
"after",
|
|
2478
|
+
`
|
|
2479
|
+
Examples:
|
|
2480
|
+
# Link interactively (prompts to select or create)
|
|
2481
|
+
$ catalyst project link
|
|
2482
|
+
|
|
2483
|
+
# Link using a project UUID directly
|
|
2484
|
+
$ catalyst project link --project-uuid <UUID>`
|
|
2485
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).action(async (options) => {
|
|
1242
2486
|
const config2 = getProjectConfig();
|
|
1243
2487
|
const writeProjectConfig = (uuid, credentials) => {
|
|
1244
2488
|
consola.start("Writing project UUID to .bigcommerce/project.json...");
|
|
@@ -1251,63 +2495,142 @@ var link = new Command5("link").description(
|
|
|
1251
2495
|
};
|
|
1252
2496
|
if (options.projectUuid) {
|
|
1253
2497
|
writeProjectConfig(options.projectUuid);
|
|
2498
|
+
await offerCommerceHostingSetup(options.projectUuid);
|
|
1254
2499
|
process.exit(0);
|
|
1255
2500
|
return;
|
|
1256
2501
|
}
|
|
1257
2502
|
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1258
2503
|
await getTelemetry().identify(storeHash);
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
2504
|
+
let selected;
|
|
2505
|
+
try {
|
|
2506
|
+
selected = await selectOrCreateInfrastructureProject(
|
|
2507
|
+
{ storeHash, accessToken, apiHost: options.apiHost },
|
|
2508
|
+
config2.get("projectUuid")
|
|
2509
|
+
);
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
if (error instanceof NoLinkedProjectError) {
|
|
2512
|
+
consola.info(
|
|
2513
|
+
"When you're ready to create a project, run `catalyst project create` or re-run `catalyst project link`."
|
|
2514
|
+
);
|
|
2515
|
+
process.exit(0);
|
|
2516
|
+
throw error;
|
|
1272
2517
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
2518
|
+
throw error;
|
|
2519
|
+
}
|
|
2520
|
+
writeProjectConfig(selected.uuid, { storeHash, accessToken });
|
|
2521
|
+
await offerCommerceHostingSetup(selected.uuid, { storeHash, accessToken });
|
|
2522
|
+
process.exit(0);
|
|
2523
|
+
});
|
|
2524
|
+
var del = new Command6("delete").configureHelp({ showGlobalOptions: true }).description(
|
|
2525
|
+
"Permanently delete a BigCommerce infrastructure project. This action is irreversible."
|
|
2526
|
+
).addHelpText(
|
|
2527
|
+
"after",
|
|
2528
|
+
`
|
|
2529
|
+
Examples:
|
|
2530
|
+
# Select a project to delete interactively
|
|
2531
|
+
$ catalyst project delete
|
|
2532
|
+
|
|
2533
|
+
# Delete a specific project (still prompts for confirmation)
|
|
2534
|
+
$ catalyst project delete --project-uuid <UUID>
|
|
2535
|
+
|
|
2536
|
+
# Skip the confirmation prompt
|
|
2537
|
+
$ catalyst project delete --project-uuid <UUID> --force`
|
|
2538
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option("--force", "Skip the confirmation prompt before deleting.").action(async (options) => {
|
|
2539
|
+
const config2 = getProjectConfig();
|
|
2540
|
+
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
2541
|
+
await getTelemetry().identify(storeHash);
|
|
2542
|
+
let targetUuid = options.projectUuid;
|
|
2543
|
+
let targetName;
|
|
2544
|
+
if (!targetUuid) {
|
|
2545
|
+
consola.start("Fetching projects...");
|
|
2546
|
+
const projects = await fetchProjects(storeHash, accessToken, options.apiHost);
|
|
2547
|
+
consola.success("Projects fetched.");
|
|
2548
|
+
if (projects.length === 0) {
|
|
2549
|
+
consola.info("No projects found.");
|
|
2550
|
+
process.exit(0);
|
|
2551
|
+
return;
|
|
1280
2552
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
2553
|
+
const linkedProjectUuid = config2.get("projectUuid");
|
|
2554
|
+
const selected = await consola.prompt(
|
|
2555
|
+
"Select a project to delete (Press <enter> to select).",
|
|
2556
|
+
{
|
|
2557
|
+
type: "select",
|
|
2558
|
+
options: [
|
|
2559
|
+
...projects.map((p) => ({
|
|
2560
|
+
label: p.uuid === linkedProjectUuid ? `${p.name} ${colorize7("green", "[linked]")}` : p.name,
|
|
2561
|
+
value: p.uuid,
|
|
2562
|
+
hint: p.uuid
|
|
2563
|
+
})),
|
|
2564
|
+
{ label: "Cancel", value: "cancel", hint: "Exit without deleting any project." }
|
|
2565
|
+
],
|
|
2566
|
+
cancel: "reject"
|
|
2567
|
+
}
|
|
2568
|
+
);
|
|
2569
|
+
if (selected === "cancel") {
|
|
2570
|
+
consola.info("Aborted. No project was deleted.");
|
|
2571
|
+
process.exit(0);
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
const matched = projects.find((p) => p.uuid === selected);
|
|
2575
|
+
if (!matched) {
|
|
2576
|
+
throw new Error(`Selected project ${String(selected)} not found in fetched list.`);
|
|
2577
|
+
}
|
|
2578
|
+
targetUuid = matched.uuid;
|
|
2579
|
+
targetName = matched.name;
|
|
2580
|
+
}
|
|
2581
|
+
if (!options.force) {
|
|
2582
|
+
const label = targetName ? `"${targetName}" (${targetUuid})` : targetUuid;
|
|
2583
|
+
const confirmed = await consola.prompt(
|
|
2584
|
+
`Are you sure you want to delete project ${label}? This action is irreversible and will permanently destroy the project and all of its data.`,
|
|
2585
|
+
{ type: "confirm", initial: false }
|
|
2586
|
+
);
|
|
2587
|
+
if (!confirmed) {
|
|
2588
|
+
consola.info("Aborted. No project was deleted.");
|
|
2589
|
+
process.exit(0);
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
consola.start(`Deleting project ${targetUuid}...`);
|
|
2594
|
+
await deleteProject(targetUuid, storeHash, accessToken, options.apiHost);
|
|
2595
|
+
consola.success(`Project ${targetUuid} deleted.`);
|
|
2596
|
+
if (config2.get("projectUuid") === targetUuid) {
|
|
2597
|
+
config2.delete("projectUuid");
|
|
2598
|
+
consola.info("Removed project UUID from .bigcommerce/project.json.");
|
|
1289
2599
|
}
|
|
1290
|
-
writeProjectConfig(projectUuid, { storeHash, accessToken });
|
|
1291
2600
|
process.exit(0);
|
|
1292
2601
|
});
|
|
1293
|
-
var project = new
|
|
2602
|
+
var project = new Command6("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure project.").addCommand(create2).addCommand(list).addCommand(link).addCommand(del);
|
|
1294
2603
|
|
|
1295
2604
|
// src/cli/commands/start.ts
|
|
1296
|
-
import { Command as
|
|
2605
|
+
import { Command as Command7 } from "commander";
|
|
1297
2606
|
import { execa as execa2 } from "execa";
|
|
1298
|
-
import { existsSync, lstatSync, symlinkSync } from "fs";
|
|
1299
|
-
import { join as
|
|
1300
|
-
var start = new
|
|
2607
|
+
import { existsSync as existsSync4, lstatSync as lstatSync2, symlinkSync as symlinkSync2 } from "fs";
|
|
2608
|
+
import { join as join11, relative } from "path";
|
|
2609
|
+
var start = new Command7("start").configureHelp({ showGlobalOptions: true }).description(
|
|
1301
2610
|
"Start a local preview of your Catalyst storefront using the OpenNext Cloudflare adapter."
|
|
2611
|
+
).addHelpText(
|
|
2612
|
+
"after",
|
|
2613
|
+
`
|
|
2614
|
+
Example:
|
|
2615
|
+
$ catalyst start`
|
|
1302
2616
|
).action(async () => {
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
2617
|
+
const state = getProjectState();
|
|
2618
|
+
if (!state.isTransformed) {
|
|
2619
|
+
consola.info("Project is not set up for Commerce Hosting \u2014 running `next start`.");
|
|
2620
|
+
await execa2("pnpm", ["exec", "next", "start"], {
|
|
2621
|
+
stdio: "inherit",
|
|
2622
|
+
cwd: process.cwd()
|
|
2623
|
+
});
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
const envLocal = join11(process.cwd(), ".env.local");
|
|
2627
|
+
const devVars = join11(process.cwd(), ".bigcommerce", ".dev.vars");
|
|
2628
|
+
if (existsSync4(envLocal)) {
|
|
2629
|
+
if (!existsSync4(devVars)) {
|
|
2630
|
+
const target = relative(join11(process.cwd(), ".bigcommerce"), envLocal);
|
|
2631
|
+
symlinkSync2(target, devVars);
|
|
1309
2632
|
consola.success("Symlinked .bigcommerce/.dev.vars \u2192 .env.local");
|
|
1310
|
-
} else if (!
|
|
2633
|
+
} else if (!lstatSync2(devVars).isSymbolicLink()) {
|
|
1311
2634
|
consola.warn(
|
|
1312
2635
|
".bigcommerce/.dev.vars already exists and is not a symlink. Wrangler will use it instead of .env.local."
|
|
1313
2636
|
);
|
|
@@ -1324,7 +2647,7 @@ var start = new Command6("start").description(
|
|
|
1324
2647
|
"opennextjs-cloudflare",
|
|
1325
2648
|
"preview",
|
|
1326
2649
|
"--config",
|
|
1327
|
-
|
|
2650
|
+
join11(".bigcommerce", "wrangler.jsonc")
|
|
1328
2651
|
],
|
|
1329
2652
|
{
|
|
1330
2653
|
stdio: "inherit",
|
|
@@ -1334,11 +2657,25 @@ var start = new Command6("start").description(
|
|
|
1334
2657
|
});
|
|
1335
2658
|
|
|
1336
2659
|
// src/cli/commands/telemetry.ts
|
|
1337
|
-
import { Argument, Command as
|
|
1338
|
-
import { colorize as
|
|
2660
|
+
import { Argument, Command as Command8, Option as Option7 } from "commander";
|
|
2661
|
+
import { colorize as colorize8 } from "consola/utils";
|
|
1339
2662
|
var telemetryService = getTelemetry();
|
|
1340
2663
|
var isEnabled = telemetryService.isEnabled();
|
|
1341
|
-
var telemetry2 = new
|
|
2664
|
+
var telemetry2 = new Command8("telemetry").configureHelp({ showGlobalOptions: true }).description(
|
|
2665
|
+
"View or change CLI telemetry collection status. Enabling telemetry helps BigCommerce support diagnose and troubleshoot errors you encounter when using the CLI."
|
|
2666
|
+
).addHelpText(
|
|
2667
|
+
"after",
|
|
2668
|
+
`
|
|
2669
|
+
Examples:
|
|
2670
|
+
# Show telemetry status
|
|
2671
|
+
$ catalyst telemetry
|
|
2672
|
+
|
|
2673
|
+
# Enable telemetry collection
|
|
2674
|
+
$ catalyst telemetry enable
|
|
2675
|
+
|
|
2676
|
+
# Disable telemetry collection
|
|
2677
|
+
$ catalyst telemetry --disable`
|
|
2678
|
+
).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new Option7("--enable", `Enables CLI telemetry collection.`).conflicts("disable")).option("--disable", `Disables CLI telemetry collection.`).action((arg, options) => {
|
|
1342
2679
|
if (options.enable || arg === "enable") {
|
|
1343
2680
|
telemetryService.setEnabled(true);
|
|
1344
2681
|
isEnabled = true;
|
|
@@ -1346,7 +2683,7 @@ var telemetry2 = new Command7("telemetry").addArgument(new Argument("[arg]").cho
|
|
|
1346
2683
|
} else if (options.disable || arg === "disable") {
|
|
1347
2684
|
telemetryService.setEnabled(false);
|
|
1348
2685
|
if (isEnabled) {
|
|
1349
|
-
consola.success("Your preference has been saved
|
|
2686
|
+
consola.success("Your preference has been saved.");
|
|
1350
2687
|
} else {
|
|
1351
2688
|
consola.info(`Catalyst CLI telemetry collection is already disabled.`);
|
|
1352
2689
|
}
|
|
@@ -1355,7 +2692,7 @@ var telemetry2 = new Command7("telemetry").addArgument(new Argument("[arg]").cho
|
|
|
1355
2692
|
consola.info("Catalyst CLI Telemetry\n");
|
|
1356
2693
|
}
|
|
1357
2694
|
consola.info(
|
|
1358
|
-
`Status: ${
|
|
2695
|
+
`Status: ${colorize8("bold", isEnabled ? colorize8("green", "Enabled") : colorize8("red", "Disabled"))}`
|
|
1359
2696
|
);
|
|
1360
2697
|
if (!isEnabled) {
|
|
1361
2698
|
consola.info(
|
|
@@ -1365,8 +2702,13 @@ var telemetry2 = new Command7("telemetry").addArgument(new Argument("[arg]").cho
|
|
|
1365
2702
|
});
|
|
1366
2703
|
|
|
1367
2704
|
// src/cli/commands/version.ts
|
|
1368
|
-
import { Command as
|
|
1369
|
-
var version = new
|
|
2705
|
+
import { Command as Command9 } from "commander";
|
|
2706
|
+
var version = new Command9("version").configureHelp({ showGlobalOptions: true }).description("Display detailed version information.").addHelpText(
|
|
2707
|
+
"after",
|
|
2708
|
+
`
|
|
2709
|
+
Example:
|
|
2710
|
+
$ catalyst version`
|
|
2711
|
+
).action(() => {
|
|
1370
2712
|
consola.log("Version Information:");
|
|
1371
2713
|
consola.log(`CLI Version: ${package_default.version}`);
|
|
1372
2714
|
consola.log(`Node Version: ${process.version}`);
|
|
@@ -1422,10 +2764,19 @@ var telemetryPostHook = async () => {
|
|
|
1422
2764
|
};
|
|
1423
2765
|
|
|
1424
2766
|
// src/cli/program.ts
|
|
1425
|
-
var
|
|
1426
|
-
|
|
2767
|
+
var defaultEnvPath = resolve(process.cwd(), ".env.local");
|
|
2768
|
+
if (existsSync5(defaultEnvPath)) {
|
|
2769
|
+
config({ path: defaultEnvPath });
|
|
2770
|
+
}
|
|
2771
|
+
if (!process.env.CATALYST_STORE_HASH && process.env.BIGCOMMERCE_STORE_HASH) {
|
|
2772
|
+
process.env.CATALYST_STORE_HASH = process.env.BIGCOMMERCE_STORE_HASH;
|
|
2773
|
+
}
|
|
2774
|
+
var program = new Command10();
|
|
2775
|
+
consola.log(colorize9("cyanBright", `\u25E2 ${package_default.name} v${package_default.version}
|
|
1427
2776
|
`));
|
|
1428
|
-
program.name(package_default.name).version(package_default.version).
|
|
2777
|
+
program.name(package_default.name).version(package_default.version).summary("CLI tool for Catalyst development").description(
|
|
2778
|
+
"CLI tool for Catalyst development.\n\nConfiguration priority: flags > env file (--env-path) > process.env > .env.local (auto-loaded from cwd) > .bigcommerce/project.json.\n\nCATALYST_STORE_HASH falls back to BIGCOMMERCE_STORE_HASH if unset.\n\nRun `catalyst <command> --help` for details on a specific command."
|
|
2779
|
+
).configureHelp({ showGlobalOptions: true }).addOption(
|
|
1429
2780
|
new Option8(
|
|
1430
2781
|
"--env-path <path>",
|
|
1431
2782
|
"Path to environment file to load (relative to current working directory)"
|
|
@@ -1442,12 +2793,12 @@ program.name(package_default.name).version(package_default.version).description(
|
|
|
1442
2793
|
const message = errCode === "ENOENT" ? `Env file not found: ${envFilePath}` : `Failed to load --env-path ${value}: ${result.error.message}`;
|
|
1443
2794
|
throw new Error(message);
|
|
1444
2795
|
}
|
|
1445
|
-
consola.log(
|
|
2796
|
+
consola.log(colorize9("cyanBright", `Loaded environment variables from ${envFilePath}
|
|
1446
2797
|
`));
|
|
1447
2798
|
}
|
|
1448
2799
|
return value;
|
|
1449
2800
|
})
|
|
1450
|
-
).addCommand(version).addCommand(start).addCommand(build).addCommand(deploy).addCommand(logs).addCommand(project).addCommand(auth).addCommand(telemetry2).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
|
|
2801
|
+
).addCommand(version).addCommand(create).addCommand(start).addCommand(build).addCommand(deploy).addCommand(logs).addCommand(project).addCommand(auth).addCommand(telemetry2).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
|
|
1451
2802
|
|
|
1452
2803
|
// src/cli/index.ts
|
|
1453
2804
|
var handleFatalError = async (error) => {
|
|
@@ -1467,13 +2818,12 @@ var handleFatalError = async (error) => {
|
|
|
1467
2818
|
consola.error(errorMessage);
|
|
1468
2819
|
if (telemetry3.isEnabled()) {
|
|
1469
2820
|
consola.info(
|
|
1470
|
-
`
|
|
1471
|
-
|
|
1472
|
-
Share this Trace ID with BigCommerce support.`
|
|
2821
|
+
`Correlation ID: ${telemetry3.correlationId}
|
|
2822
|
+
Share this Correlation ID with BigCommerce support.`
|
|
1473
2823
|
);
|
|
1474
2824
|
} else {
|
|
1475
2825
|
consola.info(
|
|
1476
|
-
"
|
|
2826
|
+
"Enable telemetry (`catalyst telemetry enable`) for improved troubleshooting with BigCommerce support."
|
|
1477
2827
|
);
|
|
1478
2828
|
}
|
|
1479
2829
|
process.exit(1);
|