Skip to content

Release

Release #4

Workflow file for this run

name: Release
# Manual, one-button release. Pick a version (a semver bump keyword or an exact
# version), and this workflow: runs the full CI gate, bumps package.json,
# writes the changelog from the commits since the last tag, commits + tags,
# publishes to public npm, and cuts a GitHub Release whose body is that
# changelog. Run `dry_run: true` first to preview without pushing anything.
#
# Publishing uses npm Trusted Publishing (OIDC) — no NPM_TOKEN secret. Configure
# a trusted publisher for @copse/streaming-markdown at npmjs.com (Settings ->
# Trusted Publisher) pointing at this repo + workflow file (release.yml). The
# package must exist first, so the initial publish is a one-time manual
# `npm publish` (interactive 2FA); every release after that flows through here.
on:
workflow_dispatch:
inputs:
bump:
description: 'Semver bump to apply (ignored when "exact version" is set below)'
required: true
default: patch
type: choice
options:
- patch
- minor
- major
- prepatch
- preminor
- premajor
- prerelease
exact_version:
description: 'Optional: exact version to release (e.g. 1.2.0). Overrides the bump dropdown when set.'
required: false
default: ''
type: string
prerelease:
description: 'Mark the GitHub Release as a pre-release'
required: false
default: false
type: boolean
dry_run:
description: 'Build the release notes and version bump but do not commit, tag, publish, or release'
required: false
default: false
type: boolean
# Least privilege: write to the repo (commit + tag + release). id-token: write
# lets the runner mint the GitHub OIDC token that npm Trusted Publishing uses to
# authenticate the publish (and to attach provenance automatically).
permissions:
contents: write
id-token: write
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# Full history + tags so the changelog can diff against the last tag.
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
registry-url: https://registry.npmjs.org
scope: '@copse'
- run: npm ci
# Release gate: never cut a release from a red tree.
- name: Typecheck
run: npm run typecheck
- name: Test
run: npm test
- name: Build
run: npm run build
- name: Normalizer parity
run: npm run check:normalizer-parity
- name: Resolve version
id: version
run: |
set -euo pipefail
# Prefer the free-text exact version when provided; otherwise use the
# bump keyword from the dropdown. `npm version` accepts both forms and
# prints the resulting `vX.Y.Z`. --no-git-tag-version edits
# package.json + package-lock.json without tagging (we tag ourselves).
TARGET="${{ inputs.exact_version || inputs.bump }}"
TAG=$(npm version "$TARGET" --no-git-tag-version)
VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Releasing $TAG"
- name: Generate changelog
run: npx tsx scripts/gen-changelog.mts "${{ steps.version.outputs.version }}" > RELEASE_NOTES.md
- name: Update CHANGELOG.md
run: |
set -euo pipefail
DATE=$(date -u +%Y-%m-%d)
npx tsx scripts/update-changelog.mts "${{ steps.version.outputs.version }}" "$DATE" RELEASE_NOTES.md
- name: Preview release notes
run: |
{
echo "## Release ${{ steps.version.outputs.tag }}"
echo
cat RELEASE_NOTES.md
} >> "$GITHUB_STEP_SUMMARY"
- name: Commit and tag
if: ${{ !inputs.dry_run }}
run: |
set -euo pipefail
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add package.json package-lock.json CHANGELOG.md
git commit -m "chore(release): ${{ steps.version.outputs.tag }}"
git tag -a "${{ steps.version.outputs.tag }}" -m "${{ steps.version.outputs.tag }}"
# Publish BEFORE pushing so a failed publish (e.g. bad token) leaves main
# and the tag list untouched — the commit + tag exist only locally on the
# runner and vanish with it, leaving nothing to clean up before a retry.
- name: Publish to npm
if: ${{ !inputs.dry_run }}
run: |
set -euo pipefail
# Trusted Publishing authenticates via the GitHub OIDC token (see the
# id-token permission above) — no NPM_TOKEN needed. It requires npm
# >= 11.5.1; the Node 22 runner ships npm 10, so upgrade first.
npm install -g npm@latest
# publishConfig pins the public registry + `--access public`, and
# provenance is attached automatically under trusted publishing.
npm publish
- name: Push commit and tag
if: ${{ !inputs.dry_run }}
run: |
set -euo pipefail
git push origin HEAD
git push origin "${{ steps.version.outputs.tag }}"
- name: Create GitHub Release
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
gh release create "${{ steps.version.outputs.tag }}" \
--title "${{ steps.version.outputs.tag }}" \
--notes-file RELEASE_NOTES.md \
${{ inputs.prerelease && '--prerelease' || '' }}