Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 90 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,99 @@ jobs:
runs-on: ubuntu-latest
needs: pypi-publish
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Generate release notes from commit messages
run: |
python3 - <<'EOF'
import json, os, re, subprocess, urllib.request

tag = os.environ["TAG_NAME"]
repo = os.environ["REPO"]
token = os.environ["GH_TOKEN"]

# Determine previous tag (version-sorted, so patch tags compare correctly)
all_tags = subprocess.run(
["git", "tag", "--sort=-version:refname"],
capture_output=True, text=True, check=True,
).stdout.strip().splitlines()
all_tags = [t for t in all_tags if t]

try:
prev_tag = all_tags[all_tags.index(tag) + 1]
except (ValueError, IndexError):
prev_tag = None

log_range = f"{prev_tag}..{tag}" if prev_tag else tag
subjects = subprocess.run(
["git", "log", "--format=%s", log_range],
capture_output=True, text=True, check=True,
).stdout.strip().splitlines()

# Extract unique PR numbers preserving order (squash-merge: "(#NNN)")
seen, pr_numbers = set(), []
for subject in subjects:
for num in re.findall(r'\(#(\d+)\)', subject):
if num not in seen:
pr_numbers.append(int(num))
seen.add(num)

def gh_get(url):
req = urllib.request.Request(
url,
headers={"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"},
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())

# Category config mirrors .github/release.yml
label_to_category = {
"enhancement": "New Features 🎉",
"breaking-change": "Breaking Changes 🛠",
"bug": "Bug Fix 🐛",
}
ignore_labels = {"ignore-for-release"}
category_order = ["New Features 🎉", "Breaking Changes 🛠", "Bug Fix 🐛", "Other Changes"]

prs_by_category: dict = {}

for pr_num in pr_numbers:
pr = gh_get(f"https://api.github.com/repos/{repo}/pulls/{pr_num}")
labels = {lbl["name"] for lbl in pr.get("labels", [])}
if labels & ignore_labels:
continue
category = next(
(cat for lbl, cat in label_to_category.items() if lbl in labels),
"Other Changes",
)
prs_by_category.setdefault(category, []).append(pr)

lines = ["## What's Changed"]
for cat in category_order:
if cat not in prs_by_category:
continue
lines.append(f"### {cat}")
for pr in prs_by_category[cat]:
lines.append(f"* {pr['title']} by @{pr['user']['login']} in {pr['html_url']}")

if prev_tag:
lines += ["", f"**Full Changelog**: https://github.com/{repo}/compare/{prev_tag}...{tag}"]

notes = "\n".join(lines) + "\n"
print(notes)
open("release_notes.md", "w").write(notes)
EOF
env:
TAG_NAME: ${{ inputs.custom_version || github.ref_name }}
REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GH_RELEASE_PAT }}

- name: Publish release on GitHub
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
body_path: release_notes.md
tag_name: ${{ inputs.custom_version || github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.GH_RELEASE_PAT }}
Loading