Skip to content

Commit cedf26c

Browse files
committed
Add breaking change support
1 parent 67d2f33 commit cedf26c

3 files changed

Lines changed: 233 additions & 40 deletions

File tree

.github/release-drafter.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name-template: 'v$RESOLVED_VERSION ⚡'
2+
tag-template: 'v$RESOLVED_VERSION'
3+
categories:
4+
- title: '⚠️ Breaking Changes'
5+
labels:
6+
- 'breaking-change'
7+
- title: '🚀 Features'
8+
labels:
9+
- 'feature'
10+
- 'enhancement'
11+
- title: '🐛 Bug Fixes'
12+
labels:
13+
- 'fix'
14+
- 'bugfix'
15+
- 'bug'
16+
- title: '📦 Dependency Updates'
17+
labels:
18+
- 'dependencies'
19+
- title: '🧰 Maintenance'
20+
labels:
21+
- 'chore'
22+
- 'maintenance'
23+
- title: '📚 Documentation'
24+
labels:
25+
- 'documentation'
26+
- 'docs'
27+
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
28+
change-title-escapes: '\<*_&'
29+
version-resolver:
30+
major:
31+
labels:
32+
- 'major'
33+
minor:
34+
labels:
35+
- 'minor'
36+
patch:
37+
labels:
38+
- 'patch'
39+
default: patch
40+
template: |
41+
## Changes
42+
43+
$CHANGES
44+
45+
## Contributors
46+
47+
$CONTRIBUTORS
48+
autolabeler:
49+
- label: 'chore'
50+
files:
51+
- '*.md'
52+
- '.github/**/*'
53+
- '.gitignore'
54+
- label: 'documentation'
55+
files:
56+
- 'docs/**/*'
57+
- '*.md'
58+
- label: 'feature'
59+
branch:
60+
- '/feature\/.+/'
61+
title:
62+
- '/^feat/i'
63+
- label: 'bug'
64+
branch:
65+
- '/fix\/.+/'
66+
title:
67+
- '/^fix/i'
68+
- '/^bug/i'
69+
- label: 'dependencies'
70+
files:
71+
- 'custom_components/victron_mqtt/manifest.json'
72+
- 'requirements*.txt'
73+
title:
74+
- '/dependencies/i'
75+
- '/deps/i'

.github/release.yml

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 158 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,180 @@
1-
name: Draft Release
1+
name: Release Drafter
22

33
on:
44
push:
55
branches:
66
- main
7+
pull_request:
8+
types: [opened, reopened, synchronize]
79

810
permissions:
9-
contents: write
11+
contents: read
1012

1113
jobs:
12-
draft_release:
14+
update_release_draft:
15+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
16+
permissions:
17+
contents: write
18+
pull-requests: write
1319
runs-on: ubuntu-latest
1420
steps:
1521
- name: Checkout repository
1622
uses: actions/checkout@v6
1723
with:
1824
fetch-depth: 0
1925

