@bigcommerce/create-catalyst 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -53,12 +53,12 @@ var __async = (__this, __arguments, generator) => {
53
53
 
54
54
  // src/index.ts
55
55
  import { program } from "@commander-js/extra-typings";
56
- import chalk7 from "chalk";
56
+ import chalk6 from "chalk";
57
57
 
58
58
  // package.json
59
59
  var package_default = {
60
60
  name: "@bigcommerce/create-catalyst",
61
- version: "0.18.0",
61
+ version: "0.19.0",
62
62
  type: "module",
63
63
  bin: "bin/index.cjs",
64
64
  files: [
@@ -77,6 +77,7 @@ var package_default = {
77
77
  },
78
78
  dependencies: {
79
79
  "@commander-js/extra-typings": "^12.1.0",
80
+ "@iarna/toml": "^2.2.5",
80
81
  "@inquirer/prompts": "^7.2.0",
81
82
  "@segment/analytics-node": "^2.2.0",
82
83
  chalk: "^5.3.0",
@@ -87,10 +88,11 @@ var package_default = {
87
88
  giget: "^1.2.3",
88
89
  "lodash.kebabcase": "^4.1.1",
89
90
  nypm: "^0.4.1",
91
+ open: "^10.1.0",
90
92
  ora: "^8.1.1",
91
93
  semver: "^7.6.3",
92
94
  "std-env": "^3.8.0",
93
- zod: "^3.23.8",
95
+ zod: "^3.24.1",
94
96
  "zod-validation-error": "^3.4.0"
95
97
  },
96
98
  devDependencies: {
@@ -101,12 +103,12 @@ var package_default = {
101
103
  "@types/fs-extra": "^11.0.4",
102
104
  "@types/jest": "^29.5.14",
103
105
  "@types/lodash.kebabcase": "^4.1.9",
104
- "@types/node": "^20.17.9",
106
+ "@types/node": "^20.17.10",
105
107
  "@types/prompts": "^2.4.9",
106
108
  "@types/semver": "^7.5.8",
107
109
  eslint: "^8.57.1",
108
110
  jest: "^29.7.0",
109
- msw: "^2.6.8",
111
+ msw: "^2.6.9",
110
112
  prettier: "^3.4.2",
111
113
  tsup: "^8.3.5",
112
114
  typescript: "^5.7.2"
@@ -115,13 +117,122 @@ var package_default = {
115
117
 
116
118
  // src/commands/create.ts
117
119
  import { Command, Option } from "@commander-js/extra-typings";
118
- import { input, select as select2 } from "@inquirer/prompts";
119
- import chalk4 from "chalk";
120
+ import { input, select } from "@inquirer/prompts";
121
+ import chalk3 from "chalk";
120
122
  import { execSync as execSync5 } from "child_process";
121
123
  import { pathExistsSync } from "fs-extra/esm";
122
124
  import kebabCase from "lodash.kebabcase";
123
- import { join as join2 } from "path";
124
- import { z as z3 } from "zod";
125
+ import { join as join3 } from "path";
126
+
127
+ // src/utils/user-agent.ts
128
+ import {
129
+ isDevelopment,
130
+ isLinux,
131
+ isMacOS,
132
+ isProduction,
133
+ isTest,
134
+ isWindows,
135
+ nodeVersion,
136
+ process as process2,
137
+ provider,
138
+ runtime
139
+ } from "std-env";
140
+ var { name, version } = package_default;
141
+ var getOS = () => {
142
+ if (isWindows) return "Windows";
143
+ if (isMacOS) return "macOS";
144
+ if (isLinux) return "Linux";
145
+ return "Unknown OS";
146
+ };
147
+ var getEnv = () => {
148
+ if (isDevelopment) return "Development";
149
+ if (isTest) return "Test";
150
+ if (isProduction) return "Production";
151
+ };
152
+ var getPlatform = () => {
153
+ const os = getOS();
154
+ const env = getEnv();
155
+ const keysOfInterest = [
156
+ os,
157
+ env,
158
+ runtime,
159
+ provider,
160
+ `Node ${nodeVersion}`,
161
+ process2.env.NODE_ENV
162
+ ].filter(Boolean);
163
+ return keysOfInterest.join("; ");
164
+ };
165
+ var detectedPlatform = getPlatform();
166
+ var getCLIUserAgent = (platform, extensions) => {
167
+ const userAgentParts = [`${name}/${version}`];
168
+ const platformValue = platform != null ? platform : detectedPlatform;
169
+ userAgentParts.push(`(${platformValue})`);
170
+ if (extensions) {
171
+ userAgentParts.push(extensions);
172
+ }
173
+ return userAgentParts.join(" ");
174
+ };
175
+
176
+ // src/utils/https.ts
177
+ var Https = class {
178
+ constructor({ baseUrl, accessToken }) {
179
+ this.baseUrl = baseUrl;
180
+ this.accessToken = accessToken;
181
+ this.userAgent = getCLIUserAgent();
182
+ }
183
+ fetch(_0) {
184
+ return __async(this, arguments, function* (path, opts = {}) {
185
+ const _a = opts, { headers = {} } = _a, rest = __objRest(_a, ["headers"]);
186
+ const options = __spreadValues({
187
+ headers: __spreadValues(__spreadProps(__spreadValues({}, headers), {
188
+ Accept: "application/json",
189
+ "User-Agent": this.userAgent
190
+ }), this.accessToken && { "X-Auth-Token": this.accessToken })
191
+ }, rest);
192
+ return fetch(`${this.baseUrl}${path}`, options);
193
+ });
194
+ }
195
+ };
196
+
197
+ // src/utils/cli-api.ts
198
+ var CliApi = class {
199
+ constructor({ origin, storeHash, accessToken }) {
200
+ this.client = new Https({
201
+ baseUrl: `${origin}/stores/${storeHash}/cli-api/v3`,
202
+ accessToken
203
+ });
204
+ }
205
+ getChannelInit(channelId) {
206
+ return __async(this, null, function* () {
207
+ return this.client.fetch(`/channels/${channelId}/init`, {
208
+ method: "GET"
209
+ });
210
+ });
211
+ }
212
+ checkEligibility() {
213
+ return __async(this, null, function* () {
214
+ return this.client.fetch("/channels/catalyst/eligibility", {
215
+ method: "GET"
216
+ });
217
+ });
218
+ }
219
+ createChannel(name2, installSampleData = false) {
220
+ return __async(this, null, function* () {
221
+ return this.client.fetch("/channels/catalyst", {
222
+ method: "POST",
223
+ headers: { "Content-Type": "application/json" },
224
+ body: JSON.stringify({
225
+ name: name2,
226
+ initialData: {
227
+ type: installSampleData ? "sample" : "none"
228
+ },
229
+ deployStorefront: true,
230
+ devOrigin: "http://localhost:3000"
231
+ })
232
+ });
233
+ });
234
+ }
235
+ };
125
236
 
126
237
  // src/utils/clone-catalyst.ts
127
238
  import { execSync as execSync4 } from "child_process";
@@ -220,8 +331,36 @@ var cloneCatalyst = ({
220
331
  }
221
332
  };
222
333
 
223
- // src/utils/https.ts
334
+ // src/utils/install-dependencies.ts
224
335
  import chalk from "chalk";
336
+ import { installDependencies as installDeps } from "nypm";
337
+
338
+ // src/utils/spinner.ts
339
+ import { oraPromise } from "ora";
340
+ var spinner = (action, oraOpts) => __async(void 0, null, function* () {
341
+ return oraPromise(action, __spreadValues({
342
+ spinner: "triangle"
343
+ }, oraOpts)).catch(() => process.exit(1));
344
+ });
345
+
346
+ // src/utils/install-dependencies.ts
347
+ var installAllDeps = (projectDir) => __async(void 0, null, function* () {
348
+ yield installDeps({ cwd: projectDir, silent: true, packageManager: "pnpm" });
349
+ });
350
+ var installDependencies = (projectDir) => __async(void 0, null, function* () {
351
+ return spinner(installAllDeps(projectDir), {
352
+ text: `Installing dependencies. This could take a minute...`,
353
+ successText: `Dependencies installed successfully`,
354
+ failText: (err) => chalk.red(`Failed to install dependencies: ${err.message}`)
355
+ });
356
+ });
357
+
358
+ // src/utils/login.ts
359
+ import chalk2 from "chalk";
360
+ import open from "open";
361
+ import { createInterface } from "readline";
362
+
363
+ // src/utils/auth.ts
225
364
  import { z as z2 } from "zod";
226
365
 
227
366
  // src/utils/parse.ts
@@ -238,113 +377,17 @@ var parse = (data, schema) => {
238
377
  }
239
378
  };
240
379
 
241
- // src/utils/user-agent.ts
242
- import {
243
- isDevelopment,
244
- isLinux,
245
- isMacOS,
246
- isProduction,
247
- isTest,
248
- isWindows,
249
- nodeVersion,
250
- process as process2,
251
- provider,
252
- runtime
253
- } from "std-env";
254
- var { name, version } = package_default;
255
- var getOS = () => {
256
- if (isWindows) return "Windows";
257
- if (isMacOS) return "macOS";
258
- if (isLinux) return "Linux";
259
- return "Unknown OS";
260
- };
261
- var getEnv = () => {
262
- if (isDevelopment) return "Development";
263
- if (isTest) return "Test";
264
- if (isProduction) return "Production";
265
- };
266
- var getPlatform = () => {
267
- const os = getOS();
268
- const env = getEnv();
269
- const keysOfInterest = [
270
- os,
271
- env,
272
- runtime,
273
- provider,
274
- `Node ${nodeVersion}`,
275
- process2.env.NODE_ENV
276
- ].filter(Boolean);
277
- return keysOfInterest.join("; ");
278
- };
279
- var detectedPlatform = getPlatform();
280
- var getCLIUserAgent = (platform, extensions) => {
281
- const userAgentParts = [`${name}/${version}`];
282
- const platformValue = platform != null ? platform : detectedPlatform;
283
- userAgentParts.push(`(${platformValue})`);
284
- if (extensions) {
285
- userAgentParts.push(extensions);
286
- }
287
- return userAgentParts.join(" ");
288
- };
289
-
290
- // src/utils/https.ts
291
- var BigCommerceStoreInfo = z2.object({
292
- features: z2.object({
293
- storefront_limits: z2.object({
294
- active: z2.number(),
295
- total_including_inactive: z2.number()
296
- })
297
- })
298
- });
299
- var BigCommerceV3ApiResponseSchema = (schema) => z2.object({
300
- data: schema,
301
- meta: z2.object({})
302
- });
303
- var BigCommerceChannelsV3ResponseSchema = BigCommerceV3ApiResponseSchema(
304
- z2.array(
305
- z2.object({
306
- id: z2.number(),
307
- name: z2.string(),
308
- status: z2.string(),
309
- platform: z2.string()
310
- })
311
- )
312
- );
313
- var Https = class {
314
- constructor({
315
- bigCommerceApiUrl,
316
- bigCommerceAuthUrl,
317
- sampleDataApiUrl,
318
- storeHash,
319
- accessToken
320
- }) {
321
- this.DEVICE_OAUTH_CLIENT_ID = "acse0vvawm9r1n0evag4b8e1ea1fo90";
322
- this.MAX_EPOC_EXPIRES_AT = 2147483647;
323
- this.bigCommerceApiUrl = bigCommerceApiUrl != null ? bigCommerceApiUrl : "";
324
- this.bigCommerceAuthUrl = bigCommerceAuthUrl != null ? bigCommerceAuthUrl : "";
325
- this.sampleDataApiUrl = sampleDataApiUrl != null ? sampleDataApiUrl : "";
326
- this.storeHash = storeHash != null ? storeHash : "";
327
- this.accessToken = accessToken != null ? accessToken : "";
328
- this.userAgent = getCLIUserAgent();
329
- }
330
- auth(path, opts = {}) {
331
- if (!this.bigCommerceAuthUrl) {
332
- throw new Error("bigCommerceAuthUrl is required to make API requests");
333
- }
334
- const _a = opts, { headers = {} } = _a, rest = __objRest(_a, ["headers"]);
335
- const options = __spreadValues({
336
- method: "POST",
337
- headers: __spreadProps(__spreadValues({}, headers), {
338
- Accept: "application/json",
339
- "Content-Type": "application/json",
340
- "User-Agent": this.userAgent
341
- })
342
- }, rest);
343
- return fetch(`${this.bigCommerceAuthUrl}${path}`, options);
380
+ // src/utils/auth.ts
381
+ var Auth = class {
382
+ constructor({ baseUrl }) {
383
+ this.DEVICE_OAUTH_CLIENT_ID = "s1q4io7mah2lm1i6uwp9yl1eit80n3b";
384
+ this.client = new Https({ baseUrl });
344
385
  }
345
386
  getDeviceCode() {
346
387
  return __async(this, null, function* () {
347
- const response = yield this.auth("/device/token", {
388
+ const response = yield this.client.fetch("/device/token", {
389
+ method: "POST",
390
+ headers: { "Content-Type": "application/json" },
348
391
  body: JSON.stringify({
349
392
  scopes: [
350
393
  "store_channel_settings",
@@ -358,14 +401,6 @@ var Https = class {
358
401
  client_id: this.DEVICE_OAUTH_CLIENT_ID
359
402
  })
360
403
  });
361
- if (!response.ok) {
362
- console.error(
363
- chalk.red(`
364
- POST /device/token failed: ${response.status} ${response.statusText}
365
- `)
366
- );
367
- process.exit(1);
368
- }
369
404
  const DeviceCodeSchema = z2.object({
370
405
  device_code: z2.string(),
371
406
  user_code: z2.string(),
@@ -378,7 +413,9 @@ POST /device/token failed: ${response.status} ${response.statusText}
378
413
  }
379
414
  checkDeviceCode(deviceCode) {
380
415
  return __async(this, null, function* () {
381
- const response = yield this.auth("/device/token", {
416
+ const response = yield this.client.fetch("/device/token", {
417
+ method: "POST",
418
+ headers: { "Content-Type": "application/json" },
382
419
  body: JSON.stringify({
383
420
  device_code: deviceCode,
384
421
  client_id: this.DEVICE_OAUTH_CLIENT_ID
@@ -396,243 +433,155 @@ POST /device/token failed: ${response.status} ${response.statusText}
396
433
  return parse(yield response.json(), DeviceCodeSuccessSchema);
397
434
  });
398
435
  }
399
- api(path, opts = {}) {
400
- if (!this.bigCommerceApiUrl || !this.storeHash || !this.accessToken) {
401
- throw new Error(
402
- "bigCommerceApiUrl, storeHash, and accessToken are required to make API requests"
403
- );
436
+ };
437
+
438
+ // src/utils/config.ts
439
+ import { parse as parseTOML, stringify as stringifyTOML } from "@iarna/toml";
440
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
441
+ import { dirname, join } from "path";
442
+ function isRecord(value) {
443
+ return typeof value === "object" && value !== null;
444
+ }
445
+ function isCatalystConfig(obj) {
446
+ if (!isRecord(obj)) {
447
+ return false;
448
+ }
449
+ if ("auth" in obj) {
450
+ if (!isRecord(obj.auth)) {
451
+ return false;
404
452
  }
405
- const _a = opts, { headers = {} } = _a, rest = __objRest(_a, ["headers"]);
406
- const options = __spreadValues({
407
- headers: __spreadProps(__spreadValues({}, headers), {
408
- Accept: "application/json",
409
- "X-Auth-Token": this.accessToken,
410
- "User-Agent": this.userAgent
411
- })
412
- }, rest);
413
- return fetch(`${this.bigCommerceApiUrl}/stores/${this.storeHash}${path}`, options);
453
+ const { storeHash, accessToken } = obj.auth;
454
+ return (storeHash === void 0 || typeof storeHash === "string") && (accessToken === void 0 || typeof accessToken === "string");
414
455
  }
415
- storeInformation() {
416
- return __async(this, null, function* () {
417
- const res = yield this.api("/v2/store");
418
- if (!res.ok) {
419
- console.error(chalk.red(`
420
- GET /v2/store failed: ${res.status} ${res.statusText}
421
- `));
422
- process.exit(1);
423
- }
424
- return parse(yield res.json(), BigCommerceStoreInfo);
425
- });
456
+ return true;
457
+ }
458
+ var Config = class {
459
+ constructor(projectDir) {
460
+ this.configPath = join(projectDir, ".catalyst");
461
+ this.config = this.read();
426
462
  }
427
- channels(query = "") {
428
- return __async(this, null, function* () {
429
- const res = yield this.api(`/v3/channels${query}`);
430
- if (!res.ok) {
431
- console.error(chalk.red(`
432
- GET /v3/channels failed: ${res.status} ${res.statusText}
433
- `));
434
- process.exit(1);
435
- }
436
- return parse(yield res.json(), BigCommerceChannelsV3ResponseSchema);
437
- });
463
+ getAuth() {
464
+ var _a;
465
+ return (_a = this.config.auth) != null ? _a : {};
438
466
  }
439
- createChannelMenus(channelId) {
440
- return __async(this, null, function* () {
441
- const res = yield this.api(`/v3/channels/${channelId}/channel-menus`, {
442
- method: "POST",
443
- headers: { "Content-Type": "application/json" },
444
- body: JSON.stringify({
445
- bigcommerce_protected_app_sections: [
446
- "storefront_settings",
447
- "currencies",
448
- "domains",
449
- "notifications",
450
- "social"
451
- ]
452
- })
453
- });
454
- if (!res.ok) {
455
- console.warn(
456
- chalk.yellow(
457
- `
458
- Failed to create channel menus: ${res.status} ${res.statusText}. You may want to create these later: https://developer.bigcommerce.com/docs/rest-management/channels/menus#create-channel-menus
459
- `
460
- )
461
- );
462
- }
463
- });
467
+ setAuth(storeHash, accessToken) {
468
+ this.config.auth = { storeHash, accessToken };
469
+ this.save();
464
470
  }
465
- storefrontToken() {
466
- return __async(this, null, function* () {
467
- const res = yield this.api("/v3/storefront/api-token", {
468
- method: "POST",
469
- headers: { "Content-Type": "application/json" },
470
- body: JSON.stringify({ expires_at: this.MAX_EPOC_EXPIRES_AT, channel_ids: [] })
471
- });
472
- if (!res.ok) {
473
- console.error(
474
- chalk.red(`
475
- POST /v3/storefront/api-token failed: ${res.status} ${res.statusText}
476
- `)
477
- );
478
- process.exit(1);
479
- }
480
- const BigCommerceStorefrontTokenSchema = z2.object({
481
- data: z2.object({
482
- token: z2.string()
483
- })
484
- });
485
- return parse(yield res.json(), BigCommerceStorefrontTokenSchema);
486
- });
487
- }
488
- sampleDataApi(path, opts = {}) {
489
- if (!this.sampleDataApiUrl || !this.storeHash || !this.accessToken) {
490
- throw new Error(
491
- "sampleDataApiUrl, storeHash, and accessToken are required to make API requests"
492
- );
471
+ save() {
472
+ var _a;
473
+ const configObj = {};
474
+ if (((_a = this.config.auth) == null ? void 0 : _a.storeHash) && this.config.auth.accessToken) {
475
+ configObj.auth = {
476
+ storeHash: this.config.auth.storeHash,
477
+ accessToken: this.config.auth.accessToken
478
+ };
493
479
  }
494
- const _a = opts, { headers = {} } = _a, rest = __objRest(_a, ["headers"]);
495
- const options = __spreadValues({
496
- method: "POST",
497
- headers: __spreadProps(__spreadValues({}, headers), {
498
- Accept: "application/json",
499
- "Content-Type": "application/json",
500
- "X-Auth-Token": this.accessToken,
501
- "User-Agent": this.userAgent
502
- })
503
- }, rest);
504
- return fetch(`${this.sampleDataApiUrl}/stores/${this.storeHash}${path}`, options);
505
- }
506
- checkEligibility() {
507
- return __async(this, null, function* () {
508
- const res = yield this.sampleDataApi("/v3/channels/catalyst/eligibility", {
509
- method: "GET"
510
- });
511
- if (!res.ok) {
512
- console.error(
513
- chalk.red(
514
- `
515
- GET /v3/channels/catalyst/eligibility failed: ${res.status} ${res.statusText}
516
- `
517
- )
518
- );
519
- process.exit(1);
520
- }
521
- const CheckEligibilitySchema = z2.object({
522
- data: z2.object({
523
- eligible: z2.boolean(),
524
- message: z2.string()
525
- })
526
- });
527
- return parse(yield res.json(), CheckEligibilitySchema);
528
- });
480
+ const dir = dirname(this.configPath);
481
+ if (!existsSync(dir)) {
482
+ mkdirSync(dir, { recursive: true });
483
+ }
484
+ const preamble = `# DO NOT commit this file to your repository!
485
+ # This file contains sensitive configuration specific to your local CLI setup.
486
+ # It includes authentication tokens and store-specific information.
487
+ # If using version control, make sure to add .catalyst to your .gitignore file.
488
+
489
+ `;
490
+ writeFileSync(this.configPath, preamble + stringifyTOML(configObj));
529
491
  }
530
- createChannel(channelName) {
531
- return __async(this, null, function* () {
532
- const res = yield this.sampleDataApi("/v3/channels/catalyst", {
533
- body: JSON.stringify({ name: channelName, tokenType: "normal" })
534
- });
535
- if (!res.ok) {
536
- console.error(
537
- chalk.red(`
538
- POST /v3/channels/catalyst failed: ${res.status} ${res.statusText}
539
- `)
540
- );
541
- process.exit(1);
492
+ read() {
493
+ if (!existsSync(this.configPath)) {
494
+ return {};
495
+ }
496
+ try {
497
+ const contents = readFileSync(this.configPath, "utf-8");
498
+ const parsed = parseTOML(contents);
499
+ if (isCatalystConfig(parsed)) {
500
+ return parsed;
542
501
  }
543
- const SampleDataChannelCreateSchema = z2.object({
544
- data: z2.object({
545
- id: z2.number(),
546
- name: z2.string().min(1),
547
- storefront_api_token: z2.string()
548
- })
549
- });
550
- return parse(yield res.json(), SampleDataChannelCreateSchema);
551
- });
502
+ console.warn("Invalid config format in .catalyst file, using defaults");
503
+ return {};
504
+ } catch (e) {
505
+ console.warn("Failed to parse .catalyst config file, using defaults");
506
+ return {};
507
+ }
552
508
  }
553
509
  };
554
510
 
555
- // src/utils/install-dependencies.ts
556
- import chalk2 from "chalk";
557
- import { installDependencies as installDeps } from "nypm";
558
-
559
- // src/utils/spinner.ts
560
- import { oraPromise } from "ora";
561
- var spinner = (action, oraOpts) => __async(void 0, null, function* () {
562
- return oraPromise(action, __spreadValues({
563
- spinner: "triangle"
564
- }, oraOpts)).catch(() => process.exit(1));
565
- });
566
-
567
- // src/utils/install-dependencies.ts
568
- var installAllDeps = (projectDir) => __async(void 0, null, function* () {
569
- yield installDeps({ cwd: projectDir, silent: true, packageManager: "pnpm" });
570
- });
571
- var installDependencies = (projectDir) => __async(void 0, null, function* () {
572
- return spinner(installAllDeps(projectDir), {
573
- text: `Installing dependencies. This could take a minute...`,
574
- successText: `Dependencies installed successfully`,
575
- failText: (err) => chalk2.red(`Failed to install dependencies: ${err.message}`)
576
- });
577
- });
578
-
579
511
  // src/utils/login.ts
580
- import { select } from "@inquirer/prompts";
581
- import chalk3 from "chalk";
582
- var poll = (auth, deviceCode, interval, expiresIn) => __async(void 0, null, function* () {
583
- const intervalMs = interval * 1e3;
584
- const expiresAtMs = expiresIn * 1e3;
585
- const retries = expiresAtMs / intervalMs;
586
- for (let i = 0; i < retries; i += 1) {
512
+ function pollDeviceCode(auth, deviceCode, interval) {
513
+ return __async(this, null, function* () {
587
514
  try {
588
- return yield auth.checkDeviceCode(deviceCode);
515
+ const credentials = yield auth.checkDeviceCode(deviceCode);
516
+ return credentials;
589
517
  } catch (e) {
518
+ yield new Promise((resolve) => setTimeout(resolve, interval * 1e3));
519
+ return null;
590
520
  }
591
- yield new Promise((resolve) => setTimeout(resolve, intervalMs));
592
- }
593
- console.error(chalk3.red("\nDevice code expired. Please try again.\n"));
594
- process.exit(1);
595
- });
596
- var login = (bigCommerceAuthUrl, storeHash, accessToken) => __async(void 0, null, function* () {
597
- if (storeHash && accessToken) {
598
- return { storeHash, accessToken };
599
- }
600
- const shouldLogin = yield select({
601
- message: "Would you like to connect to a BigCommerce store?",
602
- choices: [
603
- { name: "Yes", value: true },
604
- { name: "No", value: false }
605
- ]
606
521
  });
607
- if (!shouldLogin) {
608
- return { storeHash, accessToken };
609
- }
610
- const auth = new Https({ bigCommerceAuthUrl });
611
- const deviceCode = yield auth.getDeviceCode();
612
- console.log(
613
- [
614
- `
615
- Please visit ${chalk3.cyan(deviceCode.verification_uri)} and enter the code: ${chalk3.yellow(
616
- deviceCode.user_code
617
- )}
618
- `,
619
- `The code will expire in ${chalk3.yellow(`${deviceCode.expires_in / 60} minutes`)}
620
- `
621
- ].join("\n")
622
- );
623
- const { store_hash, access_token } = yield spinner(
624
- poll(auth, deviceCode.device_code, deviceCode.interval, deviceCode.expires_in),
625
- {
626
- text: "Waiting for device code to be authorized",
627
- successText: "Device code authorized\n",
628
- failText: "Device code expired\n"
522
+ }
523
+ function waitForCredentials(auth, deviceCode, interval) {
524
+ return __async(this, null, function* () {
525
+ const credentials = yield pollDeviceCode(auth, deviceCode, interval);
526
+ if (credentials) {
527
+ return credentials;
629
528
  }
630
- );
631
- return {
632
- storeHash: store_hash,
633
- accessToken: access_token
634
- };
635
- });
529
+ return waitForCredentials(auth, deviceCode, interval);
530
+ });
531
+ }
532
+ function waitForKeyPress(prompt) {
533
+ return __async(this, null, function* () {
534
+ const rl = createInterface({
535
+ input: process.stdin,
536
+ output: process.stdout
537
+ });
538
+ process.stdin.setRawMode(true);
539
+ process.stdin.resume();
540
+ return new Promise((resolve) => {
541
+ process.stdin.once("data", (data) => {
542
+ process.stdin.setRawMode(false);
543
+ process.stdin.pause();
544
+ rl.close();
545
+ const shouldProceed = data[0] !== 27;
546
+ process.stdout.write("\n");
547
+ resolve(shouldProceed);
548
+ });
549
+ rl.write(prompt);
550
+ });
551
+ });
552
+ }
553
+ function login(baseUrl) {
554
+ return __async(this, null, function* () {
555
+ const auth = new Auth({ baseUrl });
556
+ const deviceCode = yield auth.getDeviceCode();
557
+ console.log(
558
+ chalk2.cyan("\nPlease visit the following URL to authenticate with your BigCommerce store:")
559
+ );
560
+ console.log(chalk2.yellow(`
561
+ ${deviceCode.verification_uri}
562
+ `));
563
+ console.log(chalk2.cyan(`Enter code: `) + chalk2.yellow(`${deviceCode.user_code}
564
+ `));
565
+ const shouldOpenUrl = yield waitForKeyPress(
566
+ "Press any key to open the URL in your browser (or ESC to skip)..."
567
+ );
568
+ if (shouldOpenUrl) {
569
+ yield open(deviceCode.verification_uri);
570
+ }
571
+ const credentials = yield spinner(
572
+ () => waitForCredentials(auth, deviceCode.device_code, deviceCode.interval),
573
+ { text: "Waiting for authentication...", successText: "Authentication successful!" }
574
+ );
575
+ return {
576
+ storeHash: credentials.store_hash,
577
+ accessToken: credentials.access_token
578
+ };
579
+ });
580
+ }
581
+ function storeCredentials(projectDir, credentials) {
582
+ const config = new Config(projectDir);
583
+ config.setAuth(credentials.storeHash, credentials.accessToken);
584
+ }
636
585
 
637
586
  // src/utils/telemetry/telemetry.ts
638
587
  import { Analytics } from "@segment/analytics-node";
@@ -720,28 +669,13 @@ var Telemetry = class {
720
669
  };
721
670
 
722
671
  // src/utils/write-env.ts
723
- import { randomBytes as randomBytes2 } from "crypto";
724
672
  import { outputFileSync } from "fs-extra/esm";
725
- import { join } from "path";
726
- var writeEnv = (projectDir, {
727
- channelId,
728
- storeHash,
729
- storefrontToken,
730
- arbitraryEnv
731
- }) => {
673
+ import { join as join2 } from "path";
674
+ var writeEnv = (projectDir, envVars) => {
732
675
  outputFileSync(
733
- join(projectDir, ".env.local"),
734
- [
735
- `BIGCOMMERCE_STORE_HASH=${storeHash}`,
736
- `BIGCOMMERCE_CHANNEL_ID=${channelId}`,
737
- `BIGCOMMERCE_STOREFRONT_TOKEN=${storefrontToken}`,
738
- "",
739
- `AUTH_SECRET=${randomBytes2(32).toString("hex")}`,
740
- `CLIENT_LOGGER=false`,
741
- `ENABLE_ADMIN_ROUTE=true`,
742
- `DEFAULT_REVALIDATE_TARGET=3600`,
743
- arbitraryEnv == null ? void 0 : arbitraryEnv.map((env) => env).join("\n")
744
- ].join("\n")
676
+ join2(projectDir, ".env.local"),
677
+ `${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
678
+ `
745
679
  );
746
680
  };
747
681
 
@@ -751,285 +685,445 @@ function getPlatformCheckCommand(command) {
751
685
  return isWindows2 ? `where.exe ${command}` : `which ${command}`;
752
686
  }
753
687
  var telemetry = new Telemetry();
754
- 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("--gh-ref <ref>", "Clone a specific ref from the source repository").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(
755
- new Option("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
756
- ).addOption(
757
- new Option("--sample-data-api-url <url>", "BigCommerce sample data API URL").default("https://api.bc-sample.store").hideHelp()
758
- ).action((options) => __async(void 0, null, function* () {
759
- const { ghRef, repository } = options;
688
+ function handleChannelCreation(cliApi) {
689
+ return __async(this, null, function* () {
690
+ const newChannelName = yield input({
691
+ message: "What would you like to name your new channel?"
692
+ });
693
+ const shouldInstallSampleData = yield select({
694
+ message: "Would you like to install sample data?",
695
+ choices: [
696
+ { name: "Yes", value: true },
697
+ { name: "No", value: false }
698
+ ]
699
+ });
700
+ const response = yield cliApi.createChannel(newChannelName, shouldInstallSampleData);
701
+ if (!response.ok) {
702
+ console.error(
703
+ chalk3.red(`
704
+ POST /channels/catalyst failed: ${response.status} ${response.statusText}
705
+ `)
706
+ );
707
+ process.exit(1);
708
+ }
709
+ const channelData = yield response.json();
710
+ if (!isCreateChannelResponse(channelData)) {
711
+ console.error(chalk3.red("\nUnexpected response format from create channel endpoint\n"));
712
+ process.exit(1);
713
+ }
714
+ return {
715
+ channelId: channelData.data.id,
716
+ storefrontToken: channelData.data.storefront_api_token,
717
+ envVars: channelData.data.envVars
718
+ };
719
+ });
720
+ }
721
+ function handleChannelSelection(bc) {
722
+ return __async(this, null, function* () {
723
+ const channelSortOrder = ["catalyst", "next", "bigcommerce"];
724
+ const channelsResponse = yield bc.fetch("/v3/channels?available=true&type=storefront");
725
+ if (!channelsResponse.ok) {
726
+ console.error(
727
+ chalk3.red(
728
+ `
729
+ GET /v3/channels failed: ${channelsResponse.status} ${channelsResponse.statusText}
730
+ `
731
+ )
732
+ );
733
+ process.exit(1);
734
+ }
735
+ const availableChannels = yield channelsResponse.json();
736
+ if (!isChannelsResponse(availableChannels)) {
737
+ console.error(chalk3.red("\nUnexpected response format from channels endpoint\n"));
738
+ process.exit(1);
739
+ }
740
+ const existingChannel = yield select({
741
+ message: "Which channel would you like to use?",
742
+ choices: availableChannels.data.sort((a, b) => {
743
+ const aIndex = channelSortOrder.indexOf(a.platform);
744
+ const bIndex = channelSortOrder.indexOf(b.platform);
745
+ if (aIndex === -1 && bIndex === -1) {
746
+ return 0;
747
+ }
748
+ if (aIndex === -1) return 1;
749
+ if (bIndex === -1) return -1;
750
+ return aIndex - bIndex;
751
+ }).map((ch) => ({
752
+ name: ch.name,
753
+ value: ch,
754
+ description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
755
+ }))
756
+ });
757
+ return existingChannel.id;
758
+ });
759
+ }
760
+ function getChannelInit(cliApi, channelId) {
761
+ return __async(this, null, function* () {
762
+ const initResponse = yield cliApi.getChannelInit(channelId);
763
+ if (!initResponse.ok) {
764
+ console.error(
765
+ chalk3.red(
766
+ `
767
+ GET /channels/${channelId}/init failed: ${initResponse.status} ${initResponse.statusText}
768
+ `
769
+ )
770
+ );
771
+ process.exit(1);
772
+ }
773
+ const initData = yield initResponse.json();
774
+ if (!isInitResponse(initData)) {
775
+ console.error(chalk3.red("\nUnexpected response format from init endpoint\n"));
776
+ process.exit(1);
777
+ }
778
+ return {
779
+ storefrontToken: initData.data.storefront_api_token,
780
+ envVars: initData.data.envVars
781
+ };
782
+ });
783
+ }
784
+ function setupProject(options) {
785
+ return __async(this, null, function* () {
786
+ let { projectName, projectDir } = options;
787
+ if (!pathExistsSync(projectDir)) {
788
+ console.error(chalk3.red(`Error: --projectDir ${projectDir} is not a valid path
789
+ `));
790
+ process.exit(1);
791
+ }
792
+ if (projectName) {
793
+ projectName = kebabCase(projectName);
794
+ projectDir = join3(options.projectDir, projectName);
795
+ if (pathExistsSync(projectDir)) {
796
+ console.error(chalk3.red(`Error: ${projectDir} already exists
797
+ `));
798
+ process.exit(1);
799
+ }
800
+ }
801
+ if (!projectName) {
802
+ const validateProjectName = (i) => {
803
+ const formatted = kebabCase(i);
804
+ if (!formatted) return "Project name is required";
805
+ const targetDir = join3(options.projectDir, formatted);
806
+ if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
807
+ projectName = formatted;
808
+ projectDir = targetDir;
809
+ return true;
810
+ };
811
+ yield input({
812
+ message: "What is the name of your project?",
813
+ default: "my-catalyst-app",
814
+ validate: validateProjectName
815
+ });
816
+ }
817
+ if (!projectName) throw new Error("Something went wrong, projectName is not defined");
818
+ if (!projectDir) throw new Error("Something went wrong, projectDir is not defined");
819
+ return { projectName, projectDir };
820
+ });
821
+ }
822
+ function checkRequiredTools() {
760
823
  try {
761
824
  execSync5(getPlatformCheckCommand("git"), { stdio: "ignore" });
762
825
  } catch (e) {
763
- console.error(chalk4.red("Error: git is required to create a Catalyst project\n"));
826
+ console.error(chalk3.red("Error: git is required to create a Catalyst project\n"));
764
827
  process.exit(1);
765
828
  }
766
829
  try {
767
830
  execSync5(getPlatformCheckCommand("pnpm"), { stdio: "ignore" });
768
831
  } catch (e) {
769
- console.error(chalk4.red("Error: pnpm is required to create a Catalyst project\n"));
770
- console.error(chalk4.yellow("Tip: Enable it by running `corepack enable pnpm`\n"));
832
+ console.error(chalk3.red("Error: pnpm is required to create a Catalyst project\n"));
833
+ console.error(chalk3.yellow("Tip: Enable it by running `corepack enable pnpm`\n"));
771
834
  process.exit(1);
772
835
  }
773
- const URLSchema = z3.string().url();
774
- const sampleDataApiUrl = parse(options.sampleDataApiUrl, URLSchema);
775
- const bigcommerceApiUrl = parse(`https://api.${options.bigcommerceHostname}`, URLSchema);
776
- const bigcommerceAuthUrl = parse(`https://login.${options.bigcommerceHostname}`, URLSchema);
777
- const resetMain = options.resetMain;
778
- let projectName;
779
- let projectDir;
836
+ }
837
+ 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("--gh-ref <ref>", "Clone a specific ref from the source repository").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(
838
+ new Option("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
839
+ ).addOption(
840
+ new Option("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
841
+ ).action((options) => __async(void 0, null, function* () {
842
+ const { ghRef, repository } = options;
843
+ checkRequiredTools();
844
+ const { projectName, projectDir } = yield setupProject({
845
+ projectName: options.projectName,
846
+ projectDir: options.projectDir
847
+ });
780
848
  let storeHash = options.storeHash;
781
849
  let accessToken = options.accessToken;
782
850
  let channelId;
783
851
  let storefrontToken = options.storefrontToken;
852
+ let credentials;
784
853
  if (options.channelId) {
785
854
  channelId = parseInt(options.channelId, 10);
786
855
  }
787
- if (!pathExistsSync(options.projectDir)) {
788
- console.error(chalk4.red(`Error: --projectDir ${options.projectDir} is not a valid path
789
- `));
790
- process.exit(1);
791
- }
792
- if (options.projectName) {
793
- projectName = kebabCase(options.projectName);
794
- projectDir = join2(options.projectDir, projectName);
795
- if (pathExistsSync(projectDir)) {
796
- console.error(chalk4.red(`Error: ${projectDir} already exists
797
- `));
798
- process.exit(1);
799
- }
800
- }
801
- if (!options.projectName) {
802
- const validateProjectName = (i) => {
803
- const formatted = kebabCase(i);
804
- if (!formatted) return "Project name is required";
805
- const targetDir = join2(options.projectDir, formatted);
806
- if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
807
- projectName = formatted;
808
- projectDir = targetDir;
809
- return true;
810
- };
811
- yield input({
812
- message: "What is the name of your project?",
813
- default: "my-catalyst-app",
814
- validate: validateProjectName
815
- });
816
- }
817
- if (!projectName) throw new Error("Something went wrong, projectName is not defined");
818
- if (!projectDir) throw new Error("Something went wrong, projectDir is not defined");
819
- if (storeHash && channelId && storefrontToken) {
820
- console.log(`
821
- Creating '${projectName}' at '${projectDir}'
822
- `);
823
- cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain });
824
- writeEnv(projectDir, {
825
- channelId: channelId.toString(),
826
- storeHash,
827
- storefrontToken,
828
- arbitraryEnv: options.env
829
- });
830
- yield installDependencies(projectDir);
831
- console.log(
832
- `
833
- ${chalk4.green("Success!")} Created '${projectName}' at '${projectDir}'
834
- `,
835
- "\nNext steps:\n",
836
- chalk4.yellow(`
837
- cd ${projectName} && pnpm run dev
838
- `)
839
- );
840
- process.exit(0);
841
- }
842
- if (!options.storeHash || !options.accessToken) {
843
- const credentials = yield login(bigcommerceAuthUrl);
856
+ let envVars = {};
857
+ if ((!storeHash || !accessToken) && (!channelId || !storefrontToken)) {
858
+ credentials = yield login(`https://login.${options.bigcommerceHostname}`);
844
859
  storeHash = credentials.storeHash;
845
860
  accessToken = credentials.accessToken;
846
861
  }
847
- if (!storeHash || !accessToken) {
848
- console.log(`
862
+ if (storeHash && channelId && storefrontToken) {
863
+ envVars.BIGCOMMERCE_STORE_HASH = storeHash;
864
+ envVars.BIGCOMMERCE_CHANNEL_ID = channelId.toString();
865
+ envVars.BIGCOMMERCE_STOREFRONT_API_TOKEN = storefrontToken;
866
+ } else {
867
+ if (!storeHash || !accessToken) {
868
+ console.log(`
849
869
  Creating '${projectName}' at '${projectDir}'
850
870
  `);
851
- cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain });
852
- yield installDependencies(projectDir);
853
- console.log(
854
- [
855
- `
856
- ${chalk4.green("Success!")} Created '${projectName}' at '${projectDir}'
871
+ cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
872
+ yield installDependencies(projectDir);
873
+ if (options.env) {
874
+ const cliEnvVars = options.env.reduce((acc, env) => {
875
+ const [key, value] = env.split("=");
876
+ if (key && value) {
877
+ acc[key] = value;
878
+ }
879
+ return acc;
880
+ }, {});
881
+ Object.assign(envVars, cliEnvVars);
882
+ }
883
+ writeEnv(projectDir, envVars);
884
+ console.log(
885
+ [
886
+ `
887
+ ${chalk3.green("Success!")} Created '${projectName}' at '${projectDir}'
857
888
  `,
858
- `Next steps:`,
859
- chalk4.yellow(`
889
+ `Next steps:`,
890
+ Object.keys(envVars).length > 0 ? chalk3.yellow(`
891
+ - cd ${projectName} && pnpm run dev
892
+ `) : [
893
+ chalk3.yellow(`
860
894
  - cd ${projectName} && cp .env.example .env.local`),
861
- chalk4.yellow(`
895
+ chalk3.yellow(`
862
896
  - Populate .env.local with your BigCommerce API credentials
863
897
  `)
864
- ].join("\n")
865
- );
866
- process.exit(0);
867
- }
868
- yield telemetry.identify(storeHash);
869
- if (!channelId || !storefrontToken) {
870
- const bc = new Https({ bigCommerceApiUrl: bigcommerceApiUrl, storeHash, accessToken });
871
- const sampleDataApi = new Https({
872
- sampleDataApiUrl,
873
- storeHash,
874
- accessToken
875
- });
876
- const eligibilityResponse = yield sampleDataApi.checkEligibility();
877
- if (!eligibilityResponse.data.eligible) {
878
- console.warn(chalk4.yellow(eligibilityResponse.data.message));
898
+ ].join("")
899
+ ].join("\n")
900
+ );
901
+ process.exit(0);
879
902
  }
880
- let shouldCreateChannel;
881
- if (eligibilityResponse.data.eligible) {
882
- shouldCreateChannel = yield select2({
883
- message: "Would you like to create a new channel?",
884
- choices: [
885
- { name: "Yes", value: true },
886
- { name: "No", value: false }
887
- ]
903
+ yield telemetry.identify(storeHash);
904
+ if (!channelId || !storefrontToken) {
905
+ const bc = new Https({
906
+ baseUrl: `https://api.${options.bigcommerceHostname}/stores/${storeHash}`,
907
+ accessToken
888
908
  });
889
- }
890
- if (shouldCreateChannel) {
891
- const newChannelName = yield input({
892
- message: "What would you like to name your new channel?"
909
+ const cliApi = new CliApi({
910
+ origin: options.cliApiOrigin,
911
+ storeHash,
912
+ accessToken
893
913
  });
894
- const {
895
- data: { id: createdChannelId, storefront_api_token: storefrontApiToken }
896
- } = yield sampleDataApi.createChannel(newChannelName);
897
- yield bc.createChannelMenus(createdChannelId);
898
- channelId = createdChannelId;
899
- storefrontToken = storefrontApiToken;
900
- }
901
- if (!shouldCreateChannel) {
902
- const channelSortOrder = ["catalyst", "next", "bigcommerce"];
903
- const availableChannels = yield bc.channels("?available=true&type=storefront");
904
- const existingChannel = yield select2({
905
- message: "Which channel would you like to use?",
906
- choices: availableChannels.data.sort(
907
- (a, b) => channelSortOrder.indexOf(a.platform) - channelSortOrder.indexOf(b.platform)
908
- ).map((ch) => ({
909
- name: ch.name,
910
- value: ch,
911
- description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
912
- }))
913
- });
914
- channelId = existingChannel.id;
915
- const {
916
- data: { token: sfToken }
917
- } = yield bc.storefrontToken();
918
- storefrontToken = sfToken;
914
+ if (channelId && !storefrontToken) {
915
+ const initData = yield getChannelInit(cliApi, channelId);
916
+ envVars = __spreadValues({}, initData.envVars);
917
+ storefrontToken = initData.storefrontToken;
918
+ } else if (!channelId) {
919
+ const eligibilityResponse = yield cliApi.checkEligibility();
920
+ if (!eligibilityResponse.ok) {
921
+ console.error(
922
+ chalk3.red(
923
+ `
924
+ GET /channels/catalyst/eligibility failed: ${eligibilityResponse.status} ${eligibilityResponse.statusText}
925
+ `
926
+ )
927
+ );
928
+ process.exit(1);
929
+ }
930
+ const eligibilityData = yield eligibilityResponse.json();
931
+ if (!isEligibilityResponse(eligibilityData)) {
932
+ console.error(chalk3.red("\nUnexpected response format from eligibility endpoint\n"));
933
+ process.exit(1);
934
+ }
935
+ if (!eligibilityData.data.eligible) {
936
+ console.warn(chalk3.yellow(eligibilityData.data.message));
937
+ }
938
+ let shouldCreateChannel;
939
+ if (eligibilityData.data.eligible) {
940
+ shouldCreateChannel = yield select({
941
+ message: "Would you like to create a new channel?",
942
+ choices: [
943
+ { name: "Yes", value: true },
944
+ { name: "No", value: false }
945
+ ]
946
+ });
947
+ }
948
+ if (shouldCreateChannel) {
949
+ const channelData = yield handleChannelCreation(cliApi);
950
+ channelId = channelData.channelId;
951
+ storefrontToken = channelData.storefrontToken;
952
+ envVars = __spreadValues({}, channelData.envVars);
953
+ console.log(chalk3.green(`Channel created successfully`));
954
+ }
955
+ if (!shouldCreateChannel) {
956
+ channelId = yield handleChannelSelection(bc);
957
+ const initData = yield getChannelInit(cliApi, channelId);
958
+ envVars = __spreadValues({}, initData.envVars);
959
+ storefrontToken = initData.storefrontToken;
960
+ }
961
+ }
919
962
  }
920
963
  }
964
+ if (options.env) {
965
+ const cliEnvVars = options.env.reduce((acc, env) => {
966
+ const [key, value] = env.split("=");
967
+ if (key && value) {
968
+ acc[key] = value;
969
+ }
970
+ return acc;
971
+ }, {});
972
+ Object.assign(envVars, cliEnvVars);
973
+ }
974
+ if (options.storeHash) {
975
+ envVars.BIGCOMMERCE_STORE_HASH = options.storeHash;
976
+ }
977
+ if (options.channelId) {
978
+ envVars.BIGCOMMERCE_CHANNEL_ID = options.channelId;
979
+ }
980
+ if (options.storefrontToken) {
981
+ envVars.BIGCOMMERCE_STOREFRONT_TOKEN = options.storefrontToken;
982
+ }
921
983
  if (!channelId) throw new Error("Something went wrong, channelId is not defined");
922
984
  if (!storefrontToken) throw new Error("Something went wrong, storefrontToken is not defined");
923
985
  console.log(`
924
986
  Creating '${projectName}' at '${projectDir}'
925
987
  `);
926
- cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain });
927
- writeEnv(projectDir, {
928
- channelId: channelId.toString(),
929
- storeHash,
930
- storefrontToken,
931
- arbitraryEnv: options.env
932
- });
988
+ cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
933
989
  yield installDependencies(projectDir);
990
+ writeEnv(projectDir, envVars);
991
+ if (credentials) {
992
+ storeCredentials(projectDir, credentials);
993
+ }
934
994
  console.log(
935
995
  `
936
- ${chalk4.green("Success!")} Created '${projectName}' at '${projectDir}'
996
+ ${chalk3.green("Success!")} Created '${projectName}' at '${projectDir}'
937
997
  `,
938
998
  "\nNext steps:\n",
939
- chalk4.yellow(`
999
+ chalk3.yellow(`
940
1000
  cd ${projectName} && pnpm run dev
941
1001
  `)
942
1002
  );
943
1003
  }));
1004
+ function isInitResponse(response) {
1005
+ 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;
1006
+ }
1007
+ function isEligibilityResponse(response) {
1008
+ return typeof response === "object" && response !== null && "data" in response && typeof response.data === "object" && response.data !== null && "eligible" in response.data && "message" in response.data;
1009
+ }
1010
+ function isCreateChannelResponse(response) {
1011
+ 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;
1012
+ }
1013
+ function isChannelsResponse(response) {
1014
+ return typeof response === "object" && response !== null && "data" in response && Array.isArray(response.data) && response.data.every(
1015
+ (item) => typeof item === "object" && item !== null && "id" in item && "name" in item && "platform" in item
1016
+ );
1017
+ }
944
1018
 
945
1019
  // src/commands/init.ts
946
1020
  import { Command as Command2, Option as Option2 } from "@commander-js/extra-typings";
947
- import { input as input2, select as select3 } from "@inquirer/prompts";
948
- import chalk5 from "chalk";
949
- import * as z4 from "zod";
1021
+ import { select as select2 } from "@inquirer/prompts";
1022
+ import chalk4 from "chalk";
1023
+ function isChannelsResponse2(response) {
1024
+ return typeof response === "object" && response !== null && "data" in response && Array.isArray(response.data) && response.data.every(
1025
+ (item) => typeof item === "object" && item !== null && "id" in item && "name" in item && "platform" in item
1026
+ );
1027
+ }
1028
+ function isInitResponse2(response) {
1029
+ 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;
1030
+ }
950
1031
  var telemetry2 = new Telemetry();
951
- 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").addOption(
1032
+ 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(
952
1033
  new Option2("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
953
1034
  ).addOption(
954
- new Option2("--sample-data-api-url <url>", "BigCommerce sample data API URL").default("https://api.bc-sample.store").hideHelp()
1035
+ new Option2("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
955
1036
  ).action((options) => __async(void 0, null, function* () {
956
1037
  const projectDir = process.cwd();
957
- const URLSchema = z4.string().url();
958
- const sampleDataApiUrl = parse(options.sampleDataApiUrl, URLSchema);
959
- const bigCommerceApiUrl = `https://api.${options.bigcommerceHostname}`;
960
- const bigCommerceAuthUrl = `https://login.${options.bigcommerceHostname}`;
961
1038
  let storeHash = options.storeHash;
962
1039
  let accessToken = options.accessToken;
963
- let channelId;
964
- let storefrontToken;
965
- if (!options.storeHash || !options.accessToken) {
966
- const credentials = yield login(bigCommerceAuthUrl);
967
- storeHash = credentials.storeHash;
968
- accessToken = credentials.accessToken;
1040
+ if (!storeHash || !accessToken) {
1041
+ const config = new Config(projectDir);
1042
+ const storedAuth = config.getAuth();
1043
+ storeHash = storeHash != null ? storeHash : storedAuth.storeHash;
1044
+ accessToken = accessToken != null ? accessToken : storedAuth.accessToken;
969
1045
  }
970
1046
  if (!storeHash || !accessToken) {
971
- console.log(
972
- chalk5.yellow("\nYou must authenticate with a store to overwrite your local environment.\n")
973
- );
974
- process.exit(1);
1047
+ const credentials = yield login(`https://login.${options.bigcommerceHostname}`);
1048
+ storeHash = credentials.storeHash;
1049
+ accessToken = credentials.accessToken;
1050
+ storeCredentials(projectDir, credentials);
975
1051
  }
976
1052
  yield telemetry2.identify(storeHash);
977
- const bc = new Https({ bigCommerceApiUrl, storeHash, accessToken });
978
- const sampleDataApi = new Https({
979
- sampleDataApiUrl,
1053
+ const bc = new Https({
1054
+ baseUrl: `https://api.${options.bigcommerceHostname}/stores/${storeHash}`,
1055
+ accessToken
1056
+ });
1057
+ const cliApi = new CliApi({
1058
+ origin: options.cliApiOrigin,
980
1059
  storeHash,
981
1060
  accessToken
982
1061
  });
983
- const eligibilityResponse = yield sampleDataApi.checkEligibility();
984
- if (!eligibilityResponse.data.eligible) {
985
- console.warn(chalk5.yellow(eligibilityResponse.data.message));
1062
+ const channelSortOrder = ["catalyst", "next", "bigcommerce"];
1063
+ const channelsResponse = yield bc.fetch("/v3/channels?available=true&type=storefront");
1064
+ if (!channelsResponse.ok) {
1065
+ console.error(
1066
+ chalk4.red(
1067
+ `
1068
+ GET /v3/channels failed: ${channelsResponse.status} ${channelsResponse.statusText}
1069
+ `
1070
+ )
1071
+ );
1072
+ process.exit(1);
986
1073
  }
987
- let shouldCreateChannel;
988
- if (eligibilityResponse.data.eligible) {
989
- shouldCreateChannel = yield select3({
990
- message: "Would you like to create a new channel?",
991
- choices: [
992
- { name: "Yes", value: true },
993
- { name: "No", value: false }
994
- ]
995
- });
1074
+ const availableChannels = yield channelsResponse.json();
1075
+ if (!isChannelsResponse2(availableChannels)) {
1076
+ console.error(chalk4.red("\nUnexpected response format from channels endpoint\n"));
1077
+ process.exit(1);
996
1078
  }
997
- if (shouldCreateChannel) {
998
- const newChannelName = yield input2({
999
- message: "What would you like to name your new channel?"
1000
- });
1001
- const {
1002
- data: { id: createdChannelId, storefront_api_token: storefrontApiToken }
1003
- } = yield sampleDataApi.createChannel(newChannelName);
1004
- channelId = createdChannelId;
1005
- storefrontToken = storefrontApiToken;
1079
+ const existingChannel = yield select2({
1080
+ message: "Which channel would you like to use?",
1081
+ choices: availableChannels.data.sort(
1082
+ (a, b) => channelSortOrder.indexOf(a.platform) - channelSortOrder.indexOf(b.platform)
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
+ const channelId = existingChannel.id;
1090
+ const initResponse = yield cliApi.getChannelInit(channelId);
1091
+ if (!initResponse.ok) {
1092
+ console.error(
1093
+ chalk4.red(
1094
+ `
1095
+ GET /channels/${channelId}/init failed: ${initResponse.status} ${initResponse.statusText}
1096
+ `
1097
+ )
1098
+ );
1099
+ process.exit(1);
1006
1100
  }
1007
- if (!shouldCreateChannel) {
1008
- const channelSortOrder = ["catalyst", "next", "bigcommerce"];
1009
- const availableChannels = yield bc.channels("?available=true&type=storefront");
1010
- const existingChannel = yield select3({
1011
- message: "Which channel would you like to use?",
1012
- choices: availableChannels.data.sort(
1013
- (a, b) => channelSortOrder.indexOf(a.platform) - channelSortOrder.indexOf(b.platform)
1014
- ).map((ch) => ({
1015
- name: ch.name,
1016
- value: ch,
1017
- description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
1018
- }))
1019
- });
1020
- channelId = existingChannel.id;
1021
- const {
1022
- data: { token: sfToken }
1023
- } = yield bc.storefrontToken();
1024
- storefrontToken = sfToken;
1101
+ const initData = yield initResponse.json();
1102
+ if (!isInitResponse2(initData)) {
1103
+ console.error(chalk4.red("\nUnexpected response format from init endpoint\n"));
1104
+ process.exit(1);
1025
1105
  }
1026
- if (!channelId) throw new Error("Something went wrong, channelId is not defined");
1027
- if (!storefrontToken) throw new Error("Something went wrong, storefrontToken is not defined");
1028
- writeEnv(projectDir, {
1029
- channelId: channelId.toString(),
1030
- storeHash,
1031
- storefrontToken
1032
- });
1106
+ const envVars = __spreadValues({}, initData.data.envVars);
1107
+ if (options.env) {
1108
+ const cliEnvVars = options.env.reduce((acc, env) => {
1109
+ const [key, value] = env.split("=");
1110
+ if (key && value) {
1111
+ acc[key] = value;
1112
+ }
1113
+ return acc;
1114
+ }, {});
1115
+ Object.assign(envVars, cliEnvVars);
1116
+ }
1117
+ writeEnv(projectDir, envVars);
1118
+ console.log(chalk4.green(`
1119
+ .env.local file created for channel ${existingChannel.name}!
1120
+ `));
1121
+ console.log(chalk4.green(`
1122
+ Next steps:
1123
+ `));
1124
+ console.log(chalk4.yellow(`
1125
+ pnpm run dev
1126
+ `));
1033
1127
  }));
1034
1128
 
1035
1129
  // src/commands/integration.ts
@@ -1040,16 +1134,16 @@ import { outputFileSync as outputFileSync2, writeJsonSync } from "fs-extra/esm";
1040
1134
  import kebabCase2 from "lodash.kebabcase";
1041
1135
  import { coerce, compare } from "semver";
1042
1136
  import { promisify } from "util";
1043
- import { z as z5 } from "zod";
1137
+ import { z as z3 } from "zod";
1044
1138
  var exec = promisify(execCb);
1045
- var ManifestSchema = z5.object({
1046
- name: z5.string(),
1047
- dependencies: z5.object({ add: z5.array(z5.string()) }),
1048
- devDependencies: z5.object({ add: z5.array(z5.string()) }),
1049
- environmentVariables: z5.array(z5.string())
1139
+ var ManifestSchema = z3.object({
1140
+ name: z3.string(),
1141
+ dependencies: z3.object({ add: z3.array(z3.string()) }),
1142
+ devDependencies: z3.object({ add: z3.array(z3.string()) }),
1143
+ environmentVariables: z3.array(z3.string())
1050
1144
  });
1051
1145
  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(void 0, null, function* () {
1052
- const integrationName = z5.string().transform(kebabCase2).parse(integrationNameRaw);
1146
+ const integrationName = z3.string().transform(kebabCase2).parse(integrationNameRaw);
1053
1147
  const manifest = {
1054
1148
  name: integrationName,
1055
1149
  dependencies: { add: [] },
@@ -1071,9 +1165,9 @@ var integration = new Command3("integration").argument("<integration-name>", "Fo
1071
1165
  }
1072
1166
  return 0;
1073
1167
  }).reverse();
1074
- const PackageDependenciesSchema = z5.object({
1075
- dependencies: z5.object({}).passthrough(),
1076
- devDependencies: z5.object({}).passthrough()
1168
+ const PackageDependenciesSchema = z3.object({
1169
+ dependencies: z3.object({}).passthrough(),
1170
+ devDependencies: z3.object({}).passthrough()
1077
1171
  });
1078
1172
  const getPackageDeps = (ref) => __async(void 0, null, function* () {
1079
1173
  const { stdout } = yield exec(`git show ${ref}:core/package.json`);
@@ -1109,7 +1203,7 @@ var integration = new Command3("integration").argument("<integration-name>", "Fo
1109
1203
  import { Argument, Command as Command4, Option as Option3 } from "@commander-js/extra-typings";
1110
1204
 
1111
1205
  // src/utils/telemetry/index.ts
1112
- import chalk6 from "chalk";
1206
+ import chalk5 from "chalk";
1113
1207
  var telemetry3 = new Telemetry();
1114
1208
  var isEnabled = telemetry3.isEnabled();
1115
1209
  var catalystTelemetry = (options, arg) => {
@@ -1130,7 +1224,7 @@ var catalystTelemetry = (options, arg) => {
1130
1224
  }
1131
1225
  console.log(
1132
1226
  `
1133
- Status: ${chalk6.bold(isEnabled ? chalk6.green("Enabled") : chalk6.red("Disabled"))}`
1227
+ Status: ${chalk5.bold(isEnabled ? chalk5.green("Enabled") : chalk5.red("Disabled"))}`
1134
1228
  );
1135
1229
  if (!isEnabled) {
1136
1230
  console.log(
@@ -1183,7 +1277,7 @@ var telemetryPostHook = () => __async(void 0, null, function* () {
1183
1277
  });
1184
1278
 
1185
1279
  // src/index.ts
1186
- console.log(chalk7.cyanBright(`
1280
+ console.log(chalk6.cyanBright(`
1187
1281
  \u25E2 ${package_default.name} v${package_default.version}
1188
1282
  `));
1189
1283
  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).addCommand(telemetry4).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);