diff --git a/.changeset/config.json b/.changeset/config.json index b63b8a0935..f156edd5df 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -11,7 +11,7 @@ "version": true, "tag": true }, - "baseBranch": "integrations/makeswift", + "baseBranch": "integrations/b2b-makeswift", "updateInternalDependencies": "patch", - "ignore": ["@bigcommerce/catalyst"] + "ignore": ["@bigcommerce/catalyst", "@bigcommerce/create-catalyst"] } diff --git a/.github/scripts/prevent-invalid-changesets.js b/.github/scripts/prevent-invalid-changesets.js index c707b86651..f83cbb7766 100644 --- a/.github/scripts/prevent-invalid-changesets.js +++ b/.github/scripts/prevent-invalid-changesets.js @@ -5,18 +5,18 @@ module.exports = async ({ core, exec }) => { await exec.exec("git", [ "fetch", "https://github.com/bigcommerce/catalyst.git", - "integrations/makeswift", + "integrations/b2b-makeswift", ]); const { stdout } = await exec.getExecOutput("git", [ "diff", "--name-only", - `origin/integrations/makeswift...HEAD`, + `origin/integrations/b2b-makeswift...HEAD`, ]); const allFilenames = stdout.split("\n").filter((line) => line.trim()); const changesetFilenames = allFilenames.filter( - (file) => file.startsWith(".changeset/") && file.endsWith(".md") + (file) => file.startsWith(".changeset/") && file.endsWith(".md"), ); if (changesetFilenames.length === 0) { @@ -45,8 +45,10 @@ module.exports = async ({ core, exec }) => { } if (!fs.existsSync(filename)) { - core.setFailed(`File not found: ${filename}`); - return; + core.warning( + `File not found: ${filename}. This is likely a version PR where the changeset was already consumed. Skipping validation for this file.`, + ); + continue; } // check file size (limit to 100KB) @@ -83,18 +85,18 @@ module.exports = async ({ core, exec }) => { if (packageMatches) { const invalidPackages = packageMatches.filter( - (pkg) => pkg !== '"@bigcommerce/catalyst-makeswift"' + (pkg) => pkg !== '"@bigcommerce/catalyst-b2b-makeswift"', ); if (invalidPackages.length > 0) { core.error( - `Invalid package found in changeset file. Only @bigcommerce/catalyst-makeswift is allowed.`, - { file: filename } + `Invalid package found in changeset file. Only @bigcommerce/catalyst-b2b-makeswift is allowed.`, + { file: filename }, ); core.setFailed( `File ${filename} contains invalid packages: ${invalidPackages.join( - ", " - )}` + ", ", + )}`, ); return; } diff --git a/.github/scripts/sync-catalyst-version.mts b/.github/scripts/sync-catalyst-version.mts new file mode 100644 index 0000000000..1043fcf7a9 --- /dev/null +++ b/.github/scripts/sync-catalyst-version.mts @@ -0,0 +1,58 @@ +#!/usr/bin/env node +/* eslint-disable no-console */ + +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +export interface CorePackageJson { + name: string; + version: string; + catalyst?: { version: string; ref: string }; + [key: string]: unknown; +} + +// `catalyst.version` mirrors the released version; `catalyst.ref` is the git tag +// (`@bigcommerce/catalyst-core@`) the version corresponds to, consumed +// by the future `catalyst upgrade` command. +export function buildCatalystField( + pkg: Pick, +): { version: string; ref: string } { + return { version: pkg.version, ref: `${pkg.name}@${pkg.version}` }; +} + +// Returns the package.json text with its `catalyst` field synced to `version`. +// Re-serializes with the canonical 2-space + trailing-newline format, so a run +// where nothing changed produces no diff. +export function syncCatalystField(packageJsonText: string): string { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const pkg = JSON.parse(packageJsonText) as CorePackageJson; + + pkg.catalyst = buildCatalystField(pkg); + + return `${JSON.stringify(pkg, null, 2)}\n`; +} + +function main(): void { + const corePackageJsonPath = resolve( + dirname(fileURLToPath(import.meta.url)), + "../../core/package.json", + ); + + const updated = syncCatalystField(readFileSync(corePackageJsonPath, "utf-8")); + + writeFileSync(corePackageJsonPath, updated); + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const { catalyst } = JSON.parse(updated) as CorePackageJson; + + console.log( + `Synced core/package.json catalyst field → ${catalyst?.version} (${catalyst?.ref})`, + ); +} + +const isMain = process.argv[1] === fileURLToPath(import.meta.url); + +if (isMain) { + main(); +} diff --git a/.github/workflows/changesets-release.yml b/.github/workflows/changesets-release.yml index 251741f70a..00e6ca320e 100644 --- a/.github/workflows/changesets-release.yml +++ b/.github/workflows/changesets-release.yml @@ -5,9 +5,16 @@ on: branches: - canary - integrations/makeswift + - integrations/b2b-makeswift concurrency: ${{ github.workflow }}-${{ github.ref }} +permissions: + id-token: write + contents: write + packages: write + pull-requests: write + jobs: changesets-release: name: Changesets Release @@ -15,6 +22,9 @@ jobs: steps: - name: Checkout Repo uses: actions/checkout@v4 + with: + # Full history required for linear-release-action to scan commits. + fetch-depth: 0 - name: Setup pnpm uses: pnpm/action-setup@v3 @@ -33,13 +43,63 @@ jobs: env: CLI_SEGMENT_WRITE_KEY: ${{ secrets.CLI_SEGMENT_WRITE_KEY }} + - name: Sync Linear release + uses: linear/linear-release-action@v0 + with: + access_key: ${{ secrets.LINEAR_ACCESS_KEY }} + command: sync + # Only the storefront package ships as its own release; changes to + # other workspace packages (client, create-catalyst, the CLI) are + # released separately and shouldn't count toward this pipeline. + include_paths: core/** + - name: Create Release Pull Request or Publish to npm id: changesets uses: changesets/action@v1 with: + # Runs `changeset version`, then syncs core/package.json's `catalyst` + # field (version + git ref) to the freshly-bumped version. + version: pnpm changeset:version publish: pnpm exec changeset publish title: "Version Packages (`${{ github.ref_name }}`)" commit: "Version Packages (`${{ github.ref_name }}`)" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Resolve headline package + id: headline + if: steps.changesets.outputs.published == 'true' + run: | + PACKAGE_NAME="@bigcommerce/catalyst-core" + if [ "${{ github.ref_name }}" = "integrations/makeswift" ]; then + PACKAGE_NAME="@bigcommerce/catalyst-makeswift" + elif [ "${{ github.ref_name }}" = "integrations/b2b-makeswift" ]; then + PACKAGE_NAME="@bigcommerce/catalyst-b2b-makeswift" + fi + VERSION=$(echo '${{ steps.changesets.outputs.publishedPackages }}' | jq -r --arg name "$PACKAGE_NAME" '.[] | select(.name == $name) | .version') + if [ -z "$VERSION" ]; then + echo "found=false" >> "$GITHUB_OUTPUT" + else + echo "found=true" >> "$GITHUB_OUTPUT" + echo "tag=$PACKAGE_NAME@$VERSION" >> "$GITHUB_OUTPUT" + fi + + - name: Fetch release notes + if: steps.headline.outputs.found == 'true' + run: gh release view "${{ steps.headline.outputs.tag }}" --json body -q .body > /tmp/release-notes.md + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Complete Linear release + if: steps.headline.outputs.found == 'true' + uses: linear/linear-release-action@v0 + with: + access_key: ${{ secrets.LINEAR_ACCESS_KEY }} + command: complete + # No `version`: the `catalyst-release` pipeline is a scheduled pipeline + # whose started release is unversioned (the accumulating `sync` steps + # above run without a version). Passing `version` makes `complete` look + # up an existing release with that exact version, which never exists and + # fails with `No release found with version "…"`. Omitting it completes + # the latest started release — the one sync has been accumulating into. + release_notes: /tmp/release-notes.md diff --git a/.github/workflows/prevent-invalid-changesets.yml b/.github/workflows/prevent-invalid-changesets.yml index be99c8c55c..87754d816c 100644 --- a/.github/workflows/prevent-invalid-changesets.yml +++ b/.github/workflows/prevent-invalid-changesets.yml @@ -3,7 +3,7 @@ name: Prevent invalid packages for Changesets on: pull_request: branches: - - integrations/makeswift + - integrations/b2b-makeswift permissions: contents: read @@ -19,7 +19,7 @@ jobs: with: fetch-depth: 0 - - name: Validate changesets only target @bigcommerce/catalyst-makeswift + - name: Validate changesets only target @bigcommerce/catalyst-b2b-makeswift uses: actions/github-script@v7 with: script: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07fa2f4032..938d32dace 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,11 +68,11 @@ To pull the latest code from `canary` into `integrations/makeswift`, follow the > [!WARNING] > There are a number of "gotchas" that you need to be aware of when merging `canary` into `integrations/makeswift`: > -> - The `name` field in `core/package.json` should remain `@bigcommerce/catalyst-makeswift` -> - The `version` field in `core/package.json` should remain whatever the latest published `@bigcommerce/catalyst-makeswift` version was -> - The `.changeset/` directory should not include any files that reference the `"@bigcommerce/catalyst-core"` package. If these files are merged into `integrations/makeswift`, they will cause the `Changesets Release` GitHub Action in `.github/workflows/changesets-release.yml` to fail with the error: `Error: Found changeset for package @bigcommerce/catalyst-core which is not in the workspace` +> - The `name` field in `core/package.json` should remain `@bigcommerce/catalyst-b2b-makeswift` +> - The `version` field in `core/package.json` should remain whatever the latest published `@bigcommerce/catalyst-b2b-makeswift` version was +> - The `.changeset/` directory should only include files that reference the `"@bigcommerce/catalyst-b2b-makeswift"` package (not `"@bigcommerce/catalyst-core"` or `"@bigcommerce/catalyst-makeswift"`). If files referencing other packages are merged into `integrations/b2b-makeswift`, they will cause the `Changesets Release` GitHub Action in `.github/workflows/changesets-release.yml` to fail with an error like: `Error: Found changeset for package @bigcommerce/catalyst-core which is not in the workspace` > -> _Note: A [GitHub Action is in place](.github/workflows/prevent-invalid-changesets.yml) to help prevent invalid changesets from being merged into `integrations/makeswift`. Do not merge your PR if this GitHub Action fails._ +> _Note: A [GitHub Action is in place](.github/workflows/prevent-invalid-changesets.yml) to help prevent invalid changesets from being merged into `integrations/b2b-makeswift`. Do not merge your PR if this GitHub Action fails._ 5. After resolving any merge conflicts, open a new PR in GitHub to merge your `{new-branch-name}` into `integrations/makeswift`. This PR should be code reviewed and approved before the next steps. diff --git a/core/package.json b/core/package.json index 21f13d6811..d0774274d4 100644 --- a/core/package.json +++ b/core/package.json @@ -1,5 +1,5 @@ { - "name": "@bigcommerce/catalyst-makeswift", + "name": "@bigcommerce/catalyst-b2b-makeswift", "description": "BigCommerce Catalyst is a Next.js starter kit for building headless BigCommerce storefronts.", "version": "1.1.3", "private": true, diff --git a/package.json b/package.json index c234d41183..b28b5b795a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "build": "dotenv -e .env.local -- turbo run build", "lint": "dotenv -e .env.local -- turbo lint", "test": "turbo run test", - "typecheck": "turbo typecheck" + "typecheck": "turbo typecheck", + "changeset:version": "changeset version && node .github/scripts/sync-catalyst-version.mts" }, "devDependencies": { "@changesets/changelog-github": "^0.5.1",