Skip to content

Commit 40a1d4f

Browse files
authored
feat: Add new changelog generation task and workflow. (#33)
* add new generate-changelog task * update release action to use generate-changelog
1 parent 87c7c5e commit 40a1d4f

File tree

3 files changed

+103
-31
lines changed

3 files changed

+103
-31
lines changed

.github/workflows/release.lib.yaml

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ jobs:
2828
fetch-depth: 0
2929
submodules: recursive
3030

31+
- name: test-action
32+
run: cd hack/common; git status
3133
- name: Install Task
3234
uses: arduino/setup-task@v2
3335
with:
@@ -85,36 +87,8 @@ jobs:
8587
done
8688
8789
- name: Build Changelog
88-
id: github_release
89-
uses: mikepenz/release-changelog-builder-action@5fb6e51e44d4aea73f66549f425aa3ed5008109e # v5
90-
with:
91-
mode: "PR"
92-
configurationJson: |
93-
{
94-
"template": "#{{CHANGELOG}}",
95-
"pr_template": "- #{{TITLE}}: ##{{NUMBER}}",
96-
"categories": [
97-
{
98-
"title": "## Feature",
99-
"labels": ["feat", "feature"]
100-
},
101-
{
102-
"title": "## Fix",
103-
"labels": ["fix", "bug"]
104-
},
105-
{
106-
"title": "## Other",
107-
"labels": []
108-
}
109-
],
110-
"label_extractor": [
111-
{
112-
"pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\\([\\w\\-\\.]+\\))?(!)?: ([\\w ])+([\\s\\S]*)",
113-
"on_property": "title",
114-
"target": "$1"
115-
}
116-
]
117-
}
90+
id: github_release
91+
run: task r:generate-changelog
11892
env:
11993
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12094

@@ -124,7 +98,7 @@ jobs:
12498
with:
12599
tag_name: ${{ env.version }}
126100
name: Release ${{ env.version }}
127-
body: ${{steps.github_release.outputs.changelog}}
101+
body: CHANGELOG.md
128102
draft: true
129103
prerelease: false
130104
env:

generate-changelog.sh

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
# Ensure gh CLI available
6+
if ! command -v gh &> /dev/null; then
7+
echo "gh CLI is required but not installed." >&2
8+
exit 1
9+
fi
10+
11+
cd $(dirname "$0")/../../
12+
13+
LATEST_RELEASE_TAG=$(gh release list --json tagName,isLatest --jq '.[] | select(.isLatest)|.tagName')
14+
if [[ -e $LATEST_RELEASE_TAG ]]; then # first release?
15+
LATEST_RELEASE_TAG=$(git rev-list --max-parents=0 HEAD) # first commit in the branch.
16+
fi
17+
18+
PR_COMMITS=$(git log "$LATEST_RELEASE_TAG"..HEAD --oneline --pretty=format:"%s" main | grep -oE "#[0-9]+" | tr -d '#' | sort -u)
19+
20+
CHANGELOG_FILE=./CHANGELOG.md
21+
# File header Header
22+
echo "# Changes included in $VERSION:" > "$CHANGELOG_FILE"
23+
echo "" >> "$CHANGELOG_FILE"
24+
25+
declare -A SECTIONS
26+
SECTIONS=(
27+
[feat]="### 🚀 Features"
28+
[fix]="### 🐛 Fixes"
29+
[chore]="### 🔧 Chores"
30+
[docs]="### 📚 Documentation"
31+
[refactor]="### 🔨 Refactoring"
32+
[test]="### ✅ Tests"
33+
[perf]="### ⚡ Performance"
34+
[ci]="### 🔁 CI"
35+
)
36+
37+
# Prepare section buffers
38+
declare -A PR_ENTRIES
39+
for key in "${!SECTIONS[@]}"; do
40+
PR_ENTRIES[$key]=""
41+
done
42+
43+
for PR_NUMBER in $PR_COMMITS; do
44+
PR_JSON=$(gh pr view "$PR_NUMBER" --json number,title,body,url,author)
45+
46+
IS_BOT=$(echo "$PR_JSON" | jq -r '.author.is_bot')
47+
if [[ "$IS_BOT" == "true" ]]; then
48+
continue
49+
fi
50+
51+
TITLE=$(echo "$PR_JSON" | jq -r '.title')
52+
URL=$(echo "$PR_JSON" | jq -r '.url')
53+
BODY=$(echo "$PR_JSON" | jq -r '.body')
54+
55+
# Determine type from conventional commit (assumes title like "type(scope): message" or "type: message")
56+
TYPE=$(echo "$TITLE" | grep -oE '^[a-z]+' || echo "feat")
57+
CLEAN_TITLE=$(echo "$TITLE" | sed -E 's/^[a-z]+(\([^)]+\))?(!)?:[[:space:]]+//')
58+
59+
# Extract release note block, we only extract the "user" related notes.
60+
RELEASE_NOTE=$(echo "$BODY" | awk '/^```[[:space:]]*(breaking|feature|bugfix|doc|other)[[:space:]]+user[[:space:]]*$/ {flag=1; next} /^```[[:space:]]*$/ {flag=0} flag' || true)
61+
62+
# Format entry
63+
ENTRY="- $CLEAN_TITLE [#${PR_NUMBER}](${URL})"
64+
if [[ -n "$RELEASE_NOTE" || "$RELEASE_NOTE" != "NONE" ]]; then
65+
ENTRY+=": $RELEASE_NOTE"
66+
else
67+
ENTRY+="."
68+
fi
69+
ENTRY+="\n"
70+
71+
# Append to appropriate section
72+
if [[ -n "${PR_ENTRIES[$TYPE]+x}" ]]; then
73+
PR_ENTRIES[$TYPE]+="$ENTRY"
74+
else
75+
PR_ENTRIES[chore]+="$ENTRY"
76+
fi
77+
done
78+
79+
# Output sections
80+
for key in "${!SECTIONS[@]}"; do
81+
if [[ -n "${PR_ENTRIES[$key]}" ]]; then
82+
echo "${SECTIONS[$key]}" >> "$CHANGELOG_FILE"
83+
echo -e "${PR_ENTRIES[$key]}" >> "$CHANGELOG_FILE"
84+
echo "" >> "$CHANGELOG_FILE"
85+
fi
86+
done
87+
88+
cat "$CHANGELOG_FILE"

tasks_rls.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,13 @@ tasks:
139139
desc: "List all nested modules in the project."
140140
cmds:
141141
- cmd: 'echo "{{.NESTED_MODULES}}"'
142+
143+
generate-changelog:
144+
desc: " Generates an automated release changelog based on conventional-commits PR titles in CHANGELOG.md file."
145+
run: once
146+
requires:
147+
vars:
148+
- VERSION
149+
cmds:
150+
- 'VERSION={{.VERSION}} {{.TASKFILE_DIR2}}/generate-changelog.sh'
151+

0 commit comments

Comments
 (0)