Skip to content

release

release #1

Workflow file for this run

# Release pipeline driven by the canonical VERSION file.
#
# Trigger: push of a tag matching v*.*.* (e.g. v1.0.1, v1.2.3-beta.1).
#
# Steps:
# 1. Guard: the tag matches VERSION, and every derived version is already
# in sync. The local flow is `sync-version.sh` before tagging, so a
# mismatch here means the human forgot to commit the bump.
# 2. Publish to npm via Trusted Publisher (OIDC).
# 3. Cut a GitHub Release.
#
# Auth: npm Trusted Publisher (OIDC). No NPM_TOKEN secret. The binding on
# npmjs.com requires publishes from this repo, this workflow file, and the
# `production` environment — all three must match.
name: release
on:
push:
tags:
- 'v*.*.*'
permissions:
contents: write # for creating the GitHub Release
id-token: write # for npm publish --provenance (OIDC attestation)
jobs:
release:
runs-on: ubuntu-latest
# The `production` environment gates the publish step behind a required
# reviewer (configured in repo Settings → Environments). A pushed tag
# won't ship to npm until a human approves the deployment.
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # full history + tags so git-cliff can build release notes
- name: Resolve version from tag
id: ver
run: |
tag="${GITHUB_REF_NAME}"
version="${tag#v}"
file_version="$(tr -d '[:space:]' < VERSION)"
if [[ "$version" != "$file_version" ]]; then
echo "::error::Tag ($version) does not match VERSION file ($file_version). Bump VERSION + run scripts/release/sync-version.sh before tagging." >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Releasing $version"
- name: Verify derived files are in sync
run: bash scripts/release/sync-version.sh --check
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '22'
# Intentionally NOT setting `registry-url` — that injects a
# `_authToken=${NODE_AUTH_TOKEN}` line into a generated .npmrc and
# sets NODE_AUTH_TOKEN to a placeholder. npm then sees a non-empty
# token and skips the Trusted Publisher OIDC exchange, ending in a
# 404 from registry.npmjs.org. With no registry-url, npm uses its
# default registry and falls through to OIDC.
- name: Upgrade npm for Trusted Publisher OIDC
# npm CLI >= 11.5.1 picks up the GitHub OIDC token automatically and
# exchanges it with npmjs.com. Node 22's bundled npm (10.x) doesn't.
run: |
npm install -g npm@latest
hash -r
npm --version
- name: Publish to npm
env:
VERSION: ${{ steps.ver.outputs.version }}
run: |
published=$(npm view "agmsg@$VERSION" version 2>/dev/null || true)
if [[ "$published" == "$VERSION" ]]; then
echo "agmsg@$VERSION already on npm — skipping publish"
exit 0
fi
# --provenance attaches a signed GitHub attestation linking the
# tarball to this workflow run. No NODE_AUTH_TOKEN — auth comes
# from the Trusted Publisher OIDC exchange.
npm publish --access public --provenance
# Build release notes for this tag from Conventional Commits, matching
# the CHANGELOG.md format. `--latest` emits only the newest release's
# section; `--strip header` drops the file preamble.
- name: Generate release notes
uses: orhun/git-cliff-action@v4.8.0
id: cliff
with:
config: cliff.toml
args: --latest --strip header
env:
OUTPUT: RELEASE_NOTES.md
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.ver.outputs.version }}
run: |
if gh release view "v$VERSION" >/dev/null 2>&1; then
echo "Release v$VERSION already exists — skipping"
exit 0
fi
# Fall back to a minimal note if git-cliff produced nothing.
if [ ! -s RELEASE_NOTES.md ]; then
echo "Release $VERSION. See [commit history](../../commits/v$VERSION) for details." > RELEASE_NOTES.md
fi
gh release create "v$VERSION" \
--title "v$VERSION" \
--notes-file RELEASE_NOTES.md