Skip to content

LTRAC-910: ref(cli) - Reduce create-catalyst to a thin wrapper over catalyst create#3053

Draft
jorgemoya wants to merge 75 commits into
alphafrom
jorgemoya/ltrac-910-reduce-create-catalyst-to-a-thin-wrapper-that-delegates-to
Draft

LTRAC-910: ref(cli) - Reduce create-catalyst to a thin wrapper over catalyst create#3053
jorgemoya wants to merge 75 commits into
alphafrom
jorgemoya/ltrac-910-reduce-create-catalyst-to-a-thin-wrapper-that-delegates-to

Conversation

@jorgemoya

Copy link
Copy Markdown
Contributor

Linear: LTRAC-910
Part of LTRAC-138 — consolidating create-catalyst into the catalyst CLI (Phase 2).

What/Why?

create-catalyst shipped its own full copy of the scaffolding flow (create/init/integration/telemetry + a large utils//prompts/). Now that @bigcommerce/catalyst owns create, this reduces create-catalyst to a thin wrapper that delegates to it — single source of truth, mirroring how create-next-app fronts next.

Delegation (option B — spawn): the entry resolves the @bigcommerce/catalyst bin from its own dependency tree (createRequire → the package's bin) and spawns catalyst create, forwarding all args with inherited stdio and propagating the child's exit code / signals. Because @bigcommerce/catalyst is a regular dependency, pnpm create catalyst / npx create-catalyst install it alongside the wrapper — no global catalyst needed.

  • Added @bigcommerce/catalyst (workspace:^) as a dependency; dropped ~20 runtime deps the old in-package commands required.
  • Deleted the duplicated commands/, utils/, prompts/, hooks/ (kept node-version.spec.ts, moved to src/).
  • Bumped the tsup target to node24 so esbuild preserves import.meta.url (a lower target shimmed it to {}, which broke createRequire).

Scope note (changed from the original ticket)

The ticket assumed init + integration were ported into @bigcommerce/catalyst. They weren't: integration was dropped (LTRAC-909 canceled) and init's useful behavior became catalyst channel link (LTRAC-908). So this wrapper is scaffold-onlycreate-catalyst's init/integration/telemetry subcommands are dropped. In the consolidated CLI: initcatalyst channel link, integration → dropped, telemetrycatalyst telemetry.

Testing

pnpm build, pnpm typecheck, pnpm lint, pnpm test all pass in packages/create-catalyst (the node-version gating test is retained).

End-to-end: built both packages and ran node packages/create-catalyst/dist/index.js create --help → it shows @bigcommerce/catalyst create's banner + help, confirming delegation. Exit codes/signals are mirrored from the child.

Release / rollout

create-catalyst and @bigcommerce/catalyst must now be published in lockstep — the workspace:^ is rewritten to a concrete version at publish, so the dependency range must include the published catalyst version. Changesets should bump both together.

Migration

pnpm create catalyst / npx create-catalyst UX is unchanged for scaffolding. Anyone who used create-catalyst init/integration/telemetry directly should switch to the catalyst … equivalents noted above.

chanceaclark and others added 30 commits June 5, 2026 10:11
Deploy now runs the Catalyst build pipeline before bundling, so users
no longer need to manually run `catalyst build && catalyst deploy`.
skip build step when --prebuilt is passed; fail with actionable error
when .bigcommerce/dist/ is missing or empty.
* feat(cli): map ignition error codes to actionable messages
* feat(cli): add crash reporting with trace ids

Centralized error handling via withErrorHandler HOF, singleton telemetry
with UUID trace ids, X-Correlation-Id headers on all API calls, and
global uncaught exception handlers. On any CLI error, a trace ID is
displayed for support debugging.

* refactor(cli): remove traceId() and trackError() abstractions

* fix(cli): update deploy tests to match error handler string output

The error handler extracts the message string from Error objects before
passing to consola.error, so tests should expect strings, not Error objects.

* refactor(cli): move error handling from per-command HOF to top-level try/catch

Replace the withErrorHandler() HOF pattern with a single try/catch
around program.parseAsync() in the entry point. This makes error
handling automatic for all commands instead of requiring each command
author to remember to wrap their action.

- Add commandName property to Telemetry, set by preAction hook via
  Commander's actionCommand parameter
- Delete error-handler.ts and its spec
- Update tests to assert error propagation via rejects.toThrow()

* fix(cli): address PR feedback on error handling

- Remove redundant closeAndFlush() from try block (postAction hook
  already handles it)
- Only show trace ID when telemetry is enabled; prompt disabled users
  to enable telemetry without showing an unlookable trace ID

* fix(cli): wrap entry point in IIFE for module compatibility
OAuth device code flow for browser-based authentication.
Stores credentials in .bigcommerce/project.json.
…tility (#2888)

Add resolveCredentials() that resolves storeHash/accessToken from flags,
env vars, or stored config, with unified error message mentioning
`catalyst auth login`. Apply to project create/list/link and deploy.
users should run next dev directly instead
start command now only runs opennextjs-cloudflare preview.
vanilla next.js users should run next start directly.
chanceaclark and others added 22 commits June 5, 2026 10:12
Apply `.configureHelp({ showGlobalOptions: true })` to every Command and
subcommand so `catalyst <command> --help` surfaces the root-level
`--env-path` option (and other globals) under a "Global Options"
section. Previously these were only visible on `catalyst --help`.

Refs LTRAC-612

Co-authored-by: Claude <noreply@anthropic.com>
…son (#2997)

* LTRAC-444: fix(cli) - Read store hash and access token from project.json

`catalyst logs tail` and `catalyst logs query` now resolve credentials via
`resolveCredentials`, falling back to `.bigcommerce/project.json` when
`--store-hash` / `--access-token` flags or `CATALYST_STORE_HASH` /
`CATALYST_ACCESS_TOKEN` env vars are not provided. This matches the
behavior already used by `project`, `deploy`, and `build`, and removes
the need to re-pass credentials after running `catalyst project link`.

Also migrates the shared `--store-hash` / `--access-token` option helpers
from the legacy `BIGCOMMERCE_*` env var names to `CATALYST_*` for
consistency with the rest of the CLI.

Fixes LTRAC-444
Co-Authored-By: Claude <noreply@anthropic.com>

* LTRAC-444: fix(cli) - Migrate projectUuidOption env var to CATALYST_PROJECT_UUID

The shared `projectUuidOption()` helper was still using the legacy
`BIGCOMMERCE_PROJECT_UUID` env var name. It was missed in the earlier
`CATALYST_*` rename sweep because `logs` was the only consumer at the
time. The inline declarations in `build.ts` and `deploy.ts` already use
`CATALYST_PROJECT_UUID`, so this aligns the helper with the rest of the
CLI and the alpha.2 patch notes.

Refs LTRAC-444
Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
wrangler@4.24.3 produces a broken bundle for Next.js 16.2.x: the
app-page-turbo.runtime.prod.js webpack runtime fails to initialize at
request time with "Cannot read properties of undefined (reading
'require')". wrangler@4.90.0 bundles it correctly.

Refs TRAC-668

Co-authored-by: Claude <noreply@anthropic.com>
Move @commander-js/extra-typings from devDependencies to dependencies.
It is externalized in the tsup build and must be installed at runtime;
previously missing from the published package caused CLI startup to fail
with ERR_MODULE_NOT_FOUND.

Co-Authored-By: Claude <noreply@anthropic.com>
The deploy command used to silently scan process.env for an allowlist of
keys (BIGCOMMERCE_STORE_HASH, BIGCOMMERCE_CHANNEL_ID,
BIGCOMMERCE_STOREFRONT_TOKEN, BIGCOMMERCE_API_HOST,
BIGCOMMERCE_GRAPHQL_API_DOMAIN, AUTH_SECRET) and ship any matches as
deployment secrets. A developer with .env.local populated for local dev
could push those dev values to production without noticing.

Require explicit --secret KEY=VALUE declarations instead, matching the
explicit-only model other deploy CLIs (Vercel, Netlify, Wrangler) follow.

Co-authored-by: Claude <noreply@anthropic.com>
…eractive (#3026)

* LTRAC-613: feat(cli) - Make auth login and project create onboarding interactive

First-time users hit `failed to request device code: 404 Not Found` when
running `catalyst auth login` before `catalyst project create`, with no
obvious way to recover. Make the login flow self-sufficient by adding a
manual-credentials fallback: if the device-code endpoint fails, the CLI
warns with the underlying reason, asks whether to fall back to entering
a store hash + access token directly, and validates the manual creds
against the store profile API before persisting them.

`catalyst project create` no longer fails fast when credentials are
missing — it kicks off the same interactive login flow inline, so users
don't need to know the ordering. `catalyst create` (scaffolding) picks
up the manual fallback automatically since it shares the orchestrator.

Refs LTRAC-613
Co-Authored-By: Claude <noreply@anthropic.com>

* LTRAC-613: chore(cli) - Drop changeset for interactive auth onboarding

Refs LTRAC-613
Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…3027)

When the API proxy half-closes the socket — bytes stop arriving but no
FIN or error is surfaced — `reader.read()` blocks forever, so the
1-minute connection TTL check below it never runs and the stream goes
silent until the user restarts `catalyst logs tail`.

Race `reader.read()` against the remaining TTL so the TTL acts as an
absolute deadline. When it fires we cancel the reader, break out of the
inner loop, and the outer reconnect loop opens a fresh stream. Warn the
user only when no data arrived during the window — healthy rotations
stay silent to match existing behavior.

Refs LTRAC-440

Co-authored-by: Claude <noreply@anthropic.com>
Wrangler's `--outdir` writes the deployment bundle alongside whatever is
already in `.bigcommerce/dist` rather than replacing the directory. When
Wrangler is upgraded between builds — or any prior artifact lingers —
old files end up in the zip uploaded to the server.

In one observed case the dist contained both the current Wrangler 4.x
plain wasm names and `?module`-suffixed copies from an older Wrangler
version. The `?` in the filenames confused the server-side multipart
upload to Cloudflare and the deploy failed with:

    Uncaught Error: No such module "<hash>-resvg.wasm".
      imported from "worker.js"

Wipe the dist directory at the start of `buildCatalystProject` so each
build starts from a clean slate.

Refs LTRAC-814

Co-authored-by: Claude <noreply@anthropic.com>
Split the SSE reader's connection lifecycle from its read pump. The
outer loop in `tailLogs` now owns opening streams, retrying transient
errors, and emitting user-facing reconnect messages. The inner loop
becomes a standalone `pumpUntilRotation` that reads bytes from a single
connection and returns a `Rotation` value ('ttl' | 'idle-timeout' |
'stream-done') describing why it stopped, instead of using break +
side-effecting warnings.

No behavior change — every rotation reason and error path maps to the
same user-facing output and retry semantics as before, and the existing
test suite passes unmodified.

Refs LTRAC-440

Co-authored-by: Claude <noreply@anthropic.com>
* LTRAC-807: fix(cli) - Strip @vercel/otel hook in Commerce Hosting setup

`core/instrumentation.ts` registers `@vercel/otel`. OpenNext bundles the
storefront as a Node-style worker, so the bundler resolves `@vercel/otel`
through its `node` export condition and pulls `@opentelemetry/sdk-node`
into the worker chunk. At cold start, workerd evaluates Node-only side
effects that `nodejs_compat` doesn't cover and throws — Next.js's
instrumentation loader surfaces this as "Failed to prepare server" on
every cold start in `catalyst logs tail`.

No code in `/core` actually consumes the tracer, so we drop the file (and
the `@vercel/otel` dependency) as part of `setupCommerceHosting`, next to
the existing `convertProxyToMiddleware` step. The change is scoped to the
Commerce Hosting opt-in path; self-hosted / Vercel deploys keep the file.

Fixes LTRAC-807
Co-Authored-By: Claude <noreply@anthropic.com>

* LTRAC-807: fix(cli) - Run OTel cleanup on already-transformed projects

The first patch only removed `core/instrumentation.ts` and `@vercel/otel`
as part of `setupCommerceHosting`, which is gated by `!isTransformed` in
both `offerCommerceHostingSetup` and `deploy`. Existing Commerce Hosting
users — those already transformed — never hit that code path on re-link
or re-deploy, so they kept seeing the cold-start error.

Extract the cleanup into `cleanupCloudflareIncompatibilities` and call it
from the transformed branches of both flows (and from `setupCommerceHosting`
itself, for fresh users). Idempotent and silent unless it actually removed
something — in which case it logs a one-line `consola.info` so the user
sees what changed.

Refs LTRAC-807
Co-Authored-By: Claude <noreply@anthropic.com>

* LTRAC-807: fix(cli) - Prompt before removing instrumentation hook

Vercel users who customized `core/instrumentation.ts` would have their work
silently wiped when running `catalyst project link` or `catalyst deploy`,
because the cleanup helper unconditionally deleted the file and dropped
`@vercel/otel`. Make the cleanup interactive instead: in a TTY, prompt
before doing anything; in non-TTY (CI), warn and skip so headless deploys
preserve customizations. The dep removal is tied to the file decision —
if the file stays, the dep stays (the customized file probably imports it).

`setupCommerceHosting` is now async (it awaits the cleanup). All callers
(`offerCommerceHostingSetup`, `deploy`, `create`, `runCommerceHostingSetup`)
are updated to await it.

Refs LTRAC-807
Co-Authored-By: Claude <noreply@anthropic.com>

* LTRAC-807: style(cli) - Add blank line after instrumentation prompt

The "Removed core/instrumentation.ts ..." (or "Leaving ...") info log
landed directly under the user's Yes/No answer with no spacing, making
the prompt and the result visually run together. Insert a `consola.log('')`
right after the prompt resolves so the answer and the result are
separated by a blank line.

Refs LTRAC-807
Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…s tail (#3038)

* LTRAC-808: fix(cli) - Suppress OpenNext IMAGES binding warning in logs tail

OpenNext's Cloudflare image handler logs `env.IMAGES binding is not
defined` on every `/_next/image` request when no IMAGES binding is
configured, then falls back to serving the original bytes. Native
Hosting intentionally runs without that binding, so the warning is
expected noise that flooded testers watching `catalyst logs tail`.

Filter the warning out of the human-readable log formats and drop
events that contain nothing but suppressed noise. Raw `--format json`
output is left untouched so piped consumers still see the full stream.

Fixes LTRAC-808
Co-Authored-By: Claude <noreply@anthropic.com>

* LTRAC-808: chore(cli) - Drop changeset (not needed on alpha branch)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…#3039)

Passing `--secret KEY=VALUE` for every env var on every `catalyst deploy`
is cumbersome and error-prone, and got worse after implicit secret
detection was removed (LTRAC-640): nothing reaches a deployment unless
every secret is re-typed.

Add a `catalyst env` command group (`add`, `remove`, `list`) that stores
deployment secrets in `.bigcommerce/project.json` (gitignored, alongside
the already-persisted access token). `catalyst deploy` now sends these
automatically, merging them with any inline `--secret` flags — inline
flags win on conflict so a stored value can still be overridden per-run.

Stored vars are deploy-only (not injected into the local build) and are
always sent as `secret`. Values are masked in all command output. A
shared `parseEnvAssignment` splits on the first `=`, so values containing
`=` (e.g. base64/tokens) survive intact — a fix over deploy's prior
`split('=')`.

Refs LTRAC-442

Co-authored-by: Claude <noreply@anthropic.com>
…3040)

Expired device-code access tokens caused `project list`, `deploy`,
`logs tail`, and `auth whoami` to fail with a generic "Unauthorized"
message, leaving users unsure they needed to re-authenticate. The CLI
stores no expiry/refresh metadata, so silent renewal isn't possible.

Add a shared `SessionExpiredError` plus `assertAuthorized(response)` and
call it at every authenticated fetch site so a 401 consistently directs
the user to run `catalyst auth login`. 403 (e.g. "Infrastructure
Projects API not enabled") is intentionally left unchanged since it is
overloaded for scope/feature-flag errors.

Refs LTRAC-441

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l-worker) (#3041)

The OpenNext `env.IMAGES binding is not defined` warning is a platform
fact, not a per-client preference: Commerce Hosting intentionally runs
without that binding, so the warning is always noise for every consumer.
Filtering it in the CLI (#3038) only helped users on a new enough CLI and
did nothing for the copies forwarded into Sentry.

The suppression now lives in the ignition-tail-worker, where it applies to
all CLI versions and consumers at once and is also dropped from Sentry.
Remove the now-redundant CLI-side filter and its tests so there is a single
source of truth.

Must merge AFTER the tail-worker change is deployed; otherwise newer-CLI
users briefly see the warning again until the worker filter is live.

Refs LTRAC-808

Co-authored-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…3045)

The build pinned compatibility_date to 2025-09-15 while the deployment
service stamps a current date at deploy time, so workers ran under newer
Cloudflare runtime semantics than they were built against. Compute the
date as current UTC date minus one month, matching the same offset the
deployment service applies, so build and deploy semantics stay aligned.

Refs LTRAC-873

Co-authored-by: Claude <noreply@anthropic.com>
…catalyst create`

create-catalyst now delegates to `@bigcommerce/catalyst`: its entry resolves the
catalyst bin from its own dependency tree and spawns `catalyst create`,
forwarding all args (stdio inherited, exit code and signals propagated). All
scaffolding logic now lives in @bigcommerce/catalyst.

- Add @bigcommerce/catalyst as a workspace dependency; drop the ~20 runtime deps
  the old in-package commands needed.
- Delete the duplicated commands/, utils/, prompts/, and hooks/.
- Bump the tsup target to node24 so esbuild preserves `import.meta.url`.

Package name and `bin` are unchanged, so `pnpm create catalyst` / `npx
create-catalyst` keep working. The dropped init/integration/telemetry
subcommands are superseded by `catalyst channel link`, (integration dropped),
and `catalyst telemetry`.

Refs LTRAC-910
Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
catalyst Ready Ready Preview, Comment Jun 17, 2026 2:10pm

Request Review

@changeset-bot

changeset-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c7e8ce0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@bigcommerce/create-catalyst Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Refs LTRAC-910
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Report

Comparing against baseline from 1ab2c82 (2026-06-17).

Metric Baseline Current Delta
Total JS 435.4 kB 434.5 kB -0.9 kB (-0.2%)

Per-Route First Load JS

Route Baseline Current Delta
/(default)/(auth)/change-password/page 318.8 kB 317.5 kB -1.3 kB (-0.4%)
/(default)/(auth)/login/forgot-password/page 318 kB 316.7 kB -1.3 kB (-0.4%)
/(default)/(auth)/login/page 318.4 kB 317.2 kB -1.2 kB (-0.4%)
/(default)/(auth)/register/page 354.6 kB 353.3 kB -1.3 kB (-0.4%)
/(default)/(faceted)/brand/[slug]/page 330.5 kB 329.2 kB -1.3 kB (-0.4%)
/(default)/(faceted)/category/[slug]/page 339 kB 337.6 kB -1.4 kB (-0.4%)
/(default)/(faceted)/search/page 330.5 kB 329.2 kB -1.3 kB (-0.4%)
/(default)/[...rest]/page 313.5 kB 312.3 kB -1.2 kB (-0.4%)
/(default)/account/addresses/page 358 kB 356.7 kB -1.3 kB (-0.4%)
/(default)/account/orders/[id]/page 321.6 kB 320.4 kB -1.2 kB (-0.4%)
/(default)/account/orders/page 322.6 kB 321.3 kB -1.3 kB (-0.4%)
/(default)/account/settings/page 329.2 kB 327.9 kB -1.3 kB (-0.4%)
/(default)/account/wishlists/[id]/page 336.4 kB 335.3 kB -1.1 kB (-0.3%)
/(default)/account/wishlists/page 331.5 kB 330.3 kB -1.2 kB (-0.4%)
/(default)/blog/[blogId]/page 313.5 kB 312.3 kB -1.2 kB (-0.4%)
/(default)/blog/page 314.5 kB 313.3 kB -1.2 kB (-0.4%)
/(default)/cart/page 334.3 kB 333.1 kB -1.2 kB (-0.4%)
/(default)/compare/page 325.7 kB 324.4 kB -1.3 kB (-0.4%)
/(default)/gift-certificates/balance/page 317.5 kB 316.2 kB -1.3 kB (-0.4%)
/(default)/gift-certificates/page 313.5 kB 312.3 kB -1.2 kB (-0.4%)
/(default)/gift-certificates/purchase/page 357.1 kB 355.8 kB -1.3 kB (-0.4%)
/(default)/page 330.7 kB 329.4 kB -1.3 kB (-0.4%)
/(default)/product/[slug]/page 385.4 kB 384.2 kB -1.2 kB (-0.3%)
/(default)/webpages/[id]/contact/page 355.5 kB 354.2 kB -1.3 kB (-0.4%)
/(default)/webpages/[id]/normal/page 321.6 kB 320.4 kB -1.2 kB (-0.4%)
/(default)/wishlist/[token]/page 326.3 kB 325.2 kB -1.1 kB (-0.3%)
/maintenance/page 307.1 kB 306.4 kB -0.7 kB (-0.2%)

Threshold: 5% increase. Routes with ⚠️ exceed the threshold.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Unlighthouse Performance Comparison — Vercel

Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.

Summary Score

Aggregate score across all categories as reported by Unlighthouse.

Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Score 90 92 92 95

Category Scores

Category Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Performance 71 85 79 81
Accessibility 95 92 95 92
Best Practices 100 100 100 100
SEO 88 100 100 100

Core Web Vitals

Metric Prod Desktop Prod Mobile Preview Desktop Preview Mobile
LCP 5.1 s 4.3 s 3.0 s 5.0 s
CLS 0.039 0 0.04 0
FCP 1.2 s 1.1 s 1.2 s 1.1 s
TBT 0 ms 0 ms 10 ms 10 ms
Max Potential FID 40 ms 40 ms 60 ms 70 ms
Time to Interactive 5.1 s 4.4 s 4.5 s 5.1 s

Full Unlighthouse report →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants