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/curvy-pants-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"brand-shell": minor
---

Add two-stage npm release automation: publish to dist-tag next, validate via a starter app canary, then promote to latest.
111 changes: 107 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,45 @@ 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 == 'push') ||
(
github.event_name == 'workflow_dispatch' &&
github.ref == 'refs/heads/main'
github.ref == 'refs/heads/main' &&
(inputs.mode == '' || inputs.mode == 'publish')
)
permissions:
contents: write
pull-requests: write
id-token: write
outputs:
published: ${{ steps.changesets.outputs.published }}
publishedPackages: ${{ steps.changesets.outputs.publishedPackages }}

steps:
- name: Checkout
Expand All @@ -47,12 +65,97 @@ jobs:
- name: Install Dependencies
run: bun install --frozen-lockfile

- name: Create release PR or publish
- name: "Create release PR or publish (tag: next)"
id: changesets
uses: changesets/action@v1
with:
version: bun run version:packages
publish: bun run release:publish
publish: bun run release:publish:next
title: "chore(release): version packages"
commit: "chore(release): version packages"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

starter-canary:
name: "Starter Canary (npm tag: next)"
runs-on: ubuntu-latest
needs:
- release
if: ${{ needs.release.outputs.published == 'true' }}
permissions:
contents: read

steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- name: Setup Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24.13.0
registry-url: https://registry.npmjs.org

- name: Setup Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3
with:
bun-version: 1.3.6

- name: Resolve published version
id: published
env:
PUBLISHED_PACKAGES: ${{ needs.release.outputs.publishedPackages }}
run: |
set -euo pipefail
node -e 'const raw=process.env.PUBLISHED_PACKAGES||"[]"; const list=JSON.parse(raw); const hit=list.find(p=>p.name==="brand-shell"); if(!hit) process.exit(2); process.stdout.write(`VERSION=${hit.version}\n`);' >> "$GITHUB_OUTPUT"

- name: Install starter dependencies
run: bun install --cwd starters/react-npm

- name: Pin brand-shell to published version
run: bun add brand-shell@${{ steps.published.outputs.VERSION }} --cwd starters/react-npm --exact

- 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 }}
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
18 changes: 12 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,27 @@ 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.
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.

## CI Workflows

- `/Users/mounikathota/brand-shell/.github/workflows/ci.yml`: PR checks (commit policy, reusable verify, conditional Chromatic)
- `/Users/mounikathota/brand-shell/.github/workflows/verify.yml`: reusable quality/docs/consumer matrix gate
- `/Users/mounikathota/brand-shell/.github/workflows/release.yml`: main-branch release PR + npm publish
- `.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`

## NPM Publish Notes

- Publish uses npm Trusted Publishing (OIDC) from GitHub Actions.
- Keep `id-token: write` permission in `/Users/mounikathota/brand-shell/.github/workflows/release.yml`.
- Configure Trusted Publisher in npm package settings for this repository/workflow.
- Keep `id-token: write` permission in `.github/workflows/release.yml`.
- Configure the Trusted Publisher in npm package settings for this repository/workflow: `release.yml`.
- Do not store long-lived `NPM_TOKEN` once Trusted Publishing is active.

## 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.

## Pull Request Checklist

- Keep API and schema changes intentional and documented.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"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
3 changes: 3 additions & 0 deletions starters/react-npm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/
node_modules/
bun.lock
Loading