💥 [Major]: Drop version calculation, publish pre-stamped artifact, upload module zip to release (PSModule/Process-PSModule#326)#71
Conversation
…d upload module zip to GitHub release (PSModule/Process-PSModule#326)
There was a problem hiding this comment.
Pull request overview
Major-version bump that removes all version-calculation responsibility from Publish-PSModule. The action now consumes a pre-stamped module artifact (built by Build-PSModule v5+ after Resolve-PSModuleVersion), publishes it untouched to the PSGallery, creates a matching GitHub Release, and uploads the module as a zip asset so the released bytes match the tested bytes. The downstream Cleanup Prereleases step is reworked to derive the prerelease name from the PR head ref instead of stale PUBLISH_CONTEXT_* env vars.
Changes:
- Delete
src/init.ps1and remove all version/label/release-type inputs fromaction.yml. - Rewrite
src/publish.ps1to readModuleVersion/Prereleasefrom the manifest, publish viaPublish-PSResource, and upload<Name>-<Version>.zipto the release. - Update
src/cleanup.ps1to derive$prereleaseNamefrom the PR head ref and callgh release listitself.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/publish.ps1 | Reads stamped version from manifest, no longer mutates it; adds zip-and-upload to release. |
| src/init.ps1 | Deleted; version calculation moved out of this action. |
| src/cleanup.ps1 | Self-contained: derives prerelease name from PR head ref and lists releases via gh. |
| action.yml | Drops 9 version-calculation inputs and the Initialize step; updates cleanup gating. |
| README.md | Rewrites docs with v3.0.0 migration note and inputs table. |
Comments suppressed due to low confidence (1)
src/cleanup.ps1:40
$prereleaseNameis derived from the PR head ref using a broad pattern (-like "*$prereleaseName*"). If the sanitized branch name is short or generic (e.g. a branch namedfixordev), this will match unrelated release tags (e.g.1.2.3-fixes-001) and delete them. The previous implementation suffered the same risk, but the new model makes it easier to trigger because the pattern is computed every run from the branch name without any safeguards. Consider anchoring the match to the prerelease tag segment (e.g.-$prereleaseNamewith a numeric suffix) instead of a wildcard substring.
$prereleasesToCleanup = $releases | Where-Object { $_.tagName -like "*$prereleaseName*" }
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $releases = gh release list --json 'createdAt,isDraft,isLatest,isPrerelease,name,publishedAt,tagName' | ConvertFrom-Json | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error 'Failed to list releases for the repository.' | ||
| exit $LASTEXITCODE | ||
| } | ||
|
|
||
| $prereleasesToCleanup = $releases | Where-Object { $_.tagName -like "*$prereleaseName*" } | ||
| $tagsToDelete = @($prereleasesToCleanup | ForEach-Object { $_.tagName } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) |
| - name: Cleanup Prereleases | ||
| if: env.PUBLISH_CONTEXT_ShouldCleanup == 'true' || inputs.WhatIf == 'true' | ||
| if: inputs.AutoCleanup == 'true' || inputs.WhatIf == 'true' |
| if (Test-Path -Path $zipPath) { | ||
| Remove-Item -Path $zipPath -Force | ||
| } | ||
| Write-Host "Compressing module to [$zipPath]" | ||
| Compress-Archive -Path (Join-Path $modulePath '*') -DestinationPath $zipPath -Force | ||
|
|
||
| if ($whatIf) { | ||
| Write-Host "WhatIf: gh release upload $releaseTag $zipPath --clobber" | ||
| } else { | ||
| gh release upload $releaseTag $zipPath --clobber | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error "Failed to upload module artifact to release [$releaseTag]." | ||
| exit $LASTEXITCODE | ||
| } | ||
| Write-Host "::notice title=📦 Attached module artifact to release::$zipFileName" | ||
| } |
| if ($whatIf) { | ||
| Write-Host "WhatIf: gh release upload $releaseTag $zipPath --clobber" | ||
| } else { | ||
| gh release upload $releaseTag $zipPath --clobber | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error "Failed to upload module artifact to release [$releaseTag]." | ||
| exit $LASTEXITCODE | ||
| } | ||
| Write-Host "::notice title=📦 Attached module artifact to release::$zipFileName" |
| Remove-Item -Path $zipPath -Force | ||
| } | ||
| Write-Host "Compressing module to [$zipPath]" | ||
| Compress-Archive -Path (Join-Path $modulePath '*') -DestinationPath $zipPath -Force |
| Set-ModuleManifest -Path $manifestFilePath -Prerelease $($newVersion.Prerelease) -Verbose:$false | ||
| Show-FileContent -Path $manifestFilePath | ||
|
|
||
| $manifest = Test-ModuleManifest -Path $manifestFilePath -ErrorAction Stop |
| | Name | Description | Required | Default | | ||
| | -------------------------- | ------------------------------------------------------------------------------------------ | -------- | ---------------- | | ||
| | `Name` | Name of the module. Defaults to the repository name. | No | Repository name | | ||
| | `ModulePath` | Path to the downloaded module folder. | No | `outputs/module` | | ||
| | `APIKey` | PowerShell Gallery API key. | Yes | | | ||
| | `AutoCleanup` | Delete prerelease tags matching the PR branch after a stable release. | No | `true` | | ||
| | `WhatIf` | Log the changes that would be made without publishing, creating, or deleting anything. | No | `false` | | ||
| | `WorkingDirectory` | The working directory where the script will run from. | No | `.` | | ||
| | `UsePRTitleAsReleaseName` | Use the PR title as the release name (otherwise the version string is used). | No | `false` | | ||
| | `UsePRBodyAsReleaseNotes` | Use the PR body as the release notes (otherwise `--generate-notes` is used). | No | `true` | | ||
| | `UsePRTitleAsNotesHeading` | Prefix the release notes with the PR title as an H1 heading linking to the PR. | No | `true` | |
| @@ -126,7 +76,7 @@ runs: | |||
| run: ${{ github.action_path }}/src/publish.ps1 | |||
Companion to PSModule/Process-PSModule#339 (PSModule/Process-PSModule#326).
Publish-PSModuleno longer calculates the next version or mutates the module manifest. The artifact passed inmust already contain the final
ModuleVersion(andPrereleasetag, if any). UsePSModule/Resolve-PSModuleVersionto compute the versionand
PSModule/Build-PSModulev5+ to stamp it before testing.Removed inputs
AutoPatchingIncrementalPrereleaseDatePrereleaseFormatVersionPrefixMajorLabels,MinorLabels,PatchLabels,IgnoreLabelsReleaseTypeWhy
Today the version a module is published with is calculated after tests pass, inside
Publish-PSModule. Build-PSModulestamps
999.0.0, the tests run against that, andPublish-PSModulethen rewrites the manifest before pushing tothe Gallery. This breaks CI/CD's first principle: the artifact you tested is not the artifact you ship.
What
src/init.ps1(the old version-calculation script).src/publish.ps1now readsModuleVersionandPrereleasedirectly from the downloaded manifest, then publishesthe artifact untouched via
Publish-PSResource.gh release create, the module folder is zipped (<Name>-<Version>.zip) and uploaded as a release asset viagh release upload <tag> <zip> --clobberso the GitHub Release page exposes the exact bytes that were testedand pushed to the Gallery (fulfils the second half of Move version calculation to a Plan job (Resolve-PSModuleVersion) so Build and Publish never calculate or mutate versions Process-PSModule#326).
src/cleanup.ps1no longer relies on the removedPUBLISH_CONTEXT_*environment variables; the prerelease nameis derived from the PR head ref.
action.ymldrops the version-calculation inputs listed above and the corresponding Initialize step.Migration
Consumers on
PSModule/Process-PSModuleget this for free — the workflow has been updated end-to-end in thecompanion PR. Direct callers of
Publish-PSModuleoutside ofProcess-PSModuleneed to provide a pre-stampedartifact (use
Resolve-PSModuleVersion→Build-PSModulev5+ on the way in).