@bigcommerce/create-catalyst 0.17.1 → 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.17.1",
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,16 +117,125 @@ 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 { execSync as execSync4 } from "child_process";
120
+ import { input, select } from "@inquirer/prompts";
121
+ import chalk3 from "chalk";
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
- import { execSync as execSync3 } from "child_process";
238
+ import { execSync as execSync4 } from "child_process";
128
239
 
129
240
  // src/utils/checkout-ref.ts
130
241
  import { execSync } from "node:child_process";
@@ -181,29 +292,75 @@ function hasGitHubSSH() {
181
292
  }
182
293
  }
183
294
 
295
+ // src/utils/reset-branch-to-ref.ts
296
+ import { execSync as execSync3 } from "node:child_process";
297
+ function resetBranchToRef(projectDir, ghRef) {
298
+ execSync3(`git reset --hard ${ghRef}`, {
299
+ cwd: projectDir,
300
+ stdio: "inherit",
301
+ encoding: "utf8"
302
+ });
303
+ }
304
+
184
305
  // src/utils/clone-catalyst.ts
185
306
  var cloneCatalyst = ({
186
307
  repository,
187
308
  projectName,
188
309
  projectDir,
189
- ghRef
310
+ ghRef,
311
+ resetMain = false
190
312
  }) => {
191
313
  const useSSH = hasGitHubSSH();
192
314
  console.log(`Cloning ${repository} using ${useSSH ? "SSH" : "HTTPS"}...
193
315
  `);
194
316
  const cloneCommand = `git clone ${useSSH ? `git@github.com:${repository}` : `https://github.com/${repository}`}.git${projectName ? ` ${projectName}` : ""}`;
195
- execSync3(cloneCommand, { stdio: "inherit" });
317
+ execSync4(cloneCommand, { stdio: "inherit" });
196
318
  console.log();
197
- execSync3("git remote rename origin upstream", { cwd: projectDir, stdio: "inherit" });
319
+ execSync4("git remote rename origin upstream", { cwd: projectDir, stdio: "inherit" });
198
320
  console.log();
199
321
  if (ghRef) {
322
+ if (resetMain) {
323
+ checkoutRef(projectDir, "main");
324
+ resetBranchToRef(projectDir, ghRef);
325
+ console.log(`Reset main to ${ghRef} successfully.`);
326
+ console.log();
327
+ return;
328
+ }
200
329
  checkoutRef(projectDir, ghRef);
201
330
  console.log();
202
331
  }
203
332
  };
204
333
 
205
- // src/utils/https.ts
334
+ // src/utils/install-dependencies.ts
206
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
207
364
  import { z as z2 } from "zod";
208
365
 
209
366
  // src/utils/parse.ts
@@ -220,113 +377,17 @@ var parse = (data, schema) => {
220
377
  }
221
378
  };
222
379
 
