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