@bigcommerce/create-catalyst 0.18.0 → 0.20.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.20.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";
@@ -209,7 +320,7 @@ var cloneCatalyst = ({
209
320
  console.log();
210
321
  if (ghRef) {
211
322
  if (resetMain) {
212
- checkoutRef(projectDir, "main");
323
+ execSync4("git checkout -b main", { cwd: projectDir, stdio: "inherit" });
213
324
  resetBranchToRef(projectDir, ghRef);
214
325
  console.log(`Reset main to ${ghRef} successfully.`);
215
326
  console.log();
@@ -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,149 @@ 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
- );
404
- }
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);
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;
414
448
  }
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
- });
449
+ if ("auth" in obj) {
450
+ if (!isRecord(obj.auth)) {
451
+ return false;
452
+ }
453
+ const { storeHash, accessToken } = obj.auth;
454
+ return (storeHash === void 0 || typeof storeHash === "string") && (accessToken === void 0 || typeof accessToken === "string");
426
455
  }
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
- });
456
+ return true;
457
+ }
458
+ var Config = class {
459
+ constructor(projectDir) {
460
+ this.configPath = join(projectDir, ".catalyst");
461
+ this.config = this.read();
438
462
  }
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
- });
463
+ getAuth() {
464
+ var _a;
465
+ return (_a = this.config.auth) != null ? _a : {};
464
466
  }
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
- });
467
+ setAuth(storeHash, accessToken) {
468
+ this.config.auth = { storeHash, accessToken };
469
+ this.save();
487
470
  }
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] === 13 || data[0] === 10;
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.yellow("\n! First copy your one-time code: ") + chalk2.bold(deviceCode.user_code)
559
+ );
560
+ console.log(`Press Enter to open ${deviceCode.verification_uri} in your browser...`);
561
+ const shouldOpenUrl = yield waitForKeyPress("");
562
+ if (shouldOpenUrl) {
563
+ yield open(deviceCode.verification_uri);
564
+ }
565
+ const credentials = yield spinner(
566
+ () => waitForCredentials(auth, deviceCode.device_code, deviceCode.interval),
567
+ { text: "Waiting for authentication...", successText: "Authentication complete" }
568
+ );
569
+ return {
570
+ storeHash: credentials.store_hash,
571
+ accessToken: credentials.access_token
572
+ };
573
+ });
574
+ }
575
+ function storeCredentials(projectDir, credentials) {
576
+ const config = new Config(projectDir);
577
+ config.setAuth(credentials.storeHash, credentials.accessToken);
578
+ }
636
579
 
637
580
  // src/utils/telemetry/telemetry.ts
638
581
  import { Analytics } from "@segment/analytics-node";
@@ -720,28 +663,13 @@ var Telemetry = class {
720
663
  };
721
664
 
722
665
  // src/utils/write-env.ts
723
- import { randomBytes as randomBytes2 } from "crypto";
724
666
  import { outputFileSync } from "fs-extra/esm";
725
- import { join } from "path";
726
- var writeEnv = (projectDir, {
727
- channelId,
728
- storeHash,
729
- storefrontToken,
730
- arbitraryEnv
731
- }) => {
667
+ import { join as join2 } from "path";
668
+ var writeEnv = (projectDir, envVars) => {
732
669
  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")
670
+ join2(projectDir, ".env.local"),
671
+ `${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
672
+ `
745
673
  );
746
674
  };
747
675
 
@@ -751,285 +679,454 @@ function getPlatformCheckCommand(command) {
751
679
  return isWindows2 ? `where.exe ${command}` : `which ${command}`;
752
680
  }
753
681
  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;
