From 59b4905a4c009e4ff9fba8f6a7c3bcf9615b613c Mon Sep 17 00:00:00 2001 From: venwork-dev Date: Thu, 19 Feb 2026 17:31:21 -0600 Subject: [PATCH] feat(release): replace dist-tag promotion with a deterministic release PR gate that packs --- .changeset/ready-plants-flash.md | 5 +++ .github/workflows/ci.yml | 47 +++++++++++++++++++++ .github/workflows/release.yml | 72 ++------------------------------ CONTRIBUTING.md | 16 ++++--- package.json | 1 - starters/react-npm/package.json | 2 +- 6 files changed, 68 insertions(+), 75 deletions(-) create mode 100644 .changeset/ready-plants-flash.md diff --git a/.changeset/ready-plants-flash.md b/.changeset/ready-plants-flash.md new file mode 100644 index 0000000..03e53c9 --- /dev/null +++ b/.changeset/ready-plants-flash.md @@ -0,0 +1,5 @@ +--- +"brand-shell": minor +--- + +Replace dist-tag promotion with a deterministic release PR gate that packs the publish artifact and builds the starter app before publishing. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2646e11..11d784d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,53 @@ jobs: uses: ./.github/workflows/verify.yml secrets: inherit + pack-starter-canary: + name: Pack + Starter Canary (release PRs) + runs-on: ubuntu-latest + needs: + - conventional-commits + if: >- + github.event_name == 'pull_request' && + startsWith(github.event.pull_request.title, 'chore(release):') + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: 24.13.0 + + - name: Setup Bun + uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 + with: + bun-version: 1.3.6 + + - name: Install Dependencies + run: bun install --frozen-lockfile + + - name: Build package + run: bun run build + + - name: Pack tarball + id: pack + run: | + set -euo pipefail + tarball="$(npm pack --silent)" + echo "TARBALL=${tarball}" >> "$GITHUB_OUTPUT" + + - name: Install starter dependencies + run: bun install --cwd starters/react-npm + + - name: Install tarball into starter + run: bun add ./${{ steps.pack.outputs.TARBALL }} --cwd starters/react-npm --exact + + - name: Build starter + run: bun run --cwd starters/react-npm build + storybook-changes: name: Detect Storybook Changes runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 652314d..12363d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,38 +5,17 @@ on: branches: - main workflow_dispatch: - inputs: - mode: - description: "publish (default) or promote" - required: false - default: publish - type: choice - options: - - publish - - promote - version: - description: 'When mode=promote: version to promote to "latest" (blank = use current "next" dist-tag)' - required: false - type: string jobs: verify: uses: ./.github/workflows/verify.yml secrets: inherit - if: ${{ github.event_name != 'workflow_dispatch' || inputs.mode == '' || inputs.mode == 'publish' }} release: name: Version & Publish runs-on: ubuntu-latest needs: - verify - if: >- - (github.event_name == 'push') || - ( - github.event_name == 'workflow_dispatch' && - github.ref == 'refs/heads/main' && - (inputs.mode == '' || inputs.mode == 'publish') - ) permissions: contents: write pull-requests: write @@ -65,25 +44,26 @@ jobs: - name: Install Dependencies run: bun install --frozen-lockfile - - name: "Create release PR or publish (tag: next)" + - name: Create release PR or publish id: changesets uses: changesets/action@v1 with: version: bun run version:packages - publish: bun run release:publish:next + publish: bun run release:publish title: "chore(release): version packages" commit: "chore(release): version packages" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} starter-canary: - name: "Starter Canary (npm tag: next)" + name: Starter Canary (npm published) runs-on: ubuntu-latest needs: - release if: ${{ needs.release.outputs.published == 'true' }} permissions: contents: read + id-token: write steps: - name: Checkout @@ -116,47 +96,3 @@ jobs: - name: Build starter run: bun run --cwd starters/react-npm build - - promote-latest: - name: Promote npm dist-tag (latest) - runs-on: ubuntu-latest - if: >- - github.event_name == 'workflow_dispatch' && - github.ref == 'refs/heads/main' && - inputs.mode == 'promote' - permissions: - id-token: write - - steps: - - name: Setup Node - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 - with: - node-version: 24.13.0 - registry-url: https://registry.npmjs.org - - - name: Resolve version - id: resolved - env: - INPUT_VERSION: ${{ inputs.version }} - run: | - set -euo pipefail - if [ -n "${INPUT_VERSION}" ]; then - echo "VERSION=${INPUT_VERSION}" >> "$GITHUB_OUTPUT" - exit 0 - fi - node -e 'const { execSync } = require("node:child_process"); const raw=execSync("npm view brand-shell dist-tags --json",{encoding:"utf8"}); const tags=JSON.parse(raw); if(!tags.next) { console.error("No dist-tag: next"); process.exit(2);} process.stdout.write(`VERSION=${tags.next}\n`);' >> "$GITHUB_OUTPUT" - - - name: Show current dist-tags - run: npm view brand-shell dist-tags - - - name: Promote dist-tag - env: - VERSION: ${{ steps.resolved.outputs.VERSION }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - set -euo pipefail - echo "Promoting brand-shell@${VERSION} -> latest" - npm dist-tag add "brand-shell@${VERSION}" latest - - - name: Show updated dist-tags - run: npm view brand-shell dist-tags diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac6573a..6ce756c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,15 +59,14 @@ Release is automated through GitHub Actions. 1. Merge feature PRs into `main`. 2. Release workflow creates/updates a bot release PR with version bumps. 3. Merge the release PR. -4. Release workflow publishes to npm with dist-tag `next`. -5. Canary validates the published `next` version using `starters/react-npm`. -6. Promote the validated version to `latest` using the `Promote npm dist-tag (latest)` workflow. +4. The release PR runs a pack + starter canary gate (see below). +5. Once the release PR is merged, the release workflow publishes to npm (`latest`) via Trusted Publishing. ## CI Workflows - `.github/workflows/ci.yml`: PR checks (commit policy, reusable verify, conditional Chromatic) - `.github/workflows/verify.yml`: reusable quality/docs/consumer matrix gate -- `.github/workflows/release.yml`: publish to dist-tag `next`, run starter canary, and optionally promote to `latest` +- `.github/workflows/release.yml`: main-branch release PR + npm publish ## NPM Publish Notes @@ -78,7 +77,14 @@ Release is automated through GitHub Actions. ## Starter Canary -The `starters/react-npm` app is intentionally a "real npm consumer" (no local `dist` aliases). The release workflow pins it to the exact published version and runs `bun run build` before promoting. +The `starters/react-npm` app is intentionally a "real npm consumer" (no local `dist` aliases). + +Release PRs run a deterministic canary in CI: + +- build library +- `npm pack` the exact publish artifact +- install the tarball into `starters/react-npm` +- run `bun run build` in the starter ## Pull Request Checklist diff --git a/package.json b/package.json index 29b22e5..9cfc936 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "changeset": "bunx @changesets/cli@2.29.7", "version:packages": "bun run changeset version", "release:publish": "bun run changeset publish --provenance", - "release:publish:next": "bun run changeset publish --provenance --tag next", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "postbuild": "node scripts/copy-css.cjs" diff --git a/starters/react-npm/package.json b/starters/react-npm/package.json index 3a2f7b8..a99cb55 100644 --- a/starters/react-npm/package.json +++ b/starters/react-npm/package.json @@ -8,7 +8,7 @@ "preview": "vite preview" }, "dependencies": { - "brand-shell": "latest", + "brand-shell": "^0.0.0", "react": "^18.3.1", "react-dom": "^18.3.1" },