Skip to content

🌟 [Major]: Version calculation removed — artifact must be pre-stamped before publish#71

Merged
Marius Storhaug (MariusStorhaug) merged 22 commits into
mainfrom
feat/326-resolve-psmodule-version
May 27, 2026
Merged

🌟 [Major]: Version calculation removed — artifact must be pre-stamped before publish#71
Marius Storhaug (MariusStorhaug) merged 22 commits into
mainfrom
feat/326-resolve-psmodule-version

Conversation

@MariusStorhaug
Copy link
Copy Markdown
Member

@MariusStorhaug Marius Storhaug (MariusStorhaug) commented May 17, 2026

Publish-PSModule no longer calculates or mutates the module version. The artifact passed in must already contain the final ModuleVersion (and Prerelease tag, if any) stamped by the upstream build. Published GitHub Releases now include a downloadable zip of the exact module folder that was tested and pushed to the Gallery.

Breaking Changes

Version-calculation inputs have been removed. Callers must supply a pre-stamped artifact:

Removed inputs:

  • AutoPatching
  • IncrementalPrerelease
  • DatePrereleaseFormat
  • VersionPrefix
  • MajorLabels, MinorLabels, PatchLabels, IgnoreLabels
  • ReleaseType

Migration: Consumers on PSModule/Process-PSModule get this for free — the workflow resolves the version in the Plan job and stamps it during Build. Direct callers outside of Process-PSModule must use Resolve-PSModuleVersion to compute the version and Build-PSModule v5+ to stamp it before invoking this action.

New: Module zip uploaded to GitHub Release

After creating a GitHub Release, the module folder is zipped (<Name>-<Version>.zip) and uploaded as a release asset. The zip preserves the <Name>/ directory structure so it can be extracted directly into a PowerShell module path.

Changed: Cleanup only runs after stable releases

The cleanup step (which removes old prerelease tags/releases) now only executes when the publish was a stable release. Previously it could inadvertently delete the just-published prerelease. Cleanup also filters on isPrerelease to avoid accidentally deleting stable releases whose tag happens to match the derived prerelease name.

Technical Details

  • Deleted src/init.ps1 (the old version-calculation script).
  • src/publish.ps1 reads ModuleVersion and Prerelease directly from the downloaded manifest via Import-PowerShellDataFile, validates 3-part format, then publishes untouched via Publish-PSResource.
  • Test-ModuleManifest is called as advisory validation (non-terminating) since the built artifact may reference RequiredModules not installed on the runner. Structural validation is enforced by explicit regex guards on ModuleVersion and Prerelease.
  • src/cleanup.ps1 derives the prerelease name from the PR head ref, filters on isPrerelease, and explicitly excludes the just-published release tag from deletion.
  • action.yml cleanup step gated on env.PSMODULE_PUBLISH_PSMODULE_CONTEXT_IsPrerelease != 'true'.
  • GITHUB_ENV writes use utf8NoBOM encoding to prevent BOM corruption.
  • Zip upload and temp file cleanup wrapped in try/finally for reliable cleanup on failure.
Related issues

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ps1 and remove all version/label/release-type inputs from action.yml.
  • Rewrite src/publish.ps1 to read ModuleVersion/Prerelease from the manifest, publish via Publish-PSResource, and upload <Name>-<Version>.zip to the release.
  • Update src/cleanup.ps1 to derive $prereleaseName from the PR head ref and call gh release list itself.

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

  • $prereleaseName is 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 named fix or dev), 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. -$prereleaseName with 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.

