Skip to content

Commit c5acd9f

Browse files
authored
[repo] Release notes automation (#6070)
1 parent dcdaaae commit c5acd9f

File tree

2 files changed

+206
-3
lines changed

2 files changed

+206
-3
lines changed

.github/workflows/prepare-release.yml

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ jobs:
156156
token: ${{ secrets[needs.automation.outputs.token-secret-name] }}
157157

158158
- name: Update release date
159-
id: create-tag
160159
shell: pwsh
161160
run: |
162161
Import-Module .\build\scripts\prepare-release.psm1
@@ -169,3 +168,42 @@ jobs:
169168
-gitUserName '${{ needs.automation.outputs.username }}' `
170169
-gitUserEmail '${{ needs.automation.outputs.email }}'
171170
171+
update-releasenotes-on-prepare-pr-post-notice:
172+
runs-on: ubuntu-22.04
173+
174+
needs: automation
175+
176+
if: |
177+
github.event_name == 'issue_comment'
178+
&& github.event.issue.pull_request
179+
&& github.event.issue.state == 'open'
180+
&& github.event.comment.user.login != needs.automation.outputs.username
181+
&& contains(github.event.comment.body, '/UpdateReleaseNotes')
182+
&& startsWith(github.event.issue.title, '[release] Prepare release ')
183+
&& github.event.issue.pull_request.merged_at == null
184+
&& needs.automation.outputs.enabled
185+
186+
env:
187+
GH_TOKEN: ${{ secrets[needs.automation.outputs.token-secret-name] }}
188+
189+
steps:
190+
- name: check out code
191+
uses: actions/checkout@v4
192+
with:
193+
# Note: By default GitHub only fetches 1 commit which fails the git tag operation below
194+
fetch-depth: 0
195+
token: ${{ secrets[needs.automation.outputs.token-secret-name] }}
196+
197+
- name: Update release notes
198+
shell: pwsh
199+
run: |
200+
Import-Module .\build\scripts\prepare-release.psm1
201+
202+
UpdateReleaseNotesAndPostNoticeOnPullRequest `
203+
-gitRepository '${{ github.repository }}' `
204+
-pullRequestNumber '${{ github.event.issue.number }}' `
205+
-botUserName '${{ needs.automation.outputs.username }}' `
206+
-commentUserName '${{ github.event.comment.user.login }}' `
207+
-commentBody '${{ github.event.comment.body }}' `
208+
-gitUserName '${{ needs.automation.outputs.username }}' `
209+
-gitUserEmail '${{ needs.automation.outputs.email }}'

build/scripts/prepare-release.psm1

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function CreatePullRequestToUpdateChangelogsAndPublicApis {
1515
throw 'Input version did not match expected format'
1616
}
1717

18+
$isPrerelease = $version -match '-alpha' -or $version -match '-beta' -or $version -match '-rc'
1819
$tag="${minVerTagPrefix}${version}"
1920
$branch="release/prepare-${tag}-release"
2021

@@ -48,7 +49,7 @@ Requested by: @$requestedByUserName
4849
& ./build/scripts/update-changelogs.ps1 -minVerTagPrefix $minVerTagPrefix -version $version
4950

5051
# Update publicApi files for stable releases
51-
if ($version -notlike "*-alpha*" -and $version -notlike "*-beta*" -and $version -notlike "*-rc*")
52+
if ($isPrerelease -ne $true)
5253
{
5354
& ./build/scripts/finalize-publicapi.ps1 -minVerTagPrefix $minVerTagPrefix
5455

@@ -61,6 +62,20 @@ Requested by: @$requestedByUserName
6162
## Commands
6263
6364
``/UpdateReleaseDates``: Use to update release dates in CHANGELOGs before merging [``approvers``, ``maintainers``]
65+
"@
66+
67+
if ($minVerTagPrefix -eq 'core-' -and $isPrerelease -ne $true)
68+
{
69+
$body +=
70+
@"
71+
72+
``/UpdateReleaseNotes``: Use to update ``RELEASENOTES.md`` before merging [``approvers``, ``maintainers``]
73+
"@
74+
}
75+
76+
$body +=
77+
@"
78+
6479
``/CreateReleaseTag``: Use after merging to push the release tag and trigger the job to create packages [``approvers``, ``maintainers``]
6580
``/PushPackages``: Use after the created packages have been validated to push to NuGet [``maintainers``]
6681
"@
@@ -77,12 +92,42 @@ Requested by: @$requestedByUserName
7792
throw 'git push failure'
7893
}
7994

80-
gh pr create `
95+
$createPullRequestResponse = gh pr create `
8196
--title "[release] Prepare release $tag" `
8297
--body $body `
8398
--base $targetBranch `
8499
--head $branch `
85100
--label release
101+
102+
Write-Host $createPullRequestResponse
103+
104+
$match = [regex]::Match($createPullRequestResponse, "\/pull\/(.*)$")
105+
if ($match.Success -eq $false)
106+
{
107+
throw 'Could not parse pull request number from gh pr create response'
108+
}
109+
110+
$pullRequestNumber = $match.Groups[1].Value
111+
112+
if ($minVerTagPrefix -eq 'core-' -and $isPrerelease -ne $true)
113+
{
114+
$found = Select-String -Path "RELEASENOTES.md" -Pattern "## $version" -Quiet
115+
if ($found -eq $false)
116+
{
117+
$body =
118+
@"
119+
I noticed this PR is releasing a stable version of core packages but there isn't any content in ``RELEASENOTES.md`` for the version being released.
120+
121+
It is important to update ``RELEASENOTES.md`` before creating the release tag because a permalink will become part of the package(s).
122+
123+
Post a comment with "/UpdateReleaseNotes" in the body if you would like me to update release notes.
124+
125+
Note: In the comment everything below "/UpdateReleaseNotes" will be added to ``RELEASENOTES.md`` for the version being released. If something is already there it will be replaced.
126+
"@
127+
128+
gh pr comment $pullRequestNumber --body $body
129+
}
130+
}
86131
}
87132

