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/ready-plants-flash.md
Original file line number Diff line number Diff line change
@@ -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.
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 4 additions & 68 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
16 changes: 11 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion starters/react-npm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down