Skip to content

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

Draft
Marius Storhaug (MariusStorhaug) wants to merge 5 commits 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 5 commits into
mainfrom
feat/326-stamp-version

Conversation

@MariusStorhaug
Copy link
Copy Markdown
Member

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

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.

Inputs on Build-PSModule

Build-PSModule now exposes three module-centric inputs:

Input Required Description
Version Yes Module version (Major.Minor.Patch) to stamp into the manifest. Build fails explicitly when omitted.
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.

Name is removed — the module name is always inferred from GITHUB_REPOSITORY_NAME.

Typical usage downstream of PSModule/Resolve-PSModuleVersion:

- 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. Builds fail immediately with a clear error when Version is missing.
  • Name input is removed. The module name is taken from GITHUB_REPOSITORY_NAME. Callers that passed a custom name must stop doing so.

Technical details

  • action.yml: replaces Name with OutputFolder (default outputs/module); marks Version required: true.
  • src/main.ps1: drops the Name-to-moduleName conditional; reads OutputFolder from env; throws immediately when Version is empty.
  • 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:

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.
- Remove Name input; module name is always inferred from GITHUB_REPOSITORY_NAME
- Add OutputFolder input (default: outputs/module) to configure the build output path
- Make Version required in action.yml, mandatory in Build-PSModule and Build-PSModuleManifest helpers
- Remove 999.0.0 version fallback; build now fails explicitly when Version is not provided
- Update Action-Test.yml: remove Name input, add Version: 1.0.0 to all test jobs
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