diff --git a/.github/workflows/PublishModuleToPowerShellGallery.yaml b/.github/workflows/PublishModuleToPowerShellGallery.yaml index ef7fd63..df7c81f 100644 --- a/.github/workflows/PublishModuleToPowerShellGallery.yaml +++ b/.github/workflows/PublishModuleToPowerShellGallery.yaml @@ -54,21 +54,24 @@ jobs: shell: bash env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.version }} run: | - if gh release view "v${{ steps.version.outputs.version }}" > /dev/null 2>&1; then + if gh release view "v$VERSION" > /dev/null 2>&1; then echo "exists=true" >> $GITHUB_OUTPUT - echo "GitHub release v${{ steps.version.outputs.version }} already exists" + echo "GitHub release v$VERSION already exists" else echo "exists=false" >> $GITHUB_OUTPUT - echo "GitHub release v${{ steps.version.outputs.version }} does not exist" + echo "GitHub release v$VERSION does not exist" fi - name: Check if PSGallery Version Exists id: check_psgallery if: steps.check_release.outputs.exists == 'false' shell: pwsh + env: + VERSION: ${{ steps.version.outputs.version }} run: | - $version = "${{ steps.version.outputs.version }}" + $version = $env:VERSION $published = Find-Module -Name {{ModuleName}} -RequiredVersion $version -Repository PSGallery -ErrorAction SilentlyContinue if ($published) { Write-Host "PSGallery version $version already exists" @@ -85,13 +88,60 @@ jobs: - name: Create GitHub Release if: steps.check_release.outputs.exists == 'false' - shell: bash + shell: pwsh env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPOSITORY: ${{ github.repository }} + VERSION: ${{ steps.version.outputs.version }} run: | - gh release create "v${{ steps.version.outputs.version }}" \ - --title "v${{ steps.version.outputs.version }}" \ - --generate-notes + $version = $env:VERSION + + # Build release notes from this version's CHANGELOG.md section so the release + # body carries only the curated, user-facing entries (not the full PR list that + # --generate-notes produces, which is dominated by bot/CI/chore PRs). + # Read defensively: a missing/unreadable CHANGELOG.md must fall back to + # --generate-notes (below), never fail the publish. + $changelogLines = $null + if (Test-Path -LiteralPath './CHANGELOG.md') { + try { + $changelogLines = Get-Content -LiteralPath './CHANGELOG.md' -ErrorAction Stop + } + catch { + Write-Host "::warning::Could not read CHANGELOG.md ($($_.Exception.Message)); falling back to auto-generated notes." + } + } + $captured = [System.Collections.Generic.List[string]]::new() + if ($changelogLines) { + $headerPattern = '^##\s+\[' + [regex]::Escape($version) + '\]' + $capturing = $false + foreach ($line in $changelogLines) { + if (-not $capturing) { + if ($line -match $headerPattern) { $capturing = $true } + continue + } + if ($line -match '^##\s+\[') { break } # next version header ends the section + $captured.Add($line) + } + } + $body = ($captured -join "`n").Trim() + + if ([string]::IsNullOrWhiteSpace($body)) { + Write-Host "::warning::No CHANGELOG.md section found for $version; falling back to auto-generated notes." + gh release create "v$version" --title "v$version" --generate-notes + } + else { + # Append a compare link against the most recent existing tag. The v$version + # tag does not exist yet (this step creates it), so the latest tag is the + # previous release. + $previousTag = git tag --list 'v*' --sort=-version:refname | + Where-Object { $_ -ne "v$version" } | + Select-Object -First 1 + if ($previousTag) { + $body += "`n`n**Full Changelog**: https://github.com/$env:REPOSITORY/compare/$previousTag...v$version" + } + Set-Content -LiteralPath './release-notes.md' -Value $body -Encoding utf8 + gh release create "v$version" --title "v$version" --notes-file './release-notes.md' + } - name: Publish to PSGallery if: steps.check_release.outputs.exists == 'false' && steps.check_psgallery.outputs.exists == 'false' diff --git a/build.depend.psd1 b/build.depend.psd1 index 22be15d..29e4712 100644 --- a/build.depend.psd1 +++ b/build.depend.psd1 @@ -25,4 +25,9 @@ 'PSScriptAnalyzer' = @{ Version = '1.25.0' } + # Parses CHANGELOG.md (Keep a Changelog format) so the Publish task can populate the + # built manifest's PSData.ReleaseNotes from the matching version's entry. + 'ChangelogManagement' = @{ + Version = '3.1.0' + } } diff --git a/build.psake.ps1 b/build.psake.ps1 index cad18bc..cc5f0ce 100644 --- a/build.psake.ps1 +++ b/build.psake.ps1 @@ -45,5 +45,50 @@ Task -Name 'Init_Integration' -Description 'Load integration test environment va } } +# Populate the built manifest's ReleaseNotes from the matching CHANGELOG.md entry so the +# PowerShell Gallery release-notes panel shows the curated, user-facing notes (the same +# content used for the GitHub release) instead of just a link. Depends on Build so the +# staged manifest in ModuleOutDir exists; runs before Publish (see $PSBPublishDependency +# below). Non-fatal if the changelog can't be read or has no entry for the version being +# published, so a release is never blocked. +Task -Name 'UpdateReleaseNotes' -Depends 'Build' -Description 'Set built manifest ReleaseNotes from the matching CHANGELOG.md entry' { + $changelogPath = Join-Path -Path $PSScriptRoot -ChildPath 'CHANGELOG.md' + if (-not (Test-Path -Path $changelogPath)) { + Write-Warning 'CHANGELOG.md not found; leaving ReleaseNotes unchanged.' + return + } + + $moduleVersion = $PSBPreference.General.ModuleVersion + try { + Import-Module -Name 'ChangelogManagement' -ErrorAction Stop + $changelogData = Get-ChangelogData -Path $changelogPath -ErrorAction Stop + } + catch { + Write-Warning "Could not read CHANGELOG.md ($($_.Exception.Message)); leaving ReleaseNotes unchanged." + return + } + + $releaseEntry = $changelogData.Released | + Where-Object { [string]$_.Version -eq [string]$moduleVersion } | + Select-Object -First 1 + if (-not $releaseEntry) { + Write-Warning "No CHANGELOG.md entry found for version $moduleVersion; leaving ReleaseNotes unchanged." + return + } + + $releaseNotes = $releaseEntry.RawData.Trim() + if ([string]::IsNullOrWhiteSpace($releaseNotes)) { + Write-Warning "CHANGELOG.md entry for version $moduleVersion is empty; leaving ReleaseNotes unchanged." + return + } + $builtManifest = Join-Path -Path $PSBPreference.Build.ModuleOutDir -ChildPath "$($PSBPreference.General.ModuleName).psd1" + Update-ModuleManifest -Path $builtManifest -ReleaseNotes $releaseNotes -ErrorAction Stop + Write-Host " Set ReleaseNotes on built manifest from CHANGELOG [$($releaseEntry.Version)] ($($releaseNotes.Length) chars)" -ForegroundColor Gray +} + +# Inject ReleaseNotes into the built manifest before publishing (PowerShellBuild's Publish +# defaults to depending only on 'Test'). +$PSBPublishDependency = @('Test', 'UpdateReleaseNotes') + # Note: -Depends replaces PowerShellBuild's default dependencies, so we must include Pester and Analyze explicitly Task -Name 'Test' -FromModule 'PowerShellBuild' -MinimumVersion '0.7.3' -Depends 'Init_Integration', 'Pester', 'Analyze'