Comment thread src/cleanup.ps1 Outdated
Comment thread action.yml Outdated
Comment thread src/publish.ps1
Comment thread src/publish.ps1 Outdated
Comment thread src/publish.ps1 Outdated
Comment thread src/publish.ps1 Outdated
Comment thread README.md Outdated
Comment thread action.yml
Marius Storhaug (MariusStorhaug) added a commit to PSModule/Resolve-PSModuleVersion that referenced this pull request May 22, 2026
…tion (#1)

Module version resolution is now available as a standalone action.
Workflows can call it before building so the resolved version is stamped
into the artifact at build time, making the bytes that are tested the
bytes that ship.

- Resolves PSModule/Process-PSModule#326

## New: Standalone `Resolve-PSModuleVersion` action

The action consumes the JSON `Settings` output from
[`PSModule/Get-PSModuleSettings`](https://github.com/PSModule/Get-PSModuleSettings)
and emits:

| Output | Description |
| --- | --- |
| `Version` | `Major.Minor.Patch` portion of the resolved version. |
| `Prerelease` | Prerelease tag, empty when not a prerelease. |
| `FullVersion` | Full version string including `VersionPrefix` and
prerelease tag. |
| `ReleaseType` | `Release`, `Prerelease`, or `None` when no version
bump label is present. |
| `CreateRelease` | `true` when a release or prerelease should be
created. |

Typical usage in the Plan job:

```yaml
- name: Resolve module version
  id: resolve
  uses: PSModule/Resolve-PSModuleVersion@v1
  env:
    GH_TOKEN: ${{ github.token }}
  with:
    Settings: ${{ steps.settings.outputs.Settings }}

- name: Build module
  uses: PSModule/Build-PSModule@v5
  with:
    Version: ${{ steps.resolve.outputs.Version }}
    Prerelease: ${{ steps.resolve.outputs.Prerelease }}
```

The action validates `Settings.Publish.Module.ReleaseType`, applies
`IgnoreLabels` overrides, picks the bump type from PR labels
(`MajorLabels` > `MinorLabels` > `PatchLabels` / `AutoPatching`), then
computes the next version from the higher of the latest GitHub Release
and the latest PowerShell Gallery version. For prereleases it appends
the sanitized branch name, optional `DatePrereleaseFormat` timestamp,
and an incremental counter calculated from existing prereleases on the
same baseline + branch.

## Technical Details

- `action.yml`: composite action with inputs `Settings` (required JSON),
`Name`, `WorkingDirectory`, `Debug`, `Verbose`, `Version`, `Prerelease`,
plus `EventPath` and `EventJson` (both optional, for test overrides —
`EventJson` takes precedence over reading the file at `EventPath`). All
`${{ }}` template expressions are isolated in `env:` sections per zizmor
template-injection requirements. Installs
`PSModule/Install-PSModuleHelpers` and `PSSemVer` before running the
script.
- `scripts/main.ps1`: ports the version-resolution logic that previously
lived in `Publish-PSModule/src/init.ps1`. Reads configuration from
`PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Settings` JSON instead of
separate env vars. Reads the PR event from
`PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_EventJson` when set, falling
back to the file at `GITHUB_EVENT_PATH`. Emits outputs via
`$env:GITHUB_OUTPUT`. Cleanup-tag discovery stays in
`Publish-PSModule/cleanup.ps1` and is intentionally out of scope here.
- `.github/workflows/Action-Test.yml`: 6 test jobs covering patch,
minor, major, auto-patch, ignore-label, and None scenarios. The
ignore-label job passes the fake PR event as a JSON string via
`EventJson` to bypass the runner's real event file, which cannot be
reliably overridden at the file-system level.
- `README.md`: replaces the template scaffold with the action's contract
and usage examples.

**Implementation plan progress** (PSModule/Process-PSModule#326):
- ✅ Create `Resolve-PSModuleVersion` (LICENSE, README, `action.yml`,
`scripts/main.ps1`, Action-Test workflow)
- ✅ Inputs: `Settings`, `Name`, `WorkingDirectory` (plus
`EventPath`/`EventJson` for test overrides)
- ✅ Outputs: `Version`, `Prerelease`, `FullVersion`, `ReleaseType`,
`CreateRelease`
- ✅ Port version-resolution logic from `Publish-PSModule/src/init.ps1`
(PSSemVer install, GitHub Releases query, PSGallery query, PR-label
parsing, bump selection, prerelease sequencing, `DatePrereleaseFormat`,
`VersionPrefix`)
- ⬜ Dedicated Pester unit tests for label parsing, bump selection, and
prerelease sequencing — covered by the six integration test jobs; a
focused unit-test suite remains open

Related PRs:
- PSModule/Process-PSModule#342 — rewires the workflow's Plan → Build →
Test → Publish chain to consume the resolved version.
- PSModule/Build-PSModule#136 — accepts `Version` / `Prerelease` inputs
and stamps them into the manifest at build time.
- PSModule/Publish-PSModule#71 — removes the version-calculation logic
that moved here.
Marius Storhaug (MariusStorhaug) added a commit to PSModule/Build-PSModule that referenced this pull request May 24, 2026
…t build time (#136)

Module manifests are now stamped with the resolved version and
prerelease tag at build time. The resulting artifact contains its final
`ModuleVersion` (and `PrivateData.PSData.Prerelease`) before tests run,
so the bytes that are tested are the bytes that ship.

- Fixes PSModule/Process-PSModule#326

## Inputs on `Build-PSModule`

`Build-PSModule` now exposes new module-centric inputs:

| Input | Required | Description |
| --- | --- | --- |
| `Name` | No | Name of the module to build. Defaults to the repository
name. |
| `Version` | **Yes** | Module version (`Major.Minor.Patch`) to stamp
into the manifest. Build fails with a clear error when omitted or
malformed. |
| `Prerelease` | No | Prerelease tag (for example `mybranch001`) to
stamp into `PrivateData.PSData.Prerelease`. When empty, no prerelease
tag is written. |
| `OutputFolder` | No | Path (relative to `WorkingDirectory`) where the
built module is placed. Defaults to `outputs/module`. |

Typical usage downstream of
[`PSModule/Resolve-PSModuleVersion`](https://github.com/PSModule/Resolve-PSModuleVersion):

```yaml
- name: Build module
  uses: PSModule/Build-PSModule@v5
  with:
    Version: ${{ steps.resolve.outputs.Version }}
    Prerelease: ${{ steps.resolve.outputs.Prerelease }}
```

## Breaking changes

- `Version` is now **required**. Callers that previously omitted it
(relying on the `999.0.0` placeholder) must now pass an explicit version
in `Major.Minor.Patch` format. Builds fail immediately with a clear
error when `Version` is missing or malformed.

## Technical details

- `action.yml`: adds `OutputFolder` (default `outputs/module`),
`Version` (`required: true`), and `Prerelease` inputs; `Name` remains
optional and still defaults to the repository name.
- `src/main.ps1`: reads `OutputFolder`, `Version`, and `Prerelease` from
env; throws immediately when `Version` is missing or not in
`Major.Minor.Patch` format.
- `src/helpers/Build-PSModule.ps1`: `ModuleVersion` parameter is now
`[Parameter(Mandatory)]`.
- `src/helpers/Build/Build-PSModuleManifest.ps1`: `ModuleVersion` is
`[Parameter(Mandatory)]`; the `999.0.0` fallback is removed — the
version is assigned directly.

Related PRs:
- PSModule/Resolve-PSModuleVersion#1 — emits the `Version` and
`Prerelease` values consumed here.
- PSModule/Publish-PSModule#71 — drops its own version stamping; expects
the artifact to arrive pre-stamped.
- PSModule/Process-PSModule#342 — wires the workflow end-to-end.
@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title 💥 [Major]: Drop version calculation, publish pre-stamped artifact, upload module zip to release (PSModule/Process-PSModule#326) 🌟 [Major]: Drop version calculation, publish pre-stamped artifact, upload module zip to release (PSModule/Process-PSModule#326) May 25, 2026
…le release, fix archive path and WhatIf, cleanup zip after upload
…ble alignment

- Remove Test-ModuleManifest call that fails when RequiredModules are not
  yet installed (dependencies are resolved in a later step)
- Add SuppressMessage attributes for prNumber and prHeadRef variables
  assigned in LogGroup blocks but used in subsequent blocks
- Break long error message line to satisfy PSAvoidLongLines rule
- Fix README table pipe alignment (MD060) by shortening ModulePath
  description to fit within the aligned column width
Copilot AI review requested due to automatic review settings May 26, 2026 20:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread src/publish.ps1 Outdated
Out-File -Encoding utf8 can emit a BOM on certain PowerShell
configurations which corrupts the first variable name. Switch to
utf8NoBOM to guarantee BOM-less output.
@MariusStorhaug Marius Storhaug (MariusStorhaug) changed the title 🌟 [Major]: Drop version calculation, publish pre-stamped artifact, upload module zip to release (PSModule/Process-PSModule#326) 🌟 [Major]: Version calculation removed — artifact must be pre-stamped before publish May 26, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/publish.ps1:141

  • In the WhatIf branch, the command string is built using a single-quoted literal ('✅ $releaseType: ...'). In PowerShell, variables inside single quotes are not expanded, so the logged command will literally contain $releaseType, $name`, etc., which makes WhatIf output misleading. Build the message with a double-quoted/interpolated string (or use subexpressions) so the rendered WhatIf command shows the actual values.
    if ($whatIf) {
        Write-Host (
            "gh pr comment $prNumber -b " +
            "'✅ $releaseType`: PowerShell Gallery - [$name $publishPSVersion]($psGalleryReleaseLink)'"
        )

Comment thread src/publish.ps1 Outdated
Comment thread src/publish.ps1 Outdated
Comment thread action.yml Outdated
- Wrap zip compress/upload in try/finally so temp file is cleaned up on failure
- Use subexpression syntax in WhatIf PR comment for variable expansion clarity
- Update ModulePath input description to clarify <Name>/ subdirectory requirement
Copilot AI review requested due to automatic review settings May 26, 2026 23:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread src/publish.ps1
Comment thread src/publish.ps1
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread src/cleanup.ps1 Outdated
Copilot AI review requested due to automatic review settings May 27, 2026 06:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/publish.ps1:154

  • In the WhatIf branch, the logged gh pr comment command wraps the message in single quotes, so $releaseType, $name, $publishPSVersion, and $psGalleryReleaseLink won’t expand and the output won’t reflect what would actually be executed. Build the WhatIf log string as a double-quoted/interpolated string while still emitting literal single quotes around the message body.
    if ($whatIf) {
        Write-Host (
            "gh pr comment $prNumber -b " +
            "'✅ $releaseType`: PowerShell Gallery - [$name $publishPSVersion]($psGalleryReleaseLink)'"
        )

Comment thread src/publish.ps1 Outdated
Comment thread .github/workflows/Action-Test.yml
- Add module-prerelease test fixture with Prerelease = 'preview1'
- Add module-unstamped test fixture with placeholder 999.0.0
- Add ActionTestPrerelease job validating prerelease tag/context
- Add ActionTestUnstamped job expecting action to reject 999.0.0
- Add 999.0.0 rejection guard in publish.ps1
Copilot AI review requested due to automatic review settings May 27, 2026 09:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Comment thread .github/workflows/Action-Test.yml
Comment thread .github/workflows/Action-Test.yml
Comment thread .github/workflows/Action-Test.yml
Comment thread src/publish.ps1 Outdated
- cleanup.ps1: capture gh release list output before ConvertFrom-Json to surface exit-code errors correctly
- publish.ps1: validate module path exists before Resolve-Path with clear error message
- publish.ps1: wrap gh release create in try/finally to clean up temp notes file on failure
- Action-Test.yml: fix artifact name mismatch (use 'module' consistently so download-artifact succeeds)
- Action-Test.yml: add non-empty assertion for PSMODULE_PUBLISH_PSMODULE_CONTEXT_ReleaseTag and version equality check
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Comment thread src/publish.ps1 Outdated
Comment thread .github/workflows/Action-Test.yml
Comment thread tests/outputs/module-unstamped/PSModuleTest/PSModuleTest.psd1 Outdated
Copilot AI review requested due to automatic review settings May 27, 2026 15:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/cleanup.ps1:54

  • The return here exits only the LogGroup block, so the script will still enter the later "Delete prereleases" group and emit the final notice ("Cleaned up 0...") even when no tags were found. Consider short-circuiting the script explicitly when $tagsToDelete.Count -eq 0 (or guard the delete group / notice on a non-empty tag list) to avoid misleading output.
    if ($tagsToDelete.Count -eq 0) {
        Write-Host "No prereleases found to cleanup for [$prereleaseName]."
        return
    }

Comment thread src/cleanup.ps1
Copilot AI review requested due to automatic review settings May 27, 2026 15:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Comment thread src/publish.ps1 Outdated
Comment thread src/publish.ps1
Comment thread src/publish.ps1 Outdated
…mments

- Add ArtifactName input to action.yml (default: module) so callers can specify the artifact name for download-artifact
- Update Action-Test.yml to use unique artifact names (module-default, module-prerelease, module-unstamped) per job to avoid upload-artifact v7 name collision across parallel jobs
- Add -PathType Container to Test-Path for module directory validation
- Wrap Import-PowerShellDataFile in try/catch with actionable error message
- Remove redundant notesFilePath cleanup after finally block
@MariusStorhaug Marius Storhaug (MariusStorhaug) merged commit 03c0f8b into main May 27, 2026
20 checks passed
@MariusStorhaug Marius Storhaug (MariusStorhaug) deleted the feat/326-resolve-psmodule-version branch May 27, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move version calculation to a Plan job (Resolve-PSModuleVersion) so Build and Publish never calculate or mutate versions

2 participants