@bigcommerce/catalyst 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ import { randomBytes, randomUUID } from "crypto";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@bigcommerce/catalyst",
28
- version: "1.1.0",
28
+ version: "1.2.0",
29
29
  type: "module",
30
30
  repository: {
31
31
  type: "git",
@@ -119,6 +119,8 @@ function getUserConfig() {
119
119
  // src/cli/lib/telemetry.ts
120
120
  var TELEMETRY_KEY_ENABLED = "telemetry.enabled";
121
121
  var TELEMETRY_KEY_ID = `telemetry.anonymousId`;
122
+ var TELEMETRY_FLUSH_INTERVAL_MS = 200;
123
+ var TELEMETRY_HTTP_REQUEST_TIMEOUT_MS = 200;
122
124
  var Telemetry = class {
123
125
  constructor() {
124
126
  this.commandName = "unknown";
@@ -133,7 +135,10 @@ var Telemetry = class {
133
135
  this.correlationId = randomUUID();
134
136
  this.startTime = Date.now();
135
137
  this.analytics = new Analytics({
136
- writeKey: "OCkxLj3KTc1N3ebEbnFBqbhxfE3tVb3Z"
138
+ writeKey: "OCkxLj3KTc1N3ebEbnFBqbhxfE3tVb3Z",
139
+ flushInterval: TELEMETRY_FLUSH_INTERVAL_MS,
140
+ httpRequestTimeout: TELEMETRY_HTTP_REQUEST_TIMEOUT_MS,
141
+ maxRetries: 0
137
142
  });
138
143
  }
139
144
  durationMs() {
@@ -262,6 +267,10 @@ async function httpError(response, action) {
262
267
  var DEVICE_OAUTH_CLIENT_ID = "b8063bu6hhml4e0lqh22yut63atsbyv";
263
268
  var DEVICE_OAUTH_SCOPES = [
264
269
  "store_v2_information",
270
+ "store_v2_products",
271
+ "store_v2_content",
272
+ "store_sites",
273
+ "store_storefront_api",
265
274
  "store_infrastructure_deployments_manage",
266
275
  "store_infrastructure_logs_read_only",
267
276
  "store_infrastructure_projects_manage",
@@ -2708,7 +2717,7 @@ function collectDiagnostics({
2708
2717
  env: env2 = process.env
2709
2718
  } = {}) {
2710
2719
  const state = getProjectState(cwd);
2711
- const telemetry3 = getTelemetry();
2720
+ const telemetry2 = getTelemetry();
2712
2721
  const projectJson = readProjectJson(cwd);
2713
2722
  const storefront = readStorefrontInfo(cwd);
2714
2723
  const cliEnv = [{ source: "process.env", values: env2 }];
@@ -2760,8 +2769,8 @@ function collectDiagnostics({
2760
2769
  buildEnvVars: resolveEnvVars(buildEnv, BUILD_ENV_VARS)
2761
2770
  },
2762
2771
  telemetry: {
2763
- enabled: telemetry3.isEnabled(),
2764
- correlationId: telemetry3.correlationId
2772
+ enabled: telemetry2.isEnabled(),
2773
+ correlationId: telemetry2.correlationId
2765
2774
  },
2766
2775
  files: {
2767
2776
  ".env.local": existsSync5(join11(cwd, ".env.local")),
@@ -3143,8 +3152,8 @@ Example:
3143
3152
  const config2 = getProjectConfig();
3144
3153
  const apiHost = resolveApiHost(options, config2);
3145
3154
  const { storeHash, accessToken } = resolveCredentials(options, config2);
3146
- const telemetry3 = getTelemetry();
3147
- await telemetry3.identify(storeHash);
3155
+ const telemetry2 = getTelemetry();
3156
+ await telemetry2.identify(storeHash);
3148
3157
  const linkedProjectUuid = options.projectUuid ?? config2.get("projectUuid");
3149
3158
  let projectUuid;
3150
3159
  const promptForProject = async () => {
@@ -3297,7 +3306,22 @@ var logEntrySchema = z14.object({
3297
3306
  }).nullable().optional()
3298
3307
  });
3299
3308
  var queryLogsSchema = z14.object({
3300
- data: z14.array(logEntrySchema)
3309
+ data: z14.array(logEntrySchema),
3310
+ meta: z14.object({
3311
+ cursor_pagination: z14.object({
3312
+ // The REST proxy exposes page availability via links, while the
3313
+ // gRPC response uses has_next_page/has_prev_page. Accept both while
3314
+ // the API contract is transitioning.
3315
+ has_next_page: z14.boolean().optional(),
3316
+ has_prev_page: z14.boolean().optional(),
3317
+ start_cursor: z14.string().nullable().optional(),
3318
+ end_cursor: z14.string().nullable().optional(),
3319
+ links: z14.object({
3320
+ next: z14.string().optional(),
3321
+ previous: z14.string().optional()
3322
+ }).optional()
3323
+ }).optional()
3324
+ }).optional()
3301
3325
  });
3302
3326
  var v3ErrorSchema = z14.object({
3303
3327
  title: z14.string().optional(),
@@ -3412,6 +3436,8 @@ async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
3412
3436
  if (params.urlLike) search.set("url:like", params.urlLike);
3413
3437
  if (params.levelMin) search.set("level:min", params.levelMin);
3414
3438
  if (params.limit != null) search.set("limit", String(params.limit));
3439
+ if (params.after) search.set("after", params.after);
3440
+ if (params.before) search.set("before", params.before);
3415
3441
  const response = await fetch(
3416
3442
  `https://${apiHost}/stores/${storeHash}/v3/infrastructure/logs/${projectUuid}?${search.toString()}`,
3417
3443
  {
@@ -3430,7 +3456,9 @@ async function queryLogs(projectUuid, storeHash, accessToken, apiHost, params) {
3430
3456
  );
3431
3457
  }
3432
3458
  if (response.status === 404) {
3433
- throw new UserActionableError("Project not found. Check the project UUID.");
3459
+ throw new UserActionableError(
3460
+ "Project not found. Check the project UUID, and that --store-hash and --access-token belong to the store that owns the project."
3461
+ );
3434
3462
  }
3435
3463
  if (response.status === 400 || response.status === 422) {
3436
3464
  const body = await response.json().catch(() => null);
@@ -4079,7 +4107,6 @@ var env = new Command8("env").configureHelp({ showGlobalOptions: true }).descrip
4079
4107
  import { Command as Command9, InvalidArgumentError as InvalidArgumentError4, Option as Option8 } from "commander";
4080
4108
  import { colorize as colorize10 } from "consola/utils";
4081
4109
  import { z as z16 } from "zod";
4082
- var telemetry = new Telemetry();
4083
4110
  var DEFAULT_CONNECTION_TTL_MS = 1 * 60 * 1e3;
4084
4111
  var MAX_RETRIES = 5;
4085
4112
  var isFatalStatusCode = (status) => status >= 400 && status < 500;
@@ -4278,7 +4305,7 @@ Examples:
4278
4305
  const config2 = getProjectConfig();
4279
4306
  const apiHost = resolveApiHost(options, config2);
4280
4307
  const { storeHash, accessToken } = resolveCredentials(options, config2);
4281
- await telemetry.identify(storeHash);
4308
+ await getTelemetry().identify(storeHash);
4282
4309
  const projectUuid = resolveProjectUuid(options);
4283
4310
  await tailLogs(projectUuid, storeHash, accessToken, apiHost, options.format);
4284
4311
  } catch (error) {
@@ -4293,17 +4320,68 @@ var parseIntInRange = (flag, min, max) => (value) => {
4293
4320
  }
4294
4321
  return parsed;
4295
4322
  };
4323
+ function invocationPrefix() {
4324
+ const packageManager = process.env.npm_config_user_agent?.split("/")[0];
4325
+ switch (packageManager) {
4326
+ case "pnpm":
4327
+ case "yarn":
4328
+ return `${packageManager} catalyst`;
4329
+ case "bun":
4330
+ return "bunx catalyst";
4331
+ case "npm":
4332
+ return "npx catalyst";
4333
+ default:
4334
+ return "catalyst";
4335
+ }
4336
+ }
4337
+ function printPaginationHints(pagination, start2, end, options) {
4338
+ const hasNextPage = Boolean(pagination?.links?.next) || pagination?.has_next_page === true;
4339
+ const hasPrevPage = Boolean(pagination?.links?.previous) || pagination?.has_prev_page === true;
4340
+ const endCursor = pagination?.end_cursor;
4341
+ const startCursor = pagination?.start_cursor;
4342
+ if (!(hasNextPage && endCursor) && !(hasPrevPage && startCursor)) return;
4343
+ const quoteArg = (value) => /\s/.test(value) ? `'${value}'` : value;
4344
+ const baseFlags = [
4345
+ `--start ${quoteArg(start2)}`,
4346
+ `--end ${quoteArg(end)}`,
4347
+ ...options.method ? [`--method ${quoteArg(options.method)}`] : [],
4348
+ ...options.statusCode != null ? [`--status-code ${options.statusCode}`] : [],
4349
+ ...options.urlLike ? [`--url-like ${quoteArg(options.urlLike)}`] : [],
4350
+ ...options.levelMin ? [`--level-min ${options.levelMin}`] : [],
4351
+ ...options.limit != null ? [`--limit ${options.limit}`] : [],
4352
+ ...options.format !== "default" ? [`--format ${options.format}`] : []
4353
+ ];
4354
+ const command = `${invocationPrefix()} logs query ${baseFlags.join(" ")}`;
4355
+ if (hasNextPage && endCursor) {
4356
+ consola.info(`More results available. Next page:
4357
+ ${command} --after ${quoteArg(endCursor)}`);
4358
+ }
4359
+ if (hasPrevPage && startCursor) {
4360
+ consola.info(
4361
+ `Newer results available. Previous page:
4362
+ ${command} --before ${quoteArg(startCursor)}`
4363
+ );
4364
+ }
4365
+ }
4296
4366
  var query = new Command9("query").configureHelp({ showGlobalOptions: true }).description("Query historical logs from your deployed application.").addHelpText(
4297
4367
  "after",
4298
4368
  `
4299
4369
  Specify a time window with \`--since\` (relative to now) or \`--start\`/\`--end\`
4300
4370
  (ISO-8601 timestamps or Unix epoch seconds, UTC). The window may not exceed
4301
- 7 days. Entries print oldest-first; timestamps are UTC.
4371
+ 7 days. Entries print oldest-first; timestamps are UTC. Page toward older
4372
+ entries by passing a previous page's end cursor to \`--after\` (the CLI prints
4373
+ a ready-to-run command when more entries are available), or toward newer
4374
+ entries with \`--before\`. Cursors are only valid with the same window and
4375
+ filters they came from. With \`--format json\`, the pagination cursors are
4376
+ emitted as a final \`{"meta": ...}\` line after the entries.
4302
4377
 
4303
4378
  Examples:
4304
4379
  # Last hour of logs
4305
4380
  $ catalyst logs query --since 1h
4306
4381
 
4382
+ # Newest 20 errors in the window (follow the printed --after hint for more)
4383
+ $ catalyst logs query --since 24h --level-min error --limit 20
4384
+
4307
4385
  # Everything from today (UTC)
4308
4386
  $ catalyst logs query --start 2026-06-11T00:00:00Z
4309
4387
 
@@ -4330,7 +4408,20 @@ Examples:
4330
4408
  ).option("--url-like <substring>", "Filter by URL substring (case-sensitive).").addOption(
4331
4409
  new Option8("--level-min <level>", "Minimum log level.").choices(Array.from(LOG_LEVELS))
4332
4410
  ).addOption(
4333
- new Option8("--limit <n>", "Maximum number of entries to return (1-500).").argParser(parseIntInRange("--limit", 1, 500)).default(100)
4411
+ new Option8(
4412
+ "--limit <count>",
4413
+ "Maximum entries per page (1-500). Defaults to the API default (100)."
4414
+ ).argParser(parseIntInRange("--limit", 1, 500))
4415
+ ).addOption(
4416
+ new Option8(
4417
+ "--after <cursor>",
4418
+ "Return the page after (older than) this cursor, from a previous page's end_cursor."
4419
+ ).conflicts("before")
4420
+ ).addOption(
4421
+ new Option8(
4422
+ "--before <cursor>",
4423
+ "Return the page before (newer than) this cursor, from a previous page's start_cursor."
4424
+ )
4334
4425
  ).addOption(
4335
4426
  new Option8("--format <format>", "Output format for log entries.").choices(["json", "pretty", "default", "short", "request"]).default("default")
4336
4427
  ).action(async (options) => {
@@ -4338,7 +4429,7 @@ Examples:
4338
4429
  const config2 = getProjectConfig();
4339
4430
  const apiHost = resolveApiHost(options, config2);
4340
4431
  const { storeHash, accessToken } = resolveCredentials(options, config2);
4341
- await telemetry.identify(storeHash);
4432
+ await getTelemetry().identify(storeHash);
4342
4433
  const projectUuid = resolveProjectUuid(options);
4343
4434
  const { start: start2, end } = resolveTimeWindow(options);
4344
4435
  const result = await queryLogs(projectUuid, storeHash, accessToken, apiHost, {
@@ -4348,13 +4439,22 @@ Examples:
4348
4439
  statusCode: options.statusCode,
4349
4440
  urlLike: options.urlLike,
4350
4441
  levelMin: options.levelMin,
4351
- limit: options.limit
4442
+ limit: options.limit,
4443
+ after: options.after,
4444
+ before: options.before
4352
4445
  });
4353
4446
  const entries = result.data;
4354
4447
  const ordered = [...entries].reverse();
4355
4448
  if (options.format === "json") {
4356
4449
  ordered.forEach((entry) => process.stdout.write(`${JSON.stringify(entry)}
4357
4450
  `));
4451
+ const cursorPagination = result.meta?.cursor_pagination;
4452
+ if (cursorPagination) {
4453
+ process.stdout.write(
4454
+ `${JSON.stringify({ meta: { cursor_pagination: cursorPagination } })}
4455
+ `
4456
+ );
4457
+ }
4358
4458
  return;
4359
4459
  }
4360
4460
  if (options.format === "pretty") {
@@ -4370,11 +4470,7 @@ Examples:
4370
4470
  consola.info(
4371
4471
  `${entries.length} ${entries.length === 1 ? "entry" : "entries"} shown (oldest first, times in UTC).`
4372
4472
  );
4373
- if (entries.length === options.limit) {
4374
- consola.info(
4375
- `Limit of ${options.limit} reached; older entries may exist. Narrow the time window or raise --limit (max 500) to see more.`
4376
- );
4377
- }
4473
+ printPaginationHints(result.meta?.cursor_pagination, start2, end, options);
4378
4474
  } catch (error) {
4379
4475
  consola.error(error instanceof Error ? error.message : error);
4380
4476
  process.exit(1);
@@ -4669,7 +4765,7 @@ import { Argument, Command as Command12, Option as Option9 } from "commander";
4669
4765
  import { colorize as colorize12 } from "consola/utils";
4670
4766
  var telemetryService = getTelemetry();
4671
4767
  var isEnabled = telemetryService.isEnabled();
4672
- var telemetry2 = new Command12("telemetry").configureHelp({ showGlobalOptions: true }).description(
4768
+ var telemetry = new Command12("telemetry").configureHelp({ showGlobalOptions: true }).description(
4673
4769
  "View or change CLI telemetry collection status. Enabling telemetry helps BigCommerce support diagnose and troubleshoot errors you encounter when using the CLI."
4674
4770
  ).addHelpText(
4675
4771
  "after",
@@ -5398,17 +5494,17 @@ function getCommandPath(cmd) {
5398
5494
  return parts.join(" ");
5399
5495
  }
5400
5496
  var telemetryPreHook = async (thisCommand, actionCommand) => {
5401
- const telemetry3 = getTelemetry();
5497
+ const telemetry2 = getTelemetry();
5402
5498
  const commandName = getCommandPath(actionCommand);
5403
- telemetry3.commandName = commandName;
5404
- return await telemetry3.track(commandName, {
5499
+ telemetry2.commandName = commandName;
5500
+ return await telemetry2.track(commandName, {
5405
5501
  ...parseArguments(thisCommand.args)
5406
5502
  });
5407
5503
  };
5408
5504
  var telemetryPostHook = async () => {
5409
- const telemetry3 = getTelemetry();
5410
- await telemetry3.track("command_completed", { durationMs: telemetry3.durationMs() });
5411
- await telemetry3.analytics.closeAndFlush();
5505
+ const telemetry2 = getTelemetry();
5506
+ await telemetry2.track("command_completed", { durationMs: telemetry2.durationMs() });
5507
+ await telemetry2.analytics.closeAndFlush();
5412
5508
  };
5413
5509
 
5414
5510
  // src/cli/program.ts
@@ -5420,21 +5516,21 @@ consola.log(colorize13("cyanBright", `\u25E2 ${package_default.name} v${package_
5420
5516
  `));
5421
5517
  program.name(package_default.name).version(package_default.version).summary("CLI tool for Catalyst development").description(
5422
5518
  "CLI tool for Catalyst development.\n\nConfiguration priority: flags > process.env > .bigcommerce/project.json. `build` and `deploy` additionally load env files (--env-path, or an auto-loaded .env.local/.env) for the build.\n\nCATALYST_STORE_HASH falls back to BIGCOMMERCE_STORE_HASH if unset.\n\nRun `catalyst <command> --help` for details on a specific command."
5423
- ).configureHelp({ showGlobalOptions: true }).addCommand(version).addCommand(create2).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(project).addCommand(env).addCommand(channel).addCommand(auth).addCommand(upgrade).addCommand(telemetry2).addCommand(debug).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
5519
+ ).configureHelp({ showGlobalOptions: true }).addCommand(version).addCommand(create2).addCommand(start).addCommand(build).addCommand(deploy).addCommand(domains).addCommand(logs).addCommand(project).addCommand(env).addCommand(channel).addCommand(auth).addCommand(upgrade).addCommand(telemetry).addCommand(debug).hook("preAction", telemetryPreHook).hook("postAction", telemetryPostHook);
5424
5520
 
5425
5521
  // src/cli/index.ts
5426
5522
  var handleFatalError = async (error) => {
5427
- const telemetry3 = getTelemetry();
5523
+ const telemetry2 = getTelemetry();
5428
5524
  const errorMessage = error instanceof Error ? error.message : String(error);
5429
5525
  const errorStack = error instanceof Error ? error.stack : void 0;
5430
5526
  try {
5431
- await telemetry3.track("error", {
5432
- commandName: telemetry3.commandName,
5527
+ await telemetry2.track("error", {
5528
+ commandName: telemetry2.commandName,
5433
5529
  errorMessage,
5434
5530
  errorStack,
5435
- durationMs: telemetry3.durationMs()
5531
+ durationMs: telemetry2.durationMs()
5436
5532
  });
5437
- await telemetry3.analytics.closeAndFlush();
5533
+ await telemetry2.analytics.closeAndFlush();
5438
5534
  } catch {
5439
5535
  }
5440
5536
  if (error instanceof UserActionableError) {
@@ -5442,9 +5538,9 @@ var handleFatalError = async (error) => {
5442
5538
  process.exit(1);
5443
5539
  }
5444
5540
  consola.error(errorMessage);
5445
- if (telemetry3.isEnabled()) {
5541
+ if (telemetry2.isEnabled()) {
5446
5542
  consola.info(
5447
- `Correlation ID: ${telemetry3.correlationId}
5543
+ `Correlation ID: ${telemetry2.correlationId}
5448
5544
  Share this Correlation ID with BigCommerce support.`
5449
5545
  );
5450
5546
  } else {