Skip to content

Merge pull request #17 from slate-rehm/dev #9

Merge pull request #17 from slate-rehm/dev

Merge pull request #17 from slate-rehm/dev #9

Workflow file for this run

name: Release
# Production releases only, and only from master. Work reaches master by being
# merged dev -> master, so anything published here has already been through CI
# on a PR into dev and again on the promotion PR.
#
# This project is not published to any registry. The release artifact is the npm
# tarball attached to the GitHub Release, which users install by URL:
#
# npm i -g https://github.com/slate-rehm/knapper/releases/download/v0.1.0/knapper-0.1.0.tgz
#
# The version bump itself is an ordinary change: bump it in a PR into dev, then
# promote dev -> master. This workflow only tags and releases what is already on
# master, so it never has to write to a protected branch.
#
# Two ways in, both equivalent:
#
# 1. Push a tag — `npm version minor` on a dev PR, promote, then
# `git tag v0.2.0 && git push origin v0.2.0`.
# 2. Run this workflow from the Actions tab — it tags master's current HEAD with
# the version already in package.json and publishes that.
on:
push:
tags: ["v*.*.*"]
workflow_dispatch:
inputs:
dry_run:
description: "Build and validate, but do not publish or tag"
required: false
default: false
type: boolean
concurrency:
group: release
cancel-in-progress: false
permissions:
contents: read
jobs:
# Reuse the exact CI gates rather than a weaker copy of them.
verify:
uses: ./.github/workflows/ci.yml
release:
name: release
runs-on: ubuntu-latest
needs: verify
environment: release
permissions:
contents: write # create the tag and the GitHub Release
outputs:
version: ${{ steps.resolve.outputs.version }}
steps:
- uses: actions/checkout@v7
with:
# Full history so the master-ancestry check below can resolve.
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && 'master' || github.ref }}
- name: Refuse to release anything that is not on master
run: |
git fetch origin master --depth=1
if ! git merge-base --is-ancestor HEAD origin/master; then
echo "::error::this commit is not on master; promote dev -> master first"
exit 1
fi
- uses: actions/setup-node@v7
with:
node-version: "22"
cache: npm
- run: npm ci
- name: Tag master at the version already in package.json
if: github.event_name == 'workflow_dispatch'
run: |
VERSION=$(node -p "require('./package.json').version")
if git ls-remote --exit-code --tags origin "refs/tags/v$VERSION" >/dev/null 2>&1; then
echo "::error::v$VERSION is already tagged. Bump the version in a PR into dev first."
exit 1
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag "v$VERSION"
if [ "${{ inputs.dry_run }}" != "true" ]; then
# A tag is not a branch push, so this works against protected master.
git push origin "v$VERSION"
else
echo "dry run: not pushing v$VERSION"
fi
- name: Resolve and validate the release version
id: resolve
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if [ "${{ github.event_name }}" = "push" ]; then
TAG="${GITHUB_REF#refs/tags/}"
if [ "$TAG" != "v$VERSION" ]; then
echo "::error::tag $TAG does not match package.json version $VERSION"
exit 1
fi
fi
echo "Releasing v$VERSION"
- name: Manifests agree with package.json
run: node scripts/sync-version.mjs --check
- name: Build
run: npm run build
- name: Degraded-mode smoke test on the build
run: node scripts/ci-smoke.mjs
# The tarball attached below is the only distribution artifact, so packing it
# is a release gate rather than a convenience: if `npm pack` fails, no user
# can install this version.
- name: Pack the release artifact
id: pack
run: |
npm pack --json > pack.json
echo "tarball=$(node -p "require('./pack.json')[0].filename")" >> "$GITHUB_OUTPUT"
- name: Create the GitHub Release
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.resolve.outputs.version }}"
gh release create "v$VERSION" \
--title "v$VERSION" \
--generate-notes \
--verify-tag \
"${{ steps.pack.outputs.tarball }}"