Skip to content

Commit 2a7a771

Browse files
committed
ci(release): auto-create GitHub Release after PyPI publish
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. Previously, v0.4.0 → v0.4.1 each required a separate manual release-creation pass after the tag-triggered PyPI publish. Behavior: - 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.4.1`); the descriptive suffix on prior releases ("v0.4.0 — Dynamic subject and action fields...") is convention, not required, and the user can edit titles after the fact. - `prerelease: ${{ contains(github.ref_name, '-') }}` flags any tag with a hyphen (e.g. `v0.5.0-rc.1`) as prerelease automatically. Pinned action SHAs: - softprops/action-gh-release@b430933... (v3.0.0) - actions/checkout@de0fac2... (v6, matches existing job) Permissions: - `contents: write` on this job only; default `contents: read` preserved for the workflow-level minimum. Tested locally: - Successful extraction for v0.4.1 produces clean body (CHANGELOG section without heading or next-version boundary). - Missing version (e.g. v9.9.9) takes the fallback path without erroring. This means future releases will be one-step: `git tag vX.Y.Z` + `git push origin vX.Y.Z` and the workflow handles both PyPI publish and GitHub Release creation.
1 parent db0a064 commit 2a7a771

1 file changed

Lines changed: 43 additions & 1 deletion

File tree

.github/workflows/python-publish.yml

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,46 @@ jobs:
8888
path: dist/
8989

9090
- name: Publish to PyPI
91-
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
91+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
92+
93+
create-release:
94+
name: Create GitHub Release
95+
needs: publish-to-pypi
96+
runs-on: ubuntu-latest
97+
if: startsWith(github.ref, 'refs/tags/v')
98+
permissions:
99+
contents: write
100+
101+
steps:
102+
- name: Check out source
103+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
104+
105+
- name: Extract release notes from CHANGELOG
106+
id: notes
107+
run: |
108+
version="${GITHUB_REF_NAME#v}"
109+
# Extract the section between this version's heading and the next "## [" heading.
110+
# Uses string functions instead of regex to stay portable across awk variants.
111+
notes=$(awk -v v="$version" '
112+
BEGIN { start = "## [" v "]"; flag = 0 }
113+
substr($0, 1, length(start)) == start { flag = 1; next }
114+
substr($0, 1, 4) == "## [" { flag = 0 }
115+
flag { print }
116+
' CHANGELOG.md)
117+
if [ -z "$(printf '%s' "$notes" | tr -d '[:space:]')" ]; then
118+
echo "::warning::No CHANGELOG entry found for v${version} — using fallback body"
119+
notes="Release v${version}. See commit history for changes."
120+
fi
121+
{
122+
echo "notes<<EOF_GH_OUTPUT"
123+
printf '%s\n' "$notes"
124+
echo "EOF_GH_OUTPUT"
125+
} >> "$GITHUB_OUTPUT"
126+
127+
- name: Create GitHub Release
128+
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
129+
with:
130+
name: ${{ github.ref_name }}
131+
body: ${{ steps.notes.outputs.notes }}
132+
draft: false
133+
prerelease: ${{ contains(github.ref_name, '-') }}

0 commit comments

Comments
 (0)