1+ name : Release
2+
3+ on :
4+ push :
5+ tags :
6+ - ' *'
7+ workflow_dispatch :
8+
9+ permissions :
10+ contents : write
11+ issues : write
12+
13+ jobs :
14+ release :
15+ runs-on : windows-latest
16+
17+ steps :
18+ - name : Checkout repository
19+ uses : actions/checkout@v4
20+ with :
21+ fetch-depth : 0
22+ persist-credentials : true
23+
24+ - name : Get tag and commit message
25+ id : tag-info
26+ shell : pwsh
27+ run : |
28+ $tag = ($env:GITHUB_REF -split '/')[ -1 ]
29+ Write-Host "Tag: $tag"
30+ git fetch --tags
31+ $commit = (git rev-list -n 1 $tag).Trim()
32+ Write-Host "Commit: $commit"
33+ $rawBody = git show -s --format=%B $commit
34+ # Strip the first non-blank line (commit summary) and any intervening blank lines,
35+ # starting notes from the second non-blank line onward.
36+ $lines = $rawBody -split '(?:\r\n|\n|\r)'
37+ $nonBlank = @()
38+ for ($i = 0; $i -lt $lines.Count; $i++) {
39+ if ($lines[$i].Trim() -ne '') { $nonBlank += $i }
40+ }
41+ if ($nonBlank.Count -ge 2) {
42+ $start = $nonBlank[1]
43+ $notesLines = $lines[$start..($lines.Count - 1)]
44+ $body = ($notesLines -join "`n")
45+ } else {
46+ # No body content beyond a single-line summary
47+ $body = ''
48+ }
49+ $norm = $tag -replace '^v',''
50+ # Use a unique delimiter for multiline output to GITHUB_OUTPUT to avoid collisions
51+ $delim = [guid]::NewGuid().ToString()
52+ Add-Content -Path $env:GITHUB_OUTPUT -Value "tag=$tag"
53+ Add-Content -Path $env:GITHUB_OUTPUT -Value "body<<$delim"
54+ Add-Content -Path $env:GITHUB_OUTPUT -Value $body
55+ Add-Content -Path $env:GITHUB_OUTPUT -Value $delim
56+ Add-Content -Path $env:GITHUB_OUTPUT -Value "norm_tag=$norm"
57+
58+ - name : Create GitHub release
59+ uses : actions/create-release@v1
60+ env :
61+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
62+ with :
63+ tag_name : ${{ steps.tag-info.outputs.tag }}
64+ release_name : ${{ steps.tag-info.outputs.norm_tag }}
65+ body : ${{ steps.tag-info.outputs.body }}
66+ draft : false
67+ prerelease : false
68+
69+ - name : Update docs/CHANGELOG.html and close milestone
70+ shell : pwsh
71+ env :
72+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
73+ run : |
74+ $tag = '${{ steps.tag-info.outputs.tag }}'
75+ $norm = '${{ steps.tag-info.outputs.norm_tag }}'
76+ git fetch --tags --prune
77+ $commit = (git rev-list -n 1 $tag).Trim()
78+ $rawBody = git show -s --format=%B $commit
79+ # Strip the first non-blank line (commit summary) and any intervening blank lines,
80+ # starting notes from the second non-blank line onward.
81+ $lines = $rawBody -split '(?:\r\n|\n|\r)'
82+ $nonBlank = @()
83+ for ($i = 0; $i -lt $lines.Count; $i++) {
84+ if ($lines[$i].Trim() -ne '') { $nonBlank += $i }
85+ }
86+ if ($nonBlank.Count -ge 2) {
87+ $start = $nonBlank[1]
88+ $notesLines = $lines[$start..($lines.Count - 1)]
89+ $body = ($notesLines -join "`n")
90+ } else {
91+ $body = ''
92+ }
93+ $path = 'Prefix/docs/CHANGELOG.html'
94+ if (-not (Test-Path $path)) { Write-Error "$path not found"; exit 1 }
95+ $text = Get-Content -Raw -Encoding UTF8 $path
96+ $regex = [regex]::new('(?s)(<script id="md" type="text/markdown">\s*)(?<md>.*?)(\s*</script>)')
97+ $match = $regex.Match($text)
98+ if (-not $match.Success) { Write-Error "Embedded markdown block not found in $path"; exit 1 }
99+ $prefix = $match.Groups[1].Value
100+ $md = $match.Groups['md'].Value
101+ $suffix = $match.Groups[3].Value
102+ if ($md -match '(?m)^\s*##\s*' + [regex]::Escape($norm) + '\s*$') {
103+ Write-Host "CHANGELOG already contains $norm; skipping update."
104+ exit 0
105+ }
106+ $sepMatches = [regex]::Matches($md, '^[ \t]*---[ \t]*$', [System.Text.RegularExpressions.RegexOptions]::Multiline)
107+ if ($sepMatches.Count -ge 2) { $insIndex = $sepMatches[1].Index + $sepMatches[1].Length } elseif ($sepMatches.Count -ge 1) { $insIndex = $sepMatches[0].Index + $sepMatches[0].Length } else { $insIndex = 0 }
108+ # Insert the commit message body exactly as provided (no added headers or placeholders).
109+ $sectionBody = $body
110+ $section = "## $norm`n`n" + $sectionBody + "`n`n---`n`n"
111+ $new_md = $md.Substring(0, $insIndex) + $section + $md.Substring($insIndex)
112+ $new_text = $text.Substring(0,$match.Index) + $prefix + $new_md + $suffix + $text.Substring($match.Index + $match.Length)
113+ Set-Content -Path $path -Value $new_text -Encoding UTF8
114+ git config user.name "github-actions[bot]"
115+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
116+ git add $path
117+ git commit -m "chore(release): update CHANGELOG for $tag" || Write-Host "No changelog changes"
118+ git remote set-head origin --auto
119+ $default_branch = (git symbolic-ref refs/remotes/origin/HEAD).Replace('refs/remotes/origin/','')
120+ git push origin HEAD:$default_branch
121+ # close matching open milestone (exact title match)
122+ $repo = $env:GITHUB_REPOSITORY
123+ $token = $env:GITHUB_TOKEN
124+ $miles = Invoke-RestMethod -Headers @{ Authorization = "token $token"; 'User-Agent' = 'github-actions' } -Uri "https://api.github.com/repos/$repo/milestones?state=open&per_page=100" -Method Get
125+ foreach ($m in $miles) {
126+ if ($m.title -eq $norm) {
127+ Write-Host "Closing milestone $($m.title) (number $($m.number))"
128+ Invoke-RestMethod -Headers @{ Authorization = "token $token"; 'User-Agent' = 'github-actions' } -Method Patch -Uri "https://api.github.com/repos/$repo/milestones/$($m.number)" -Body (@{ state = 'closed' } | ConvertTo-Json)
129+ }
130+ }
0 commit comments