682
+ function handleChannelCreation(cliApi) {
683
+ return __async(this, null, function* () {
684
+ const newChannelName = yield input({
685
+ message: "What would you like to name your new channel?"
686
+ });
687
+ const shouldInstallSampleData = yield select({
688
+ message: "Would you like to install sample data?",
689
+ choices: [
690
+ { name: "Yes", value: true },
691
+ { name: "No", value: false }
692
+ ]
693
+ });
694
+ const response = yield cliApi.createChannel(newChannelName, shouldInstallSampleData);
695
+ if (!response.ok) {
696
+ console.error(
697
+ chalk3.red(`
698
+ POST /channels/catalyst failed: ${response.status} ${response.statusText}
699
+ `)
700
+ );
701
+ process.exit(1);
702
+ }
703
+ const channelData = yield response.json();
704
+ if (!isCreateChannelResponse(channelData)) {
705
+ console.error(chalk3.red("\nUnexpected response format from create channel endpoint\n"));
706
+ process.exit(1);
707
+ }
708
+ return {
709
+ channelId: channelData.data.id,
710
+ storefrontToken: channelData.data.storefront_api_token,
711
+ envVars: channelData.data.envVars
712
+ };
713
+ });
714
+ }
715
+ function handleChannelSelection(bc) {
716
+ return __async(this, null, function* () {
717
+ const channelSortOrder = ["catalyst", "next", "bigcommerce"];
718
+ const channelsResponse = yield bc.fetch("/v3/channels?available=true&type=storefront");
719
+ if (!channelsResponse.ok) {
720
+ console.error(
721
+ chalk3.red(
722
+ `
723
+ GET /v3/channels failed: ${channelsResponse.status} ${channelsResponse.statusText}
724
+ `
725
+ )
726
+ );
727
+ process.exit(1);
728
+ }
729
+ const availableChannels = yield channelsResponse.json();
730
+ if (!isChannelsResponse(availableChannels)) {
731
+ console.error(chalk3.red("\nUnexpected response format from channels endpoint\n"));
732
+ process.exit(1);
733
+ }
734
+ const existingChannel = yield select({
735
+ message: "Which channel would you like to use?",
736
+ choices: availableChannels.data.sort((a, b) => {
737
+ const aIndex = channelSortOrder.indexOf(a.platform);
738
+ const bIndex = channelSortOrder.indexOf(b.platform);
739
+ if (aIndex === -1 && bIndex === -1) {
740
+ return 0;
741
+ }
742
+ if (aIndex === -1) return 1;
743
+ if (bIndex === -1) return -1;
744
+ return aIndex - bIndex;
745
+ }).map((ch) => ({
746
+ name: ch.name,
747
+ value: ch,
748
+ description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
749
+ }))
750
+ });
751
+ return existingChannel.id;
752
+ });
753
+ }
754
+ function getChannelInit(cliApi, channelId) {
755
+ return __async(this, null, function* () {
756
+ const initResponse = yield cliApi.getChannelInit(channelId);
757
+ if (!initResponse.ok) {
758
+ console.error(
759
+ chalk3.red(
760
+ `
761
+ GET /channels/${channelId}/init failed: ${initResponse.status} ${initResponse.statusText}
762
+ `
763
+ )
764
+ );
765
+ process.exit(1);
766
+ }
767
+ const initData = yield initResponse.json();
768
+ if (!isInitResponse(initData)) {
769
+ console.error(chalk3.red("\nUnexpected response format from init endpoint\n"));
770
+ process.exit(1);
771
+ }
772
+ return {
773
+ storefrontToken: initData.data.storefront_api_token,
774
+ envVars: initData.data.envVars
775
+ };
776
+ });
777
+ }
778
+ function setupProject(options) {
779
+ return __async(this, null, function* () {
780
+ let { projectName, projectDir } = options;
781
+ if (!pathExistsSync(projectDir)) {
782
+ console.error(chalk3.red(`Error: --projectDir ${projectDir} is not a valid path
783
+ `));
784
+ process.exit(1);
785
+ }
786
+ if (projectName) {
787
+ projectName = kebabCase(projectName);
788
+ projectDir = join3(options.projectDir, projectName);
789
+ if (pathExistsSync(projectDir)) {
790
+ console.error(chalk3.red(`Error: ${projectDir} already exists
791
+ `));
792
+ process.exit(1);
793
+ }
794
+ }
795
+ if (!projectName) {
796
+ const validateProjectName = (i) => {
797
+ const formatted = kebabCase(i);
798
+ if (!formatted) return "Project name is required";
799
+ const targetDir = join3(options.projectDir, formatted);
800
+ if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
801
+ projectName = formatted;
802
+ projectDir = targetDir;
803
+ return true;
804
+ };
805
+ yield input({
806
+ message: "What is the name of your project?",
807
+ default: "my-catalyst-app",
808
+ validate: validateProjectName
809
+ });
810
+ }
811
+ if (!projectName) throw new Error("Something went wrong, projectName is not defined");
812
+ if (!projectDir) throw new Error("Something went wrong, projectDir is not defined");
813
+ return { projectName, projectDir };
814
+ });
815
+ }
816
+ function checkRequiredTools() {
760
817
  try {
761
818
  execSync5(getPlatformCheckCommand("git"), { stdio: "ignore" });
762
819
  } catch (e) {
763
- console.error(chalk4.red("Error: git is required to create a Catalyst project\n"));
820
+ console.error(chalk3.red("Error: git is required to create a Catalyst project\n"));
764
821
  process.exit(1);
765
822
  }
766
823
  try {
767
824
  execSync5(getPlatformCheckCommand("pnpm"), { stdio: "ignore" });
768
825
  } 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"));
826
+ console.error(chalk3.red("Error: pnpm is required to create a Catalyst project\n"));
827
+ console.error(chalk3.yellow("Tip: Enable it by running `corepack enable pnpm`\n"));
771
828
  process.exit(1);
772
829
  }
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;
830
+ }
831
+ 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(
832
+ "--gh-ref <ref>",
833
+ "Clone a specific ref from the source repository",
834
+ "@bigcommerce/catalyst-core@latest"
835
+ ).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(
836
+ new Option("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
837
+ ).addOption(
838
+ new Option("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
839
+ ).action((options) => __async(void 0, null, function* () {
840
+ const { ghRef, repository } = options;
841
+ checkRequiredTools();
842
+ const { projectName, projectDir } = yield setupProject({
843
+ projectName: options.projectName,
844
+ projectDir: options.projectDir
845
+ });
780
846
  let storeHash = options.storeHash;
781
847
  let accessToken = options.accessToken;
782
848
  let channelId;
783
849
  let storefrontToken = options.storefrontToken;
850
+ let credentials;
784
851
  if (options.channelId) {
785
852
  channelId = parseInt(options.channelId, 10);
786
853
  }
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);
854
+ let envVars = {};
855
+ if ((!storeHash || !accessToken) && (!channelId || !storefrontToken)) {
856
+ credentials = yield login(`https://login.${options.bigcommerceHostname}`);
844
857
  storeHash = credentials.storeHash;
845
858
  accessToken = credentials.accessToken;
846
859
  }
847
- if (!storeHash || !accessToken) {
848
- console.log(`
860
+ if (storeHash && channelId && storefrontToken) {
861
+ envVars.BIGCOMMERCE_STORE_HASH = storeHash;
862
+ envVars.BIGCOMMERCE_CHANNEL_ID = channelId.toString();
863
+ envVars.BIGCOMMERCE_STOREFRONT_API_TOKEN = storefrontToken;
864
+ } else {
865
+ if (!storeHash || !accessToken) {
866
+ console.log(`
849
867
  Creating '${projectName}' at '${projectDir}'
850
868
  `);
851
- cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain });
852
- yield installDependencies(projectDir);
853
- console.log(
854
- [
855
- `
856
- ${chalk4.green("Success!")} Created '${projectName}' at '${projectDir}'
869
+ cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
870
+ yield installDependencies(projectDir);
871
+ if (options.env) {
872
+ const cliEnvVars = options.env.reduce((acc, env) => {
873
+ const [key, value] = env.split("=");
874
+ if (key && value) {
875
+ acc[key] = value;
876
+ }
877
+ return acc;
878
+ }, {});
879
+ Object.assign(envVars, cliEnvVars);
880
+ }
881
+ writeEnv(projectDir, envVars);
882
+ console.log(
883
+ [
884
+ `
885
+ ${chalk3.green("Success!")} Created '${projectName}' at '${projectDir}'
857
886
  `,
858
- `Next steps:`,
859
- chalk4.yellow(`
887
+ `Next steps:`,
888
+ Object.keys(envVars).length > 0 ? chalk3.yellow(`
889
+ - cd ${projectName} && pnpm run dev
890
+ `) : [
891
+ chalk3.yellow(`
860
892
  - cd ${projectName} && cp .env.example .env.local`),
861
- chalk4.yellow(`
893
+ chalk3.yellow(`
862
894
  - Populate .env.local with your BigCommerce API credentials
863
895
  `)
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));
879
- }
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
- ]
888
- });
896
+ ].join("")
897
+ ].join("\n")
898
+ );
899
+ process.exit(0);
889
900
  }
