Skip to content

sync: TRAC-1236 sync integrations/b2b-makeswift with integrations/makeswift@1.9.0 - #3151

Merged
chanceaclark merged 37 commits into
integrations/b2b-makeswiftfrom
sync-integrations-b2b-makeswift
Jul 30, 2026
Merged

sync: TRAC-1236 sync integrations/b2b-makeswift with integrations/makeswift@1.9.0#3151
chanceaclark merged 37 commits into
integrations/b2b-makeswiftfrom
sync-integrations-b2b-makeswift

Conversation

@chanceaclark

@chanceaclark chanceaclark commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Jira: TRAC-1236

What/Why?

Rung 8 of 9 in the makeswift catch-up climb (LTRAC-1297): 1.8.0 → 1.9.0 (36 commits). This is the original "makeswift ≥1.9.0" target from the ticket, hit here for the first time.

Big rung — includes a major create-catalystcatalyst CLI consolidation (dozens of new packages/catalyst/src/cli/lib/* files, create-catalyst's old util files deleted/absorbed), a new product-video-embed feature, and account-settings changes.

First actual conflict ever in .github/workflows/changesets-release.yml (previously either untouched or auto-merged clean). Two separate conflicts in the same file:

  • The b2b package-name elif — makeswift@1.9.0 still doesn't know about b2b, guardrail still applies, kept ours.
  • A Complete Linear release step conflict, unrelated to b2b identity: our copy omits the version param (with a comment explaining why passing it fails), theirs passes version: ${{ steps.headline.outputs.tag }}. Checked canary's current copy of this file to see which is actually correct, canary matches ours, so makeswift@1.9.0's tag has the older, since-fixed version of this shared logic. Kept ours on both.

core/vibes/soul/sections/cart/client.tsx: upstream introduced a broader isCartMutationPending state (combining the existing line-item-pending flag with a new debounced-intents tracker) used throughout the file, superseding the narrower isLineItemActionPending for UI purposes. This is a genuine upstream improvement, not a b2b thing, adopted it for the checkout button's pending state while keeping b2b's own AddCartToQuoteButton in place alongside it.

Same package.json/CHANGELOG.md silent auto-merge, caught again (6th rung in a row) — this time git actually flagged the package.json half as a real conflict (same duplicate-catalyst-block pattern as rung 7, just properly flagged this time instead of silent), reverted to b2b's 1.8.0. CHANGELOG.md again had makeswift's own entry pasted in, reverted to ours.

Full diff-check on package.json confirmed the rest is legitimate new dependencies (@base-ui/react, lite-youtube-embed, storefront-kit; dompurify just moved alphabetical position).

Confirmed the whole B2B surface (register/route.ts redirect, withB2B proxy wiring, core/b2b/*) survived this large merge intact.

Added .changeset/sync-makeswift-1-9-0.md for this rung's own bump.

Rollout/Rollback

Internal upgrade branch, no runtime/customer impact until eventually released and adopted. Rollback = revert the merge commit.

Do not squash or rebase-and-merge this PR. Use a true merge commit to preserve the merge base between integrations/makeswift and integrations/b2b-makeswift.

Testing

  • pnpm install --no-frozen-lockfile clean (Node 24).
  • pnpm --filter @bigcommerce/catalyst-b2b-makeswift typecheck — clean (0 errors).
  • pnpm --filter @bigcommerce/catalyst-b2b-makeswift lint — clean (0 errors, 0 warnings).
  • No conflict markers remain anywhere in the tree; core/package.json re-validated as valid JSON with a single catalyst key.

jorgemoya and others added 30 commits July 1, 2026 22:08
…canary) (#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 (#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 (#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 (#2881)

* feat(cli): add crash reporting with trace ids (#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 (#2887)

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

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

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

users should run next dev directly instead

* feat(cli): remove next start proxy from start command (#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 (#2895)

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

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

* fix(cli): symlink .env.local to .dev.vars before preview (#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 (#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 (#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" (#2962)

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

* chore: add changeset for auto-detect deploy secrets (#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 (#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 (#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 (#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 (#3003)

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

* build(cli): TRAC-668 bump wrangler to 4.90.0 (#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 (#2988)

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

* fix(cli): remove implicit secret detection from deploy (#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 (#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 (#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 (#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 (#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 (#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 (#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` (#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 (#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) (#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>

* 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 (#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 (#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` (#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 #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 (#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 (#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` (#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 (#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 (#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 (#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 (#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>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…#3079)

The canary Changesets release publishes via npm OIDC trusted publishing,
which enforces provenance. Publishing @bigcommerce/catalyst@1.0.0 failed
with E422 because package.json had no repository.url to match the signed
provenance statement. create-catalyst@2.0.0 published successfully and now
depends on the unpublished catalyst@1.0.0, breaking `npx create-catalyst`.

Add the repository field (matching create-catalyst and client) so the next
canary release republishes catalyst@1.0.0 with valid provenance.

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

BigCommerce revalidates required custom customer fields on every
updateCustomer call, even fields that already have a stored value.
Account Settings only ever submitted firstName/lastName/email/company,
so any store with a merchant-defined required custom customer field
(e.g. a Terms and Conditions checkbox added after accounts already
existed) locked customers out of saving any change at all.

This restores custom-field support (dropped in b14180c when the page
was migrated to Vibes) by reusing the existing DynamicForm/
formFieldTransformer pattern already used by Register and Address, so
required fields render, prefill from stored values, and get resent on
every save alongside the fixed fields.

Fixes #3074

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sync every push to canary/integrations-makeswift into Linear's "Catalyst
Release" pipeline via linear/linear-release-action, so merged issues get
attached to the currently open release as PRs land. Once changesets
actually publishes a new @bigcommerce/catalyst-core or
@bigcommerce/catalyst-makeswift version, complete that release using the
GitHub Release notes changesets already generated for the tag.

Refs TRAC-881

Co-authored-by: Claude <noreply@anthropic.com>
…nd plp (#3057)

* feat(promotions): PROMO-1507 add featured promotions callout to pdp and plp using mock data

* feat(promotions): PROMO-1507 wire GQL query for featured promotions on PDP and PLP pages

* feat(promotions): PROMO-1507 use callout component from storefront-kit on PDP and PLP pages
The "Sync Linear release" step scanned every commit on canary and
integrations/makeswift, including changes to packages/client,
packages/create-catalyst, and the CLI. Those ship as their own
independent releases, so their issues don't belong in the "Catalyst
Release" pipeline. Add include_paths so only commits touching core/**
count toward this pipeline's release tracking.

Refs TRAC-881

Co-authored-by: Claude <noreply@anthropic.com>
* TRAC-296: fix(footer) - Respect blog visibility setting in footer nav

Footer "Navigate" section listed every page returned by content.pages
without checking isVisibleInNavigation, so the Blog link always showed
even when Blog visibility was turned off in Control Panel. Fetch
isVisibleInNavigation on each page node and filter hidden pages out
before rendering nav links.

Fixes TRAC-296

* TRAC-296: chore - Add changeset for footer blog visibility fix

Refs TRAC-296
…#3084)

* feat(cli) - Add domains claim command for cross-store ownership

Add `catalyst domains claim <domain>` so users can claim ownership of a
custom domain that is already in use on another store. Publishing the
ownership-verification TXT record and then claiming releases the domain
from the other store and binds it to the current project.

The Domains API returns the TXT record in the `meta.ownership_verification`
field of a cross-store collision. Surface it as a typed
DomainOwnershipVerificationError so both `domains add` (on a 409 collision)
and `domains claim` (on a 422 unverified claim) print the record and the
next step instead of an opaque error.

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

* fix(cli) - Tidy the already-claimed domain message

When `domains add` hits a domain already in use on another store, the WARN line
echoed the V3 error's title and field message, which repeat each other. Print a
concise, purpose-written line instead and let the DNS record plus next-step
guidance carry the detail. Same tidy-up for `domains claim`'s not-yet-verified
message. Follows the existing convention of mapping a detected error condition
to CLI-authored guidance (see UnauthorizedError, the "API not enabled" 403s).

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

---------

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

A clear 4xx API response (validation error, not-found, not-enabled, conflict)
or bad command input was printed through the top-level handler's bug-report
path, appending a Correlation ID and "share this with BigCommerce support" to
an already-actionable message.

Add a reusable UserActionableError base class that the top-level handler prints
plainly (UnauthorizedError now extends it). Adopt it across the API clients for
their 4xx / input-validation cases:

- domains: 4xx responses, "API not enabled", ownership-verification errors
- project: validation errors, "not enabled", project-not-found
- logs: "not enabled", project-not-found, invalid query, bad time-window input
- channel: channel-site auth/scope failures
- auth/login: missing or unvalidatable credentials

5xx server-side failures and unexpected errors stay plain Errors and keep the
Correlation ID + support framing.

Refs LTRAC-1115

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The one-time bootstrap boundary (base_ref: @bigcommerce/catalyst-core@1.8.0)
was hardcoded permanently rather than removed after seeding a baseline, so
every push kept re-scanning the full 1.8.0..HEAD range instead of narrowing
to just new commits. That range included a squash-merged "alpha" branch
commit whose message body listed dozens of already-shipped issues, which
kept getting reattached to the in-progress release on every run.

Drop base_ref for both branches down to a single unconditional sync step.
A real baseline was seeded manually for canary before this lands, and
integrations/makeswift's baseline will be seeded manually the same way
before merging in whatever brings these workflow changes into that branch.

Refs TRAC-881

Co-authored-by: Claude <noreply@anthropic.com>
Original work by BC-AdamWard (#3044); recreated on a fresh branch rebased on
current canary.

Product videos were unsupported on the Catalyst PDP even though the Storefront
GraphQL API exposes them on Product.videos (a { title, url } pair — a YouTube
watch URL). This adds the query and renders videos in a dedicated "Videos"
section below the primary product content (mirroring the Stencil/Cornerstone
product_below_content layout), not inside the image gallery.

- page-data.ts: fetch videos(first: 25) on the PDP product query
- page.tsx: stream videos independently of gallery images; mount <ProductVideos>
  below ProductDetail (before related products / reviews)
- ProductVideos: featured lite-youtube player + thumbnail strip that swaps the
  featured video, with a Hide/Show toggle (YouTube-only, BC's supported provider)
- lite-youtube: client-only facade (poster + play button; injects the
  youtube-nocookie iframe only on click) — no heavy player on PDP view
- video-embed: getYouTubeId() defensive watch-URL parser + poster URL helper
- next.config: allow i.ytimg.com poster thumbnails through next/image
- i18n: videosTitle / hideVideos / showVideos / playVideo / viewVideo (en source)

Co-authored-by: BC-AdamWard <95659751+BC-AdamWard@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a `catalyst channel create` subcommand so developers with an existing
Catalyst project can create additional storefront channels without
scaffolding a new project.

Extract the channel-creation flow out of `create.ts` into a shared
`create-channel-flow` lib so both `catalyst create` and `catalyst channel
create` call the same prompt/eligibility code path. The new subcommand
resolves credentials like `channel link`, checks eligibility first, then
runs the shared flow. After creation it offers to link the channel by
writing its credentials to .env.local (`--link` opts in non-interactively).
All prompts are overridable via flags for non-interactive use.

Refs LTRAC-976

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

* feat(cli) - Add domains transfer command for same-store project moves

Add `catalyst domains transfer <domain>` to move a custom domain from its
current project to another project in the same store — the same-store
counterpart to `domains claim` (cross-store). The source is the linked project;
the destination is `--to-project-uuid`, or, when that's omitted, chosen
interactively from the store's other projects (the source is excluded).

Adds a `transferDomain` client (`POST .../domains/{domain}/transfer` with
`{ new_project_uuid }`), the subcommand with `--wait`, a default MSW handler,
and tests.

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

* feat(cli) - Verify domain is on source project before transfer

`domains transfer` moves a domain from the linked (source) project to a
destination. When the linked project doesn't own the domain, the Domains API
rejected the transfer with an opaque "domain is not owned by the specified
project" error — after the user had already picked a destination.

Add a `findDomain` client (a `getDomain` that returns null on a 404 instead of
throwing) and a pre-flight in the transfer command: before prompting for a
destination, confirm the domain is on the source project, otherwise throw an
actionable error pointing at `--project-uuid` and `catalyst domains list`.

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

* fix(cli) - Drop per-line log timestamps and isolate command hints

consola's fancy reporter printed a per-line wall-clock timestamp. It adds
noise for a short-lived CLI (no comparable dev CLI does it), and on long lines
consola can't right-align it, so it falls back to an inline `[time]` prefix
that breaks copy-pasteable command suggestions. Disable it with
`formatOptions: { date: false }`. The `logs` command is unaffected: its
timestamps come from the log payload, not consola's clock.

Also move the runnable command in the add/claim "already in use" error
branches onto its own unprefixed `consola.log` line so it stays clean to copy
at any terminal width.

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

* fix(cli) - Name the domain's owning project on transfer pre-flight

The transfer pre-flight told the user to run `catalyst domains list` to find
which project owns the domain, but `list` only shows the *current* project's
domains — so it could never surface a domain that lives on another project.

When the linked project doesn't own the domain, scan the store's other
projects and name the one that does, with the exact `--project-uuid` command
to re-run. If no project in the store owns it, say so and point at
`catalyst domains claim` for the cross-store case.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): validate channel name and clarify invalid-name error

The Catalyst channels API rejects channel names containing unsupported
characters (e.g. an apostrophe in "Bob's Store") with an opaque server
error, leaving users to guess what went wrong. Validate the name in the
shared channel-creation flow used by both `catalyst create` and
`catalyst channel create`: the interactive prompt validates as you type,
and an invalid `--name` flag throws a UserActionableError before the API
call. Either way the message names the offending input and lists the
allowed characters (letters, numbers, spaces, hyphens, underscores).

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

* style(cli): fix prettier formatting in channel-name validation

Apply eslint --fix (prettier) to create-channel-flow.ts and its spec to
satisfy the CI lint step (--max-warnings 0).

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): require --env-path instead of auto-loading .env.local

The CLI auto-loaded .env.local from cwd but not .env, an inconsistent bit of
magic that surprised users. Drop automatic env-file loading entirely: env
files are now loaded only when passed explicitly via --env-path. This removes
the .env vs .env.local asymmetry and makes env resolution explicit and
predictable. Help text is updated to match.

BREAKING: users who relied on auto-loaded .env.local must now pass
--env-path .env.local (or the appropriate path).

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

* docs(cli): unwrap require-env-path changeset paragraphs

The changeset body was hard-wrapped mid-paragraph, which rendered as
pre-split lines in the changelog. Put each paragraph on a single line to
match the repo's changeset style.

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

* docs(cli): drop redundant no-auto-load note from program description

The top-level description restated that env files aren't auto-loaded, which
is already covered by the --env-path option help. Keep the (now accurate)
configuration-priority line and let --env-path document the behavior.

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

* refactor(cli): scope env-file loading to build and deploy

Instead of removing env-file auto-loading globally, restrict it to the only
commands that need storefront env vars: `build` and `deploy`. Both now
auto-load `.env.local` then `.env` from the current directory (`.env.local`
wins; neither overrides the real environment), or an explicit `--env-path`.
The global `--env-path` option is removed so no other command is affected by
on-disk env files. The env is loaded before the build runs so the build child
process (and any pre-build checks) can see the variables.

Adds a shared `loadBuildEnv` helper and moves `--env-path` to a per-command
option via `envPathOption`.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(other): LOCAL-1444 delivery translation

* chore(core): create translations patch

---------

Co-authored-by: bc-svc-local <bc-svc-local@users.noreply.github.com>
* fix(cli): make auth login device-code UX consistent with create

The device-code login flow behaved differently across commands: `auth login`
opened the browser immediately and asked the user to copy the code by hand,
while `create` waited for Enter first. Users expected the code on their
clipboard and got confused.

Consolidate the flow in `lib/login.ts` so `auth login`, `create`, and the
channel commands all share one path: print the one-time code, wait for the
user to press Enter before opening the browser, and best-effort copy the code
to the clipboard (still printing it as a fallback). A new `lib/clipboard.ts`
helper shells out to the platform-native utility (pbcopy/clip/wl-copy/xclip)
and degrades gracefully — a copy failure never interrupts login. Non-TTY/CI
runs skip the Enter prompt and clipboard copy and open directly.

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

* fix(cli): fix login.spec type error for CI typecheck

The `input` prompt returns a CancelablePromise, so mocking it with an
implementation that returns a plain Promise failed `tsc` in CI. Assert the
"browser opens only after Enter" ordering via invocationCallOrder with a
mockResolvedValueOnce instead, which is type-safe and satisfies the repo's
no-type-assertions lint rule.

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

---------

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

* fix(cli): give a clear error on deploy when required env vars are missing

`catalyst deploy` (and the Commerce Hosting `catalyst build` path) surfaced a
raw OpenNext/Next.js build stack trace when required env vars weren't loaded —
the values are read during prerender, so a missing/empty `BIGCOMMERCE_STORE_HASH`,
`BIGCOMMERCE_STOREFRONT_TOKEN`, or `AUTH_SECRET` failed deep in the build with no
hint about the cause or the fix.

Pre-check these vars in `buildCatalystProject` before shelling out to the build.
When any are missing we throw a `UserActionableError` (printed plainly, without
the Correlation-ID bug-report framing) that names each missing variable and
suggests loading a `.env.local` via `--env-path`, so the ugly build error never
happens.

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

* fix(cli): cover channel ID and next build in required-env check

BIGCOMMERCE_CHANNEL_ID is passed to createClient() with no fallback, so it
hard-throws ("Client configuration must include a channelId") before the other
storefront vars even matter — add it to the required set. Also run the check on
the plain `next build` fallthrough (non-transformed projects), which previously
skipped it and surfaced the raw build error.

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

* fix(cli): reword missing-env message to reflect .env.local auto-load

The previous message suggested `--env-path .env.local`, but the build already
auto-loads `.env.local` from the current directory — so that advice was
redundant/misleading. Explain that the build auto-loads `.env.local` from the
current directory (named explicitly) and reserve `--env-path` for loading a
file from a different location.

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

* fix(cli): mention .env alongside .env.local in missing-env message

The missing-required-env message now references both `.env.local` and `.env`
(with `.env.local` taking precedence), matching the build/deploy env-loading
behavior. This aligns with #3098; no need to wait for merge order.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Infrastructure Projects API returns 404 (not just 403) when the beta
feature flag is off for a store, which previously fell through to a cryptic
"Not Found" error. Treat 404 the same as 403 and surface the actionable
"contact support to join the beta" guidance via UserActionableError.

Refs LTRAC-1089

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

The CLI is a build-time tool that only backs the generated build/start/
deploy npm scripts and is never imported at runtime, so setupCoreProject
now writes it into devDependencies instead of dependencies.

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

Most CLI API calls threw a raw `... failed: ${status} ${statusText}`, giving
users no idea what went wrong or what to do. Add a shared HTTP-error helper
that turns a non-OK Response into a readable error: prefer the API's own
structured error body (title/detail), falling back to a status-class message.
4xx failures throw a UserActionableError (dropping the "share your Correlation
ID with support" framing); 5xx keep it as transient/server-side. Migrate the
raw throws across the channels, project, observability, localization, deploy,
logs, and auth paths. Existing UnauthorizedError (401) and deployment-code
handling are left intact.

Refs LTRAC-977

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server actions run strictly serially, so rapid clicks on cart quantity
+/- or delete buttons queued unboundedly, and navigating away while the
queue drained could freeze the page until a hard refresh. Quantity
clicks now coalesce into a single absolute-quantity update per line
item via a debounced, single-flight dispatcher; deletes are serialized
the same way. Each control is still a real progressive-enhancement
form (posting straight to the server action) so it keeps working
before hydration or if the app bundle fails to load; JS intercepts the
submit to route through the dispatcher instead.

The cart section is now keyed by cart entityId only (previously
entityId-version), since remounting on every mutation could swallow
clicks landing during the DOM swap and drop queued actions, leaving
the UI stuck pending until a hard refresh. Cart revalidation is
unified on revalidateTag (previously a mix of that and revalidatePath).

Also fixes the checkout button spinning forever if its navigation is
cancelled (Esc, "Stay" on the leave-page prompt, a flaky connection):
the URL-string checkout action awaited a promise that never resolved,
so a cancelled redirect left retry clicks queued behind a dead action.
It now settles after a short timeout or on bfcache restore.

Fixes TRAC-284

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

* LTRAC-955: feat - respect control panel default search sorting

* LTRAC-955: feat - respect control panel default search for category page
….local (#3100)

* fix(cli): follow .env.example ordering and comments when writing .env.local

writeEnv previously emitted a flat, unordered list of KEY=VALUE pairs,
ignoring core/.env.example — the file that documents every variable with
comments, ordering, and sensible defaults. Generated .env.local files were
therefore inconsistent and undocumented, and channel link/create clobbered
any values the user had already set.

writeEnv now reads the project's own .env.example as the source of truth for
shape. It reproduces the documented ordering and per-key comment blocks,
substitutes CLI-supplied values in place, and leaves documented-but-unsupplied
keys as their example line (blank or default) so users still see what's
expected. CLI-only variables (e.g. access tokens not in .env.example) are
appended in a clearly separated trailing section.

Existing .env.local files are reconciled rather than overwritten: user-set
values are preserved unless the CLI explicitly supplies a new one, and newly
documented keys are added in their canonical position without disturbing the
ordering of unknown keys. Falls back to a flat merge when no .env.example is
present.

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

* fix(cli): use writeEnv for channel link and keep existing values on empty

`catalyst channel link` inlined its own .env.local dump instead of going
through writeEnv, so it skipped the .env.example ordering/comments and
overwrote the file rather than reconciling it. Route it through writeEnv
like `channel create --link`.

Also stop empty CLI-supplied values from clobbering existing on-disk
values: the channel init API returns blank placeholders for keys it does
not own (e.g. MAKESWIFT_SITE_API_KEY), which were wiping the user's real
value on re-run. An empty supplied value now falls back to the existing
.env.local value.

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

---------

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

The webpage sidebar navigation (child page links) always fetched
anonymously with a revalidate-based cache, regardless of customer
session, so customer-only child pages wouldn't reliably show up when
logged in and could otherwise be shared across visitors. Thread the
customer access token through getWebPageChildren and switch to an
uncached fetch when it's present, matching the pattern already used
by the header, footer, and webpage page-data queries.

Also bring with-routes.ts's getRoute and getRawWebPageContent in line
with that same pattern for consistency, since they accepted a
customer access token but never varied their fetch cache behavior on
it.

Refs LTRAC-274

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…oy (#3105)

* LTRAC-1086: feat(cli) - Add --wrangler-version flag to build and deploy

catalyst build pins the Wrangler version it shells out to, so developers
had no way to build against a different version without editing CLI source.

Add a --wrangler-version <version> option to both `catalyst build` and
`catalyst deploy` that overrides the pinned default when provided and falls
back to the pinned version when omitted. The value is validated as a
version or dist-tag before it's interpolated into the wrangler@<version>
spec so it can't smuggle extra args or shell metacharacters.

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

* LTRAC-1086: fix(cli) - Validate --wrangler-version with the semver package

Switch parseWranglerVersion to validate proper semver strings with
semver.valid() instead of a hand-rolled regex, per review feedback.
A narrow character allowlist is kept only for npm dist-tags (e.g.
latest, beta), which aren't valid semver but still need the same
injection-safety guarantee before being interpolated into the
wrangler@<version> spec.

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

---------

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

* fix(cli): prevent dependency install from hanging on link/deploy setup

The install step in `catalyst project link` and the `catalyst deploy`
first-run setup could hang forever on "Installing dependencies...". nypm
was called with `silent: true`, which pipes the child's stdio; since nypm
routes pnpm/yarn through corepack, corepack's package-manager download
confirmation prompt blocked on stdin that never arrived — an invisible,
unanswerable deadlock.

Inherit stdio instead so corepack/pnpm prompts and progress are visible
and answerable. Also detect the project's actual package manager from its
lockfile in the link/deploy setup paths instead of always defaulting to
pnpm, so npm/yarn/bun projects aren't forced through corepack pnpm.

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

* fix(cli): silence child stderr flood that deadlocked dependency install

The real cause of the install hang was not corepack's prompt: nypm runs the
install via tinyexec, which in silent mode fully drains the child's stdout
before it reads stderr. pnpm sends progress to stdout but floods stderr with
Node warnings (on Node 26, thousands of "File descriptor N opened in unmanaged
mode" lines). That stderr fills its ~64KB pipe buffer while tinyexec is still
on stdout, pnpm blocks writing, and the install deadlocks. Inheriting stdio
avoided the deadlock but surfaced the warning spam in the terminal.

Keep the silent spinner UX and instead starve the flood: run the install child
with NODE_NO_WARNINGS=1 (drops the internal Node warnings) and pin
COREPACK_ENABLE_DOWNLOAD_PROMPT=0 defensively. Both are saved and restored so
they don't leak into later CLI operations.

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

* fix(cli): default project package manager fallback to npm

Detection of an existing project's package manager fell back to pnpm
when a lockfile or packageManager field couldn't be resolved. Most
users have npm installed by default, so default there instead — pnpm
requires an extra install step most contributors won't have.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
…e --api-host from project.json (#3111)

* feat(cli): rename BIGCOMMERCE_API_HOST to CATALYST_API_HOST, resolve from project.json

Make the API host a first-class CATALYST_ config, resolved the same way
as the other credentials: --api-host flag > CATALYST_API_HOST env >
.bigcommerce/project.json apiHost > default (api.bigcommerce.com).

- shared-options: rename the env binding, drop the Commander default, add
  a resolveApiHost(options, config) helper and DEFAULT_API_HOST.
- project-config: add apiHost to ProjectConfigSchema so it can be
  persisted/read from .bigcommerce/project.json.
- Route every command that takes --api-host (project, logs, domains,
  channel, deploy, auth) through resolveApiHost instead of reading
  options.apiHost directly.

BREAKING CHANGE: BIGCOMMERCE_API_HOST is no longer read; set
CATALYST_API_HOST instead.

Refs LTRAC-1249

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

* ref(cli): use shared credential options in auth command

auth was the only command hand-rolling --store-hash, --access-token, and
--api-host inline instead of using the storeHashOption/accessTokenOption/
apiHostOption helpers. Switch to the shared helpers so there's a single
source of truth (this is what nearly caused the CATALYST_API_HOST rename
to miss auth). No behavior change; the --store-hash/--access-token help
text now matches the other commands.

Refs LTRAC-1249

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jorgemoya and others added 7 commits July 16, 2026 18:07
… reports (#3110)

* feat(cli): add debug command to gather diagnostics for bug reports

Add `catalyst debug`, which prints a diagnostic report developers can
attach to a bug report: CLI version, runtime, detected package manager,
project/config state, telemetry correlation ID, and which key files
exist. Supports `--json` for machine-readable output.

Secrets and PII are never printed. Credentials, stored env vars, and
environment variables are reported by presence only, never by value; the
collector reads project.json raw (no Conf side effects) and reports
config sources following the process.env > project.json priority chain.

Refs LTRAC-1087

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

* fix(cli): detect project package manager and env files in debug

Two fixes to `catalyst debug`:

- Report the project's package manager from its lockfile
  (pnpm-lock.yaml / yarn.lock / bun.lock(b) / package-lock.json),
  falling back to the invoking manager. Previously it always reported
  the manager that launched the CLI, which is usually npm/npx.
- Resolve env vars and credentials across the same chain a build uses
  (process.env > .env.local > .env > project.json), reporting the source
  each resolved from. Previously `debug` only read process.env, so vars
  set in .env.local showed as unset. Env files are parsed for key
  presence only and are never loaded into process.env; values are still
  never printed.

Refs LTRAC-1087

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

* feat(cli): include Catalyst core version in debug report

Add the storefront package name + version (e.g.
@bigcommerce/catalyst-core@1.8.0) to the `catalyst debug` report,
resolved from the project's package.json the same way `upgrade` does
(catalyst.version, falling back to version). This tells us which core a
bug report is against, not just the CLI version.

Refs LTRAC-1087

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

* feat(cli): group debug env vars by CLI vs Next.js app build

Split the diagnostic report's environment variables into two sections:
CLI vars the commands read (CATALYST_*, plus the --api-host/--login-url
bindings) and the storefront build vars the Next.js app reads
(BIGCOMMERCE_STORE_HASH, BIGCOMMERCE_STOREFRONT_TOKEN,
BIGCOMMERCE_CHANNEL_ID, AUTH_SECRET, mirroring REQUIRED_BUILD_ENV_VARS).
Makes it clear which layer a missing var affects.

Refs LTRAC-1087

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

* feat(cli): reflect CATALYST_API_HOST rename in debug report

Now that --api-host reads CATALYST_API_HOST and resolves from
project.json (LTRAC-1249), update the debug report to match:

- Rename BIGCOMMERCE_API_HOST -> CATALYST_API_HOST in the CLI env-var group.
- Add a resolved 'API host' line to the config section, resolved across
  the same chain (CATALYST_API_HOST env > project.json apiHost); 'not set'
  renders as 'default (api.bigcommerce.com)' since the host always has a
  fallback.

Refs LTRAC-1087

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

* feat(cli): relabel debug 'Catalyst core' as 'Storefront'

The line reports the project's storefront package, which may be any
Catalyst family (catalyst-core, catalyst-makeswift, catalyst-b2b, …), so
'Catalyst core' was inaccurate. Relabel to 'Storefront' and rename the
diagnostics fields storefrontName/storefrontVersion to match.

Refs LTRAC-1087

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

* fix(cli): walk up to find the lockfile for debug package-manager detection

Package-manager detection only checked the current directory, so running
`catalyst debug` from a subdirectory (e.g. core/ in the monorepo, where
pnpm-lock.yaml lives at the repo root) missed the lockfile and fell back
to the invoking manager — reporting npm for a pnpm project. Walk up from
cwd to the nearest lockfile instead.

Refs LTRAC-1087

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

* fix(cli): don't report .env files as a source for CLI vars in debug

The CLI reads its own config (CATALYST_* and the credentials) only from
process.env and .bigcommerce/project.json — never from .env files (only
build/deploy load those, for the storefront BUILD vars). The debug report
was resolving every group across the .env.local/.env layers, so it could
claim e.g. 'CATALYST_STORE_HASH: set (.env.local)' for a value the CLI
would never actually read from there.

Resolve CLI vars and credentials from process.env (+ project.json) only;
keep the .env.local/.env layers for the build-env group, which is the
only thing genuinely loaded from those files.

Refs LTRAC-1087

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Version Packages (`canary`)

* chore: trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jorge Moya <jorge.moya@bigcommerce.com>
…akeswift

# Conflicts:
#	.changeset/config.json
#	core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts
#	core/app/[locale]/(default)/page.tsx
#	core/app/[locale]/(default)/product/[slug]/page.tsx
#	core/package.json
#	pnpm-lock.yaml
The makeswift useSwitchLocale returns an async callback, so the merged
locale switcher left a floating promise. Await it, matching the intent
of syncing the cart locale before switching.

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

The canary sync brought the @bigcommerce/create-catalyst package (which
depends on the ignored @bigcommerce/catalyst) onto integrations/makeswift.
Changesets requires any package depending on an ignored package to also be
ignored, so changeset:version failed validation. Add create-catalyst to the
ignore list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…ons-b2b-makeswift

@bigcommerce/catalyst-makeswift@1.9.0

# Conflicts:
#	.github/workflows/changesets-release.yml
#	core/package.json
#	core/vibes/soul/sections/cart/client.tsx
@chanceaclark
chanceaclark requested a review from a team as a code owner July 30, 2026 16:33
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 62ac611

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

This PR includes changesets to release 1 package
Name Type
@bigcommerce/catalyst-b2b-makeswift Minor

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

@vercel

vercel Bot commented Jul 30, 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 Jul 30, 2026 4:33pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Report

Comparing against baseline from 7a0cdc1 (2026-07-30).

Metric Baseline Current Delta
First Load JS 128.8 kB 129.6 kB +0.8 kB (+0.6%)
Total JS 448.2 kB 745.6 kB +297.4 kB (+66.4%) ⚠️

Per-Route First Load JS

Route Baseline Current Delta
/(default)/(auth)/change-password/page 332.5 kB 626 kB +293.5 kB (+88.3%) ⚠️
/(default)/(auth)/login/forgot-password/page 331.6 kB 625.1 kB +293.5 kB (+88.5%) ⚠️
/(default)/(auth)/login/page 332 kB 625.7 kB +293.7 kB (+88.5%) ⚠️
/(default)/(auth)/register/page 368.4 kB -- REMOVED
/(default)/(faceted)/brand/[slug]/page 344.1 kB 631.9 kB +287.8 kB (+83.6%) ⚠️
/(default)/(faceted)/category/[slug]/page 352.6 kB 640.2 kB +287.6 kB (+81.6%) ⚠️
/(default)/(faceted)/search/page 344.1 kB 631.9 kB +287.8 kB (+83.6%) ⚠️
/(default)/[...rest]/page 327.1 kB 627.1 kB +300 kB (+91.7%) ⚠️
/(default)/account/addresses/page 371.8 kB 629.6 kB +257.8 kB (+69.3%) ⚠️
/(default)/account/orders/[id]/page 335.2 kB 624.7 kB +289.5 kB (+86.4%) ⚠️
/(default)/account/orders/page 336.2 kB 625.6 kB +289.4 kB (+86.1%) ⚠️
/(default)/account/settings/page 373.3 kB 630.8 kB +257.5 kB (+69%) ⚠️
/(default)/account/wishlists/[id]/page 350 kB 634.3 kB +284.3 kB (+81.2%) ⚠️
/(default)/account/wishlists/page 345.1 kB 629 kB +283.9 kB (+82.3%) ⚠️
/(default)/blog/[blogId]/page 327.1 kB 627.1 kB +300 kB (+91.7%) ⚠️
/(default)/blog/page 328.1 kB 627.8 kB +299.7 kB (+91.3%) ⚠️
/(default)/cart/page 348.9 kB 634.7 kB +285.8 kB (+81.9%) ⚠️
/(default)/compare/page 339.3 kB 628.3 kB +289 kB (+85.2%) ⚠️
/(default)/gift-certificates/balance/page 331.1 kB 628.9 kB +297.8 kB (+89.9%) ⚠️
/(default)/gift-certificates/page 327.1 kB 627.1 kB +300 kB (+91.7%) ⚠️
/(default)/gift-certificates/purchase/page 370.9 kB 628.8 kB +257.9 kB (+69.5%) ⚠️
/(default)/page 344.3 kB 627.1 kB +282.8 kB (+82.1%) ⚠️
/(default)/product/[slug]/page 400.2 kB 641.7 kB +241.5 kB (+60.3%) ⚠️
/(default)/webpages/[id]/contact/page 369.3 kB 630.3 kB +261 kB (+70.7%) ⚠️
/(default)/webpages/[id]/normal/page 335.2 kB 627.6 kB +292.4 kB (+87.2%) ⚠️
/(default)/wishlist/[token]/page 339.9 kB 631 kB +291.1 kB (+85.6%) ⚠️
/maintenance/page 316.4 kB 623.6 kB +307.2 kB (+97.1%) ⚠️
/_global-error/page 142.8 kB 143.8 kB +1 kB (+0.7%)
/_not-found/page 142.8 kB 575.8 kB +433 kB (+303.2%) ⚠️

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

@chanceaclark
chanceaclark merged commit 62ac611 into integrations/b2b-makeswift Jul 30, 2026
16 of 18 checks passed
@chanceaclark
chanceaclark deleted the sync-integrations-b2b-makeswift branch July 30, 2026 16:49
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.

7 participants