Skip to content

Commit 1653be8

Browse files
🪲 [Fix]: Release tags keep the configured version prefix (#440)
Releases created by the module pipeline are tagged with the version prefix configured in `.github/PSModule.yml` again. A repository that keeps the default `VersionPrefix: 'v'` is tagged `v1.1.10`, not `1.1.10`, and its prereleases are tagged `v1.1.11-mybranch001`. A repository that sets `VersionPrefix: ''` keeps its unprefixed tags exactly as before. ## Fixed: release tags no longer lose the configured version prefix Since v6 the release tag was built from the compiled manifest's `ModuleVersion` alone. That value is `Major.Minor.Patch` by definition, so the prefix had nowhere to live and every repository publishing on v6 with the default prefix picked up a tag that did not match its own history — `PSModule/Toml` went from `v0.0.1` to `0.0.2`, `PSModule/Domeneshop` from `v0.0.2` to `1.0.0`, `PSModule/PSSemVer` from `v1.1.9` to `1.1.10`. Nothing needs to change in a module repository. The prefix is read from the setting that already exists: ```yaml Publish: Module: VersionPrefix: 'v' # default; set to '' for unprefixed tags ``` **The prefix applies to the GitHub release tag and to nothing else.** A PowerShell module manifest's `ModuleVersion` and a PowerShell Gallery package version only accept plain SemVer, so the version published to the Gallery, the version in the Gallery link, and the name of the module zip attached to the release all stay unprefixed. With `VersionPrefix: 'v'` a release looks like this: | | Value | | --- | --- | | GitHub release tag and title | `v1.1.10` | | PowerShell Gallery version | `1.1.10` | | Manifest `ModuleVersion` | `1.1.10` | | Attached artifact | `MyModule-1.1.10.zip` | Repositories that already published an unprefixed tag on v6 keep it. Those releases are public, their artifacts are linked from the release pages, and the PowerShell Gallery listing points at them, so they are left alone and the prefix resumes from the next release. An unprefixed tag left in the history does not affect future version resolution. --- <details> <summary>Technical details</summary> **What changed** - `.github/actions/Publish-PSModule/src/Publish-PSModule.Helpers.psm1` — new action-scoped helper module with `Get-ModuleVersionString`, which composes the module's SemVer string, and `Get-ReleaseTag`, which prefixes it. - `.github/actions/Publish-PSModule/src/publish.ps1` — reads the new `VersionPrefix` input, derives both version strings from those helpers in one place, and reports both in the resolved-version summary and the closing log line. - `.github/actions/Publish-PSModule/action.yml` — new optional `VersionPrefix` input, defaulting to `''`. - `.github/workflows/Publish-Module.yml` — passes `Settings.Publish.Module.VersionPrefix` into the action. - `.github/actions/Publish-PSModule/tests/Publish-PSModule.Helpers.Tests.ps1` — new Pester suite, picked up automatically by the existing `Test-Actions` discovery over `.github/actions/*/tests`. Five files, all on the tag-derivation path. No test fixture or pipeline behaviour outside it changes. **Approach** The manifest stays the source of the numeric version. `ModuleVersion` is what was built, tested, and pushed to the Gallery, so the tag has to agree with it — the `^\d+\.\d+\.\d+$` guard and the `999.0.0` placeholder check are unchanged. Only the prefix, the one piece of the tag the manifest cannot carry, is taken from the settings the Plan job already resolves. Composing the two is equivalent to using `Resolution.FullVersion` in the normal case and stays anchored to the artifact if the two ever disagree. **Keeping the prefix off the module version.** `publish.ps1` previously built the Gallery version with its own copy of the prerelease composition, independent of the tag. Two independent implementations of the same string is how they drift, and drift in this direction means a prefixed version reaching `Publish-PSResource`. Both now come from `Get-ModuleVersionString`; `Get-ReleaseTag` is that string with the prefix in front, so the prefix is the only possible difference between them, by construction rather than by convention. The prefix reaches: the release tag, the release title fallback, the `gh release upload` target, the release URL, the GitHub half of the pull request comment, and `PSMODULE_PUBLISH_PSMODULE_CONTEXT_ReleaseTag` for cleanup. It reaches nothing else. `publish.ps1` never writes to the manifest — it is read-only on the artifact by design — and `Build-PSModule` stamps the manifest from `Resolution.Version` and `Resolution.Prerelease`, which are unprefixed. `Resolution.FullVersion`, the one prefix-bearing value in the Settings object, is consumed by no downstream job. `Get-ModuleVersionString` and `Get-ReleaseTag` both trim their inputs, because prefix and label arrive through environment variables, and both treat a whitespace-only prerelease label as a stable release. **Verification — unit tests, red then green in CI** | Commit | Change | `Test actions` | | --- | --- | --- | | `fd2c7d9` | Extract tag derivation into a helper, no behavior change | success | | `d0f8f7a` | Add the regression test | **failure** (13 of 15) | | `06b4714` | Apply the configured version prefix | success (15 of 15) | 32 cases now. Alongside the prefixed and unprefixed tag shapes, absent and null prefixes, whitespace normalization, and the tag shape `Cleanup-PSModulePrereleases` depends on, a `the prefix reaches the release tag and nothing else` context pins the separation directly: - for five prefix/version/label combinations, the tag equals `$Prefix` + the module version string; - the module version string never begins with the prefix and always matches `^\d+\.\d+\.\d+(-[0-9A-Za-z\-.]+)?$`; - `Get-ModuleVersionString` has no `VersionPrefix` parameter at all, so a caller cannot pass one; - an unprefixed repository gets two identical strings, and stripping `v` from a prefixed tag returns the module version string. **Verification — the wiring, observed once in CI on an interim commit** This bug was a wiring failure, not a logic failure. `VersionPrefix` was resolved correctly by the Plan job and present in the Settings JSON; it simply never reached the tag. Unit tests prove the helpers compose correctly given the right input — they cannot prove that `fromJson(inputs.Settings).Publish.Module.VersionPrefix` → action input → `PSMODULE_PUBLISH_PSMODULE_INPUT_VersionPrefix` → `$versionPrefix` delivers the value. `Publish-Module` is skipped at the *job* level in every self-test run — `Publish.Module.Enabled` is `(ReleaseType -ne 'None') -or shouldAutoCleanup`, and an open pull request satisfies neither without a prerelease label — so the self-test does not exercise that chain on this diff. To close that gap once, an interim commit on this branch added `Fix` to a fixture's `PrereleaseLabels`, which made the self-test run the publish path under `WhatIf`. That commit has since been reset and is **not part of this pull request**; the observation below is from [run 30759608449](https://github.com/PSModule/Process-PSModule/actions/runs/30759608449) and is reported as a one-time measurement, not as coverage this change carries forward. ```text Module name: [PSModuleTest] Version prefix: [v] WhatIf: [True] ``` ```text ModuleVersion : 6.1.16 VersionPrefix : v Prerelease : fixversionprefixreleasetag001 CreatePrerelease : True ReleaseTag : v6.1.16-fixversionprefixreleasetag001 ``` ```text WhatIf: gh release create v6.1.16-fixversionprefixreleasetag001 --title v6.1.16-fixversionprefixreleasetag001 --notes-file /tmp/tmpjar7Eu.tmp --target fix-version-prefix-release-tag --prerelease ``` Nothing was published in that run: `Publish-PSResource` was logged rather than executed, no release or tag was created, and the `Release` workflow on the same push reported `Create a prerelease: [False]` / `Skipping release creation.` Standing publish-path coverage in CI is the subject of #436. **Verification — locally, outside CI** `publish.ps1` run end to end in `WhatIf` mode against a fabricated artifact, all four combinations, re-run after the separation change. Every line below is from those runs: | Prefix | Prerelease | Gallery version | Release tag | Artifact | Exported `…_CONTEXT_ReleaseTag` | | --- | --- | --- | --- | --- | --- | | `v` | — | `1.1.10` | `v1.1.10` | `PSModuleTest-1.1.10.zip` | `v1.1.10` | | `v` | `mybranch001` | `1.1.11-mybranch001` | `v1.1.11-mybranch001` | `PSModuleTest-1.1.11-mybranch001.zip` | `v1.1.11-mybranch001` | | `''` | — | `1.1.10` | `1.1.10` | `PSModuleTest-1.1.10.zip` | `1.1.10` | | `''` | `mybranch001` | `1.1.11-mybranch001` | `1.1.11-mybranch001` | `PSModuleTest-1.1.11-mybranch001.zip` | `1.1.11-mybranch001` | The first row is the `PSModule/PSSemVer` case from the bug report, which produced tag `1.1.10` before this change. The Gallery link and comment carried the unprefixed version in every run: ```text Publishing complete. PowerShell Gallery version: [1.1.10]. GitHub release tag: [v1.1.10]. gh pr comment 42 -b '✅ New release: PowerShell Gallery - [PSModuleTest 1.1.10](https://www.powershellgallery.com/packages/PSModuleTest/1.1.10)' ``` `AutoCleanup` was verified the same way, running `cleanup.ps1` against fixture release lists with the `gh` CLI shadowed. It keys off `tagName -like "*$prereleaseName*"`, which is prefix-agnostic, and excludes the published release by comparing `tagName` to `PSMODULE_PUBLISH_PSMODULE_CONTEXT_ReleaseTag`, which is now prefixed on both sides: | Repository | Published tag | Deleted | Excluded | | --- | --- | --- | --- | | Prefixed | `v1.1.11-mybranch003` | `v1.1.11-mybranch002`, `v1.1.11-mybranch001` | published tag, `v1.1.10`, another branch's prerelease | | Unprefixed | `1.1.11-mybranch003` | `1.1.11-mybranch002`, `1.1.11-mybranch001` | published tag, `1.1.10`, another branch's prerelease | | Mixed history — unprefixed leftovers from v6 | `v1.1.11-mybranch003` | `1.1.11-mybranch002`, `1.1.11-mybranch001` | published tag | | Stable release run | `v1.1.11` | all three branch prereleases | published tag | The mixed row is the migration case: a repository whose open pull request already has unprefixed prerelease tags created by the current v6 still has them cleaned up after this change. `Invoke-ScriptAnalyzer -Recurse -Settings .github/linters/.powershell-psscriptanalyzer.psd1` reports no findings for the action. **What is verified where** | Path | Evidence | | --- | --- | | Settings → action input → env var → `$versionPrefix` | One-time CI observation above; not covered by this diff going forward | | Prefix reaches the release tag and nothing else | Unit tests, plus all four local end-to-end runs | | Prefixed stable and prerelease tags | Unit tests, plus local end-to-end runs | | Unprefixed stable and prerelease tags | Unit tests, plus local end-to-end runs | | `AutoCleanup` tag matching, prefixed / unprefixed / mixed history | Local runs of `cleanup.ps1` against fixture release lists | **Implementation plan progress** Completes every step of the plan in #439 — regression test confirmed failing first, tag derivation changed, prefixed and unprefixed fixtures plus the `AutoCleanup` path re-run. The two decisions the issue records are answered in [a comment on it](#439 (comment)): the already-published unprefixed tags are left in place, and the prefix-consistency warning is carried by #441 rather than widened into this pull request. **Standards and framework alignment** | Changed surface | Standards checked | Framework docs checked | Result | | --- | --- | --- | --- | | `.github/actions/Publish-PSModule/src/**` (PowerShell) | PowerShell Functions, Naming, Messaging, Error Handling | GitHub Actions — helper modules named after the action | Aligned | | `.github/actions/Publish-PSModule/tests/**` (Pester) | Testing, PowerShell Testing | Action unit-test discovery in `Test-Actions.yml` | Aligned | | `.github/actions/Publish-PSModule/action.yml` (GitHub Actions) | GitHub Actions — behavior driven by inputs, defaults only at the interface layer | Action contract | Aligned | | `.github/actions/Publish-PSModule/` (folder conventions) | GitHub Actions — entry script named `main.ps1`, README per action | Action contract | Exception — #442 | | `.github/workflows/Publish-Module.yml` (GitHub Actions) | GitHub Actions | Reusable workflow contract | Aligned | The helper module is named after the action, per the standard, and matches the existing `Resolve-PSModuleVersion.Helpers.psm1`. The entry script here is `src/publish.ps1` rather than `src/main.ps1` and the action has no README; both predate this change and apply to `Cleanup-PSModulePrereleases` too, so they are carried by #442 instead of being renamed inside a bugfix. **Issue convergence sweep** Scoped to the publish and version-resolution surface: open issues in this repository touching `Publish-PSModule`, `Resolve-PSModuleVersion`, `Cleanup-PSModulePrereleases`, release tags, or versioning. #439 is the only one this diff fully satisfies. #438 (v5 to v6 migration) is affected by the fix but not delivered by it — the 48 repositories still on v5 need this merged and released before they migrate, so it is linked as context. #436 is linked as context only; this diff does not advance it. #441, #442, and #443 were opened by this session for findings deliberately left out of scope. </details> <details> <summary>Relevant issues (or links)</summary> - Fixes #439 - #436 - #438 - #441 - #442 - #443 </details> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 688896d commit 1653be8

5 files changed

Lines changed: 296 additions & 6 deletions

File tree

.github/actions/Publish-PSModule/action.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ inputs:
3737
description: Name of the uploaded artifact to download. Must match the name used in the upstream upload-artifact step.
3838
required: false
3939
default: module
40+
VersionPrefix:
41+
description: |
42+
Prefix put in front of the version in the git tag of the GitHub release, for example 'v'.
43+
Comes from Settings.Publish.Module.VersionPrefix, which the Plan job resolves. The compiled manifest carries the module version as
44+
Major.Minor.Patch and cannot carry the prefix, so it is supplied here. An empty value tags the release without a prefix.
45+
required: false
46+
default: ''
4047

4148
runs:
4249
using: composite
@@ -65,4 +72,5 @@ runs:
6572
PSMODULE_PUBLISH_PSMODULE_INPUT_UsePRBodyAsReleaseNotes: ${{ inputs.UsePRBodyAsReleaseNotes }}
6673
PSMODULE_PUBLISH_PSMODULE_INPUT_UsePRTitleAsReleaseName: ${{ inputs.UsePRTitleAsReleaseName }}
6774
PSMODULE_PUBLISH_PSMODULE_INPUT_UsePRTitleAsNotesHeading: ${{ inputs.UsePRTitleAsNotesHeading }}
75+
PSMODULE_PUBLISH_PSMODULE_INPUT_VersionPrefix: ${{ inputs.VersionPrefix }}
6876
run: ${{ github.action_path }}/src/publish.ps1
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
function Get-ModuleVersionString {
2+
<#
3+
.SYNOPSIS
4+
Builds the SemVer version string that identifies the module itself.
5+
6+
.DESCRIPTION
7+
Composes the module version and, when there is one, the prerelease label. This is the string the
8+
PowerShell Gallery and the module manifest understand: `Major.Minor.Patch` optionally followed by
9+
`-<prerelease>`. It never carries the repository's version prefix, because neither the manifest's
10+
`ModuleVersion` nor a Gallery package version accepts one.
11+
12+
.OUTPUTS
13+
String with the module version.
14+
15+
.EXAMPLE
16+
Get-ModuleVersionString -ModuleVersion '1.1.10'
17+
18+
Returns '1.1.10'.
19+
20+
.EXAMPLE
21+
Get-ModuleVersionString -ModuleVersion '1.1.10' -Prerelease 'mybranch001'
22+
23+
Returns '1.1.10-mybranch001'.
24+
#>
25+
[CmdletBinding()]
26+
[OutputType([string])]
27+
param(
28+
# The module version from the compiled manifest, in Major.Minor.Patch format.
29+
[Parameter(Mandatory)]
30+
[ValidateNotNullOrEmpty()]
31+
[string] $ModuleVersion,
32+
33+
# The prerelease label from the compiled manifest. Empty for a stable release.
34+
[Parameter()]
35+
[AllowEmptyString()]
36+
[AllowNull()]
37+
[string] $Prerelease
38+
)
39+
40+
if ([string]::IsNullOrWhiteSpace($Prerelease)) {
41+
return $ModuleVersion
42+
}
43+
44+
"$ModuleVersion-$($Prerelease.Trim())"
45+
}
46+
47+
function Get-ReleaseTag {
48+
<#
49+
.SYNOPSIS
50+
Builds the git tag used for the GitHub release.
51+
52+
.DESCRIPTION
53+
Prefixes the module's SemVer version string with the configured version prefix. The version comes
54+
from the compiled manifest, which is the artifact that is published, so the tag always names the
55+
exact bytes that were tested and pushed to the PowerShell Gallery. The manifest's ModuleVersion is
56+
Major.Minor.Patch by definition and cannot carry the prefix, so the prefix is supplied from the
57+
resolved settings (Publish.Module.VersionPrefix) instead.
58+
59+
The prefix belongs to the GitHub release tag and to nothing else. PowerShell manifests and Gallery
60+
package versions only accept plain SemVer, so callers that need the module's own version use
61+
Get-ModuleVersionString. Deriving both from the same composition keeps the prefix as the only
62+
difference between them.
63+
64+
.OUTPUTS
65+
String with the release tag.
66+
67+
.EXAMPLE
68+
Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10'
69+
70+
Returns 'v1.1.10'.
71+
72+
.EXAMPLE
73+
Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' -Prerelease 'mybranch001'
74+
75+
Returns 'v1.1.10-mybranch001'.
76+
77+
.EXAMPLE
78+
Get-ReleaseTag -VersionPrefix '' -ModuleVersion '1.1.10'
79+
80+
Returns '1.1.10'.
81+
#>
82+
[CmdletBinding()]
83+
[OutputType([string])]
84+
param(
85+
# The module version from the compiled manifest, in Major.Minor.Patch format.
86+
[Parameter(Mandatory)]
87+
[ValidateNotNullOrEmpty()]
88+
[string] $ModuleVersion,
89+
90+
# The prefix put in front of the version, for example 'v'. Empty for an unprefixed repository.
91+
[Parameter()]
92+
[AllowEmptyString()]
93+
[AllowNull()]
94+
[string] $VersionPrefix,
95+
96+
# The prerelease label from the compiled manifest. Empty for a stable release.
97+
[Parameter()]
98+
[AllowEmptyString()]
99+
[AllowNull()]
100+
[string] $Prerelease
101+
)
102+
103+
"$($VersionPrefix.Trim())$(Get-ModuleVersionString -ModuleVersion $ModuleVersion -Prerelease $Prerelease)"
104+
}

.github/actions/Publish-PSModule/src/publish.ps1

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ param()
2828
$PSStyle.OutputRendering = 'Ansi'
2929

3030
Import-Module -Name 'PSModule' -Force
31+
Import-Module -Name "$PSScriptRoot/Publish-PSModule.Helpers.psm1" -Force
3132

3233
#region Load inputs
3334
LogGroup 'Load inputs' {
@@ -58,10 +59,14 @@ LogGroup 'Load inputs' {
5859
$usePRBodyAsReleaseNotes = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_UsePRBodyAsReleaseNotes -eq 'true'
5960
$usePRTitleAsReleaseName = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_UsePRTitleAsReleaseName -eq 'true'
6061
$usePRTitleAsNotesHeading = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_UsePRTitleAsNotesHeading -eq 'true'
62+
# The prefix the repository tags releases with, resolved by the Plan job from
63+
# Settings.Publish.Module.VersionPrefix. Empty means the repository tags without a prefix.
64+
$versionPrefix = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_VersionPrefix
6165

62-
Write-Host "Module name: [$name]"
63-
Write-Host "Module path: [$modulePath]"
64-
Write-Host "WhatIf: [$whatIf]"
66+
Write-Host "Module name: [$name]"
67+
Write-Host "Module path: [$modulePath]"
68+
Write-Host "Version prefix: [$versionPrefix]"
69+
Write-Host "WhatIf: [$whatIf]"
6570
}
6671
#endregion Load inputs
6772

@@ -129,12 +134,18 @@ LogGroup 'Resolve version from manifest' {
129134
$createPrerelease = $true
130135
}
131136

132-
$releaseTag = if ($createPrerelease) { "$moduleVersion-$prerelease" } else { $moduleVersion }
137+
# The PowerShell Gallery and the module manifest only accept plain SemVer, so the configured
138+
# VersionPrefix is applied to the GitHub release tag and to nothing else. Both strings are derived
139+
# from the same composition here, so the prefix is the only difference between them.
140+
$publishPSVersion = Get-ModuleVersionString -ModuleVersion $moduleVersion -Prerelease $prerelease
141+
$releaseTag = Get-ReleaseTag -VersionPrefix $versionPrefix -ModuleVersion $moduleVersion -Prerelease $prerelease
133142

134143
[PSCustomObject]@{
135144
ModuleVersion = $moduleVersion
145+
VersionPrefix = $versionPrefix
136146
Prerelease = $prerelease
137147
CreatePrerelease = $createPrerelease
148+
GalleryVersion = $publishPSVersion
138149
ReleaseTag = $releaseTag
139150
PRNumber = $prNumber
140151
PRHeadRef = $prHeadRef
@@ -154,7 +165,6 @@ LogGroup 'Install module dependencies' {
154165
#region Publish to PSGallery
155166
LogGroup 'Publish to PSGallery' {
156167
$releaseType = if ($createPrerelease) { 'New prerelease' } else { 'New release' }
157-
$publishPSVersion = if ($createPrerelease) { "$moduleVersion-$prerelease" } else { $moduleVersion }
158168
$psGalleryReleaseLink = "https://www.powershellgallery.com/packages/$name/$publishPSVersion"
159169

160170
Write-Host 'Publish module to PowerShell Gallery using API key from environment.'
@@ -280,4 +290,4 @@ LogGroup 'Create GitHub release' {
280290
}
281291
#endregion Create GitHub release
282292

283-
Write-Host "Publishing complete. Version: [$releaseTag]"
293+
Write-Host "Publishing complete. PowerShell Gallery version: [$publishPSVersion]. GitHub release tag: [$releaseTag]."
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
2+
'PSUseDeclaredVarsMoreThanAssignments', '',
3+
Justification = 'Variables are assigned in BeforeAll and used inside It blocks.'
4+
)]
5+
[CmdletBinding()]
6+
param()
7+
8+
BeforeAll {
9+
Import-Module -Name 'PSModule' -Force
10+
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../src/Publish-PSModule.Helpers.psm1') -Force
11+
}
12+
13+
Describe 'Publish-PSModule.Helpers' {
14+
Describe 'Get-ModuleVersionString' {
15+
Context 'Get-ModuleVersionString - SemVer only, never prefixed' {
16+
It 'Get-ModuleVersionString - returns the module version for a stable release' {
17+
Get-ModuleVersionString -ModuleVersion '1.1.10' | Should -Be '1.1.10'
18+
}
19+
20+
It 'Get-ModuleVersionString - appends the prerelease label' {
21+
Get-ModuleVersionString -ModuleVersion '1.1.10' -Prerelease 'mybranch001' |
22+
Should -Be '1.1.10-mybranch001'
23+
}
24+
25+
It 'Get-ModuleVersionString - treats an empty prerelease label as a stable release' {
26+
Get-ModuleVersionString -ModuleVersion '1.1.10' -Prerelease '' | Should -Be '1.1.10'
27+
}
28+
29+
It 'Get-ModuleVersionString - treats a whitespace-only prerelease label as a stable release' {
30+
Get-ModuleVersionString -ModuleVersion '1.1.10' -Prerelease ' ' | Should -Be '1.1.10'
31+
}
32+
33+
It 'Get-ModuleVersionString - trims whitespace around the prerelease label' {
34+
Get-ModuleVersionString -ModuleVersion '1.1.10' -Prerelease ' mybranch001 ' |
35+
Should -Be '1.1.10-mybranch001'
36+
}
37+
38+
It 'Get-ModuleVersionString - takes no version prefix parameter at all' {
39+
(Get-Command Get-ModuleVersionString).Parameters.Keys | Should -Not -Contain 'VersionPrefix'
40+
}
41+
42+
It 'Get-ModuleVersionString - requires a module version' {
43+
{ Get-ModuleVersionString -ModuleVersion '' } | Should -Throw
44+
}
45+
}
46+
}
47+
48+
Describe 'Get-ReleaseTag' {
49+
Context 'Get-ReleaseTag - repository with a version prefix' {
50+
It 'Get-ReleaseTag - prefixes a stable release tag with the configured prefix' {
51+
Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' | Should -Be 'v1.1.10'
52+
}
53+
54+
It 'Get-ReleaseTag - prefixes a stable release tag when the prerelease label is empty' {
55+
Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' -Prerelease '' | Should -Be 'v1.1.10'
56+
}
57+
58+
It 'Get-ReleaseTag - prefixes a prerelease tag with the configured prefix' {
59+
Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' -Prerelease 'mybranch001' |
60+
Should -Be 'v1.1.10-mybranch001'
61+
}
62+
63+
It 'Get-ReleaseTag - supports a multi-character prefix' {
64+
Get-ReleaseTag -VersionPrefix 'release-v' -ModuleVersion '2.0.0' | Should -Be 'release-v2.0.0'
65+
}
66+
}
67+
68+
Context 'Get-ReleaseTag - repository without a version prefix' {
69+
It 'Get-ReleaseTag - leaves a stable release tag unprefixed' {
70+
Get-ReleaseTag -VersionPrefix '' -ModuleVersion '1.1.10' | Should -Be '1.1.10'
71+
}
72+
73+
It 'Get-ReleaseTag - leaves a prerelease tag unprefixed' {
74+
Get-ReleaseTag -VersionPrefix '' -ModuleVersion '1.1.10' -Prerelease 'mybranch001' |
75+
Should -Be '1.1.10-mybranch001'
76+
}
77+
78+
It 'Get-ReleaseTag - treats an absent prefix as no prefix' {
79+
Get-ReleaseTag -ModuleVersion '1.1.10' | Should -Be '1.1.10'
80+
}
81+
82+
It 'Get-ReleaseTag - treats a null prefix as no prefix' {
83+
Get-ReleaseTag -VersionPrefix $null -ModuleVersion '1.1.10' | Should -Be '1.1.10'
84+
}
85+
}
86+
87+
Context 'Get-ReleaseTag - input normalization' {
88+
It 'Get-ReleaseTag - trims whitespace around the prefix' {
89+
Get-ReleaseTag -VersionPrefix ' v ' -ModuleVersion '1.1.10' | Should -Be 'v1.1.10'
90+
}
91+
92+
It 'Get-ReleaseTag - trims whitespace around the prerelease label' {
93+
Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' -Prerelease ' mybranch001 ' |
94+
Should -Be 'v1.1.10-mybranch001'
95+
}
96+
97+
It 'Get-ReleaseTag - treats a whitespace-only prerelease label as a stable release' {
98+
Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' -Prerelease ' ' | Should -Be 'v1.1.10'
99+
}
100+
101+
It 'Get-ReleaseTag - requires a module version' {
102+
{ Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '' } | Should -Throw
103+
}
104+
}
105+
106+
# Cleanup-PSModulePrereleases selects the releases to delete with
107+
# `tagName -like "*$prereleaseName*" -and tagName -ne $publishedReleaseTag`, where the published tag is
108+
# the value publish.ps1 exports as PSMODULE_PUBLISH_PSMODULE_CONTEXT_ReleaseTag. Both halves of that
109+
# filter have to keep working once the tag carries a prefix.
110+
Context 'Get-ReleaseTag - AutoCleanup tag matching contract' {
111+
It 'Get-ReleaseTag - keeps the prerelease name inside a prefixed tag so cleanup still matches it' {
112+
Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' -Prerelease 'mybranch001' |
113+
Should -BeLike '*mybranch*'
114+
}
115+
116+
It 'Get-ReleaseTag - keeps the prerelease name inside an unprefixed tag so cleanup still matches it' {
117+
Get-ReleaseTag -VersionPrefix '' -ModuleVersion '1.1.10' -Prerelease 'mybranch001' |
118+
Should -BeLike '*mybranch*'
119+
}
120+
121+
It 'Get-ReleaseTag - produces the same tag twice so cleanup can exclude the published release' {
122+
$first = Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' -Prerelease 'mybranch001'
123+
$second = Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' -Prerelease 'mybranch001'
124+
$first | Should -Be $second
125+
}
126+
}
127+
128+
# The PowerShell Gallery and the module manifest only accept plain SemVer. The prefix therefore
129+
# belongs to the GitHub release tag and to nothing else, and the two strings must differ by exactly
130+
# the prefix - never by anything else, and never in the other direction.
131+
Context 'Get-ReleaseTag - the prefix reaches the release tag and nothing else' {
132+
It 'Get-ReleaseTag - the tag is the prefix followed by the module version string' -ForEach @(
133+
@{ Prefix = 'v'; Version = '1.1.10'; Label = '' }
134+
@{ Prefix = 'v'; Version = '1.1.10'; Label = 'mybranch001' }
135+
@{ Prefix = ''; Version = '1.1.10'; Label = '' }
136+
@{ Prefix = ''; Version = '1.1.10'; Label = 'mybranch001' }
137+
@{ Prefix = 'release-v'; Version = '2.0.0'; Label = 'mybranch001' }
138+
) {
139+
$moduleVersion = Get-ModuleVersionString -ModuleVersion $Version -Prerelease $Label
140+
$tag = Get-ReleaseTag -VersionPrefix $Prefix -ModuleVersion $Version -Prerelease $Label
141+
$tag | Should -Be "$Prefix$moduleVersion"
142+
}
143+
144+
It 'Get-ReleaseTag - the module version string never gains the prefix' -ForEach @(
145+
@{ Prefix = 'v'; Version = '1.1.10'; Label = '' }
146+
@{ Prefix = 'v'; Version = '1.1.10'; Label = 'mybranch001' }
147+
@{ Prefix = 'release-v'; Version = '2.0.0'; Label = 'mybranch001' }
148+
) {
149+
$moduleVersion = Get-ModuleVersionString -ModuleVersion $Version -Prerelease $Label
150+
$moduleVersion | Should -Not -BeLike "$Prefix*"
151+
$moduleVersion | Should -Match '^\d+\.\d+\.\d+(-[0-9A-Za-z\-.]+)?$'
152+
}
153+
154+
It 'Get-ReleaseTag - an unprefixed repository gets identical strings' {
155+
$moduleVersion = Get-ModuleVersionString -ModuleVersion '1.1.10' -Prerelease 'mybranch001'
156+
$tag = Get-ReleaseTag -VersionPrefix '' -ModuleVersion '1.1.10' -Prerelease 'mybranch001'
157+
$tag | Should -Be $moduleVersion
158+
}
159+
160+
It 'Get-ReleaseTag - stripping the prefix from the tag yields the module version string' {
161+
$moduleVersion = Get-ModuleVersionString -ModuleVersion '1.1.10' -Prerelease 'mybranch001'
162+
$tag = Get-ReleaseTag -VersionPrefix 'v' -ModuleVersion '1.1.10' -Prerelease 'mybranch001'
163+
$tag -replace '^v' | Should -Be $moduleVersion
164+
}
165+
}
166+
}
167+
}

.github/workflows/Publish-Module.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ jobs:
5050
UsePRTitleAsReleaseName: ${{ fromJson(inputs.Settings).Publish.Module.UsePRTitleAsReleaseName }}
5151
UsePRBodyAsReleaseNotes: ${{ fromJson(inputs.Settings).Publish.Module.UsePRBodyAsReleaseNotes }}
5252
UsePRTitleAsNotesHeading: ${{ fromJson(inputs.Settings).Publish.Module.UsePRTitleAsNotesHeading }}
53+
VersionPrefix: ${{ fromJson(inputs.Settings).Publish.Module.VersionPrefix }}
5354
WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }}
5455

5556
- name: Cleanup prereleases

0 commit comments

Comments
 (0)