890
- if (shouldCreateChannel) {
891
- const newChannelName = yield input({
892
- message: "What would you like to name your new channel?"
901
+ yield telemetry.identify(storeHash);
902
+ if (!channelId || !storefrontToken) {
903
+ const bc = new Https({
904
+ baseUrl: `https://api.${options.bigcommerceHostname}/stores/${storeHash}`,
905
+ accessToken
893
906
  });
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
- }))
907
+ const cliApi = new CliApi({
908
+ origin: options.cliApiOrigin,
909
+ storeHash,
910
+ accessToken
913
911
  });
914
- channelId = existingChannel.id;
915
- const {
916
- data: { token: sfToken }
917
- } = yield bc.storefrontToken();
918
- storefrontToken = sfToken;
912
+ if (channelId && !storefrontToken) {
913
+ const initData = yield getChannelInit(cliApi, channelId);
914
+ envVars = __spreadValues({}, initData.envVars);
915
+ storefrontToken = initData.storefrontToken;
916
+ } else if (!channelId) {
917
+ const eligibilityResponse = yield cliApi.checkEligibility();
918
+ if (!eligibilityResponse.ok) {
919
+ console.error(
920
+ chalk3.red(
921
+ `
922
+ GET /channels/catalyst/eligibility failed: ${eligibilityResponse.status} ${eligibilityResponse.statusText}
923
+ `
924
+ )
925
+ );
926
+ process.exit(1);
927
+ }
928
+ const eligibilityData = yield eligibilityResponse.json();
929
+ if (!isEligibilityResponse(eligibilityData)) {
930
+ console.error(chalk3.red("\nUnexpected response format from eligibility endpoint\n"));
931
+ process.exit(1);
932
+ }
933
+ if (!eligibilityData.data.eligible) {
934
+ console.warn(chalk3.yellow(eligibilityData.data.message));
935
+ }
936
+ let shouldCreateChannel;
937
+ if (eligibilityData.data.eligible) {
938
+ shouldCreateChannel = yield select({
939
+ message: "Would you like to create a new channel?",
940
+ choices: [
941
+ { name: "Yes", value: true },
942
+ { name: "No", value: false }
943
+ ]
944
+ });
945
+ }
946
+ if (shouldCreateChannel) {
947
+ const channelData = yield handleChannelCreation(cliApi);
948
+ channelId = channelData.channelId;
949
+ storefrontToken = channelData.storefrontToken;
950
+ envVars = __spreadValues({}, channelData.envVars);
951
+ console.log(chalk3.green(`Channel created successfully`));
952
+ console.warn(
953
+ chalk3.yellow(
954
+ "\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."
955
+ )
956
+ );
957
+ }
958
+ if (!shouldCreateChannel) {
959
+ channelId = yield handleChannelSelection(bc);
960
+ const initData = yield getChannelInit(cliApi, channelId);
961
+ envVars = __spreadValues({}, initData.envVars);
962
+ storefrontToken = initData.storefrontToken;
963
+ }
964
+ }
919
965
  }
