Skip to content

Commit ee65fa7

Browse files
Preflight Gallery writes and preserve release-side ownership rechecks
1 parent 6b5ffcd commit ee65fa7

7 files changed

Lines changed: 511 additions & 250 deletions

File tree

‎.github/actions/Publish-PSModule/action.yml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ inputs:
1717
description: Normalized pull request context JSON from Get-PSModuleSettings.
1818
required: false
1919
default: ''
20+
ReleaseTag:
21+
description: Exact GitHub tag already resolved for the tested module artifact.
22+
required: true
23+
CommitSha:
24+
description: Immutable commit that produced the tested module artifact; defaults to the original workflow SHA.
25+
required: false
26+
default: ''
2027
WhatIf:
2128
description: If specified, the action will only log the changes it would make, but will not publish the module.
2229
required: false
@@ -55,5 +62,7 @@ runs:
5562
PSMODULE_PUBLISH_PSMODULE_INPUT_ModulePath: ${{ inputs.ModulePath }}
5663
PSMODULE_PUBLISH_PSMODULE_INPUT_PSGALLERY_API_KEY: ${{ inputs.PSGALLERY_API_KEY }}
5764
PSMODULE_PUBLISH_PSMODULE_INPUT_PullRequest: ${{ inputs.PullRequest }}
65+
PSMODULE_PUBLISH_PSMODULE_INPUT_ReleaseTag: ${{ inputs.ReleaseTag }}
66+
PSMODULE_PUBLISH_PSMODULE_INPUT_CommitSha: ${{ inputs.CommitSha || github.sha }}
5867
PSMODULE_PUBLISH_PSMODULE_INPUT_WhatIf: ${{ inputs.WhatIf }}
5968
run: ${{ github.action_path }}/src/publish.ps1

‎.github/actions/Publish-PSModule/src/publish.ps1‎

Lines changed: 85 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22
'PSUseDeclaredVarsMoreThanAssignments', 'psGalleryApiKey',
33
Justification = 'Variable is used in script blocks.'
44
)]
5+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
6+
'PSUseDeclaredVarsMoreThanAssignments', 'publicationState',
7+
Justification = 'The verified state is shared across logging script blocks.'
8+
)]
59
[CmdletBinding()]
610
param()
711

812
$PSStyle.OutputRendering = 'Ansi'
13+
$ErrorActionPreference = 'Stop'
914

1015
Import-Module -Name 'PSModule' -Force
1116
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
1220

1321
#region Load inputs
1422
LogGroup 'Load inputs' {
@@ -36,6 +44,17 @@ LogGroup 'Load inputs' {
3644
$modulePath = Resolve-Path -Path $modulePathCandidate | Select-Object -ExpandProperty Path
3745
$psGalleryApiKey = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_PSGALLERY_API_KEY
3846
$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+
}
3958

4059
Write-Host "Module name: [$name]"
4160
Write-Host "Module path: [$modulePath]"
@@ -130,58 +149,80 @@ LogGroup 'Resolve version from manifest' {
130149
}
131150
#endregion Resolve version from manifest
132151

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+
}
138181

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+
}
143185

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"
159189

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 ***"
165193
} 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+
}
167205
}
168-
}
169206

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+
}
181217
}
182-
} else {
183218
Write-Host "::notice title=✅ $releaseType`: PowerShell Gallery - $name $publishPSVersion::$psGalleryReleaseLink"
184219
}
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+
}
185227
}
186-
#endregion Publish to PSGallery
187228
Write-Host "Gallery publishing complete. Version: [$publishPSVersion]"

0 commit comments

Comments
 (0)