Skip to content

Commit 672aaa7

Browse files
🚀 [Feature]: Module manifests now stamped with the resolved version at 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.
1 parent d97391a commit 672aaa7

5 files changed

Lines changed: 170 additions & 9 deletions

File tree

‎.github/workflows/Action-Test.yml‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,32 @@ jobs:
3131
uses: ./
3232
with:
3333
Name: PSModuleTest
34+
Version: 1.0.0
3435
ArtifactName: PSModuleTestDefault
3536
WorkingDirectory: tests/srcTestRepo
3637

38+
- name: Verify manifest
39+
shell: pwsh
40+
run: |
41+
$PSStyle.OutputRendering = 'Ansi'
42+
43+
$manifest = Import-PowerShellDataFile 'tests/srcTestRepo/outputs/module/PSModuleTest/PSModuleTest.psd1'
44+
45+
Write-Host '--- Input ---'
46+
Write-Host 'Name: PSModuleTest Version: 1.0.0'
47+
Write-Host ''
48+
49+
@(
50+
[PSCustomObject]@{ Output = 'ModuleVersion'; Expected = '1.0.0'; Actual = $manifest.ModuleVersion; Passed = $manifest.ModuleVersion -eq '1.0.0' }
51+
) | Format-Table -AutoSize | Out-String -Width 200
52+
53+
$failed = $false
54+
if ($manifest.ModuleVersion -ne '1.0.0') {
55+
Write-Error "ModuleVersion: expected '1.0.0', got '$($manifest.ModuleVersion)'"
56+
$failed = $true
57+
}
58+
if ($failed) { exit 1 }
59+
3760
ActionTestMinimal:
3861
name: Action-Test - [Minimal]
3962
runs-on: ubuntu-latest
@@ -47,9 +70,78 @@ jobs:
4770
uses: ./
4871
with:
4972
Name: PSModuleTest
73+
Version: 1.0.0
5074
ArtifactName: PSModuleTestMinimal
5175
WorkingDirectory: tests/srcMinimalTestRepo
5276

77+
- name: Verify manifest
78+
shell: pwsh
79+
run: |
80+
$PSStyle.OutputRendering = 'Ansi'
81+
82+
$manifest = Import-PowerShellDataFile 'tests/srcMinimalTestRepo/outputs/module/PSModuleTest/PSModuleTest.psd1'
83+
84+
Write-Host '--- Input ---'
85+
Write-Host 'Name: PSModuleTest Version: 1.0.0'
86+
Write-Host ''
87+
88+
@(
89+
[PSCustomObject]@{ Output = 'ModuleVersion'; Expected = '1.0.0'; Actual = $manifest.ModuleVersion; Passed = $manifest.ModuleVersion -eq '1.0.0' }
90+
) | Format-Table -AutoSize | Out-String -Width 200
91+
92+
$failed = $false
93+
if ($manifest.ModuleVersion -ne '1.0.0') {
94+
Write-Error "ModuleVersion: expected '1.0.0', got '$($manifest.ModuleVersion)'"
95+
$failed = $true
96+
}
97+
if ($failed) { exit 1 }
98+
99+
ActionTestPrerelease:
100+
name: Action-Test - [Prerelease]
101+
runs-on: ubuntu-latest
102+
steps:
103+
- name: Checkout repo
104+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
105+
with:
106+
persist-credentials: false
107+
108+
- name: Action-Test
109+
uses: ./
110+
with:
111+
Name: PSModuleTest
112+
Version: 2.0.0
113+
Prerelease: alpha001
114+
ArtifactName: PSModuleTestPrerelease
115+
WorkingDirectory: tests/srcTestRepo
116+
117+
- name: Verify manifest
118+
shell: pwsh
119+
run: |
120+
$PSStyle.OutputRendering = 'Ansi'
121+
122+
$manifest = Import-PowerShellDataFile 'tests/srcTestRepo/outputs/module/PSModuleTest/PSModuleTest.psd1'
123+
$prerelease = $manifest.PrivateData.PSData.Prerelease
124+
125+
Write-Host '--- Input ---'
126+
Write-Host 'Name: PSModuleTest Version: 2.0.0 Prerelease: alpha001'
127+
Write-Host ''
128+
129+
@(
130+
[PSCustomObject]@{ Output = 'ModuleVersion'; Expected = '2.0.0'; Actual = $manifest.ModuleVersion; Passed = $manifest.ModuleVersion -eq '2.0.0' }
131+
[PSCustomObject]@{ Output = 'Prerelease'; Expected = 'alpha001'; Actual = $prerelease; Passed = $prerelease -eq 'alpha001' }
132+
) | Format-Table -AutoSize | Out-String -Width 200
133+
134+
$failed = $false
135+
if ($manifest.ModuleVersion -ne '2.0.0') {
136+
Write-Error "ModuleVersion: expected '2.0.0', got '$($manifest.ModuleVersion)'"
137+
$failed = $true
138+
}
139+
if ($prerelease -ne 'alpha001') {
140+
Write-Error "Prerelease: expected 'alpha001', got '$prerelease'"
141+
$failed = $true
142+
}
143+
if ($failed) { exit 1 }
144+
53145
ActionTestWithManifest:
54146
name: Action-Test - [DefaultWithManifest]
55147
runs-on: ubuntu-24.04
@@ -63,5 +155,28 @@ jobs:
63155
uses: ./
64156
with:
65157
Name: PSModuleTest
158+
Version: 1.0.0
66159
ArtifactName: PSModuleTestWithManifest
67160
WorkingDirectory: tests/srcWithManifestTestRepo
161+
162+
- name: Verify manifest
163+
shell: pwsh
164+
run: |
165+
$PSStyle.OutputRendering = 'Ansi'
166+
167+
$manifest = Import-PowerShellDataFile 'tests/srcWithManifestTestRepo/outputs/module/PSModuleTest/PSModuleTest.psd1'
168+
169+
Write-Host '--- Input ---'
170+
Write-Host 'Name: PSModuleTest Version: 1.0.0'
171+
Write-Host ''
172+
173+
@(
174+
[PSCustomObject]@{ Output = 'ModuleVersion'; Expected = '1.0.0'; Actual = $manifest.ModuleVersion; Passed = $manifest.ModuleVersion -eq '1.0.0' }
175+
) | Format-Table -AutoSize | Out-String -Width 200
176+
177+
$failed = $false
178+
if ($manifest.ModuleVersion -ne '1.0.0') {
179+
Write-Error "ModuleVersion: expected '1.0.0', got '$($manifest.ModuleVersion)'"
180+
$failed = $true
181+
}
182+
if ($failed) { exit 1 }