920
966
  }
967
+ if (options.env) {
968
+ const cliEnvVars = options.env.reduce((acc, env) => {
969
+ const [key, value] = env.split("=");
970
+ if (key && value) {
971
+ acc[key] = value;
972
+ }
973
+ return acc;
974
+ }, {});
975
+ Object.assign(envVars, cliEnvVars);
976
+ }
977
+ if (options.storeHash) {
978
+ envVars.BIGCOMMERCE_STORE_HASH = options.storeHash;
979
+ }
980
+ if (options.channelId) {
981
+ envVars.BIGCOMMERCE_CHANNEL_ID = options.channelId;
982
+ }
983
+ if (options.storefrontToken) {
984
+ envVars.BIGCOMMERCE_STOREFRONT_TOKEN = options.storefrontToken;
985
+ }
921
986
  if (!channelId) throw new Error("Something went wrong, channelId is not defined");
922
987
  if (!storefrontToken) throw new Error("Something went wrong, storefrontToken is not defined");
923
988
  console.log(`
924
989
  Creating '${projectName}' at '${projectDir}'
925
990
  `);
926
- cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain });
927
- writeEnv(projectDir, {
928
- channelId: channelId.toString(),
929
- storeHash,
930
- storefrontToken,
931
- arbitraryEnv: options.env
932
- });
991
+ cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
933
992
  yield installDependencies(projectDir);
