From 63546c64791ae086383b1e1e75cdf7b711a0f385 Mon Sep 17 00:00:00 2001 From: Danielle McCool Date: Mon, 4 May 2026 09:28:11 +0200 Subject: [PATCH] chore(ci): auto-create a GitHub release on tag push Adds a workflow that fires on push of any tag matching 'v*'. It takes that version's section from the changelog (everything between the version and the one underneath) and then creates a GitHub release using the text as the body. The release title is the tag name. git push origin v.X.Y.Z is now the entire release flow. This does mean that future releases need to have '## vX.Y.Z - YYYY-MM-DD' format (although it can also be -- or an emdash, whatever, just make sure there's a proper semver and YMD format). --- .github/workflows/release.yml | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..22f443826 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,58 @@ +name: Release on tag + +# When an annotated tag matching `v*` is pushed, extract that +# version's section from CHANGELOG.md and create a GitHub release +# using it as the body. This lets `git push origin vX.Y.Z` be the +# entire release flow — no `gh` CLI, no web UI, no PAT needed +# beyond the workflow's automatic GITHUB_TOKEN. +# +# Convention: each released version has a heading of the form +# ## vX.Y.Z — YYYY-MM-DD +# in CHANGELOG.md. The workflow extracts the body between that +# heading and the next `## v[0-9]` heading. + +on: + push: + tags: + - 'v*' + +permissions: + contents: write # required to create releases + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Extract release notes from CHANGELOG + env: + # Bind the tag name (e.g. "v2.0.1") into a shell variable + # via env: rather than ${{ }} interpolation. Safer pattern + # for any field GitHub populates from a context, even when + # the field (a tag name) is not user-content-controlled. + TAG_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + version="${TAG_NAME#v}" + awk -v ver="$version" ' + $0 ~ ("^## v" ver " ") { flag = 1; next } + flag && /^## v[0-9]/ { exit } + flag { print } + ' CHANGELOG.md > /tmp/notes.md + + if [ ! -s /tmp/notes.md ]; then + echo "::error::No CHANGELOG section found for ${TAG_NAME}. Expected '## ${TAG_NAME} — ' heading in CHANGELOG.md." + exit 1 + fi + + echo "::group::Extracted notes" + cat /tmp/notes.md + echo "::endgroup::" + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + body_path: /tmp/notes.md