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