‎action.yml‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@ author: PSModule
44

55
inputs:
66
Name:
7-
description: Name of the module to process.
7+
description: Name of the module to build. Defaults to the repository name.
8+
required: false
9+
OutputFolder:
10+
description: Path to the folder where the built module is outputted.
11+
required: false
12+
default: 'outputs/module'
13+
Version:
14+
description: Module version to stamp into the manifest.
15+
required: true
16+
Prerelease:
17+
description: Prerelease tag to stamp into the manifest's `PrivateData.PSData.Prerelease`. When empty, no prerelease tag is written.
818
required: false
919
ArtifactName:
1020
description: Name of the artifact to upload.
@@ -27,6 +37,9 @@ runs:
2737
working-directory: ${{ inputs.WorkingDirectory }}
2838
env:
2939
PSMODULE_BUILD_PSMODULE_INPUT_Name: ${{ inputs.Name }}
40+
PSMODULE_BUILD_PSMODULE_INPUT_OutputFolder: ${{ inputs.OutputFolder }}
41+
PSMODULE_BUILD_PSMODULE_INPUT_Version: ${{ inputs.Version }}
42+
PSMODULE_BUILD_PSMODULE_INPUT_Prerelease: ${{ inputs.Prerelease }}
3043
run: |
3144
# Build-PSModule
3245
${{ github.action_path }}/src/main.ps1

‎src/helpers/Build-PSModule.ps1‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@
2727

2828
# Path to the folder where the built modules are outputted.
2929
[Parameter(Mandatory)]
30-
[string] $ModuleOutputFolderPath
30+
[string] $ModuleOutputFolderPath,
31+
32+
# Module version to stamp into the manifest.
33+
[Parameter(Mandatory)]
34+
[string] $ModuleVersion,
35+
36+
# Prerelease tag to stamp into the manifest. When empty, no prerelease tag is written.
37+
[Parameter()]
38+
[string] $ModulePrerelease
3139
)
3240

3341
Set-GitHubLogGroup "Building module [$ModuleName]" {
@@ -40,7 +48,8 @@
4048
}
4149

