Skip to content

Commit cafde72

Browse files
authored
chore: add version check & changelog generation to manual-release (#1756)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Automated release-notes generation with layered fallbacks (use provided notes, extract from changelog, generate from previous release, call provider APIs, or default message). * New version-validation step to ensure package versions are consistent before publishing. * **Chores** * Moved release-notes logic into a reusable workflow and rewired the manual release process to consume its outputs for more consistent releases. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 2b481c3 commit cafde72

2 files changed

Lines changed: 335 additions & 33 deletions

File tree

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
name: Generate Release Notes
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
version:
7+
description: 'Version number (e.g., 4.25.3)'
8+
required: true
9+
type: string
10+
target_commitish:
11+
description: 'Commit SHA or branch (leave empty for current HEAD)'
12+
required: false
13+
type: string
14+
release_notes:
15+
description: 'Custom release notes (leave empty to auto-generate)'
16+
required: false
17+
type: string
18+
outputs:
19+
release_notes:
20+
description: 'Generated or provided release notes'
21+
value: ${{ jobs.generate.outputs.release_notes }}
22+
secrets:
23+
UNRAID_BOT_GITHUB_ADMIN_TOKEN:
24+
required: true
25+
26+
jobs:
27+
generate:
28+
name: Generate Release Notes
29+
runs-on: ubuntu-latest
30+
outputs:
31+
release_notes: ${{ steps.generate_notes.outputs.release_notes }}
32+
steps:
33+
- name: Checkout repo
34+
uses: actions/checkout@v5
35+
with:
36+
ref: ${{ inputs.target_commitish || github.ref }}
37+
fetch-depth: 0
38+
token: ${{ secrets.UNRAID_BOT_GITHUB_ADMIN_TOKEN }}
39+
40+
- name: Setup Node.js
41+
uses: actions/setup-node@v4
42+
with:
43+
node-version: '20'
44+
45+
- name: Generate Release Notes
46+
id: generate_notes
47+
env:
48+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49+
run: |
50+
TAG_NAME="v${{ inputs.version }}"
51+
VERSION="${{ inputs.version }}"
52+
53+
if [ -n "${{ inputs.release_notes }}" ]; then
54+
NOTES="${{ inputs.release_notes }}"
55+
else
56+
CHANGELOG_PATH="api/CHANGELOG.md"
57+
58+
if [ -f "$CHANGELOG_PATH" ]; then
59+
echo "Extracting release notes from CHANGELOG.md for version ${VERSION}"
60+
61+
NOTES=$(awk -v ver="$VERSION" '
62+
BEGIN {
63+
found=0; capture=0; output="";
64+
gsub(/\./, "\\.", ver);
65+
}
66+
/^## \[/ {
67+
if (capture) exit;
68+
if ($0 ~ "\\[" ver "\\]") {
69+
found=1;
70+
capture=1;
71+
next;
72+
}
73+
}
74+
capture && /^## \[/ { exit }
75+
capture {
76+
if (output != "") output = output "\n";
77+
output = output $0;
78+
}
79+
END {
80+
if (found) print output;
81+
else exit 1;
82+
}
83+
' "$CHANGELOG_PATH") || EXTRACTION_STATUS=$?
84+
85+
if [ ${EXTRACTION_STATUS:-0} -eq 0 ] && [ -n "$NOTES" ]; then
86+
echo "✓ Found release notes in CHANGELOG.md"
87+
else
88+
echo "⚠ Version ${VERSION} not found in CHANGELOG.md, generating with conventional-changelog"
89+
90+
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
91+
CHANGELOG_GENERATED=false
92+
93+
if [ -n "$PREV_TAG" ]; then
94+
echo "Generating changelog from ${PREV_TAG}..HEAD using conventional-changelog"
95+
96+
npm install -g conventional-changelog-cli
97+
98+
TEMP_NOTES=$(mktemp)
99+
conventional-changelog -p conventionalcommits \
100+
--release-count 1 \
101+
--output-unreleased \
102+
> "$TEMP_NOTES" 2>/dev/null || true
103+
104+
if [ -s "$TEMP_NOTES" ]; then
105+
NOTES=$(cat "$TEMP_NOTES")
106+
107+
if [ -n "$NOTES" ]; then
108+
echo "✓ Generated changelog with conventional-changelog"
109+
CHANGELOG_GENERATED=true
110+
111+
TEMP_CHANGELOG=$(mktemp)
112+
{
113+
if [ -f "$CHANGELOG_PATH" ]; then
114+
head -n 1 "$CHANGELOG_PATH"
115+
echo ""
116+
echo "$NOTES"
117+
echo ""
118+
tail -n +2 "$CHANGELOG_PATH"
119+
else
120+
echo "# Changelog"
121+
echo ""
122+
echo "$NOTES"
123+
fi
124+
} > "$TEMP_CHANGELOG"
125+
126+
mv "$TEMP_CHANGELOG" "$CHANGELOG_PATH"
127+
echo "✓ Updated CHANGELOG.md with generated notes"
128+
else
129+
echo "⚠ conventional-changelog produced empty output, using GitHub auto-generation"
130+
NOTES=$(gh api repos/${{ github.repository }}/releases/generate-notes \
131+
-f tag_name="${TAG_NAME}" \
132+
-f target_commitish="${{ inputs.target_commitish || github.sha }}" \
133+
-f previous_tag_name="${PREV_TAG}" \
134+
--jq '.body')
135+
fi
136+
else
137+
echo "⚠ conventional-changelog failed, using GitHub auto-generation"
138+
NOTES=$(gh api repos/${{ github.repository }}/releases/generate-notes \
139+
-f tag_name="${TAG_NAME}" \
140+
-f target_commitish="${{ inputs.target_commitish || github.sha }}" \
141+
-f previous_tag_name="${PREV_TAG}" \
142+
--jq '.body')
143+
fi
144+
145+
rm -f "$TEMP_NOTES"
146+
else
147+
echo "⚠ No previous tag found, using GitHub auto-generation"
148+
NOTES=$(gh api repos/${{ github.repository }}/releases/generate-notes \
149+
-f tag_name="${TAG_NAME}" \
150+
-f target_commitish="${{ inputs.target_commitish || github.sha }}" \
151+
--jq '.body' || echo "Release ${VERSION}")
152+
fi
153+
154+
if [ "$CHANGELOG_GENERATED" = true ]; then
155+
BRANCH_OR_SHA="${{ inputs.target_commitish || github.ref }}"
156+
157+
if git show-ref --verify --quiet "refs/heads/${BRANCH_OR_SHA}"; then
158+
echo ""
159+
echo "=========================================="
160+
echo "CHANGELOG GENERATED AND COMMITTED"
161+
echo "=========================================="
162+
echo ""
163+
164+
git config user.name "github-actions[bot]"
165+
git config user.email "github-actions[bot]@users.noreply.github.com"
166+
167+
BEFORE_SHA=$(git rev-parse HEAD)
168+
169+
git add "$CHANGELOG_PATH"
170+
git commit -m "chore: add changelog for version ${VERSION}"
171+
git push origin "HEAD:${BRANCH_OR_SHA}"
172+
173+
AFTER_SHA=$(git rev-parse HEAD)
174+
175+
echo "✓ Changelog committed and pushed successfully"
176+
echo ""
177+
echo "Previous SHA: ${BEFORE_SHA}"
178+
echo "New SHA: ${AFTER_SHA}"
179+
echo ""
180+
echo "⚠️ CRITICAL: A new commit was created, but github.sha is immutable."
181+
echo "⚠️ github.sha = ${BEFORE_SHA} (original workflow trigger)"
182+
echo "⚠️ The release tag must point to ${AFTER_SHA} (with changelog)"
183+
echo ""
184+
echo "Re-run this workflow to create the release with the correct commit."
185+
echo ""
186+
exit 1
187+
else
188+
echo "⚠ Target is a commit SHA, not a branch. Cannot push changelog updates."
189+
echo "Changelog was generated but not committed."
190+
fi
191+
fi
192+
fi
193+
else
194+
echo "⚠ CHANGELOG.md not found, using GitHub auto-generation"
195+
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
196+
197+
if [ -n "$PREV_TAG" ]; then
198+
NOTES=$(gh api repos/${{ github.repository }}/releases/generate-notes \
199+
-f tag_name="${TAG_NAME}" \
200+
-f target_commitish="${{ inputs.target_commitish || github.sha }}" \
201+
-f previous_tag_name="${PREV_TAG}" \
202+
--jq '.body')
203+
else
204+
NOTES="Release ${VERSION}"
205+
fi
206+
fi
207+
fi
208+
209+
echo "release_notes<<EOF" >> $GITHUB_OUTPUT
210+
echo "$NOTES" >> $GITHUB_OUTPUT
211+
echo "EOF" >> $GITHUB_OUTPUT
212+

0 commit comments

Comments
 (0)