From 7bf708560b3193904f0e293d5a887b446fc7a769 Mon Sep 17 00:00:00 2001 From: lolodawit Date: Wed, 9 Sep 2026 12:03:00 -0700 Subject: [PATCH] Cut releases from the Actions tab Releasing still meant knowing the next version, tagging by hand and getting the tag format right, which is three chances to be wrong about something a script can work out. Actions -> Release -> Run workflow now offers major, minor or patch, patch being the default. The workflow reads the last tag, works out the next version, runs both suites, tags the commit and publishes. - Version sorting is version-aware, so v10.0.0 wins over v2.9.9 rather than losing to it alphabetically, and v2.9.9 as a minor gives v2.10.0. - It refuses to run from anywhere but main, and refuses to reuse a version that already exists. - The chosen version appears in the run summary before anything is pushed. The tag-push trigger is gone: two routes to a release is one more than is useful, and a tag pushed by a workflow does not trigger workflows anyway, so it would have been a trap. --- .github/workflows/release.yml | 77 ++++++++++++++++++++++++++++++----- CONTRIBUTING.md | 19 +++++---- README.md | 9 ++-- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a96ac9b..db434fb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,21 @@ -# Publishes a release when a version tag is pushed. +# Cuts a release from the Actions tab. # -# The notes are generated from the commits and pull requests since the last -# tag, so a release describes what actually changed rather than what someone -# remembered to write down. +# Actions -> Release -> Run workflow, choose major, minor or patch. The +# workflow works out the next version from the last tag, runs both test +# suites, tags the commit and publishes. Nothing is tagged by hand. name: Release on: - push: - tags: - - 'v*' + workflow_dispatch: + inputs: + bump: + description: Which part of the version to increase + type: choice + options: + - patch + - minor + - major + default: patch permissions: contents: write @@ -19,8 +26,16 @@ jobs: steps: - uses: actions/checkout@v4 with: + # Tags and full history: the next version comes from the last tag, + # and the notes come from the commits since it. fetch-depth: 0 + - name: Refuse to release from anywhere but main + if: github.ref_name != 'main' + run: | + echo "::error::Releases are cut from main, not ${{ github.ref_name }}." + exit 1 + - uses: actions/setup-java@v4 with: distribution: temurin @@ -41,13 +56,53 @@ jobs: - run: npm test working-directory: web - # --generate-notes works out the previous tag on its own, and copes - # with there not being one. + - name: Work out the next version + id: version + run: | + set -euo pipefail + previous=$(git tag -l 'v*' --sort=-v:refname | head -1) + + if [ -z "$previous" ]; then + next="v0.1.0" + else + IFS=. read -r major minor patch <<< "${previous#v}" + case "${{ inputs.bump }}" in + major) major=$((major + 1)); minor=0; patch=0 ;; + minor) minor=$((minor + 1)); patch=0 ;; + patch) patch=$((patch + 1)) ;; + esac + next="v${major}.${minor}.${patch}" + fi + + if git rev-parse -q --verify "refs/tags/$next" >/dev/null; then + echo "::error::$next already exists." + exit 1 + fi + + echo "next=$next" >> "$GITHUB_OUTPUT" + echo "previous=${previous:-none}" >> "$GITHUB_OUTPUT" + echo "### $next" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "A ${{ inputs.bump }} release, up from ${previous:-no previous tag}." \ + >> "$GITHUB_STEP_SUMMARY" + + - name: Tag it + env: + NEXT: ${{ steps.version.outputs.next }} + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "$NEXT" -m "$NEXT" + git push origin "$NEXT" + - name: Publish env: GH_TOKEN: ${{ github.token }} + NEXT: ${{ steps.version.outputs.next }} run: | - gh release create "$GITHUB_REF_NAME" \ - --title "$GITHUB_REF_NAME" \ + set -euo pipefail + gh release create "$NEXT" \ + --title "$NEXT" \ --generate-notes \ --verify-tag diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1751fe2..270bedd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,14 +84,19 @@ easier to argue for than changes that add controls. ## Releasing -Tag a commit on `main` and push the tag. Everything else is automatic: +Go to **Actions → Release → Run workflow** and pick which part of the version to +increase. `patch` is the default. -```bash -git tag -a v2.1.0 -m "v2.1.0" -git push origin v2.1.0 -``` +| Choice | v2.4.1 becomes | +|---|---| +| `patch` | v2.4.2 | +| `minor` | v2.5.0 | +| `major` | v3.0.0 | + +The workflow reads the last tag, works out the next version, runs both test +suites, tags the commit and publishes. It refuses to run from anywhere but +`main`, and refuses to reuse a version that already exists. -The release workflow runs both test suites, and publishes only if they pass. The notes are generated from the commits and pull requests since the previous tag, so they describe what actually changed rather than what someone remembered -to write down. +to write down. There is no need to tag anything by hand. diff --git a/README.md b/README.md index 7fda9bb..cd3adf7 100644 --- a/README.md +++ b/README.md @@ -149,9 +149,12 @@ their children without overlapping anything. ## Releasing -Push a tag on `main` — `git tag -a v2.1.0 -m "v2.1.0" && git push origin v2.1.0`. -The workflow runs both suites and publishes only if they pass, with notes -generated from the commits since the previous tag. +**Actions → Release → Run workflow**, and choose `patch`, `minor` or `major`. + +The workflow works out the next version from the last tag, runs both test +suites, tags the commit and publishes — with notes generated from the commits +since the previous tag. Nothing is tagged by hand, and nothing is released that +does not pass. ## License