88133
Export-ModuleMember -Function CreatePullRequestToUpdateChangelogsAndPublicApis
@@ -294,3 +339,123 @@ Released $(Get-Date -UFormat '%Y-%b-%d')
294339
}
295340

296341
Export-ModuleMember -Function UpdateChangelogReleaseDatesAndPostNoticeOnPullRequest
342+
343+
function UpdateReleaseNotesAndPostNoticeOnPullRequest {
344+
param(
345+
[Parameter(Mandatory=$true)][string]$gitRepository,
346+
[Parameter(Mandatory=$true)][string]$pullRequestNumber,
347+
[Parameter(Mandatory=$true)][string]$botUserName,
348+
[Parameter(Mandatory=$true)][string]$commentUserName,
349+
[Parameter(Mandatory=$true)][string]$commentBody,
350+
[Parameter()][string]$gitUserName,
351+
[Parameter()][string]$gitUserEmail
352+
)
353+
354+
$prViewResponse = gh pr view $pullRequestNumber --json headRefName,author,title | ConvertFrom-Json
355+
356+
if ($prViewResponse.author.login -ne $botUserName)
357+
{
358+
throw 'PR author was unexpected'
359+
}
360+
361+
$match = [regex]::Match($prViewResponse.title, '^\[release\] Prepare release (.*)$')
362+
if ($match.Success -eq $false)
363+
{
364+
throw 'Could not parse tag from PR title'
365+
}
366+
367+
$tag = $match.Groups[1].Value
368+
369+
$match = [regex]::Match($tag, '^(.*?-)(.*)$')
370+
if ($match.Success -eq $false)
371+
{
372+
throw 'Could not parse prefix or version from tag'
373+
}
374+
375+
$tagPrefix = $match.Groups[1].Value
376+
$version = $match.Groups[2].Value
377+
$isPrerelease = $version -match '-alpha' -or $version -match '-beta' -or $version -match '-rc'
378+
379+
$commentUserPermission = gh api "repos/$gitRepository/collaborators/$commentUserName/permission" | ConvertFrom-Json
380+
if ($commentUserPermission.permission -ne 'admin' -and $commentUserPermission.permission -ne 'write')
381+
{
382+
gh pr comment $pullRequestNumber `
383+
--body "I'm sorry @$commentUserName but you don't have permission to update this PR. Only maintainers and approvers can update this PR."
384+
return
385+
}
386+
387+
if ($tagPrefix -ne 'core-' -or $isPrerelease -eq $true)
388+
{
389+
gh pr comment $pullRequestNumber `
390+
--body "I'm sorry @$commentUserName but we don't typically add release notes for prereleases or unstable packages."
391+
return
392+
}
393+
394+
if ([string]::IsNullOrEmpty($gitUserName) -eq $false)
395+
{
396+
git config user.name $gitUserName
397+
}
398+
if ([string]::IsNullOrEmpty($gitUserEmail) -eq $false)
399+
{
400+
git config user.email $gitUserEmail
401+
}
402+
403+
git switch $prViewResponse.headRefName 2>&1 | % ToString
404+
if ($LASTEXITCODE -gt 0)
405+
{
406+
throw 'git switch failure'
407+
}
408+
409+
$releaseNotesContent = (Get-Content -Path "RELEASENOTES.md" -Raw)
410+
411+
$match = [regex]::Match($commentBody, '[\w\W\s]*\/UpdateReleaseNotes.*$([\w\W\s]*)', [Text.RegularExpressions.RegexOptions]::Multiline)
412+
if ($match.Success -eq $false)
413+
{
414+
throw 'Could not find release notes content'
415+
}
416+
417+
$content = $match.Groups[1].Value.Trim() -replace "`r`n", "`n"
418+
419+
$body =
420+
@"
421+
## $version
422+
423+
$content
424+
425+
##
426+
"@
427+
428+
$match = [regex]::Match($releaseNotesContent, "(## $version[\w\W\s]*?)##", [Text.RegularExpressions.RegexOptions]::Multiline)
429+
if ($match.Success -eq $true)
430+
{
431+
$content = [regex]::Replace($releaseNotesContent, "(## $version[\w\W\s]*?)##", $body, [Text.RegularExpressions.RegexOptions]::Multiline)
432+
Set-Content -Path "RELEASENOTES.md" -Value $content.TrimEnd()
433+
}
434+
else {
435+
$match = [regex]::Match($releaseNotesContent, '(# Release Notes[\w\W\s]*?)##', [Text.RegularExpressions.RegexOptions]::Multiline)
436+
if ($match.Success -eq $false)
437+
{
438+
throw 'Could not find release notes header'
439+
}
440+
441+
$body = $match.Groups[1].Value + $body
442+
$content = [regex]::Replace($releaseNotesContent, '(# Release Notes[\w\W\s]*?)##', $body, [Text.RegularExpressions.RegexOptions]::Multiline)
443+
Set-Content -Path "RELEASENOTES.md" -Value $content.TrimEnd()
444+
}
445+
446+
git commit -a -m "Update RELEASENOTES for $tag." 2>&1 | % ToString
447+
if ($LASTEXITCODE -gt 0)
448+
{
449+
throw 'git commit failure'
450+
}
451+
452+
git push -u origin $prViewResponse.headRefName 2>&1 | % ToString
453+
if ($LASTEXITCODE -gt 0)
454+
{
455+
throw 'git push failure'
456+
}
457+
458+
gh pr comment $pullRequestNumber --body "I updated ``RELEASENOTES.md``."
459+
}
460+
461+
Export-ModuleMember -Function UpdateReleaseNotesAndPostNoticeOnPullRequest

0 commit comments

Comments
 (0)