20-
- name: Get version from manifest
21-
id: version
26+
- name: Set up Python
27+
uses: actions/setup-python@v6
28+
with:
29+
python-version: '3.14'
30+
31+
- name: Install victron-mqtt library
32+
run: pip install victron-mqtt
33+
34+
- name: Find previous release tag
35+
id: prev_tag
36+
env:
37+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2238
run: |
23-
version=$(jq -r .version custom_components/victron_mqtt/manifest.json)
24-
echo "version=$version" >> $GITHUB_OUTPUT
39+
prev=$(gh release list --json tagName,isDraft -q '[.[] | select(.isDraft | not)] | .[0].tagName')
40+
echo "Previous release tag: $prev"
41+
echo "tag=$prev" >> $GITHUB_OUTPUT
2542
26-
- name: Create or update draft release
43+
- name: Get commit messages since previous tag
2744
env:
2845
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2946
run: |
30-
tag="v${{ steps.version.outputs.version }}"
31-
# Check if a draft release already exists for this tag
32-
existing=$(gh release list --json tagName,isDraft -q ".[] | select(.tagName == \"$tag\" and .isDraft) | .tagName")
33-
if [ -n "$existing" ]; then
34-
gh release edit "$tag" --draft --generate-notes
47+
prev_tag="${{ steps.prev_tag.outputs.tag }}"
48+
if [ -z "$prev_tag" ]; then
49+
prev_tag=$(git rev-list --max-parents=0 HEAD)
50+
fi
51+
COMMITS=$(git log "$prev_tag"..HEAD --pretty=format:"- %s" | head -20)
52+
echo "COMMITS<<EOF" >> $GITHUB_ENV
53+
echo "$COMMITS" >> $GITHUB_ENV
54+
echo "EOF" >> $GITHUB_ENV
55+
56+
# Collect unique commit authors as GitHub usernames
57+
AUTHORS=$(git log "$prev_tag"..HEAD --pretty=format:"%an" | sort -u | while read -r name; do
58+
email=$(git log --author="$name" --pretty=format:"%ae" -1)
59+
username=$(echo "$email" | sed -n 's/.*+\(.*\)@users.noreply.github.com/\1/p')
60+
if [ -n "$username" ]; then
61+
echo "@$username"
62+
else
63+
username=$(gh api "/search/users?q=$email+in:email" --jq '.items[0].login' 2>/dev/null || true)
64+
if [ -n "$username" ] && [ "$username" != "null" ]; then
65+
echo "@$username"
66+
else
67+
echo "@$name"
68+
fi
69+
fi
70+
done | sort -u | tr '\n' ' ')
71+
echo "AUTHORS=$AUTHORS" >> $GITHUB_ENV
72+
73+
- name: Detect breaking changes
74+
id: breaking
75+
run: |
76+
prev_tag="${{ steps.prev_tag.outputs.tag }}"
77+
if [ -z "$prev_tag" ]; then
78+
echo "found=false" >> $GITHUB_OUTPUT
79+
exit 0
80+
fi
81+
82+
git show "${prev_tag}:victron_mqtt.json" > old.json 2>/dev/null || true
83+
if [ ! -f old.json ] || [ ! -s old.json ]; then
84+
echo "found=false" >> $GITHUB_OUTPUT
85+
exit 0
86+
fi
87+
88+
set +e
89+
breaking_output=$(python -m victron_mqtt.utils.detect_breaking_changes old.json victron_mqtt.json 2>/dev/null)
90+
exit_code=$?
91+
set -e
92+
93+
rm -f old.json
94+
95+
if [ $exit_code -eq 1 ] && [ -n "$breaking_output" ]; then
96+
echo "found=true" >> $GITHUB_OUTPUT
97+
echo "content<<EOF" >> $GITHUB_OUTPUT
98+
echo "$breaking_output" >> $GITHUB_OUTPUT
99+
echo "EOF" >> $GITHUB_OUTPUT
35100
else
36-
gh release create "$tag" --draft --generate-notes --title "$tag"
37-
fi
101+
echo "found=false" >> $GITHUB_OUTPUT
102+
fi
103+
104+
- uses: release-drafter/release-drafter@v7
105+
env:
106+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
107+
108+
- name: Enhance draft release notes
109+
env:
110+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
111+
run: |
112+
DRAFT_TAG=$(gh release list --limit 50 --json isDraft,tagName -q '[.[] | select(.isDraft)] | .[0].tagName')
113+
if [ -z "$DRAFT_TAG" ] || [ "$DRAFT_TAG" = "null" ]; then
114+
echo "No draft release found"
115+
exit 0
116+
fi
117+
118+
BODY=$(gh release view "$DRAFT_TAG" --json body -q .body)
119+
120+
if [ -n "$COMMITS" ]; then
121+
export BODY
122+
BODY=$(python3 -c "
123+
import os, re
124+
body = os.environ['BODY']
125+
commits = os.environ['COMMITS']
126+
authors = os.environ.get('AUTHORS', '').strip()
127+
128+
# Remove release-drafter's empty placeholders
129+
body = body.replace('No changes\n', '').replace('No changes', '')
130+
body = body.replace('No contributors\n', '').replace('No contributors', '')
131+
132+
# Split body into sections
133+
parts = re.split(r'(## [^\n]+)', body)
134+
135+
# Modify section contents
136+
for i, part in enumerate(parts):
137+
if part.strip() == '## Changes' and i + 1 < len(parts):
138+
existing = parts[i + 1].strip()
139+
# Remove lone asterisk (release-drafter empty placeholder)
140+
if existing == '*':
141+
existing = ''
142+
items = []
143+
if existing:
144+
items.append(existing)
145+
items.append(commits)
146+
parts[i + 1] = '\n\n' + '\n'.join(items) + '\n\n'
147+
elif part.strip() == '## Contributors' and i + 1 < len(parts):
148+
existing = parts[i + 1].strip()
149+
if not existing and authors:
150+
parts[i + 1] = '\n\n' + authors + '\n'
151+
152+
body = ''.join(parts)
153+
154+
if '## Contributors' not in body and authors:
155+
body = body.rstrip() + '\n\n## Contributors\n\n' + authors
156+
157+
print(body.rstrip())
158+
")
159+
fi
160+
161+
# Prepend breaking changes
162+
if [ "${{ steps.breaking.outputs.found }}" = "true" ]; then
163+
breaking_section="${{ steps.breaking.outputs.content }}"
164+
BODY="${breaking_section}
165+
166+
${BODY}"
167+
fi
168+
169+
gh release edit "$DRAFT_TAG" --notes "$BODY"
170+
171+
autolabeler:
172+
if: github.event_name == 'pull_request'
173+
permissions:
174+
contents: read
175+
pull-requests: write
176+
runs-on: ubuntu-latest
177+
steps:
178+
- uses: release-drafter/release-drafter@v7
179+
env:
180+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

0 commit comments

Comments
 (0)