|
2 | 2 | 'PSUseDeclaredVarsMoreThanAssignments', 'psGalleryApiKey', |
3 | 3 | Justification = 'Variable is used in script blocks.' |
4 | 4 | )] |
| 5 | +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( |
| 6 | + 'PSUseDeclaredVarsMoreThanAssignments', 'publicationState', |
| 7 | + Justification = 'The verified state is shared across logging script blocks.' |
| 8 | +)] |
5 | 9 | [CmdletBinding()] |
6 | 10 | param() |
7 | 11 |
|
8 | 12 | $PSStyle.OutputRendering = 'Ansi' |
| 13 | +$ErrorActionPreference = 'Stop' |
9 | 14 |
|
10 | 15 | Import-Module -Name 'PSModule' -Force |
11 | 16 | Import-Module -Name "$PSScriptRoot/Publish-PSModule.Helpers.psm1" -Force |
| 17 | +Import-Module -Name ([IO.Path]::Combine( |
| 18 | + $PSScriptRoot, '..', '..', 'Release-PSModule', 'src', 'Release-PSModule.Helpers.psm1' |
| 19 | + )) -Force |
12 | 20 |
|
13 | 21 | #region Load inputs |
14 | 22 | LogGroup 'Load inputs' { |
@@ -36,6 +44,17 @@ LogGroup 'Load inputs' { |
36 | 44 | $modulePath = Resolve-Path -Path $modulePathCandidate | Select-Object -ExpandProperty Path |
37 | 45 | $psGalleryApiKey = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_PSGALLERY_API_KEY |
38 | 46 | $whatIf = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_WhatIf -eq 'true' |
| 47 | + $releaseTag = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_ReleaseTag |
| 48 | + $sourceCommit = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_CommitSha |
| 49 | + if ([string]::IsNullOrWhiteSpace($sourceCommit)) { |
| 50 | + $sourceCommit = $env:GITHUB_SHA |
| 51 | + } |
| 52 | + if ([string]::IsNullOrWhiteSpace($releaseTag)) { |
| 53 | + throw 'ReleaseTag is required. Pass the already-resolved Publish.Module.Resolution.FullVersion.' |
| 54 | + } |
| 55 | + if ([string]::IsNullOrWhiteSpace($sourceCommit)) { |
| 56 | + throw 'CommitSha or the original GITHUB_SHA is required to establish the tested artifact source.' |
| 57 | + } |
39 | 58 |
|
40 | 59 | Write-Host "Module name: [$name]" |
41 | 60 | Write-Host "Module path: [$modulePath]" |
@@ -130,58 +149,80 @@ LogGroup 'Resolve version from manifest' { |
130 | 149 | } |
131 | 150 | #endregion Resolve version from manifest |
132 | 151 |
|
133 | | -#region Install module dependencies |
134 | | -LogGroup 'Install module dependencies' { |
135 | | - Resolve-PSModuleDependency -ManifestFilePath $manifestFilePath |
136 | | -} |
137 | | -#endregion Install module dependencies |
| 152 | +$zipDirectory = $null |
| 153 | +$zipPath = $null |
| 154 | +try { |
| 155 | + LogGroup 'Source-bound publication preflight' { |
| 156 | + if ($whatIf) { |
| 157 | + Write-Host "WhatIf: Verify source [$sourceCommit], tag [$releaseTag], and the tested module before publication." |
| 158 | + } else { |
| 159 | + $zipDirectory = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath "PSModulePreflight-$([guid]::NewGuid())" |
| 160 | + $null = New-Item -Path $zipDirectory -ItemType Directory |
| 161 | + $zipFileName = "$name-$publishPSVersion.zip" |
| 162 | + $zipPath = Join-Path -Path $zipDirectory -ChildPath $zipFileName |
| 163 | + $artifactDigest = Get-PSModuleArtifactDigest -Path $modulePath |
| 164 | + $null = New-PSModuleReleaseArchive -Path $modulePath -DestinationPath $zipPath |
| 165 | + $claim = [pscustomobject]@{ |
| 166 | + Repository = $env:GITHUB_REPOSITORY |
| 167 | + PullRequest = if ($null -eq $prNumber) { 0 } else { $prNumber } |
| 168 | + CommitSha = $sourceCommit |
| 169 | + ModuleName = $name |
| 170 | + GalleryVersion = $publishPSVersion |
| 171 | + ReleaseTag = $releaseTag |
| 172 | + ReleaseType = if ($createPrerelease) { 'Prerelease' } else { 'Release' } |
| 173 | + ArtifactSha256 = $artifactDigest |
| 174 | + AssetName = $zipFileName |
| 175 | + AssetSha256 = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant() |
| 176 | + } |
| 177 | + $publicationReader = Get-PSModulePublicationReader -Repository $env:GITHUB_REPOSITORY |
| 178 | + $publicationState = Get-PSModulePublicationState -ExpectedClaim $claim -Reader $publicationReader |
| 179 | + } |
| 180 | + } |
138 | 181 |
|
139 | | -#region Publish to PSGallery |
140 | | -LogGroup 'Publish to PSGallery' { |
141 | | - $releaseType = if ($createPrerelease) { 'New prerelease' } else { 'New release' } |
142 | | - $psGalleryReleaseLink = "https://www.powershellgallery.com/packages/$name/$publishPSVersion" |
| 182 | + LogGroup 'Install module dependencies' { |
| 183 | + Resolve-PSModuleDependency -ManifestFilePath $manifestFilePath |
| 184 | + } |
143 | 185 |
|
144 | | - Write-Host 'Publish module to PowerShell Gallery using API key from environment.' |
145 | | - if ($whatIf) { |
146 | | - Write-Host "Publish-PSResource -Path $modulePath -Repository PSGallery -ApiKey ***" |
147 | | - } else { |
148 | | - # A version that is not on the Gallery is the expected state for a new release, but PSResourceGet |
149 | | - # reports it as a PackageNotFound error, which -ErrorAction Stop turns into a throw. Only that error |
150 | | - # may be treated as 'not published'; any other failure (for example a Gallery outage) must stay fatal, |
151 | | - # otherwise a version that already exists would be re-published and fail the upload with a conflict. |
152 | | - $publishedPackage = $null |
153 | | - try { |
154 | | - $publishedPackage = Find-PSResource -Name $name -Version $publishPSVersion -Repository PSGallery -ErrorAction Stop |
155 | | - } catch { |
156 | | - if ($_.FullyQualifiedErrorId -notlike 'PackageNotFound,*') { throw } |
157 | | - Write-Host "$name $publishPSVersion is not on the PowerShell Gallery yet." |
158 | | - } |
| 186 | + LogGroup 'Publish to PSGallery' { |
| 187 | + $releaseType = if ($createPrerelease) { 'New prerelease' } else { 'New release' } |
| 188 | + $psGalleryReleaseLink = "https://www.powershellgallery.com/packages/$name/$publishPSVersion" |
159 | 189 |
|
160 | | - if ($publishedPackage) { |
161 | | - Write-Host ( |
162 | | - "::notice title=♻️ Resuming Gallery-only publication::$name $publishPSVersion is already " + |
163 | | - 'published to the PowerShell Gallery.' |
164 | | - ) |
| 190 | + if ($whatIf) { |
| 191 | + Write-Host "WhatIf: Create-only annotated claim for [$releaseTag]; retain any existing published bytes." |
| 192 | + Write-Host "Publish-PSResource -Path $modulePath -Repository PSGallery -ApiKey ***" |
165 | 193 | } else { |
166 | | - Publish-PSResource -Path $modulePath -Repository PSGallery -ApiKey $psGalleryApiKey |
| 194 | + $publicationState = New-PSModulePublicationTag -Claim $claim -State $publicationState -Reader $publicationReader |
| 195 | + $currentState = Get-PSModulePublicationState -ExpectedClaim $claim -Reader $publicationReader |
| 196 | + if ($currentState.TagSha -cne $publicationState.TagSha -or |
| 197 | + (Get-PSModuleArtifactDigest -Path $modulePath) -cne $claim.ArtifactSha256) { |
| 198 | + throw 'The publication claim or tested artifact changed before the Gallery write. Refusing publication.' |
| 199 | + } |
| 200 | + if ($null -ne $currentState.Package) { |
| 201 | + Write-Host "Retaining the proven identical Gallery package [$name] [$publishPSVersion]." |
| 202 | + } else { |
| 203 | + Publish-PSResource -Path $modulePath -Repository PSGallery -ApiKey $psGalleryApiKey -ErrorAction Stop |
| 204 | + } |
167 | 205 | } |
168 | | - } |
169 | 206 |
|
170 | | - if ($whatIf -and $prNumber) { |
171 | | - Write-Host ( |
172 | | - "gh pr comment $prNumber -b " + |
173 | | - "'✅ $releaseType`: PowerShell Gallery - [$name $publishPSVersion]($psGalleryReleaseLink)'" |
174 | | - ) |
175 | | - } elseif (-not $whatIf -and $prNumber) { |
176 | | - Write-Host "::notice title=✅ $releaseType`: PowerShell Gallery - $name $publishPSVersion::$psGalleryReleaseLink" |
177 | | - gh pr comment $prNumber -b "✅ $releaseType`: PowerShell Gallery - [$name $publishPSVersion]($psGalleryReleaseLink)" |
178 | | - if ($LASTEXITCODE -ne 0) { |
179 | | - Write-Error 'Failed to comment on the pull request.' |
180 | | - exit $LASTEXITCODE |
| 207 | + if ($whatIf -and $prNumber) { |
| 208 | + Write-Host ( |
| 209 | + "gh pr comment $prNumber -b " + |
| 210 | + "'✅ $releaseType`: PowerShell Gallery - [$name $publishPSVersion]($psGalleryReleaseLink)'" |
| 211 | + ) |
| 212 | + } elseif (-not $whatIf -and $prNumber) { |
| 213 | + gh pr comment $prNumber -b "✅ $releaseType`: PowerShell Gallery - [$name $publishPSVersion]($psGalleryReleaseLink)" |
| 214 | + if ($LASTEXITCODE -ne 0) { |
| 215 | + throw 'Failed to comment on the pull request.' |
| 216 | + } |
181 | 217 | } |
182 | | - } else { |
183 | 218 | Write-Host "::notice title=✅ $releaseType`: PowerShell Gallery - $name $publishPSVersion::$psGalleryReleaseLink" |
184 | 219 | } |
| 220 | +} finally { |
| 221 | + if ($zipPath -and (Test-Path -LiteralPath $zipPath)) { |
| 222 | + Remove-Item -LiteralPath $zipPath -Force |
| 223 | + } |
| 224 | + if ($zipDirectory -and (Test-Path -LiteralPath $zipDirectory)) { |
| 225 | + Remove-Item -LiteralPath $zipDirectory -Force |
| 226 | + } |
185 | 227 | } |
186 | | -#endregion Publish to PSGallery |
187 | 228 | Write-Host "Gallery publishing complete. Version: [$publishPSVersion]" |
0 commit comments