@@ -3,144 +3,220 @@ name: 'Generate Release Note'
33on :
44 push :
55 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.
118
129permissions :
1310 contents : write
1411 pull-requests : read
1512
1613jobs :
17- check-for- release :
14+ check-release-needed :
1815 runs-on : ubuntu-latest
1916 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 }}
2421 steps :
2522 - name : Checkout code
26- uses : actions/checkout@v5
23+ uses : actions/checkout@v4
2724 with :
2825 fetch-depth : 0
26+ token : ${{ secrets.GITHUB_TOKEN }}
2927
30- - name : Check if release is needed
28+ - name : Check if release notes generation is needed
3129 id : check
30+ env :
31+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
3232 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 "")
3442
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
3846 exit 0
3947 fi
4048
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
4555 exit 0
4656 fi
4757
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=""
4960
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"
5474 fi
5575
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
6082
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'
6486 runs-on : ubuntu-latest
6587 steps :
6688 - name : Checkout code
67- uses : actions/checkout@v5
89+ uses : actions/checkout@v4
6890 with :
6991 fetch-depth : 0
92+ token : ${{ secrets.GITHUB_TOKEN }}
7093
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'
7398 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 }}"
76102
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)
80110 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)
83113 fi
84114
85- # Categorize commits
115+ # Initialize categories
86116 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
100152 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'
102167 else
103- OTHER ="$OTHER$commit "$'\n'
168+ OTHER_CHANGES ="$OTHER_CHANGES$FORMATTED_COMMIT "$'\n'
104169 fi
105170 done <<< "$COMMITS"
106171
107172 # 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
109178
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'
112181 fi
113182
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'
116185 fi
117186
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'
120189 fi
121190
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'
124193 fi
125194
126195 # Add installation instructions
127196 RELEASE_NOTES="$RELEASE_NOTES"$'\n'"## Installation"$'\n\n'
128197 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'
130199 RELEASE_NOTES="$RELEASE_NOTES\`\`\`"$'\n\n'
131200
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
136209
137- - name : Create Release
138- uses : actions/create-release@v1
210+ - name : Create draft release
139211 env :
140212 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