Skip to content

Commit 087bf89

Browse files
Publish stable releases from main pushes
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 94674b7 commit 087bf89

12 files changed

Lines changed: 166 additions & 44 deletions

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ inputs:
1111
description: GitHub release tag to retain during cleanup.
1212
required: false
1313
default: ''
14+
PullRequest:
15+
description: Normalized pull request context JSON from Get-PSModuleSettings.
16+
required: false
17+
default: ''
1418
WhatIf:
1519
description: If specified, the action will only log the changes it would make.
1620
required: false
@@ -33,5 +37,6 @@ runs:
3337
env:
3438
GH_TOKEN: ${{ env.GH_TOKEN }}
3539
PSMODULE_CLEANUP_PSMODULEPRERELEASES_INPUT_WhatIf: ${{ inputs.WhatIf }}
40+
PSMODULE_CLEANUP_PSMODULEPRERELEASES_INPUT_PullRequest: ${{ inputs.PullRequest }}
3641
PSMODULE_CLEANUP_PSMODULEPRERELEASES_CONTEXT_ReleaseTag: ${{ inputs.ReleaseTag }}
3742
run: ${{ github.action_path }}/src/cleanup.ps1

.github/actions/Cleanup-PSModulePrereleases/src/cleanup.ps1

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ Import-Module -Name 'PSModule' -Force
99
LogGroup 'Load inputs' {
1010
$whatIf = $env:PSMODULE_CLEANUP_PSMODULEPRERELEASES_INPUT_WhatIf -eq 'true'
1111

12-
$githubEventJson = Get-Content -Raw $env:GITHUB_EVENT_PATH
13-
$githubEvent = $githubEventJson | ConvertFrom-Json
14-
$pull_request = $githubEvent.pull_request
15-
if (-not $pull_request) {
16-
throw 'GitHub event does not contain pull_request data. This script must be run from a pull_request event.'
12+
$pullRequestJson = $env:PSMODULE_CLEANUP_PSMODULEPRERELEASES_INPUT_PullRequest
13+
$prHeadRef = if (-not [string]::IsNullOrWhiteSpace($pullRequestJson) -and $pullRequestJson -ne 'null') {
14+
($pullRequestJson | ConvertFrom-Json).HeadRef
15+
} else {
16+
$githubEventJson = Get-Content -Raw $env:GITHUB_EVENT_PATH
17+
$githubEvent = $githubEventJson | ConvertFrom-Json
18+
$githubEvent.pull_request.head.ref
19+
}
20+
21+
if ([string]::IsNullOrWhiteSpace($prHeadRef)) {
22+
Write-Host '::notice::No pull request head ref is available. Nothing to cleanup.'
23+
exit 0
1724
}
18-
$prHeadRef = $pull_request.head.ref
1925
$prereleaseName = $prHeadRef -replace '[^a-zA-Z0-9]'
2026

2127
if ([string]::IsNullOrWhiteSpace($prereleaseName)) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ inputs:
1313
PSGALLERY_API_KEY:
1414
description: PowerShell Gallery API Key.
1515
required: true
16+
PullRequest:
17+
description: Normalized pull request context JSON from Get-PSModuleSettings.
18+
required: false
19+
default: ''
1620
WhatIf:
1721
description: If specified, the action will only log the changes it would make, but will not publish the module.
1822
required: false
@@ -50,5 +54,6 @@ runs:
5054
PSMODULE_PUBLISH_PSMODULE_INPUT_Name: ${{ inputs.Name }}
5155
PSMODULE_PUBLISH_PSMODULE_INPUT_ModulePath: ${{ inputs.ModulePath }}
5256
PSMODULE_PUBLISH_PSMODULE_INPUT_PSGALLERY_API_KEY: ${{ inputs.PSGALLERY_API_KEY }}
57+
PSMODULE_PUBLISH_PSMODULE_INPUT_PullRequest: ${{ inputs.PullRequest }}
5358
PSMODULE_PUBLISH_PSMODULE_INPUT_WhatIf: ${{ inputs.WhatIf }}
5459
run: ${{ github.action_path }}/src/publish.ps1

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

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
'PSUseDeclaredVarsMoreThanAssignments', 'psGalleryApiKey',
33
Justification = 'Variable is used in script blocks.'
44
)]
5-
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
6-
'PSUseDeclaredVarsMoreThanAssignments', 'prNumber',
7-
Justification = 'Variable is used in script blocks.'
8-
)]
95
[CmdletBinding()]
106
param()
117