4250
Build-PSModuleBase -ModuleName $ModuleName -ModuleSourceFolder $moduleSourceFolder -ModuleOutputFolder $moduleOutputFolder
43-
Build-PSModuleManifest -ModuleName $ModuleName -ModuleOutputFolder $moduleOutputFolder
51+
Build-PSModuleManifest -ModuleName $ModuleName -ModuleOutputFolder $moduleOutputFolder `
52+
-ModuleVersion $ModuleVersion -ModulePrerelease $ModulePrerelease
4453
Build-PSModuleRootModule -ModuleName $ModuleName -ModuleOutputFolder $moduleOutputFolder
4554
Update-PSModuleManifestAliasesToExport -ModuleName $ModuleName -ModuleSourceFolder $moduleSourceFolder -ModuleOutputFolder $moduleOutputFolder
4655

‎src/helpers/Build/Build-PSModuleManifest.ps1‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@
3030

3131
# Folder where the built modules are outputted. 'outputs/modules/MyModule'
3232
[Parameter(Mandatory)]
33-
[System.IO.DirectoryInfo] $ModuleOutputFolder
33+
[System.IO.DirectoryInfo] $ModuleOutputFolder,
34+
35+
# Module version to stamp into the manifest.
36+
[Parameter(Mandatory)]
37+
[string] $ModuleVersion,
38+
39+
# Prerelease tag to stamp into the manifest's `PrivateData.PSData.Prerelease`.
40+
[Parameter()]
41+
[string] $ModulePrerelease
3442
)
3543

3644
Set-GitHubLogGroup 'Build manifest file' {
@@ -55,7 +63,7 @@
5563
$manifest.RootModule = $rootModule
5664
Write-Host "[RootModule] - [$($manifest.RootModule)]"
5765

58-
$manifest.ModuleVersion = '999.0.0'
66+
$manifest.ModuleVersion = $ModuleVersion
5967
Write-Host "[ModuleVersion] - [$($manifest.ModuleVersion)]"
6068

6169
$manifest.Author = $manifest.Keys -contains 'Author' ? (-not [string]::IsNullOrEmpty($manifest.Author)) ? $manifest.Author : $env:GITHUB_REPOSITORY_OWNER : $env:GITHUB_REPOSITORY_OWNER
@@ -417,9 +425,11 @@
417425
$manifest.Remove('ReleaseNotes')
418426
}
419427

420-
Write-Host '[PreRelease]'
421-
# $manifest.PreRelease = ""
422-
# Is managed by the publish action
428+
Write-Host '[Prerelease]'
429+
if (-not [string]::IsNullOrWhiteSpace($ModulePrerelease)) {
430+
$manifest.Prerelease = $ModulePrerelease
431+
Write-Host "[Prerelease] - [$($manifest.Prerelease)]"
432+
}
423433

424434
Write-Host '[RequireLicenseAcceptance]'
425435
$manifest.RequireLicenseAcceptance = $PSData.Keys -contains 'RequireLicenseAcceptance' ? $null -ne $PSData.RequireLicenseAcceptance ? $PSData.RequireLicenseAcceptance : $false : $false

‎src/main.ps1‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,27 @@ Set-GitHubLogGroup 'Loading inputs' {
2121
} else {
2222
$env:PSMODULE_BUILD_PSMODULE_INPUT_Name
2323
}
24+
$moduleVersion = $env:PSMODULE_BUILD_PSMODULE_INPUT_Version
25+
$modulePrerelease = $env:PSMODULE_BUILD_PSMODULE_INPUT_Prerelease
2426
$sourceFolderPath = Resolve-Path -Path 'src' | Select-Object -ExpandProperty Path
25-
$moduleOutputFolderPath = Join-Path $pwd -ChildPath 'outputs/module'
27+
$moduleOutputFolderPath = Join-Path $pwd -ChildPath $env:PSMODULE_BUILD_PSMODULE_INPUT_OutputFolder
2628
[pscustomobject]@{
2729
moduleName = $moduleName
30+
moduleVersion = $moduleVersion
31+
modulePrerelease = $modulePrerelease
2832
sourceFolderPath = $sourceFolderPath
2933
moduleOutputFolderPath = $moduleOutputFolderPath
3034
} | Format-List | Out-String
3135
}
3236

37+
if ([string]::IsNullOrWhiteSpace($moduleVersion)) {
38+
throw 'Version is required. Please provide a module version.'
39+
}
40+
41+
if ($moduleVersion -notmatch '^\d+\.\d+\.\d+$') {
42+
throw "Version '$moduleVersion' is not a valid version. Expected format: 'Major.Minor.Patch' (e.g., '1.2.3')."
43+
}
44+
3345
Set-GitHubLogGroup 'Build local scripts' {
3446
Write-Host 'Execution order:'
3547
$scripts = Get-ChildItem -Filter '*build.ps1' -Recurse | Sort-Object -Property Name | Resolve-Path -Relative
@@ -47,6 +59,8 @@ $params = @{
4759
ModuleName = $moduleName
4860
ModuleSourceFolderPath = $sourceFolderPath
4961
ModuleOutputFolderPath = $moduleOutputFolderPath
62+
ModuleVersion = $moduleVersion
63+
ModulePrerelease = $modulePrerelease
5064
}
5165
Build-PSModule @params
5266

0 commit comments

Comments
 (0)