@bigcommerce/catalyst 1.0.0-alpha.4 → 1.0.0-alpha.5
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 +975 -471
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/cli/lib/auth-errors.ts
|
|
4
|
+
var UnauthorizedError = class extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(
|
|
7
|
+
"Your access token is invalid or has expired.\nRun `catalyst auth login` to re-authenticate."
|
|
8
|
+
);
|
|
9
|
+
this.name = "UnauthorizedError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
function assertAuthorized(response) {
|
|
13
|
+
if (response.status === 401) {
|
|
14
|
+
throw new UnauthorizedError();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
3
18
|
// src/cli/lib/logger.ts
|
|
4
19
|
import { createConsola } from "consola";
|
|
5
20
|
var consola = createConsola({
|
|
@@ -13,7 +28,7 @@ import { randomBytes, randomUUID } from "crypto";
|
|
|
13
28
|
// package.json
|
|
14
29
|
var package_default = {
|
|
15
30
|
name: "@bigcommerce/catalyst",
|
|
16
|
-
version: "1.0.0-alpha.
|
|
31
|
+
version: "1.0.0-alpha.5",
|
|
17
32
|
type: "module",
|
|
18
33
|
bin: {
|
|
19
34
|
catalyst: "dist/cli.js"
|
|
@@ -113,7 +128,7 @@ var Telemetry = class {
|
|
|
113
128
|
this.correlationId = randomUUID();
|
|
114
129
|
this.startTime = Date.now();
|
|
115
130
|
this.analytics = new Analytics({
|
|
116
|
-
writeKey: "
|
|
131
|
+
writeKey: "not-a-valid-segment-write-key"
|
|
117
132
|
});
|
|
118
133
|
}
|
|
119
134
|
durationMs() {
|
|
@@ -180,19 +195,23 @@ function getTelemetry() {
|
|
|
180
195
|
}
|
|
181
196
|
|
|
182
197
|
// src/cli/program.ts
|
|
183
|
-
import { Option as
|
|
184
|
-
import { Command as
|
|
185
|
-
import { colorize as
|
|
198
|
+
import { Option as Option9 } from "@commander-js/extra-typings";
|
|
199
|
+
import { Command as Command12 } from "commander";
|
|
200
|
+
import { colorize as colorize8 } from "consola/utils";
|
|
186
201
|
import { config } from "dotenv";
|
|
187
202
|
import { existsSync as existsSync5 } from "fs";
|
|
188
203
|
import { resolve } from "path";
|
|
189
204
|
|
|
190
205
|
// src/cli/commands/auth.ts
|
|
191
|
-
import { Command, Option } from "commander";
|
|
206
|
+
import { Command, Option as Option2 } from "commander";
|
|
207
|
+
import { z as z4 } from "zod";
|
|
208
|
+
|
|
209
|
+
// src/cli/lib/login.ts
|
|
210
|
+
import { password } from "@inquirer/prompts";
|
|
192
211
|
import { colorize } from "consola/utils";
|
|
193
212
|
import open from "open";
|
|
194
213
|
import yoctoSpinner from "yocto-spinner";
|
|
195
|
-
import { z as
|
|
214
|
+
import { z as z2 } from "zod";
|
|
196
215
|
|
|
197
216
|
// src/cli/lib/auth.ts
|
|
198
217
|
import { z } from "zod";
|
|
@@ -201,7 +220,8 @@ var DEVICE_OAUTH_SCOPES = [
|
|
|
201
220
|
"store_v2_information",
|
|
202
221
|
"store_infrastructure_deployments_manage",
|
|
203
222
|
"store_infrastructure_logs_read_only",
|
|
204
|
-
"store_infrastructure_projects_manage"
|
|
223
|
+
"store_infrastructure_projects_manage",
|
|
224
|
+
"store_channel_settings"
|
|
205
225
|
].join(" ");
|
|
206
226
|
var DEFAULT_LOGIN_URL = "https://login.bigcommerce.com";
|
|
207
227
|
var DeviceCodeResponseSchema = z.object({
|
|
@@ -258,19 +278,119 @@ async function waitForDeviceToken(loginUrl, deviceCode, interval) {
|
|
|
258
278
|
return waitForDeviceToken(loginUrl, deviceCode, interval);
|
|
259
279
|
}
|
|
260
280
|
|
|
281
|
+
// src/cli/lib/login.ts
|
|
282
|
+
var LoginAbortedError = class extends Error {
|
|
283
|
+
constructor() {
|
|
284
|
+
super("Login aborted by user.");
|
|
285
|
+
this.name = "LoginAbortedError";
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
var StoreProfileSchema = z2.object({
|
|
289
|
+
data: z2.object({
|
|
290
|
+
store_name: z2.string()
|
|
291
|
+
})
|
|
292
|
+
});
|
|
293
|
+
async function fetchStoreProfile(storeHash, accessToken, apiHost) {
|
|
294
|
+
const response = await fetch(`https://${apiHost}/stores/${storeHash}/v3/settings/store/profile`, {
|
|
295
|
+
method: "GET",
|
|
296
|
+
headers: {
|
|
297
|
+
"X-Auth-Token": accessToken,
|
|
298
|
+
Accept: "application/json"
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
if (!response.ok) {
|
|
302
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
303
|
+
}
|
|
304
|
+
const res = await response.json();
|
|
305
|
+
const result = StoreProfileSchema.safeParse(res);
|
|
306
|
+
if (!result.success) {
|
|
307
|
+
throw new Error("Unexpected response from store profile API");
|
|
308
|
+
}
|
|
309
|
+
return result.data.data;
|
|
310
|
+
}
|
|
311
|
+
async function deviceCodeLogin(loginUrl) {
|
|
312
|
+
const deviceCode = await requestDeviceCode(loginUrl);
|
|
313
|
+
consola.info(
|
|
314
|
+
`${colorize("yellow", "Your one-time code:")} ${colorize("bold", deviceCode.user_code)}`
|
|
315
|
+
);
|
|
316
|
+
try {
|
|
317
|
+
await open(deviceCode.verification_uri);
|
|
318
|
+
consola.info(`Opened ${deviceCode.verification_uri} in your browser.`);
|
|
319
|
+
} catch {
|
|
320
|
+
consola.info(`Open ${deviceCode.verification_uri} in your browser and enter the code above.`);
|
|
321
|
+
}
|
|
322
|
+
const spinner = yoctoSpinner().start("Waiting for authentication...");
|
|
323
|
+
const credentials = await waitForDeviceToken(
|
|
324
|
+
loginUrl,
|
|
325
|
+
deviceCode.device_code,
|
|
326
|
+
deviceCode.interval
|
|
327
|
+
);
|
|
328
|
+
spinner.success("Authentication complete.");
|
|
329
|
+
return {
|
|
330
|
+
storeHash: credentials.store_hash,
|
|
331
|
+
accessToken: credentials.access_token
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async function manualLogin(apiHost) {
|
|
335
|
+
consola.info(
|
|
336
|
+
`Create a store-level API account from your BigCommerce Control Panel:
|
|
337
|
+
Settings \u2192 API \u2192 Store-level API accounts \u2192 Create API account
|
|
338
|
+
Grant these OAuth scopes: ${DEVICE_OAUTH_SCOPES}`
|
|
339
|
+
);
|
|
340
|
+
const storeHashInput = await consola.prompt("Store hash:", { type: "text" });
|
|
341
|
+
const storeHash = String(storeHashInput).trim();
|
|
342
|
+
if (!storeHash) {
|
|
343
|
+
throw new Error("Store hash is required.");
|
|
344
|
+
}
|
|
345
|
+
const accessTokenInput = await password({ message: "Access token:", mask: true });
|
|
346
|
+
const accessToken = accessTokenInput.trim();
|
|
347
|
+
if (!accessToken) {
|
|
348
|
+
throw new Error("Access token is required.");
|
|
349
|
+
}
|
|
350
|
+
const spinner = yoctoSpinner().start("Validating credentials...");
|
|
351
|
+
try {
|
|
352
|
+
const profile = await fetchStoreProfile(storeHash, accessToken, apiHost);
|
|
353
|
+
spinner.success(`Validated credentials for ${profile.store_name} (${storeHash}).`);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
spinner.error("Failed to validate credentials.");
|
|
356
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
357
|
+
throw new Error(
|
|
358
|
+
`Could not validate credentials (${message}). Double-check your store hash and access token, then try again.`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
return { storeHash, accessToken };
|
|
362
|
+
}
|
|
363
|
+
async function login(loginUrl, apiHost) {
|
|
364
|
+
try {
|
|
365
|
+
return await deviceCodeLogin(loginUrl);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
368
|
+
consola.warn(`Browser login didn't work (${message}).`);
|
|
369
|
+
const shouldFallback = await consola.prompt(
|
|
370
|
+
"Try logging in manually with a store hash and access token instead?",
|
|
371
|
+
{ type: "confirm", initial: true }
|
|
372
|
+
);
|
|
373
|
+
if (!shouldFallback) {
|
|
374
|
+
throw new LoginAbortedError();
|
|
375
|
+
}
|
|
376
|
+
return manualLogin(apiHost);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
261
380
|
// src/cli/lib/project.ts
|
|
262
|
-
import { z as
|
|
381
|
+
import { z as z3 } from "zod";
|
|
263
382
|
var InfrastructureProjectValidationError = class extends Error {
|
|
264
383
|
constructor(message) {
|
|
265
384
|
super(message);
|
|
266
385
|
this.name = "InfrastructureProjectValidationError";
|
|
267
386
|
}
|
|
268
387
|
};
|
|
269
|
-
var fetchProjectsSchema =
|
|
270
|
-
data:
|
|
271
|
-
|
|
272
|
-
uuid:
|
|
273
|
-
name:
|
|
388
|
+
var fetchProjectsSchema = z3.object({
|
|
389
|
+
data: z3.array(
|
|
390
|
+
z3.object({
|
|
391
|
+
uuid: z3.string(),
|
|
392
|
+
name: z3.string(),
|
|
393
|
+
deployment_hostnames: z3.array(z3.string())
|
|
274
394
|
})
|
|
275
395
|
)
|
|
276
396
|
});
|
|
@@ -288,6 +408,7 @@ async function hasProjectsAccess(storeHash, accessToken, apiHost) {
|
|
|
288
408
|
method: "GET",
|
|
289
409
|
headers: authHeaders(accessToken)
|
|
290
410
|
});
|
|
411
|
+
assertAuthorized(response);
|
|
291
412
|
if (response.status === 200) return true;
|
|
292
413
|
if (response.status === 403) return false;
|
|
293
414
|
throw new Error(
|
|
@@ -299,6 +420,7 @@ async function fetchProjects(storeHash, accessToken, apiHost) {
|
|
|
299
420
|
method: "GET",
|
|
300
421
|
headers: authHeaders(accessToken)
|
|
301
422
|
});
|
|
423
|
+
assertAuthorized(response);
|
|
302
424
|
if (response.status === 403) {
|
|
303
425
|
throw new Error(
|
|
304
426
|
"Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
|
|
@@ -311,18 +433,18 @@ async function fetchProjects(storeHash, accessToken, apiHost) {
|
|
|
311
433
|
const { data } = fetchProjectsSchema.parse(res);
|
|
312
434
|
return data;
|
|
313
435
|
}
|
|
314
|
-
var createProjectSchema =
|
|
315
|
-
data:
|
|
316
|
-
uuid:
|
|
317
|
-
name:
|
|
318
|
-
date_created:
|
|
319
|
-
date_modified:
|
|
436
|
+
var createProjectSchema = z3.object({
|
|
437
|
+
data: z3.object({
|
|
438
|
+
uuid: z3.string(),
|
|
439
|
+
name: z3.string(),
|
|
440
|
+
date_created: z3.coerce.date(),
|
|
441
|
+
date_modified: z3.coerce.date()
|
|
320
442
|
})
|
|
321
443
|
});
|
|
322
|
-
var validationErrorBodySchema =
|
|
323
|
-
title:
|
|
324
|
-
detail:
|
|
325
|
-
errors:
|
|
444
|
+
var validationErrorBodySchema = z3.object({
|
|
445
|
+
title: z3.string().optional(),
|
|
446
|
+
detail: z3.string().optional(),
|
|
447
|
+
errors: z3.record(z3.string(), z3.string()).optional()
|
|
326
448
|
});
|
|
327
449
|
function extractValidationMessage(body) {
|
|
328
450
|
const parsed = validationErrorBodySchema.safeParse(body);
|
|
@@ -343,6 +465,7 @@ async function createProject(name, storeHash, accessToken, apiHost) {
|
|
|
343
465
|
},
|
|
344
466
|
body: JSON.stringify({ name })
|
|
345
467
|
});
|
|
468
|
+
assertAuthorized(response);
|
|
346
469
|
if (response.status === 400 || response.status === 422) {
|
|
347
470
|
const body = await response.json().catch(() => null);
|
|
348
471
|
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;
|
|
@@ -369,6 +492,7 @@ async function deleteProject(projectUuid, storeHash, accessToken, apiHost) {
|
|
|
369
492
|
method: "DELETE",
|
|
370
493
|
headers: authHeaders(accessToken)
|
|
371
494
|
});
|
|
495
|
+
assertAuthorized(response);
|
|
372
496
|
if (response.status === 403) {
|
|
373
497
|
throw new Error(
|
|
374
498
|
"Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
|
|
@@ -398,18 +522,50 @@ function getProjectConfig() {
|
|
|
398
522
|
default: "catalyst"
|
|
399
523
|
},
|
|
400
524
|
storeHash: { type: "string" },
|
|
401
|
-
accessToken: { type: "string" }
|
|
525
|
+
accessToken: { type: "string" },
|
|
526
|
+
env: {
|
|
527
|
+
type: "object",
|
|
528
|
+
additionalProperties: { type: "string" },
|
|
529
|
+
default: {}
|
|
530
|
+
}
|
|
402
531
|
}
|
|
403
532
|
});
|
|
404
533
|
}
|
|
405
534
|
|
|
535
|
+
// src/cli/lib/shared-options.ts
|
|
536
|
+
import { Option } from "commander";
|
|
537
|
+
var storeHashOption = () => new Option(
|
|
538
|
+
"--store-hash <hash>",
|
|
539
|
+
"BigCommerce store hash. Can be found in the URL of your store Control Panel. Read from .bigcommerce/project.json or .env when not provided."
|
|
540
|
+
).env("CATALYST_STORE_HASH");
|
|
541
|
+
var accessTokenOption = () => new Option(
|
|
542
|
+
"--access-token <token>",
|
|
543
|
+
"BigCommerce access token. Can be found after creating a store-level API account. Read from .bigcommerce/project.json or .env when not provided."
|
|
544
|
+
).env("CATALYST_ACCESS_TOKEN");
|
|
545
|
+
var apiHostOption = () => new Option("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp();
|
|
546
|
+
var loginUrlOption = () => new Option("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp();
|
|
547
|
+
var projectUuidOption = () => new Option(
|
|
548
|
+
"--project-uuid <uuid>",
|
|
549
|
+
"BigCommerce infrastructure project UUID. Read from .bigcommerce/project.json or .env when not provided."
|
|
550
|
+
).env("CATALYST_PROJECT_UUID");
|
|
551
|
+
var resolveProjectUuid = (options) => {
|
|
552
|
+
const config2 = getProjectConfig();
|
|
553
|
+
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
554
|
+
if (!projectUuid) {
|
|
555
|
+
throw new Error(
|
|
556
|
+
"Project UUID is required. Please run either `catalyst project link` or `catalyst project create` or this command again with --project-uuid <uuid>."
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
return projectUuid;
|
|
560
|
+
};
|
|
561
|
+
|
|
406
562
|
// src/cli/commands/auth.ts
|
|
407
|
-
var
|
|
408
|
-
data:
|
|
409
|
-
store_name:
|
|
563
|
+
var StoreProfileSchema2 = z4.object({
|
|
564
|
+
data: z4.object({
|
|
565
|
+
store_name: z4.string()
|
|
410
566
|
})
|
|
411
567
|
});
|
|
412
|
-
async function
|
|
568
|
+
async function fetchStoreProfile2(storeHash, accessToken, apiHost) {
|
|
413
569
|
const response = await fetch(`https://${apiHost}/stores/${storeHash}/v3/settings/store/profile`, {
|
|
414
570
|
method: "GET",
|
|
415
571
|
headers: {
|
|
@@ -417,11 +573,12 @@ async function fetchStoreProfile(storeHash, accessToken, apiHost) {
|
|
|
417
573
|
Accept: "application/json"
|
|
418
574
|
}
|
|
419
575
|
});
|
|
576
|
+
assertAuthorized(response);
|
|
420
577
|
if (!response.ok) {
|
|
421
578
|
throw new Error(`${response.status} ${response.statusText}`);
|
|
422
579
|
}
|
|
423
580
|
const res = await response.json();
|
|
424
|
-
const result =
|
|
581
|
+
const result = StoreProfileSchema2.safeParse(res);
|
|
425
582
|
if (!result.success) {
|
|
426
583
|
throw new Error("Unexpected response from store profile API");
|
|
427
584
|
}
|
|
@@ -435,17 +592,17 @@ Example:
|
|
|
435
592
|
|
|
436
593
|
Logged in to My Store (abc123), connected to project my-project (43eba682-0c48-11f1-9bd5-827a48b0ce1e)`
|
|
437
594
|
).addOption(
|
|
438
|
-
new
|
|
595
|
+
new Option2(
|
|
439
596
|
"--store-hash <hash>",
|
|
440
597
|
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
441
598
|
).env("CATALYST_STORE_HASH")
|
|
442
599
|
).addOption(
|
|
443
|
-
new
|
|
600
|
+
new Option2(
|
|
444
601
|
"--access-token <token>",
|
|
445
602
|
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
446
603
|
).env("CATALYST_ACCESS_TOKEN")
|
|
447
604
|
).addOption(
|
|
448
|
-
new
|
|
605
|
+
new Option2("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp()
|
|
449
606
|
).action(async (options) => {
|
|
450
607
|
try {
|
|
451
608
|
const config2 = getProjectConfig();
|
|
@@ -459,7 +616,7 @@ Example:
|
|
|
459
616
|
process.exit(1);
|
|
460
617
|
return;
|
|
461
618
|
}
|
|
462
|
-
const store = await
|
|
619
|
+
const store = await fetchStoreProfile2(storeHash, accessToken, options.apiHost);
|
|
463
620
|
const projectUuid = config2.get("projectUuid");
|
|
464
621
|
if (projectUuid) {
|
|
465
622
|
const projects = await fetchProjects(storeHash, accessToken, options.apiHost);
|
|
@@ -479,7 +636,11 @@ Example:
|
|
|
479
636
|
process.exit(0);
|
|
480
637
|
} catch (error) {
|
|
481
638
|
const message = error instanceof Error ? error.message : String(error);
|
|
482
|
-
if (
|
|
639
|
+
if (error instanceof UnauthorizedError) {
|
|
640
|
+
consola.error(
|
|
641
|
+
"Not logged in: your access token is invalid or has expired. Run `catalyst auth login`."
|
|
642
|
+
);
|
|
643
|
+
} else if (message.includes("401") || message.includes("403")) {
|
|
483
644
|
consola.error(`Not logged in: invalid credentials (${message})`);
|
|
484
645
|
} else {
|
|
485
646
|
consola.error(`Failed to verify credentials: ${message}`);
|
|
@@ -487,30 +648,30 @@ Example:
|
|
|
487
648
|
process.exit(1);
|
|
488
649
|
}
|
|
489
650
|
});
|
|
490
|
-
var
|
|
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."
|
|
651
|
+
var login2 = new Command("login").configureHelp({ showGlobalOptions: true }).description(
|
|
652
|
+
"Authenticate via browser using the OAuth device code flow. Falls back to an interactive store hash + access token prompt if the browser flow is unavailable. If already logged in, displays current credentials and suggests running `catalyst auth logout` to re-authenticate."
|
|
492
653
|
).addHelpText(
|
|
493
654
|
"after",
|
|
494
655
|
`
|
|
495
656
|
Examples:
|
|
496
|
-
# Login
|
|
657
|
+
# Login interactively (browser, with manual fallback)
|
|
497
658
|
$ catalyst auth login
|
|
498
659
|
|
|
499
|
-
# Login with existing credentials (skips
|
|
660
|
+
# Login with existing credentials (skips interactive flow)
|
|
500
661
|
$ catalyst auth login --store-hash <STORE_HASH> --access-token <ACCESS_TOKEN>`
|
|
501
662
|
).addOption(
|
|
502
|
-
new
|
|
663
|
+
new Option2(
|
|
503
664
|
"--store-hash <hash>",
|
|
504
665
|
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
505
666
|
).env("CATALYST_STORE_HASH")
|
|
506
667
|
).addOption(
|
|
507
|
-
new
|
|
668
|
+
new Option2(
|
|
508
669
|
"--access-token <token>",
|
|
509
670
|
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
510
671
|
).env("CATALYST_ACCESS_TOKEN")
|
|
511
672
|
).addOption(
|
|
512
|
-
new
|
|
513
|
-
).action(async (options) => {
|
|
673
|
+
new Option2("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com").hideHelp()
|
|
674
|
+
).addOption(loginUrlOption()).action(async (options) => {
|
|
514
675
|
try {
|
|
515
676
|
const config2 = getProjectConfig();
|
|
516
677
|
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
@@ -521,30 +682,19 @@ Examples:
|
|
|
521
682
|
process.exit(0);
|
|
522
683
|
return;
|
|
523
684
|
}
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
);
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
} catch {
|
|
685
|
+
const credentials = await login(options.loginUrl, options.apiHost);
|
|
686
|
+
config2.set("storeHash", credentials.storeHash);
|
|
687
|
+
config2.set("accessToken", credentials.accessToken);
|
|
688
|
+
consola.success(`Logged in to store ${credentials.storeHash}.`);
|
|
689
|
+
process.exit(0);
|
|
690
|
+
} catch (error) {
|
|
691
|
+
if (error instanceof LoginAbortedError) {
|
|
532
692
|
consola.info(
|
|
533
|
-
|
|
693
|
+
"Login aborted. Re-run `catalyst auth login` when you have your credentials ready."
|
|
534
694
|
);
|
|
695
|
+
process.exit(0);
|
|
696
|
+
return;
|
|
535
697
|
}
|
|
536
|
-
const spinner = yoctoSpinner().start("Waiting for authentication...");
|
|
537
|
-
const credentials = await waitForDeviceToken(
|
|
538
|
-
options.loginUrl,
|
|
539
|
-
deviceCode.device_code,
|
|
540
|
-
deviceCode.interval
|
|
541
|
-
);
|
|
542
|
-
spinner.success("Authentication complete.");
|
|
543
|
-
config2.set("storeHash", credentials.store_hash);
|
|
544
|
-
config2.set("accessToken", credentials.access_token);
|
|
545
|
-
consola.success(`Logged in to store ${credentials.store_hash}.`);
|
|
546
|
-
process.exit(0);
|
|
547
|
-
} catch (error) {
|
|
548
698
|
const message = error instanceof Error ? error.message : String(error);
|
|
549
699
|
consola.error(`Login failed: ${message}`);
|
|
550
700
|
process.exit(1);
|
|
@@ -575,12 +725,12 @@ Example:
|
|
|
575
725
|
process.exit(1);
|
|
576
726
|
}
|
|
577
727
|
});
|
|
578
|
-
var auth = new Command("auth").configureHelp({ showGlobalOptions: true }).description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(
|
|
728
|
+
var auth = new Command("auth").configureHelp({ showGlobalOptions: true }).description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(login2).addCommand(logout);
|
|
579
729
|
|
|
580
730
|
// src/cli/commands/build.ts
|
|
581
|
-
import { Command as Command2, Option as
|
|
731
|
+
import { Command as Command2, Option as Option3 } from "commander";
|
|
582
732
|
import { execa } from "execa";
|
|
583
|
-
import { copyFile, cp, writeFile } from "fs/promises";
|
|
733
|
+
import { copyFile, cp, rm, writeFile } from "fs/promises";
|
|
584
734
|
import { join as join4 } from "path";
|
|
585
735
|
|
|
586
736
|
// src/cli/lib/get-module-cli-path.ts
|
|
@@ -595,12 +745,12 @@ function getModuleCliPath() {
|
|
|
595
745
|
// src/cli/lib/project-state.ts
|
|
596
746
|
import { existsSync, readFileSync } from "fs";
|
|
597
747
|
import { join as join3 } from "path";
|
|
598
|
-
import { z as
|
|
599
|
-
var projectJsonSchema =
|
|
600
|
-
projectUuid:
|
|
748
|
+
import { z as z5 } from "zod";
|
|
749
|
+
var projectJsonSchema = z5.looseObject({
|
|
750
|
+
projectUuid: z5.string().optional()
|
|
601
751
|
});
|
|
602
|
-
var packageJsonSchema =
|
|
603
|
-
dependencies:
|
|
752
|
+
var packageJsonSchema = z5.looseObject({
|
|
753
|
+
dependencies: z5.record(z5.string(), z5.string()).optional()
|
|
604
754
|
});
|
|
605
755
|
var safeReadJson = (path) => {
|
|
606
756
|
try {
|
|
@@ -697,6 +847,7 @@ async function buildCatalystProject(projectUuid) {
|
|
|
697
847
|
const openNextOutDir = join4(coreDir, ".open-next");
|
|
698
848
|
const bigcommerceDistDir = join4(coreDir, ".bigcommerce", "dist");
|
|
699
849
|
const wranglerConfig = getWranglerConfig(projectUuid);
|
|
850
|
+
await rm(bigcommerceDistDir, { recursive: true, force: true });
|
|
700
851
|
consola.start("Copying templates...");
|
|
701
852
|
await copyFile(
|
|
702
853
|
join4(getModuleCliPath(), "templates", "open-next.config.ts"),
|
|
@@ -758,7 +909,7 @@ Examples:
|
|
|
758
909
|
# Include project UUID
|
|
759
910
|
$ catalyst build --project-uuid <UUID>`
|
|
760
911
|
).addOption(
|
|
761
|
-
new
|
|
912
|
+
new Option3(
|
|
762
913
|
"--project-uuid <uuid>",
|
|
763
914
|
"Project UUID to be included in the deployment configuration."
|
|
764
915
|
).env("CATALYST_PROJECT_UUID")
|
|
@@ -783,42 +934,11 @@ Examples:
|
|
|
783
934
|
await buildCatalystProject(projectUuid);
|
|
784
935
|
});
|
|
785
936
|
|
|
786
|
-
// src/cli/commands/
|
|
787
|
-
import { Command as Command3,
|
|
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
|
-
};
|
|
937
|
+
// src/cli/commands/channel.ts
|
|
938
|
+
import { Command as Command3, Option as Option4 } from "commander";
|
|
819
939
|
|
|
820
940
|
// src/cli/lib/channels.ts
|
|
821
|
-
import { z as
|
|
941
|
+
import { z as z6 } from "zod";
|
|
822
942
|
var cliApiUrl = (origin, storeHash, path) => `${origin}/stores/${storeHash}/cli-api/v3${path}`;
|
|
823
943
|
var channelsUrl = (storeHash, apiHost, query2 = {}) => {
|
|
824
944
|
const params = new URLSearchParams(query2).toString();
|
|
@@ -829,32 +949,39 @@ var authHeaders2 = (accessToken) => ({
|
|
|
829
949
|
"X-Correlation-Id": getTelemetry().correlationId,
|
|
830
950
|
Accept: "application/json"
|
|
831
951
|
});
|
|
832
|
-
var envVarsSchema =
|
|
833
|
-
var channelSchema =
|
|
834
|
-
id:
|
|
835
|
-
name:
|
|
836
|
-
platform:
|
|
952
|
+
var envVarsSchema = z6.record(z6.string(), z6.coerce.string());
|
|
953
|
+
var channelSchema = z6.object({
|
|
954
|
+
id: z6.number(),
|
|
955
|
+
name: z6.string(),
|
|
956
|
+
platform: z6.string()
|
|
837
957
|
});
|
|
838
|
-
var channelsResponseSchema =
|
|
839
|
-
data:
|
|
958
|
+
var channelsResponseSchema = z6.object({
|
|
959
|
+
data: z6.array(channelSchema)
|
|
840
960
|
});
|
|
841
|
-
var initResponseSchema =
|
|
842
|
-
data:
|
|
843
|
-
storefront_api_token:
|
|
961
|
+
var initResponseSchema = z6.object({
|
|
962
|
+
data: z6.object({
|
|
963
|
+
storefront_api_token: z6.string(),
|
|
844
964
|
envVars: envVarsSchema
|
|
845
965
|
})
|
|
846
966
|
});
|
|
847
|
-
var createChannelResponseSchema =
|
|
848
|
-
data:
|
|
849
|
-
id:
|
|
850
|
-
storefront_api_token:
|
|
967
|
+
var createChannelResponseSchema = z6.object({
|
|
968
|
+
data: z6.object({
|
|
969
|
+
id: z6.number(),
|
|
970
|
+
storefront_api_token: z6.string(),
|
|
851
971
|
envVars: envVarsSchema
|
|
852
972
|
})
|
|
853
973
|
});
|
|
854
|
-
var eligibilityResponseSchema =
|
|
855
|
-
data:
|
|
856
|
-
eligible:
|
|
857
|
-
message:
|
|
974
|
+
var eligibilityResponseSchema = z6.object({
|
|
975
|
+
data: z6.object({
|
|
976
|
+
eligible: z6.boolean(),
|
|
977
|
+
message: z6.string()
|
|
978
|
+
})
|
|
979
|
+
});
|
|
980
|
+
var channelSiteSchema = z6.object({
|
|
981
|
+
data: z6.object({
|
|
982
|
+
id: z6.number(),
|
|
983
|
+
url: z6.string(),
|
|
984
|
+
channel_id: z6.number()
|
|
858
985
|
})
|
|
859
986
|
});
|
|
860
987
|
async function getChannelInit(channelId, storeHash, accessToken, origin) {
|
|
@@ -917,118 +1044,44 @@ async function fetchAvailableChannels(storeHash, accessToken, apiHost) {
|
|
|
917
1044
|
}
|
|
918
1045
|
}
|
|
919
1046
|
);
|
|
1047
|
+
assertAuthorized(response);
|
|
920
1048
|
if (!response.ok) {
|
|
921
1049
|
throw new Error(`GET /v3/channels failed: ${response.status} ${response.statusText}`);
|
|
922
1050
|
}
|
|
923
1051
|
return channelsResponseSchema.parse(await response.json()).data;
|
|
924
1052
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
-
}
|
|
1053
|
+
async function updateChannelSiteUrl(channelId, siteUrl, storeHash, accessToken, apiHost) {
|
|
1054
|
+
const response = await fetch(
|
|
1055
|
+
`https://${apiHost}/stores/${storeHash}/v3/channels/${channelId}/site`,
|
|
1056
|
+
{
|
|
1057
|
+
method: "PUT",
|
|
1058
|
+
headers: {
|
|
1059
|
+
"X-Auth-Token": accessToken,
|
|
1060
|
+
"Content-Type": "application/json",
|
|
1061
|
+
Accept: "application/json",
|
|
1062
|
+
"X-Correlation-Id": getTelemetry().correlationId
|
|
1063
|
+
},
|
|
1064
|
+
body: JSON.stringify({ url: siteUrl })
|
|
984
1065
|
}
|
|
985
|
-
|
|
1066
|
+
);
|
|
1067
|
+
if (response.status === 401 || response.status === 403) {
|
|
1068
|
+
throw new Error(
|
|
1069
|
+
`Failed to update channel site (${response.status}). Re-run \`catalyst auth login\` to refresh your access token with the store_channel_settings scope.`
|
|
1070
|
+
);
|
|
986
1071
|
}
|
|
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);
|
|
1072
|
+
if (!response.ok) {
|
|
1073
|
+
throw new Error(`Failed to update channel site: ${response.status} ${response.statusText}`);
|
|
1000
1074
|
}
|
|
1075
|
+
const res = await response.json();
|
|
1076
|
+
const { data } = channelSiteSchema.parse(res);
|
|
1077
|
+
return { id: data.id, url: data.url, channelId: data.channel_id };
|
|
1001
1078
|
}
|
|
1002
1079
|
|
|
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
1080
|
// src/cli/lib/commerce-hosting.ts
|
|
1028
1081
|
import { input, select, Separator } from "@inquirer/prompts";
|
|
1029
1082
|
import { colorize as colorize2 } from "consola/utils";
|
|
1030
1083
|
import {
|
|
1031
|
-
existsSync as
|
|
1084
|
+
existsSync as existsSync2,
|
|
1032
1085
|
lstatSync,
|
|
1033
1086
|
mkdirSync,
|
|
1034
1087
|
readFileSync as readFileSync2,
|
|
@@ -1036,8 +1089,8 @@ import {
|
|
|
1036
1089
|
unlinkSync,
|
|
1037
1090
|
writeFileSync
|
|
1038
1091
|
} from "fs";
|
|
1039
|
-
import { dirname as dirname2, join as
|
|
1040
|
-
import { z as
|
|
1092
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
1093
|
+
import { z as z7 } from "zod";
|
|
1041
1094
|
|
|
1042
1095
|
// src/cli/lib/sort-package-json.ts
|
|
1043
1096
|
var FIELD_ORDER = [
|
|
@@ -1067,8 +1120,8 @@ function sortPackageJsonFields(pkg) {
|
|
|
1067
1120
|
|
|
1068
1121
|
// src/cli/lib/commerce-hosting.ts
|
|
1069
1122
|
var OPENNEXT_CLOUDFLARE_VERSION = "1.17.3";
|
|
1070
|
-
var corePackageJsonSchema =
|
|
1071
|
-
dependencies:
|
|
1123
|
+
var corePackageJsonSchema = z7.looseObject({
|
|
1124
|
+
dependencies: z7.record(z7.string(), z7.string()).optional()
|
|
1072
1125
|
});
|
|
1073
1126
|
var writeJson = (path, value) => {
|
|
1074
1127
|
mkdirSync(dirname2(path), { recursive: true });
|
|
@@ -1076,7 +1129,7 @@ var writeJson = (path, value) => {
|
|
|
1076
1129
|
`);
|
|
1077
1130
|
};
|
|
1078
1131
|
var symlinkRootEnvToCore = (projectDir) => {
|
|
1079
|
-
const coreEnvPath =
|
|
1132
|
+
const coreEnvPath = join5(projectDir, "core", ".env.local");
|
|
1080
1133
|
if (lstatSync(coreEnvPath, { throwIfNoEntry: false })) return;
|
|
1081
1134
|
try {
|
|
1082
1135
|
symlinkSync("../.env.local", coreEnvPath);
|
|
@@ -1090,20 +1143,54 @@ You will need to keep .env.local and core/.env.local in sync manually.`
|
|
|
1090
1143
|
}
|
|
1091
1144
|
};
|
|
1092
1145
|
var convertProxyToMiddleware = (projectDir) => {
|
|
1093
|
-
const proxyPath =
|
|
1094
|
-
const middlewarePath =
|
|
1095
|
-
if (!
|
|
1146
|
+
const proxyPath = join5(projectDir, "core", "proxy.ts");
|
|
1147
|
+
const middlewarePath = join5(projectDir, "core", "middleware.ts");
|
|
1148
|
+
if (!existsSync2(proxyPath)) return;
|
|
1096
1149
|
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
1150
|
writeFileSync(middlewarePath, contents);
|
|
1098
1151
|
unlinkSync(proxyPath);
|
|
1099
1152
|
};
|
|
1100
|
-
var
|
|
1153
|
+
var cleanupCloudflareIncompatibilities = async (projectDir) => {
|
|
1154
|
+
const instrumentationPath = join5(projectDir, "core", "instrumentation.ts");
|
|
1155
|
+
if (!existsSync2(instrumentationPath)) return;
|
|
1156
|
+
if (!process.stdin.isTTY) {
|
|
1157
|
+
consola.warn(
|
|
1158
|
+
"core/instrumentation.ts is present and may be incompatible with the Cloudflare Workers bundle (the default @vercel/otel scaffolding throws at cold start under workerd). Skipping automatic cleanup in non-interactive mode \u2014 re-run interactively to remove it, or delete/gate it manually."
|
|
1159
|
+
);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
const shouldRemove = await consola.prompt(
|
|
1163
|
+
'Catalyst found core/instrumentation.ts, which is incompatible with the Cloudflare Workers bundle when it uses @vercel/otel (causes "Failed to prepare server" at cold start). Remove it and drop @vercel/otel from core/package.json?',
|
|
1164
|
+
{ type: "confirm", initial: true }
|
|
1165
|
+
);
|
|
1166
|
+
consola.log("");
|
|
1167
|
+
if (!shouldRemove) {
|
|
1168
|
+
consola.info(
|
|
1169
|
+
'Leaving core/instrumentation.ts in place. The Cloudflare worker will continue to log "Failed to prepare server" at cold start until this is resolved manually.'
|
|
1170
|
+
);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
unlinkSync(instrumentationPath);
|
|
1174
|
+
consola.info("Removed core/instrumentation.ts (incompatible with Cloudflare Workers).");
|
|
1175
|
+
const corePackageJsonPath = join5(projectDir, "core", "package.json");
|
|
1176
|
+
if (existsSync2(corePackageJsonPath)) {
|
|
1177
|
+
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
|
|
1178
|
+
if (pkg.dependencies && "@vercel/otel" in pkg.dependencies) {
|
|
1179
|
+
const { "@vercel/otel": _removedVercelOtel, ...preservedDeps } = pkg.dependencies;
|
|
1180
|
+
pkg.dependencies = preservedDeps;
|
|
1181
|
+
writeJson(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1182
|
+
consola.info("Dropped @vercel/otel from core/package.json.");
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
var setupCommerceHosting = async ({
|
|
1101
1187
|
projectDir,
|
|
1102
1188
|
projectUuid,
|
|
1103
1189
|
storeHash,
|
|
1104
1190
|
accessToken
|
|
1105
1191
|
}) => {
|
|
1106
|
-
|
|
1192
|
+
await cleanupCloudflareIncompatibilities(projectDir);
|
|
1193
|
+
const corePackageJsonPath = join5(projectDir, "core", "package.json");
|
|
1107
1194
|
const pkg = corePackageJsonSchema.parse(JSON.parse(readFileSync2(corePackageJsonPath, "utf-8")));
|
|
1108
1195
|
pkg.dependencies = {
|
|
1109
1196
|
...pkg.dependencies,
|
|
@@ -1116,7 +1203,7 @@ var setupCommerceHosting = ({
|
|
|
1116
1203
|
};
|
|
1117
1204
|
if (storeHash) projectJson.storeHash = storeHash;
|
|
1118
1205
|
if (accessToken) projectJson.accessToken = accessToken;
|
|
1119
|
-
writeJson(
|
|
1206
|
+
writeJson(join5(projectDir, "core", ".bigcommerce", "project.json"), projectJson);
|
|
1120
1207
|
symlinkRootEnvToCore(projectDir);
|
|
1121
1208
|
convertProxyToMiddleware(projectDir);
|
|
1122
1209
|
};
|
|
@@ -1137,7 +1224,7 @@ async function promptForNewProjectName(api) {
|
|
|
1137
1224
|
api.apiHost
|
|
1138
1225
|
);
|
|
1139
1226
|
consola.success(`Project "${data.name}" created successfully.`);
|
|
1140
|
-
return { uuid: data.uuid, name: data.name };
|
|
1227
|
+
return { uuid: data.uuid, name: data.name, deployment_hostnames: [] };
|
|
1141
1228
|
}
|
|
1142
1229
|
async function selectOrCreateInfrastructureProject(api, linkedProjectUuid) {
|
|
1143
1230
|
consola.start("Fetching projects...");
|
|
@@ -1267,7 +1354,7 @@ async function promptAndCreateCommerceHostingProject(api, takenNames, defaultNam
|
|
|
1267
1354
|
api.apiHost
|
|
1268
1355
|
);
|
|
1269
1356
|
consola.success(`Commerce Hosting project "${created.name}" created successfully`);
|
|
1270
|
-
return { uuid: created.uuid, name: created.name };
|
|
1357
|
+
return { uuid: created.uuid, name: created.name, deployment_hostnames: [] };
|
|
1271
1358
|
} catch (error) {
|
|
1272
1359
|
if (error instanceof InfrastructureProjectValidationError) {
|
|
1273
1360
|
consola.error(error.message);
|
|
@@ -1303,21 +1390,303 @@ async function autoCreateCommerceHostingProject(api, name, existingProjects, use
|
|
|
1303
1390
|
process.exit(1);
|
|
1304
1391
|
}
|
|
1305
1392
|
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 };
|
|
1393
|
+
const created = await createProject(name, api.storeHash, api.accessToken, api.apiHost);
|
|
1394
|
+
consola.success(`Commerce Hosting project "${created.name}" created successfully`);
|
|
1395
|
+
return { uuid: created.uuid, name: created.name, deployment_hostnames: [] };
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
if (error instanceof InfrastructureProjectValidationError) {
|
|
1398
|
+
consola.error(
|
|
1399
|
+
`Failed to create Commerce Hosting project "${name}": ${error.message}
|
|
1400
|
+
Re-run with a different --project-name.`
|
|
1401
|
+
);
|
|
1402
|
+
process.exit(1);
|
|
1403
|
+
}
|
|
1404
|
+
throw error;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// src/cli/lib/channel-site-flow.ts
|
|
1409
|
+
async function resolveProject(options) {
|
|
1410
|
+
const api = {
|
|
1411
|
+
storeHash: options.storeHash,
|
|
1412
|
+
accessToken: options.accessToken,
|
|
1413
|
+
apiHost: options.apiHost
|
|
1414
|
+
};
|
|
1415
|
+
if (options.projectUuid) {
|
|
1416
|
+
const projects = await fetchProjects(api.storeHash, api.accessToken, api.apiHost);
|
|
1417
|
+
const matched = projects.find((p) => p.uuid === options.projectUuid);
|
|
1418
|
+
if (matched) return matched;
|
|
1419
|
+
consola.warn(
|
|
1420
|
+
`Project ${options.projectUuid} not found on this store. Pick another to continue.`
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
return selectOrCreateInfrastructureProject(api, options.projectUuid);
|
|
1424
|
+
}
|
|
1425
|
+
async function resolveChannel(options) {
|
|
1426
|
+
if (options.channelId !== void 0) {
|
|
1427
|
+
return { id: options.channelId };
|
|
1428
|
+
}
|
|
1429
|
+
consola.start("Fetching channels...");
|
|
1430
|
+
const channels = await fetchAvailableChannels(
|
|
1431
|
+
options.storeHash,
|
|
1432
|
+
options.accessToken,
|
|
1433
|
+
options.apiHost
|
|
1434
|
+
);
|
|
1435
|
+
consola.success("Channels fetched.");
|
|
1436
|
+
const catalystChannels = channels.filter((c) => c.platform === "catalyst");
|
|
1437
|
+
if (catalystChannels.length === 0) {
|
|
1438
|
+
throw new Error(
|
|
1439
|
+
"No Catalyst channels found on this store. Create one with `catalyst create` and try again."
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
const selectedId = await consola.prompt("Select the channel to update.", {
|
|
1443
|
+
type: "select",
|
|
1444
|
+
options: catalystChannels.map((c) => ({
|
|
1445
|
+
label: c.name,
|
|
1446
|
+
value: String(c.id),
|
|
1447
|
+
hint: `id: ${c.id}`
|
|
1448
|
+
})),
|
|
1449
|
+
cancel: "reject"
|
|
1450
|
+
});
|
|
1451
|
+
const id = Number(selectedId);
|
|
1452
|
+
const matched = catalystChannels.find((c) => c.id === id);
|
|
1453
|
+
return { id, name: matched?.name };
|
|
1454
|
+
}
|
|
1455
|
+
async function resolveHostname(project2, options) {
|
|
1456
|
+
if (options.hostname) {
|
|
1457
|
+
return options.hostname;
|
|
1458
|
+
}
|
|
1459
|
+
if (project2.deployment_hostnames.length === 0) {
|
|
1460
|
+
throw new Error(
|
|
1461
|
+
`Project "${project2.name}" has no deployment hostnames yet. Run \`catalyst deploy\` first to create one.`
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
const ordered = options.preferHostname ? [
|
|
1465
|
+
...project2.deployment_hostnames.filter((h) => h === options.preferHostname),
|
|
1466
|
+
...project2.deployment_hostnames.filter((h) => h !== options.preferHostname)
|
|
1467
|
+
] : project2.deployment_hostnames;
|
|
1468
|
+
const selected = await consola.prompt("Select the hostname to point the channel at.", {
|
|
1469
|
+
type: "select",
|
|
1470
|
+
options: ordered.map((h) => ({ label: h, value: h })),
|
|
1471
|
+
cancel: "reject"
|
|
1472
|
+
});
|
|
1473
|
+
return String(selected);
|
|
1474
|
+
}
|
|
1475
|
+
async function runChannelSiteUrlFlow(options) {
|
|
1476
|
+
const project2 = await resolveProject(options);
|
|
1477
|
+
const channel2 = await resolveChannel(options);
|
|
1478
|
+
const hostname = await resolveHostname(project2, options);
|
|
1479
|
+
const siteUrl = hostname.startsWith("https://") ? hostname : `https://${hostname}`;
|
|
1480
|
+
await updateChannelSiteUrl(
|
|
1481
|
+
channel2.id,
|
|
1482
|
+
siteUrl,
|
|
1483
|
+
options.storeHash,
|
|
1484
|
+
options.accessToken,
|
|
1485
|
+
options.apiHost
|
|
1486
|
+
);
|
|
1487
|
+
const channelLabel = channel2.name ? `"${channel2.name}" (${channel2.id})` : String(channel2.id);
|
|
1488
|
+
consola.success(`Updated channel ${channelLabel} site URL to ${siteUrl}.`);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// src/cli/lib/resolve-credentials.ts
|
|
1492
|
+
function resolveCredentials(options, config2) {
|
|
1493
|
+
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
1494
|
+
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
1495
|
+
if (!storeHash || !accessToken) {
|
|
1496
|
+
consola.error("Missing credentials.");
|
|
1497
|
+
consola.info(
|
|
1498
|
+
"Run `catalyst auth login`, or provide --store-hash and --access-token flags (or set CATALYST_STORE_HASH and CATALYST_ACCESS_TOKEN environment variables)."
|
|
1499
|
+
);
|
|
1500
|
+
process.exit(1);
|
|
1501
|
+
throw new Error("Missing credentials");
|
|
1502
|
+
}
|
|
1503
|
+
return { storeHash, accessToken };
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// src/cli/commands/channel.ts
|
|
1507
|
+
var update = new Command3("update").configureHelp({ showGlobalOptions: true }).description(
|
|
1508
|
+
"Update a BigCommerce channel's site URL to point at one of your project's deployment hostnames."
|
|
1509
|
+
).addHelpText(
|
|
1510
|
+
"after",
|
|
1511
|
+
`
|
|
1512
|
+
Examples:
|
|
1513
|
+
# Pick a channel and hostname interactively
|
|
1514
|
+
$ catalyst channel update
|
|
1515
|
+
|
|
1516
|
+
# Skip both prompts
|
|
1517
|
+
$ catalyst channel update --channel-id 123 --hostname my-storefront.example.com`
|
|
1518
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
1519
|
+
new Option4(
|
|
1520
|
+
"--channel-id <id>",
|
|
1521
|
+
"Skip the channel prompt and target this channel directly."
|
|
1522
|
+
).argParser((value) => Number(value))
|
|
1523
|
+
).addOption(
|
|
1524
|
+
new Option4(
|
|
1525
|
+
"--hostname <hostname>",
|
|
1526
|
+
"Skip the hostname prompt and use this hostname directly. Must be one of the project's deployment_hostnames."
|
|
1527
|
+
)
|
|
1528
|
+
).action(async (options) => {
|
|
1529
|
+
const config2 = getProjectConfig();
|
|
1530
|
+
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1531
|
+
await getTelemetry().identify(storeHash);
|
|
1532
|
+
try {
|
|
1533
|
+
await runChannelSiteUrlFlow({
|
|
1534
|
+
storeHash,
|
|
1535
|
+
accessToken,
|
|
1536
|
+
apiHost: options.apiHost,
|
|
1537
|
+
projectUuid: options.projectUuid ?? config2.get("projectUuid"),
|
|
1538
|
+
channelId: options.channelId,
|
|
1539
|
+
hostname: options.hostname
|
|
1540
|
+
});
|
|
1541
|
+
} catch (error) {
|
|
1542
|
+
if (error instanceof NoLinkedProjectError) {
|
|
1543
|
+
consola.info(
|
|
1544
|
+
"When you're ready to create a project, run `catalyst project create` or re-run `catalyst channel update`."
|
|
1545
|
+
);
|
|
1546
|
+
process.exit(0);
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
throw error;
|
|
1550
|
+
}
|
|
1551
|
+
process.exit(0);
|
|
1552
|
+
});
|
|
1553
|
+
var channel = new Command3("channel").configureHelp({ showGlobalOptions: true }).description("Manage BigCommerce channels.").addCommand(update);
|
|
1554
|
+
|
|
1555
|
+
// src/cli/commands/create.ts
|
|
1556
|
+
import { Command as Command4, InvalidArgumentError, Option as Option5 } from "@commander-js/extra-typings";
|
|
1557
|
+
import { input as input2, select as select2 } from "@inquirer/prompts";
|
|
1558
|
+
import { execSync as execSync4 } from "child_process";
|
|
1559
|
+
import { colorize as colorize3 } from "consola/utils";
|
|
1560
|
+
import { pathExistsSync } from "fs-extra/esm";
|
|
1561
|
+
import kebabCase from "lodash.kebabcase";
|
|
1562
|
+
import { join as join9 } from "path";
|
|
1563
|
+
|
|
1564
|
+
// src/cli/lib/build-workspace-packages.ts
|
|
1565
|
+
import { execSync } from "child_process";
|
|
1566
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1567
|
+
import { join as join6 } from "path";
|
|
1568
|
+
import yoctoSpinner2 from "yocto-spinner";
|
|
1569
|
+
var WORKSPACE_PACKAGES = ["packages/catalyst", "packages/client"];
|
|
1570
|
+
var buildWorkspacePackages = (projectDir) => {
|
|
1571
|
+
const hasCore = existsSync3(join6(projectDir, "core"));
|
|
1572
|
+
const hasAllWorkspacePackages = WORKSPACE_PACKAGES.every(
|
|
1573
|
+
(pkg) => existsSync3(join6(projectDir, pkg))
|
|
1574
|
+
);
|
|
1575
|
+
if (!hasCore || !hasAllWorkspacePackages) return;
|
|
1576
|
+
WORKSPACE_PACKAGES.forEach((pkg) => {
|
|
1577
|
+
const spinner = yoctoSpinner2().start(`Building ${pkg}...`);
|
|
1578
|
+
try {
|
|
1579
|
+
execSync("pnpm build", { cwd: join6(projectDir, pkg), stdio: "ignore" });
|
|
1580
|
+
spinner.success(`Built ${pkg}.`);
|
|
1581
|
+
} catch (error) {
|
|
1582
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
1583
|
+
spinner.error(`Failed to build ${pkg}: ${message}`);
|
|
1584
|
+
throw error;
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
};
|
|
1588
|
+
|
|
1589
|
+
// src/cli/lib/clone-catalyst.ts
|
|
1590
|
+
import { execSync as execSync3 } from "child_process";
|
|
1591
|
+
|
|
1592
|
+
// src/cli/lib/checkout-ref.ts
|
|
1593
|
+
import { sync as spawnSync } from "cross-spawn";
|
|
1594
|
+
|
|
1595
|
+
// src/cli/lib/is-exec-exception.ts
|
|
1596
|
+
function isExecException(error) {
|
|
1597
|
+
return typeof error === "object" && error !== null && "stdout" in error && "stderr" in error;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
// src/cli/lib/checkout-ref.ts
|
|
1601
|
+
function checkoutRef(repoDir, ref) {
|
|
1602
|
+
try {
|
|
1603
|
+
const spawn = spawnSync("git", ["checkout", ref, "--"], {
|
|
1604
|
+
cwd: repoDir,
|
|
1605
|
+
encoding: "utf8",
|
|
1606
|
+
shell: false
|
|
1607
|
+
});
|
|
1608
|
+
const stderr = spawn.stderr.trim();
|
|
1609
|
+
if (spawn.status !== 0 && stderr) {
|
|
1610
|
+
throw new Error(stderr);
|
|
1611
|
+
}
|
|
1612
|
+
consola.success(`Checked out ref ${ref} successfully.`);
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
if (isExecException(error)) {
|
|
1615
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
1616
|
+
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`)) {
|
|
1617
|
+
consola.error(`Ref '${ref}' not found in the repository.`);
|
|
1618
|
+
} else {
|
|
1619
|
+
consola.error(`Error checking out ref '${ref}':`, stderr.trim());
|
|
1620
|
+
}
|
|
1621
|
+
} else if (error instanceof Error) {
|
|
1622
|
+
consola.error(`Error checking out ref '${ref}':`, error.message);
|
|
1623
|
+
} else {
|
|
1624
|
+
consola.error(`Unknown error occurred while checking out ref '${ref}'.`);
|
|
1625
|
+
}
|
|
1626
|
+
consola.warn(`Falling back to the default branch.`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// src/cli/lib/has-github-ssh.ts
|
|
1631
|
+
import { execSync as execSync2 } from "child_process";
|
|
1632
|
+
function hasGitHubSSH() {
|
|
1633
|
+
try {
|
|
1634
|
+
const output = execSync2("ssh -T git@github.com", {
|
|
1635
|
+
encoding: "utf8",
|
|
1636
|
+
stdio: "pipe"
|
|
1637
|
+
}).toString();
|
|
1638
|
+
return output.includes("successfully authenticated");
|
|
1309
1639
|
} catch (error) {
|
|
1310
|
-
if (error
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
)
|
|
1315
|
-
|
|
1640
|
+
if (isExecException(error)) {
|
|
1641
|
+
const stdout = error.stdout ? error.stdout.toString() : "";
|
|
1642
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
1643
|
+
const combinedOutput = stdout + stderr;
|
|
1644
|
+
if (combinedOutput.includes("successfully authenticated")) {
|
|
1645
|
+
return true;
|
|
1646
|
+
}
|
|
1316
1647
|
}
|
|
1317
|
-
|
|
1648
|
+
return false;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// src/cli/lib/reset-branch-to-ref.ts
|
|
1653
|
+
import { sync as spawnSync2 } from "cross-spawn";
|
|
1654
|
+
function resetBranchToRef(projectDir, ghRef) {
|
|
1655
|
+
const spawn = spawnSync2("git", ["reset", "--hard", ghRef, "--"], {
|
|
1656
|
+
cwd: projectDir,
|
|
1657
|
+
encoding: "utf8",
|
|
1658
|
+
shell: false
|
|
1659
|
+
});
|
|
1660
|
+
const stderr = spawn.stderr.trim();
|
|
1661
|
+
if (spawn.status !== 0 && stderr) {
|
|
1662
|
+
throw new Error(stderr);
|
|
1318
1663
|
}
|
|
1319
1664
|
}
|
|
1320
1665
|
|
|
1666
|
+
// src/cli/lib/clone-catalyst.ts
|
|
1667
|
+
var cloneCatalyst = ({
|
|
1668
|
+
repository,
|
|
1669
|
+
projectName,
|
|
1670
|
+
projectDir,
|
|
1671
|
+
ghRef,
|
|
1672
|
+
resetMain = false
|
|
1673
|
+
}) => {
|
|
1674
|
+
const useSSH = hasGitHubSSH();
|
|
1675
|
+
consola.info(`Cloning ${repository} using ${useSSH ? "SSH" : "HTTPS"}...`);
|
|
1676
|
+
const cloneCommand = `git clone ${useSSH ? `git@github.com:${repository}` : `https://github.com/${repository}`}.git${projectName ? ` ${projectName}` : ""}`;
|
|
1677
|
+
execSync3(cloneCommand, { stdio: "inherit" });
|
|
1678
|
+
execSync3("git remote rename origin upstream", { cwd: projectDir, stdio: "inherit" });
|
|
1679
|
+
if (ghRef) {
|
|
1680
|
+
if (resetMain) {
|
|
1681
|
+
execSync3("git checkout -b main", { cwd: projectDir, stdio: "inherit" });
|
|
1682
|
+
resetBranchToRef(projectDir, ghRef);
|
|
1683
|
+
consola.success(`Reset main to ${ghRef} successfully.`);
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
checkoutRef(projectDir, ghRef);
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
|
|
1321
1690
|
// src/cli/lib/install-dependencies.ts
|
|
1322
1691
|
import { installDependencies as installDeps } from "nypm";
|
|
1323
1692
|
import yoctoSpinner3 from "yocto-spinner";
|
|
@@ -1334,7 +1703,7 @@ var installDependencies = async (projectDir) => {
|
|
|
1334
1703
|
};
|
|
1335
1704
|
|
|
1336
1705
|
// src/cli/lib/localization.ts
|
|
1337
|
-
import { z as
|
|
1706
|
+
import { z as z8 } from "zod";
|
|
1338
1707
|
var allowedLocales = [
|
|
1339
1708
|
"en",
|
|
1340
1709
|
"da",
|
|
@@ -1356,13 +1725,13 @@ var allowedLocales = [
|
|
|
1356
1725
|
"pt-BR",
|
|
1357
1726
|
"sv"
|
|
1358
1727
|
];
|
|
1359
|
-
var AvailableLocalesSuccessSchema =
|
|
1360
|
-
data:
|
|
1361
|
-
|
|
1362
|
-
id:
|
|
1363
|
-
name:
|
|
1364
|
-
fallback:
|
|
1365
|
-
is_supported:
|
|
1728
|
+
var AvailableLocalesSuccessSchema = z8.object({
|
|
1729
|
+
data: z8.array(
|
|
1730
|
+
z8.object({
|
|
1731
|
+
id: z8.string(),
|
|
1732
|
+
name: z8.string(),
|
|
1733
|
+
fallback: z8.string().nullable(),
|
|
1734
|
+
is_supported: z8.boolean()
|
|
1366
1735
|
})
|
|
1367
1736
|
)
|
|
1368
1737
|
});
|
|
@@ -1378,6 +1747,7 @@ var getAvailableLocales = async (storeHash, accessToken, apiHost) => {
|
|
|
1378
1747
|
}
|
|
1379
1748
|
}
|
|
1380
1749
|
);
|
|
1750
|
+
assertAuthorized(response);
|
|
1381
1751
|
if (!response.ok) {
|
|
1382
1752
|
throw new Error(
|
|
1383
1753
|
`GET /v3/settings/store/available-locales failed: ${response.status} ${response.statusText}`
|
|
@@ -1386,41 +1756,13 @@ var getAvailableLocales = async (storeHash, accessToken, apiHost) => {
|
|
|
1386
1756
|
return AvailableLocalesSuccessSchema.parse(await response.json()).data.filter(({ id }) => allowedLocales.includes(id)).map(({ name, id }) => ({ name: `${name} (${id})`, value: id }));
|
|
1387
1757
|
};
|
|
1388
1758
|
|
|
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
1759
|
// src/cli/lib/setup-core-project.ts
|
|
1418
1760
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1419
1761
|
import { dirname as dirname3, join as join7 } from "path";
|
|
1420
|
-
import { z as
|
|
1421
|
-
var corePackageJsonSchema2 =
|
|
1422
|
-
scripts:
|
|
1423
|
-
dependencies:
|
|
1762
|
+
import { z as z9 } from "zod";
|
|
1763
|
+
var corePackageJsonSchema2 = z9.looseObject({
|
|
1764
|
+
scripts: z9.record(z9.string(), z9.string()).optional(),
|
|
1765
|
+
dependencies: z9.record(z9.string(), z9.string()).optional()
|
|
1424
1766
|
});
|
|
1425
1767
|
var writeJson2 = (path, value) => {
|
|
1426
1768
|
mkdirSync2(dirname3(path), { recursive: true });
|
|
@@ -1443,32 +1785,6 @@ var setupCoreProject = (projectDir) => {
|
|
|
1443
1785
|
writeJson2(corePackageJsonPath, sortPackageJsonFields(pkg));
|
|
1444
1786
|
};
|
|
1445
1787
|
|
|
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
1788
|
// src/cli/lib/write-env.ts
|
|
1473
1789
|
import { outputFileSync } from "fs-extra/esm";
|
|
1474
1790
|
import { join as join8 } from "path";
|
|
@@ -1515,7 +1831,7 @@ async function handleChannelCreation(storeHash, accessToken, apiHost, cliApiOrig
|
|
|
1515
1831
|
choices: availableLocales,
|
|
1516
1832
|
theme: {
|
|
1517
1833
|
style: {
|
|
1518
|
-
help: () =>
|
|
1834
|
+
help: () => colorize3("dim", "(Select locale from the list or start typing the name)")
|
|
1519
1835
|
}
|
|
1520
1836
|
}
|
|
1521
1837
|
});
|
|
@@ -1631,7 +1947,7 @@ function checkRequiredTools() {
|
|
|
1631
1947
|
process.exit(1);
|
|
1632
1948
|
}
|
|
1633
1949
|
}
|
|
1634
|
-
var create = new
|
|
1950
|
+
var create = new Command4("create").configureHelp({ showGlobalOptions: true }).description("Scaffold and connect a Catalyst storefront to your BigCommerce store.").addHelpText(
|
|
1635
1951
|
"after",
|
|
1636
1952
|
`
|
|
1637
1953
|
Examples:
|
|
@@ -1652,7 +1968,7 @@ Examples:
|
|
|
1652
1968
|
"Arbitrary environment variables to set in .env.local. Format: KEY=VALUE (repeatable).",
|
|
1653
1969
|
parseEnvFlag
|
|
1654
1970
|
).addOption(
|
|
1655
|
-
new
|
|
1971
|
+
new Option5(
|
|
1656
1972
|
"--hosting <mode>",
|
|
1657
1973
|
'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
1974
|
).choices(["self-hosted", "commerce"])
|
|
@@ -1660,11 +1976,11 @@ Examples:
|
|
|
1660
1976
|
"--use-existing",
|
|
1661
1977
|
"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
1978
|
).addOption(
|
|
1663
|
-
new
|
|
1979
|
+
new Option5("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
|
|
1664
1980
|
).addOption(
|
|
1665
|
-
new
|
|
1981
|
+
new Option5("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL).hideHelp()
|
|
1666
1982
|
).addOption(
|
|
1667
|
-
new
|
|
1983
|
+
new Option5("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
|
|
1668
1984
|
).action(async (options) => {
|
|
1669
1985
|
const { ghRef, repository } = options;
|
|
1670
1986
|
if (options.useExisting && options.hosting !== "commerce") {
|
|
@@ -1681,9 +1997,21 @@ Examples:
|
|
|
1681
1997
|
let storefrontToken = options.storefrontToken;
|
|
1682
1998
|
let envVars = {};
|
|
1683
1999
|
if (!storeHash || !accessToken) {
|
|
1684
|
-
const
|
|
1685
|
-
|
|
1686
|
-
|
|
2000
|
+
const apiHost = `api.${options.bigcommerceHostname}`;
|
|
2001
|
+
try {
|
|
2002
|
+
const credentials = await login(options.loginUrl, apiHost);
|
|
2003
|
+
storeHash = credentials.storeHash;
|
|
2004
|
+
accessToken = credentials.accessToken;
|
|
2005
|
+
} catch (error) {
|
|
2006
|
+
if (error instanceof LoginAbortedError) {
|
|
2007
|
+
consola.info(
|
|
2008
|
+
"Login aborted. Re-run `catalyst create` when you have your credentials ready."
|
|
2009
|
+
);
|
|
2010
|
+
process.exit(0);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
throw error;
|
|
2014
|
+
}
|
|
1687
2015
|
}
|
|
1688
2016
|
const useCommerceHosting = options.hosting === "commerce";
|
|
1689
2017
|
await getTelemetry().identify(storeHash);
|
|
@@ -1769,7 +2097,7 @@ Examples:
|
|
|
1769
2097
|
cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
|
|
1770
2098
|
setupCoreProject(projectDir);
|
|
1771
2099
|
if (useCommerceHosting && commerceHostingProject && storeHash && accessToken) {
|
|
1772
|
-
setupCommerceHosting({
|
|
2100
|
+
await setupCommerceHosting({
|
|
1773
2101
|
projectDir,
|
|
1774
2102
|
projectUuid: commerceHostingProject.uuid,
|
|
1775
2103
|
storeHash,
|
|
@@ -1789,10 +2117,10 @@ Examples:
|
|
|
1789
2117
|
}
|
|
1790
2118
|
consola.success(`Created '${projectName}' at '${projectDir}'`);
|
|
1791
2119
|
consola.info("Next steps:");
|
|
1792
|
-
consola.info(
|
|
2120
|
+
consola.info(colorize3("yellow", ` cd ${projectName}/core && pnpm run dev`));
|
|
1793
2121
|
if (useCommerceHosting) {
|
|
1794
2122
|
consola.info(
|
|
1795
|
-
|
|
2123
|
+
colorize3(
|
|
1796
2124
|
"yellow",
|
|
1797
2125
|
` Run 'cd ${projectName}/core && pnpm run deploy' when ready to deploy to Commerce Hosting.`
|
|
1798
2126
|
)
|
|
@@ -1802,12 +2130,12 @@ Examples:
|
|
|
1802
2130
|
|
|
1803
2131
|
// src/cli/commands/deploy.ts
|
|
1804
2132
|
import AdmZip from "adm-zip";
|
|
1805
|
-
import { Command as
|
|
1806
|
-
import { colorize as
|
|
2133
|
+
import { Command as Command5, Option as Option6 } from "commander";
|
|
2134
|
+
import { colorize as colorize4 } from "consola/utils";
|
|
1807
2135
|
import { access, readdir, readFile } from "fs/promises";
|
|
1808
2136
|
import { dirname as dirname4, join as join10 } from "path";
|
|
1809
|
-
import
|
|
1810
|
-
import { z as
|
|
2137
|
+
import yoctoSpinner4 from "yocto-spinner";
|
|
2138
|
+
import { z as z10 } from "zod";
|
|
1811
2139
|
|
|
1812
2140
|
// src/cli/lib/deployment-errors.ts
|
|
1813
2141
|
var ERROR_MESSAGES = {
|
|
@@ -1825,23 +2153,40 @@ function getDeploymentErrorMessage(code) {
|
|
|
1825
2153
|
return ERROR_MESSAGES[code] ?? `Deployment failed with an unexpected error (code: ${code}). Please try again. If the issue persists, contact support.`;
|
|
1826
2154
|
}
|
|
1827
2155
|
|
|
1828
|
-
// src/cli/lib/
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
const
|
|
1832
|
-
if (
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
2156
|
+
// src/cli/lib/env-config.ts
|
|
2157
|
+
var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2158
|
+
var parseEnvAssignment = (input3) => {
|
|
2159
|
+
const separatorIndex = input3.indexOf("=");
|
|
2160
|
+
if (separatorIndex === -1) {
|
|
2161
|
+
throw new Error(`Invalid env var format: ${input3}. Expected format: KEY=VALUE`);
|
|
2162
|
+
}
|
|
2163
|
+
const key = input3.slice(0, separatorIndex).trim();
|
|
2164
|
+
const value = input3.slice(separatorIndex + 1).trim();
|
|
2165
|
+
if (!key || !value) {
|
|
2166
|
+
throw new Error(`Invalid env var format: ${input3}. Expected format: KEY=VALUE`);
|
|
2167
|
+
}
|
|
2168
|
+
if (!ENV_KEY_PATTERN.test(key)) {
|
|
2169
|
+
throw new Error(
|
|
2170
|
+
`Invalid env var name: ${key}. Names must start with a letter or underscore and contain only letters, numbers, and underscores.`
|
|
1836
2171
|
);
|
|
1837
|
-
process.exit(1);
|
|
1838
|
-
throw new Error("Missing credentials");
|
|
1839
2172
|
}
|
|
1840
|
-
return {
|
|
1841
|
-
}
|
|
2173
|
+
return { key, value };
|
|
2174
|
+
};
|
|
2175
|
+
var getStoredEnv = (config2) => {
|
|
2176
|
+
return config2.get("env") ?? {};
|
|
2177
|
+
};
|
|
2178
|
+
var toDeploymentSecrets = (envVars) => {
|
|
2179
|
+
return Object.entries(envVars).map(([key, value]) => ({ type: "secret", key, value }));
|
|
2180
|
+
};
|
|
2181
|
+
var mergeDeploymentSecrets = (persisted, flagSecrets) => {
|
|
2182
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2183
|
+
persisted.forEach((secret) => byKey.set(secret.key, secret));
|
|
2184
|
+
flagSecrets.forEach((secret) => byKey.set(secret.key, secret));
|
|
2185
|
+
return Array.from(byKey.values());
|
|
2186
|
+
};
|
|
1842
2187
|
|
|
1843
2188
|
// src/cli/commands/deploy.ts
|
|
1844
|
-
var stepsEnum =
|
|
2189
|
+
var stepsEnum = z10.enum([
|
|
1845
2190
|
"initializing",
|
|
1846
2191
|
"downloading",
|
|
1847
2192
|
"unzipping",
|
|
@@ -1859,35 +2204,35 @@ var STEPS = {
|
|
|
1859
2204
|
finalizing: "Finalizing...",
|
|
1860
2205
|
complete: "Complete"
|
|
1861
2206
|
};
|
|
1862
|
-
var UploadSignatureSchema =
|
|
1863
|
-
data:
|
|
1864
|
-
upload_url:
|
|
1865
|
-
upload_uuid:
|
|
2207
|
+
var UploadSignatureSchema = z10.object({
|
|
2208
|
+
data: z10.object({
|
|
2209
|
+
upload_url: z10.url(),
|
|
2210
|
+
upload_uuid: z10.string()
|
|
1866
2211
|
})
|
|
1867
2212
|
});
|
|
1868
|
-
var CreateDeploymentSchema =
|
|
1869
|
-
data:
|
|
1870
|
-
deployment_uuid:
|
|
2213
|
+
var CreateDeploymentSchema = z10.object({
|
|
2214
|
+
data: z10.object({
|
|
2215
|
+
deployment_uuid: z10.uuid()
|
|
1871
2216
|
})
|
|
1872
2217
|
});
|
|
1873
|
-
var ProjectSchema =
|
|
1874
|
-
data:
|
|
1875
|
-
|
|
1876
|
-
uuid:
|
|
1877
|
-
name:
|
|
2218
|
+
var ProjectSchema = z10.object({
|
|
2219
|
+
data: z10.array(
|
|
2220
|
+
z10.object({
|
|
2221
|
+
uuid: z10.uuid(),
|
|
2222
|
+
name: z10.string()
|
|
1878
2223
|
})
|
|
1879
2224
|
)
|
|
1880
2225
|
});
|
|
1881
|
-
var DeploymentStatusSchema =
|
|
1882
|
-
deployment_uuid:
|
|
1883
|
-
deployment_status:
|
|
1884
|
-
event:
|
|
2226
|
+
var DeploymentStatusSchema = z10.object({
|
|
2227
|
+
deployment_uuid: z10.uuid(),
|
|
2228
|
+
deployment_status: z10.enum(["queued", "in_progress", "failed", "completed"]),
|
|
2229
|
+
event: z10.object({
|
|
1885
2230
|
step: stepsEnum,
|
|
1886
|
-
progress:
|
|
2231
|
+
progress: z10.number()
|
|
1887
2232
|
}).nullable(),
|
|
1888
|
-
|
|
1889
|
-
error:
|
|
1890
|
-
code:
|
|
2233
|
+
deployment_hostnames: z10.array(z10.string()).optional(),
|
|
2234
|
+
error: z10.object({
|
|
2235
|
+
code: z10.number()
|
|
1891
2236
|
}).optional()
|
|
1892
2237
|
});
|
|
1893
2238
|
var generateBundleZip = async () => {
|
|
@@ -1924,6 +2269,7 @@ var generateUploadSignature = async (storeHash, accessToken, apiHost) => {
|
|
|
1924
2269
|
body: JSON.stringify({})
|
|
1925
2270
|
}
|
|
1926
2271
|
);
|
|
2272
|
+
assertAuthorized(response);
|
|
1927
2273
|
if (!response.ok) {
|
|
1928
2274
|
throw new Error(`Failed to fetch upload signature: ${response.status} ${response.statusText}`);
|
|
1929
2275
|
}
|
|
@@ -1951,41 +2297,14 @@ var uploadBundleZip = async (uploadUrl) => {
|
|
|
1951
2297
|
};
|
|
1952
2298
|
var parseEnvironmentVariables = (secretOption) => {
|
|
1953
2299
|
return secretOption?.map((envVar) => {
|
|
1954
|
-
const
|
|
1955
|
-
if (!key || !value) {
|
|
1956
|
-
throw new Error(`Invalid secret format: ${envVar}. Expected format: KEY=VALUE`);
|
|
1957
|
-
}
|
|
2300
|
+
const { key, value } = parseEnvAssignment(envVar);
|
|
1958
2301
|
return {
|
|
1959
2302
|
type: "secret",
|
|
1960
|
-
key
|
|
1961
|
-
value
|
|
2303
|
+
key,
|
|
2304
|
+
value
|
|
1962
2305
|
};
|
|
1963
2306
|
});
|
|
1964
2307
|
};
|
|
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
|
-
};
|
|
1989
2308
|
var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, apiHost, environmentVariables) => {
|
|
1990
2309
|
consola.info("Creating deployment...");
|
|
1991
2310
|
const response = await fetch(
|
|
@@ -2005,6 +2324,7 @@ var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, a
|
|
|
2005
2324
|
})
|
|
2006
2325
|
}
|
|
2007
2326
|
);
|
|
2327
|
+
assertAuthorized(response);
|
|
2008
2328
|
if (!response.ok) {
|
|
2009
2329
|
throw new Error(`Failed to create deployment: ${response.status} ${response.statusText}`);
|
|
2010
2330
|
}
|
|
@@ -2015,7 +2335,7 @@ var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, a
|
|
|
2015
2335
|
};
|
|
2016
2336
|
var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost) => {
|
|
2017
2337
|
consola.info("Fetching deployment status...");
|
|
2018
|
-
const spinner =
|
|
2338
|
+
const spinner = yoctoSpinner4().start("Fetching...");
|
|
2019
2339
|
const response = await fetch(
|
|
2020
2340
|
`https://${apiHost}/stores/${storeHash}/v3/infrastructure/deployments/${deploymentUuid}/events`,
|
|
2021
2341
|
{
|
|
@@ -2028,6 +2348,7 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
2028
2348
|
}
|
|
2029
2349
|
}
|
|
2030
2350
|
);
|
|
2351
|
+
assertAuthorized(response);
|
|
2031
2352
|
if (!response.ok) {
|
|
2032
2353
|
throw new Error(`Failed to open event stream: ${response.status} ${response.statusText}`);
|
|
2033
2354
|
}
|
|
@@ -2037,7 +2358,7 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
2037
2358
|
}
|
|
2038
2359
|
const decoder = new TextDecoder();
|
|
2039
2360
|
let done = false;
|
|
2040
|
-
let
|
|
2361
|
+
let deploymentHostname;
|
|
2041
2362
|
while (!done) {
|
|
2042
2363
|
const { value, done: streamDone } = await reader.read();
|
|
2043
2364
|
let json;
|
|
@@ -2060,18 +2381,20 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
2060
2381
|
if (data.event && STEPS[data.event.step] !== spinner.text) {
|
|
2061
2382
|
spinner.text = STEPS[data.event.step];
|
|
2062
2383
|
}
|
|
2063
|
-
if (data.
|
|
2064
|
-
|
|
2384
|
+
if (data.deployment_hostnames && data.deployment_hostnames.length > 0) {
|
|
2385
|
+
deploymentHostname = data.deployment_hostnames[0];
|
|
2065
2386
|
}
|
|
2066
2387
|
});
|
|
2067
2388
|
}
|
|
2068
2389
|
done = streamDone;
|
|
2069
2390
|
}
|
|
2070
2391
|
spinner.success("Deployment completed successfully.");
|
|
2071
|
-
if (
|
|
2072
|
-
|
|
2073
|
-
|
|
2392
|
+
if (deploymentHostname) {
|
|
2393
|
+
consola.success(
|
|
2394
|
+
`View your deployment at: ${colorize4("blue", `https://${deploymentHostname}`)}`
|
|
2395
|
+
);
|
|
2074
2396
|
}
|
|
2397
|
+
return deploymentHostname;
|
|
2075
2398
|
};
|
|
2076
2399
|
var fetchProject = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
2077
2400
|
const response = await fetch(
|
|
@@ -2086,6 +2409,7 @@ var fetchProject = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
|
2086
2409
|
}
|
|
2087
2410
|
}
|
|
2088
2411
|
);
|
|
2412
|
+
assertAuthorized(response);
|
|
2089
2413
|
if (!response.ok) {
|
|
2090
2414
|
throw new Error(`Failed to fetch projects: ${response.status} ${response.statusText}`);
|
|
2091
2415
|
}
|
|
@@ -2093,15 +2417,21 @@ var fetchProject = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
|
2093
2417
|
const { data } = ProjectSchema.parse(res);
|
|
2094
2418
|
return data.find((project2) => project2.uuid === projectUuid);
|
|
2095
2419
|
};
|
|
2096
|
-
var deploy = new
|
|
2420
|
+
var deploy = new Command5("deploy").configureHelp({ showGlobalOptions: true }).description("Deploy your application to Cloudflare.").addHelpText(
|
|
2097
2421
|
"after",
|
|
2098
2422
|
`
|
|
2423
|
+
Environment variables saved with \`catalyst env add\` are sent automatically on every deploy.
|
|
2424
|
+
Use \`--secret\` to set or override a variable for a single run.
|
|
2425
|
+
|
|
2099
2426
|
Example:
|
|
2100
2427
|
$ 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()).
|
|
2102
|
-
|
|
2428
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).option(
|
|
2429
|
+
"--update-site-url",
|
|
2430
|
+
"After a successful deploy, prompt to update a channel's site URL to the new hostname."
|
|
2431
|
+
).addOption(
|
|
2432
|
+
new Option6(
|
|
2103
2433
|
"--secret <value>",
|
|
2104
|
-
"Secret to set for
|
|
2434
|
+
"Secret to set for this deployment (repeatable). Overrides a stored value for the same key. Format: --secret KEY=VALUE"
|
|
2105
2435
|
).argParser((value, previous = []) => {
|
|
2106
2436
|
return previous.concat([value]);
|
|
2107
2437
|
})
|
|
@@ -2167,9 +2497,11 @@ Example:
|
|
|
2167
2497
|
process.exit(0);
|
|
2168
2498
|
}
|
|
2169
2499
|
const projectDir = dirname4(process.cwd());
|
|
2170
|
-
setupCommerceHosting({ projectDir, projectUuid, storeHash, accessToken });
|
|
2500
|
+
await setupCommerceHosting({ projectDir, projectUuid, storeHash, accessToken });
|
|
2171
2501
|
consola.success("Commerce Hosting setup complete.");
|
|
2172
2502
|
await installDependencies(projectDir);
|
|
2503
|
+
} else {
|
|
2504
|
+
await cleanupCloudflareIncompatibilities(dirname4(process.cwd()));
|
|
2173
2505
|
}
|
|
2174
2506
|
if (options.prebuilt) {
|
|
2175
2507
|
const distDir = join10(process.cwd(), ".bigcommerce", "dist");
|
|
@@ -2201,7 +2533,10 @@ Example:
|
|
|
2201
2533
|
}
|
|
2202
2534
|
const uploadSignature = await generateUploadSignature(storeHash, accessToken, options.apiHost);
|
|
2203
2535
|
await uploadBundleZip(uploadSignature.upload_url);
|
|
2204
|
-
const
|
|
2536
|
+
const flagSecrets = parseEnvironmentVariables(options.secret) ?? [];
|
|
2537
|
+
const persistedSecrets = toDeploymentSecrets(getStoredEnv(config2));
|
|
2538
|
+
const mergedSecrets = mergeDeploymentSecrets(persistedSecrets, flagSecrets);
|
|
2539
|
+
const environmentVariables = mergedSecrets.length > 0 ? mergedSecrets : void 0;
|
|
2205
2540
|
const { deployment_uuid: deploymentUuid } = await createDeployment(
|
|
2206
2541
|
projectUuid,
|
|
2207
2542
|
uploadSignature.upload_uuid,
|
|
@@ -2210,13 +2545,109 @@ Example:
|
|
|
2210
2545
|
options.apiHost,
|
|
2211
2546
|
environmentVariables
|
|
2212
2547
|
);
|
|
2213
|
-
await getDeploymentStatus(
|
|
2548
|
+
const deploymentHostname = await getDeploymentStatus(
|
|
2549
|
+
deploymentUuid,
|
|
2550
|
+
storeHash,
|
|
2551
|
+
accessToken,
|
|
2552
|
+
options.apiHost
|
|
2553
|
+
);
|
|
2554
|
+
if (!options.updateSiteUrl) {
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
try {
|
|
2558
|
+
await runChannelSiteUrlFlow({
|
|
2559
|
+
storeHash,
|
|
2560
|
+
accessToken,
|
|
2561
|
+
apiHost: options.apiHost,
|
|
2562
|
+
projectUuid,
|
|
2563
|
+
preferHostname: deploymentHostname
|
|
2564
|
+
});
|
|
2565
|
+
} catch (error) {
|
|
2566
|
+
consola.warn(
|
|
2567
|
+
`Failed to update channel site URL: ${error instanceof Error ? error.message : String(error)}`
|
|
2568
|
+
);
|
|
2569
|
+
consola.info(
|
|
2570
|
+
"Update it manually in the control panel, or re-run `catalyst auth login` if the token is missing the store_channel_settings scope."
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
});
|
|
2574
|
+
|
|
2575
|
+
// src/cli/commands/env.ts
|
|
2576
|
+
import { Command as Command6 } from "commander";
|
|
2577
|
+
var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
2578
|
+
var add = new Command6("add").configureHelp({ showGlobalOptions: true }).description(
|
|
2579
|
+
"Add or update one or more deployment environment variables. Stored in .bigcommerce/project.json and sent as secrets on every `catalyst deploy`."
|
|
2580
|
+
).argument("<vars...>", "One or more environment variables in KEY=VALUE format.").addHelpText(
|
|
2581
|
+
"after",
|
|
2582
|
+
`
|
|
2583
|
+
Example:
|
|
2584
|
+
$ catalyst env add BIGCOMMERCE_STORE_HASH=abc123 BIGCOMMERCE_STOREFRONT_TOKEN=ey...`
|
|
2585
|
+
).action((vars) => {
|
|
2586
|
+
const config2 = getProjectConfig();
|
|
2587
|
+
const stored = { ...getStoredEnv(config2) };
|
|
2588
|
+
const parsed = vars.map((entry) => parseEnvAssignment(entry));
|
|
2589
|
+
parsed.forEach(({ key, value }) => {
|
|
2590
|
+
stored[key] = value;
|
|
2591
|
+
});
|
|
2592
|
+
config2.set("env", stored);
|
|
2593
|
+
const keys = parsed.map(({ key }) => key);
|
|
2594
|
+
consola.success(
|
|
2595
|
+
`Saved ${keys.length} environment variable${keys.length === 1 ? "" : "s"} to .bigcommerce/project.json:`
|
|
2596
|
+
);
|
|
2597
|
+
keys.forEach((key) => consola.log(` ${key}=${MASK}`));
|
|
2598
|
+
process.exit(0);
|
|
2599
|
+
});
|
|
2600
|
+
var remove = new Command6("remove").configureHelp({ showGlobalOptions: true }).description("Remove one or more stored deployment environment variables.").argument("<keys...>", "One or more environment variable names to remove.").addHelpText(
|
|
2601
|
+
"after",
|
|
2602
|
+
`
|
|
2603
|
+
Example:
|
|
2604
|
+
$ catalyst env remove BIGCOMMERCE_STORE_HASH BIGCOMMERCE_STOREFRONT_TOKEN`
|
|
2605
|
+
).action((keys) => {
|
|
2606
|
+
const config2 = getProjectConfig();
|
|
2607
|
+
const stored = getStoredEnv(config2);
|
|
2608
|
+
const toRemove = /* @__PURE__ */ new Set();
|
|
2609
|
+
keys.forEach((key) => {
|
|
2610
|
+
if (key in stored) {
|
|
2611
|
+
toRemove.add(key);
|
|
2612
|
+
} else {
|
|
2613
|
+
consola.warn(`No stored environment variable named "${key}". Skipping.`);
|
|
2614
|
+
}
|
|
2615
|
+
});
|
|
2616
|
+
const next = Object.fromEntries(Object.entries(stored).filter(([key]) => !toRemove.has(key)));
|
|
2617
|
+
config2.set("env", next);
|
|
2618
|
+
const removed = Array.from(toRemove);
|
|
2619
|
+
if (removed.length > 0) {
|
|
2620
|
+
consola.success(
|
|
2621
|
+
`Removed ${removed.length} environment variable${removed.length === 1 ? "" : "s"}: ${removed.join(", ")}.`
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
process.exit(0);
|
|
2625
|
+
});
|
|
2626
|
+
var list = new Command6("list").configureHelp({ showGlobalOptions: true }).description("List stored deployment environment variables (values are masked).").addHelpText(
|
|
2627
|
+
"after",
|
|
2628
|
+
`
|
|
2629
|
+
Example:
|
|
2630
|
+
$ catalyst env list`
|
|
2631
|
+
).action(() => {
|
|
2632
|
+
const config2 = getProjectConfig();
|
|
2633
|
+
const stored = getStoredEnv(config2);
|
|
2634
|
+
const keys = Object.keys(stored).sort();
|
|
2635
|
+
if (keys.length === 0) {
|
|
2636
|
+
consola.info("No environment variables stored. Add one with `catalyst env add KEY=VALUE`.");
|
|
2637
|
+
process.exit(0);
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
keys.forEach((key) => consola.log(`${key}=${MASK}`));
|
|
2641
|
+
process.exit(0);
|
|
2214
2642
|
});
|
|
2643
|
+
var env = new Command6("env").configureHelp({ showGlobalOptions: true }).description(
|
|
2644
|
+
"Manage persistent deployment environment variables. These are sent as secrets on every `catalyst deploy`, so you no longer need to pass `--secret` each time."
|
|
2645
|
+
).addCommand(add).addCommand(remove).addCommand(list);
|
|
2215
2646
|
|
|
2216
2647
|
// src/cli/commands/logs.ts
|
|
2217
|
-
import { Command as
|
|
2218
|
-
import { colorize as
|
|
2219
|
-
import { z as
|
|
2648
|
+
import { Command as Command7, Option as Option7 } from "commander";
|
|
2649
|
+
import { colorize as colorize5 } from "consola/utils";
|
|
2650
|
+
import { z as z11 } from "zod";
|
|
2220
2651
|
var telemetry = new Telemetry();
|
|
2221
2652
|
var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
|
|
2222
2653
|
var MAX_RETRIES = 5;
|
|
@@ -2227,23 +2658,23 @@ var LEVEL_COLORS = {
|
|
|
2227
2658
|
ERROR: "red",
|
|
2228
2659
|
DEBUG: "gray"
|
|
2229
2660
|
};
|
|
2230
|
-
var LogEventSchema =
|
|
2231
|
-
uuid:
|
|
2232
|
-
project_uuid:
|
|
2233
|
-
request:
|
|
2234
|
-
method:
|
|
2235
|
-
url:
|
|
2236
|
-
status_code:
|
|
2661
|
+
var LogEventSchema = z11.object({
|
|
2662
|
+
uuid: z11.string(),
|
|
2663
|
+
project_uuid: z11.string(),
|
|
2664
|
+
request: z11.object({
|
|
2665
|
+
method: z11.string(),
|
|
2666
|
+
url: z11.string(),
|
|
2667
|
+
status_code: z11.number()
|
|
2237
2668
|
}),
|
|
2238
|
-
logs:
|
|
2239
|
-
|
|
2240
|
-
timestamp:
|
|
2241
|
-
level:
|
|
2242
|
-
messages:
|
|
2669
|
+
logs: z11.array(
|
|
2670
|
+
z11.object({
|
|
2671
|
+
timestamp: z11.string(),
|
|
2672
|
+
level: z11.string(),
|
|
2673
|
+
messages: z11.array(z11.unknown())
|
|
2243
2674
|
})
|
|
2244
2675
|
),
|
|
2245
|
-
exceptions:
|
|
2246
|
-
timestamp:
|
|
2676
|
+
exceptions: z11.array(z11.unknown()),
|
|
2677
|
+
timestamp: z11.string()
|
|
2247
2678
|
}).loose();
|
|
2248
2679
|
var StreamError = class extends Error {
|
|
2249
2680
|
constructor(message, fatal) {
|
|
@@ -2257,7 +2688,7 @@ var formatLogEvent = (event, format) => {
|
|
|
2257
2688
|
logEntries.forEach((entry) => {
|
|
2258
2689
|
const msg = formatMessages(entry.messages);
|
|
2259
2690
|
const level = entry.level.toUpperCase();
|
|
2260
|
-
const coloredLevel =
|
|
2691
|
+
const coloredLevel = colorize5(LEVEL_COLORS[level] ?? "white", level);
|
|
2261
2692
|
switch (format) {
|
|
2262
2693
|
case "short":
|
|
2263
2694
|
consola.log(msg);
|
|
@@ -2298,6 +2729,20 @@ var processLogEvent = (event, format) => {
|
|
|
2298
2729
|
consola.warn(`Failed to parse log event: ${event}`);
|
|
2299
2730
|
}
|
|
2300
2731
|
};
|
|
2732
|
+
var raceWithTimeout = async (promise, timeoutMs) => {
|
|
2733
|
+
let timeoutHandle;
|
|
2734
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
2735
|
+
timeoutHandle = setTimeout(() => resolve2({ kind: "timeout" }), timeoutMs);
|
|
2736
|
+
});
|
|
2737
|
+
try {
|
|
2738
|
+
return await Promise.race([
|
|
2739
|
+
promise.then((value) => ({ kind: "value", value })),
|
|
2740
|
+
timeoutPromise
|
|
2741
|
+
]);
|
|
2742
|
+
} finally {
|
|
2743
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2744
|
+
}
|
|
2745
|
+
};
|
|
2301
2746
|
var openLogStream = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
2302
2747
|
const response = await fetch(
|
|
2303
2748
|
`https://${apiHost}/stores/${storeHash}/v3/infrastructure/logs/${projectUuid}/tail`,
|
|
@@ -2310,6 +2755,9 @@ var openLogStream = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
|
2310
2755
|
}
|
|
2311
2756
|
}
|
|
2312
2757
|
);
|
|
2758
|
+
if (response.status === 401) {
|
|
2759
|
+
throw new StreamError(new UnauthorizedError().message, true);
|
|
2760
|
+
}
|
|
2313
2761
|
if (!response.ok) {
|
|
2314
2762
|
throw new StreamError(
|
|
2315
2763
|
`Failed to open log stream: ${response.status} ${response.statusText}`,
|
|
@@ -2322,28 +2770,46 @@ var openLogStream = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
|
2322
2770
|
}
|
|
2323
2771
|
return reader;
|
|
2324
2772
|
};
|
|
2773
|
+
var pumpUntilRotation = async (reader, format, connectionTtlMs) => {
|
|
2774
|
+
const decoder = new TextDecoder();
|
|
2775
|
+
const connectTime = Date.now();
|
|
2776
|
+
let buffer = "";
|
|
2777
|
+
let receivedData = false;
|
|
2778
|
+
while (true) {
|
|
2779
|
+
const remainingTtlMs = connectionTtlMs - (Date.now() - connectTime);
|
|
2780
|
+
if (remainingTtlMs <= 0) {
|
|
2781
|
+
void reader.cancel();
|
|
2782
|
+
return "ttl";
|
|
2783
|
+
}
|
|
2784
|
+
const readResult = await raceWithTimeout(reader.read(), remainingTtlMs);
|
|
2785
|
+
if (readResult.kind === "timeout") {
|
|
2786
|
+
void reader.cancel();
|
|
2787
|
+
return receivedData ? "ttl" : "idle-timeout";
|
|
2788
|
+
}
|
|
2789
|
+
const { value, done: streamDone } = readResult.value;
|
|
2790
|
+
if (value) {
|
|
2791
|
+
receivedData = true;
|
|
2792
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2793
|
+
const parts = buffer.split("\n\n");
|
|
2794
|
+
buffer = parts.pop() ?? "";
|
|
2795
|
+
parts.map((raw) => parseSSEEvent(raw)).filter((event) => event !== null).forEach((event) => processLogEvent(event, format));
|
|
2796
|
+
}
|
|
2797
|
+
if (streamDone) {
|
|
2798
|
+
void reader.cancel();
|
|
2799
|
+
return "stream-done";
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
};
|
|
2325
2803
|
var tailLogs = async (projectUuid, storeHash, accessToken, apiHost, format, connectionTtlMs = DEFAULT_CONNECTION_TTL_MS) => {
|
|
2326
2804
|
consola.info("Tailing logs...");
|
|
2327
2805
|
let retries = 0;
|
|
2328
2806
|
while (true) {
|
|
2329
2807
|
try {
|
|
2330
2808
|
const reader = await openLogStream(projectUuid, storeHash, accessToken, apiHost);
|
|
2331
|
-
const decoder = new TextDecoder();
|
|
2332
|
-
const connectTime = Date.now();
|
|
2333
|
-
let buffer = "";
|
|
2334
2809
|
retries = 0;
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
buffer += decoder.decode(value, { stream: true });
|
|
2339
|
-
const parts = buffer.split("\n\n");
|
|
2340
|
-
buffer = parts.pop() ?? "";
|
|
2341
|
-
parts.map((raw) => parseSSEEvent(raw)).filter((event) => event !== null).forEach((event) => processLogEvent(event, format));
|
|
2342
|
-
}
|
|
2343
|
-
if (streamDone || Date.now() - connectTime >= connectionTtlMs) {
|
|
2344
|
-
void reader.cancel();
|
|
2345
|
-
break;
|
|
2346
|
-
}
|
|
2810
|
+
const rotation = await pumpUntilRotation(reader, format, connectionTtlMs);
|
|
2811
|
+
if (rotation === "idle-timeout") {
|
|
2812
|
+
consola.warn("Log stream idle, reconnecting...");
|
|
2347
2813
|
}
|
|
2348
2814
|
} catch (error) {
|
|
2349
2815
|
if (error instanceof StreamError && error.fatal) {
|
|
@@ -2365,7 +2831,7 @@ var tailLogs = async (projectUuid, storeHash, accessToken, apiHost, format, conn
|
|
|
2365
2831
|
}
|
|
2366
2832
|
}
|
|
2367
2833
|
};
|
|
2368
|
-
var tail = new
|
|
2834
|
+
var tail = new Command7("tail").configureHelp({ showGlobalOptions: true }).description("Tail live logs from your deployed application.").addHelpText(
|
|
2369
2835
|
"after",
|
|
2370
2836
|
`
|
|
2371
2837
|
Examples:
|
|
@@ -2377,7 +2843,7 @@ Examples:
|
|
|
2377
2843
|
# Tail logs as raw JSON (useful for piping to other tools)
|
|
2378
2844
|
$ catalyst logs tail --format json`
|
|
2379
2845
|
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
2380
|
-
new
|
|
2846
|
+
new Option7("--format <format>", "Output format for log events.").choices(["json", "pretty", "default", "short", "request"]).default("default")
|
|
2381
2847
|
).action(async (options) => {
|
|
2382
2848
|
try {
|
|
2383
2849
|
const config2 = getProjectConfig();
|
|
@@ -2390,7 +2856,7 @@ Examples:
|
|
|
2390
2856
|
process.exit(1);
|
|
2391
2857
|
}
|
|
2392
2858
|
});
|
|
2393
|
-
var query = new
|
|
2859
|
+
var query = new Command7("query").configureHelp({ showGlobalOptions: true }).description("Query historical logs from your deployed application.").addHelpText(
|
|
2394
2860
|
"after",
|
|
2395
2861
|
`
|
|
2396
2862
|
Example:
|
|
@@ -2401,21 +2867,24 @@ Example:
|
|
|
2401
2867
|
consola.error("The query command is not yet implemented.");
|
|
2402
2868
|
process.exit(1);
|
|
2403
2869
|
});
|
|
2404
|
-
var logs = new
|
|
2870
|
+
var logs = new Command7("logs").configureHelp({ showGlobalOptions: true }).description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
|
|
2405
2871
|
|
|
2406
2872
|
// src/cli/commands/project.ts
|
|
2407
|
-
import { Command as
|
|
2408
|
-
import { colorize as
|
|
2873
|
+
import { Command as Command8 } from "commander";
|
|
2874
|
+
import { colorize as colorize6 } from "consola/utils";
|
|
2409
2875
|
import { dirname as dirname5 } from "path";
|
|
2410
2876
|
async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
2411
|
-
|
|
2877
|
+
const projectDir = dirname5(process.cwd());
|
|
2878
|
+
if (getProjectState().isTransformed) {
|
|
2879
|
+
await cleanupCloudflareIncompatibilities(projectDir);
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2412
2882
|
const shouldSetup = await consola.prompt(
|
|
2413
2883
|
"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
2884
|
{ type: "confirm", initial: true }
|
|
2415
2885
|
);
|
|
2416
2886
|
if (!shouldSetup) return;
|
|
2417
|
-
|
|
2418
|
-
setupCommerceHosting({
|
|
2887
|
+
await setupCommerceHosting({
|
|
2419
2888
|
projectDir,
|
|
2420
2889
|
projectUuid,
|
|
2421
2890
|
storeHash: credentials?.storeHash,
|
|
@@ -2424,7 +2893,7 @@ async function offerCommerceHostingSetup(projectUuid, credentials) {
|
|
|
2424
2893
|
consola.success("Commerce Hosting setup complete.");
|
|
2425
2894
|
await installDependencies(projectDir);
|
|
2426
2895
|
}
|
|
2427
|
-
var
|
|
2896
|
+
var list2 = new Command8("list").configureHelp({ showGlobalOptions: true }).description("List BigCommerce infrastructure projects for your store.").addHelpText(
|
|
2428
2897
|
"after",
|
|
2429
2898
|
`
|
|
2430
2899
|
Example:
|
|
@@ -2443,21 +2912,52 @@ Example:
|
|
|
2443
2912
|
}
|
|
2444
2913
|
const linkedProjectUuid = config2.get("projectUuid");
|
|
2445
2914
|
projects.forEach((p) => {
|
|
2446
|
-
const marker = p.uuid === linkedProjectUuid ? ` ${
|
|
2915
|
+
const marker = p.uuid === linkedProjectUuid ? ` ${colorize6("green", "[linked]")}` : "";
|
|
2447
2916
|
consola.log(`${p.name} (${p.uuid})${marker}`);
|
|
2917
|
+
if (p.deployment_hostnames.length === 0) {
|
|
2918
|
+
consola.log(" (not deployed)");
|
|
2919
|
+
} else {
|
|
2920
|
+
p.deployment_hostnames.forEach((hostname) => {
|
|
2921
|
+
consola.log(` ${colorize6("blue", `https://${hostname}`)}`);
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
consola.log("");
|
|
2448
2925
|
});
|
|
2449
2926
|
process.exit(0);
|
|
2450
2927
|
});
|
|
2451
|
-
var create2 = new
|
|
2452
|
-
"Create a new BigCommerce infrastructure project and link it to your local Catalyst project."
|
|
2928
|
+
var create2 = new Command8("create").configureHelp({ showGlobalOptions: true }).description(
|
|
2929
|
+
"Create a new BigCommerce infrastructure project and link it to your local Catalyst project. Walks you through logging in if you are not authenticated yet."
|
|
2453
2930
|
).addHelpText(
|
|
2454
2931
|
"after",
|
|
2455
2932
|
`
|
|
2456
2933
|
Example:
|
|
2457
2934
|
$ catalyst project create`
|
|
2458
|
-
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).action(async (options) => {
|
|
2935
|
+
).addOption(storeHashOption()).addOption(accessTokenOption()).addOption(apiHostOption()).addOption(loginUrlOption()).action(async (options) => {
|
|
2459
2936
|
const config2 = getProjectConfig();
|
|
2460
|
-
|
|
2937
|
+
let storeHash = options.storeHash ?? config2.get("storeHash");
|
|
2938
|
+
let accessToken = options.accessToken ?? config2.get("accessToken");
|
|
2939
|
+
if (!storeHash || !accessToken) {
|
|
2940
|
+
consola.info(
|
|
2941
|
+
"You're not logged in yet. Let's get you authenticated before creating a project."
|
|
2942
|
+
);
|
|
2943
|
+
try {
|
|
2944
|
+
const credentials = await login(options.loginUrl, options.apiHost);
|
|
2945
|
+
storeHash = credentials.storeHash;
|
|
2946
|
+
accessToken = credentials.accessToken;
|
|
2947
|
+
config2.set("storeHash", storeHash);
|
|
2948
|
+
config2.set("accessToken", accessToken);
|
|
2949
|
+
consola.success(`Logged in to store ${storeHash}.`);
|
|
2950
|
+
} catch (error) {
|
|
2951
|
+
if (error instanceof LoginAbortedError) {
|
|
2952
|
+
consola.info(
|
|
2953
|
+
"Login aborted. Re-run `catalyst project create` when you have your credentials ready."
|
|
2954
|
+
);
|
|
2955
|
+
process.exit(0);
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
throw error;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2461
2961
|
await getTelemetry().identify(storeHash);
|
|
2462
2962
|
const newProjectName = await consola.prompt("Enter a name for the new project:", {
|
|
2463
2963
|
type: "text"
|
|
@@ -2471,7 +2971,7 @@ Example:
|
|
|
2471
2971
|
consola.success("Project UUID written to .bigcommerce/project.json.");
|
|
2472
2972
|
process.exit(0);
|
|
2473
2973
|
});
|
|
2474
|
-
var link = new
|
|
2974
|
+
var link = new Command8("link").configureHelp({ showGlobalOptions: true }).description(
|
|
2475
2975
|
"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."
|
|
2476
2976
|
).addHelpText(
|
|
2477
2977
|
"after",
|
|
@@ -2521,7 +3021,7 @@ Examples:
|
|
|
2521
3021
|
await offerCommerceHostingSetup(selected.uuid, { storeHash, accessToken });
|
|
2522
3022
|
process.exit(0);
|
|
2523
3023
|
});
|
|
2524
|
-
var del = new
|
|
3024
|
+
var del = new Command8("delete").configureHelp({ showGlobalOptions: true }).description(
|
|
2525
3025
|
"Permanently delete a BigCommerce infrastructure project. This action is irreversible."
|
|
2526
3026
|
).addHelpText(
|
|
2527
3027
|
"after",
|
|
@@ -2557,7 +3057,7 @@ Examples:
|
|
|
2557
3057
|
type: "select",
|
|
2558
3058
|
options: [
|
|
2559
3059
|
...projects.map((p) => ({
|
|
2560
|
-
label: p.uuid === linkedProjectUuid ? `${p.name} ${
|
|
3060
|
+
label: p.uuid === linkedProjectUuid ? `${p.name} ${colorize6("green", "[linked]")}` : p.name,
|
|
2561
3061
|
value: p.uuid,
|
|
2562
3062
|
hint: p.uuid
|
|
2563
3063
|
})),
|
|
@@ -2599,14 +3099,14 @@ Examples:
|
|
|
2599
3099
|
}
|
|
2600
3100
|
process.exit(0);
|
|
2601
3101
|
});
|
|
2602
|
-
var project = new
|
|
3102
|
+
var project = new Command8("project").configureHelp({ showGlobalOptions: true }).description("Manage your BigCommerce infrastructure project.").addCommand(create2).addCommand(list2).addCommand(link).addCommand(del);
|
|
2603
3103
|
|
|
2604
3104
|
// src/cli/commands/start.ts
|
|
2605
|
-
import { Command as
|
|
3105
|
+
import { Command as Command9 } from "commander";
|
|
2606
3106
|
import { execa as execa2 } from "execa";
|
|
2607
3107
|
import { existsSync as existsSync4, lstatSync as lstatSync2, symlinkSync as symlinkSync2 } from "fs";
|
|
2608
3108
|
import { join as join11, relative } from "path";
|
|
2609
|
-
var start = new
|
|
3109
|
+
var start = new Command9("start").configureHelp({ showGlobalOptions: true }).description(
|
|
2610
3110
|
"Start a local preview of your Catalyst storefront using the OpenNext Cloudflare adapter."
|
|
2611
3111
|
).addHelpText(
|
|
2612
3112
|
"after",
|
|
@@ -2657,11 +3157,11 @@ Example:
|
|
|
2657
3157
|
});
|
|
2658
3158
|
|
|
2659
3159
|
// src/cli/commands/telemetry.ts
|
|
2660
|
-
import { Argument, Command as
|
|
2661
|
-
import { colorize as
|
|
3160
|
+
import { Argument, Command as Command10, Option as Option8 } from "commander";
|
|
3161
|
+
import { colorize as colorize7 } from "consola/utils";
|
|
2662
3162
|
var telemetryService = getTelemetry();
|
|
2663
3163
|
var isEnabled = telemetryService.isEnabled();
|
|
2664
|
-
var telemetry2 = new
|
|
3164
|
+
var telemetry2 = new Command10("telemetry").configureHelp({ showGlobalOptions: true }).description(
|
|
2665
3165
|
"View or change CLI telemetry collection status. Enabling telemetry helps BigCommerce support diagnose and troubleshoot errors you encounter when using the CLI."
|
|
2666
3166
|
).addHelpText(
|
|
2667
3167
|
"after",
|
|
@@ -2675,7 +3175,7 @@ Examples:
|
|
|
2675
3175
|
|
|
2676
3176
|
# Disable telemetry collection
|
|
2677
3177
|
$ catalyst telemetry --disable`
|
|
2678
|
-
).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new
|
|
3178
|
+
).addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new Option8("--enable", `Enables CLI telemetry collection.`).conflicts("disable")).option("--disable", `Disables CLI telemetry collection.`).action((arg, options) => {
|
|
2679
3179
|
if (options.enable || arg === "enable") {
|
|
2680
3180
|
telemetryService.setEnabled(true);
|
|
2681
3181
|
isEnabled = true;
|
|
@@ -2692,7 +3192,7 @@ Examples:
|
|
|
2692
3192
|
consola.info("Catalyst CLI Telemetry\n");
|
|
2693
3193
|
}
|
|
2694
3194
|
consola.info(
|
|
2695
|
-
`Status: ${
|
|
3195
|
+
`Status: ${colorize7("bold", isEnabled ? colorize7("green", "Enabled") : colorize7("red", "Disabled"))}`
|
|
2696
3196
|
);
|
|
2697
3197
|
if (!isEnabled) {
|
|
2698
3198
|
consola.info(
|
|
@@ -2702,8 +3202,8 @@ Examples:
|
|
|
2702
3202
|
});
|
|
2703
3203
|
|
|
2704
3204
|
// src/cli/commands/version.ts
|
|
2705
|
-
import { Command as
|
|
2706
|
-
var version = new
|
|
3205
|
+
import { Command as Command11 } from "commander";
|
|
3206
|
+
var version = new Command11("version").configureHelp({ showGlobalOptions: true }).description("Display detailed version information.").addHelpText(
|
|
2707
3207
|
"after",
|
|
2708
3208
|
`
|
|
2709
3209
|
Example:
|
|
@@ -2771,13 +3271,13 @@ if (existsSync5(defaultEnvPath)) {
|
|
|
2771
3271
|
if (!process.env.CATALYST_STORE_HASH && process.env.BIGCOMMERCE_STORE_HASH) {
|
|
2772
3272
|
process.env.CATALYST_STORE_HASH = process.env.BIGCOMMERCE_STORE_HASH;
|
|
2773
3273
|
}
|
|
2774
|
-
var program = new
|
|
2775
|
-
consola.log(
|
|
3274
|
+
var program = new Command12();
|
|
3275
|
+
consola.log(colorize8("cyanBright", `\u25E2 ${package_default.name} v${package_default.version}
|
|
2776
3276
|
`));
|
|
2777
3277
|
program.name(package_default.name).version(package_default.version).summary("CLI tool for Catalyst development").description(
|
|
2778
3278
|
"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
3279
|
).configureHelp({ showGlobalOptions: true }).addOption(
|
|
2780
|
-
new
|
|
3280
|
+
new Option9(
|
|
2781
3281
|
"--env-path <path>",
|
|
2782
3282
|
"Path to environment file to load (relative to current working directory)"
|
|
2783
3283
|
// We are using argParser, because commander loads in environment variables before executing hooks.
|
|
@@ -2793,12 +3293,12 @@ program.name(package_default.name).version(package_default.version).summary("CLI
|
|
|
2793
3293
|
const message = errCode === "ENOENT" ? `Env file not found: ${envFilePath}` : `Failed to load --env-path ${value}: ${result.error.message}`;
|
|
2794
3294
|
throw new Error(message);
|
|
2795
3295
|
}
|
|
2796
|
-
consola.log(
|
|
3296
|
+
consola.log(colorize8("cyanBright", `Loaded environment variables from ${envFilePath}
|
|
2797
3297
|
`));
|
|
2798
3298
|
}
|
|
2799
3299
|
return value;
|
|
2800
3300
|
})
|
|
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);
|
|
3301
|
+
).addCommand(version).addCommand(create).addCommand(start).addCommand(build).addCommand(deploy).addCommand(logs).addCommand(project).addCommand(env).addCommand(channel).addCommand(auth).addCommand(telemetry2).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
|
|
2802
3302
|
|
|
2803
3303
|
// src/cli/index.ts
|
|
2804
3304
|
var handleFatalError = async (error) => {
|
|
@@ -2815,6 +3315,10 @@ var handleFatalError = async (error) => {
|
|
|
2815
3315
|
await telemetry3.analytics.closeAndFlush();
|
|
2816
3316
|
} catch {
|
|
2817
3317
|
}
|
|
3318
|
+
if (error instanceof UnauthorizedError) {
|
|
3319
|
+
consola.error(errorMessage);
|
|
3320
|
+
process.exit(1);
|
|
3321
|
+
}
|
|
2818
3322
|
consola.error(errorMessage);
|
|
2819
3323
|
if (telemetry3.isEnabled()) {
|
|
2820
3324
|
consola.info(
|