Skip to content

Commit 8e697e3

Browse files
Use resolved release tags in publish workflow
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 3bbf103 commit 8e697e3

7 files changed

Lines changed: 109 additions & 187 deletions

File tree

.github/actions/Cleanup-PSModulePrereleases/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ runs:
2828
working-directory: ${{ inputs.WorkingDirectory }}
2929
env:
3030
PSMODULE_CLEANUP_PSMODULEPRERELEASES_INPUT_WhatIf: ${{ inputs.WhatIf }}
31-
PSMODULE_CLEANUP_PSMODULEPRERELEASES_CONTEXT_ReleaseTag: ${{ env.PSMODULE_PUBLISH_PSMODULE_CONTEXT_ReleaseTag }}
31+
PSMODULE_CLEANUP_PSMODULEPRERELEASES_CONTEXT_ReleaseTag: ${{ env.PSMODULE_RELEASE_PSMODULE_CONTEXT_ReleaseTag }}
3232
run: ${{ github.action_path }}/src/cleanup.ps1

.github/actions/Publish-PSModule/src/Publish-PSModule.Helpers.psm1

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -43,62 +43,3 @@
4343

4444
"$ModuleVersion-$($Prerelease.Trim())"
4545
}
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/tests/Publish-PSModule.Helpers.Tests.ps1

Lines changed: 0 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -45,123 +45,4 @@ Describe 'Publish-PSModule.Helpers' {
4545
}
4646
}
4747

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-
}
16748
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ inputs:
3434
description: When enabled along with UsePRBodyAsReleaseNotes, the release notes will begin with the pull request title as a H1 heading followed by the pull request body. The title will reference the pull request number.
3535
required: false
3636
default: 'true'
37+
ReleaseTag:
38+
description: Full GitHub release tag resolved by Resolve-PSModuleVersion.
39+
required: true
3740

3841
runs:
3942
using: composite
@@ -57,4 +60,5 @@ runs:
5760
PSMODULE_RELEASE_PSMODULE_INPUT_UsePRBodyAsReleaseNotes: ${{ inputs.UsePRBodyAsReleaseNotes }}
5861
PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsReleaseName: ${{ inputs.UsePRTitleAsReleaseName }}
5962
PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsNotesHeading: ${{ inputs.UsePRTitleAsNotesHeading }}
63+
PSMODULE_RELEASE_PSMODULE_INPUT_ReleaseTag: ${{ inputs.ReleaseTag }}
6064
run: ${{ github.action_path }}/src/release.ps1

.github/actions/Release-PSModule/src/release.ps1

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@
2222
'PSUseDeclaredVarsMoreThanAssignments', 'releaseType',
2323
Justification = 'Variable is used in script blocks.'
2424
)]
25-
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
26-
'PSUseDeclaredVarsMoreThanAssignments', 'publishPSVersion',
27-
Justification = 'Variable is used in script blocks.'
28-
)]
2925
[CmdletBinding()]
3026
param()
3127

@@ -61,9 +57,14 @@ LogGroup 'Load inputs' {
6157
$usePRBodyAsReleaseNotes = $env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRBodyAsReleaseNotes -eq 'true'
6258
$usePRTitleAsReleaseName = $env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsReleaseName -eq 'true'
6359
$usePRTitleAsNotesHeading = $env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsNotesHeading -eq 'true'
60+
$releaseTag = $env:PSMODULE_RELEASE_PSMODULE_INPUT_ReleaseTag
61+
if ([string]::IsNullOrWhiteSpace($releaseTag)) {
62+
throw 'ReleaseTag is required. Ensure Publish.Module.Resolution.FullVersion is passed from the Plan job.'
63+
}
6464

6565
Write-Host "Module name: [$name]"
6666
Write-Host "Module path: [$modulePath]"
67+
Write-Host "Release tag: [$releaseTag]"
6768
Write-Host "WhatIf: [$whatIf]"
6869
}
6970
#endregion Load inputs
@@ -130,21 +131,21 @@ LogGroup 'Resolve version from manifest' {
130131
$createPrerelease = $true
131132
}
132133

133-
$releaseTag = if ($createPrerelease) { "$moduleVersion-$prerelease" } else { $moduleVersion }
134134
$releaseType = if ($createPrerelease) { 'New prerelease' } else { 'New release' }
135135
$publishPSVersion = if ($createPrerelease) { "$moduleVersion-$prerelease" } else { $moduleVersion }
136136

137137
[PSCustomObject]@{
138138
ModuleVersion = $moduleVersion
139139
Prerelease = $prerelease
140140
CreatePrerelease = $createPrerelease
141+
GalleryVersion = $publishPSVersion
141142
ReleaseTag = $releaseTag
142143
PRNumber = $prNumber
143144
PRHeadRef = $prHeadRef
144145
} | Format-List | Out-String
145146