993
+ writeEnv(projectDir, envVars);
994
+ if (credentials) {
995
+ storeCredentials(projectDir, credentials);
996
+ }
934
997
  console.log(
935
998
  `
936
- ${chalk4.green("Success!")} Created '${projectName}' at '${projectDir}'
999
+ ${chalk3.green("Success!")} Created '${projectName}' at '${projectDir}'
937
1000
  `,
938
1001
  "\nNext steps:\n",
939
- chalk4.yellow(`
1002
+ chalk3.yellow(`
940
1003
  cd ${projectName} && pnpm run dev
941
1004
  `)
942
1005
  );
943
1006
  }));
1007
+ function isInitResponse(response) {
1008
+ 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;
1009
+ }
1010
+ function isEligibilityResponse(response) {
1011
+ return typeof response === "object" && response !== null && "data" in response && typeof response.data === "object" && response.data !== null && "eligible" in response.data && "message" in response.data;
1012
+ }
1013
+ function isCreateChannelResponse(response) {
1014
+ 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;
1015
+ }
1016
+ function isChannelsResponse(response) {
1017
+ return typeof response === "object" && response !== null && "data" in response && Array.isArray(response.data) && response.data.every(
1018
+ (item) => typeof item === "object" && item !== null && "id" in item && "name" in item && "platform" in item
1019
+ );
1020
+ }
944
1021
 
945
1022
  // src/commands/init.ts
946
1023
  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";
1024
+ import { select as select2 } from "@inquirer/prompts";
1025
+ import chalk4 from "chalk";
1026
+ function isChannelsResponse2(response) {
1027
+ return typeof response === "object" && response !== null && "data" in response && Array.isArray(response.data) && response.data.every(
1028
+ (item) => typeof item === "object" && item !== null && "id" in item && "name" in item && "platform" in item
1029
+ );
1030
+ }
1031
+ function isInitResponse2(response) {
1032
+ 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;
1033
+ }
950
1034
  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(
1035
+ 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
1036
  new Option2("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
953
1037
  ).addOption(
954
- new Option2("--sample-data-api-url <url>", "BigCommerce sample data API URL").default("https://api.bc-sample.store").hideHelp()
1038
+ new Option2("--cli-api-origin <origin>", "Catalyst CLI API origin").default("https://cxm-prd.bigcommerceapp.com").hideHelp()
955
1039
  ).action((options) => __async(void 0, null, function* () {
956
1040
  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
1041
  let storeHash = options.storeHash;
962
1042
  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;
1043
+ if (!storeHash || !accessToken) {
1044
+ const config = new Config(projectDir);
1045
+ const storedAuth = config.getAuth();
1046
+ storeHash = storeHash != null ? storeHash : storedAuth.storeHash;
1047
+ accessToken = accessToken != null ? accessToken : storedAuth.accessToken;
969
1048
  }
970
1049
  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);
1050
+ const credentials = yield login(`https://login.${options.bigcommerceHostname}`);
1051
+ storeHash = credentials.storeHash;
1052
+ accessToken = credentials.accessToken;
1053
+ storeCredentials(projectDir, credentials);
975
1054
  }
976
1055
  yield telemetry2.identify(storeHash);
977
- const bc = new Https({ bigCommerceApiUrl, storeHash, accessToken });
978
- const sampleDataApi = new Https({
979
- sampleDataApiUrl,
1056
+ const bc = new Https({
1057
+ baseUrl: `https://api.${options.bigcommerceHostname}/stores/${storeHash}`,
1058
+ accessToken
1059
+ });
1060
+ const cliApi = new CliApi({
1061
+ origin: options.cliApiOrigin,
980
1062
  storeHash,
981
1063
  accessToken
982
1064
  });
983
- const eligibilityResponse = yield sampleDataApi.checkEligibility();
984
- if (!eligibilityResponse.data.eligible) {
985
- console.warn(chalk5.yellow(eligibilityResponse.data.message));
1065
+ const channelSortOrder = ["catalyst", "next", "bigcommerce"];
1066
+ const channelsResponse = yield bc.fetch("/v3/channels?available=true&type=storefront");
1067
+ if (!channelsResponse.ok) {
1068
+ console.error(
1069
+ chalk4.red(
1070
+ `
1071
+ GET /v3/channels failed: ${channelsResponse.status} ${channelsResponse.statusText}
1072
+ `
1073
+ )
1074
+ );
1075
+ process.exit(1);
986
1076
  }
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
- });
1077
+ const availableChannels = yield channelsResponse.json();
1078
+ if (!isChannelsResponse2(availableChannels)) {
1079
+ console.error(chalk4.red("\nUnexpected response format from channels endpoint\n"));
1080
+ process.exit(1);
996
1081
  }
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;
1082
+ const existingChannel = yield select2({
1083
+ message: "Which channel would you like to use?",
1084
+ choices: availableChannels.data.sort(
1085
+ (a, b) => channelSortOrder.indexOf(a.platform) - channelSortOrder.indexOf(b.platform)
1086
+ ).map((ch) => ({
1087
+ name: ch.name,
1088
+ value: ch,
1089
+ description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
1090
+ }))
1091
+ });
1092
+ const channelId = existingChannel.id;
1093
+ const initResponse = yield cliApi.getChannelInit(channelId);
1094
+ if (!initResponse.ok) {
1095
+ console.error(
1096
+ chalk4.red(
1097
+ `
1098
+ GET /channels/${channelId}/init failed: ${initResponse.status} ${initResponse.statusText}
1099
+ `
1100
+ )
1101
+ );
1102
+ process.exit(1);
1006
1103
  }
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;
1104
+ const initData = yield initResponse.json();
1105
+ if (!isInitResponse2(initData)) {
1106
+ console.error(chalk4.red("\nUnexpected response format from init endpoint\n"));
1107
+ process.exit(1);
1025
1108
  }
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
- });
1109
+ const envVars = __spreadValues({}, initData.data.envVars);
1110
+ if (options.env) {
1111
+ const cliEnvVars = options.env.reduce((acc, env) => {
1112
+ const [key, value] = env.split("=");
1113
+ if (key && value) {
1114
+ acc[key] = value;
1115
+ }
1116
+ return acc;
1117
+ }, {});
1118
+ Object.assign(envVars, cliEnvVars);
1119
+ }
1120
+ writeEnv(projectDir, envVars);
1121
+ console.log(chalk4.green(`
1122
+ .env.local file created for channel ${existingChannel.name}!
1123
+ `));
1124
+ console.log(chalk4.green(`
1125
+ Next steps:
1126
+ `));
1127
+ console.log(chalk4.yellow(`
1128
+ pnpm run dev
1129
+ `));
1033
1130
  }));
1034
1131
 
1035
1132
  // src/commands/integration.ts
@@ -1040,16 +1137,16 @@ import { outputFileSync as outputFileSync2, writeJsonSync } from "fs-extra/esm";
1040
1137
  import kebabCase2 from "lodash.kebabcase";
1041
1138
  import { coerce, compare } from "semver";
1042
1139
  import { promisify } from "util";
1043
- import { z as z5 } from "zod";
1140
+ import { z as z3 } from "zod";
1044
1141
  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())
1142
+ var ManifestSchema = z3.object({
1143
+ name: z3.string(),
1144
+ dependencies: z3.object({ add: z3.array(z3.string()) }),
1145
+ devDependencies: z3.object({ add: z3.array(z3.string()) }),
1146
+ environmentVariables: z3.array(z3.string())
1050
1147
  });
1051
1148
  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);
1149
+ const integrationName = z3.string().transform(kebabCase2).parse(integrationNameRaw);
1053
1150
  const manifest = {
1054
1151
  name: integrationName,
1055
1152
  dependencies: { add: [] },
@@ -1071,9 +1168,9 @@ var integration = new Command3("integration").argument("<integration-name>", "Fo
1071
1168
  }
1072
1169
  return 0;
1073
1170
  }).reverse();
1074
- const PackageDependenciesSchema = z5.object({
1075
- dependencies: z5.object({}).passthrough(),
1076
- devDependencies: z5.object({}).passthrough()
1171
+ const PackageDependenciesSchema = z3.object({
1172
+ dependencies: z3.object({}).passthrough(),
1173
+ devDependencies: z3.object({}).passthrough()
1077
1174
  });
1078
1175
  const getPackageDeps = (ref) => __async(void 0, null, function* () {
1079
1176
  const { stdout } = yield exec(`git show ${ref}:core/package.json`);
@@ -1109,7 +1206,7 @@ var integration = new Command3("integration").argument("<integration-name>", "Fo
1109
1206
  import { Argument, Command as Command4, Option as Option3 } from "@commander-js/extra-typings";
1110
1207
 
1111
1208
  // src/utils/telemetry/index.ts
1112
- import chalk6 from "chalk";
1209
+ import chalk5 from "chalk";
1113
1210
  var telemetry3 = new Telemetry();
1114
1211
  var isEnabled = telemetry3.isEnabled();
1115
1212
  var catalystTelemetry = (options, arg) => {
@@ -1130,7 +1227,7 @@ var catalystTelemetry = (options, arg) => {
1130
1227
  }
1131
1228
  console.log(
1132
1229
  `
1133
- Status: ${chalk6.bold(isEnabled ? chalk6.green("Enabled") : chalk6.red("Disabled"))}`
1230
+ Status: ${chalk5.bold(isEnabled ? chalk5.green("Enabled") : chalk5.red("Disabled"))}`
1134
1231
  );
1135
1232
  if (!isEnabled) {
1136
1233
  console.log(
@@ -1183,7 +1280,7 @@ var telemetryPostHook = () => __async(void 0, null, function* () {
1183
1280
  });
1184
1281
 
1185
1282
  // src/index.ts
1186
- console.log(chalk7.cyanBright(`
1283
+ console.log(chalk6.cyanBright(`
1187
1284
  \u25E2 ${package_default.name} v${package_default.version}
1188
1285
  `));
1189
1286
  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);