Skip to content

Commit 838114d

Browse files
tablackburnclaude
andauthored
ci(release): build release notes from CHANGELOG; auto-populate PSData.ReleaseNotes; harden version expansion (#19)
* ci(release): build release notes from CHANGELOG and harden version expansion Adopts the release-tooling pattern from PowerShellModuleTemplate. - Create GitHub Release: extract the published version's CHANGELOG.md section and pass it via --notes-file (pwsh) instead of --generate-notes. Reads defensively (Test-Path + try/catch -> falls back to --generate-notes if missing/unreadable) and excludes the current tag from the compare-link base. - Pass the version output via env (VERSION) in the release-check, PSGallery-check, and create-release steps instead of inlining ${{ steps.version.outputs.version }} (template-injection class). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(release): populate PSData.ReleaseNotes from CHANGELOG at publish time So the PowerShell Gallery release-notes panel shows curated, user-facing notes per version (matching the GitHub release) instead of a static link. - build.depend.psd1: add ChangelogManagement 3.1.0. - build.psake.ps1: new UpdateReleaseNotes task (Depends Build) that sets the built manifest's PrivateData.PSData.ReleaseNotes from the matching CHANGELOG entry via Update-ModuleManifest, wired in through $PSBPublishDependency. Non-fatal at every step so a release is never blocked. Verified: ChangelogManagement parses this repo's CHANGELOG. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(release): include manifest path in the ReleaseNotes-write failure warning The catch warning now names the built manifest (like the missing-path warning) so logs identify which file failed to update. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a0611f7 commit 838114d

3 files changed

Lines changed: 117 additions & 8 deletions

File tree

.github/workflows/PublishModuleToPowerShellGallery.yaml

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,24 @@ jobs:
3535
shell: bash
3636
env:
3737
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
VERSION: ${{ steps.version.outputs.version }}
3839
run: |
39-
if gh release view "v${{ steps.version.outputs.version }}" > /dev/null 2>&1; then
40+
if gh release view "v$VERSION" > /dev/null 2>&1; then
4041
echo "exists=true" >> $GITHUB_OUTPUT
41-
echo "GitHub release v${{ steps.version.outputs.version }} already exists"
42+
echo "GitHub release v$VERSION already exists"
4243
else
4344
echo "exists=false" >> $GITHUB_OUTPUT
44-
echo "GitHub release v${{ steps.version.outputs.version }} does not exist"
45+
echo "GitHub release v$VERSION does not exist"
4546
fi
4647
4748
- name: Check if PSGallery Version Exists
4849
id: check_psgallery
4950
if: steps.check_release.outputs.exists == 'false'
5051
shell: pwsh
52+
env:
53+
VERSION: ${{ steps.version.outputs.version }}
5154
run: |
52-
$version = "${{ steps.version.outputs.version }}"
55+
$version = $env:VERSION
5356
$published = Find-Module -Name ReScenePS -RequiredVersion $version -Repository PSGallery -ErrorAction SilentlyContinue
5457
if ($published) {
5558
Write-Host "PSGallery version $version already exists"
@@ -66,13 +69,59 @@ jobs:
6669

6770
- name: Create GitHub Release
6871
if: steps.check_release.outputs.exists == 'false'
69-
shell: bash
72+
shell: pwsh
7073
env:
7174
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75+
REPOSITORY: ${{ github.repository }}
76+
VERSION: ${{ steps.version.outputs.version }}
7277
run: |
73-
gh release create "v${{ steps.version.outputs.version }}" \
74-
--title "v${{ steps.version.outputs.version }}" \
75-
--generate-notes
78+
$version = $env:VERSION
79+
80+
# Build release notes from this version's CHANGELOG.md section so the release
81+
# body carries only the curated, user-facing entries (not the full PR list that
82+
# --generate-notes produces, which is dominated by bot/CI/chore PRs).
83+
# Read defensively: a missing/unreadable CHANGELOG.md must fall back to
84+
# --generate-notes (below), never fail the publish.
85+
$changelogLines = $null
86+
if (Test-Path -LiteralPath './CHANGELOG.md') {
87+
try {
88+
$changelogLines = Get-Content -LiteralPath './CHANGELOG.md' -ErrorAction Stop
89+
}
90+
catch {
91+
Write-Host "::warning::Could not read CHANGELOG.md ($($_.Exception.Message)); falling back to auto-generated notes."
92+
}
93+
}
94+
$captured = [System.Collections.Generic.List[string]]::new()
95+
if ($changelogLines) {
96+
$headerPattern = '^##\s+\[' + [regex]::Escape($version) + '\]'
97+
$capturing = $false
98+
foreach ($line in $changelogLines) {
99+
if (-not $capturing) {
100+
if ($line -match $headerPattern) { $capturing = $true }
101+
continue
102+
}
103+
if ($line -match '^##\s+\[') { break } # next version header ends the section
104+
$captured.Add($line)
105+
}
106+
}
107+
$body = ($captured -join "`n").Trim()
108+
109+
if ([string]::IsNullOrWhiteSpace($body)) {
110+
Write-Host "::warning::No CHANGELOG.md section found for $version; falling back to auto-generated notes."
111+
gh release create "v$version" --title "v$version" --generate-notes
112+
}
113+
else {
114+
# Append a compare link against the most recent existing tag, excluding the
115+
# current version's tag (which may already exist on a re-run).
116+
$previousTag = git tag --list 'v*' --sort=-version:refname |
117+
Where-Object { $_ -ne "v$version" } |
118+
Select-Object -First 1
119+
if ($previousTag) {
120+
$body += "`n`n**Full Changelog**: https://github.com/$env:REPOSITORY/compare/$previousTag...v$version"
121+
}
122+
Set-Content -LiteralPath './release-notes.md' -Value $body -Encoding utf8
123+
gh release create "v$version" --title "v$version" --notes-file './release-notes.md'
124+
}
76125
77126
- name: Publish to PSGallery
78127
if: steps.check_release.outputs.exists == 'false' && steps.check_psgallery.outputs.exists == 'false'

build.depend.psd1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
'PSScriptAnalyzer' = @{
2626
Version = '1.24.0'
2727
}
28+
# Parses CHANGELOG.md (Keep a Changelog format) so the Publish task can populate the
29+
# built manifest's PSData.ReleaseNotes from the matching version's entry.
30+
'ChangelogManagement' = @{
31+
Version = '3.1.0'
32+
}
2833
'PlexAutomationToolkit' = @{
2934
Version = '0.6.3'
3035
}

build.psake.ps1

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,60 @@ Task -Name 'DownloadTestTools' -Description 'Download test dependencies (UnRAR)'
146146
}
147147
}
148148

