Skip to content

Commit a2a3659

Browse files
Automate release notes (#1523)
* add workflow that updates repository RELEASENOTES
1 parent bdc2bcb commit a2a3659

2 files changed

Lines changed: 154 additions & 1 deletion

File tree

.github/workflows/test.yml

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
name: Update RELEASENOTES.md
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
branches: [main]
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
update-releasenotes:
13+
if: >-
14+
github.event.pull_request.merged == true &&
15+
!contains(github.event.pull_request.labels.*.name, 'skip-changelog')
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
with:
20+
ref: main
21+
fetch-depth: 0
22+
23+
- name: Compute next patch version
24+
id: version
25+
shell: bash
26+
run: |
27+
FILE="RELEASENOTES.md"
28+
LATEST=$(grep -oP '(?<=^# NuGet Version )\d+\.\d+\.\d+' "$FILE" | head -1)
29+
if [ -z "$LATEST" ]; then
30+
echo "::error::Could not find any version heading in RELEASENOTES.md"
31+
exit 1
32+
fi
33+
MAJOR=$(echo "$LATEST" | cut -d. -f1)
34+
MINOR=$(echo "$LATEST" | cut -d. -f2)
35+
PATCH=$(echo "$LATEST" | cut -d. -f3)
36+
NEXT_PATCH=$((PATCH + 1))
37+
NEXT_VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}"
38+
echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
39+
echo "next=$NEXT_VERSION" >> "$GITHUB_OUTPUT"
40+
echo "Detected latest version: $LATEST → next: $NEXT_VERSION"
41+
42+
- name: Determine category from labels
43+
id: category
44+
shell: bash
45+
run: |
46+
LABELS='${{ toJson(github.event.pull_request.labels.*.name) }}'
47+
if echo "$LABELS" | grep -qiE '"breaking-change"'; then
48+
echo "section=__Breaking Changes__" >> "$GITHUB_OUTPUT"
49+
elif echo "$LABELS" | grep -qiE '"bug"|"fix"'; then
50+
echo "section=__Bug Fixes__" >> "$GITHUB_OUTPUT"
51+
elif echo "$LABELS" | grep -qiE '"enhancement"|"api-change"|"Missing Feature"'; then
52+
echo "section=__API Changes__" >> "$GITHUB_OUTPUT"
53+
elif echo "$LABELS" | grep -qiE '"build"|"ci"|"infra"'; then
54+
echo "section=__Build/Infrastructure__" >> "$GITHUB_OUTPUT"
55+
elif echo "$LABELS" | grep -qiE '"issue-fixed"'; then
56+
echo "section=__Issues Fixed__" >> "$GITHUB_OUTPUT"
57+
else
58+
echo "section=__Other Changes__" >> "$GITHUB_OUTPUT"
59+
fi
60+
61+
- name: Update RELEASENOTES.md
62+
shell: bash
63+
env:
64+
PR_NUMBER: ${{ github.event.pull_request.number }}
65+
PR_TITLE: ${{ github.event.pull_request.title }}
66+
SECTION: ${{ steps.category.outputs.section }}
67+
NEXT_VERSION: ${{ steps.version.outputs.next }}
68+
run: |
69+
FILE="RELEASENOTES.md"
70+
ENTRY="#${PR_NUMBER} ${PR_TITLE}<br/>"
71+
VERSION_HEADER="# NuGet Version ${NEXT_VERSION}"
72+
73+
python3 - "$FILE" "$VERSION_HEADER" "$SECTION" "$ENTRY" <<'PYEOF'
74+
import sys
75+
76+
file_path, ver_header, section, entry = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
77+
78+
with open(file_path, 'r') as f:
79+
content = f.read()
80+
lines = content.splitlines(keepends=True)
81+
82+
# Step 1: If the next version heading doesn't exist, insert it before the first existing one
83+
if ver_header not in content:
84+
result = []
85+
inserted = False
86+
for line in lines:
87+
if not inserted and line.startswith('# NuGet Version '):
88+
result.append(ver_header + '\n')
89+
result.append('\n')
90+
inserted = True
91+
result.append(line)
92+
if not inserted:
93+
result.append('\n' + ver_header + '\n\n')
94+
lines = result
95+
96+
# Step 2: Find the version block and insert the entry
97+
result = []
98+
in_version_block = False
99+
section_found = False
100+
inserted = False
101+
102+
for i, line in enumerate(lines):
103+
stripped = line.rstrip('\n')
104+
105+
if stripped == ver_header:
106+
in_version_block = True
107+
result.append(line)
108+
continue
109+
110+
if in_version_block and stripped.startswith('# NuGet Version ') and stripped != ver_header:
111+
# Reached next version block without finding/creating section
112+
if not inserted:
113+
result.append('\n' + section + ':\n\n' + entry + '\n')
114+
inserted = True
115+
in_version_block = False
116+
result.append(line)
117+
continue
118+
119+
if in_version_block and not inserted and stripped == section + ':':
120+
section_found = True
121+
result.append(line)
122+
continue
123+
124+
if section_found and not inserted:
125+
# Insert after the blank line following the section heading
126+
if stripped == '':
127+
result.append(line)
128+
result.append(entry + '\n')
129+
inserted = True
130+
continue
131+
132+
result.append(line)
133+
134+
# If we reached EOF still inside the version block
135+
if in_version_block and not inserted:
136+
result.append('\n' + section + ':\n\n' + entry + '\n')
137+
138+
with open(file_path, 'w') as f:
139+
f.writelines(result)
140+
PYEOF
141+
142+
- name: Commit and push
143+
shell: bash
144+
run: |
145+
git config user.name "github-actions[bot]"
146+
git config user.email "github-actions[bot]@users.noreply.github.com"
147+
git add RELEASENOTES.md
148+
if git diff --cached --quiet; then
149+
echo "No changes to commit"
150+
else
151+
git commit -m "docs: add PR #${{ github.event.pull_request.number }} to RELEASENOTES.md [skip ci]"
152+
git push
153+
fi

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ If you send us a PR, whether for documentation, examples, or library code, we re
2525
* **DO** refer to any relevant issues, and include [keywords](https://help.github.com/articles/closing-issues-via-commit-messages/) that automatically close issues when the PR is merged.
2626
* **DO** tag any users that should know about and/or review the change.
2727
* **DO** ensure each commit successfully builds. The entire PR must pass all tests in the Continuous Integration (CI) system before it'll be merged.
28-
* **DO** add a brief description to the RELEASENOTES.md file at the top under the heading of the upcoming release.
28+
* **DO** label your PR so it appears in the correct release-note category (see the PR template for the label table). Release notes are generated automatically — you do not need to edit `RELEASENOTES.md`.
2929
* **DO** address PR feedback in an additional commit(s) rather than amending the existing commits, and only rebase/squash them when necessary. This makes it easier for reviewers to track changes.
3030
* **DO** assume that ["Squash and Merge"](https://github.com/blog/2141-squash-your-commits) will be used to merge your commit unless you request otherwise in the PR.
3131
* **DO NOT** fix merge conflicts using a merge commit. Prefer `git rebase`.

0 commit comments

Comments
 (0)