@bigcommerce/create-catalyst 1.1.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,1645 +1,26 @@
1
- #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __defProps = Object.defineProperties;
4
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
- var __spreadValues = (a, b) => {
10
- for (var prop in b || (b = {}))
11
- if (__hasOwnProp.call(b, prop))
12
- __defNormalProp(a, prop, b[prop]);
13
- if (__getOwnPropSymbols)
14
- for (var prop of __getOwnPropSymbols(b)) {
15
- if (__propIsEnum.call(b, prop))
16
- __defNormalProp(a, prop, b[prop]);
17
- }
18
- return a;
19
- };
20
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
- var __objRest = (source, exclude) => {
22
- var target = {};
23
- for (var prop in source)
24
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
25
- target[prop] = source[prop];
26
- if (source != null && __getOwnPropSymbols)
27
- for (var prop of __getOwnPropSymbols(source)) {
28
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
29
- target[prop] = source[prop];
30
- }
31
- return target;
32
- };
33
- var __async = (__this, __arguments, generator) => {
34
- return new Promise((resolve, reject) => {
35
- var fulfilled = (value) => {
36
- try {
37
- step(generator.next(value));
38
- } catch (e) {
39
- reject(e);
40
- }
41
- };
42
- var rejected = (value) => {
43
- try {
44
- step(generator.throw(value));
45
- } catch (e) {
46
- reject(e);
47
- }
48
- };
49
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
50
- step((generator = generator.apply(__this, __arguments)).next());
51
- });
52
- };
53
-
54
1
  // src/index.ts