223
- // src/utils/user-agent.ts
224
- import {
225
- isDevelopment,
226
- isLinux,
227
- isMacOS,
228
- isProduction,
229
- isTest,
230
- isWindows,
231
- nodeVersion,
232
- process as process2,
233
- provider,
234
- runtime
235
- } from "std-env";
236
- var { name, version } = package_default;
237
- var getOS = () => {
238
- if (isWindows) return "Windows";
239
- if (isMacOS) return "macOS";
240
- if (isLinux) return "Linux";
241
- return "Unknown OS";
242
- };
243
- var getEnv = () => {
244
- if (isDevelopment) return "Development";
245
- if (isTest) return "Test";
246
- if (isProduction) return "Production";
247
- };
248
- var getPlatform = () => {
249
- const os = getOS();
250
- const env = getEnv();
251
- const keysOfInterest = [
252
- os,
253
- env,
254
- runtime,
255
- provider,
256
- `Node ${nodeVersion}`,
257
- process2.env.NODE_ENV
258
- ].filter(Boolean);
259
- return keysOfInterest.join("; ");
260
- };
261
- var detectedPlatform = getPlatform();
262
- var getCLIUserAgent = (platform, extensions) => {
263
- const userAgentParts = [`${name}/${version}`];
264
- const platformValue = platform != null ? platform : detectedPlatform;
265
- userAgentParts.push(`(${platformValue})`);
266
- if (extensions) {
267
- userAgentParts.push(extensions);
268
- }
269
- return userAgentParts.join(" ");
270
- };
271
-
272
- // src/utils/https.ts
273
- var BigCommerceStoreInfo = z2.object({
274
- features: z2.object({
275
- storefront_limits: z2.object({
276
- active: z2.number(),
277
- total_including_inactive: z2.number()
278
- })
279
- })
280
- });
281
- var BigCommerceV3ApiResponseSchema = (schema) => z2.object({
282
- data: schema,
283
- meta: z2.object({})
284
- });
285
- var BigCommerceChannelsV3ResponseSchema = BigCommerceV3ApiResponseSchema(
286
- z2.array(
287
- z2.object({
288
- id: z2.number(),
289
- name: z2.string(),
290
- status: z2.string(),
291
- platform: z2.string()
292
- })
293
- )
294
- );
295
- var Https = class {
296
- constructor({
297
- bigCommerceApiUrl,
298
- bigCommerceAuthUrl,
299
- sampleDataApiUrl,
300
- storeHash,
301
- accessToken
302
- }) {
303
- this.DEVICE_OAUTH_CLIENT_ID = "acse0vvawm9r1n0evag4b8e1ea1fo90";
304
- this.MAX_EPOC_EXPIRES_AT = 2147483647;
305
- this.bigCommerceApiUrl = bigCommerceApiUrl != null ? bigCommerceApiUrl : "";
306
- this.bigCommerceAuthUrl = bigCommerceAuthUrl != null ? bigCommerceAuthUrl : "";
307
- this.sampleDataApiUrl = sampleDataApiUrl != null ? sampleDataApiUrl : "";
308
- this.storeHash = storeHash != null ? storeHash : "";
309
- this.accessToken = accessToken != null ? accessToken : "";
310
- this.userAgent = getCLIUserAgent();
311
- }
312
- auth(path, opts = {}) {
313
- if (!this.bigCommerceAuthUrl) {
314
- throw new Error("bigCommerceAuthUrl is required to make API requests");
315
- }
316
- const _a = opts, { headers = {} } = _a, rest = __objRest(_a, ["headers"]);
317
- const options = __spreadValues({
318
- method: "POST",
319
- headers: __spreadProps(__spreadValues({}, headers), {
320
- Accept: "application/json",
321
- "Content-Type": "application/json",
322
- "User-Agent": this.userAgent
323
- })
324
- }, rest);
325
- 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 });
326
385
  }
