Skip to content

🚀 [Feature]: Module manifests now stamped with the resolved version at build time#136

Draft
Marius Storhaug (MariusStorhaug) wants to merge 1 commit into
mainfrom
feat/326-stamp-version
Draft

🚀 [Feature]: Module manifests now stamped with the resolved version at build time#136
Marius Storhaug (MariusStorhaug) wants to merge 1 commit into
mainfrom
feat/326-stamp-version

Conversation

@MariusStorhaug
Copy link
Copy Markdown
Member

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

Module manifests can now be stamped with a 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.

New: Version and Prerelease inputs on Build-PSModule

Build-PSModule now accepts two optional inputs:

Input Description
Version Module version (Major.Minor.Patch) to stamp into the manifest. When empty, the manifest keeps the legacy 999.0.0 default.
Prerelease Prerelease tag (for example mybranch001) to stamp into PrivateData.PSData.Prerelease. When empty, no prerelease tag is written.

Typical usage downstream of PSModule/Resolve-PSModuleVersion:

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

Changed: Manifest version source of truth

The hard-coded '999.0.0' placeholder is now used only when no Version input is supplied (for example when Build-PSModule is run outside the standard Plan → Build → Test → Publish flow). When Version is supplied, that value is written directly to ModuleVersion.

Existing workflows that do not pass Version continue to behave exactly as before.

Technical Details

  • action.yml: adds Version and Prerelease inputs mapped to PSMODULE_BUILD_PSMODULE_INPUT_Version and PSMODULE_BUILD_PSMODULE_INPUT_Prerelease.
  • src/main.ps1: reads the new env vars and forwards them to Build-PSModule as ModuleVersion and ModulePrerelease.
  • src/helpers/Build-PSModule.ps1: adds [string] $ModuleVersion and [string] $ModulePrerelease parameters, forwards both to Build-PSModuleManifest.
  • src/helpers/Build/Build-PSModuleManifest.ps1:
    • $manifest.ModuleVersion is now if ([string]::IsNullOrWhiteSpace($ModuleVersion)) { '999.0.0' } else { $ModuleVersion }.
    • The previously commented-out # $manifest.PreRelease = "" # Is managed by the publish action block now sets $manifest.Prerelease = $ModulePrerelease when the input is provided. New-ModuleManifest routes this into PrivateData.PSData.Prerelease.

Related PRs:

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: 🆕 New

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

1 participant