@@ -3,144 +3,220 @@ name: 'Generate Release Note'
3
3
on :
4
4
push :
5
5
tags :
6
- - ' v*' # Triggers for any tag starting with 'v' (e.g., v1.0, v2.1.3)
7
- - ' release-*' # Triggers for any tag starting with 'release-'
8
- - ' 1.*.*' # Triggers for any tag starting with '1.' (e.g., 1.0.0, 1.1.0)
9
- - ' 2.*.*' # Triggers for any tag starting with '2.' (e.g., 2.0.0, 2.1.0)
10
- - ' 3.*.*' # Triggers for any tag starting with '3.' (e.g., 3.0.0, 3.1.0)
6
+ - ' [0-9]+.[0-9]+.[0-9]+' # Triggers for tags like 1.0.0, 2.1.3, etc.
7
+ - ' v[0-9]+.[0-9]+.[0-9]+' # Triggers for tags like v1.0.0, v2.1.3, etc.
11
8
12
9
permissions :
13
10
contents : write
14
11
pull-requests : read
15
12
16
13
jobs :
17
- check-for- release :
14
+ check-release-needed :
18
15
runs-on : ubuntu-latest
19
16
outputs :
20
- should-release : ${{ steps.check.outputs.should-release }}
21
- version : ${{ steps.check.outputs.version }}
22
- tag : ${{ steps.check.outputs.tag }}
23
- latest_tag : ${{ steps.check.outputs.latest_tag }}
17
+ should-create- release : ${{ steps.check.outputs.should-create -release }}
18
+ current-tag : ${{ steps.check.outputs.current-tag }}
19
+ latest- tag : ${{ steps.check.outputs.latest- tag }}
20
+ clean-version : ${{ steps.check.outputs.clean-version }}
24
21
steps :
25
22
- name : Checkout code
26
- uses : actions/checkout@v5
23
+ uses : actions/checkout@v4
27
24
with :
28
25
fetch-depth : 0
26
+ token : ${{ secrets.GITHUB_TOKEN }}
29
27
30
- - name : Check if release is needed
28
+ - name : Check if release notes generation is needed
31
29
id : check
30
+ env :
31
+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
32
32
run : |
33
- echo "Tag pushed: ${{ github.ref_name }}"
33
+ CURRENT_TAG="${{ github.ref_name }}"
34
+ echo "Current tag: $CURRENT_TAG"
35
+
36
+ # Remove 'v' prefix if present for version comparison
37
+ CLEAN_VERSION="${CURRENT_TAG#v}"
38
+ echo "Clean version: $CLEAN_VERSION"
39
+
40
+ # Check if any release (including draft) already exists for this tag
41
+ EXISTING_RELEASE=$(gh release view "$CURRENT_TAG" --json isDraft,tagName 2>/dev/null || echo "")
34
42
35
- if [[ -z "${{ github.ref_name }} " ]]; then
36
- echo "No tag name found in the event. "
37
- echo "should-release=false" >> $GITHUB_OUTPUT
43
+ if [[ -n "$EXISTING_RELEASE " ]]; then
44
+ echo "Release (draft or published) already exists for tag: $CURRENT_TAG "
45
+ echo "should-create- release=false" >> $GITHUB_OUTPUT
38
46
exit 0
39
47
fi
40
48
41
- HAVE_RELEASE=$(gh release view ${{ github.ref_name }} --json tagName --jq '.tagName' 2>/dev/null || echo "")
42
- if [[ -n "$HAVE_RELEASE" ]]; then
43
- echo "Release already exists for tag: ${{ github.ref_name }}"
44
- echo "should-release=false" >> $GITHUB_OUTPUT
49
+ # Parse version components
50
+ IFS='.' read -r MAJOR MINOR PATCH <<< "$CLEAN_VERSION"
51
+
52
+ if [[ -z "$MAJOR" || -z "$MINOR" || -z "$PATCH" ]]; then
53
+ echo "Invalid version format: $CLEAN_VERSION"
54
+ echo "should-create-release=false" >> $GITHUB_OUTPUT
45
55
exit 0
46
56
fi
47
57
48
- IFS='.' read -r MAJOR MINOR PATCH <<< "${{ github.ref_name }}"
58
+ # Find the latest tag with same major.minor but lower patch version
59
+ LATEST_TAG=""
49
60
50
- if [[ -z "$MAJOR" || -z "$MINOR" || -z "$PATCH" ]]; then
51
- LATEST_TAG=null
52
- else
53
- LATEST_TAG=$(git tag --list "${MAJOR}.${MINOR}.*" | sort -V | tail -n 1)
61
+ # Get all tags, filter by pattern, and find the latest one before current
62
+ ALL_TAGS=$(git tag --list | grep -E "^v?${MAJOR}\.${MINOR}\.[0-9]+$" | sed 's/^v//' | sort -V)
63
+
64
+ for tag in $ALL_TAGS; do
65
+ IFS='.' read -r tag_major tag_minor tag_patch <<< "$tag"
66
+ if [[ "$tag_patch" -lt "$PATCH" ]]; then
67
+ LATEST_TAG="$tag"
68
+ fi
69
+ done
70
+
71
+ # Add 'v' prefix back if original tag had it
72
+ if [[ "$CURRENT_TAG" == v* ]] && [[ -n "$LATEST_TAG" ]]; then
73
+ LATEST_TAG="v$LATEST_TAG"
54
74
fi
55
75
56
- echo "should-release=true" >> $GITHUB_OUTPUT
57
- echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
58
- echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
59
- echo "tag=${{ github.ref_name }}" >> $GITHUB_OUTPUT
76
+ echo "Latest tag found: $LATEST_TAG"
77
+
78
+ echo "should-create-release=true" >> $GITHUB_OUTPUT
79
+ echo "current-tag=$CURRENT_TAG" >> $GITHUB_OUTPUT
80
+ echo "latest-tag=$LATEST_TAG" >> $GITHUB_OUTPUT
81
+ echo "clean-version=$CLEAN_VERSION" >> $GITHUB_OUTPUT
60
82
61
- create -release :
62
- needs : check-for- release
63
- if : needs.check-for- release.outputs.should-release == 'true'
83
+ generate -release-notes :
84
+ needs : check-release-needed
85
+ if : needs.check-release-needed .outputs.should-create -release == 'true'
64
86
runs-on : ubuntu-latest
65
87
steps :
66
88
- name : Checkout code
67
- uses : actions/checkout@v5
89
+ uses : actions/checkout@v4
68
90
with :
69
91
fetch-depth : 0
92
+ token : ${{ secrets.GITHUB_TOKEN }}
70
93
71
- - name : Generate Release Notes
72
- id : generate_notes
94
+ - name : Generate release notes content
95
+ id : generate-notes
96
+ env :
97
+ PACKAGE_NAME : ' solution-forest/filament-tree'
73
98
run : |
74
- # Get the latest tag
75
- LATEST_TAG="${{ needs.check-for-release.outputs.latest_tag }}"
99
+ CURRENT_TAG="${{ needs.check-release-needed.outputs.current-tag }}"
100
+ LATEST_TAG="${{ needs.check-release-needed.outputs.latest-tag }}"
101
+ CLEAN_VERSION="${{ needs.check-release-needed.outputs.clean-version }}"
76
102
77
- if [ -z "$LATEST_TAG" ]; then
78
- # If no previous tag, get all commits
79
- COMMITS=$(git log --pretty=format:"- %s (%h)" --reverse)
103
+ echo "Generating release notes for: $CURRENT_TAG"
104
+ echo "Previous tag: $LATEST_TAG"
105
+
106
+ # Get commits between tags
107
+ if [[ -z "$LATEST_TAG" ]]; then
108
+ echo "No previous tag found, getting all commits"
109
+ COMMITS=$(git log --pretty=format:"%s|||%H" --reverse)
80
110
else
81
- # Get commits since last tag
82
- COMMITS=$(git log ${LATEST_TAG}..HEAD --pretty=format:"- %s (%h) " --reverse)
111
+ echo "Getting commits since: $LATEST_TAG"
112
+ COMMITS=$(git log ${LATEST_TAG}..HEAD --pretty=format:"%s|||%H " --reverse)
83
113
fi
84
114
85
- # Categorize commits
115
+ # Initialize categories
86
116
BREAKING_CHANGES=""
87
- FEATURES=""
88
- FIXES=""
89
- OTHER=""
90
-
91
- while IFS= read -r commit; do
92
- if echo "$commit" | grep -qE "(BREAKING CHANGE|!:)"; then
93
- BREAKING_CHANGES="$BREAKING_CHANGES$commit"$'\n'
94
- elif echo "$commit" | grep -qE "^- feat"; then
95
- FEATURES="$FEATURES$commit"$'\n'
96
- elif echo "$commit" | grep -qE "^- fix"; then
97
- # Skip "Fix styling" commits
98
- if echo "$commit" | grep -qE "Fix styling"; then
99
- continue
117
+ NEW_FEATURES=""
118
+ DOCUMENTATION=""
119
+ BUG_FIXES=""
120
+ OTHER_CHANGES=""
121
+
122
+ # Process each commit
123
+ while IFS= read -r commit_line; do
124
+ if [[ -z "$commit_line" ]]; then
125
+ continue
126
+ fi
127
+
128
+ # Split commit message and hash
129
+ COMMIT_MSG="${commit_line%|||*}"
130
+ COMMIT_HASH="${commit_line#*|||}"
131
+ SHORT_HASH="${COMMIT_HASH:0:7}"
132
+
133
+ # Skip excluded commit patterns
134
+ if echo "$COMMIT_MSG" | grep -qiE "^(Create |Update |Fix styling|wip|Merge branch)"; then
135
+ continue
136
+ fi
137
+
138
+ # Skip dependabot merge requests
139
+ if echo "$COMMIT_MSG" | grep -qiE "Merge pull request.*dependabot/github_actions"; then
140
+ continue
141
+ fi
142
+
143
+ # Handle merge pull request commits - get PR title
144
+ if echo "$COMMIT_MSG" | grep -qiE "^Merge pull request #[0-9]+"; then
145
+ PR_NUMBER=$(echo "$COMMIT_MSG" | grep -oE "#[0-9]+" | sed 's/#//')
146
+ if [[ -n "$PR_NUMBER" ]]; then
147
+ # Try to get PR title using GitHub CLI
148
+ PR_TITLE=$(gh pr view "$PR_NUMBER" --json title --jq '.title' 2>/dev/null || echo "")
149
+ if [[ -n "$PR_TITLE" ]]; then
150
+ COMMIT_MSG="$PR_TITLE"
151
+ fi
100
152
fi
101
- FIXES="$FIXES$commit"$'\n'
153
+ fi
154
+
155
+ # Format commit for release notes
156
+ FORMATTED_COMMIT="- $COMMIT_MSG ($SHORT_HASH)"
157
+
158
+ # Categorize commits
159
+ if echo "$COMMIT_MSG" | grep -qiE "(BREAKING CHANGE|breaking:|!:)"; then
160
+ BREAKING_CHANGES="$BREAKING_CHANGES$FORMATTED_COMMIT"$'\n'
161
+ elif echo "$COMMIT_MSG" | grep -qiE "^(feat|feature):|Merge.*feature/|Add.*feature"; then
162
+ NEW_FEATURES="$NEW_FEATURES$FORMATTED_COMMIT"$'\n'
163
+ elif echo "$COMMIT_MSG" | grep -qiE "^docs?:|documentation|readme|Update.*\.md"; then
164
+ DOCUMENTATION="$DOCUMENTATION$FORMATTED_COMMIT"$'\n'
165
+ elif echo "$COMMIT_MSG" | grep -qiE "^(fix|bugfix):|Fix "; then
166
+ BUG_FIXES="$BUG_FIXES$FORMATTED_COMMIT"$'\n'
102
167
else
103
- OTHER ="$OTHER$commit "$'\n'
168
+ OTHER_CHANGES ="$OTHER_CHANGES$FORMATTED_COMMIT "$'\n'
104
169
fi
105
170
done <<< "$COMMITS"
106
171
107
172
# Build release notes
108
- RELEASE_NOTES="## What's Changed in ${{ needs.check-for-release.outputs.version }}"$'\n\n'
173
+ RELEASE_NOTES="## What's Changed in $CURRENT_TAG"$'\n\n'
174
+
175
+ if [[ -n "$BREAKING_CHANGES" ]]; then
176
+ RELEASE_NOTES="$RELEASE_NOTES### ⚠️ Breaking changes"$'\n'"$BREAKING_CHANGES"$'\n'
177
+ fi
109
178
110
- if [ -n "$BREAKING_CHANGES" ]; then
111
- RELEASE_NOTES="$RELEASE_NOTES### 💥 Breaking Changes "$'\n'"$BREAKING_CHANGES "$'\n'
179
+ if [[ -n "$NEW_FEATURES" ] ]; then
180
+ RELEASE_NOTES="$RELEASE_NOTES### 🚀 New features "$'\n'"$NEW_FEATURES "$'\n'
112
181
fi
113
182
114
- if [ -n "$FEATURES" ]; then
115
- RELEASE_NOTES="$RELEASE_NOTES### ✨ New Features "$'\n'"$FEATURES "$'\n'
183
+ if [[ -n "$DOCUMENTATION" ] ]; then
184
+ RELEASE_NOTES="$RELEASE_NOTES### 📘 Documentation updates "$'\n'"$DOCUMENTATION "$'\n'
116
185
fi
117
186
118
- if [ -n "$FIXES" ]; then
119
- RELEASE_NOTES="$RELEASE_NOTES### 🐛 Bug Fixes "$'\n'"$FIXES "$'\n'
187
+ if [[ -n "$BUG_FIXES" ] ]; then
188
+ RELEASE_NOTES="$RELEASE_NOTES### 🐛 Bug fixes "$'\n'"$BUG_FIXES "$'\n'
120
189
fi
121
190
122
- if [ -n "$OTHER" ]; then
123
- RELEASE_NOTES="$RELEASE_NOTES### 🔧 Other Changes"$'\n'"$OTHER "$'\n'
191
+ if [[ -n "$OTHER_CHANGES" ] ]; then
192
+ RELEASE_NOTES="$RELEASE_NOTES### 🔧 Other Changes"$'\n'"$OTHER_CHANGES "$'\n'
124
193
fi
125
194
126
195
# Add installation instructions
127
196
RELEASE_NOTES="$RELEASE_NOTES"$'\n'"## Installation"$'\n\n'
128
197
RELEASE_NOTES="$RELEASE_NOTES\`\`\`bash"$'\n'
129
- RELEASE_NOTES="$RELEASE_NOTES""composer require solution-forest/filament-tree :^${{ needs.check-for-release.outputs.version }} "$'\n'
198
+ RELEASE_NOTES="$RELEASE_NOTES""composer require $PACKAGE_NAME :^$CLEAN_VERSION "$'\n'
130
199
RELEASE_NOTES="$RELEASE_NOTES\`\`\`"$'\n\n'
131
200
132
- # Output for GitHub (escape newlines)
133
- echo "notes<<EOF" >> $GITHUB_OUTPUT
134
- echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
135
- echo "EOF" >> $GITHUB_OUTPUT
201
+ RELEASE_NOTES="$RELEASE_NOTES""**Full Changelog**: https://github.com/${{ github.repository }}/compare/${LATEST_TAG}...${CURRENT_TAG}"$'\n'
202
+
203
+ # Save to output (properly escape for GitHub Actions)
204
+ {
205
+ echo "notes<<EOF"
206
+ echo "$RELEASE_NOTES"
207
+ echo "EOF"
208
+ } >> $GITHUB_OUTPUT
136
209
137
- - name : Create Release
138
- uses : actions/create-release@v1
210
+ - name : Create draft release
139
211
env :
140
212
GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
141
- with :
142
- tag_name : ${{ needs.check-for-release.outputs.tag }}
143
- release_name : ' ${{ needs.check-for-release.outputs.version }}'
144
- body : ${{ steps.generate_notes.outputs.notes }}
145
- draft : true
146
- prerelease : false
213
+ run : |
214
+ CURRENT_TAG="${{ needs.check-release-needed.outputs.current-tag }}"
215
+
216
+ gh release create "$CURRENT_TAG" \
217
+ --title "$CURRENT_TAG" \
218
+ --notes "${{ steps.generate-notes.outputs.notes }}" \
219
+ --draft \
220
+ --latest
221
+
222
+ echo "Draft release created successfully for $CURRENT_TAG"
0 commit comments