From a637f1838cd9f90d200d3ac5ca910e9a2922b5ae Mon Sep 17 00:00:00 2001 From: TiM Date: Thu, 18 Jun 2026 20:57:32 +1200 Subject: [PATCH] chore: create a GitHub Release on every tagged publish Make a GitHub Release part of the default release flow instead of a manual, forgettable step. After a successful PyPI publish, the workflow creates a Release for the tag with notes auto-generated from the merged PRs since the previous tag (idempotent: skips if the release already exists). Grant the publish job contents: write for this, and document it in RELEASING.md. --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/publish.yml | 33 ++++++++++++++++++++++++++------- RELEASING.md | 7 +++++++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f204ee3..84a50c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,10 +14,10 @@ jobs: name: Lint and type-check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-python@v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" - name: Install @@ -39,10 +39,10 @@ jobs: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-python@v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} - name: Install diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bd50f6d..b297422 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,10 +13,10 @@ jobs: name: Lint and type-check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-python@v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" - name: Install @@ -38,10 +38,10 @@ jobs: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-python@v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} - name: Install @@ -61,13 +61,14 @@ jobs: environment: pypi permissions: id-token: write # OIDC trusted publishing; no API token needed + contents: write # create the GitHub Release for the tag steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: # hatch-vcs needs the tag (and history) to derive the version. fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" - name: Install build tooling @@ -79,4 +80,22 @@ jobs: - name: Check distribution metadata run: twine check dist/* - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + - name: Create GitHub Release + # Runs only after a successful publish. Notes are auto-generated from the + # merged PRs since the previous tag; edit the release afterwards to add + # highlights. Idempotent: skips if the release already exists. + env: + GH_TOKEN: ${{ github.token }} + run: | + tag="${GITHUB_REF_NAME}" + if gh release view "$tag" >/dev/null 2>&1; then + echo "Release $tag already exists; skipping." + else + gh release create "$tag" --verify-tag --generate-notes || { + # Tolerate a concurrent run that created the release first. + gh release view "$tag" >/dev/null 2>&1 \ + && echo "Release $tag was created concurrently; skipping." \ + || exit 1 + } + fi diff --git a/RELEASING.md b/RELEASING.md index 6e8a404..8107317 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -29,6 +29,13 @@ Pushing the tag triggers `.github/workflows/publish.yml`, which: 2. Builds the sdist and wheel (`python -m build`); the version reflects the tag. 3. Runs `twine check dist/*`. 4. Publishes to PyPI via OIDC trusted publishing (no API token). +5. Creates a GitHub Release for the tag with notes auto-generated from the merged + PRs since the previous tag. + +A GitHub Release is part of every release, not an optional extra: the workflow +creates one automatically (step 5). After the run, edit the release on GitHub to +add a short highlights section if the auto-generated PR list needs framing. For +the very first release (no prior tag to diff against), write the notes by hand. ## One-time prerequisites