@bigcommerce/catalyst 1.0.0-alpha.0 → 1.0.0-alpha.2
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/README.md +33 -1
- package/dist/cli.js +1060 -496
- package/dist/cli.js.map +1 -1
- package/package.json +4 -3
- package/templates/open-next.config.ts +8 -2
- package/templates/public_headers +2 -0
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/cli/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
// src/cli/lib/logger.ts
|
|
4
|
+
import { createConsola } from "consola";
|
|
5
|
+
var consola = createConsola({
|
|
6
|
+
level: "3" ? parseInt("3", 10) : 3
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/cli/lib/telemetry.ts
|
|
10
|
+
import { Analytics } from "@segment/analytics-node";
|
|
11
|
+
import { randomBytes, randomUUID } from "crypto";
|
|
8
12
|
|
|
9
13
|
// package.json
|
|
10
14
|
var package_default = {
|
|
11
15
|
name: "@bigcommerce/catalyst",
|
|
12
|
-
version: "1.0.0-alpha.
|
|
16
|
+
version: "1.0.0-alpha.1",
|
|
13
17
|
type: "module",
|
|
14
18
|
bin: {
|
|
15
19
|
catalyst: "dist/cli.js"
|
|
@@ -28,7 +32,7 @@ var package_default = {
|
|
|
28
32
|
build: "tsup"
|
|
29
33
|
},
|
|
30
34
|
engines: {
|
|
31
|
-
node: "^20.0.0 || ^22.0.0"
|
|
35
|
+
node: "^20.0.0 || ^22.0.0 || ^24.0.0"
|
|
32
36
|
},
|
|
33
37
|
dependencies: {
|
|
34
38
|
"@segment/analytics-node": "^2.2.1",
|
|
@@ -38,6 +42,7 @@ var package_default = {
|
|
|
38
42
|
consola: "^3.4.2",
|
|
39
43
|
dotenv: "^16.5.0",
|
|
40
44
|
execa: "^9.6.0",
|
|
45
|
+
open: "^10.1.0",
|
|
41
46
|
"yocto-spinner": "^1.0.0",
|
|
42
47
|
zod: "^4.0.5"
|
|
43
48
|
},
|
|
@@ -57,47 +62,27 @@ var package_default = {
|
|
|
57
62
|
vitest: "^3.2.4"
|
|
58
63
|
},
|
|
59
64
|
peerDependencies: {
|
|
60
|
-
"@opennextjs/cloudflare": "
|
|
65
|
+
"@opennextjs/cloudflare": "1.17.3"
|
|
61
66
|
}
|
|
62
67
|
};
|
|
63
68
|
|
|
64
|
-
// src/cli/commands/build.ts
|
|
65
|
-
import { Command, Option } from "commander";
|
|
66
|
-
import { execa } from "execa";
|
|
67
|
-
import { existsSync } from "fs";
|
|
68
|
-
import { copyFile, cp, writeFile } from "fs/promises";
|
|
69
|
-
import { join as join3 } from "path";
|
|
70
|
-
|
|
71
|
-
// src/cli/lib/get-module-cli-path.ts
|
|
72
|
-
import { dirname, join } from "path";
|
|
73
|
-
import { fileURLToPath } from "url";
|
|
74
|
-
function getModuleCliPath() {
|
|
75
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
76
|
-
const __dirname = dirname(__filename);
|
|
77
|
-
return join(__dirname, "..");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/cli/lib/logger.ts
|
|
81
|
-
import { createConsola } from "consola";
|
|
82
|
-
var consola = createConsola({
|
|
83
|
-
level: "3" ? parseInt("3", 10) : 3
|
|
84
|
-
});
|
|
85
|
-
|
|
86
69
|
// src/cli/lib/project-config.ts
|
|
87
70
|
import Conf from "conf";
|
|
88
|
-
import { join
|
|
89
|
-
function getProjectConfig(
|
|
71
|
+
import { join } from "path";
|
|
72
|
+
function getProjectConfig() {
|
|
90
73
|
return new Conf({
|
|
91
|
-
cwd:
|
|
74
|
+
cwd: join(process.cwd(), ".bigcommerce"),
|
|
92
75
|
projectSuffix: "",
|
|
93
76
|
configName: "project",
|
|
94
77
|
schema: {
|
|
95
78
|
projectUuid: { type: "string", format: "uuid" },
|
|
96
79
|
framework: {
|
|
97
80
|
type: "string",
|
|
98
|
-
enum: ["catalyst"
|
|
99
|
-
default: "
|
|
81
|
+
enum: ["catalyst"],
|
|
82
|
+
default: "catalyst"
|
|
100
83
|
},
|
|
84
|
+
storeHash: { type: "string" },
|
|
85
|
+
accessToken: { type: "string" },
|
|
101
86
|
telemetry: {
|
|
102
87
|
type: "object",
|
|
103
88
|
properties: {
|
|
@@ -109,13 +94,416 @@ function getProjectConfig(rootDir = process.cwd()) {
|
|
|
109
94
|
});
|
|
110
95
|
}
|
|
111
96
|
|
|
97
|
+
// src/cli/lib/telemetry.ts
|
|
98
|
+
var TELEMETRY_KEY_ENABLED = "telemetry.enabled";
|
|
99
|
+
var TELEMETRY_KEY_ID = `telemetry.anonymousId`;
|
|
100
|
+
var Telemetry = class {
|
|
101
|
+
constructor() {
|
|
102
|
+
this.commandName = "unknown";
|
|
103
|
+
this.projectName = "catalyst-cli";
|
|
104
|
+
this.projectVersion = package_default.version;
|
|
105
|
+
this.setEnabled = (_enabled) => {
|
|
106
|
+
const enabled = Boolean(_enabled);
|
|
107
|
+
this.projectConfig.set("telemetry.enabled", enabled);
|
|
108
|
+
};
|
|
109
|
+
this.CATALYST_TELEMETRY_DISABLED = process.env.CATALYST_TELEMETRY_DISABLED;
|
|
110
|
+
this.projectConfig = getProjectConfig();
|
|
111
|
+
this.sessionId = randomUUID();
|
|
112
|
+
this.startTime = Date.now();
|
|
113
|
+
this.analytics = new Analytics({
|
|
114
|
+
writeKey: "not-a-valid-segment-write-key"
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
durationMs() {
|
|
118
|
+
return Date.now() - this.startTime;
|
|
119
|
+
}
|
|
120
|
+
async track(eventName, payload) {
|
|
121
|
+
if (!this.isEnabled()) {
|
|
122
|
+
return Promise.resolve(void 0);
|
|
123
|
+
}
|
|
124
|
+
this.analytics.track({
|
|
125
|
+
event: eventName,
|
|
126
|
+
anonymousId: this.getAnonymousId(),
|
|
127
|
+
properties: {
|
|
128
|
+
...payload,
|
|
129
|
+
sessionId: this.sessionId,
|
|
130
|
+
nodeVersion: process.version,
|
|
131
|
+
platform: process.platform,
|
|
132
|
+
arch: process.arch,
|
|
133
|
+
cliVersion: package_default.version
|
|
134
|
+
},
|
|
135
|
+
context: {
|
|
136
|
+
app: {
|
|
137
|
+
name: this.projectName,
|
|
138
|
+
version: this.projectVersion
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async identify(storeHash) {
|
|
144
|
+
if (!this.isEnabled()) {
|
|
145
|
+
return Promise.resolve(void 0);
|
|
146
|
+
}
|
|
147
|
+
if (!storeHash) {
|
|
148
|
+
return Promise.resolve(void 0);
|
|
149
|
+
}
|
|
150
|
+
this.analytics.identify({
|
|
151
|
+
userId: storeHash,
|
|
152
|
+
anonymousId: this.getAnonymousId(),
|
|
153
|
+
context: {
|
|
154
|
+
app: {
|
|
155
|
+
name: this.projectName,
|
|
156
|
+
version: this.projectVersion
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
isEnabled() {
|
|
162
|
+
return !this.CATALYST_TELEMETRY_DISABLED && this.projectConfig.get(TELEMETRY_KEY_ENABLED, true);
|
|
163
|
+
}
|
|
164
|
+
getAnonymousId() {
|
|
165
|
+
const val = this.projectConfig.get(TELEMETRY_KEY_ID);
|
|
166
|
+
if (val) {
|
|
167
|
+
return val;
|
|
168
|
+
}
|
|
169
|
+
const generated = randomBytes(32).toString("hex");
|
|
170
|
+
this.projectConfig.set(TELEMETRY_KEY_ID, generated);
|
|
171
|
+
return generated;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
var telemetryInstance;
|
|
175
|
+
function getTelemetry() {
|
|
176
|
+
telemetryInstance ?? (telemetryInstance = new Telemetry());
|
|
177
|
+
return telemetryInstance;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/cli/program.ts
|
|
181
|
+
import { Option as Option8 } from "@commander-js/extra-typings";
|
|
182
|
+
import { Command as Command9 } from "commander";
|
|
183
|
+
import { colorize as colorize4 } from "consola/utils";
|
|
184
|
+
import { config } from "dotenv";
|
|
185
|
+
import { resolve } from "path";
|
|
186
|
+
|
|
187
|
+
// src/cli/commands/auth.ts
|
|
188
|
+
import { Command, Option } from "commander";
|
|
189
|
+
import { colorize } from "consola/utils";
|
|
190
|
+
import open from "open";
|
|
191
|
+
import yoctoSpinner from "yocto-spinner";
|
|
192
|
+
import { z as z3 } from "zod";
|
|
193
|
+
|
|
194
|
+
// src/cli/lib/auth.ts
|
|
195
|
+
import { z } from "zod";
|
|
196
|
+
var DEVICE_OAUTH_CLIENT_ID = "s1q4io7mah2lm1i6uwp9yl1eit80n3b";
|
|
197
|
+
var DEVICE_OAUTH_SCOPES = [
|
|
198
|
+
"store_v2_information",
|
|
199
|
+
"store_infrastructure_deployments_manage",
|
|
200
|
+
"store_infrastructure_logs_read_only",
|
|
201
|
+
"store_infrastructure_projects_manage"
|
|
202
|
+
].join(" ");
|
|
203
|
+
var DEFAULT_LOGIN_URL = "https://login.bigcommerce.com";
|
|
204
|
+
var DeviceCodeResponseSchema = z.object({
|
|
205
|
+
device_code: z.string(),
|
|
206
|
+
user_code: z.string(),
|
|
207
|
+
verification_uri: z.string(),
|
|
208
|
+
expires_in: z.number(),
|
|
209
|
+
interval: z.number()
|
|
210
|
+
});
|
|
211
|
+
var DeviceCodeSuccessSchema = z.object({
|
|
212
|
+
access_token: z.string(),
|
|
213
|
+
store_hash: z.string(),
|
|
214
|
+
context: z.string(),
|
|
215
|
+
api_uri: z.string()
|
|
216
|
+
});
|
|
217
|
+
async function requestDeviceCode(loginUrl) {
|
|
218
|
+
const response = await fetch(`${loginUrl}/device/token`, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers: { "Content-Type": "application/json" },
|
|
221
|
+
body: JSON.stringify({
|
|
222
|
+
client_id: DEVICE_OAUTH_CLIENT_ID,
|
|
223
|
+
scopes: DEVICE_OAUTH_SCOPES
|
|
224
|
+
})
|
|
225
|
+
});
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
throw new Error(`Failed to request device code: ${response.status} ${response.statusText}`);
|
|
228
|
+
}
|
|
229
|
+
const res = await response.json();
|
|
230
|
+
return DeviceCodeResponseSchema.parse(res);
|
|
231
|
+
}
|
|
232
|
+
async function pollDeviceToken(loginUrl, deviceCode) {
|
|
233
|
+
const response = await fetch(`${loginUrl}/device/token`, {
|
|
234
|
+
method: "POST",
|
|
235
|
+
headers: { "Content-Type": "application/json" },
|
|
236
|
+
body: JSON.stringify({
|
|
237
|
+
device_code: deviceCode,
|
|
238
|
+
client_id: DEVICE_OAUTH_CLIENT_ID
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
if (response.status !== 200) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const res = await response.json();
|
|
245
|
+
return DeviceCodeSuccessSchema.parse(res);
|
|
246
|
+
}
|
|
247
|
+
async function waitForDeviceToken(loginUrl, deviceCode, interval) {
|
|
248
|
+
const credentials = await pollDeviceToken(loginUrl, deviceCode);
|
|
249
|
+
if (credentials) {
|
|
250
|
+
return credentials;
|
|
251
|
+
}
|
|
252
|
+
await new Promise((resolve2) => {
|
|
253
|
+
setTimeout(resolve2, interval * 1e3);
|
|
254
|
+
});
|
|
255
|
+
return waitForDeviceToken(loginUrl, deviceCode, interval);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/cli/lib/project.ts
|
|
259
|
+
import { z as z2 } from "zod";
|
|
260
|
+
var fetchProjectsSchema = z2.object({
|
|
261
|
+
data: z2.array(
|
|
262
|
+
z2.object({
|
|
263
|
+
uuid: z2.string(),
|
|
264
|
+
name: z2.string()
|
|
265
|
+
})
|
|
266
|
+
)
|
|
267
|
+
});
|
|
268
|
+
async function fetchProjects(storeHash, accessToken, apiHost) {
|
|
269
|
+
const response = await fetch(
|
|
270
|
+
`https://${apiHost}/stores/${storeHash}/v3/infrastructure/projects`,
|
|
271
|
+
{
|
|
272
|
+
method: "GET",
|
|
273
|
+
headers: {
|
|
274
|
+
"X-Auth-Token": accessToken,
|
|
275
|
+
"X-Correlation-Id": getTelemetry().sessionId
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
if (response.status === 403) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
"Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
if (!response.ok) {
|
|
285
|
+
throw new Error(`Failed to fetch projects: ${response.statusText}`);
|
|
286
|
+
}
|
|
287
|
+
const res = await response.json();
|
|
288
|
+
const { data } = fetchProjectsSchema.parse(res);
|
|
289
|
+
return data;
|
|
290
|
+
}
|
|
291
|
+
var createProjectSchema = z2.object({
|
|
292
|
+
data: z2.object({
|
|
293
|
+
uuid: z2.string(),
|
|
294
|
+
name: z2.string(),
|
|
295
|
+
date_created: z2.coerce.date(),
|
|
296
|
+
date_modified: z2.coerce.date()
|
|
297
|
+
})
|
|
298
|
+
});
|
|
299
|
+
async function createProject(name, storeHash, accessToken, apiHost) {
|
|
300
|
+
const response = await fetch(
|
|
301
|
+
`https://${apiHost}/stores/${storeHash}/v3/infrastructure/projects`,
|
|
302
|
+
{
|
|
303
|
+
method: "POST",
|
|
304
|
+
headers: {
|
|
305
|
+
"X-Auth-Token": accessToken,
|
|
306
|
+
Accept: "application/json",
|
|
307
|
+
"Content-Type": "application/json",
|
|
308
|
+
"X-Correlation-Id": getTelemetry().sessionId
|
|
309
|
+
},
|
|
310
|
+
body: JSON.stringify({ name })
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
if (response.status === 502) {
|
|
314
|
+
throw new Error("Failed to create project, is the name already in use?");
|
|
315
|
+
}
|
|
316
|
+
if (response.status === 403) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
"Infrastructure Projects API not enabled. If you are part of the alpha, contact support@bigcommerce.com to enable it."
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
if (response.status === 422) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
"The project name you entered doesn't meet the requirements. It must be 3\u201332 characters long and use only letters, numbers, hyphens (-), underscores (_), and periods (.)"
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new Error(`Failed to create project: ${response.statusText}`);
|
|
328
|
+
}
|
|
329
|
+
const res = await response.json();
|
|
330
|
+
const { data } = createProjectSchema.parse(res);
|
|
331
|
+
return data;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/cli/commands/auth.ts
|
|
335
|
+
var StoreProfileSchema = z3.object({
|
|
336
|
+
data: z3.object({
|
|
337
|
+
store_name: z3.string()
|
|
338
|
+
})
|
|
339
|
+
});
|
|
340
|
+
async function fetchStoreProfile(storeHash, accessToken, apiHost) {
|
|
341
|
+
const response = await fetch(`https://${apiHost}/stores/${storeHash}/v3/settings/store/profile`, {
|
|
342
|
+
method: "GET",
|
|
343
|
+
headers: {
|
|
344
|
+
"X-Auth-Token": accessToken,
|
|
345
|
+
Accept: "application/json"
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
if (!response.ok) {
|
|
349
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
350
|
+
}
|
|
351
|
+
const res = await response.json();
|
|
352
|
+
const result = StoreProfileSchema.safeParse(res);
|
|
353
|
+
if (!result.success) {
|
|
354
|
+
throw new Error("Unexpected response from store profile API");
|
|
355
|
+
}
|
|
356
|
+
return result.data.data;
|
|
357
|
+
}
|
|
358
|
+
var whoami = new Command("whoami").description("Verify stored credentials and display store/project info.").addOption(
|
|
359
|
+
new Option(
|
|
360
|
+
"--store-hash <hash>",
|
|
361
|
+
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
362
|
+
).env("CATALYST_STORE_HASH")
|
|
363
|
+
).addOption(
|
|
364
|
+
new Option(
|
|
365
|
+
"--access-token <token>",
|
|
366
|
+
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
367
|
+
).env("CATALYST_ACCESS_TOKEN")
|
|
368
|
+
).addOption(
|
|
369
|
+
new Option("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com")
|
|
370
|
+
).action(async (options) => {
|
|
371
|
+
try {
|
|
372
|
+
const config2 = getProjectConfig();
|
|
373
|
+
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
374
|
+
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
375
|
+
if (!storeHash || !accessToken) {
|
|
376
|
+
consola.info("Not logged in: no credentials found.");
|
|
377
|
+
consola.info(
|
|
378
|
+
"Run `catalyst auth login`, or provide --store-hash and --access-token flags (or set CATALYST_STORE_HASH and CATALYST_ACCESS_TOKEN environment variables)."
|
|
379
|
+
);
|
|
380
|
+
process.exit(1);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const store = await fetchStoreProfile(storeHash, accessToken, options.apiHost);
|
|
384
|
+
const projectUuid = config2.get("projectUuid");
|
|
385
|
+
if (projectUuid) {
|
|
386
|
+
const projects = await fetchProjects(storeHash, accessToken, options.apiHost);
|
|
387
|
+
const linkedProject = projects.find((p) => p.uuid === projectUuid);
|
|
388
|
+
if (linkedProject) {
|
|
389
|
+
consola.info(
|
|
390
|
+
`Logged in to ${store.store_name} (${storeHash}), connected to project ${linkedProject.name} (${projectUuid})`
|
|
391
|
+
);
|
|
392
|
+
} else {
|
|
393
|
+
consola.info(
|
|
394
|
+
`Logged in to ${store.store_name} (${storeHash}), project ${projectUuid} not found`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
consola.info(`Logged in to ${store.store_name} (${storeHash})`);
|
|
399
|
+
}
|
|
400
|
+
process.exit(0);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
403
|
+
if (message.includes("401") || message.includes("403")) {
|
|
404
|
+
consola.error(`Not logged in: invalid credentials (${message})`);
|
|
405
|
+
} else {
|
|
406
|
+
consola.error(`Failed to verify credentials: ${message}`);
|
|
407
|
+
}
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
var login = new Command("login").description("Authenticate via browser using the OAuth device code flow.").addOption(
|
|
412
|
+
new Option(
|
|
413
|
+
"--store-hash <hash>",
|
|
414
|
+
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
415
|
+
).env("CATALYST_STORE_HASH")
|
|
416
|
+
).addOption(
|
|
417
|
+
new Option(
|
|
418
|
+
"--access-token <token>",
|
|
419
|
+
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
420
|
+
).env("CATALYST_ACCESS_TOKEN")
|
|
421
|
+
).addOption(
|
|
422
|
+
new Option("--login-url <url>", "BigCommerce login URL.").env("BIGCOMMERCE_LOGIN_URL").default(DEFAULT_LOGIN_URL)
|
|
423
|
+
).action(async (options) => {
|
|
424
|
+
try {
|
|
425
|
+
const config2 = getProjectConfig();
|
|
426
|
+
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
427
|
+
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
428
|
+
if (storeHash && accessToken) {
|
|
429
|
+
consola.info(`Already logged in to store ${storeHash}.`);
|
|
430
|
+
consola.info("Run `catalyst auth logout` first to re-authenticate.");
|
|
431
|
+
process.exit(0);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const deviceCode = await requestDeviceCode(options.loginUrl);
|
|
435
|
+
consola.info(
|
|
436
|
+
`${colorize("yellow", "Your one-time code:")} ${colorize("bold", deviceCode.user_code)}`
|
|
437
|
+
);
|
|
438
|
+
try {
|
|
439
|
+
await open(deviceCode.verification_uri);
|
|
440
|
+
consola.info(`Opened ${deviceCode.verification_uri} in your browser.`);
|
|
441
|
+
} catch {
|
|
442
|
+
consola.info(
|
|
443
|
+
`Open ${deviceCode.verification_uri} in your browser and enter the code above.`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
const spinner = yoctoSpinner().start("Waiting for authentication...");
|
|
447
|
+
const credentials = await waitForDeviceToken(
|
|
448
|
+
options.loginUrl,
|
|
449
|
+
deviceCode.device_code,
|
|
450
|
+
deviceCode.interval
|
|
451
|
+
);
|
|
452
|
+
spinner.success("Authentication complete.");
|
|
453
|
+
config2.set("storeHash", credentials.store_hash);
|
|
454
|
+
config2.set("accessToken", credentials.access_token);
|
|
455
|
+
consola.success(`Logged in to store ${credentials.store_hash}.`);
|
|
456
|
+
process.exit(0);
|
|
457
|
+
} catch (error) {
|
|
458
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
459
|
+
consola.error(`Login failed: ${message}`);
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
var logout = new Command("logout").description("Remove stored credentials for the current project.").action(() => {
|
|
464
|
+
try {
|
|
465
|
+
const config2 = getProjectConfig();
|
|
466
|
+
const storeHash = config2.get("storeHash");
|
|
467
|
+
const accessToken = config2.get("accessToken");
|
|
468
|
+
if (!storeHash && !accessToken) {
|
|
469
|
+
consola.info("Not logged in: no credentials found.");
|
|
470
|
+
process.exit(0);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
config2.delete("storeHash");
|
|
474
|
+
config2.delete("accessToken");
|
|
475
|
+
consola.success(`Logged out from store ${storeHash ?? "unknown"}.`);
|
|
476
|
+
process.exit(0);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
479
|
+
consola.error(`Logout failed: ${message}`);
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
var auth = new Command("auth").description("Manage authentication for the BigCommerce CLI.").addCommand(whoami).addCommand(login).addCommand(logout);
|
|
484
|
+
|
|
485
|
+
// src/cli/commands/build.ts
|
|
486
|
+
import { Command as Command2, Option as Option2 } from "commander";
|
|
487
|
+
import { execa } from "execa";
|
|
488
|
+
import { copyFile, cp, writeFile } from "fs/promises";
|
|
489
|
+
import { join as join3 } from "path";
|
|
490
|
+
|
|
491
|
+
// src/cli/lib/get-module-cli-path.ts
|
|
492
|
+
import { dirname, join as join2 } from "path";
|
|
493
|
+
import { fileURLToPath } from "url";
|
|
494
|
+
function getModuleCliPath() {
|
|
495
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
496
|
+
const __dirname = dirname(__filename);
|
|
497
|
+
return join2(__dirname, "..");
|
|
498
|
+
}
|
|
499
|
+
|
|
112
500
|
// src/cli/lib/wrangler-config.ts
|
|
113
|
-
function getWranglerConfig(projectUuid
|
|
501
|
+
function getWranglerConfig(projectUuid) {
|
|
114
502
|
return {
|
|
115
503
|
$schema: "node_modules/wrangler/config-schema.json",
|
|
116
504
|
main: "../.open-next/worker.js",
|
|
117
505
|
name: `project-${projectUuid}`,
|
|
118
|
-
compatibility_date: "2025-
|
|
506
|
+
compatibility_date: "2025-09-15",
|
|
119
507
|
compatibility_flags: ["nodejs_compat", "global_fetch_strictly_public"],
|
|
120
508
|
observability: {
|
|
121
509
|
enabled: true,
|
|
@@ -136,10 +524,10 @@ function getWranglerConfig(projectUuid, kvNamespaceId) {
|
|
|
136
524
|
service: `project-${projectUuid}`
|
|
137
525
|
}
|
|
138
526
|
],
|
|
139
|
-
|
|
527
|
+
r2_buckets: [
|
|
140
528
|
{
|
|
141
|
-
binding: "
|
|
142
|
-
|
|
529
|
+
binding: "NEXT_INC_CACHE_R2_BUCKET",
|
|
530
|
+
bucket_name: `project-${projectUuid}`
|
|
143
531
|
}
|
|
144
532
|
],
|
|
145
533
|
durable_objects: {
|
|
@@ -169,184 +557,118 @@ function getWranglerConfig(projectUuid, kvNamespaceId) {
|
|
|
169
557
|
|
|
170
558
|
// src/cli/commands/build.ts
|
|
171
559
|
var WRANGLER_VERSION = "4.24.3";
|
|
172
|
-
|
|
173
|
-
"[next-build-options...]",
|
|
174
|
-
"Next.js `build` options (see: https://nextjs.org/docs/app/api-reference/cli/next#next-build-options)"
|
|
175
|
-
).addOption(
|
|
176
|
-
new Option(
|
|
177
|
-
"--project-uuid <uuid>",
|
|
178
|
-
"Project UUID to be included in the deployment configuration."
|
|
179
|
-
).env("BIGCOMMERCE_PROJECT_UUID")
|
|
180
|
-
).addOption(
|
|
181
|
-
new Option("--framework <framework>", "The framework to use for the build.").choices([
|
|
182
|
-
"nextjs",
|
|
183
|
-
"catalyst"
|
|
184
|
-
])
|
|
185
|
-
).action(async (nextBuildOptions, options) => {
|
|
560
|
+
async function buildCatalystProject(projectUuid) {
|
|
186
561
|
const coreDir = process.cwd();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
562
|
+
const openNextOutDir = join3(coreDir, ".open-next");
|
|
563
|
+
const bigcommerceDistDir = join3(coreDir, ".bigcommerce", "dist");
|
|
564
|
+
const wranglerConfig = getWranglerConfig(projectUuid);
|
|
565
|
+
consola.start("Copying templates...");
|
|
566
|
+
await copyFile(
|
|
567
|
+
join3(getModuleCliPath(), "templates", "open-next.config.ts"),
|
|
568
|
+
join3(coreDir, ".bigcommerce", "open-next.config.ts")
|
|
569
|
+
);
|
|
570
|
+
await writeFile(
|
|
571
|
+
join3(coreDir, ".bigcommerce", "wrangler.jsonc"),
|
|
572
|
+
JSON.stringify(wranglerConfig, null, 2)
|
|
573
|
+
);
|
|
574
|
+
consola.success("Templates copied");
|
|
575
|
+
consola.start("Building project...");
|
|
576
|
+
await execa(
|
|
577
|
+
"pnpm",
|
|
578
|
+
[
|
|
579
|
+
"exec",
|
|
580
|
+
"opennextjs-cloudflare",
|
|
581
|
+
"build",
|
|
582
|
+
"--skipWranglerConfigCheck",
|
|
583
|
+
"--openNextConfigPath",
|
|
584
|
+
join3(coreDir, ".bigcommerce", "open-next.config.ts")
|
|
585
|
+
],
|
|
586
|
+
{
|
|
587
|
+
stdout: ["pipe", "inherit"],
|
|
588
|
+
cwd: coreDir
|
|
201
589
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
JSON.stringify(wranglerConfig, null, 2)
|
|
220
|
-
);
|
|
221
|
-
consola.success("Templates copied");
|
|
222
|
-
consola.start("Building project...");
|
|
223
|
-
await execa(
|
|
224
|
-
"pnpm",
|
|
225
|
-
[
|
|
226
|
-
"exec",
|
|
227
|
-
"opennextjs-cloudflare",
|
|
228
|
-
"build",
|
|
229
|
-
"--skipWranglerConfigCheck",
|
|
230
|
-
"--openNextConfigPath",
|
|
231
|
-
join3(coreDir, ".bigcommerce", "open-next.config.ts")
|
|
232
|
-
],
|
|
233
|
-
{
|
|
234
|
-
stdout: ["pipe", "inherit"],
|
|
235
|
-
cwd: coreDir
|
|
236
|
-
}
|
|
237
|
-
);
|
|
238
|
-
await execa(
|
|
239
|
-
"pnpm",
|
|
240
|
-
[
|
|
241
|
-
"dlx",
|
|
242
|
-
`wrangler@${WRANGLER_VERSION}`,
|
|
243
|
-
"deploy",
|
|
244
|
-
"--config",
|
|
245
|
-
join3(coreDir, ".bigcommerce", "wrangler.jsonc"),
|
|
246
|
-
"--keep-vars",
|
|
247
|
-
"--outdir",
|
|
248
|
-
bigcommerceDistDir,
|
|
249
|
-
"--dry-run"
|
|
250
|
-
],
|
|
251
|
-
{
|
|
252
|
-
stdout: ["pipe", "inherit"],
|
|
253
|
-
cwd: coreDir
|
|
254
|
-
}
|
|
255
|
-
);
|
|
256
|
-
consola.success("Project built");
|
|
257
|
-
await cp(join3(openNextOutDir, "assets"), join3(bigcommerceDistDir, "assets"), {
|
|
258
|
-
recursive: true,
|
|
259
|
-
force: true
|
|
260
|
-
});
|
|
590
|
+
);
|
|
591
|
+
await execa(
|
|
592
|
+
"pnpm",
|
|
593
|
+
[
|
|
594
|
+
"dlx",
|
|
595
|
+
`wrangler@${WRANGLER_VERSION}`,
|
|
596
|
+
"deploy",
|
|
597
|
+
"--config",
|
|
598
|
+
join3(coreDir, ".bigcommerce", "wrangler.jsonc"),
|
|
599
|
+
"--keep-vars",
|
|
600
|
+
"--outdir",
|
|
601
|
+
bigcommerceDistDir,
|
|
602
|
+
"--dry-run"
|
|
603
|
+
],
|
|
604
|
+
{
|
|
605
|
+
stdout: ["pipe", "inherit"],
|
|
606
|
+
cwd: coreDir
|
|
261
607
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
608
|
+
);
|
|
609
|
+
consola.success("Project built");
|
|
610
|
+
await cp(join3(openNextOutDir, "assets"), join3(bigcommerceDistDir, "assets"), {
|
|
611
|
+
recursive: true,
|
|
612
|
+
force: true
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
var build = new Command2("build").addOption(
|
|
616
|
+
new Option2(
|
|
617
|
+
"--project-uuid <uuid>",
|
|
618
|
+
"Project UUID to be included in the deployment configuration."
|
|
619
|
+
).env("CATALYST_PROJECT_UUID")
|
|
620
|
+
).action(async (options) => {
|
|
621
|
+
const config2 = getProjectConfig();
|
|
622
|
+
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
623
|
+
if (!projectUuid) {
|
|
624
|
+
throw new Error(
|
|
625
|
+
"Project UUID is required. Please run `catalyst project create` or `catalyst project link` or this command again with --project-uuid <uuid>."
|
|
626
|
+
);
|
|
265
627
|
}
|
|
628
|
+
await buildCatalystProject(projectUuid);
|
|
266
629
|
});
|
|
267
630
|
|
|
268
631
|
// src/cli/commands/deploy.ts
|
|
269
632
|
import AdmZip from "adm-zip";
|
|
270
|
-
import { Command as
|
|
633
|
+
import { Command as Command3, Option as Option3 } from "commander";
|
|
271
634
|
import { access, readdir, readFile } from "fs/promises";
|
|
272
635
|
import { join as join4 } from "path";
|
|
273
|
-
import
|
|
274
|
-
import { z } from "zod";
|
|
636
|
+
import yoctoSpinner2 from "yocto-spinner";
|
|
637
|
+
import { z as z4 } from "zod";
|
|
275
638
|
|
|
276
|
-
// src/cli/lib/
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
properties: {
|
|
304
|
-
...payload,
|
|
305
|
-
sessionId: this.sessionId
|
|
306
|
-
},
|
|
307
|
-
context: {
|
|
308
|
-
app: {
|
|
309
|
-
name: this.projectName,
|
|
310
|
-
version: this.projectVersion
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
async identify(storeHash) {
|
|
316
|
-
if (!this.isEnabled()) {
|
|
317
|
-
return Promise.resolve(void 0);
|
|
318
|
-
}
|
|
319
|
-
if (!storeHash) {
|
|
320
|
-
return Promise.resolve(void 0);
|
|
321
|
-
}
|
|
322
|
-
this.analytics.identify({
|
|
323
|
-
userId: storeHash,
|
|
324
|
-
anonymousId: this.getAnonymousId(),
|
|
325
|
-
context: {
|
|
326
|
-
app: {
|
|
327
|
-
name: this.projectName,
|
|
328
|
-
version: this.projectVersion
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
isEnabled() {
|
|
334
|
-
return !this.CATALYST_TELEMETRY_DISABLED && this.projectConfig.get(TELEMETRY_KEY_ENABLED, true);
|
|
335
|
-
}
|
|
336
|
-
getAnonymousId() {
|
|
337
|
-
const val = this.projectConfig.get(TELEMETRY_KEY_ID);
|
|
338
|
-
if (val) {
|
|
339
|
-
return val;
|
|
340
|
-
}
|
|
341
|
-
const generated = randomBytes(32).toString("hex");
|
|
342
|
-
this.projectConfig.set(TELEMETRY_KEY_ID, generated);
|
|
343
|
-
return generated;
|
|
639
|
+
// src/cli/lib/deployment-errors.ts
|
|
640
|
+
var ERROR_MESSAGES = {
|
|
641
|
+
10: "Something went wrong on our end. Please try again. If the issue persists, contact support.",
|
|
642
|
+
20: "We couldn't retrieve your bundle. This is usually a temporary issue \u2014 please try deploying again. If the problem continues, contact support.",
|
|
643
|
+
30: "Your bundle could not be extracted. This may mean your build output is too large (max 64 MB compressed / 512 MB uncompressed) or the archive is corrupted. Try reducing your build size or rebuilding your project and deploying again.",
|
|
644
|
+
40: `There's a problem with your build output. This could be caused by:
|
|
645
|
+
- A worker.js file larger than 40 MB
|
|
646
|
+
- An individual asset file larger than 25 MB
|
|
647
|
+
- More than 1,000 total files in the bundle`,
|
|
648
|
+
50: "Deployment failed. This is usually a temporary issue \u2014 please try again. If the problem persists, contact support.",
|
|
649
|
+
60: "Your code was deployed, but we couldn't determine your deployment URL. Please try deploying again. If the issue persists, contact support."
|
|
650
|
+
};
|
|
651
|
+
function getDeploymentErrorMessage(code) {
|
|
652
|
+
return ERROR_MESSAGES[code] ?? `Deployment failed with an unexpected error (code: ${code}). Please try again. If the issue persists, contact support.`;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/cli/lib/resolve-credentials.ts
|
|
656
|
+
function resolveCredentials(options, config2) {
|
|
657
|
+
const storeHash = options.storeHash ?? config2.get("storeHash");
|
|
658
|
+
const accessToken = options.accessToken ?? config2.get("accessToken");
|
|
659
|
+
if (!storeHash || !accessToken) {
|
|
660
|
+
consola.error("Missing credentials.");
|
|
661
|
+
consola.info(
|
|
662
|
+
"Run `catalyst auth login`, or provide --store-hash and --access-token flags (or set CATALYST_STORE_HASH and CATALYST_ACCESS_TOKEN environment variables)."
|
|
663
|
+
);
|
|
664
|
+
process.exit(1);
|
|
665
|
+
throw new Error("Missing credentials");
|
|
344
666
|
}
|
|
345
|
-
};
|
|
667
|
+
return { storeHash, accessToken };
|
|
668
|
+
}
|
|
346
669
|
|
|
347
670
|
// src/cli/commands/deploy.ts
|
|
348
|
-
var
|
|
349
|
-
var stepsEnum = z.enum([
|
|
671
|
+
var stepsEnum = z4.enum([
|
|
350
672
|
"initializing",
|
|
351
673
|
"downloading",
|
|
352
674
|
"unzipping",
|
|
@@ -364,26 +686,35 @@ var STEPS = {
|
|
|
364
686
|
finalizing: "Finalizing...",
|
|
365
687
|
complete: "Complete"
|
|
366
688
|
};
|
|
367
|
-
var UploadSignatureSchema =
|
|
368
|
-
data:
|
|
369
|
-
upload_url:
|
|
370
|
-
upload_uuid:
|
|
689
|
+
var UploadSignatureSchema = z4.object({
|
|
690
|
+
data: z4.object({
|
|
691
|
+
upload_url: z4.url(),
|
|
692
|
+
upload_uuid: z4.string()
|
|
371
693
|
})
|
|
372
694
|
});
|
|
373
|
-
var CreateDeploymentSchema =
|
|
374
|
-
data:
|
|
375
|
-
deployment_uuid:
|
|
695
|
+
var CreateDeploymentSchema = z4.object({
|
|
696
|
+
data: z4.object({
|
|
697
|
+
deployment_uuid: z4.uuid()
|
|
376
698
|
})
|
|
377
699
|
});
|
|
378
|
-
var
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
700
|
+
var ProjectSchema = z4.object({
|
|
701
|
+
data: z4.array(
|
|
702
|
+
z4.object({
|
|
703
|
+
uuid: z4.uuid(),
|
|
704
|
+
name: z4.string()
|
|
705
|
+
})
|
|
706
|
+
)
|
|
707
|
+
});
|
|
708
|
+
var DeploymentStatusSchema = z4.object({
|
|
709
|
+
deployment_uuid: z4.uuid(),
|
|
710
|
+
deployment_status: z4.enum(["queued", "in_progress", "failed", "completed"]),
|
|
711
|
+
event: z4.object({
|
|
382
712
|
step: stepsEnum,
|
|
383
|
-
progress:
|
|
713
|
+
progress: z4.number()
|
|
384
714
|
}).nullable(),
|
|
385
|
-
|
|
386
|
-
|
|
715
|
+
deployment_url: z4.string().nullable(),
|
|
716
|
+
error: z4.object({
|
|
717
|
+
code: z4.number()
|
|
387
718
|
}).optional()
|
|
388
719
|
});
|
|
389
720
|
var generateBundleZip = async () => {
|
|
@@ -414,7 +745,8 @@ var generateUploadSignature = async (storeHash, accessToken, apiHost) => {
|
|
|
414
745
|
headers: {
|
|
415
746
|
"X-Auth-Token": accessToken,
|
|
416
747
|
"Content-Type": "application/json",
|
|
417
|
-
Accept: "application/json"
|
|
748
|
+
Accept: "application/json",
|
|
749
|
+
"X-Correlation-Id": getTelemetry().sessionId
|
|
418
750
|
},
|
|
419
751
|
body: JSON.stringify({})
|
|
420
752
|
}
|
|
@@ -466,7 +798,8 @@ var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, a
|
|
|
466
798
|
headers: {
|
|
467
799
|
"X-Auth-Token": accessToken,
|
|
468
800
|
"Content-Type": "application/json",
|
|
469
|
-
Accept: "application/json"
|
|
801
|
+
Accept: "application/json",
|
|
802
|
+
"X-Correlation-Id": getTelemetry().sessionId
|
|
470
803
|
},
|
|
471
804
|
body: JSON.stringify({
|
|
472
805
|
project_uuid: projectUuid,
|
|
@@ -485,7 +818,7 @@ var createDeployment = async (projectUuid, uploadUuid, storeHash, accessToken, a
|
|
|
485
818
|
};
|
|
486
819
|
var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost) => {
|
|
487
820
|
consola.info("Fetching deployment status...");
|
|
488
|
-
const spinner =
|
|
821
|
+
const spinner = yoctoSpinner2().start("Fetching...");
|
|
489
822
|
const response = await fetch(
|
|
490
823
|
`https://${apiHost}/stores/${storeHash}/v3/infrastructure/deployments/${deploymentUuid}/events`,
|
|
491
824
|
{
|
|
@@ -493,7 +826,8 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
493
826
|
headers: {
|
|
494
827
|
"X-Auth-Token": accessToken,
|
|
495
828
|
Accept: "text/event-stream",
|
|
496
|
-
Connection: "keep-alive"
|
|
829
|
+
Connection: "keep-alive",
|
|
830
|
+
"X-Correlation-Id": getTelemetry().sessionId
|
|
497
831
|
}
|
|
498
832
|
}
|
|
499
833
|
);
|
|
@@ -506,6 +840,7 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
506
840
|
}
|
|
507
841
|
const decoder = new TextDecoder();
|
|
508
842
|
let done = false;
|
|
843
|
+
let deploymentUrl;
|
|
509
844
|
while (!done) {
|
|
510
845
|
const { value, done: streamDone } = await reader.read();
|
|
511
846
|
let json;
|
|
@@ -521,325 +856,489 @@ var getDeploymentStatus = async (deploymentUuid, storeHash, accessToken, apiHost
|
|
|
521
856
|
}
|
|
522
857
|
const data = DeploymentStatusSchema.parse(json);
|
|
523
858
|
if (data.error) {
|
|
524
|
-
throw new Error(
|
|
859
|
+
throw new Error(
|
|
860
|
+
`Deployment failed (error code ${data.error.code}): ${getDeploymentErrorMessage(data.error.code)}`
|
|
861
|
+
);
|
|
525
862
|
}
|
|
526
863
|
if (data.event && STEPS[data.event.step] !== spinner.text) {
|
|
527
864
|
spinner.text = STEPS[data.event.step];
|
|
528
865
|
}
|
|
866
|
+
if (data.deployment_url) {
|
|
867
|
+
console.log(data.deployment_url);
|
|
868
|
+
deploymentUrl = data.deployment_url;
|
|
869
|
+
}
|
|
529
870
|
});
|
|
530
871
|
}
|
|
531
872
|
done = streamDone;
|
|
532
873
|
}
|
|
533
|
-
spinner.success("Deployment completed successfully
|
|
874
|
+
spinner.success("Deployment completed successfully.\n");
|
|
875
|
+
if (deploymentUrl) {
|
|
876
|
+
consola.success(`View your deployment at: ${deploymentUrl}`);
|
|
877
|
+
}
|
|
534
878
|
};
|
|
535
|
-
var deploy = new
|
|
536
|
-
new
|
|
879
|
+
var deploy = new Command3("deploy").description("Deploy your application to Cloudflare.").addOption(
|
|
880
|
+
new Option3(
|
|
537
881
|
"--store-hash <hash>",
|
|
538
|
-
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
539
|
-
).env("
|
|
882
|
+
"BigCommerce store hash. Can be found in the URL of your store Control Panel. Read from .bigcommerce/project.json when not provided."
|
|
883
|
+
).env("CATALYST_STORE_HASH")
|
|
540
884
|
).addOption(
|
|
541
|
-
new
|
|
885
|
+
new Option3(
|
|
542
886
|
"--access-token <token>",
|
|
543
|
-
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
544
|
-
).env("
|
|
887
|
+
"BigCommerce access token. Can be found after creating a store-level API account. Read from .bigcommerce/project.json when not provided."
|
|
888
|
+
).env("CATALYST_ACCESS_TOKEN")
|
|
545
889
|
).addOption(
|
|
546
|
-
new
|
|
890
|
+
new Option3("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com")
|
|
547
891
|
).addOption(
|
|
548
|
-
new
|
|
892
|
+
new Option3(
|
|
549
893
|
"--project-uuid <uuid>",
|
|
550
894
|
"BigCommerce intrastructure project UUID. Can be found via the BigCommerce API (GET /v3/infrastructure/projects)."
|
|
551
|
-
).env("
|
|
895
|
+
).env("CATALYST_PROJECT_UUID")
|
|
552
896
|
).addOption(
|
|
553
|
-
new
|
|
554
|
-
"--secret <
|
|
555
|
-
"
|
|
556
|
-
)
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
897
|
+
new Option3(
|
|
898
|
+
"--secret <value>",
|
|
899
|
+
"Secret to set for the deployment (repeatable). Format: --secret KEY=VALUE"
|
|
900
|
+
).argParser((value, previous = []) => {
|
|
901
|
+
return previous.concat([value]);
|
|
902
|
+
})
|
|
903
|
+
).option("--dry-run", "Run the command to generate the bundle without uploading or deploying.").option(
|
|
904
|
+
"--prebuilt",
|
|
905
|
+
"Skip the build step. Requires .bigcommerce/dist/ to already contain build output."
|
|
906
|
+
).action(async (options) => {
|
|
907
|
+
const config2 = getProjectConfig();
|
|
908
|
+
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
909
|
+
const telemetry3 = getTelemetry();
|
|
910
|
+
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
911
|
+
if (!projectUuid) {
|
|
912
|
+
throw new Error(
|
|
913
|
+
"Project UUID is required. Please run either `catalyst project link` or `catalyst project create` or this command again with --project-uuid <uuid>."
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
await telemetry3.identify(storeHash);
|
|
917
|
+
if (options.prebuilt) {
|
|
918
|
+
const distDir = join4(process.cwd(), ".bigcommerce", "dist");
|
|
919
|
+
try {
|
|
920
|
+
await access(distDir);
|
|
921
|
+
} catch {
|
|
563
922
|
throw new Error(
|
|
564
|
-
"
|
|
923
|
+
"No build output found at .bigcommerce/dist/. Run `catalyst build` first or remove `--prebuilt` to build automatically."
|
|
565
924
|
);
|
|
566
925
|
}
|
|
567
|
-
await
|
|
568
|
-
if (
|
|
569
|
-
consola.info("Dry run enabled \u2014 skipping upload and deployment steps.");
|
|
570
|
-
consola.info("Next steps (skipped):");
|
|
571
|
-
consola.info("- Generate upload signature");
|
|
572
|
-
consola.info("- Upload bundle.zip");
|
|
573
|
-
consola.info("- Create deployment");
|
|
574
|
-
process.exit(0);
|
|
575
|
-
}
|
|
576
|
-
const uploadSignature = await generateUploadSignature(
|
|
577
|
-
options.storeHash,
|
|
578
|
-
options.accessToken,
|
|
579
|
-
options.apiHost
|
|
580
|
-
);
|
|
581
|
-
await uploadBundleZip(uploadSignature.upload_url);
|
|
582
|
-
const environmentVariables = parseEnvironmentVariables(options.secret);
|
|
583
|
-
const { deployment_uuid: deploymentUuid } = await createDeployment(
|
|
584
|
-
projectUuid,
|
|
585
|
-
uploadSignature.upload_uuid,
|
|
586
|
-
options.storeHash,
|
|
587
|
-
options.accessToken,
|
|
588
|
-
options.apiHost,
|
|
589
|
-
environmentVariables
|
|
590
|
-
);
|
|
591
|
-
await getDeploymentStatus(
|
|
592
|
-
deploymentUuid,
|
|
593
|
-
options.storeHash,
|
|
594
|
-
options.accessToken,
|
|
595
|
-
options.apiHost
|
|
596
|
-
);
|
|
597
|
-
} catch (error) {
|
|
598
|
-
consola.error(error);
|
|
599
|
-
process.exit(1);
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
// src/cli/commands/dev.ts
|
|
604
|
-
import { Command as Command3 } from "commander";
|
|
605
|
-
import { execa as execa2 } from "execa";
|
|
606
|
-
import { existsSync as existsSync2 } from "fs";
|
|
607
|
-
import { join as join5 } from "path";
|
|
608
|
-
var dev = new Command3("dev").description("Start the Catalyst development server.").helpOption(false).allowUnknownOption(true).argument(
|
|
609
|
-
"[options...]",
|
|
610
|
-
"Next.js `dev` options (see: https://nextjs.org/docs/app/api-reference/cli/next#next-dev-options)"
|
|
611
|
-
).action(async (options) => {
|
|
612
|
-
try {
|
|
613
|
-
const nextBin = join5("node_modules", ".bin", "next");
|
|
614
|
-
if (!existsSync2(nextBin)) {
|
|
926
|
+
const contents = await readdir(distDir);
|
|
927
|
+
if (contents.length === 0) {
|
|
615
928
|
throw new Error(
|
|
616
|
-
|
|
929
|
+
"No build output found at .bigcommerce/dist/. Run `catalyst build` first or remove `--prebuilt` to build automatically."
|
|
617
930
|
);
|
|
618
931
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
});
|
|
623
|
-
} catch (error) {
|
|
624
|
-
consola.error(error instanceof Error ? error.message : error);
|
|
625
|
-
process.exit(1);
|
|
932
|
+
consola.info("Using existing build output (--prebuilt).");
|
|
933
|
+
} else {
|
|
934
|
+
await buildCatalystProject(projectUuid);
|
|
626
935
|
}
|
|
936
|
+
await generateBundleZip();
|
|
937
|
+
if (options.dryRun) {
|
|
938
|
+
consola.info("Dry run enabled \u2014 skipping upload and deployment steps.");
|
|
939
|
+
consola.info("Next steps (skipped):");
|
|
940
|
+
consola.info("- Generate upload signature");
|
|
941
|
+
consola.info("- Upload bundle.zip");
|
|
942
|
+
consola.info("- Create deployment");
|
|
943
|
+
process.exit(0);
|
|
944
|
+
}
|
|
945
|
+
const uploadSignature = await generateUploadSignature(storeHash, accessToken, options.apiHost);
|
|
946
|
+
await uploadBundleZip(uploadSignature.upload_url);
|
|
947
|
+
const environmentVariables = parseEnvironmentVariables(options.secret);
|
|
948
|
+
const { deployment_uuid: deploymentUuid } = await createDeployment(
|
|
949
|
+
projectUuid,
|
|
950
|
+
uploadSignature.upload_uuid,
|
|
951
|
+
storeHash,
|
|
952
|
+
accessToken,
|
|
953
|
+
options.apiHost,
|
|
954
|
+
environmentVariables
|
|
955
|
+
);
|
|
956
|
+
await getDeploymentStatus(deploymentUuid, storeHash, accessToken, options.apiHost);
|
|
627
957
|
});
|
|
628
958
|
|
|
629
|
-
// src/cli/commands/
|
|
630
|
-
import { Command as Command4, Option as
|
|
631
|
-
import {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
959
|
+
// src/cli/commands/logs.ts
|
|
960
|
+
import { Command as Command4, Option as Option5 } from "commander";
|
|
961
|
+
import { colorize as colorize2 } from "consola/utils";
|
|
962
|
+
import { z as z5 } from "zod";
|
|
963
|
+
|
|
964
|
+
// src/cli/lib/shared-options.ts
|
|
965
|
+
import { Option as Option4 } from "commander";
|
|
966
|
+
var storeHashOption = () => new Option4(
|
|
967
|
+
"--store-hash <hash>",
|
|
968
|
+
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
969
|
+
).env("BIGCOMMERCE_STORE_HASH");
|
|
970
|
+
var accessTokenOption = () => new Option4(
|
|
971
|
+
"--access-token <token>",
|
|
972
|
+
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
973
|
+
).env("BIGCOMMERCE_ACCESS_TOKEN");
|
|
974
|
+
var apiHostOption = () => new Option4("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com");
|
|
975
|
+
var projectUuidOption = () => new Option4(
|
|
976
|
+
"--project-uuid <uuid>",
|
|
977
|
+
"BigCommerce infrastructure project UUID. Can be found via the BigCommerce API (GET /v3/infrastructure/projects)."
|
|
978
|
+
).env("BIGCOMMERCE_PROJECT_UUID");
|
|
979
|
+
var resolveProjectUuid = (options) => {
|
|
980
|
+
const config2 = getProjectConfig();
|
|
981
|
+
const projectUuid = options.projectUuid ?? config2.get("projectUuid");
|
|
982
|
+
if (!projectUuid) {
|
|
983
|
+
throw new Error(
|
|
984
|
+
"Project UUID is required. Please run either `catalyst project link` or `catalyst project create` or this command again with --project-uuid <uuid>."
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
return projectUuid;
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// src/cli/commands/logs.ts
|
|
991
|
+
var telemetry = new Telemetry();
|
|
992
|
+
var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
|
|
993
|
+
var MAX_RETRIES = 5;
|
|
994
|
+
var isFatalStatusCode = (status) => status >= 400 && status < 500;
|
|
995
|
+
var LEVEL_COLORS = {
|
|
996
|
+
INFO: "green",
|
|
997
|
+
WARN: "yellow",
|
|
998
|
+
ERROR: "red",
|
|
999
|
+
DEBUG: "gray"
|
|
1000
|
+
};
|
|
1001
|
+
var LogEventSchema = z5.object({
|
|
1002
|
+
uuid: z5.string(),
|
|
1003
|
+
project_uuid: z5.string(),
|
|
1004
|
+
request: z5.object({
|
|
1005
|
+
method: z5.string(),
|
|
1006
|
+
url: z5.string(),
|
|
1007
|
+
status_code: z5.number()
|
|
1008
|
+
}),
|
|
1009
|
+
logs: z5.array(
|
|
1010
|
+
z5.object({
|
|
1011
|
+
timestamp: z5.string(),
|
|
1012
|
+
level: z5.string(),
|
|
1013
|
+
messages: z5.array(z5.unknown())
|
|
638
1014
|
})
|
|
639
|
-
)
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
1015
|
+
),
|
|
1016
|
+
exceptions: z5.array(z5.unknown()),
|
|
1017
|
+
timestamp: z5.string()
|
|
1018
|
+
}).loose();
|
|
1019
|
+
var StreamError = class extends Error {
|
|
1020
|
+
constructor(message, fatal) {
|
|
1021
|
+
super(message);
|
|
1022
|
+
this.fatal = fatal;
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
var formatMessages = (messages) => messages.map((m) => typeof m === "string" ? m : JSON.stringify(m)).join(" ");
|
|
1026
|
+
var formatLogEvent = (event, format) => {
|
|
1027
|
+
const { request, logs: logEntries, exceptions } = event;
|
|
1028
|
+
logEntries.forEach((entry) => {
|
|
1029
|
+
const msg = formatMessages(entry.messages);
|
|
1030
|
+
const level = entry.level.toUpperCase();
|
|
1031
|
+
const coloredLevel = colorize2(LEVEL_COLORS[level] ?? "white", level);
|
|
1032
|
+
switch (format) {
|
|
1033
|
+
case "short":
|
|
1034
|
+
consola.log(msg);
|
|
1035
|
+
break;
|
|
1036
|
+
case "request":
|
|
1037
|
+
consola.log(
|
|
1038
|
+
`[${entry.timestamp}] [${coloredLevel}] ${request.method} ${request.url} (${request.status_code}) ${msg}`
|
|
1039
|
+
);
|
|
1040
|
+
break;
|
|
1041
|
+
default:
|
|
1042
|
+
consola.log(`[${entry.timestamp}] [${coloredLevel}] ${msg}`);
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
exceptions.forEach((exception) => {
|
|
1047
|
+
consola.error(`[${event.timestamp}] EXCEPTION`, exception);
|
|
1048
|
+
});
|
|
1049
|
+
};
|
|
1050
|
+
var parseSSEEvent = (raw) => {
|
|
1051
|
+
const joined = raw.split("\n").flatMap((line) => line.startsWith("data:") ? [line.slice(5).trim()] : []).join("\n");
|
|
1052
|
+
return joined.length > 0 ? joined : null;
|
|
1053
|
+
};
|
|
1054
|
+
var processLogEvent = (event, format) => {
|
|
1055
|
+
if (format === "json") {
|
|
1056
|
+
process.stdout.write(`${event}
|
|
1057
|
+
`);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
const parsed = JSON.parse(event);
|
|
1062
|
+
const logEvent = LogEventSchema.parse(parsed);
|
|
1063
|
+
if (format === "pretty") {
|
|
1064
|
+
consola.log(JSON.stringify(logEvent, null, 2));
|
|
1065
|
+
} else {
|
|
1066
|
+
formatLogEvent(logEvent, format);
|
|
1067
|
+
}
|
|
1068
|
+
} catch {
|
|
1069
|
+
consola.warn(`Failed to parse log event: ${event}`);
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
var openLogStream = async (projectUuid, storeHash, accessToken, apiHost) => {
|
|
650
1073
|
const response = await fetch(
|
|
651
|
-
`https://${apiHost}/stores/${storeHash}/v3/infrastructure/
|
|
1074
|
+
`https://${apiHost}/stores/${storeHash}/v3/infrastructure/logs/${projectUuid}/tail`,
|
|
652
1075
|
{
|
|
653
1076
|
method: "GET",
|
|
654
1077
|
headers: {
|
|
655
|
-
"X-Auth-Token": accessToken
|
|
1078
|
+
"X-Auth-Token": accessToken,
|
|
1079
|
+
Accept: "text/event-stream",
|
|
1080
|
+
Connection: "keep-alive"
|
|
656
1081
|
}
|
|
657
1082
|
}
|
|
658
1083
|
);
|
|
659
|
-
if (response.
|
|
660
|
-
throw new
|
|
661
|
-
|
|
1084
|
+
if (!response.ok) {
|
|
1085
|
+
throw new StreamError(
|
|
1086
|
+
`Failed to open log stream: ${response.status} ${response.statusText}`,
|
|
1087
|
+
isFatalStatusCode(response.status)
|
|
662
1088
|
);
|
|
663
1089
|
}
|
|
664
|
-
|
|
665
|
-
|
|
1090
|
+
const reader = response.body?.getReader();
|
|
1091
|
+
if (!reader) {
|
|
1092
|
+
throw new StreamError("Failed to read log stream.", true);
|
|
666
1093
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
1094
|
+
return reader;
|
|
1095
|
+
};
|
|
1096
|
+
var tailLogs = async (projectUuid, storeHash, accessToken, apiHost, format, connectionTtlMs = DEFAULT_CONNECTION_TTL_MS) => {
|
|
1097
|
+
consola.info("Tailing logs...");
|
|
1098
|
+
let retries = 0;
|
|
1099
|
+
while (true) {
|
|
1100
|
+
try {
|
|
1101
|
+
const reader = await openLogStream(projectUuid, storeHash, accessToken, apiHost);
|
|
1102
|
+
const decoder = new TextDecoder();
|
|
1103
|
+
const connectTime = Date.now();
|
|
1104
|
+
let buffer = "";
|
|
1105
|
+
retries = 0;
|
|
1106
|
+
while (true) {
|
|
1107
|
+
const { value, done: streamDone } = await reader.read();
|
|
1108
|
+
if (value) {
|
|
1109
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1110
|
+
const parts = buffer.split("\n\n");
|
|
1111
|
+
buffer = parts.pop() ?? "";
|
|
1112
|
+
parts.map((raw) => parseSSEEvent(raw)).filter((event) => event !== null).forEach((event) => processLogEvent(event, format));
|
|
1113
|
+
}
|
|
1114
|
+
if (streamDone || Date.now() - connectTime >= connectionTtlMs) {
|
|
1115
|
+
void reader.cancel();
|
|
1116
|
+
break;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
if (error instanceof StreamError && error.fatal) {
|
|
1121
|
+
throw error;
|
|
1122
|
+
}
|
|
1123
|
+
const isServerDisconnect = error instanceof TypeError && error.message === "terminated";
|
|
1124
|
+
if (isServerDisconnect) {
|
|
1125
|
+
consola.warn("Log stream closed by server, reconnecting...");
|
|
1126
|
+
} else {
|
|
1127
|
+
retries += 1;
|
|
1128
|
+
if (retries >= MAX_RETRIES) {
|
|
1129
|
+
throw new Error(`Failed to connect to log stream after ${MAX_RETRIES} retries.`);
|
|
1130
|
+
}
|
|
1131
|
+
consola.warn(
|
|
1132
|
+
`Log stream disconnected, reconnecting (attempt ${retries}/${MAX_RETRIES})...`,
|
|
1133
|
+
error
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
682
1136
|
}
|
|
683
|
-
);
|
|
684
|
-
if (response.status === 502) {
|
|
685
|
-
throw new Error("Failed to create project, is the name already in use?");
|
|
686
1137
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1138
|
+
};
|
|
1139
|
+
var tail = new Command4("tail").description("Tail live logs from your deployed application.").addOption(storeHashOption().makeOptionMandatory()).addOption(accessTokenOption().makeOptionMandatory()).addOption(apiHostOption()).addOption(projectUuidOption()).addOption(
|
|
1140
|
+
new Option5("--format <format>", "Output format for log events.").choices(["json", "pretty", "default", "short", "request"]).default("default")
|
|
1141
|
+
).action(async (options) => {
|
|
1142
|
+
try {
|
|
1143
|
+
await telemetry.identify(options.storeHash);
|
|
1144
|
+
const projectUuid = resolveProjectUuid(options);
|
|
1145
|
+
await tailLogs(
|
|
1146
|
+
projectUuid,
|
|
1147
|
+
options.storeHash,
|
|
1148
|
+
options.accessToken,
|
|
1149
|
+
options.apiHost,
|
|
1150
|
+
options.format
|
|
690
1151
|
);
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
consola.error(error);
|
|
1154
|
+
process.exit(1);
|
|
691
1155
|
}
|
|
692
|
-
|
|
693
|
-
|
|
1156
|
+
});
|
|
1157
|
+
var query = new Command4("query").description("Query historical logs from your deployed application.").addOption(storeHashOption().makeOptionMandatory()).addOption(accessTokenOption().makeOptionMandatory()).addOption(apiHostOption()).addOption(projectUuidOption()).action((_options) => {
|
|
1158
|
+
consola.error("The query command is not yet implemented.");
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
});
|
|
1161
|
+
var logs = new Command4("logs").description("View logs from your deployed application.").addCommand(tail, { isDefault: true }).addCommand(query);
|
|
1162
|
+
|
|
1163
|
+
// src/cli/commands/project.ts
|
|
1164
|
+
import { Command as Command5, Option as Option6 } from "commander";
|
|
1165
|
+
var list = new Command5("list").description("List BigCommerce infrastructure projects for your store.").addOption(
|
|
1166
|
+
new Option6(
|
|
1167
|
+
"--store-hash <hash>",
|
|
1168
|
+
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
1169
|
+
).env("CATALYST_STORE_HASH")
|
|
1170
|
+
).addOption(
|
|
1171
|
+
new Option6(
|
|
1172
|
+
"--access-token <token>",
|
|
1173
|
+
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
1174
|
+
).env("CATALYST_ACCESS_TOKEN")
|
|
1175
|
+
).addOption(
|
|
1176
|
+
new Option6("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com")
|
|
1177
|
+
).action(async (options) => {
|
|
1178
|
+
const config2 = getProjectConfig();
|
|
1179
|
+
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1180
|
+
await getTelemetry().identify(storeHash);
|
|
1181
|
+
consola.start("Fetching projects...");
|
|
1182
|
+
const projects = await fetchProjects(storeHash, accessToken, options.apiHost);
|
|
1183
|
+
consola.success("Projects fetched.");
|
|
1184
|
+
if (projects.length === 0) {
|
|
1185
|
+
consola.info("No projects found.");
|
|
1186
|
+
process.exit(0);
|
|
1187
|
+
return;
|
|
694
1188
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
1189
|
+
projects.forEach((p) => {
|
|
1190
|
+
consola.log(`${p.name} (${p.uuid})`);
|
|
1191
|
+
});
|
|
1192
|
+
process.exit(0);
|
|
1193
|
+
});
|
|
1194
|
+
var create = new Command5("create").description(
|
|
1195
|
+
"Create a new BigCommerce infrastructure project and link it to your local Catalyst project."
|
|
1196
|
+
).addOption(
|
|
1197
|
+
new Option6(
|
|
1198
|
+
"--store-hash <hash>",
|
|
1199
|
+
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
1200
|
+
).env("CATALYST_STORE_HASH")
|
|
1201
|
+
).addOption(
|
|
1202
|
+
new Option6(
|
|
1203
|
+
"--access-token <token>",
|
|
1204
|
+
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
1205
|
+
).env("CATALYST_ACCESS_TOKEN")
|
|
1206
|
+
).addOption(
|
|
1207
|
+
new Option6("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com")
|
|
1208
|
+
).action(async (options) => {
|
|
1209
|
+
const config2 = getProjectConfig();
|
|
1210
|
+
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1211
|
+
await getTelemetry().identify(storeHash);
|
|
1212
|
+
const newProjectName = await consola.prompt("Enter a name for the new project:", {
|
|
1213
|
+
type: "text"
|
|
1214
|
+
});
|
|
1215
|
+
const data = await createProject(newProjectName, storeHash, accessToken, options.apiHost);
|
|
1216
|
+
consola.success(`Project "${data.name}" created successfully.`);
|
|
1217
|
+
consola.start("Writing project UUID to .bigcommerce/project.json...");
|
|
1218
|
+
config2.set("projectUuid", data.uuid);
|
|
1219
|
+
config2.set("storeHash", storeHash);
|
|
1220
|
+
config2.set("accessToken", accessToken);
|
|
1221
|
+
consola.success("Project UUID written to .bigcommerce/project.json.");
|
|
1222
|
+
process.exit(0);
|
|
1223
|
+
});
|
|
1224
|
+
var link = new Command5("link").description(
|
|
700
1225
|
"Link your local Catalyst project to a BigCommerce infrastructure project. You can provide a project UUID directly, or fetch and select from available projects using your store credentials."
|
|
701
1226
|
).addOption(
|
|
702
|
-
new
|
|
1227
|
+
new Option6(
|
|
703
1228
|
"--store-hash <hash>",
|
|
704
1229
|
"BigCommerce store hash. Can be found in the URL of your store Control Panel."
|
|
705
|
-
).env("
|
|
1230
|
+
).env("CATALYST_STORE_HASH")
|
|
706
1231
|
).addOption(
|
|
707
|
-
new
|
|
1232
|
+
new Option6(
|
|
708
1233
|
"--access-token <token>",
|
|
709
1234
|
"BigCommerce access token. Can be found after creating a store-level API account."
|
|
710
|
-
).env("
|
|
1235
|
+
).env("CATALYST_ACCESS_TOKEN")
|
|
711
1236
|
).addOption(
|
|
712
|
-
new
|
|
1237
|
+
new Option6("--api-host <host>", "BigCommerce API host. The default is api.bigcommerce.com.").env("BIGCOMMERCE_API_HOST").default("api.bigcommerce.com")
|
|
713
1238
|
).option(
|
|
714
1239
|
"--project-uuid <uuid>",
|
|
715
1240
|
"BigCommerce infrastructure project UUID. Can be found via the BigCommerce API (GET /v3/infrastructure/projects). Use this to link directly without fetching projects."
|
|
716
|
-
).option(
|
|
717
|
-
"--root-dir <path>",
|
|
718
|
-
"Path to the root directory of your Catalyst project (default: current working directory).",
|
|
719
|
-
process.cwd()
|
|
720
1241
|
).action(async (options) => {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
config2.set("
|
|
727
|
-
|
|
728
|
-
};
|
|
729
|
-
if (options.projectUuid) {
|
|
730
|
-
writeProjectConfig(options.projectUuid);
|
|
731
|
-
process.exit(0);
|
|
1242
|
+
const config2 = getProjectConfig();
|
|
1243
|
+
const writeProjectConfig = (uuid, credentials) => {
|
|
1244
|
+
consola.start("Writing project UUID to .bigcommerce/project.json...");
|
|
1245
|
+
config2.set("projectUuid", uuid);
|
|
1246
|
+
if (credentials) {
|
|
1247
|
+
config2.set("storeHash", credentials.storeHash);
|
|
1248
|
+
config2.set("accessToken", credentials.accessToken);
|
|
732
1249
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
"Select a project or create a new project (Press <enter> to select).",
|
|
756
|
-
{
|
|
757
|
-
type: "select",
|
|
758
|
-
options: promptOptions,
|
|
759
|
-
cancel: "reject"
|
|
760
|
-
}
|
|
761
|
-
);
|
|
762
|
-
if (projectUuid === "create") {
|
|
763
|
-
const newProjectName = await consola.prompt("Enter a name for the new project:", {
|
|
764
|
-
type: "text"
|
|
765
|
-
});
|
|
766
|
-
const data = await createProject(
|
|
767
|
-
newProjectName,
|
|
768
|
-
options.storeHash,
|
|
769
|
-
options.accessToken,
|
|
770
|
-
options.apiHost
|
|
771
|
-
);
|
|
772
|
-
projectUuid = data.uuid;
|
|
773
|
-
consola.success(`Project "${data.name}" created successfully.`);
|
|
774
|
-
}
|
|
775
|
-
writeProjectConfig(projectUuid);
|
|
776
|
-
process.exit(0);
|
|
1250
|
+
consola.success("Project UUID written to .bigcommerce/project.json.");
|
|
1251
|
+
};
|
|
1252
|
+
if (options.projectUuid) {
|
|
1253
|
+
writeProjectConfig(options.projectUuid);
|
|
1254
|
+
process.exit(0);
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const { storeHash, accessToken } = resolveCredentials(options, config2);
|
|
1258
|
+
await getTelemetry().identify(storeHash);
|
|
1259
|
+
consola.start("Fetching projects...");
|
|
1260
|
+
const projects = await fetchProjects(storeHash, accessToken, options.apiHost);
|
|
1261
|
+
consola.success("Projects fetched.");
|
|
1262
|
+
const promptOptions = [
|
|
1263
|
+
...projects.map((proj) => ({
|
|
1264
|
+
label: proj.name,
|
|
1265
|
+
value: proj.uuid,
|
|
1266
|
+
hint: proj.uuid
|
|
1267
|
+
})),
|
|
1268
|
+
{
|
|
1269
|
+
label: "Create a new project",
|
|
1270
|
+
value: "create",
|
|
1271
|
+
hint: "Create a new infrastructure project for this BigCommerce store."
|
|
777
1272
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1273
|
+
];
|
|
1274
|
+
let projectUuid = await consola.prompt(
|
|
1275
|
+
"Select a project or create a new project (Press <enter> to select).",
|
|
1276
|
+
{
|
|
1277
|
+
type: "select",
|
|
1278
|
+
options: promptOptions,
|
|
1279
|
+
cancel: "reject"
|
|
1280
|
+
}
|
|
1281
|
+
);
|
|
1282
|
+
if (projectUuid === "create") {
|
|
1283
|
+
const newProjectName = await consola.prompt("Enter a name for the new project:", {
|
|
1284
|
+
type: "text"
|
|
1285
|
+
});
|
|
1286
|
+
const data = await createProject(newProjectName, storeHash, accessToken, options.apiHost);
|
|
1287
|
+
projectUuid = data.uuid;
|
|
1288
|
+
consola.success(`Project "${data.name}" created successfully.`);
|
|
785
1289
|
}
|
|
1290
|
+
writeProjectConfig(projectUuid, { storeHash, accessToken });
|
|
1291
|
+
process.exit(0);
|
|
786
1292
|
});
|
|
1293
|
+
var project = new Command5("project").description("Manage your BigCommerce infrastructure project.").addCommand(create).addCommand(list).addCommand(link);
|
|
787
1294
|
|
|
788
1295
|
// src/cli/commands/start.ts
|
|
789
|
-
import { Command as
|
|
790
|
-
import { execa as
|
|
791
|
-
import { existsSync
|
|
792
|
-
import { join as
|
|
793
|
-
var start = new
|
|
794
|
-
"
|
|
795
|
-
|
|
796
|
-
).
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
if (!existsSync3(nextBin)) {
|
|
808
|
-
throw new Error(
|
|
809
|
-
`Next.js is not installed in ${process.cwd()}. Are you in a valid Next.js project?`
|
|
810
|
-
);
|
|
811
|
-
}
|
|
812
|
-
await execa3(nextBin, ["start", ...startOptions], {
|
|
813
|
-
stdio: "inherit",
|
|
814
|
-
cwd: process.cwd()
|
|
815
|
-
});
|
|
1296
|
+
import { Command as Command6 } from "commander";
|
|
1297
|
+
import { execa as execa2 } from "execa";
|
|
1298
|
+
import { existsSync, lstatSync, symlinkSync } from "fs";
|
|
1299
|
+
import { join as join5, relative } from "path";
|
|
1300
|
+
var start = new Command6("start").description(
|
|
1301
|
+
"Start a local preview of your Catalyst storefront using the OpenNext Cloudflare adapter."
|
|
1302
|
+
).action(async () => {
|
|
1303
|
+
const envLocal = join5(process.cwd(), ".env.local");
|
|
1304
|
+
const devVars = join5(process.cwd(), ".bigcommerce", ".dev.vars");
|
|
1305
|
+
if (existsSync(envLocal)) {
|
|
1306
|
+
if (!existsSync(devVars)) {
|
|
1307
|
+
const target = relative(join5(process.cwd(), ".bigcommerce"), envLocal);
|
|
1308
|
+
symlinkSync(target, devVars);
|
|
1309
|
+
consola.success("Symlinked .bigcommerce/.dev.vars \u2192 .env.local");
|
|
1310
|
+
} else if (!lstatSync(devVars).isSymbolicLink()) {
|
|
1311
|
+
consola.warn(
|
|
1312
|
+
".bigcommerce/.dev.vars already exists and is not a symlink. Wrangler will use it instead of .env.local."
|
|
1313
|
+
);
|
|
816
1314
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
"exec",
|
|
821
|
-
"opennextjs-cloudflare",
|
|
822
|
-
"preview",
|
|
823
|
-
"--config",
|
|
824
|
-
join6(".bigcommerce", "wrangler.jsonc"),
|
|
825
|
-
...startOptions
|
|
826
|
-
],
|
|
827
|
-
{
|
|
828
|
-
stdio: "inherit",
|
|
829
|
-
cwd: process.cwd()
|
|
830
|
-
}
|
|
1315
|
+
} else {
|
|
1316
|
+
consola.warn(
|
|
1317
|
+
"No .env.local file found. The preview server may fail without environment variables.\nCreate a .env.local file in your project root with the required variables."
|
|
831
1318
|
);
|
|
832
|
-
} catch (error) {
|
|
833
|
-
consola.error(error instanceof Error ? error.message : error);
|
|
834
1319
|
}
|
|
1320
|
+
await execa2(
|
|
1321
|
+
"pnpm",
|
|
1322
|
+
[
|
|
1323
|
+
"exec",
|
|
1324
|
+
"opennextjs-cloudflare",
|
|
1325
|
+
"preview",
|
|
1326
|
+
"--config",
|
|
1327
|
+
join5(".bigcommerce", "wrangler.jsonc")
|
|
1328
|
+
],
|
|
1329
|
+
{
|
|
1330
|
+
stdio: "inherit",
|
|
1331
|
+
cwd: process.cwd()
|
|
1332
|
+
}
|
|
1333
|
+
);
|
|
835
1334
|
});
|
|
836
1335
|
|
|
837
1336
|
// src/cli/commands/telemetry.ts
|
|
838
|
-
import { Argument, Command as
|
|
839
|
-
import { colorize } from "consola/utils";
|
|
840
|
-
var telemetryService =
|
|
1337
|
+
import { Argument, Command as Command7, Option as Option7 } from "commander";
|
|
1338
|
+
import { colorize as colorize3 } from "consola/utils";
|
|
1339
|
+
var telemetryService = getTelemetry();
|
|
841
1340
|
var isEnabled = telemetryService.isEnabled();
|
|
842
|
-
var
|
|
1341
|
+
var telemetry2 = new Command7("telemetry").addArgument(new Argument("[arg]").choices(["disable", "enable", "status"])).addOption(new Option7("--enable", `Enables CLI telemetry collection.`).conflicts("disable")).option("--disable", `Disables CLI telemetry collection.`).action((arg, options) => {
|
|
843
1342
|
if (options.enable || arg === "enable") {
|
|
844
1343
|
telemetryService.setEnabled(true);
|
|
845
1344
|
isEnabled = true;
|
|
@@ -856,7 +1355,7 @@ var telemetry3 = new Command6("telemetry").addArgument(new Argument("[arg]").cho
|
|
|
856
1355
|
consola.info("Catalyst CLI Telemetry\n");
|
|
857
1356
|
}
|
|
858
1357
|
consola.info(
|
|
859
|
-
`Status: ${
|
|
1358
|
+
`Status: ${colorize3("bold", isEnabled ? colorize3("green", "Enabled") : colorize3("red", "Disabled"))}`
|
|
860
1359
|
);
|
|
861
1360
|
if (!isEnabled) {
|
|
862
1361
|
consola.info(
|
|
@@ -866,8 +1365,8 @@ var telemetry3 = new Command6("telemetry").addArgument(new Argument("[arg]").cho
|
|
|
866
1365
|
});
|
|
867
1366
|
|
|
868
1367
|
// src/cli/commands/version.ts
|
|
869
|
-
import { Command as
|
|
870
|
-
var version = new
|
|
1368
|
+
import { Command as Command8 } from "commander";
|
|
1369
|
+
var version = new Command8("version").description("Display detailed version information.").action(() => {
|
|
871
1370
|
consola.log("Version Information:");
|
|
872
1371
|
consola.log(`CLI Version: ${package_default.version}`);
|
|
873
1372
|
consola.log(`Node Version: ${process.version}`);
|
|
@@ -875,7 +1374,6 @@ var version = new Command7("version").description("Display detailed version info
|
|
|
875
1374
|
});
|
|
876
1375
|
|
|
877
1376
|
// src/cli/hooks/telemetry.ts
|
|
878
|
-
var telemetry4 = new Telemetry();
|
|
879
1377
|
var allowlistArguments = ["--keep-temp-dir", "--api-host", "--project-uuid"];
|
|
880
1378
|
function parseArguments(args) {
|
|
881
1379
|
return args.reduce((result, arg, index, array) => {
|
|
@@ -900,31 +1398,97 @@ function parseArguments(args) {
|
|
|
900
1398
|
return result;
|
|
901
1399
|
}, {});
|
|
902
1400
|
}
|
|
903
|
-
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
1401
|
+
function getCommandPath(cmd) {
|
|
1402
|
+
const parts = [];
|
|
1403
|
+
let current = cmd;
|
|
1404
|
+
while (current.parent) {
|
|
1405
|
+
parts.unshift(current.name());
|
|
1406
|
+
current = current.parent;
|
|
1407
|
+
}
|
|
1408
|
+
return parts.join(" ");
|
|
1409
|
+
}
|
|
1410
|
+
var telemetryPreHook = async (thisCommand, actionCommand) => {
|
|
1411
|
+
const telemetry3 = getTelemetry();
|
|
1412
|
+
const commandName = getCommandPath(actionCommand);
|
|
1413
|
+
telemetry3.commandName = commandName;
|
|
1414
|
+
return await telemetry3.track(commandName, {
|
|
1415
|
+
...parseArguments(thisCommand.args)
|
|
907
1416
|
});
|
|
908
1417
|
};
|
|
909
1418
|
var telemetryPostHook = async () => {
|
|
910
|
-
|
|
1419
|
+
const telemetry3 = getTelemetry();
|
|
1420
|
+
await telemetry3.track("command_completed", { durationMs: telemetry3.durationMs() });
|
|
1421
|
+
await telemetry3.analytics.closeAndFlush();
|
|
911
1422
|
};
|
|
912
1423
|
|
|
913
1424
|
// src/cli/program.ts
|
|
914
|
-
var program = new
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1425
|
+
var program = new Command9();
|
|
1426
|
+
consola.log(colorize4("cyanBright", `\u25E2 ${package_default.name} v${package_default.version}
|
|
1427
|
+
`));
|
|
1428
|
+
program.name(package_default.name).version(package_default.version).description("CLI tool for Catalyst development").addOption(
|
|
1429
|
+
new Option8(
|
|
1430
|
+
"--env-path <path>",
|
|
1431
|
+
"Path to environment file to load (relative to current working directory)"
|
|
1432
|
+
// We are using argParser, because commander loads in environment variables before executing hooks.
|
|
1433
|
+
).argParser((value) => {
|
|
1434
|
+
if (value) {
|
|
1435
|
+
const envFilePath = resolve(process.cwd(), value);
|
|
1436
|
+
const result = config({
|
|
1437
|
+
path: envFilePath,
|
|
1438
|
+
override: true
|
|
1439
|
+
});
|
|
1440
|
+
if (result.error) {
|
|
1441
|
+
const errCode = "code" in result.error && typeof result.error.code === "string" ? result.error.code : void 0;
|
|
1442
|
+
const message = errCode === "ENOENT" ? `Env file not found: ${envFilePath}` : `Failed to load --env-path ${value}: ${result.error.message}`;
|
|
1443
|
+
throw new Error(message);
|
|
1444
|
+
}
|
|
1445
|
+
consola.log(colorize4("cyanBright", `Loaded environment variables from ${envFilePath}
|
|
925
1446
|
`));
|
|
926
|
-
|
|
1447
|
+
}
|
|
1448
|
+
return value;
|
|
1449
|
+
})
|
|
1450
|
+
).addCommand(version).addCommand(start).addCommand(build).addCommand(deploy).addCommand(logs).addCommand(project).addCommand(auth).addCommand(telemetry2).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
|
|
927
1451
|
|
|
928
1452
|
// src/cli/index.ts
|
|
929
|
-
|
|
1453
|
+
var handleFatalError = async (error) => {
|
|
1454
|
+
const telemetry3 = getTelemetry();
|
|
1455
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1456
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
1457
|
+
try {
|
|
1458
|
+
await telemetry3.track("error", {
|
|
1459
|
+
commandName: telemetry3.commandName,
|
|
1460
|
+
errorMessage,
|
|
1461
|
+
errorStack,
|
|
1462
|
+
durationMs: telemetry3.durationMs()
|
|
1463
|
+
});
|
|
1464
|
+
await telemetry3.analytics.closeAndFlush();
|
|
1465
|
+
} catch {
|
|
1466
|
+
}
|
|
1467
|
+
consola.error(errorMessage);
|
|
1468
|
+
if (telemetry3.isEnabled()) {
|
|
1469
|
+
consola.info(
|
|
1470
|
+
`
|
|
1471
|
+
Trace ID: ${telemetry3.sessionId}
|
|
1472
|
+
Share this Trace ID with BigCommerce support.`
|
|
1473
|
+
);
|
|
1474
|
+
} else {
|
|
1475
|
+
consola.info(
|
|
1476
|
+
"\nEnable telemetry (`catalyst telemetry enable`) for improved troubleshooting with BigCommerce support."
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
process.exit(1);
|
|
1480
|
+
};
|
|
1481
|
+
process.on("uncaughtException", (error) => {
|
|
1482
|
+
void handleFatalError(error);
|
|
1483
|
+
});
|
|
1484
|
+
process.on("unhandledRejection", (reason) => {
|
|
1485
|
+
void handleFatalError(reason);
|
|
1486
|
+
});
|
|
1487
|
+
void (async () => {
|
|
1488
|
+
try {
|
|
1489
|
+
await program.parseAsync(process.argv);
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
await handleFatalError(error);
|
|
1492
|
+
}
|
|
1493
|
+
})();
|
|
930
1494
|
//# sourceMappingURL=cli.js.map
|