From af6679cca08c073e8b0698cfdd8a7b142ff2b91e Mon Sep 17 00:00:00 2001 From: Albert Mavashev Date: Thu, 7 May 2026 06:33:45 -0400 Subject: [PATCH] ci(release): auto-create GitHub Release after PyPI publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `create-release` job to `python-publish.yml` that runs after `publish-to-pypi` succeeds and creates a GitHub Release with body extracted from the corresponding CHANGELOG.md section. Eliminates the manual `gh release create` step every release. The recent v0.2.1 release exposed this gap — PyPI got the new package but no GitHub Release was created automatically; the release was backfilled by hand. Mirrors the same pattern shipped in cycles-client-python PR #57: - Triggered by tag push only (not manual workflow_dispatch republishes). - Extracts the CHANGELOG section between `## [VERSION]` and the next `## [` heading using a portable string-based awk script (no regex escaping issues across awk variants). - If no CHANGELOG entry is found for the version, falls back to a generic "see commit history" body — does not fail the job. - Title is the tag name (e.g. `v0.2.2`); descriptive suffix on prior releases is convention, the user can edit titles after the fact. - `prerelease: ${{ contains(github.ref_name, '-') }}` flags any tag with a hyphen (e.g. `v0.3.0-rc.1`) as prerelease automatically. Pinned action SHAs match other repos in the org: - softprops/action-gh-release@b430933... (v3.0.0) - actions/checkout@de0fac2... (v6, matches existing job) Tested locally — extraction produces clean body for v0.2.1 (no heading bleed, no next-version boundary). After this lands, future releases collapse to one step: `git tag vX.Y.Z && git push origin vX.Y.Z`. PyPI publish + GitHub Release both happen automatically. --- .github/workflows/python-publish.yml | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 7125d85..1b0b030 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -92,3 +92,45 @@ jobs: uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 with: skip-existing: true + + create-release: + name: Create GitHub Release + needs: publish-to-pypi + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + + steps: + - name: Check out source + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Extract release notes from CHANGELOG + id: notes + run: | + version="${GITHUB_REF_NAME#v}" + # Extract the section between this version's heading and the next "## [" heading. + # Uses string functions instead of regex to stay portable across awk variants. + notes=$(awk -v v="$version" ' + BEGIN { start = "## [" v "]"; flag = 0 } + substr($0, 1, length(start)) == start { flag = 1; next } + substr($0, 1, 4) == "## [" { flag = 0 } + flag { print } + ' CHANGELOG.md) + if [ -z "$(printf '%s' "$notes" | tr -d '[:space:]')" ]; then + echo "::warning::No CHANGELOG entry found for v${version} — using fallback body" + notes="Release v${version}. See commit history for changes." + fi + { + echo "notes<> "$GITHUB_OUTPUT" + + - name: Create GitHub Release + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + with: + name: ${{ github.ref_name }} + body: ${{ steps.notes.outputs.notes }} + draft: false + prerelease: ${{ contains(github.ref_name, '-') }}