@@ -47,17 +43,29 @@ LogGroup 'Load inputs' {
4743
}
4844
#endregion Load inputs
4945

50-
#region Load PR information
51-
LogGroup 'Load PR information' {
52-
$githubEventJson = Get-Content -Raw $env:GITHUB_EVENT_PATH
53-
$githubEvent = $githubEventJson | ConvertFrom-Json
54-
$pull_request = $githubEvent.pull_request
55-
if (-not $pull_request) {
56-
throw 'GitHub event does not contain pull_request data. This script must be run from a pull_request event.'
46+
#region Load release context
47+
LogGroup 'Load release context' {
48+
$pullRequestJson = $env:PSMODULE_PUBLISH_PSMODULE_INPUT_PullRequest
49+
$pullRequest = if (-not [string]::IsNullOrWhiteSpace($pullRequestJson) -and $pullRequestJson -ne 'null') {
50+
$pullRequestJson | ConvertFrom-Json
51+
} else {
52+
$githubEventJson = Get-Content -Raw $env:GITHUB_EVENT_PATH
53+
$githubEvent = $githubEventJson | ConvertFrom-Json
54+
if ($githubEvent.pull_request) {
55+
[pscustomobject]@{
56+
Number = $githubEvent.pull_request.number
57+
}
58+
}
59+
}
60+
61+
$prNumber = $pullRequest.Number
62+
if ($prNumber) {
63+
Write-Host "Pull request: [#$prNumber]"
64+
} else {
65+
Write-Host 'No pull request context is available; publication comments are disabled.'
5766
}
58-
$prNumber = $pull_request.number
5967
}
60-
#endregion Load PR information
68+
#endregion Load release context
6169

6270
#region Resolve version from manifest
6371
# The manifest was stamped with the final version during Build-PSModule. This step is read-only
@@ -145,18 +153,20 @@ LogGroup 'Publish to PSGallery' {
145153
}
146154
}
147155

148-
if ($whatIf) {
156+
if ($whatIf -and $prNumber) {
149157
Write-Host (
150158
"gh pr comment $prNumber -b " +
151159
"'✅ $releaseType`: PowerShell Gallery - [$name $publishPSVersion]($psGalleryReleaseLink)'"
152160
)
153-
} else {
161+
} elseif (-not $whatIf -and $prNumber) {
154162
Write-Host "::notice title=✅ $releaseType`: PowerShell Gallery - $name $publishPSVersion::$psGalleryReleaseLink"
155163
gh pr comment $prNumber -b "$releaseType`: PowerShell Gallery - [$name $publishPSVersion]($psGalleryReleaseLink)"
156164
if ($LASTEXITCODE -ne 0) {
157165
Write-Error 'Failed to comment on the pull request.'
158166
exit $LASTEXITCODE
159167
}
168+
} else {
169+
Write-Host "::notice title=✅ $releaseType`: PowerShell Gallery - $name $publishPSVersion::$psGalleryReleaseLink"
160170
}
161171
}
162172
#endregion Publish to PSGallery

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ inputs:
3737
ReleaseTag:
3838
description: Full GitHub release tag resolved by Resolve-PSModuleVersion.
3939
required: true
40+
PullRequest:
41+
description: Normalized pull request context JSON from Get-PSModuleSettings.
42+
required: false
43+
default: ''
44+
CommitSha:
45+
description: Commit SHA that produced the module artifact. Stable tags target this exact commit.
46+
required: false
47+
default: ''
4048

4149
outputs:
4250
ReleaseTag:
@@ -70,4 +78,6 @@ runs:
7078
PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsReleaseName: ${{ inputs.UsePRTitleAsReleaseName }}
7179
PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsNotesHeading: ${{ inputs.UsePRTitleAsNotesHeading }}
7280
PSMODULE_RELEASE_PSMODULE_INPUT_ReleaseTag: ${{ inputs.ReleaseTag }}
81+
PSMODULE_RELEASE_PSMODULE_INPUT_PullRequest: ${{ inputs.PullRequest }}
82+
PSMODULE_RELEASE_PSMODULE_INPUT_CommitSha: ${{ inputs.CommitSha || github.sha }}
7383
run: ${{ github.action_path }}/src/release.ps1

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

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,15 @@
3333
Justification = 'Variable is used in script blocks.'
3434
)]
3535
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
36-
'PSUseDeclaredVarsMoreThanAssignments', 'prNumber',
36+
'PSUseDeclaredVarsMoreThanAssignments', 'prHeadRef',
3737
Justification = 'Variable is used in script blocks.'
3838
)]
3939
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
40-
'PSUseDeclaredVarsMoreThanAssignments', 'prHeadRef',
40+
'PSUseDeclaredVarsMoreThanAssignments', 'commitSha',
41+
Justification = 'Variable is used in script blocks.'
42+
)]
43+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
44+
'PSUseDeclaredVarsMoreThanAssignments', 'commitMessage',
4145
Justification = 'Variable is used in script blocks.'
4246
)]
4347
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
@@ -80,6 +84,7 @@ LogGroup 'Load inputs' {
8084
$usePRTitleAsReleaseName = $env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsReleaseName -eq 'true'
8185
$usePRTitleAsNotesHeading = $env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsNotesHeading -eq 'true'
8286
$releaseTag = $env:PSMODULE_RELEASE_PSMODULE_INPUT_ReleaseTag
87+
$commitSha = $env:PSMODULE_RELEASE_PSMODULE_INPUT_CommitSha
8388
if ([string]::IsNullOrWhiteSpace($releaseTag)) {
8489
throw 'ReleaseTag is required. Ensure Publish.Module.Resolution.FullVersion is passed from the Plan job.'
8590
}
@@ -90,15 +95,29 @@ LogGroup 'Load inputs' {
9095
Write-Host "WhatIf: [$whatIf]"
9196
}
9297

93-
LogGroup 'Load PR information' {
98+
LogGroup 'Load release context' {
9499
$githubEventJson = Get-Content -Raw $env:GITHUB_EVENT_PATH
95100
$githubEvent = $githubEventJson | ConvertFrom-Json
96-
$pull_request = $githubEvent.pull_request
97-
if (-not $pull_request) {
98-
throw 'GitHub event does not contain pull_request data. This script must be run from a pull_request event.'
101+
$pullRequestJson = $env:PSMODULE_RELEASE_PSMODULE_INPUT_PullRequest
102+
$pullRequest = if (-not [string]::IsNullOrWhiteSpace($pullRequestJson) -and $pullRequestJson -ne 'null') {
103+
$pullRequestJson | ConvertFrom-Json
104+
} elseif ($githubEvent.pull_request) {
105+
[pscustomobject]@{
106+
Number = $githubEvent.pull_request.number
107+
Title = $githubEvent.pull_request.title
108+
Body = $githubEvent.pull_request.body
109+
HeadRef = $githubEvent.pull_request.head.ref
110+
}
111+
}
112+
$prNumber = $pullRequest.Number
113+
$prHeadRef = $pullRequest.HeadRef
114+
$commitMessage = $githubEvent.head_commit.message
115+
116+
if ($prNumber) {
117+
Write-Host "Pull request: [#$prNumber]"
118+
} else {
119+
Write-Host 'No pull request context is available; using the pushed commit message for release notes.'
99120
}
100-
$prNumber = $pull_request.number
101-
$prHeadRef = $pull_request.head.ref
102121
}
103122

104123
LogGroup 'Resolve version from manifest' {
@@ -153,6 +172,7 @@ LogGroup 'Resolve version from manifest' {
153172
ReleaseTag = $releaseTag
154173
PRNumber = $prNumber
155174
PRHeadRef = $prHeadRef
175+
CommitSha = $commitSha
156176
} | Format-List | Out-String
157177
)
158178
}
@@ -178,31 +198,38 @@ LogGroup 'Create GitHub release' {
178198
}
179199

180200
if (-not $releaseExists) {
181-
if ($usePRTitleAsReleaseName -and $pull_request.title) {
182-
$releaseCreateCommand += @('--title', $pull_request.title)
183-
Write-Host "Using PR title as release name: [$($pull_request.title)]"
201+
if ($usePRTitleAsReleaseName -and $pullRequest.Title) {
202+
$releaseCreateCommand += @('--title', $pullRequest.Title)
203+
Write-Host "Using PR title as release name: [$($pullRequest.Title)]"
184204
} else {
185205
$releaseCreateCommand += @('--title', $releaseTag)
186206
}
187207

188208
# Build release notes content. Uses a file to preserve special characters.
189-
if ($usePRTitleAsNotesHeading -and $usePRBodyAsReleaseNotes -and $pull_request.title -and $pull_request.body) {
190-
$notes = "# $($pull_request.title) (#$prNumber)`n`n$($pull_request.body)"
209+
if ($usePRTitleAsNotesHeading -and $usePRBodyAsReleaseNotes -and $pullRequest.Title -and $pullRequest.Body) {
210+
$notes = "# $($pullRequest.Title) (#$prNumber)`n`n$($pullRequest.Body)"
191211
$notesFilePath = [System.IO.Path]::GetTempFileName()
192212
Set-Content -Path $notesFilePath -Value $notes -Encoding utf8
193213
$releaseCreateCommand += @('--notes-file', $notesFilePath)
194214
Write-Host 'Using PR title as H1 heading with link and body as release notes'
195-
} elseif ($usePRBodyAsReleaseNotes -and $pull_request.body) {
215+
} elseif ($usePRBodyAsReleaseNotes -and $pullRequest.Body) {
196216
$notesFilePath = [System.IO.Path]::GetTempFileName()
197-
Set-Content -Path $notesFilePath -Value $pull_request.body -Encoding utf8
217+
Set-Content -Path $notesFilePath -Value $pullRequest.Body -Encoding utf8
198218
$releaseCreateCommand += @('--notes-file', $notesFilePath)
199219
Write-Host 'Using PR body as release notes'
220+
} elseif (-not [string]::IsNullOrWhiteSpace($commitMessage)) {
221+
$notesFilePath = [System.IO.Path]::GetTempFileName()
222+
Set-Content -Path $notesFilePath -Value $commitMessage -Encoding utf8
223+
$releaseCreateCommand += @('--notes-file', $notesFilePath)
224+
Write-Host 'Using the pushed commit message as release notes'
200225
} else {
201226
$releaseCreateCommand += @('--generate-notes')
202227
}
203228

204229
if ($createPrerelease) {
205230
$releaseCreateCommand += @('--target', $prHeadRef, '--prerelease')
231+
} elseif (-not [string]::IsNullOrWhiteSpace($commitSha)) {
232+
$releaseCreateCommand += @('--target', $commitSha)
206233
}
207234

208235
try {
@@ -247,9 +274,9 @@ LogGroup 'Create GitHub release' {
247274
}
248275
}
249276

250-
if ($whatIf) {
277+
if ($whatIf -and $prNumber) {
251278
Write-Host "gh pr comment $prNumber -b '✅ $($releaseType): GitHub - $name $releaseTag'"
252-
} else {
279+
} elseif (-not $whatIf -and $prNumber) {
253280
gh pr comment $prNumber -b "$releaseType`: GitHub - [$name $releaseTag]($releaseURL)"
254281
if ($LASTEXITCODE -ne 0) {
255282
throw 'Failed to comment on the pull request.'

.github/actions/Release-PSModule/tests/Release-PSModule.WhatIf.Tests.ps1

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ BeforeAll {
2222
'PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsReleaseName'
2323
'PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsNotesHeading'
2424
'PSMODULE_RELEASE_PSMODULE_INPUT_ReleaseTag'
25+
'PSMODULE_RELEASE_PSMODULE_INPUT_PullRequest'
26+
'PSMODULE_RELEASE_PSMODULE_INPUT_CommitSha'
2527
)
2628
$script:originalEnvironment = @{}
2729
foreach ($name in $script:environmentVariableNames) {
@@ -88,6 +90,8 @@ Describe 'Release-PSModule WhatIf' {
8890
$env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsReleaseName = 'false'
8991
$env:PSMODULE_RELEASE_PSMODULE_INPUT_UsePRTitleAsNotesHeading = 'true'
9092
$env:PSMODULE_RELEASE_PSMODULE_INPUT_ReleaseTag = 'v1.2.3-preview.1'
93+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_PullRequest = ''
94+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_CommitSha = ''
9195
}
9296

9397
It 'creates a prefixed prerelease tag without GitHub side effects' {
@@ -122,4 +126,18 @@ Describe 'Release-PSModule WhatIf' {
122126
$ghCalls | Should -Not -Match 'release create'
123127
Get-Content -Path $script:githubOutputPath | Should -Contain 'ReleaseTag=v1.2.3-preview.1'
124128
}
129+
130+
It 'creates a stable release from a direct push without pull request context' {
131+
$manifest = Get-Content -Path $manifestPath -Raw
132+
$manifest -replace "Prerelease = 'preview.1'", "Prerelease = ''" | Set-Content -Path $manifestPath
133+
@{ head_commit = @{ message = 'Publish direct push release' } } |
134+
ConvertTo-Json -Depth 5 | Set-Content -Path $eventPath
135+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_ReleaseTag = 'v1.2.3'
136+
$env:PSMODULE_RELEASE_PSMODULE_INPUT_CommitSha = 'pushed-commit-sha'
137+
138+
$releaseOutput = & $script:releaseScriptPath 6>&1 | Out-String
139+
140+
Get-Content -Path $script:githubOutputPath | Should -Contain 'ReleaseTag=v1.2.3'
141+
$releaseOutput | Should -Match '--target pushed-commit-sha'
142+
}
125143
}

.github/workflows/Publish-Module.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ jobs:
5454
permission-pull-requests: write
5555

5656
- name: Publish module
57+
id: publish-module
5758
if: fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType != 'None'
5859
uses: ./_wf/.github/actions/Publish-PSModule
5960
env:
@@ -62,12 +63,16 @@ jobs:
6263
Name: ${{ fromJson(inputs.Settings).Name }}
6364
ModulePath: outputs/module
6465
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
66+
PullRequest: ${{ toJson(fromJson(inputs.Settings).Context.PullRequest) }}
6567
WhatIf: ${{ github.repository == 'PSModule/Process-PSModule' }}
6668
WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }}
6769

6870
- name: Create GitHub release
6971
id: create-github-release
70-
if: always() && !cancelled() && fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType != 'None'
72+
if: >-
73+
!cancelled() &&
74+
fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType != 'None' &&
75+
steps.publish-module.outcome == 'success'
7176
uses: ./_wf/.github/actions/Release-PSModule
7277
env:
7378
GH_TOKEN: ${{ steps.App-Token.outputs.token }}
@@ -80,19 +85,23 @@ jobs:
8085
UsePRBodyAsReleaseNotes: ${{ fromJson(inputs.Settings).Publish.Module.UsePRBodyAsReleaseNotes }}
8186
UsePRTitleAsNotesHeading: ${{ fromJson(inputs.Settings).Publish.Module.UsePRTitleAsNotesHeading }}
8287
ReleaseTag: ${{ fromJson(inputs.Settings).Publish.Module.Resolution.FullVersion }}
88+
PullRequest: ${{ toJson(fromJson(inputs.Settings).Context.PullRequest) }}
89+
CommitSha: ${{ fromJson(inputs.Settings).Context.CommitSha }}
8390
WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }}
8491

8592
- name: Cleanup prereleases
8693
if: >-
8794
always() && !cancelled() &&
8895
fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType != 'Prerelease' &&
8996
(fromJson(inputs.Settings).Publish.Module.Resolution.ReleaseType == 'None' ||
90-
steps.create-github-release.outcome == 'success')
97+
(steps.publish-module.outcome == 'success' &&
98+
steps.create-github-release.outcome == 'success'))
9199
uses: ./_wf/.github/actions/Cleanup-PSModulePrereleases
92100
env:
93101
GH_TOKEN: ${{ steps.App-Token.outputs.token }}
94102
with:
95103
WhatIf: ${{ github.repository == 'PSModule/Process-PSModule' }}
96104
AutoCleanup: ${{ fromJson(inputs.Settings).Publish.Module.AutoCleanup }}
97105
ReleaseTag: ${{ steps.create-github-release.outputs.ReleaseTag }}
106+
PullRequest: ${{ toJson(fromJson(inputs.Settings).Context.PullRequest) }}
98107
WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }}

0 commit comments

Comments
 (0)