146147
# Expose release tag to subsequent steps so cleanup can exclude the just-created tag.
147-
"PSMODULE_PUBLISH_PSMODULE_CONTEXT_ReleaseTag=$releaseTag" | Out-File -Path $env:GITHUB_ENV -Append -Encoding utf8NoBOM
148+
"PSMODULE_RELEASE_PSMODULE_CONTEXT_ReleaseTag=$releaseTag" | Out-File -Path $env:GITHUB_ENV -Append -Encoding utf8NoBOM
148149
}
149150
#endregion Resolve version from manifest
150151

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
11+
$script:releaseScriptPath = Join-Path -Path $PSScriptRoot -ChildPath '../src/release.ps1'
12+
$script:environmentVariableNames = @(
13+
'GITHUB_ENV'
14+
'GITHUB_EVENT_PATH'
15+
'GITHUB_REPOSITORY'
16+
'GITHUB_WORKSPACE'
17+
'PSMODULE_RELEASE_PSMODULE_INPUT_Name'
18+
'PSMODULE_RELEASE_PSMODULE_INPUT_ModulePath'
19+
'PSMODULE_RELEASE_PSMODULE_INPUT_WhatIf'
20+
'PSMODULE_RELEASE_PSMODULE_INPUT_UsePRBodyAsReleaseNotes'
21+
'PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsReleaseName'
22+
'PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsNotesHeading'
23+
'PSMODULE_RELEASE_PSMODULE_INPUT_ReleaseTag'
24+
)
25+
$script:originalEnvironment = @{}
26+
foreach ($name in $script:environmentVariableNames) {
27+
$script:originalEnvironment[$name] = [System.Environment]::GetEnvironmentVariable($name)
28+
}
29+
}
30+
31+
AfterAll {
32+
foreach ($name in $script:environmentVariableNames) {
33+
[System.Environment]::SetEnvironmentVariable($name, $script:originalEnvironment[$name])
34+
}
35+
}
36+
37+
Describe 'Release-PSModule WhatIf' {
38+
It 'creates a prefixed prerelease tag without GitHub side effects' {
39+
$moduleName = 'TestModule'
40+
$workspacePath = Join-Path -Path $TestDrive -ChildPath 'workspace'
41+
$modulePath = Join-Path -Path $workspacePath -ChildPath "outputs/module/$moduleName"
42+
$manifestPath = Join-Path -Path $modulePath -ChildPath "$moduleName.psd1"
43+
$eventPath = Join-Path -Path $TestDrive -ChildPath 'event.json'
44+
$githubEnvironmentPath = Join-Path -Path $TestDrive -ChildPath 'github-environment'
45+
46+
New-Item -Path $modulePath -ItemType Directory -Force | Out-Null
47+
Set-Content -Path (Join-Path -Path $modulePath -ChildPath "$moduleName.psm1") -Value ''
48+
Set-Content -Path $manifestPath -Value @"
49+
@{
50+
RootModule = '$moduleName.psm1'
51+
ModuleVersion = '1.2.3'
52+
GUID = '3c0f8d73-60b7-4f38-b3cf-9386db4982a4'
53+
Author = 'PSModule'
54+
Description = 'Test module'
55+
PowerShellVersion = '5.1'
56+
PrivateData = @{
57+
PSData = @{
58+
Prerelease = 'preview.1'
59+
}
60+
}
61+
}
62+
"@
63+
@{
64+
pull_request = @{
65+
number = 42
66+
title = 'Create test release'
67+
body = 'Release notes'
68+
head = @{
69+
ref = 'feature/release'
70+
}
71+
}
72+
} | ConvertTo-Json -Depth 5 | Set-Content -Path $eventPath
73+
74+
$env:GITHUB_ENV = $githubEnvironmentPath
75+
$env:GITHUB_EVENT_PATH = $eventPath
76+
$env:GITHUB_REPOSITORY = "PSModule/$moduleName"
77+
$env:GITHUB_WORKSPACE = $workspacePath
78+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_Name = $moduleName
79+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_ModulePath = 'outputs/module'
80+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_WhatIf = 'true'
81+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRBodyAsReleaseNotes = 'true'
82+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsReleaseName = 'false'
83+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsNotesHeading = 'true'
84+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_ReleaseTag = 'v1.2.3-preview.1'
85+
86+
{ & $script:releaseScriptPath } | Should -Not -Throw
87+
88+
Get-Content -Path $githubEnvironmentPath | Should -Contain 'PSMODULE_RELEASE_PSMODULE_CONTEXT_ReleaseTag=v1.2.3-preview.1'
89+
}
90+
}

.github/workflows/Publish-Module.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ jobs:
4949
WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }}
5050

5151
- name: Create GitHub release
52+
id: Create-GitHub-Release
5253
if: always() && !cancelled() && fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType != 'None'
5354
uses: ./_wf/.github/actions/Release-PSModule
5455
env:
@@ -61,11 +62,15 @@ jobs:
6162
UsePRTitleAsReleaseName: ${{ fromJson(inputs.Settings).Publish.Module.UsePRTitleAsReleaseName }}
6263
UsePRBodyAsReleaseNotes: ${{ fromJson(inputs.Settings).Publish.Module.UsePRBodyAsReleaseNotes }}
6364
UsePRTitleAsNotesHeading: ${{ fromJson(inputs.Settings).Publish.Module.UsePRTitleAsNotesHeading }}
64-
VersionPrefix: ${{ fromJson(inputs.Settings).Publish.Module.VersionPrefix }}
65+
ReleaseTag: ${{ fromJson(inputs.Settings).Publish.Module.Resolution.FullVersion }}
6566
WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }}
6667

6768
- name: Cleanup prereleases
68-
if: always() && !cancelled() && fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType != 'Prerelease'
69+
if: >-
70+
always() && !cancelled() &&
71+
fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType != 'Prerelease' &&
72+
(fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType == 'None' ||
73+
steps.Create-GitHub-Release.outcome == 'success')
6974
uses: ./_wf/.github/actions/Cleanup-PSModulePrereleases
7075
env:
7176
GH_TOKEN: ${{ github.token }}

0 commit comments

Comments
 (0)