@@ -171,6 +171,246 @@ jobs:
171
171
name : windows-installer
172
172
path : release-artifacts
173
173
174
+ - name : Rename installer for pre-release
175
+ if : github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
176
+ shell : pwsh
177
+ run : |
178
+ $files = Get-ChildItem release-artifacts\*.exe
179
+ foreach ($file in $files) {
180
+ $newName = $file.Name -replace '_setup_.*\.exe$', '_setup_latest_pre_release.exe'
181
+ Rename-Item $file.FullName $newName
182
+ Write-Host "Renamed $($file.Name) to $newName"
183
+ }
184
+
185
+ - name : Generate categorized release notes
186
+ id : release_notes
187
+ shell : pwsh
188
+ run : |
189
+ # Determine if this is a pre-release or stable release
190
+ $isPreRelease = "${{ github.ref }}" -eq "refs/heads/${{ github.event.repository.default_branch }}"
191
+ $isStableRelease = "${{ github.ref }}" -like "refs/tags/v*"
192
+
193
+ try {
194
+ if ($isPreRelease) {
195
+ # For pre-releases, compare against latest stable release
196
+ Write-Host "Fetching latest stable release..."
197
+ $releases = gh release list --limit 50 --json tagName,isPrerelease 2>$null
198
+ if (-not $releases) {
199
+ Write-Host "Failed to fetch releases"
200
+ echo "notes=" >> $env:GITHUB_OUTPUT
201
+ return
202
+ }
203
+
204
+ $releaseList = $releases | ConvertFrom-Json
205
+ $latestStable = $releaseList | Where-Object { -not $_.isPrerelease -and $_.tagName } | Select-Object -First 1
206
+
207
+ if ($latestStable -and $latestStable.tagName) {
208
+ $compareFrom = $latestStable.tagName
209
+ $releaseType = "pre-release"
210
+ $releaseTitle = "🚧 Development Build"
211
+ $releaseWarning = "**⚠️ This is an unstable development build. Use at your own risk!**"
212
+ Write-Host "Pre-release: Comparing against latest stable release: $compareFrom"
213
+ } else {
214
+ $compareFrom = ""
215
+ Write-Host "No stable release found for comparison"
216
+ }
217
+ } elseif ($isStableRelease) {
218
+ # For stable releases, compare against previous release (stable or pre-release)
219
+ Write-Host "Fetching previous releases..."
220
+ $releases = gh release list --limit 50 --json tagName 2>$null
221
+ if (-not $releases) {
222
+ Write-Host "Failed to fetch releases"
223
+ echo "notes=" >> $env:GITHUB_OUTPUT
224
+ return
225
+ }
226
+
227
+ $allReleases = $releases | ConvertFrom-Json
228
+ $currentTag = "${{ github.ref }}" -replace "refs/tags/", ""
229
+ $previousRelease = $allReleases | Where-Object { $_.tagName -ne $currentTag -and $_.tagName } | Select-Object -First 1
230
+
231
+ if ($previousRelease -and $previousRelease.tagName) {
232
+ $compareFrom = $previousRelease.tagName
233
+ $releaseType = "stable"
234
+ $releaseTitle = "🎉 Stable Release"
235
+ $releaseWarning = ""
236
+ Write-Host "Stable release: Comparing against previous release: $compareFrom"
237
+ } else {
238
+ $compareFrom = ""
239
+ Write-Host "No previous release found for comparison"
240
+ }
241
+ } else {
242
+ Write-Host "Neither pre-release nor stable release, skipping"
243
+ echo "notes=" >> $env:GITHUB_OUTPUT
244
+ return
245
+ }
246
+ }
247
+ catch {
248
+ Write-Host "Error during release detection: $($_.Exception.Message). Using auto-generated notes."
249
+ echo "notes=" >> $env:GITHUB_OUTPUT
250
+ return
251
+ }
252
+
253
+ if ($compareFrom) {
254
+ try {
255
+ # Get commits since comparison point with error handling
256
+ Write-Host "Fetching commits from $compareFrom to ${{ github.sha }}"
257
+ $commits = gh api "repos/${{ github.repository }}/compare/$compareFrom...${{ github.sha }}" --jq '.commits[] | {message: .commit.message, sha: .sha, author: .commit.author.name, url: .html_url}' 2>$null
258
+
259
+ if (-not $commits) {
260
+ Write-Host "No commits found or API call failed, falling back to auto-generated notes"
261
+ echo "notes=" >> $env:GITHUB_OUTPUT
262
+ return
263
+ }
264
+
265
+ $commitList = $commits | ConvertFrom-Json
266
+ if (-not $commitList -or $commitList.Count -eq 0) {
267
+ Write-Host "No commits to process"
268
+ echo "notes=" >> $env:GITHUB_OUTPUT
269
+ return
270
+ }
271
+ }
272
+ catch {
273
+ Write-Host "Error fetching commits: $($_.Exception.Message). Using auto-generated notes."
274
+ echo "notes=" >> $env:GITHUB_OUTPUT
275
+ return
276
+ }
277
+
278
+ # Initialize grouped commits using ArrayList for better performance
279
+ $grouped = @{
280
+ 'feat' = New-Object System.Collections.ArrayList
281
+ 'fix' = New-Object System.Collections.ArrayList
282
+ 'docs' = New-Object System.Collections.ArrayList
283
+ 'style' = New-Object System.Collections.ArrayList
284
+ 'refactor' = New-Object System.Collections.ArrayList
285
+ 'perf' = New-Object System.Collections.ArrayList
286
+ 'test' = New-Object System.Collections.ArrayList
287
+ 'build' = New-Object System.Collections.ArrayList
288
+ 'ci' = New-Object System.Collections.ArrayList
289
+ 'chore' = New-Object System.Collections.ArrayList
290
+ 'revert' = New-Object System.Collections.ArrayList
291
+ 'other' = New-Object System.Collections.ArrayList
292
+ }
293
+
294
+ foreach ($commit in $commitList) {
295
+ # Input validation
296
+ if (-not $commit.message -or -not $commit.sha -or -not $commit.url) {
297
+ Write-Host "Skipping invalid commit object"
298
+ continue
299
+ }
300
+
301
+ $message = ($commit.message -split "`n" | Select-Object -First 1).Trim()
302
+
303
+ # Safe substring operation
304
+ $shortSha = if ($commit.sha.Length -ge 7) {
305
+ $commit.sha.Substring(0, 7)
306
+ } else {
307
+ $commit.sha
308
+ }
309
+
310
+ $commitUrl = $commit.url
311
+
312
+ # Sanitize message to prevent markdown injection
313
+ $sanitizedMessage = $message -replace '[<>&"]', '' -replace '\[', '\\[' -replace '\]', '\\]' -replace '`', '\\`'
314
+
315
+ # Parse conventional commit format
316
+ if ($sanitizedMessage -match '^(\w+)(\(.+\))?\s*!?\s*:\s*(.+)$') {
317
+ $type = $matches[1].ToLower()
318
+ $description = $matches[3].Trim()
319
+
320
+ # Limit description length
321
+ if ($description.Length -gt 100) {
322
+ $description = $description.Substring(0, 97) + "..."
323
+ }
324
+
325
+ if ($grouped.ContainsKey($type)) {
326
+ $null = $grouped[$type].Add("- **$description** ([$shortSha]($commitUrl))")
327
+ } else {
328
+ $null = $grouped['other'].Add("- **$description** ([$shortSha]($commitUrl))")
329
+ }
330
+ } else {
331
+ # Non-conventional commit
332
+ $truncatedMessage = if ($sanitizedMessage.Length -gt 100) {
333
+ $sanitizedMessage.Substring(0, 97) + "..."
334
+ } else {
335
+ $sanitizedMessage
336
+ }
337
+ $null = $grouped['other'].Add("- **$truncatedMessage** ([$shortSha]($commitUrl))")
338
+ }
339
+ }
340
+
341
+ # Build categorized release notes
342
+ $sections = @()
343
+
344
+ # Define section mappings with emojis
345
+ $sectionMap = @{
346
+ 'feat' = @{ title = '🚀 New Features'; items = $grouped['feat'] }
347
+ 'fix' = @{ title = '🐛 Bug Fixes'; items = $grouped['fix'] }
348
+ 'docs' = @{ title = '📚 Documentation'; items = $grouped['docs'] }
349
+ 'perf' = @{ title = '⚡ Performance'; items = $grouped['perf'] }
350
+ 'refactor' = @{ title = '♻️ Code Refactoring'; items = $grouped['refactor'] }
351
+ 'style' = @{ title = '💎 Style Changes'; items = $grouped['style'] }
352
+ 'test' = @{ title = '🧪 Tests'; items = $grouped['test'] }
353
+ 'build' = @{ title = '📦 Build System'; items = $grouped['build'] }
354
+ 'ci' = @{ title = '⚙️ CI/CD'; items = $grouped['ci'] }
355
+ 'chore' = @{ title = '🔧 Maintenance'; items = $grouped['chore'] }
356
+ 'revert' = @{ title = '⏪ Reverts'; items = $grouped['revert'] }
357
+ 'other' = @{ title = '📋 Other Changes'; items = $grouped['other'] }
358
+ }
359
+
360
+ # Process sections in a defined order for consistency
361
+ $orderedKeys = @('feat', 'fix', 'perf', 'refactor', 'docs', 'style', 'test', 'build', 'ci', 'chore', 'revert', 'other')
362
+ foreach ($key in $orderedKeys) {
363
+ if ($sectionMap.ContainsKey($key) -and $sectionMap[$key].items.Count -gt 0) {
364
+ $sections += ""
365
+ $sections += "### $($sectionMap[$key].title)"
366
+ $sections += ""
367
+ $sections += $sectionMap[$key].items.ToArray()
368
+ }
369
+ }
370
+
371
+ $changesSummary = if ($sections.Count -gt 0) { $sections -join "`n" } else { "No changes found." }
372
+
373
+ # Build release notes based on release type
374
+ if ($isPreRelease) {
375
+ $fullNotes = @"
376
+ ## $releaseTitle
377
+
378
+ $releaseWarning
379
+
380
+ ### Changes since last stable release ($compareFrom):
381
+ $changesSummary
382
+
383
+ ---
384
+ **Build Info:**
385
+ - Commit: [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }})
386
+ - Build: #${{ github.run_number }}
387
+ - Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC')
388
+ "@
389
+ } else {
390
+ $currentVersion = "${{ github.ref }}" -replace "refs/tags/", ""
391
+ $fullNotes = @"
392
+ ## $releaseTitle
393
+
394
+ ### Changes since previous release ($compareFrom):
395
+ $changesSummary
396
+
397
+ ---
398
+ **Release Info:**
399
+ - Version: $currentVersion
400
+ - Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC')
401
+ "@
402
+ }
403
+
404
+ # Output the notes for next step (escape for multiline)
405
+ $fullNotes = $fullNotes -replace "(\r\n|\r|\n)", "%0A"
406
+ echo "notes=$fullNotes" >> $env:GITHUB_OUTPUT
407
+ } else {
408
+ Write-Host "No comparison point found, using auto-generated notes"
409
+ echo "notes=" >> $env:GITHUB_OUTPUT
410
+ }
411
+ env :
412
+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
413
+
174
414
- name : Pre Release
175
415
uses : softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
176
416
if : github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
@@ -179,16 +419,17 @@ jobs:
179
419
prerelease : true
180
420
name : " Development Build"
181
421
files : release-artifacts/*
182
- generate_release_notes : true
422
+ body : ${{ steps.release_notes.outputs.notes || '' }}
423
+ generate_release_notes : ${{ steps.release_notes.outputs.notes == '' }}
183
424
token : ${{ secrets.GITHUB_TOKEN }}
184
- append_body : false
185
425
186
426
- name : Release
187
427
uses : softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
188
428
if : startsWith(github.ref, 'refs/tags/v')
189
429
with :
190
430
prerelease : false
191
431
files : release-artifacts/*
192
- generate_release_notes : true
432
+ body : ${{ steps.release_notes.outputs.notes || '' }}
433
+ generate_release_notes : ${{ steps.release_notes.outputs.notes == '' }}
193
434
token : ${{ secrets.GITHUB_TOKEN }}
194
435
make_latest : true
0 commit comments