Skip to content

fix(cli): symlink .env.local to .dev.vars before preview#2944

Merged
matthewvolk merged 1 commit into
alphafrom
CATALYST-1826-start-cmd-dev-vars
Mar 24, 2026
Merged

fix(cli): symlink .env.local to .dev.vars before preview#2944
matthewvolk merged 1 commit into
alphafrom
CATALYST-1826-start-cmd-dev-vars

Conversation

@matthewvolk

Copy link
Copy Markdown
Contributor

Summary

  • Copy .env.local to .bigcommerce/.dev.vars before running opennextjs-cloudflare preview in the start command
  • Wrangler reads secrets from .dev.vars, not .env.local, and silently ignores symlinks
  • Warns (instead of erroring) if .env.local is missing, to avoid breaking CI flows

Test plan

  • Existing tests updated (copyFileSync mock added)
  • All 38 tests pass (pnpm test)
  • Manual: catalyst build && catalyst start with .env.local present — verify preview has env vars
  • Manual: catalyst start without .env.local — verify warning is logged

@matthewvolk
matthewvolk requested a review from a team as a code owner March 20, 2026 20:09
@vercel

vercel Bot commented Mar 20, 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 Mar 20, 2026 9:52pm

Request Review

@changeset-bot

changeset-bot Bot commented Mar 20, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 42a860f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

Comment on lines +47 to +49
consola.start('Copying .env.local to .bigcommerce/.dev.vars...');
copyFileSync(envLocal, devVars);
consola.success('.dev.vars created');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought wrangler/opennext can now read from .env.local?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that was my assumption but the only reason I opened this PR is because it wasn't working. If you're able to test it locally and it works for you, I've got no problem closing this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think CF is trying to move to .env.local instead of .dev.vars.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is legit. I just tested locally. Here's what happened.

running from core/

With core/.env.local, no env vars detected:

$ node ../packages/catalyst/dist/cli.js start
◢ @bigcommerce/catalyst v1.0.0-alpha.0                                                                                          4:14:46 PM


┌───────────────────────────────┐
│ OpenNext — Cloudflare preview │
└───────────────────────────────┘

Monorepo detected at /Users/matt.volk/code/work/catalyst-deploy

Populating R2 incremental cache...
Successfully populated cache with 25 assets
Tag cache does not need populating

 ⛅️ wrangler 4.76.0
───────────────────
Your Worker has access to the following bindings:
Binding                                                                           Resource            Mode
env.NEXT_CACHE_DO_QUEUE (DOQueueHandler)                                          Durable Object      local
env.NEXT_TAG_CACHE_DO_SHARDED (DOShardedTagCache)                                 Durable Object      local
env.NEXT_CACHE_DO_PURGE (BucketCachePurge)                                        Durable Object      local
env.NEXT_INC_CACHE_R2_BUCKET (project-accd0361-2470-11f1-875e-1e6bd38089e2)       R2 Bucket           local
env.WORKER_SELF_REFERENCE (project-accd0361-2470-11f1-875e-1e6bd38089e2)          Worker              local [connected]
env.ASSETS                                                                        Assets              local

Since the file must be relative to the wrangler config file, first copy .env.local to core/.bigcommerce/.env.local

$ cp .env.local .bigcommerce/.env.local
$ node ../packages/catalyst/dist/cli.js start
◢ @bigcommerce/catalyst v1.0.0-alpha.0                                                                                          4:14:46 PM


┌───────────────────────────────┐
│ OpenNext — Cloudflare preview │
└───────────────────────────────┘

Monorepo detected at /Users/matt.volk/code/work/catalyst-deploy

Populating R2 incremental cache...
Successfully populated cache with 25 assets
Tag cache does not need populating

 ⛅️ wrangler 4.76.0
───────────────────
Your Worker has access to the following bindings:
Binding                                                                           Resource            Mode
env.NEXT_CACHE_DO_QUEUE (DOQueueHandler)                                          Durable Object      local
env.NEXT_TAG_CACHE_DO_SHARDED (DOShardedTagCache)                                 Durable Object      local
env.NEXT_CACHE_DO_PURGE (BucketCachePurge)                                        Durable Object      local
env.NEXT_INC_CACHE_R2_BUCKET (project-accd0361-2470-11f1-875e-1e6bd38089e2)       R2 Bucket           local
env.WORKER_SELF_REFERENCE (project-accd0361-2470-11f1-875e-1e6bd38089e2)          Worker              local [connected]
env.ASSETS                                                                        Assets              local