55
- import { program } from "@commander-js/extra-typings";
56
- import { colorize as colorize8 } from "consola/utils";
57
-
58
- // package.json
59
- var package_default = {
60
- name: "@bigcommerce/create-catalyst",
61
- version: "1.1.0",
62
- repository: {
63
- type: "git",
64
- url: "https://github.com/bigcommerce/catalyst",
65
- directory: "packages/create-catalyst"
66
- },
67
- type: "module",
68
- bin: "bin/index.cjs",
69
- files: [
70
- "bin",
71
- "dist"
72
- ],
73
- scripts: {
74
- dev: "tsup --watch",
75
- typecheck: "tsc --noEmit",
76
- lint: "eslint . --max-warnings 0",
77
- test: "jest",
78
- build: "tsup"
79
- },
80
- engines: {
81
- node: "^24.0.0"
82
- },
83
- dependencies: {
84
- "@commander-js/extra-typings": "^14.0.0",
85
- "@iarna/toml": "^2.2.5",
86
- "@inquirer/core": "^10.1.13",
87
- "@inquirer/figures": "^1.0.12",
88
- "@inquirer/prompts": "^7.5.3",
89
- "@inquirer/type": "^3.0.7",
90
- "@segment/analytics-node": "^2.2.1",
91
- "ansi-escapes": "^7.0.0",
92
- commander: "^14.0.0",
93
- conf: "^13.1.0",
94
- consola: "^3.4.2",
95
- "cross-spawn": "^7.0.6",
96
- dotenv: "^16.5.0",
97
- "fs-extra": "^11.3.0",
98
- giget: "^1.2.5",
99
- "lodash.kebabcase": "^4.1.1",
100
- nypm: "^0.5.4",
101
- open: "^10.1.2",
102
- ora: "^8.2.0",
103
- semver: "^7.7.2",
104
- "std-env": "^3.9.0",
105
- zod: "^3.25.51",
106
- "zod-validation-error": "^3.4.1"
107
- },
108
- devDependencies: {
109
- "@bigcommerce/eslint-config": "^2.11.0",
110
- "@bigcommerce/eslint-config-catalyst": "workspace:^",
111
- "@swc/core": "^1.11.31",
112
- "@swc/jest": "^0.2.38",
113
- "@types/cross-spawn": "^6.0.6",
114
- "@types/fs-extra": "^11.0.4",
115
- "@types/jest": "^29.5.14",
116
- "@types/lodash.kebabcase": "^4.1.9",
117
- "@types/node": "^22.15.30",
118
- "@types/prompts": "^2.4.9",
119
- "@types/semver": "^7.7.0",
120
- eslint: "^8.57.1",
121
- jest: "^29.7.0",
122
- msw: "^2.9.0",
123
- prettier: "^3.6.2",
124
- tsup: "^8.5.0",
125
- typescript: "^5.8.3"
126
- }
127
- };
128
-
129
- // src/commands/create.ts
130
- import { Command, Option } from "@commander-js/extra-typings";
131
- import { input, select } from "@inquirer/prompts";
132
- import { execSync as execSync3 } from "child_process";
133
- import { colorize as colorize4 } from "consola/utils";
134
- import { pathExistsSync } from "fs-extra/esm";
135
- import kebabCase from "lodash.kebabcase";
136
- import { join as join3 } from "path";
137
-
138
- // src/prompts/multi-select/multi-select.ts
139
- import {
140
- createPrompt,
141
- isBackspaceKey,
142
- isDownKey,
143
- isEnterKey,
144
- isNumberKey,
145
- isSpaceKey,
146
- isUpKey,
147
- makeTheme,
148
- Separator as Separator2,
149
- useEffect,
150
- useKeypress,
151
- useMemo,
152
- usePagination,
153
- usePrefix,
154
- useRef,
155
- useState,
156
- ValidationError
157
- } from "@inquirer/core";
158
- import ansiEscapes from "ansi-escapes";
159
-
160
- // src/prompts/multi-select/helpers.ts
161
- import { Separator } from "@inquirer/core";
162
- import figures from "@inquirer/figures";
163
- import { colorize } from "consola/utils";
164
- var selectTheme = {
165
- helpMode: "auto",
166
- icon: {
167
- checked: colorize("green", figures.circleFilled),
168
- unchecked: figures.circle,
169
- cursor: figures.pointer
170
- },
171
- style: {
172
- description: (text) => colorize("cyan", text),
173
- disabledChoice: (text) => colorize("dim", `- ${text}`),
174
- renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(", ")
175
- }
176
- };
177
- var isSelectable = (item) => !Separator.isSeparator(item) && !item.disabled;
178
- var isChecked = (item) => isSelectable(item) && Boolean(item.checked);
179
- var toggle = (item) => isSelectable(item) ? __spreadProps(__spreadValues({}, item), { checked: !item.checked }) : item;
180
- var normalizeChoices = (choices) => choices.map((choice) => {
181
- var _a, _b, _c, _d;
182
- if (Separator.isSeparator(choice)) {
183
- return choice;
184
- }
185
- if (typeof choice === "string") {
186
- return {
187
- checked: false,
188
- disabled: false,
189
- name: choice,
190
- short: choice,
191
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
192
- value: choice
193
- };
194
- }
195
- const name2 = (_a = choice.name) != null ? _a : String(choice.value);
196
- return {
197
- checked: (_b = choice.checked) != null ? _b : false,
198
- description: choice.description,
199
- disabled: (_c = choice.disabled) != null ? _c : false,
200
- name: name2,
201
- short: (_d = choice.short) != null ? _d : name2,
202
- value: choice.value
203
- };
204
- });
205
-
206
- // src/prompts/multi-select/multi-select.ts
207
- import { Separator as Separator3 } from "@inquirer/core";
208
- var multiSelect = createPrompt(
209
- (config, done) => {
210
- const theme = makeTheme(selectTheme, config.theme);
211
- const searchTimeoutRef = useRef();
212
- const firstRender = useRef(true);
213
- const [items, setItems] = useState(
214
- normalizeChoices(config.choices)
215
- );
216
- const [status, setStatus] = useState("idle");
217
- const [errorMsg, setError] = useState();
218
- const prefix = usePrefix({ status, theme });
219
- const { instructions, loop = true, pageSize = 7, required, validate = () => true } = config;
220
- const bounds = useMemo(() => {
221
- const first = items.findIndex(isSelectable);
222
- const last = items.findLastIndex(isSelectable);
223
- if (first === -1) {
224
- throw new ValidationError(
225
- "[multi select prompt] No selectable choices. All choices are disabled."
226
- );
227
- }
228
- return { first, last };
229
- }, [items]);
230
- const [showHelpTip, setShowHelpTip] = useState(true);
231
- const [active, setActive] = useState(bounds.first);
232
- useEffect(
233
- () => () => {
234
- clearTimeout(searchTimeoutRef.current);
235
- },
236
- []
237
- );
238
- useKeypress((key, rl) => __async(null, null, function* () {
239
- clearTimeout(searchTimeoutRef.current);
240
- if (isEnterKey(key)) {
241
- const selection = items.filter(isChecked);
242
- const isValid = yield validate([...selection]);
243
- if (required && !items.some(isChecked)) {
244
- setError("At least one choice must be selected");
245
- } else if (isValid === true) {
246
- setStatus("done");
247
- done(selection.map((choice) => choice.value));
248
- } else {
249
- setError(isValid || "You must select a valid value");
250
- }
251
- } else if (isUpKey(key) || isDownKey(key)) {
252
- rl.clearLine(0);
253
- if (loop || isUpKey(key) && active !== bounds.first || isDownKey(key) && active !== bounds.last) {
254
- const offset = isUpKey(key) ? -1 : 1;
255
- let next = active;
256
- do {
257
- next = (next + offset + items.length) % items.length;
258
- } while (!isSelectable(items[next]));
259
- setActive(next);
260
- }
261
- } else if (isNumberKey(key)) {
262
- rl.clearLine(0);
263
- const position = Number(key.name) - 1;
264
- if (isSelectable(items[position])) {
265
- setActive(position);
266
- }
267
- } else if (isBackspaceKey(key)) {
268
- rl.clearLine(0);
269
- } else if (isSpaceKey(key)) {
270
- rl.clearLine(0);
271
- setShowHelpTip(false);
272
- const nextItems = items.map((choice, i) => i === active ? toggle(choice) : choice);
273
- const selection = nextItems.filter(isChecked);
274
- const isValid = yield validate([...selection]);
275
- if (isValid === true) {
276
- setError(void 0);
277
- setItems(nextItems);
278
- } else {
279
- setError(isValid || "You must select a valid value");
280
- }
281
- } else {
282
- const searchTerm = rl.line.toLowerCase();
283
- const matchIndex = items.findIndex(
284
- (item) => Separator2.isSeparator(item) || !isSelectable(item) ? false : item.name.toLowerCase().startsWith(searchTerm)
285
- );
286
- if (matchIndex !== -1) {
287
- setActive(matchIndex);
288
- }
289
- searchTimeoutRef.current = setTimeout(() => {
290
- rl.clearLine(0);
291
- }, 700);
292
- }
293
- }));
294
- const message = theme.style.message(config.message, status);
295
- let helpTipTop = "";
296
- let helpTipBottom = "";
297
- let description;
298
- if (theme.helpMode === "always" || theme.helpMode === "auto" && showHelpTip && (instructions === void 0 || instructions)) {
299
- if (typeof instructions === "string") {
300
- helpTipTop = instructions;
301
- } else {
302
- const keys = [
303
- `${theme.style.key("space")} to select`,
304
- `${theme.style.key("enter")} to proceed`
305
- ];
306
- helpTipTop = ` (Press ${keys.filter((key) => key !== "").join(", ")})`;
307
- }
308
- if (items.length > pageSize && (theme.helpMode === "always" || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
309
- theme.helpMode === "auto" && firstRender.current)) {
310
- helpTipBottom = `
311
- ${theme.style.help("(Use arrow keys to reveal more choices)")}`;
312
- firstRender.current = false;
313
- }
314
- }
315
- const page = usePagination({
316
- items,
317
- active,
318
- renderItem({ item, isActive }) {
319
- if (Separator2.isSeparator(item)) {
320
- return ` ${item.separator}`;
321
- }
322
- if (item.disabled) {
323
- return theme.style.disabledChoice(
324
- `${item.name} ${typeof item.disabled === "string" ? item.disabled : "(disabled)"}`
325
- );
326
- }
327
- if (isActive) {
328
- description = item.description;
329
- }
330
- const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
331
- const color = isActive ? theme.style.highlight : (x) => x;
332
- const cursor = isActive ? theme.icon.cursor : ` `;
333
- return color(`${cursor}${checkbox} ${item.name}`);
334
- },
335
- pageSize,
336
- loop
337
- });
338
- if (status === "done") {
339
- return `${prefix} ${message} ${theme.style.answer(theme.style.renderSelectedChoices(items.filter(isChecked), items))}`;
340
- }
341
- const selectedChoices = items.filter(isChecked).length > 0 ? ` (${theme.style.answer(theme.style.renderSelectedChoices(items.filter(isChecked), items))})` : "";
342
- const choiceDescription = description ? `
343
- ${theme.style.description(description)}` : ``;
344
- let error = "";
345
- if (errorMsg) {
346
- error = `
347
- ${theme.style.error(errorMsg)}`;
348
- }
349
- return `${prefix} ${message}${selectedChoices}${helpTipTop}
350
- ${page}${helpTipBottom}${choiceDescription}${error}${ansiEscapes.cursorHide}`;
351
- }
352
- );
353
-
354
- // src/utils/user-agent.ts
355
- import {
356
- isDevelopment,
357
- isLinux,
358
- isMacOS,
359
- isProduction,
360
- isTest,
361
- isWindows,
362
- nodeVersion,
363
- process as process2,
364
- provider,
365
- runtime
366
- } from "std-env";
367
- var { name, version } = package_default;
368
- var getOS = () => {
369
- if (isWindows) return "Windows";
370
- if (isMacOS) return "macOS";
371
- if (isLinux) return "Linux";
372
- return "Unknown OS";
373
- };
374
- var getEnv = () => {
375
- if (isDevelopment) return "Development";
376
- if (isTest) return "Test";
377
- if (isProduction) return "Production";
378
- };
379
- var getPlatform = () => {
380
- const os = getOS();
381
- const env = getEnv();
382
- const keysOfInterest = [
383
- os,
384
- env,
385
- runtime,
386
- provider,
387
- `Node ${nodeVersion}`,
388
- process2.env.NODE_ENV
389
- ].filter(Boolean);
390
- return keysOfInterest.join("; ");
391
- };
392
- var detectedPlatform = getPlatform();
393
- var getCLIUserAgent = (platform, extensions) => {
394
- const userAgentParts = [`${name}/${version}`];
395
- const platformValue = platform != null ? platform : detectedPlatform;
396
- userAgentParts.push(`(${platformValue})`);
397
- if (extensions) {
398
- userAgentParts.push(extensions);
399
- }
400
- return userAgentParts.join(" ");
401
- };
402
-
403
- // src/utils/https.ts
404
- var Https = class {
405
- constructor({ baseUrl, accessToken }) {
406
- this.baseUrl = baseUrl;
407
- this.accessToken = accessToken;
408
- this.userAgent = getCLIUserAgent();
409
- }
410
- fetch(_0) {
411
- return __async(this, arguments, function* (path, opts = {}) {
412
- const _a = opts, { headers = {} } = _a, rest = __objRest(_a, ["headers"]);
413
- const options = __spreadValues({
414
- headers: __spreadValues(__spreadProps(__spreadValues({}, Object.fromEntries(new Headers(headers))), {
415
- Accept: "application/json",
416
- "User-Agent": this.userAgent
417
- }), this.accessToken && { "X-Auth-Token": this.accessToken })
418
- }, rest);
419
- return fetch(`${this.baseUrl}${path}`, options);
420
- });
421
- }
422
- };
423
-
424
- // src/utils/cli-api.ts
425
- var CliApi = class {
426
- constructor({ origin, storeHash, accessToken }) {
427
- this.client = new Https({
428
- baseUrl: `${origin}/stores/${storeHash}/cli-api/v3`,
429
- accessToken
430
- });
431
- }
432
- getChannelInit(channelId) {
433
- return __async(this, null, function* () {
434
- return this.client.fetch(`/channels/${channelId}/init`, {
435
- method: "GET"
436
- });
437
- });
438
- }
439
- checkEligibility() {
440
- return __async(this, null, function* () {
441
- return this.client.fetch("/channels/catalyst/eligibility", {
442
- method: "GET"
443
- });
444
- });
445
- }
446
- createChannel(name2, storefrontLocale, additionalLocales, installSampleData = false) {
447
- return __async(this, null, function* () {
448
- return this.client.fetch("/channels/catalyst", {
449
- method: "POST",
450
- headers: { "Content-Type": "application/json" },
451
- body: JSON.stringify({
452
- name: name2,
453
- initialData: {
454
- type: installSampleData ? "sample" : "none"
455
- },
456
- deployStorefront: true,
457
- devOrigin: "http://localhost:3000",
458
- storefrontLanguage: storefrontLocale,
459
- additionalLocales
460
- })
461
- });
462
- });
463
- }
464
- };
465
-
466
- // src/utils/clone-catalyst.ts
467
- import { execSync as execSync2 } from "child_process";
468
-
469
- // src/utils/checkout-ref.ts
470
- import { sync as spawnSync } from "cross-spawn";
471
-
472
- // src/utils/is-exec-exception.ts
473
- function isExecException(error) {
474
- return typeof error === "object" && error !== null && "stdout" in error && "stderr" in error;
475
- }
476
-
477
- // src/utils/checkout-ref.ts
478
- function checkoutRef(repoDir, ref) {
479
- try {
480
- const spawn = spawnSync("git", ["checkout", ref, "--"], {
481
- cwd: repoDir,
482
- encoding: "utf8",
483
- // Explicitly set shell to false to avoid shell injection
484
- // Don't use shell: true as it's a security risk
485
- shell: false
486
- });
487
- const stderr = spawn.stderr.trim();
488
- if (spawn.status !== 0 && stderr) {
489
- throw new Error(stderr);
490
- }
491
- console.log(`Checked out ref ${ref} successfully.`);
492
- } catch (error) {
493
- if (isExecException(error)) {
494
- const stderr = error.stderr ? error.stderr.toString() : "";
495
- 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`)) {
496
- console.error(`Ref '${ref}' not found in the repository.`);
497
- } else {
498
- console.error(`Error checking out ref '${ref}':`, stderr.trim());
499
- }
500
- } else if (error instanceof Error) {
501
- console.error(`Error checking out ref '${ref}':`, error.message);
502
- } else {
503
- console.error(`Unknown error occurred while checking out ref '${ref}'.`);
504
- }
505
- console.warn(`Falling back to the default branch.`);
506
- }
507
- }
508
-
509
- // src/utils/has-github-ssh.ts
510
- import { execSync } from "child_process";
511
- function hasGitHubSSH() {
512
- try {
513
- const output = execSync("ssh -T git@github.com", {
514
- encoding: "utf8",
515
- stdio: "pipe"
516
- }).toString();
517
- return output.includes("successfully authenticated");
518
- } catch (error) {
519
- if (isExecException(error)) {
520
- const stdout = error.stdout ? error.stdout.toString() : "";
521
- const stderr = error.stderr ? error.stderr.toString() : "";
522
- const combinedOutput = stdout + stderr;
523
- if (combinedOutput.includes("successfully authenticated")) {
524
- return true;
525
- }
526
- }
527
- return false;
528
- }
529
- }
530
-
531
- // src/utils/reset-branch-to-ref.ts
532
- import { sync as spawnSync2 } from "cross-spawn";
533
- function resetBranchToRef(projectDir, ghRef) {
534
- const spawn = spawnSync2("git", ["reset", "--hard", ghRef, "--"], {
535
- cwd: projectDir,
536
- encoding: "utf8",
537
- // Explicitly set shell to false to avoid shell injection
538
- // Don't use shell: true as it's a security risk
539
- shell: false
540
- });
541
- const stderr = spawn.stderr.trim();
542
- if (spawn.status !== 0 && stderr) {
543
- throw new Error(stderr);
544
- }
545
- }
546
-
547
- // src/utils/clone-catalyst.ts
548
- var cloneCatalyst = ({
549
- repository,
550
- projectName,
551
- projectDir,
552
- ghRef,
553
- resetMain = false
554
- }) => {
555
- const useSSH = hasGitHubSSH();
556
- console.log(`Cloning ${repository} using ${useSSH ? "SSH" : "HTTPS"}...
557
- `);
558
- const cloneCommand = `git clone ${useSSH ? `git@github.com:${repository}` : `https://github.com/${repository}`}.git${projectName ? ` ${projectName}` : ""}`;
559
- execSync2(cloneCommand, { stdio: "inherit" });
560
- console.log();
561
- execSync2("git remote rename origin upstream", { cwd: projectDir, stdio: "inherit" });
562
- console.log();
563
- if (ghRef) {
564
- if (resetMain) {
565
- execSync2("git checkout -b main", { cwd: projectDir, stdio: "inherit" });
566
- resetBranchToRef(projectDir, ghRef);
567
- console.log(`Reset main to ${ghRef} successfully.
568
- `);
569
- return;
570
- }
571
- checkoutRef(projectDir, ghRef);
572
- console.log();
573
- }
574
- };
575
-
576
- // src/utils/install-dependencies.ts
577
- import { colorize as colorize2 } from "consola/utils";
578
- import { installDependencies as installDeps } from "nypm";
579
-
580
- // src/utils/spinner.ts
581
- import { oraPromise } from "ora";
582
- var spinner = (action, oraOpts) => __async(null, null, function* () {
583
- return oraPromise(action, __spreadValues({
584
- spinner: "triangle"
585
- }, oraOpts)).catch(() => process.exit(1));
586
- });
587
-
588
- // src/utils/install-dependencies.ts
589
- var installAllDeps = (projectDir) => __async(null, null, function* () {
590
- yield installDeps({ cwd: projectDir, silent: true, packageManager: "pnpm" });
591
- });
592
- var installDependencies = (projectDir) => __async(null, null, function* () {
593
- return spinner(installAllDeps(projectDir), {
594
- text: `Installing dependencies. This could take a minute...`,
595
- successText: `Dependencies installed successfully`,
596
- failText: (err) => colorize2("red", `Failed to install dependencies: ${err.message}`)
597
- });
598
- });
599
-
600
- // src/utils/localization.ts
601
- import { z } from "zod";
602
- var allowedLocales = [
603
- "en",
604
- "da",
605
- "es-AR",
606
- "es-CL",
607
- "es-CO",
608
- "es-MX",
609
- "es-PE",
610
- "es-419",
611
- "es",
612
- "it",
613
- "nl",
614
- "pl",
615
- "pt",
616
- "de",
617
- "fr",
618
- "ja",
619
- "no",
620
- "pt-BR",
621
- "sv"
622
- ];
623
- var AvailableLocalesSuccessSchema = z.object({
624
- data: z.array(
625
- z.object({
626
- id: z.string(),
627
- name: z.string(),
628
- fallback: z.string().nullable(),
629
- is_supported: z.boolean()
630
- })
631
- )
632
- });
633
- var getAvailableLocales = (bc) => __async(null, null, function* () {
634
- const response = yield bc.fetch("/v3/settings/store/available-locales");
635
- if (!response.ok) {
636
- throw new Error(
637
- `GET /v3/settings/store/available-locales failed: ${response.status} ${response.statusText}`
638
- );
639
- }
640
- return AvailableLocalesSuccessSchema.parse(yield response.json()).data.filter(({ id }) => allowedLocales.includes(id)).map(({ name: name2, id }) => ({ name: `${name2} (${id})`, value: id }));
641
- });
642
-
643
- // src/utils/login.ts
644
- import { colorize as colorize3 } from "consola/utils";
645
- import open from "open";
646
- import { createInterface } from "readline";
647
-
648
- // src/utils/auth.ts
649
- import { z as z3 } from "zod";
650
-
651
- // src/utils/parse.ts
652
- import * as z2 from "zod";
653
- import { fromZodError } from "zod-validation-error";
654
- var parse = (data, schema) => {
655
- try {
656
- return schema.parse(data);
657
- } catch (error) {
658
- if (error instanceof z2.ZodError) {
659
- console.error(fromZodError(error).toString());
660
- }
661
- process.exit(1);
662
- }
663
- };
664
-
665
- // src/utils/auth.ts
666
- var Auth = class {
667
- constructor({ baseUrl }) {
668
- this.DEVICE_OAUTH_CLIENT_ID = "b8063bu6hhml4e0lqh22yut63atsbyv";
669
- this.client = new Https({ baseUrl });
670
- }
671
- getDeviceCode() {
672
- return __async(this, null, function* () {
673
- const response = yield this.client.fetch("/device/token", {
674
- method: "POST",
675
- headers: { "Content-Type": "application/json" },
676
- body: JSON.stringify({
677
- scopes: [
678
- "store_channel_settings",
679
- "store_sites",
680
- "store_storefront_api",
681
- "store_v2_content",
682
- "store_v2_information",
683
- "store_v2_products",
684
- "store_cart"
685
- ].join(" "),
686
- client_id: this.DEVICE_OAUTH_CLIENT_ID
687
- })
688
- });
689
- const DeviceCodeSchema = z3.object({
690
- device_code: z3.string(),
691
- user_code: z3.string(),
692
- verification_uri: z3.string(),
693
- expires_in: z3.number(),
694
- interval: z3.number()
695
- });
696
- return parse(yield response.json(), DeviceCodeSchema);
697
- });
698
- }
699
- checkDeviceCode(deviceCode) {
700
- return __async(this, null, function* () {
701
- const response = yield this.client.fetch("/device/token", {
702
- method: "POST",
703
- headers: { "Content-Type": "application/json" },
704
- body: JSON.stringify({
705
- device_code: deviceCode,
706
- client_id: this.DEVICE_OAUTH_CLIENT_ID
707
- })
708
- });
709
- if (response.status !== 200) {
710
- throw new Error("Device code not yet verified");
711
- }
712
- const DeviceCodeSuccessSchema = z3.object({
713
- access_token: z3.string(),
714
- store_hash: z3.string(),
715
- context: z3.string(),
716
- api_uri: z3.string().url()
717
- });
718
- return parse(yield response.json(), DeviceCodeSuccessSchema);
719
- });
720
- }
721
- };
722
-
723
- // src/utils/config.ts
724
- import { parse as parseTOML, stringify as stringifyTOML } from "@iarna/toml";
725
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { spawn } from "child_process";
3
+ import { createRequire } from "module";
726
4
  import { dirname, join } from "path";
727
- function isRecord(value) {
728
- return typeof value === "object" && value !== null;
729
- }
730
- function isCatalystConfig(obj) {
731
- if (!isRecord(obj)) {
732
- return false;
733
- }
734
- if ("auth" in obj) {
735
- if (!isRecord(obj.auth)) {
736
- return false;
737
- }
738
- const { storeHash, accessToken } = obj.auth;
739
- return (storeHash === void 0 || typeof storeHash === "string") && (accessToken === void 0 || typeof accessToken === "string");
740
- }
741
- return true;
742
- }
743
- var Config = class {
744
- constructor(projectDir) {
745
- this.configPath = join(projectDir, ".catalyst");
746
- this.config = this.read();
747
- }
748
- getAuth() {
749
- var _a;
750
- return (_a = this.config.auth) != null ? _a : {};
751
- }
752
- setAuth(storeHash, accessToken) {
753
- this.config.auth = { storeHash, accessToken };
754
- this.save();
755
- }
756
- save() {
757
- var _a;
758
- const configObj = {};
759
- if (((_a = this.config.auth) == null ? void 0 : _a.storeHash) && this.config.auth.accessToken) {
760
- configObj.auth = {
761
- storeHash: this.config.auth.storeHash,
762
- accessToken: this.config.auth.accessToken
763
- };
764
- }
765
- const dir = dirname(this.configPath);
766
- if (!existsSync(dir)) {
767
- mkdirSync(dir, { recursive: true });
768
- }
769
- const preamble = `# DO NOT commit this file to your repository!
770
- # This file contains sensitive configuration specific to your local CLI setup.
771
- # It includes authentication tokens and store-specific information.
772
- # If using version control, make sure to add .catalyst to your .gitignore file.
773
-
774
- `;
775
- writeFileSync(this.configPath, preamble + stringifyTOML(configObj));
776
- }
777
- read() {
778
- if (!existsSync(this.configPath)) {
779
- return {};
780
- }
781
- try {
782
- const contents = readFileSync(this.configPath, "utf-8");
783
- const parsed = parseTOML(contents);
784
- if (isCatalystConfig(parsed)) {
785
- return parsed;
786
- }
787
- console.warn("Invalid config format in .catalyst file, using defaults");
788
- return {};
789
- } catch (e) {
790
- console.warn("Failed to parse .catalyst config file, using defaults");
791
- return {};
792
- }
793
- }
794
- };
795
-
796
- // src/utils/login.ts
797
- function pollDeviceCode(auth, deviceCode, interval) {
798
- return __async(this, null, function* () {
799
- try {
800
- const credentials = yield auth.checkDeviceCode(deviceCode);
801
- return credentials;
802
- } catch (e) {
803
- yield new Promise((resolve) => setTimeout(resolve, interval * 1e3));
804
- return null;
805
- }
806
- });
807
- }
808
- function waitForCredentials(auth, deviceCode, interval) {
809
- return __async(this, null, function* () {
810
- const credentials = yield pollDeviceCode(auth, deviceCode, interval);
811
- if (credentials) {
812
- return credentials;
813
- }
814
- return waitForCredentials(auth, deviceCode, interval);
815
- });
816
- }
817
- function waitForKeyPress(prompt) {
818
- return __async(this, null, function* () {
819
- const rl = createInterface({
820
- input: process.stdin,
821
- output: process.stdout
822
- });
823
- process.stdin.setRawMode(true);
824
- process.stdin.resume();
825
- return new Promise((resolve) => {
826
- process.stdin.once("data", (data) => {
827
- process.stdin.setRawMode(false);
828
- process.stdin.pause();
829
- rl.close();
830
- const shouldProceed = data[0] === 13 || data[0] === 10;
831
- process.stdout.write("\n");
832
- resolve(shouldProceed);
833
- });
834
- rl.write(prompt);
835
- });
836
- });
837
- }
838
- function login(baseUrl) {
839
- return __async(this, null, function* () {
840
- const auth = new Auth({ baseUrl });
841
- const deviceCode = yield auth.getDeviceCode();
842
- console.log(
843
- colorize3("yellow", "\n! First copy your one-time code: ") + colorize3("bold", deviceCode.user_code)
844
- );
845
- console.log(`Press Enter to open ${deviceCode.verification_uri} in your browser...`);
846
- const shouldOpenUrl = yield waitForKeyPress("");
847
- if (shouldOpenUrl) {
848
- yield open(deviceCode.verification_uri);
849
- }
850
- const credentials = yield spinner(
851
- () => waitForCredentials(auth, deviceCode.device_code, deviceCode.interval),
852
- { text: "Waiting for authentication...", successText: "Authentication complete" }
853
- );
854
- return {
855
- storeHash: credentials.store_hash,
856
- accessToken: credentials.access_token
857
- };
858
- });
859
- }
860
- function storeCredentials(projectDir, credentials) {
861
- const config = new Config(projectDir);
862
- config.setAuth(credentials.storeHash, credentials.accessToken);
863
- }
864
-
865
- // src/utils/telemetry/telemetry.ts
866
- import { Analytics } from "@segment/analytics-node";
867
- import Conf from "conf";
868
- import { randomBytes } from "crypto";
869
- var TELEMETRY_KEY_ENABLED = "telemetry.enabled";
870
- var TELEMETRY_KEY_ID = `telemetry.anonymousId`;
871
- var Telemetry = class {
872
- constructor() {
873
- this.projectName = "catalyst-cli";
874
- this.projectVersion = package_default.version;
875
- this.setEnabled = (_enabled) => {
876
- var _a, _b;
877
- const enabled = Boolean(_enabled);
878
- (_a = this.conf) == null ? void 0 : _a.set(TELEMETRY_KEY_ENABLED, enabled);
879
- return (_b = this.conf) == null ? void 0 : _b.path;
880
- };
881
- this.CATALYST_TELEMETRY_DISABLED = process.env.CATALYST_TELEMETRY_DISABLED;
882
- try {
883
- this.conf = new Conf({
884
- projectName: this.projectName,
885
- projectVersion: this.projectVersion
886
- });
887
- } catch (e) {
888
- this.conf = null;
889
- }
890
- this.sessionId = randomBytes(32).toString("hex");
891
- this.analytics = new Analytics({
892
- writeKey: "OCkxLj3KTc1N3ebEbnFBqbhxfE3tVb3Z"
893
- });
894
- }
895
- track(eventName, payload) {
896
- return __async(this, null, function* () {
897
- if (!this.isEnabled()) {
898
- return Promise.resolve(void 0);
899
- }
900
- this.analytics.track({
901
- event: eventName,
902
- anonymousId: this.getAnonymousId(),
903
- properties: __spreadProps(__spreadValues({}, payload), {
904
- sessionId: this.sessionId
905
- }),
906
- context: {
907
- app: {
908
- name: this.projectName,
909
- version: this.projectVersion
910
- }
911
- }
912
- });
913
- });
914
- }
915
- identify(storeHash) {
916
- return __async(this, null, function* () {
917
- if (!this.isEnabled()) {
918
- return Promise.resolve(void 0);
919
- }
920
- if (!storeHash) {
921
- return Promise.resolve(void 0);
922
- }
923
- this.analytics.identify({
924
- userId: storeHash,
925
- anonymousId: this.getAnonymousId(),
926
- context: {
927
- app: {
928
- name: this.projectName,
929
- version: this.projectVersion
930
- }
931
- }
932
- });
933
- });
934
- }
935
- isEnabled() {
936
- return !this.CATALYST_TELEMETRY_DISABLED && !!this.conf && this.conf.get(TELEMETRY_KEY_ENABLED, true);
937
- }
938
- getAnonymousId() {
939
- var _a, _b;
940
- const val = (_a = this.conf) == null ? void 0 : _a.get(TELEMETRY_KEY_ID);
941
- if (val) {
942
- return val;
943
- }
944
- const generated = randomBytes(32).toString("hex");
945
- (_b = this.conf) == null ? void 0 : _b.set(TELEMETRY_KEY_ID, generated);
946
- return generated;
947
- }
948
- };
949
-
950
- // src/utils/write-env.ts
951
- import { outputFileSync } from "fs-extra/esm";
952
- import { join as join2 } from "path";
953
- var writeEnv = (projectDir, envVars) => {
954
- outputFileSync(
955
- join2(projectDir, ".env.local"),
956
- `${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
957
- `
958
- );
959
- };
960
-
961
- // src/commands/create.ts
962
- function getPlatformCheckCommand(command) {
963
- const isWindows2 = process.platform === "win32";
964
- return isWindows2 ? `where.exe ${command}` : `which ${command}`;
965
- }
966
- var telemetry = new Telemetry();
967
- function handleChannelCreation(bc, cliApi) {
968
- return __async(this, null, function* () {
969
- const newChannelName = yield input({
970
- message: "What would you like to name your new channel?"
971
- });
972
- let availableLocales = [];
973
- try {
974
- availableLocales = yield getAvailableLocales(bc);
975
- } catch (error) {
976
- if (error instanceof Error) {
977
- console.error(colorize4("red", error.message));
978
- }
979
- process.exit(1);
980
- }
981
- const storefrontLocale = yield select({
982
- message: "Which default language would you like to set for your channel?",
983
- default: "en",
984
- choices: availableLocales,
985
- theme: {
986
- style: {
987
- help: () => colorize4("dim", "(Select locale from the list or start typing the name)")
988
- }
989
- }
990
- });
991
- const shouldAddAdditionalLocales = yield select({
992
- message: "Would you like to add additional languages?",
993
- choices: [
994
- { name: "Yes", value: true },
995
- { name: "No", value: false }
996
- ]
997
- });
998
- let additionalLocales = [];
999
- if (shouldAddAdditionalLocales) {
1000
- additionalLocales = yield multiSelect({
1001
- choices: availableLocales.filter(({ value }) => value !== storefrontLocale),
1002
- message: "Which additional languages would you like to add to your channel?",
1003
- theme: {
1004
- style: {
1005
- help: () => colorize4("dim", "(Select locale from the list or start typing the name)")
1006
- }
1007
- },
1008
- validate: (selections) => {
1009
- if (selections.length > 4) {
1010
- return "You can only select up to 4 additional languages";
1011
- }
1012
- return true;
1013
- }
1014
- });
1015
- }
1016
- const shouldInstallSampleData = yield select({
1017
- message: "Would you like to install sample data?",
1018
- choices: [
1019
- { name: "Yes", value: true },
1020
- { name: "No", value: false }
1021
- ]
1022
- });
1023
- const response = yield cliApi.createChannel(
1024
- newChannelName,
1025
- storefrontLocale,
1026
- additionalLocales,
1027
- shouldInstallSampleData
1028
- );
1029
- if (!response.ok) {
1030
- console.error(
1031
- colorize4(
1032
- "red",
1033
- `
1034
- POST /channels/catalyst failed: ${response.status} ${response.statusText}
1035
- `
1036
- )
1037
- );
1038
- process.exit(1);
1039
- }
1040
- const channelData = yield response.json();
1041
- if (!isCreateChannelResponse(channelData)) {
1042
- console.error(colorize4("red", "\nUnexpected response format from create channel endpoint\n"));
1043
- process.exit(1);
1044
- }
1045
- return {
1046
- channelId: channelData.data.id,
1047
- storefrontToken: channelData.data.storefront_api_token,
1048
- envVars: channelData.data.envVars
1049
- };
1050
- });
1051
- }
1052
- function handleChannelSelection(bc) {
1053
- return __async(this, null, function* () {
1054
- const channelSortOrder = ["catalyst", "next", "bigcommerce"];
1055
- const channelsResponse = yield bc.fetch("/v3/channels?available=true&type=storefront");
1056
- if (!channelsResponse.ok) {
1057
- console.error(
1058
- colorize4(
1059
- "red",
1060
- `
1061
- GET /v3/channels failed: ${channelsResponse.status} ${channelsResponse.statusText}
1062
- `
1063
- )
1064
- );
1065
- process.exit(1);
1066
- }
1067
- const availableChannels = yield channelsResponse.json();
1068
- if (!isChannelsResponse(availableChannels)) {
1069
- console.error(colorize4("red", "\nUnexpected response format from channels endpoint\n"));
1070
- process.exit(1);
1071
- }
1072
- const existingChannel = yield select({
1073
- message: "Which channel would you like to use?",
1074
- choices: availableChannels.data.sort((a, b) => {
1075
- const aIndex = channelSortOrder.indexOf(a.platform);
1076
- const bIndex = channelSortOrder.indexOf(b.platform);
1077
- if (aIndex === -1 && bIndex === -1) {
1078
- return 0;
1079
- }
1080
- if (aIndex === -1) return 1;
1081
- if (bIndex === -1) return -1;
1082
- return aIndex - bIndex;
1083
- }).map((ch) => ({
1084
- name: ch.name,
1085
- value: ch,
1086
- description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
1087
- }))
1088
- });
1089
- return existingChannel.id;
1090
- });
1091
- }
1092
- function getChannelInit(cliApi, channelId) {
1093
- return __async(this, null, function* () {
1094
- const initResponse = yield cliApi.getChannelInit(channelId);
1095
- if (!initResponse.ok) {
1096
- console.error(
1097
- colorize4(
1098
- "red",
1099
- `
1100
- GET /channels/${channelId}/init failed: ${initResponse.status} ${initResponse.statusText}
1101
- `
1102
- )
1103
- );
1104
- process.exit(1);
1105
- }
1106
- const initData = yield initResponse.json();
1107
- if (!isInitResponse(initData)) {
1108
- console.error(colorize4("red", "\nUnexpected response format from init endpoint\n"));
1109
- process.exit(1);
1110
- }
1111
- return {
1112
- storefrontToken: initData.data.storefront_api_token,
1113
- envVars: initData.data.envVars
1114
- };
1115
- });
1116
- }
1117
- function setupProject(options) {
1118
- return __async(this, null, function* () {
1119
- let { projectName, projectDir } = options;
1120
- if (!pathExistsSync(projectDir)) {
1121
- console.error(colorize4("red", `Error: --projectDir ${projectDir} is not a valid path
1122
- `));
1123
- process.exit(1);
1124
- }
1125
- if (projectName) {
1126
- projectName = kebabCase(projectName);
1127
- projectDir = join3(options.projectDir, projectName);
1128
- if (pathExistsSync(projectDir)) {
1129
- console.error(colorize4("red", `Error: ${projectDir} already exists
1130
- `));
1131
- process.exit(1);
1132
- }
1133
- }
1134
- if (!projectName) {
1135
- const validateProjectName = (i) => {
1136
- const formatted = kebabCase(i);
1137
- if (!formatted) return "Project name is required";
1138
- const targetDir = join3(options.projectDir, formatted);
1139
- if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
1140
- projectName = formatted;
1141
- projectDir = targetDir;
1142
- return true;
1143
- };
1144
- yield input({
1145
- message: "What is the name of your project?",
1146
- default: "my-catalyst-app",
1147
- validate: validateProjectName
1148
- });
1149
- }
1150
- if (!projectName) throw new Error("Something went wrong, projectName is not defined");
1151
- if (!projectDir) throw new Error("Something went wrong, projectDir is not defined");
1152
- return { projectName, projectDir };
1153
- });
1154
- }
1155
- function checkRequiredTools() {
1156
- try {
1157
- execSync3(getPlatformCheckCommand("git"), { stdio: "ignore" });
1158
- } catch (e) {
1159
- console.error(colorize4("red", "Error: git is required to create a Catalyst project\n"));
1160
- process.exit(1);
1161
- }
1162
- try {
1163
- execSync3(getPlatformCheckCommand("pnpm"), { stdio: "ignore" });
1164
- } catch (e) {
1165
- console.error(colorize4("red", "Error: pnpm is required to create a Catalyst project\n"));
1166
- console.error("Tip: Enable it by running `corepack enable pnpm`\n");
1167
- process.exit(1);
1168
- }
1169
- }
1170
- var create = new Command("create").description("Command to scaffold and connect a Catalyst storefront to your BigCommerce store").option("--project-name <name>", "Name of your Catalyst project").option("--project-dir <dir>", "Directory in which to create your project", process.cwd()).option("--store-hash <hash>", "BigCommerce store hash").option("--access-token <token>", "BigCommerce access token").option("--channel-id <id>", "BigCommerce channel ID").option("--storefront-token <token>", "BigCommerce storefront token").option(
1171
- "--gh-ref <ref>",
1172
- "Clone a specific ref from the source repository",
1173
- "@bigcommerce/catalyst-core@latest"
1174
- ).option("--reset-main", "Reset the main branch to the gh-ref").option("--repository <repository>", "GitHub repository to clone from", "bigcommerce/catalyst").option("--env <vars...>", "Arbitrary environment variables to set in .env.local").addOption(
1175
- new Option("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
1176
- ).addOption(
1177
- new Option("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
1178
- ).action((options) => __async(null, null, function* () {
1179
- const { ghRef, repository } = options;
1180
- checkRequiredTools();
1181
- const { projectName, projectDir } = yield setupProject({
1182
- projectName: options.projectName,
1183
- projectDir: options.projectDir
1184
- });
1185
- let storeHash = options.storeHash;
1186
- let accessToken = options.accessToken;
1187
- let channelId;
1188
- let storefrontToken = options.storefrontToken;
1189
- let credentials;
1190
- if (options.channelId) {
1191
- channelId = parseInt(options.channelId, 10);
1192
- }
1193
- let envVars = {};
1194
- if ((!storeHash || !accessToken) && (!channelId || !storefrontToken)) {
1195
- credentials = yield login(`https://login.${options.bigcommerceHostname}`);
1196
- storeHash = credentials.storeHash;
1197
- accessToken = credentials.accessToken;
1198
- }
1199
- if (storeHash && channelId && storefrontToken) {
1200
- envVars.BIGCOMMERCE_STORE_HASH = storeHash;
1201
- envVars.BIGCOMMERCE_CHANNEL_ID = channelId.toString();
1202
- envVars.BIGCOMMERCE_STOREFRONT_API_TOKEN = storefrontToken;
5
+ var require2 = createRequire(import.meta.url);
6
+ function resolveCatalystBin() {
7
+ const packageJsonPath = require2.resolve("@bigcommerce/catalyst/package.json");
8
+ const { bin } = require2("@bigcommerce/catalyst/package.json");
9
+ const relativeBin = typeof bin === "string" ? bin : bin.catalyst;
10
+ return join(dirname(packageJsonPath), relativeBin);
11
+ }
12
+ var child = spawn(process.execPath, [resolveCatalystBin(), "create", ...process.argv.slice(2)], {
13
+ stdio: "inherit"
14
+ });
15
+ child.on("error", (error) => {
16
+ console.error(error);
17
+ process.exit(1);
18
+ });
19
+ child.on("exit", (code, signal) => {
20
+ if (signal) {
21
+ process.kill(process.pid, signal);
1203
22
  } else {
1204
- if (!storeHash || !accessToken) {
1205
- console.log(`
1206
- Creating '${projectName}' at '${projectDir}'
1207
- `);
1208
- cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
1209
- yield installDependencies(projectDir);
1210
- if (options.env) {
1211
- const cliEnvVars = options.env.reduce((acc, env) => {
1212
- const [key, value] = env.split("=");
1213
- if (key && value) {
1214
- acc[key] = value;
1215
- }
1216
- return acc;
1217
- }, {});
1218
- Object.assign(envVars, cliEnvVars);
1219
- }
1220
- writeEnv(projectDir, envVars);
1221
- console.log(
1222
- [
1223
- colorize4("green", `
1224
- Success! Created '${projectName}' at '${projectDir}'
1225
- `),
1226
- `Next steps:`,
1227
- Object.keys(envVars).length > 0 ? colorize4("yellow", `
1228
- - cd ${projectName} && pnpm run dev
1229
- `) : [
1230
- colorize4("yellow", `
1231
- - cd ${projectName} && cp .env.example .env.local`),
1232
- colorize4(
1233
- "yellow",
1234
- `
1235
- - Populate .env.local with your BigCommerce API credentials
1236
- `
1237
- )
1238
- ].join("")
1239
- ].join("\n")
1240
- );
1241
- process.exit(0);
1242
- }
1243
- yield telemetry.identify(storeHash);
1244
- if (!channelId || !storefrontToken) {
1245
- const bc = new Https({
1246
- baseUrl: `https://api.${options.bigcommerceHostname}/stores/${storeHash}`,
1247
- accessToken
1248
- });
1249
- const cliApi = new CliApi({
1250
- origin: options.cliApiOrigin,
1251
- storeHash,
1252
- accessToken
1253
- });
1254
- if (channelId && !storefrontToken) {
1255
- const initData = yield getChannelInit(cliApi, channelId);
1256
- envVars = __spreadValues({}, initData.envVars);
1257
- storefrontToken = initData.storefrontToken;
1258
- } else if (!channelId) {
1259
- const eligibilityResponse = yield cliApi.checkEligibility();
1260
- if (!eligibilityResponse.ok) {
1261
- console.error(
1262
- colorize4(
1263
- "red",
1264
- `
1265
- GET /channels/catalyst/eligibility failed: ${eligibilityResponse.status} ${eligibilityResponse.statusText}
1266
- `
1267
- )
1268
- );
1269
- process.exit(1);
1270
- }
1271
- const eligibilityData = yield eligibilityResponse.json();
1272
- if (!isEligibilityResponse(eligibilityData)) {
1273
- console.error(
1274
- colorize4("red", "\nUnexpected response format from eligibility endpoint\n")
1275
- );
1276
- process.exit(1);
1277
- }
1278
- if (!eligibilityData.data.eligible) {
1279
- console.warn(colorize4("yellow", eligibilityData.data.message));
1280
- }
1281
- let shouldCreateChannel;
1282
- if (eligibilityData.data.eligible) {
1283
- shouldCreateChannel = yield select({
1284
- message: "Would you like to create a new channel?",
1285
- choices: [
1286
- { name: "Yes", value: true },
1287
- { name: "No", value: false }
1288
- ]
1289
- });
1290
- }
1291
- if (shouldCreateChannel) {
1292
- const channelData = yield handleChannelCreation(bc, cliApi);
1293
- channelId = channelData.channelId;
1294
- storefrontToken = channelData.storefrontToken;
1295
- envVars = __spreadValues({}, channelData.envVars);
1296
- console.log(colorize4("green", `Channel created successfully`));
1297
- console.warn(
1298
- colorize4(
1299
- "yellow",
1300
- "\nNote: A preview storefront has been deployed in your BigCommerce control panel. This preview may look different from your local environment as it may be running different code."
1301
- )
1302
- );
1303
- }
1304
- if (!shouldCreateChannel) {
1305
- channelId = yield handleChannelSelection(bc);
1306
- const initData = yield getChannelInit(cliApi, channelId);
1307
- envVars = __spreadValues({}, initData.envVars);
1308
- storefrontToken = initData.storefrontToken;
1309
- }
1310
- }
1311
- }
1312
- }
1313
- if (options.env) {
1314
- const cliEnvVars = options.env.reduce((acc, env) => {
1315
- const [key, value] = env.split("=");
1316
- if (key && value) {
1317
- acc[key] = value;
1318
- }
1319
- return acc;
1320
- }, {});
1321
- Object.assign(envVars, cliEnvVars);
23
+ process.exit(code ?? 0);
1322
24
  }
1323
- if (options.storeHash) {
1324
- envVars.BIGCOMMERCE_STORE_HASH = options.storeHash;
1325
- }
1326
- if (options.channelId) {
1327
- envVars.BIGCOMMERCE_CHANNEL_ID = options.channelId;
1328
- }
1329
- if (options.storefrontToken) {
1330
- envVars.BIGCOMMERCE_STOREFRONT_TOKEN = options.storefrontToken;
1331
- }
1332
- if (!channelId) throw new Error("Something went wrong, channelId is not defined");
1333
- if (!storefrontToken) throw new Error("Something went wrong, storefrontToken is not defined");
1334
- console.log(`
1335
- Creating '${projectName}' at '${projectDir}'
1336
- `);
1337
- cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
1338
- yield installDependencies(projectDir);
1339
- writeEnv(projectDir, envVars);
1340
- if (credentials) {
1341
- storeCredentials(projectDir, credentials);
1342
- }
1343
- console.log(
1344
- colorize4("green", `
1345
- Success! Created '${projectName}' at '${projectDir}'
1346
- `),
1347
- "\nNext steps:\n",
1348
- colorize4("yellow", `
1349
- cd ${projectName} && pnpm run dev
1350
- `)
1351
- );
1352
- }));
1353
- function isInitResponse(response) {
1354
- return typeof response === "object" && response !== null && "data" in response && typeof response.data === "object" && response.data !== null && "storefront_api_token" in response.data && "envVars" in response.data;
1355
- }
1356
- function isEligibilityResponse(response) {
1357
- return typeof response === "object" && response !== null && "data" in response && typeof response.data === "object" && response.data !== null && "eligible" in response.data && "message" in response.data;
1358
- }
1359
- function isCreateChannelResponse(response) {
1360
- return typeof response === "object" && response !== null && "data" in response && typeof response.data === "object" && response.data !== null && "id" in response.data && "storefront_api_token" in response.data && "envVars" in response.data;
1361
- }
1362
- function isChannelsResponse(response) {
1363
- return typeof response === "object" && response !== null && "data" in response && Array.isArray(response.data) && response.data.every(
1364
- (item) => typeof item === "object" && item !== null && "id" in item && "name" in item && "platform" in item
1365
- );
1366
- }
1367
-
1368
- // src/commands/init.ts
1369
- import { Command as Command2, Option as Option2 } from "@commander-js/extra-typings";
1370
- import { select as select2 } from "@inquirer/prompts";
1371
- import { colorize as colorize5 } from "consola/utils";
1372
- function isChannelsResponse2(response) {
1373
- return typeof response === "object" && response !== null && "data" in response && Array.isArray(response.data) && response.data.every(
1374
- (item) => typeof item === "object" && item !== null && "id" in item && "name" in item && "platform" in item
1375
- );
1376
- }
1377
- function isInitResponse2(response) {
1378
- return typeof response === "object" && response !== null && "data" in response && typeof response.data === "object" && response.data !== null && "storefront_api_token" in response.data && "envVars" in response.data;
1379
- }
1380
- var telemetry2 = new Telemetry();
1381
- var init = new Command2("init").description("Connect a BigCommerce store with an existing Catalyst project").option("--store-hash <hash>", "BigCommerce store hash").option("--access-token <token>", "BigCommerce access token").option("--env <vars...>", "Arbitrary environment variables to set in .env.local").addOption(
1382
- new Option2("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
1383
- ).addOption(
1384
- new Option2("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
1385
- ).action((options) => __async(null, null, function* () {
1386
- const projectDir = process.cwd();
1387
- let storeHash = options.storeHash;
1388
- let accessToken = options.accessToken;
1389
- if (!storeHash || !accessToken) {
1390
- const config = new Config(projectDir);
1391
- const storedAuth = config.getAuth();
1392
- storeHash = storeHash != null ? storeHash : storedAuth.storeHash;
1393
- accessToken = accessToken != null ? accessToken : storedAuth.accessToken;
1394
- }
1395
- if (!storeHash || !accessToken) {
1396
- const credentials = yield login(`https://login.${options.bigcommerceHostname}`);
1397
- storeHash = credentials.storeHash;
1398
- accessToken = credentials.accessToken;
1399
- storeCredentials(projectDir, credentials);
1400
- }
1401
- yield telemetry2.identify(storeHash);
1402
- const bc = new Https({
1403
- baseUrl: `https://api.${options.bigcommerceHostname}/stores/${storeHash}`,
1404
- accessToken
1405
- });
1406
- const cliApi = new CliApi({
1407
- origin: options.cliApiOrigin,
1408
- storeHash,
1409
- accessToken
1410
- });
1411
- const channelSortOrder = ["catalyst", "next", "bigcommerce"];
1412
- const channelsResponse = yield bc.fetch("/v3/channels?available=true&type=storefront");
1413
- if (!channelsResponse.ok) {
1414
- console.error(
1415
- colorize5(
1416
- "red",
1417
- `
1418
- GET /v3/channels failed: ${channelsResponse.status} ${channelsResponse.statusText}
1419
- `
1420
- )
1421
- );
1422
- process.exit(1);
1423
- }
1424
- const availableChannels = yield channelsResponse.json();
1425
- if (!isChannelsResponse2(availableChannels)) {
1426
- console.error(colorize5("red", "\nUnexpected response format from channels endpoint\n"));
1427
- process.exit(1);
1428
- }
1429
- const existingChannel = yield select2({
1430
- message: "Which channel would you like to use?",
1431
- choices: availableChannels.data.sort(
1432
- (a, b) => channelSortOrder.indexOf(a.platform) - channelSortOrder.indexOf(b.platform)
1433
- ).map((ch) => ({
1434
- name: ch.name,
1435
- value: ch,
1436
- description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
1437
- }))
1438
- });
1439
- const channelId = existingChannel.id;
1440
- const initResponse = yield cliApi.getChannelInit(channelId);
1441
- if (!initResponse.ok) {
1442
- console.error(
1443
- colorize5(
1444
- "red",
1445
- `
1446
- GET /channels/${channelId}/init failed: ${initResponse.status} ${initResponse.statusText}
1447
- `
1448
- )
1449
- );
1450
- process.exit(1);
1451
- }
1452
- const initData = yield initResponse.json();
1453
- if (!isInitResponse2(initData)) {
1454
- console.error(colorize5("red", "\nUnexpected response format from init endpoint\n"));
1455
- process.exit(1);
1456
- }
1457
- const envVars = __spreadValues({}, initData.data.envVars);
1458
- if (options.env) {
1459
- const cliEnvVars = options.env.reduce((acc, env) => {
1460
- const [key, value] = env.split("=");
1461
- if (key && value) {
1462
- acc[key] = value;
1463
- }
1464
- return acc;
1465
- }, {});
1466
- Object.assign(envVars, cliEnvVars);
1467
- }
1468
- writeEnv(projectDir, envVars);
1469
- console.log(
1470
- colorize5("green", `
1471
- .env.local file created for channel ${existingChannel.name}!
1472
- `)
1473
- );
1474
- console.log(colorize5("green", `
1475
- Next steps:
1476
- `));
1477
- console.log(colorize5("yellow", `
1478
- pnpm run dev
1479
- `));
1480
- }));
1481
-
1482
- // src/commands/integration.ts
1483
- import { Command as Command3 } from "@commander-js/extra-typings";
1484
- import { exec as execCb } from "child_process";
1485
- import { colorize as colorize6 } from "consola/utils";
1486
- import { parse as parse2 } from "dotenv";
1487
- import { outputFileSync as outputFileSync2, writeJsonSync } from "fs-extra/esm";
1488
- import kebabCase2 from "lodash.kebabcase";
1489
- import { coerce, compare } from "semver";
1490
- import { promisify } from "util";
1491
- import { z as z4 } from "zod";
1492
- var exec = promisify(execCb);
1493
- var ManifestSchema = z4.object({
1494
- name: z4.string(),
1495
- dependencies: z4.object({ add: z4.array(z4.string()) }),
1496
- devDependencies: z4.object({ add: z4.array(z4.string()) }),
1497
- environmentVariables: z4.array(z4.string())
1498
- });
1499
- var integration = new Command3("integration").argument("<integration-name>", "Formatted name of the integration").option("--commit-hash <hash>", "Override integration source branch with a specific commit hash").action((integrationNameRaw, options) => __async(null, null, function* () {
1500
- console.warn(
1501
- colorize6(
1502
- "yellow",
1503
- "\u26A0 `create-catalyst integration` is deprecated and will be replaced by the `catalyst upgrade` command."
1504
- )
1505
- );
1506
- const integrationName = z4.string().transform(kebabCase2).parse(integrationNameRaw);
1507
- const manifest = {
1508
- name: integrationName,
1509
- dependencies: { add: [] },
1510
- devDependencies: { add: [] },
1511
- environmentVariables: []
1512
- };
1513
- yield exec("git fetch --tags");
1514
- const { stdout: headRefStdOut } = yield exec("git rev-parse --abbrev-ref HEAD");
1515
- let [sourceRef] = headRefStdOut.split("\n");
1516
- if (options.commitHash) {
1517
- sourceRef = options.commitHash;
1518
- }
1519
- const { stdout: catalystTags } = yield exec("git tag --list @bigcommerce/catalyst-core@\\*");
1520
- const [latestCoreTag] = catalystTags.split("\n").filter(Boolean).sort((a, b) => {
1521
- const versionA = coerce(a.replace("@bigcommerce/catalyst-core@", ""));
1522
- const versionB = coerce(b.replace("@bigcommerce/catalyst-core@", ""));
1523
- if (versionA && versionB) {
1524
- return compare(versionA, versionB);
1525
- }
1526
- return 0;
1527
- }).reverse();
1528
- const PackageDependenciesSchema = z4.object({
1529
- dependencies: z4.object({}).passthrough(),
1530
- devDependencies: z4.object({}).passthrough()
1531
- });
1532
- const getPackageDeps = (ref) => __async(null, null, function* () {
1533
- const { stdout } = yield exec(`git show ${ref}:core/package.json`);
1534
- return PackageDependenciesSchema.parse(JSON.parse(stdout));
1535
- });
1536
- const integrationJson = yield getPackageDeps(sourceRef);
1537
- const latestCoreTagJson = yield getPackageDeps(latestCoreTag);
1538
- const diffObjectKeys = (a, b) => {
1539
- return Object.keys(a).filter((key) => !Object.keys(b).includes(key));
1540
- };
1541
- manifest.dependencies.add = diffObjectKeys(
1542
- integrationJson.dependencies,
1543
- latestCoreTagJson.dependencies
1544
- );
1545
- manifest.devDependencies.add = diffObjectKeys(
1546
- integrationJson.devDependencies,
1547
- latestCoreTagJson.devDependencies
1548
- );
1549
- const { stdout: latestCoreEnv } = yield exec(`git show ${latestCoreTag}:core/.env.example`);
1550
- const { stdout: integrationEnv } = yield exec(`git show ${sourceRef}:core/.env.example`);
1551
- manifest.environmentVariables = diffObjectKeys(parse2(integrationEnv), parse2(latestCoreEnv));
1552
- const { stdout: integrationDiff } = yield exec(
1553
- `git diff ${latestCoreTag}...${sourceRef} -- ':(exclude)core/package.json' ':(exclude)pnpm-lock.yaml'`
1554
- );
1555
- outputFileSync2(`integrations/${integrationName}/integration.patch`, integrationDiff);
1556
- writeJsonSync(`integrations/${integrationName}/manifest.json`, manifest, {
1557
- spaces: 2
1558
- });
1559
- console.log("Integration created successfully.");
1560
- }));
1561
-
1562
- // src/commands/telemetry.ts
1563
- import { Argument, Command as Command4, Option as Option3 } from "@commander-js/extra-typings";
1564
-
1565
- // src/utils/telemetry/index.ts
1566
- import { colorize as colorize7 } from "consola/utils";
1567
- var telemetry3 = new Telemetry();
1568
- var isEnabled = telemetry3.isEnabled();
1569
- var catalystTelemetry = (options, arg) => {
1570
- if (options.enable || arg === "enable") {
1571
- telemetry3.setEnabled(true);
1572
- isEnabled = true;
1573
- console.log("Success!");
1574
- } else if (options.disable || arg === "disable") {
1575
- const path = telemetry3.setEnabled(false);
1576
- if (isEnabled) {
1577
- console.log(`Your preference has been saved${path ? ` to ${path}` : ""}`);
1578
- } else {
1579
- console.log(`Catalyst CLI telemetry collection is already disabled.`);
1580
- }
1581
- isEnabled = false;
1582
- } else {
1583
- console.log("Catalyst CLI Telemetry");
1584
- }
1585
- console.log(
1586
- `
1587
- Status: ${colorize7("bold", isEnabled ? colorize7("green", "Enabled") : colorize7("red", "Disabled"))}`
1588
- );
1589
- if (!isEnabled) {
1590
- console.log(
1591
- `
1592
- You have opted-out of Catalyst CLI telemetry.
1593
- No data will be collected from your machine.`
1594
- );
1595
- }
1596
- };
1597
-
1598
- // src/commands/telemetry.ts
1599
- var telemetry4 = new Command4("telemetry").addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new Option3("--enable", `Enables CLI telemetry collection.`).conflicts("disable")).option("--disable", `Disables CLI telemetry collection.`).action(
1600
- (arg, options) => catalystTelemetry(options, arg)
1601
- );
1602
-
1603
- // src/hooks/telemetry.ts
1604
- var telemetry5 = new Telemetry();
1605
- var allowlistArguments = ["--gh-ref", "--repository", "--project-name"];
1606
- function parseArguments(args) {
1607
- return args.reduce((result, arg, index, array) => {
1608
- if (arg.includes("=")) {
1609
- const [key, value] = arg.split("=");
1610
- if (allowlistArguments.includes(key)) {
1611
- return __spreadProps(__spreadValues({}, result), {
1612
- [key]: value
1613
- });
1614
- }
1615
- }
1616
- if (allowlistArguments.includes(arg)) {
1617
- const nextValue = array[index + 1] && !array[index + 1].startsWith("--") ? array[index + 1] : null;
1618
- if (nextValue && !nextValue.includes("--")) {
1619
- return __spreadProps(__spreadValues({}, result), {
1620
- [arg]: nextValue
1621
- });
1622
- }
1623
- }
1624
- return result;
1625
- }, {});
1626
- }
1627
- var telemetryPreHook = (command) => __async(null, null, function* () {
1628
- const availableCommands = command.commands.map((cmd) => cmd._name);
1629
- const [commandName = "create", ...args] = command.args;
1630
- if (!availableCommands.includes(commandName)) {
1631
- return yield telemetry5.track("create", __spreadValues({}, parseArguments(args)));
1632
- }
1633
- return yield telemetry5.track(commandName, __spreadValues({}, parseArguments(args)));
1634
25
  });
1635
- var telemetryPostHook = () => __async(null, null, function* () {
1636
- yield telemetry5.analytics.closeAndFlush();
1637
- });
1638
-
1639
- // src/index.ts
1640
- console.log(colorize8("cyanBright", `
1641
- \u25E2 ${package_default.name} v${package_default.version}
1642
- `));
1643
- program.name(package_default.name).version(package_default.version).description("A command line tool to create a new Catalyst project.").addCommand(create, { isDefault: true }).addCommand(init).addCommand(integration, { hidden: true }).addCommand(telemetry4).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
1644
- program.parse(process.argv);
1645
26
  //# sourceMappingURL=index.js.map