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
7 changes: 7 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Summary

## Test plan

## Todo

- [ ] PR title has a `fix:`/`feat:`/`feat!:` prefix (or intentionally none, for no release)
106 changes: 71 additions & 35 deletions .github/workflows/npm-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,36 @@ name: NPM release

on:
push:
tags:
- '*'
branches: ["main"]
workflow_dispatch: {}

# Run releases one at a time, so two runs can't publish the same version or move `latest`
# backwards. A queued run that gets superseded is dropped; its commits roll into the next release.
concurrency:
group: npm-release-${{ github.ref_name }}
cancel-in-progress: false

env:
CI: true

permissions:
id-token: write
contents: write
pull-requests: write

jobs:
publish:
name: "Publish ${{ github.ref_name }}"
runs-on: ubuntu-latest
# npm's Trusted Publisher config for @mapbox/mr-ui is bound to this workflow file and environment.
environment: npm-release
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v4

- name: Extract package version from package.json
run: |
packageVersion=$(jq -r .version package.json)
echo "versionTag=v$packageVersion" >> "$GITHUB_ENV"

- name: Verify tag matches package.json version
if: ${{ github.ref_name != env.versionTag }}
run: |
echo "Tag ${{ github.ref_name }} does not match package.json version (expected ${{ env.versionTag }})."
exit 1
- uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: npm
Expand All @@ -41,35 +42,70 @@ jobs:
- name: Install npm dependencies
run: npm ci --no-fund --no-audit --prefer-offline

- name: Build publishable package
# Publishes from pkg/, not the repo root - see scripts/build-package.js.
# This compiles src/components into pkg/, with a package.json that has
# `private`/`scripts`/`devDependencies` stripped out.
run: npm run build
# pretest runs lint and tsc
- name: Lint, typecheck, and test
run: npm test

- name: Release production
if: ${{ !contains(github.ref_name, '-') }}
working-directory: pkg
run: npm publish
# Reads the current version from public npmjs and git history
- id: version
uses: mapbox/gha@action-conventional-commit-version-v0
with:
before-sha: ${{ github.event.before }}
after-sha: ${{ github.event.after }}

- name: Release development
if: ${{ contains(github.ref_name, '-') }}
# Versioned before building because scripts/build-package.js copies package.json into pkg/
- name: Build publishable package
if: steps.version.outputs.skip != 'true'
run: |
npm version ${{ steps.version.outputs.version }} --no-git-tag-version
npm run build

- name: Publish
if: steps.version.outputs.skip != 'true'
working-directory: pkg
run: npm publish --tag dev
run: npm publish --tag ${{ steps.version.outputs.npm_publish_tag }}

- id: release
name: Create GitHub Release
continue-on-error: true
if: steps.version.outputs.skip != 'true' && steps.version.outputs.npm_publish_tag == 'latest'
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.version.outputs.version }}
run: gh release create "v$VERSION" --target "$GITHUB_SHA" --title "v$VERSION" --generate-notes

- name: Comment released version on PRs
continue-on-error: true
if: steps.release.outcome == 'success'
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.version.outputs.version }}
BEFORE_SHA: ${{ github.event.before }}
run: |
set -o pipefail
package_name="$(node -p 'require("./package.json").name')"
for sha in $(git rev-list "$BEFORE_SHA..$GITHUB_SHA"); do
gh api "repos/$GITHUB_REPOSITORY/commits/$sha/pulls" --jq '.[].number'
done | sort -u | while read -r pr; do
gh pr comment "$pr" --body "Released in \`$package_name@$VERSION\` (release [\`v$VERSION\`](https://github.com/$GITHUB_REPOSITORY/releases/tag/v$VERSION))."
done

- name: Build docs
if: ${{ !contains(github.ref_name, '-') }}
if: steps.version.outputs.skip != 'true' && steps.version.outputs.npm_publish_tag == 'latest'
run: npm run build-docs

- name: Deploy docs
if: ${{ !contains(github.ref_name, '-') }}
if: steps.version.outputs.skip != 'true' && steps.version.outputs.npm_publish_tag == 'latest'
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.version.outputs.version }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

worktree=$(mktemp -d)
git fetch origin gh-pages --depth=1
git worktree add "$worktree" gh-pages
git worktree add "$worktree" FETCH_HEAD

rsync -a --delete --exclude=.git build/ "$worktree/"

Expand All @@ -78,6 +114,6 @@ jobs:
if git diff --cached --quiet; then
echo "No changes to deploy."
else
git commit -m "Deploy docs for ${{ github.ref_name }}"
git push origin gh-pages
git commit -m "Deploy docs for v$VERSION"
git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:gh-pages
fi
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: npm
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Changelog

Starting after 2.25.0, release notes are published as [GitHub Releases](https://github.com/mapbox/mr-ui/releases).

## HEAD

## 2.25.0
Expand Down
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,17 @@ npm test

The `build` command creates a `pkg/` directory that contains the code we want to publish, organized the way we want it. So `pkg/` is the directory that we publish. `pkg/package.json` is a clone of `package.json` but with `private`/`scripts`/`devDependencies` removed.

Publishing is automated via the `NPM release` GitHub Actions workflow, triggered by pushing a version tag:
Pull Requests merged to `main` with conventional commit prefixes are automatically published to npm by the `NPM release` GitHub Actions workflow, as new major/minor/patch versions (`feat!:`/`feat:`/`fix:` respectively). When using "Squash and merge", ensure your PR title includes the conventional commit prefix; a `BREAKING CHANGE:` footer only counts if it ends up in the squash commit's body. Other prefixes (`chore:`, `docs:`, etc.) or no prefix don't trigger a release.

- Increment version numbers in `package.json` and `package-lock.json`, and ensure the changelog has an entry for the latest version. Commit this.
- Create a Git tag matching the version, prefixed with `v`: `git tag v2.25.0`.
- Push the tag: `git push origin v2.25.0`.
- This triggers the workflow, which builds `pkg/` and publishes it via [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) (OIDC) — no npm token needed. It also builds and deploys the docs to https://mapbox.github.io/mr-ui/.
- Any prerelease tag (a version containing `-`, e.g. `v2.25.0-dev.0`) publishes under the npm `dev` dist-tag instead of `latest`, for testing a release without affecting `latest` consumers - dev releases don't touch the live docs site.
Examples:
- `fix: resolved issue with dropdowns` -> patch
- `feat: added dropdowns` -> minor
- `feat!: Overhauled dropdowns` (requires manual migration work) -> major

The workflow builds `pkg/` and publishes it via [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) (OIDC) — no npm token needed. It then creates a GitHub Release with generated notes, comments the released version on the merged PRs, and builds and deploys the docs to https://mapbox.github.io/mr-ui/.

`package.json`'s `version` may be incremented manually if needed (for example, to skip a minor version), but you shouldn't normally need to touch it; the published npm version, not `package.json`, is the source of truth for what gets released next.

#### Pre-release versions on other branches

For testing in downstream projects, you can manually trigger the "NPM release" workflow on a branch to publish a pre-release tagged with the short commit sha (e.g. `2.25.0-3ce8575`) under the npm `prerelease` dist-tag, without affecting `latest` or the live docs site. Check the workflow results for the published version.
Loading