Still doesn't work

Finally, rename to .dev.vars:

$ cp .env.local .bigcommerce/.dev.vars
$ node ../packages/catalyst/dist/cli.js start
◢ @bigcommerce/catalyst v1.0.0-alpha.0                                                                                          4:15:19 PM


┌───────────────────────────────┐
│ OpenNext — Cloudflare preview │
└───────────────────────────────┘

Monorepo detected at /Users/matt.volk/code/work/catalyst-deploy

Populating R2 incremental cache...
Successfully populated cache with 25 assets
Tag cache does not need populating

 ⛅️ wrangler 4.76.0
───────────────────
Using secrets defined in .bigcommerce/.dev.vars
Your Worker has access to the following bindings:
Binding                                                                              Resource                  Mode
env.NEXT_CACHE_DO_QUEUE (DOQueueHandler)                                             Durable Object            local
env.NEXT_TAG_CACHE_DO_SHARDED (DOShardedTagCache)                                    Durable Object            local
env.NEXT_CACHE_DO_PURGE (BucketCachePurge)                                           Durable Object            local
env.NEXT_INC_CACHE_R2_BUCKET (project-accd0361-2470-11f1-875e-1e6bd38089e2)          R2 Bucket                 local
env.WORKER_SELF_REFERENCE (project-accd0361-2470-11f1-875e-1e6bd38089e2)             Worker                    local [connected]
env.ASSETS                                                                           Assets                    local
env.BIGCOMMERCE_STORE_HASH ("(hidden)")                                              Environment Variable      local
env.BIGCOMMERCE_CHANNEL_ID ("(hidden)")                                              Environment Variable      local
env.BIGCOMMERCE_STOREFRONT_TOKEN ("(hidden)")                                        Environment Variable      local
env.MAKESWIFT_SITE_API_KEY ("(hidden)")                                              Environment Variable      local
env.AUTH_SECRET ("(hidden)")                                                         Environment Variable      local
env.CLIENT_LOGGER ("(hidden)")                                                       Environment Variable      local
env.KV_LOGGER ("(hidden)")                                                           Environment Variable      local
env.DEFAULT_REVALIDATE_TARGET ("(hidden)")                                           Environment Variable      local
env.AUTH_TRUST_HOST ("(hidden)")                                                     Environment Variable      local
env.TRAILING_SLASH ("(hidden)")                                                      Environment Variable      local
env.ENABLE_ADMIN_ROUTE ("(hidden)")                                                  Environment Variable      local
env.B2B_API_TOKEN ("(hidden)")                                                       Environment Variable      local
env.B2B_API_HOST ("(hidden)")                                                        Environment Variable      local
env.CONTENTFUL_SPACE_ID ("(hidden)")                                                 Environment Variable      local
env.CONTENTFUL_ACCESS_TOKEN ("(hidden)")                                             Environment Variable      local
env.DATABASE_URL ("(hidden)")                                                        Environment Variable      local
env.DATABASE_AUTH_TOKEN ("(hidden)")                                                 Environment Variable      local
env.AUTH_RESEND_KEY ("(hidden)")                                                     Environment Variable      local

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked offline, agreed to symlink core/.env.local to core/.bigcommerce/.dev.vars if core/.bigcommerce/.dev.vars does not already exist.

Seperately, asked Cloudflare if there are there plans to support .env.local across all entry points (preview, dev, deploy, etc.). Currently .env.local only works during next dev via initOpenNextCloudflareForDev(), but opennextjs-cloudflare preview goes through wrangler which only reads .dev.vars. The inconsistency is a bit of a footgun — easy to assume .env.local works everywhere when it only works in dev.

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Report

Comparing against baseline from c965f4d (2026-03-20).

No bundle size changes detected.