149+
# Populate the built manifest's ReleaseNotes from the matching CHANGELOG.md entry so the
150+
# PowerShell Gallery release-notes panel shows the curated, user-facing notes (the same
151+
# content used for the GitHub release) instead of just a link. Depends on Build so the
152+
# staged manifest in ModuleOutDir exists; runs before Publish (see $PSBPublishDependency
153+
# below). Non-fatal at every step so a release is never blocked.
154+
Task -Name 'UpdateReleaseNotes' -Depends 'Build' -Description 'Set built manifest ReleaseNotes from the matching CHANGELOG.md entry' {
155+
$changelogPath = Join-Path -Path $PSScriptRoot -ChildPath 'CHANGELOG.md'
156+
if (-not (Test-Path -Path $changelogPath)) {
157+
Write-Warning 'CHANGELOG.md not found; leaving ReleaseNotes unchanged.'
158+
return
159+
}
160+
161+
$moduleVersion = $PSBPreference.General.ModuleVersion
162+
try {
163+
Import-Module -Name 'ChangelogManagement' -ErrorAction Stop
164+
$changelogData = Get-ChangelogData -Path $changelogPath -ErrorAction Stop
165+
}
166+
catch {
167+
Write-Warning "Could not read CHANGELOG.md ($($_.Exception.Message)); leaving ReleaseNotes unchanged."
168+
return
169+
}
170+
171+
$releaseEntry = $changelogData.Released |
172+
Where-Object { [string]$_.Version -eq [string]$moduleVersion } |
173+
Select-Object -First 1
174+
if (-not $releaseEntry) {
175+
Write-Warning "No CHANGELOG.md entry found for version $moduleVersion; leaving ReleaseNotes unchanged."
176+
return
177+
}
178+
179+
$releaseNotes = $releaseEntry.RawData.Trim()
180+
if ([string]::IsNullOrWhiteSpace($releaseNotes)) {
181+
Write-Warning "CHANGELOG.md entry for version $moduleVersion is empty; leaving ReleaseNotes unchanged."
182+
return
183+
}
184+
$builtManifest = Join-Path -Path $PSBPreference.Build.ModuleOutDir -ChildPath "$($PSBPreference.General.ModuleName).psd1"
185+
if (-not (Test-Path -Path $builtManifest)) {
186+
Write-Warning "Built manifest not found at '$builtManifest'; leaving ReleaseNotes unchanged."
187+
return
188+
}
189+
try {
190+
Update-ModuleManifest -Path $builtManifest -ReleaseNotes $releaseNotes -ErrorAction Stop
191+
Write-Host " Set ReleaseNotes on built manifest from CHANGELOG [$($releaseEntry.Version)] ($($releaseNotes.Length) chars)" -ForegroundColor Gray
192+
}
193+
catch {
194+
# Keep publishing unblocked: a failure here just leaves the manifest's existing
195+
# ReleaseNotes in place rather than aborting the release.
196+
Write-Warning "Failed to set ReleaseNotes on the built manifest '$builtManifest' ($($_.Exception.Message)); leaving it unchanged."
197+
}
198+
}
199+
200+
# Inject ReleaseNotes into the built manifest before publishing (PowerShellBuild's Publish
201+
# defaults to depending only on 'Test').
202+
$PSBPublishDependency = @('Test', 'UpdateReleaseNotes')
203+
149204
# Import the Test task from PowerShellBuild (uses $PSBTestDependency which includes Pester and Analyze)
150205
Task -Name 'Test' -FromModule 'PowerShellBuild' -MinimumVersion '0.7.3'

0 commit comments

Comments
 (0)