327
386
  getDeviceCode() {
328
387
  return __async(this, null, function* () {
329
- 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" },
330
391
  body: JSON.stringify({
331
392
  scopes: [
332
393
  "store_channel_settings",
@@ -340,14 +401,6 @@ var Https = class {
340
401
  client_id: this.DEVICE_OAUTH_CLIENT_ID
341
402
  })
342
403
  });
343
- if (!response.ok) {
344
- console.error(
345
- chalk.red(`
346
- POST /device/token failed: ${response.status} ${response.statusText}
347
- `)
348
- );
349
- process.exit(1);
350
- }
351
404
  const DeviceCodeSchema = z2.object({
352
405
  device_code: z2.string(),
353
406
  user_code: z2.string(),
@@ -360,7 +413,9 @@ POST /device/token failed: ${response.status} ${response.statusText}
360
413
  }
361
414
  checkDeviceCode(deviceCode) {
362
415
  return __async(this, null, function* () {
363
- 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" },
364
419
  body: JSON.stringify({
365
420
  device_code: deviceCode,
366
421
  client_id: this.DEVICE_OAUTH_CLIENT_ID
@@ -378,243 +433,155 @@ POST /device/token failed: ${response.status} ${response.statusText}
378
433
  return parse(yield response.json(), DeviceCodeSuccessSchema);
379
434
  });
380
435
  }
381
- api(path, opts = {}) {
382
- if (!this.bigCommerceApiUrl || !this.storeHash || !this.accessToken) {
383
- throw new Error(
384
- "bigCommerceApiUrl, storeHash, and accessToken are required to make API requests"
385
- );
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;
386
452
  }
387
- const _a = opts, { headers = {} } = _a, rest = __objRest(_a, ["headers"]);
388
- const options = __spreadValues({
389
- headers: __spreadProps(__spreadValues({}, headers), {
390
- Accept: "application/json",
391
- "X-Auth-Token": this.accessToken,
392
- "User-Agent": this.userAgent
393
- })
394
- }, rest);
395
- 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");
396
455
  }
397
- storeInformation() {
398
- return __async(this, null, function* () {
399
- const res = yield this.api("/v2/store");
400
- if (!res.ok) {
401
- console.error(chalk.red(`
402
- GET /v2/store failed: ${res.status} ${res.statusText}
403
- `));
404
- process.exit(1);
405
- }
406
- return parse(yield res.json(), BigCommerceStoreInfo);
407
- });
456
+ return true;
457
+ }
458
+ var Config = class {
459
+ constructor(projectDir) {
460
+ this.configPath = join(projectDir, ".catalyst");
461
+ this.config = this.read();
408
462
  }
409
- channels(query = "") {
410
- return __async(this, null, function* () {
411
- const res = yield this.api(`/v3/channels${query}`);
412
- if (!res.ok) {
413
- console.error(chalk.red(`
414
- GET /v3/channels failed: ${res.status} ${res.statusText}
415
- `));
416
- process.exit(1);
417
- }
418
- return parse(yield res.json(), BigCommerceChannelsV3ResponseSchema);
419
- });
463
+ getAuth() {
464
+ var _a;
465
+ return (_a = this.config.auth) != null ? _a : {};
420
466
  }
421
- createChannelMenus(channelId) {
422
- return __async(this, null, function* () {
423
- const res = yield this.api(`/v3/channels/${channelId}/channel-menus`, {
424
- method: "POST",
425
- headers: { "Content-Type": "application/json" },
426
- body: JSON.stringify({
427
- bigcommerce_protected_app_sections: [
428
- "storefront_settings",
429
- "currencies",
430
- "domains",
431
- "notifications",
432
- "social"
433
- ]
434
- })
435
- });
436
- if (!res.ok) {
437
- console.warn(
438
- chalk.yellow(
439
- `
440
- 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
441
- `
442
- )
443
- );
444
- }
445
- });
467
+ setAuth(storeHash, accessToken) {
468
+ this.config.auth = { storeHash, accessToken };
469
+ this.save();
446
470
  }
447
- storefrontToken() {
448
- return __async(this, null, function* () {
449
- const res = yield this.api("/v3/storefront/api-token", {
450
- method: "POST",
451
- headers: { "Content-Type": "application/json" },
452
- body: JSON.stringify({ expires_at: this.MAX_EPOC_EXPIRES_AT, channel_ids: [] })
453
- });
454
- if (!res.ok) {
455
- console.error(
456
- chalk.red(`
457
- POST /v3/storefront/api-token failed: ${res.status} ${res.statusText}
458
- `)
459
- );
460
- process.exit(1);
461
- }
462
- const BigCommerceStorefrontTokenSchema = z2.object({
463
- data: z2.object({
464
- token: z2.string()
465
- })
466
- });
467
- return parse(yield res.json(), BigCommerceStorefrontTokenSchema);
468
- });
469
- }
470
- sampleDataApi(path, opts = {}) {
471
- if (!this.sampleDataApiUrl || !this.storeHash || !this.accessToken) {
472
- throw new Error(
473
- "sampleDataApiUrl, storeHash, and accessToken are required to make API requests"
474
- );
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
+ };
475
479
  }
476
- const _a = opts, { headers = {} } = _a, rest = __objRest(_a, ["headers"]);
477
- const options = __spreadValues({
478
- method: "POST",
479
- headers: __spreadProps(__spreadValues({}, headers), {
480
- Accept: "application/json",
481
- "Content-Type": "application/json",
482
- "X-Auth-Token": this.accessToken,
483
- "User-Agent": this.userAgent
484
- })
485
- }, rest);
486
- return fetch(`${this.sampleDataApiUrl}/stores/${this.storeHash}${path}`, options);
487
- }
488
- checkEligibility() {
489
- return __async(this, null, function* () {
490
- const res = yield this.sampleDataApi("/v3/channels/catalyst/eligibility", {
491
- method: "GET"
492
- });
493
- if (!res.ok) {
494
- console.error(
495
- chalk.red(
496
- `
497
- GET /v3/channels/catalyst/eligibility failed: ${res.status} ${res.statusText}
498
- `
499
- )
500
- );
501
- process.exit(1);
502
- }
503
- const CheckEligibilitySchema = z2.object({
504
- data: z2.object({
505
- eligible: z2.boolean(),
506
- message: z2.string()
507
- })
508
- });
509
- return parse(yield res.json(), CheckEligibilitySchema);
510
- });
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));
511
491
  }
512
- createChannel(channelName) {
513
- return __async(this, null, function* () {
514
- const res = yield this.sampleDataApi("/v3/channels/catalyst", {
515
- body: JSON.stringify({ name: channelName, tokenType: "normal" })
516
- });
517
- if (!res.ok) {
518
- console.error(
519
- chalk.red(`
520
- POST /v3/channels/catalyst failed: ${res.status} ${res.statusText}
521
- `)
522
- );
523
- 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;
524
501
  }
525
- const SampleDataChannelCreateSchema = z2.object({
526
- data: z2.object({
527
- id: z2.number(),
528
- name: z2.string().min(1),
529
- storefront_api_token: z2.string()
530
- })
531
- });
532
- return parse(yield res.json(), SampleDataChannelCreateSchema);
533
- });
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
+ }
534
508
  }
535
509
  };
536
510
 
537
- // src/utils/install-dependencies.ts
538
- import chalk2 from "chalk";
539
- import { installDependencies as installDeps } from "nypm";
540
-
541
- // src/utils/spinner.ts
542
- import { oraPromise } from "ora";
543
- var spinner = (action, oraOpts) => __async(void 0, null, function* () {
544
- return oraPromise(action, __spreadValues({
545
- spinner: "triangle"
546
- }, oraOpts)).catch(() => process.exit(1));
547
- });
548
-
549
- // src/utils/install-dependencies.ts
550
- var installAllDeps = (projectDir) => __async(void 0, null, function* () {
551
- yield installDeps({ cwd: projectDir, silent: true, packageManager: "pnpm" });
552
- });
553
- var installDependencies = (projectDir) => __async(void 0, null, function* () {
554
- return spinner(installAllDeps(projectDir), {
555
- text: `Installing dependencies. This could take a minute...`,
556
- successText: `Dependencies installed successfully`,
557
- failText: (err) => chalk2.red(`Failed to install dependencies: ${err.message}`)
558
- });
559
- });
560
-
561
511
  // src/utils/login.ts
562
- import { select } from "@inquirer/prompts";
563
- import chalk3 from "chalk";
564
- var poll = (auth, deviceCode, interval, expiresIn) => __async(void 0, null, function* () {
565
- const intervalMs = interval * 1e3;
566
- const expiresAtMs = expiresIn * 1e3;
567
- const retries = expiresAtMs / intervalMs;
568
- for (let i = 0; i < retries; i += 1) {
512
+ function pollDeviceCode(auth, deviceCode, interval) {
513
+ return __async(this, null, function* () {
569
514
  try {
570
- return yield auth.checkDeviceCode(deviceCode);
515
+ const credentials = yield auth.checkDeviceCode(deviceCode);
516
+ return credentials;
571
517
  } catch (e) {
518
+ yield new Promise((resolve) => setTimeout(resolve, interval * 1e3));
519
+ return null;
572
520
  }
573
- yield new Promise((resolve) => setTimeout(resolve, intervalMs));
574
- }
575
- console.error(chalk3.red("\nDevice code expired. Please try again.\n"));
576
- process.exit(1);
577
- });
578
- var login = (bigCommerceAuthUrl, storeHash, accessToken) => __async(void 0, null, function* () {
579
- if (storeHash && accessToken) {
580
- return { storeHash, accessToken };
581
- }
582
- const shouldLogin = yield select({
583
- message: "Would you like to connect to a BigCommerce store?",
584
- choices: [
585
- { name: "Yes", value: true },
586
- { name: "No", value: false }
587
- ]
588
521
  });
589
- if (!shouldLogin) {
590
- return { storeHash, accessToken };
591
- }
592
- const auth = new Https({ bigCommerceAuthUrl });
593
- const deviceCode = yield auth.getDeviceCode();
594
- console.log(
595
- [
596
- `
597
- Please visit ${chalk3.cyan(deviceCode.verification_uri)} and enter the code: ${chalk3.yellow(
598
- deviceCode.user_code
599
- )}
600
- `,
601
- `The code will expire in ${chalk3.yellow(`${deviceCode.expires_in / 60} minutes`)}
602
- `
603
- ].join("\n")
604
- );
605
- const { store_hash, access_token } = yield spinner(
606
- poll(auth, deviceCode.device_code, deviceCode.interval, deviceCode.expires_in),
607
- {
608
- text: "Waiting for device code to be authorized",
609
- successText: "Device code authorized\n",
610
- 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;
611
528
  }
612
- );
613
- return {
614
- storeHash: store_hash,
615
- accessToken: access_token
616
- };
617
- });
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
+ }
618
585
 
619
586
  // src/utils/telemetry/telemetry.ts
620
587
  import { Analytics } from "@segment/analytics-node";
@@ -702,28 +669,13 @@ var Telemetry = class {
702
669
  };
703
670
 
704
671
  // src/utils/write-env.ts
705
- import { randomBytes as randomBytes2 } from "crypto";
706
672
  import { outputFileSync } from "fs-extra/esm";
707
- import { join } from "path";
708
- var writeEnv = (projectDir, {
709
- channelId,
710
- storeHash,
711
- storefrontToken,
712
- arbitraryEnv
713
- }) => {
673
+ import { join as join2 } from "path";
674
+ var writeEnv = (projectDir, envVars) => {
714
675
  outputFileSync(
715
- join(projectDir, ".env.local"),
716
- [
717
- `BIGCOMMERCE_STORE_HASH=${storeHash}`,
718
- `BIGCOMMERCE_CHANNEL_ID=${channelId}`,
719
- `BIGCOMMERCE_STOREFRONT_TOKEN=${storefrontToken}`,
720
- "",
721
- `AUTH_SECRET=${randomBytes2(32).toString("hex")}`,
722
- `CLIENT_LOGGER=false`,
723
- `ENABLE_ADMIN_ROUTE=true`,
724
- `DEFAULT_REVALIDATE_TARGET=3600`,
725
- arbitraryEnv == null ? void 0 : arbitraryEnv.map((env) => env).join("\n")
726
- ].join("\n")
676
+ join2(projectDir, ".env.local"),
677
+ `${Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n")}
678
+ `
727
679
  );
728
680
  };
729
681
 
@@ -733,284 +685,445 @@ function getPlatformCheckCommand(command) {
733
685
  return isWindows2 ? `where.exe ${command}` : `which ${command}`;
734
686
  }
735
687
  var telemetry = new Telemetry();
736
- 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("--repository <repository>", "GitHub repository to clone from", "bigcommerce/catalyst").option("--env <vars...>", "Arbitrary environment variables to set in .env.local").addOption(
737
- new Option("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
738
- ).addOption(
739
- new Option("--sample-data-api-url <url>", "BigCommerce sample data API URL").default("https://api.bc-sample.store").hideHelp()
740
- ).action((options) => __async(void 0, null, function* () {
741
- 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() {
742
823
  try {
743
- execSync4(getPlatformCheckCommand("git"), { stdio: "ignore" });
824
+ execSync5(getPlatformCheckCommand("git"), { stdio: "ignore" });
744
825
  } catch (e) {
745
- 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"));
746
827
  process.exit(1);
747
828
  }
748
829
  try {
749
- execSync4(getPlatformCheckCommand("pnpm"), { stdio: "ignore" });
830
+ execSync5(getPlatformCheckCommand("pnpm"), { stdio: "ignore" });
750
831
  } catch (e) {
751
- console.error(chalk4.red("Error: pnpm is required to create a Catalyst project\n"));
752
- 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"));
753
834
  process.exit(1);
754
835
  }
755
- const URLSchema = z3.string().url();
756
- const sampleDataApiUrl = parse(options.sampleDataApiUrl, URLSchema);
757
- const bigcommerceApiUrl = parse(`https://api.${options.bigcommerceHostname}`, URLSchema);
758
- const bigcommerceAuthUrl = parse(`https://login.${options.bigcommerceHostname}`, URLSchema);
759
- let projectName;
760
- 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
+ });
761
848
  let storeHash = options.storeHash;
762
849
  let accessToken = options.accessToken;
763
850
  let channelId;
764
851
  let storefrontToken = options.storefrontToken;
852
+ let credentials;
765
853
  if (options.channelId) {
766
854
  channelId = parseInt(options.channelId, 10);
767
855
  }
768
- if (!pathExistsSync(options.projectDir)) {
769
- console.error(chalk4.red(`Error: --projectDir ${options.projectDir} is not a valid path
770
- `));
771
- process.exit(1);
772
- }
773
- if (options.projectName) {
774
- projectName = kebabCase(options.projectName);
775
- projectDir = join2(options.projectDir, projectName);
776
- if (pathExistsSync(projectDir)) {
777
- console.error(chalk4.red(`Error: ${projectDir} already exists
778
- `));
779
- process.exit(1);
780
- }
781
- }
782
- if (!options.projectName) {
783
- const validateProjectName = (i) => {
784
- const formatted = kebabCase(i);
785
- if (!formatted) return "Project name is required";
786
- const targetDir = join2(options.projectDir, formatted);
787
- if (pathExistsSync(targetDir)) return `Destination '${targetDir}' already exists`;
788
- projectName = formatted;
789
- projectDir = targetDir;
790
- return true;
791
- };
792
- yield input({
793
- message: "What is the name of your project?",
794
- default: "my-catalyst-app",
795
- validate: validateProjectName
796
- });
797
- }
798
- if (!projectName) throw new Error("Something went wrong, projectName is not defined");
799
- if (!projectDir) throw new Error("Something went wrong, projectDir is not defined");
800
- if (storeHash && channelId && storefrontToken) {
801
- console.log(`
802
- Creating '${projectName}' at '${projectDir}'
803
- `);
804
- cloneCatalyst({ repository, projectName, projectDir, ghRef });
805
- writeEnv(projectDir, {
806
- channelId: channelId.toString(),
807
- storeHash,
808
- storefrontToken,
809
- arbitraryEnv: options.env
810
- });
811
- yield installDependencies(projectDir);
812
- console.log(
813
- `
814
- ${chalk4.green("Success!")} Created '${projectName}' at '${projectDir}'
815
- `,
816
- "\nNext steps:\n",
817
- chalk4.yellow(`
818
- cd ${projectName} && pnpm run dev
819
- `)
820
- );
821
- process.exit(0);
822
- }
823
- if (!options.storeHash || !options.accessToken) {
824
- const credentials = yield login(bigcommerceAuthUrl);
856
+ let envVars = {};
857
+ if ((!storeHash || !accessToken) && (!channelId || !storefrontToken)) {
858
+ credentials = yield login(`https://login.${options.bigcommerceHostname}`);
825
859
  storeHash = credentials.storeHash;
826
860
  accessToken = credentials.accessToken;
827
861
  }
828
- if (!storeHash || !accessToken) {
829
- 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(`
830
869
  Creating '${projectName}' at '${projectDir}'
831
870
  `);
832
- cloneCatalyst({ repository, projectName, projectDir, ghRef });
833
- yield installDependencies(projectDir);
834
- console.log(
835
- [
836
- `
837
- ${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}'
838
888
  `,
839
- `Next steps:`,
840
- chalk4.yellow(`
889
+ `Next steps:`,
890
+ Object.keys(envVars).length > 0 ? chalk3.yellow(`
891
+ - cd ${projectName} && pnpm run dev
892
+ `) : [
893
+ chalk3.yellow(`
841
894
  - cd ${projectName} && cp .env.example .env.local`),
842
- chalk4.yellow(`
895
+ chalk3.yellow(`
843
896
  - Populate .env.local with your BigCommerce API credentials
844
897
  `)
845
- ].join("\n")
846
- );
847
- process.exit(0);
848
- }
849
- yield telemetry.identify(storeHash);
850
- if (!channelId || !storefrontToken) {
851
- const bc = new Https({ bigCommerceApiUrl: bigcommerceApiUrl, storeHash, accessToken });
852
- const sampleDataApi = new Https({
853
- sampleDataApiUrl,
854
- storeHash,
855
- accessToken
856
- });
857
- const eligibilityResponse = yield sampleDataApi.checkEligibility();
858
- if (!eligibilityResponse.data.eligible) {
859
- console.warn(chalk4.yellow(eligibilityResponse.data.message));
860
- }
861
- let shouldCreateChannel;
862
- if (eligibilityResponse.data.eligible) {
863
- shouldCreateChannel = yield select2({
864
- message: "Would you like to create a new channel?",
865
- choices: [
866
- { name: "Yes", value: true },
867
- { name: "No", value: false }
868
- ]
869
- });
898
+ ].join("")
899
+ ].join("\n")
900
+ );
901
+ process.exit(0);
870
902
  }
871
- if (shouldCreateChannel) {
872
- const newChannelName = yield input({
873
- message: "What would you like to name your new channel?"
903
+ yield telemetry.identify(storeHash);
904
+ if (!channelId || !storefrontToken) {
905
+ const bc = new Https({
906
+ baseUrl: `https://api.${options.bigcommerceHostname}/stores/${storeHash}`,
907
+ accessToken
874
908
  });
875
- const {
876
- data: { id: createdChannelId, storefront_api_token: storefrontApiToken }
877
- } = yield sampleDataApi.createChannel(newChannelName);
878
- yield bc.createChannelMenus(createdChannelId);
879
- channelId = createdChannelId;
880
- storefrontToken = storefrontApiToken;
881
- }
882
- if (!shouldCreateChannel) {
883
- const channelSortOrder = ["catalyst", "next", "bigcommerce"];
884
- const availableChannels = yield bc.channels("?available=true&type=storefront");
885
- const existingChannel = yield select2({
886
- message: "Which channel would you like to use?",
887
- choices: availableChannels.data.sort(
888
- (a, b) => channelSortOrder.indexOf(a.platform) - channelSortOrder.indexOf(b.platform)
889
- ).map((ch) => ({
890
- name: ch.name,
891
- value: ch,
892
- description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
893
- }))
909
+ const cliApi = new CliApi({
910
+ origin: options.cliApiOrigin,
911
+ storeHash,
912
+ accessToken
894
913
  });
895
- channelId = existingChannel.id;
896
- const {
897
- data: { token: sfToken }
898
- } = yield bc.storefrontToken();
899
- 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
+ }
900
962
  }
901
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
+ }
902
983
  if (!channelId) throw new Error("Something went wrong, channelId is not defined");
903
984
  if (!storefrontToken) throw new Error("Something went wrong, storefrontToken is not defined");
904
985
  console.log(`
905
986
  Creating '${projectName}' at '${projectDir}'
906
987
  `);
907
- cloneCatalyst({ repository, projectName, projectDir, ghRef });
908
- writeEnv(projectDir, {
909
- channelId: channelId.toString(),
910
- storeHash,
911
- storefrontToken,
912
- arbitraryEnv: options.env
913
- });
988
+ cloneCatalyst({ repository, projectName, projectDir, ghRef, resetMain: options.resetMain });
914
989
  yield installDependencies(projectDir);
990
+ writeEnv(projectDir, envVars);
991
+ if (credentials) {
992
+ storeCredentials(projectDir, credentials);
993
+ }
915
994
  console.log(
916
995
  `
917
- ${chalk4.green("Success!")} Created '${projectName}' at '${projectDir}'
996
+ ${chalk3.green("Success!")} Created '${projectName}' at '${projectDir}'
918
997
  `,
919
998
  "\nNext steps:\n",
920
- chalk4.yellow(`
999
+ chalk3.yellow(`
921
1000
  cd ${projectName} && pnpm run dev
922
1001
  `)
923
1002
  );
924
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
+ }
925
1018
 
926
1019
  // src/commands/init.ts
927
1020
  import { Command as Command2, Option as Option2 } from "@commander-js/extra-typings";
928
- import { input as input2, select as select3 } from "@inquirer/prompts";
929
- import chalk5 from "chalk";
930
- 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
+ }
931
1031
  var telemetry2 = new Telemetry();
932
- 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(
933
1033
  new Option2("--bigcommerce-hostname <hostname>", "BigCommerce hostname").default("bigcommerce.com").hideHelp()
934
1034
  ).addOption(
935
- 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()
936
1036
  ).action((options) => __async(void 0, null, function* () {
937
1037
  const projectDir = process.cwd();
938
- const URLSchema = z4.string().url();
939
- const sampleDataApiUrl = parse(options.sampleDataApiUrl, URLSchema);
940
- const bigCommerceApiUrl = `https://api.${options.bigcommerceHostname}`;
941
- const bigCommerceAuthUrl = `https://login.${options.bigcommerceHostname}`;
942
1038
  let storeHash = options.storeHash;
943
1039
  let accessToken = options.accessToken;
944
- let channelId;
945
- let storefrontToken;
946
- if (!options.storeHash || !options.accessToken) {
947
- const credentials = yield login(bigCommerceAuthUrl);
948
- storeHash = credentials.storeHash;
949
- 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;
950
1045
  }
951
1046
  if (!storeHash || !accessToken) {
952
- console.log(
953
- chalk5.yellow("\nYou must authenticate with a store to overwrite your local environment.\n")
954
- );
955
- process.exit(1);
1047
+ const credentials = yield login(`https://login.${options.bigcommerceHostname}`);
1048
+ storeHash = credentials.storeHash;
1049
+ accessToken = credentials.accessToken;
1050
+ storeCredentials(projectDir, credentials);
956
1051
  }
957
1052
  yield telemetry2.identify(storeHash);
958
- const bc = new Https({ bigCommerceApiUrl, storeHash, accessToken });
959
- const sampleDataApi = new Https({
960
- 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,
961
1059
  storeHash,
962
1060
  accessToken
963
1061
  });
964
- const eligibilityResponse = yield sampleDataApi.checkEligibility();
965
- if (!eligibilityResponse.data.eligible) {
966
- 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);
967
1073
  }
968
- let shouldCreateChannel;
969
- if (eligibilityResponse.data.eligible) {
970
- shouldCreateChannel = yield select3({
971
- message: "Would you like to create a new channel?",
972
- choices: [
973
- { name: "Yes", value: true },
974
- { name: "No", value: false }
975
- ]
976
- });
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);
977
1078
  }
978
- if (shouldCreateChannel) {
979
- const newChannelName = yield input2({
980
- message: "What would you like to name your new channel?"
981
- });
982
- const {
983
- data: { id: createdChannelId, storefront_api_token: storefrontApiToken }
984
- } = yield sampleDataApi.createChannel(newChannelName);
985
- channelId = createdChannelId;
986
- 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);
987
1100
  }
988
- if (!shouldCreateChannel) {
989
- const channelSortOrder = ["catalyst", "next", "bigcommerce"];
990
- const availableChannels = yield bc.channels("?available=true&type=storefront");
991
- const existingChannel = yield select3({
992
- message: "Which channel would you like to use?",
993
- choices: availableChannels.data.sort(
994
- (a, b) => channelSortOrder.indexOf(a.platform) - channelSortOrder.indexOf(b.platform)
995
- ).map((ch) => ({
996
- name: ch.name,
997
- value: ch,
998
- description: `Channel Platform: ${ch.platform === "bigcommerce" ? "Stencil" : ch.platform.charAt(0).toUpperCase() + ch.platform.slice(1)}`
999
- }))
1000
- });
1001
- channelId = existingChannel.id;
1002
- const {
1003
- data: { token: sfToken }
1004
- } = yield bc.storefrontToken();
1005
- 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);
1006
1105
  }
1007
- if (!channelId) throw new Error("Something went wrong, channelId is not defined");
1008
- if (!storefrontToken) throw new Error("Something went wrong, storefrontToken is not defined");
1009
- writeEnv(projectDir, {
1010
- channelId: channelId.toString(),
1011
- storeHash,
1012
- storefrontToken
1013
- });
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
+ `));
1014
1127
  }));
1015
1128
 
1016
1129
  // src/commands/integration.ts
@@ -1021,16 +1134,16 @@ import { outputFileSync as outputFileSync2, writeJsonSync } from "fs-extra/esm";
1021
1134
  import kebabCase2 from "lodash.kebabcase";
1022
1135
  import { coerce, compare } from "semver";
1023
1136
  import { promisify } from "util";
1024
- import { z as z5 } from "zod";
1137
+ import { z as z3 } from "zod";
1025
1138
  var exec = promisify(execCb);
1026
- var ManifestSchema = z5.object({
1027
- name: z5.string(),
1028
- dependencies: z5.object({ add: z5.array(z5.string()) }),
1029
- devDependencies: z5.object({ add: z5.array(z5.string()) }),
1030
- 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())
1031
1144
  });
1032
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* () {
1033
- const integrationName = z5.string().transform(kebabCase2).parse(integrationNameRaw);
1146
+ const integrationName = z3.string().transform(kebabCase2).parse(integrationNameRaw);
1034
1147
  const manifest = {
1035
1148
  name: integrationName,
1036
1149
  dependencies: { add: [] },
@@ -1052,9 +1165,9 @@ var integration = new Command3("integration").argument("<integration-name>", "Fo
1052
1165
  }
1053
1166
  return 0;
1054
1167
  }).reverse();
1055
- const PackageDependenciesSchema = z5.object({
1056
- dependencies: z5.object({}).passthrough(),
1057
- devDependencies: z5.object({}).passthrough()
1168
+ const PackageDependenciesSchema = z3.object({
1169
+ dependencies: z3.object({}).passthrough(),
1170
+ devDependencies: z3.object({}).passthrough()
1058
1171
  });
1059
1172
  const getPackageDeps = (ref) => __async(void 0, null, function* () {
1060
1173
  const { stdout } = yield exec(`git show ${ref}:core/package.json`);
@@ -1090,7 +1203,7 @@ var integration = new Command3("integration").argument("<integration-name>", "Fo
1090
1203
  import { Argument, Command as Command4, Option as Option3 } from "@commander-js/extra-typings";
1091
1204
 
1092
1205
  // src/utils/telemetry/index.ts
1093
- import chalk6 from "chalk";
1206
+ import chalk5 from "chalk";
1094
1207
  var telemetry3 = new Telemetry();
1095
1208
  var isEnabled = telemetry3.isEnabled();
1096
1209
  var catalystTelemetry = (options, arg) => {
@@ -1111,7 +1224,7 @@ var catalystTelemetry = (options, arg) => {
1111
1224
  }
1112
1225
  console.log(
1113
1226
  `
1114
- Status: ${chalk6.bold(isEnabled ? chalk6.green("Enabled") : chalk6.red("Disabled"))}`
1227
+ Status: ${chalk5.bold(isEnabled ? chalk5.green("Enabled") : chalk5.red("Disabled"))}`
1115
1228
  );
1116
1229
  if (!isEnabled) {
1117
1230
  console.log(
@@ -1164,7 +1277,7 @@ var telemetryPostHook = () => __async(void 0, null, function* () {
1164
1277
  });
1165
1278
 
1166
1279
  // src/index.ts
1167
- console.log(chalk7.cyanBright(`
1280
+ console.log(chalk6.cyanBright(`
1168
1281
  \u25E2 ${package_default.name} v${package_default.version}
1169
1282
  `));
1170
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);