@matthewvolk
matthewvolk force-pushed the CATALYST-1826-start-cmd-dev-vars branch from e69a84e to 375f476 Compare March 20, 2026 21:42
@matthewvolk matthewvolk changed the title fix(cli): copy .env.local to .dev.vars before preview fix(cli): symlink .env.local to .dev.vars before preview Mar 20, 2026
Wrangler reads secrets from .dev.vars, not .env.local. Create a symlink
from .bigcommerce/.dev.vars to .env.local before running
opennextjs-cloudflare preview so the local server has access to
environment variables.
@matthewvolk
matthewvolk merged commit 199c58a into alpha Mar 24, 2026
14 of 16 checks passed
@matthewvolk
matthewvolk deleted the CATALYST-1826-start-cmd-dev-vars branch March 24, 2026 00:35
matthewvolk added a commit that referenced this pull request Mar 25, 2026
Wrangler reads secrets from .dev.vars, not .env.local. Create a symlink
from .bigcommerce/.dev.vars to .env.local before running
opennextjs-cloudflare preview so the local server has access to
environment variables.
chanceaclark pushed a commit that referenced this pull request Mar 31, 2026
Wrangler reads secrets from .dev.vars, not .env.local. Create a symlink
from .bigcommerce/.dev.vars to .env.local before running
opennextjs-cloudflare preview so the local server has access to
environment variables.
chanceaclark pushed a commit that referenced this pull request Apr 27, 2026
Wrangler reads secrets from .dev.vars, not .env.local. Create a symlink
from .bigcommerce/.dev.vars to .env.local before running
opennextjs-cloudflare preview so the local server has access to
environment variables.
chanceaclark pushed a commit that referenced this pull request May 12, 2026
Wrangler reads secrets from .dev.vars, not .env.local. Create a symlink
from .bigcommerce/.dev.vars to .env.local before running
opennextjs-cloudflare preview so the local server has access to
environment variables.
jorgemoya pushed a commit that referenced this pull request Jun 5, 2026
Wrangler reads secrets from .dev.vars, not .env.local. Create a symlink
from .bigcommerce/.dev.vars to .env.local before running
opennextjs-cloudflare preview so the local server has access to
environment variables.
jorgemoya pushed a commit that referenced this pull request Jun 22, 2026
Wrangler reads secrets from .dev.vars, not .env.local. Create a symlink
from .bigcommerce/.dev.vars to .env.local before running
opennextjs-cloudflare preview so the local server has access to
environment variables.
jorgemoya pushed a commit that referenced this pull request Jul 1, 2026
Wrangler reads secrets from .dev.vars, not .env.local. Create a symlink
from .bigcommerce/.dev.vars to .env.local before running
opennextjs-cloudflare preview so the local server has access to
environment variables.
BC-AdamWard pushed a commit to BC-AdamWard/catalyst that referenced this pull request Jul 17, 2026
…canary) (bigcommerce#3077)

* chore: add initial changeset for @bigcommerce/catalyst

* chore: add pre version setup files

* fix: optimize catalyst deployments

* rename variables to be prefixed with CATALYST_

* feat(cli): run build automatically when deploying

Deploy now runs the Catalyst build pipeline before bundling, so users
no longer need to manually run `catalyst build && catalyst deploy`.

* feat(cli): add --prebuilt flag to deploy command (bigcommerce#2874)

skip build step when --prebuilt is passed; fail with actionable error
when .bigcommerce/dist/ is missing or empty.

* fix(cli): change --secret from variadic to repeatable

* feat(cli): map ignition error codes to actionable messages (bigcommerce#2879)

* feat(cli): map ignition error codes to actionable messages

* write and read access token and store hash from project json file

* fix linting

* fix linting

* added changeset

* updated deploy tests to use vitest stubbing for environment variables

* check valid options first in deploy command

* remove default loading of environment variable files

* added changeset

* add .bigcommerce directory to core .gitignore file

* add changeset

* add comment and new line in gitignore

* remove changeset

* feat(cli): add whoami command (bigcommerce#2881)

* feat(cli): add crash reporting with trace ids (bigcommerce#2880)

* 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

* feat(cli): add auth login and auth logout commands (bigcommerce#2887)

OAuth device code flow for browser-based authentication.
Stores credentials in .bigcommerce/project.json.

* feat(cli): centralize credential resolution with resolveCredentials utility (bigcommerce#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.

* feat(cli): remove --root-dir flag from project create and link commands (bigcommerce#2892)

* feat(cli): remove dev command (bigcommerce#2893)

users should run next dev directly instead

* feat(cli): remove next start proxy from start command (bigcommerce#2894)

start command now only runs opennextjs-cloudflare preview.
vanilla next.js users should run next start directly.

* add --env-file flag allowing user to pass path to environment variable file to load

* rebase on alpha

* added changeset

* rename variable to fix linting issue

* fix linting

* remove changeset

* throw error on failed load of env file

* add arg parsing for env path

* fix linting

* add tests for --env-path flag

* fix linting

* feat(cli): remove framework config and simplify build command (bigcommerce#2895)

* fix: show readable error when project creation errors with 422 (bigcommerce#2938)

* CATALYST-1718: feat(cli) - Add catalyst logs command (bigcommerce#2921)

* fix(cli): symlink .env.local to .dev.vars before preview (bigcommerce#2944)

Wrangler reads secrets from .dev.vars, not .env.local. Create a symlink
from .bigcommerce/.dev.vars to .env.local before running
opennextjs-cloudflare preview so the local server has access to
environment variables.

* fix(cli): pin @opennextjs/cloudflare peer dependency to 1.17.3 (bigcommerce#2949)

* chore: consolidate changesets for alpha release

Remove individual changesets and update the umbrella major changeset
with full CLI release notes covering all commands and features.

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

* chore: version @bigcommerce/catalyst@1.0.0-alpha.1

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

* chore: version @bigcommerce/catalyst@1.0.0-alpha.2

Rebuild dist to fix env var resolution (CATALYST_* instead of
BIGCOMMERCE_*) and remove stale makeOptionMandatory on deploy.

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

* fix(cli): clean up deployment url output (bigcommerce#2960)

* fix(cli): clean up deployment url output

Remove redundant console.log, trailing \n on spinner message, and
prepend https:// scheme so the url is clickable in terminal emulators.

* fix(cli): fix lint warnings and update test expectations

* refactor(cli): rename "trace id" to "correlation id" (bigcommerce#2962)

* feat(cli): auto-detect environment variables as deploy secrets (bigcommerce#2965)

* chore: add changeset for auto-detect deploy secrets (bigcommerce#2972)

* chore: version @bigcommerce/catalyst@1.0.0-alpha.3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): add Commander.js built-in help text to all commands (bigcommerce#2984)

Add .description(), .addHelpText(), and .summary() to all CLI commands
so that `catalyst <command> --help` becomes the canonical reference.
Hide internal-only options (--api-host, --login-url) from help output.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update catalyst cli with new client id

* feat(cli): LTRAC-612 show global options in subcommand help (bigcommerce#2992)

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>

* LTRAC-444: fix(cli) - Read store hash and access token from project.json (bigcommerce#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>

* TRAC-137: Add native hosting option in catalyst CLI (bigcommerce#3003)

* TRAC-614: Add project delete command to catalyst CLI (bigcommerce#3004)

* build(cli): TRAC-668 bump wrangler to 4.90.0 (bigcommerce#3010)

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>

* chore: version @bigcommerce/catalyst@1.0.0-alpha.4

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>

* feat(cli): show deployment hostnames in project list output (bigcommerce#2988)

* feat(cli): add `channel update` command to update channel site url (bigcommerce#3001)

* fix(cli): remove implicit secret detection from deploy (bigcommerce#3025)

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>

* feat(cli): make catalyst auth login and project create onboarding interactive (bigcommerce#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>

* LTRAC-440: fix(cli) - Autoreconnect log stream when API proxy stalls (bigcommerce#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>

* fix(cli): clean dist directory before each build (bigcommerce#3030)

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>

* LTRAC-440: ref(cli) - Extract pumpUntilRotation from tailLogs (bigcommerce#3029)

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>

* fix(cli): strip @vercel/otel hook in Commerce Hosting setup (bigcommerce#3028)

* 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>

* LTRAC-808: fix(cli) - Suppress OpenNext IMAGES binding warning in logs tail (bigcommerce#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>

* LTRAC-442: feat(cli) - Persist deployment env vars via `catalyst env` (bigcommerce#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>

* LTRAC-441: fix(cli) - Surface clear re-auth error on expired token (bigcommerce#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>

* LTRAC-808: ref(cli) - Remove IMAGES warning suppression (moved to tail-worker) (bigcommerce#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 (bigcommerce#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>

* chore: version @bigcommerce/catalyst@1.0.0-alpha.5

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

* LTRAC-873: fix(cli) - Derive Wrangler compatibility_date dynamically (bigcommerce#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>

* LTRAC-908: feat(cli) - Add `catalyst channel link` command (bigcommerce#3051)

* LTRAC-908: feat(cli) - Add `catalyst channel connect` command

Add a `connect` subcommand under `catalyst channel` that connects an existing
local checkout to a BigCommerce channel and writes its credentials to
.env.local — useful when a teammate clones a Catalyst repo (where .env.local
is gitignored) and needs to regenerate it from the channel.

Resolves credentials from flags/env, the persisted project config, or an
interactive device-code login (persisting the result), then selects a channel
(or takes --channel-id), fetches its channel-init data, and writes .env.local
in the cwd. Supports repeatable --env KEY=VALUE overrides.

Supersedes the dropped plan to port create-catalyst's standalone `init`: the
catalyst CLI is native-hosting only and `catalyst create` already connects a
channel for new projects, so the only residual need — bootstrapping .env.local
for an existing checkout — lives under `channel`.

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

* LTRAC-908: ref(cli) - Clean up `channel connect` success output

Drop the per-line info-icon clutter on the next-steps hint: print a single
success line (channel + .env.local) followed by a spaced, plain block with the
`pnpm run dev` command.

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

* LTRAC-908: ref(cli) - Rename `channel connect` to `channel link`

Align with `catalyst project link` — the established CLI verb for linking a local
checkout to a remote BigCommerce resource (infrastructure project vs storefront
channel). The success line now reads "Linked to channel …".

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

* LTRAC-908: ref(cli) - Extract shared channel sort/label helpers

`channel link` had copied `catalyst create`'s channel-platform sort comparator
and the Stencil/title-case label. Move both into channels.ts as
`sortChannelsByPlatform` (returns a sorted copy) and `channelPlatformLabel`,
and use them from both pickers. No behavior change.

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

---------

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

* LTRAC-911: ref(cli) - Standardize interactive prompts on `@inquirer/prompts` (bigcommerce#3054)

* LTRAC-911: ref(cli) - Standardize interactive prompts on @inquirer/prompts

Replace all `consola.prompt` usages with `@inquirer/prompts` (confirm/input/select/
checkbox) so the CLI uses one prompt library; consola is retained for logging
only. Converts create's locale multiselect to an inquirer `checkbox` with a
`validate` (drops the consola cast + recursion hack), and migrates the prompts in
login, channel link/update, project, deploy, channel-site-flow, and
commerce-hosting. Specs updated to mock `@inquirer/prompts`.

Chosen over consola because consola.prompt has no masked input (login's access
token uses `password({ mask: true })`), plus inquirer's validate/theming.

Stacked on bigcommerce#3051 (channel link).

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

* LTRAC-911: style(cli) - Standardize spacing on prompts and command output

Stray blank lines came from leading/trailing newlines and `consola.log('')`
calls, which the fancy reporter timestamps as their own log line. Collapse
multi-line "Next steps" output into a single `consola.log` call (one
timestamp, predictable spacing) and drop the manual blank-line separators
after prompts. `project list` now joins its project blocks into one log call
so each blank separator isn't timestamped.

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

---------

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

* chore: version @bigcommerce/catalyst@1.0.0-alpha.6

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

* LTRAC-468: feat(cli) - Replace git clone with tarball extraction in create (bigcommerce#3061)

Merchants now receive a clean standalone project instead of the full
monorepo. `catalyst create` downloads the core/ subdirectory as a tarball
from GitHub's codeload endpoint and stream-extracts it to the project root,
resolves workspace: dependencies to their published npm versions, detects
the invoking package manager (npm/pnpm/yarn/bun) from npm_config_user_agent,
and initializes a fresh git repository.

Previously create cloned the entire bigcommerce/catalyst monorepo (full
history, packages/*, turbo) and built the workspace packages locally. The
core/ contents are now flattened to the project root, so deploy/project/
commerce-hosting no longer assume a core/ subdirectory.

Refs LTRAC-468

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat: TRAC-924 upgrade: utility helpers and per-file merge engine

Adds the foundational layer for `catalyst upgrade`: small fs utilities, ref parsing, base-version similarity scoring, and the per-file 3-way merge engine (`git merge-file`).

Refs TRAC-924
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: TRAC-925 upgrade: whole-tree merge engine and index staging

Adds the whole-tree merge engine (`git merge-tree --write-tree`) as the default strategy, plus `applyIndexState` which registers conflicts as real unmerged index entries so editors surface the merge UI.

Refs TRAC-925
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: TRAC-926 upgrade: CLI command, project resolution, download, and tests

Completes the `catalyst upgrade` command: project layout detection, tarball download/cache, base ref resolution, the full CLI action, and the integration and action-level test suite.

Refs TRAC-926, TRAC-927
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* TRAC-499: feat - implement query logs in catalyst cli (bigcommerce#3068)

* TRAC-499: feat - implement query logs in catalyst cli

* TRAC-499: test - implement tests for query logs

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

* LTRAC-910: ref(cli) - Reduce create-catalyst to a thin wrapper over `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>

* LTRAC-910: chore - Add changeset for create-catalyst thin wrapper

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

---------

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

* LTRAC-988: Add custom domain command (bigcommerce#3070)

* LTRAC-988: feat(cli) - Add custom domain command

Add a domains command group with a create-domain API client and wait support for pending domain verification.

Refs LTRAC-988

* LTRAC-988: fix(cli) - Clarify domain API server errors

Include the HTTP status and correlation ID when domain API requests fail with a server-side response so 502 Bad Gateway errors are actionable.

Refs LTRAC-988

* LTRAC-988: fix(cli) - Avoid duplicate correlation ID

Keep domain API server error messages concise because the CLI fatal error handler already prints the correlation ID for support.

Refs LTRAC-988

* LTRAC-989: feat(cli) - List custom domains (bigcommerce#3071)

Add domain listing support with optional domain and verification-status filters so users can scan configured Native Hosting domains from the CLI.

Refs LTRAC-989

* LTRAC-990: feat(cli) - Show custom domain status (bigcommerce#3072)

Add a domains status command that fetches one domain, renders its color-coded state, and can wait for pending verification to finish.

Refs LTRAC-990

* LTRAC-991: feat(cli) - Remove custom domain (bigcommerce#3073)

Add a domains remove command that checks current status, confirms active domain removal, and deletes the domain through the Native Hosting API.

Refs LTRAC-991

* chore(cli) - Consolidate CLI changesets into single 1.0.0 release note

Collapse the 11 individual @bigcommerce/catalyst changesets accumulated
across the alpha prerelease series into one comprehensive major changeset
so the stable 1.0.0 changelog reads as a single release announcement.
Refresh the command surface to include create, channel, env, domains, and
upgrade. No change to the resulting version bump (still major -> 1.0.0).

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

* chore(cli) - Exit changeset pre mode for stable release

Run `changeset pre exit` so the Version Packages step produces stable
versions (@bigcommerce/catalyst 1.0.0, @bigcommerce/create-catalyst 2.0.0)
instead of continuing the 1.0.0-alpha.* prerelease series.

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

* fix(cli) - Update program-status copy from alpha to beta

The Infrastructure API 403 error messages told users to reach out 'if you
are part of the alpha'. The Native Hosting program is now in beta, so
update the copy across project, domains, and logs error paths (and the
matching test assertions).

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

* chore(cli) - Collapse subcommands to top-level groups in 1.0.0 changelog

Use `catalyst project`, `catalyst auth`, `catalyst logs`, and
`catalyst channel` in the release-note command table instead of listing
each subcommand row, for a more concise changelog.

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

---------

Co-authored-by: Chancellor Clark <chancellor.clark@bigcommerce.com>
Co-authored-by: jamesqquick <james.q.quick@gmail.com>
Co-authored-by: Matthew Volk <matt.volk@bigcommerce.com>
Co-authored-by: Jordan Arldt <jordan.arldt@bigcommerce.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Parth Shah <48393781+parthshahp@users.noreply.github.com>
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.

3 participants