Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sync-makeswift-1-6-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-b2b-makeswift": minor
---

Pulls in changes from the `@bigcommerce/catalyst-makeswift@1.6.0` release. For more information, see the [changelog entry](https://github.com/bigcommerce/catalyst/blob/7daf598c469e8f8792504bb013111293ee0765fe/core/CHANGELOG.md#160).
120 changes: 120 additions & 0 deletions .claude/skills/release-catalyst/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
name: release-catalyst
description: >
Cut a new release of Catalyst (`@bigcommerce/catalyst-core` and `@bigcommerce/catalyst-makeswift`).
Use when the user says "/release-catalyst", "cut a release", "release catalyst", or asks to
publish new versions of the Catalyst packages. This skill orchestrates the full two-stage release
process: merging the Version Packages PR on canary, syncing integrations/makeswift, and pushing
@latest tags.
---

# Release Catalyst

Execute stages in order. Pause for user input where indicated.

## Stage 1: Cut release from `canary`

### 1a. Find and merge the Version Packages PR

```bash
gh pr list --search "Version Packages (canary)" --state open --json number,title,reviews,mergeable
```

- If **no open PR** exists, inform the user that there are no pending changesets on `canary` and stop.
- If the PR is **approved and checks are passing**, merge it: `gh pr merge <number> --squash`
- If the PR is **not approved or checks are not passing**, tell the user and wait.
- Bot-opened PRs often don't trigger CI. If checks aren't running, push an empty commit to trigger them:
```bash
git checkout --track origin/changeset-release/canary
git commit --allow-empty -m "chore: trigger CI"
git push origin changeset-release/canary
git checkout canary && git branch -D changeset-release/canary
```
- **Stop here.** Wait for the user to confirm checks pass and the PR is approved before merging.

### 1b. Verify the release

After the PR merges:

```bash
git fetch origin --tags
```

Determine the new `@bigcommerce/catalyst-core` version from the PR body (look for `## @bigcommerce/catalyst-core@X.Y.Z`). Then verify:

```bash
gh release view @bigcommerce/catalyst-core@<version> --json tagName,name,isDraft,isPrerelease
```

If the release and tag don't exist yet, wait briefly and retry — the Changesets action may still be running.

Record the **version number** and **bump type** (patch/minor/major) for use in Stage 2.

## Stage 2: Sync and release `integrations/makeswift`

### 2a. Sync branches

Invoke the `/sync-makeswift` skill, with one addition: during the sync (after merge, before pushing), also add a changeset for `@bigcommerce/catalyst-makeswift`:

**Determine bump type**: Match the bump type from Stage 1 (e.g., if core went `1.4.2` → `1.5.0`, that's a `minor`).

**Create changeset file** (`.changeset/sync-canary-<version>.md`):

```markdown
---
"@bigcommerce/catalyst-makeswift": <patch|minor|major>
---

Pulls in changes from the `@bigcommerce/catalyst-core@<version>` release. For more information about what was included in the `@bigcommerce/catalyst-core@<version>` release, see the [changelog entry](https://github.com/bigcommerce/catalyst/blob/<canary-sha>/core/CHANGELOG.md#<version-anchor>).
```

Where:
- `<canary-sha>` is the merge commit SHA on canary (from the Version Packages merge)
- `<version-anchor>` is the version with dots removed (e.g., `1.5.0` → `150`)

Include this changeset in the merge commit (amend if needed) alongside the normal sync work.

### 2b. Merge the Version Packages (`integrations/makeswift`) PR

After the sync lands, the Changesets action will open a "Version Packages (`integrations/makeswift`)" PR.

```bash
gh pr list --search "Version Packages (integrations/makeswift)" --state open --json number,title
```

Same flow as Stage 1a:
- If checks aren't running (bot PR), push an empty commit to trigger CI, then **drop it before merging** by resetting to the parent and force-pushing.
- Once approved and green, merge with `gh pr merge <number> --squash`.
- Note: squash merging is normally disallowed on `integrations/makeswift` to preserve merge bases for sync PRs. The user may need to temporarily enable squash merging in the branch protection rules for this step, then re-disable it after.

### 2c. Verify the makeswift release

```bash
git fetch origin --tags
gh release view @bigcommerce/catalyst-makeswift@<version> --json tagName,name,isDraft,isPrerelease
```

## Stage 3: Push `@latest` tags

Update both `@latest` tags to point to the new releases:

```bash
git fetch origin --tags
git tag @bigcommerce/catalyst-core@latest @bigcommerce/catalyst-core@<version> -f
git tag @bigcommerce/catalyst-makeswift@latest @bigcommerce/catalyst-makeswift@<version> -f
git push origin @bigcommerce/catalyst-core@latest -f
git push origin @bigcommerce/catalyst-makeswift@latest -f
```

Confirm both tags were pushed successfully.

## Stage 4: Cleanup

```bash
git checkout canary
git pull
```

Delete any leftover local branches (`changeset-release/*`, `sync-integrations-makeswift`, `integrations/makeswift`).

Report the final state: both package versions released, tags updated, branches cleaned up.
82 changes: 82 additions & 0 deletions .claude/skills/sync-makeswift/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
name: sync-makeswift
description: >
Sync the `integrations/makeswift` branch with `canary` in the Catalyst monorepo.
Use when the user says "/sync-makeswift", "sync makeswift", "sync integrations/makeswift",
or asks to bring `integrations/makeswift` up to date with `canary`.
---

# Sync `integrations/makeswift` with `canary`

Execute the following phases in order. Pause for user input where indicated.

## Phase 1: Prepare and merge

```bash
git fetch origin
git checkout -B sync-integrations-makeswift origin/integrations/makeswift
git merge canary
```

If the merge completes cleanly, skip to changeset cleanup. Otherwise, resolve conflicts.

### Conflict resolution rules

- `core/package.json`: the `name` field MUST stay `@bigcommerce/catalyst-makeswift`. The `version` field MUST stay at the latest published `@bigcommerce/catalyst-makeswift` version (check what's on `origin/integrations/makeswift`, not `canary`).
- `core/CHANGELOG.md`: the latest release entry MUST match the latest published `@bigcommerce/catalyst-makeswift` version.
- `pnpm-lock.yaml`: accept canary's version (`git checkout --theirs pnpm-lock.yaml`), then regenerate with `pnpm install --no-frozen-lockfile`.
- For all other conflicts, prefer canary's structure/patterns while preserving makeswift-specific additions (imports, components, config).

After resolving all conflicts, stage everything and verify no unresolved conflicts remain:

```bash
git add <resolved files>
git diff --name-only --diff-filter=U # should return empty
```

### Changeset cleanup

Remove any `.changeset/*.md` files that do NOT target `@bigcommerce/catalyst-makeswift`. Read each changeset file and delete any that reference `@bigcommerce/catalyst-core` or other packages. Amend the removals into the merge commit.

### Commit the merge

```bash
git commit --no-edit
```

If changesets were removed after the initial commit, amend them in (`git commit --amend --no-edit`) rather than creating a separate commit.

## Phase 2: Push and open PR

```bash
git push origin sync-integrations-makeswift
```

Open a PR into `integrations/makeswift` (not `canary`):

- Title: `sync \`integrations/makeswift\` with \`canary\``
- Body: summarize what came from canary, list conflict resolutions, and include this notice:

> **Do not squash or rebase-and-merge this PR.** Use a true merge commit or rebase locally to preserve the merge base between `canary` and `integrations/makeswift`.

**Stop here.** Tell the user the PR is ready for review and wait for them to confirm approval before continuing.

## Phase 3: Rebase and push (after PR approval)

```bash
git fetch origin
git checkout -B integrations/makeswift origin/integrations/makeswift
git rebase sync-integrations-makeswift
git push origin integrations/makeswift --force-with-lease
```

This closes the PR automatically. Confirm with the user that the push succeeded and the PR closed.

## Phase 4: Cleanup

Switch back to `canary` and delete the local branches that are no longer needed:

```bash
git checkout canary
git branch -D sync-integrations-makeswift integrations/makeswift
```
39 changes: 16 additions & 23 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,40 +171,33 @@ This ensures `integrations/makeswift` remains a faithful mirror of `canary` whil

#### Stage 2: Sync and Release `integrations/makeswift`

2. Follow steps 1-6 under "[Keeping `integrations/makeswift` in sync with `canary`](#keeping-integrationsmakeswift-in-sync-with-canary)"
2. Follow steps 1-6 under "[Keeping `integrations/makeswift` in sync with `canary`](#keeping-integrationsmakeswift-in-sync-with-canary)", with one addition: **include a changeset for `@bigcommerce/catalyst-makeswift` in the sync merge commit** rather than opening a separate PR for it afterwards.

3. **IMPORTANT**: After step 6, you'll need to open another PR into `integrations/makeswift`
- Ensure a local `integrations/makeswift` branch exists and is up to date (`git checkout -B integrations/makeswift origin/integrations/makeswift`)
- Run `git fetch origin` and create a new branch from `integrations/makeswift` (`git checkout -B bump-version origin/integrations/makeswift`)
- From this new `bump-version` branch, run `pnpm changeset`
- Select `@bigcommerce/catalyst-makeswift`
- For choosing between a `patch/minor/major` bump, you should copy the bump from Stage 1. (e.g., if `@bigcommerce/catalyst-core` went from `1.1.0` to `1.2.0`, choose `minor`)
- Example changeset:
- Match the bump type from Stage 1 (e.g., if `@bigcommerce/catalyst-core` went from `1.4.2` to `1.5.0`, use `minor`)
- Create a changeset file in `.changeset/` (e.g., `.changeset/sync-canary-1-5-0.md`):

```
---
"@bigcommerce/catalyst-makeswift": patch
"@bigcommerce/catalyst-makeswift": minor
---

Pulls in changes from the `@bigcommerce/catalyst-core@1.4.1` patch.
Pulls in changes from the `@bigcommerce/catalyst-core@1.5.0` release. For more information, see the [changelog entry](https://github.com/bigcommerce/catalyst/blob/<canary-sha>/core/CHANGELOG.md#150).
```

- Commit the generated changeset file and open a PR to merge this branch into `integrations/makeswift`
- Once merged, you can proceed to the next step
- Replace `<canary-sha>` with the merge commit SHA of the Version Packages PR on `canary` so the link remains stable
- Amend this changeset into the merge commit alongside any other sync changes (changeset cleanup, `core/package.json` and `core/CHANGELOG.md` fixes, etc.)

4. Merge the **Version Packages (`integrations/makeswift`)** PR: Changesets will open another PR (similar to Stage 1) bumping `@bigcommerce/catalyst-makeswift`. Merge it following the same process. This cuts a new release of the Makeswift variant.
3. Merge the **Version Packages (`integrations/makeswift`)** PR: After the sync lands, Changesets will open a PR (similar to Stage 1) bumping `@bigcommerce/catalyst-makeswift`. Merge it following the same process. This cuts a new release of the Makeswift variant.

5. **Tags and Releases:** Confirm tags exist for both `@bigcommerce/catalyst-core` and `@bigcommerce/catalyst-makeswift`. If needed, update `latest` tags in GitHub manually.
4. **Tags and Releases:** Confirm tags exist for both `@bigcommerce/catalyst-core` and `@bigcommerce/catalyst-makeswift`. Update `latest` tags to point to the new releases:

- Push manually:
```
git checkout canary
# Make sure you have the latest code
git fetch origin
git pull
git tag @bigcommerce/catalyst-core@latest -f
git push origin @bigcommerce/catalyst-core@latest -f
```
```bash
git fetch origin --tags
git tag @bigcommerce/catalyst-core@latest @bigcommerce/catalyst-core@<version> -f
git tag @bigcommerce/catalyst-makeswift@latest @bigcommerce/catalyst-makeswift@<version> -f
git push origin @bigcommerce/catalyst-core@latest -f
git push origin @bigcommerce/catalyst-makeswift@latest -f
```

### Additional Notes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import { schema } from '@/vibes/soul/sections/reviews/schema';
import { getSessionCustomerAccessToken } from '~/auth';
import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { assertRecaptchaTokenPresent, getRecaptchaFromForm } from '~/lib/recaptcha';

const AddProductReviewMutation = graphql(`
mutation AddProductReviewMutation($input: AddProductReviewInput!) {
mutation AddProductReviewMutation(
$input: AddProductReviewInput!
$reCaptchaV2: ReCaptchaV2Input
) {
catalog {
addProductReview(input: $input) {
addProductReview(input: $input, reCaptchaV2: $reCaptchaV2) {
__typename
errors {
__typename
Expand All @@ -38,6 +42,16 @@ export async function submitReview(
return { ...prevState, lastResult: submission.reply() };
}

const { siteKey, token } = await getRecaptchaFromForm(payload);
const recaptchaValidation = assertRecaptchaTokenPresent(siteKey, token, t('recaptchaRequired'));

if (!recaptchaValidation.success) {
return {
...prevState,
lastResult: submission.reply({ formErrors: recaptchaValidation.formErrors }),
};
}

const { productEntityId, ...input } = submission.value;

try {
Expand All @@ -52,6 +66,8 @@ export async function submitReview(
},
productEntityId,
},
reCaptchaV2:
recaptchaValidation.token != null ? { token: recaptchaValidation.token } : undefined,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,15 @@ interface Props {
pageInfo?: { hasNextPage: boolean; endCursor: string | null };
}>;
streamableProduct: Streamable<Awaited<ReturnType<typeof getStreamableProduct>>>;
recaptchaSiteKey?: string;
}

export const Reviews = async ({
productId,
searchParams,
streamableProduct,
streamableImages,
recaptchaSiteKey,
}: Props) => {
const t = await getTranslations('Product.Reviews');

Expand Down Expand Up @@ -189,6 +191,7 @@ export const Reviews = async ({
paginationInfo={streamablePaginationInfo}
previousLabel={t('previous')}
productId={productId}
recaptchaSiteKey={recaptchaSiteKey}
reviews={streamableReviews}
reviewsLabel={t('title')}
streamableImages={streamableImages}
Expand Down
8 changes: 7 additions & 1 deletion core/app/[locale]/(default)/product/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { productOptionsTransformer } from '~/data-transformers/product-options-t
import { getPreferredCurrencyCode } from '~/lib/currency';
import { getMakeswiftPageMetadata } from '~/lib/makeswift';
import { ProductDetail } from '~/lib/makeswift/components/product-detail';
import { getRecaptchaSiteKey } from '~/lib/recaptcha';
import { getMetadataAlternates } from '~/lib/seo/canonical';

import { addToCart } from './_actions/add-to-cart';
Expand Down Expand Up @@ -80,7 +81,10 @@ export default async function Product({ params, searchParams }: Props) {

const productId = Number(slug);

const { product: baseProduct, settings } = await getProduct(productId, customerAccessToken);
const [{ product: baseProduct, settings }, recaptchaSiteKey] = await Promise.all([
getProduct(productId, customerAccessToken),
getRecaptchaSiteKey(),
]);

const reviewsEnabled = Boolean(settings?.reviews.enabled && !settings.display.showProductRating);
const showRating = Boolean(settings?.reviews.enabled && settings.display.showProductRating);
Expand Down Expand Up @@ -586,6 +590,7 @@ export default async function Product({ params, searchParams }: Props) {
}}
productId={baseProduct.entityId}
quantityLabel={t('ProductDetails.quantity')}
recaptchaSiteKey={recaptchaSiteKey}
reviewFormAction={submitReview}
thumbnailLabel={t('ProductDetails.thumbnail')}
user={streamableUser}
Expand All @@ -607,6 +612,7 @@ export default async function Product({ params, searchParams }: Props) {
<div id="reviews">
<Reviews
productId={productId}
recaptchaSiteKey={recaptchaSiteKey}
searchParams={searchParams}
streamableImages={streamableImages}
streamableProduct={streamableProduct}
Expand Down
Loading
Loading