Skip to content

Commit c2ee566

Browse files
Compose configurable defaults with dispatch recovery and current framework guidance
2 parents 8add11c + ddd57bd commit c2ee566

65 files changed

Lines changed: 1196 additions & 216 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/actions/Build-PSModule/action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ inputs:
99
OutputFolder:
1010
description: Path to the folder where the built module is outputted.
1111
required: false
12-
default: 'outputs/module'
12+
default: '.PSModule/module'
1313
Version:
1414
description: Module version to stamp into the manifest.
1515
required: true

‎.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleManifest.ps1‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
[Parameter(Mandatory)]
2929
[string] $ModuleName,
3030

31-
# Folder where the built modules are outputted. 'outputs/modules/MyModule'
31+
# Folder where the built module is outputted. '.PSModule/module/MyModule'
3232
[Parameter(Mandatory)]
3333
[System.IO.DirectoryInfo] $ModuleOutputFolder,
3434

‎.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleRootModule.ps1‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
[Parameter(Mandatory)]
4444
[string] $ModuleName,
4545

46-
# Folder where the built modules are outputted. 'outputs/modules/MyModule'
46+
# Folder where the built module is outputted. '.PSModule/module/MyModule'
4747
[Parameter(Mandatory)]
4848
[System.IO.DirectoryInfo] $ModuleOutputFolder
4949
)

‎.github/actions/Build-ZensicalSite/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Build-ZensicalSite
22

3-
Builds Zensical documentation site output and normalizes it to `<working-directory>/_site`.
3+
Builds Zensical documentation site output at `<working-directory>/.PSModule/site/_site`.
44

55
## Inputs
66

‎.github/actions/Build-ZensicalSite/src/build-zensical-site.ps1‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,17 @@ param(
66
$ErrorActionPreference = 'Stop'
77

88
$resolvedWorkingDirectory = Resolve-Path -Path $WorkingDirectory | Select-Object -ExpandProperty Path
9-
$siteWorkingDirectory = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'outputs/site'
10-
$outputSitePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath '_site'
9+
$siteWorkingDirectory = Join-Path -Path $resolvedWorkingDirectory -ChildPath '.PSModule/site'
10+
$outputSitePath = Join-Path -Path $siteWorkingDirectory -ChildPath '_site'
1111

1212
Set-Location -Path $siteWorkingDirectory
1313

1414
if (-not (Test-Path -Path 'zensical.toml')) {
15-
throw "No documentation config file found in outputs/site. Expected zensical.toml."
15+
throw "No documentation config file found in .PSModule/site. Expected zensical.toml."
1616
}
1717

1818
zensical build --config-file 'zensical.toml'
1919

20-
if (Test-Path -Path '_site') {
21-
if (Test-Path -Path $outputSitePath) {
22-
Remove-Item -Path $outputSitePath -Recurse -Force
23-
}
24-
Move-Item -Path '_site' -Destination $outputSitePath -Force
25-
}
26-
2720
if (-not (Test-Path -Path $outputSitePath)) {
2821
throw "Expected Zensical output at $outputSitePath but it was not created."
2922
}

‎.github/actions/Build-ZensicalSite/src/main.ps1‎

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
<#
44
.SYNOPSIS
5-
Builds the Zensical site and normalizes output to the expected _site path.
5+
Builds the Zensical site under the framework artifact directory.
66
77
.DESCRIPTION
8-
Runs zensical build from outputs/site and moves the generated _site directory
9-
to <WorkingDirectory>/_site for downstream workflow steps.
8+
Runs zensical build from .PSModule/site. The generated site remains at
9+
<WorkingDirectory>/.PSModule/site/_site for downstream workflow steps.
1010
1111
.EXAMPLE
1212
./main.ps1 -WorkingDirectory '.'
@@ -19,32 +19,25 @@
1919
#>
2020
[CmdletBinding()]
2121
param(
22-
# Build working directory containing outputs/site and destination _site.
22+
# Build working directory containing the Zensical project and output.
2323
[Parameter(Mandatory)]
2424
[string]$WorkingDirectory
2525
)
2626

2727
$ErrorActionPreference = 'Stop'
2828

2929
$resolvedWorkingDirectory = Resolve-Path -Path $WorkingDirectory | Select-Object -ExpandProperty Path
30-
$siteWorkingDirectory = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'outputs/site'
31-
$outputSitePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath '_site'
30+
$siteWorkingDirectory = Join-Path -Path $resolvedWorkingDirectory -ChildPath '.PSModule/site'
31+
$outputSitePath = Join-Path -Path $siteWorkingDirectory -ChildPath '_site'
3232

3333
Set-Location -Path $siteWorkingDirectory
3434

3535
if (-not (Test-Path -Path 'zensical.toml')) {
36-
throw "No documentation config file found in outputs/site. Expected zensical.toml."
36+
throw "No documentation config file found in .PSModule/site. Expected zensical.toml."
3737
}
3838

3939
zensical build --config-file 'zensical.toml'
4040

41-
if (Test-Path -Path '_site') {
42-
if (Test-Path -Path $outputSitePath) {
43-
Remove-Item -Path $outputSitePath -Recurse -Force
44-
}
45-
Move-Item -Path '_site' -Destination $outputSitePath -Force
46-
}
47-
4841
if (-not (Test-Path -Path $outputSitePath)) {
4942
throw "Expected Zensical output at $outputSitePath but it was not created."
5043
}

‎.github/actions/Document-PSModule/src/main.ps1‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ Get-ChildItem -Path $path -Filter '*.ps1' -Recurse | Resolve-Path -Relative | Fo
4040
Write-Host '::group::Loading inputs'
4141
$env:GITHUB_REPOSITORY_NAME = $env:GITHUB_REPOSITORY -replace '.+/'
4242
$moduleSourceFolderPath = Resolve-Path -Path 'src' | Select-Object -ExpandProperty Path
43-
$modulesOutputFolderPath = Join-Path -Path . -ChildPath 'outputs/module'
44-
$docsOutputFolderPath = Join-Path -Path . -ChildPath 'outputs/docs'
43+
$modulesOutputFolderPath = Join-Path -Path . -ChildPath '.PSModule/module'
44+
$docsOutputFolderPath = Join-Path -Path . -ChildPath '.PSModule/docs'
4545

4646
$params = @{
4747
ModuleName = [string]::IsNullOrEmpty($Name) ? $env:GITHUB_REPOSITORY_NAME : $Name

‎.github/actions/Get-PSModuleSettings/src/Get-PSModuleSettings.Helpers.psm1‎

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,170 @@ function Select-PullRequestForPush {
180180
Select-Object -First 1
181181
}
182182

183+
function Get-DiscardedReleasePullRequest {
184+
<#
185+
.SYNOPSIS
186+
Reports merged default-branch pull requests whose version label would be discarded.
187+
188+
.DESCRIPTION
189+
Select-PullRequestForPush returns nothing both when a commit has no associated pull request
190+
and when every associated pull request fails its criteria. Only the first case is safe: a
191+
commit pushed directly to the default branch has no label to honour, so the direct-release
192+
path applies the default patch bump.
193+
194+
The unsafe case is a pull request that was merged into the default branch, and therefore
195+
carries the version label that was meant to drive the release, but was not selected because
196+
its merge commit does not match the commit being released. Falling back to a patch bump
197+
there publishes a version nobody asked for, and a PowerShell Gallery version cannot be
198+
reclaimed, so the caller must fail instead.
199+
200+
Pull requests that are not merged are ignored. The GitHub commit association endpoint also
201+
returns open pull requests whose branch contains the commit, which is expected and carries
202+
no release intent.
203+
204+
.OUTPUTS
205+
String, one description per merged pull request that was rejected. Nothing when the
206+
associated pull requests carry no release intent.
207+
208+
.EXAMPLE
209+
Get-DiscardedReleasePullRequest -PullRequest $associated -DefaultBranch main -CommitSha $sha
210+
211+
Returns '#412 was merged into [main] with merge commit [abc123]'.
212+
#>
213+
[CmdletBinding()]
214+
[OutputType([string])]
215+
param(
216+
# The pull requests the GitHub API associated with the commit.
217+
[Parameter()]
218+
[object[]] $PullRequest,
219+
220+
# The repository default branch a release must target.
221+
[Parameter(Mandatory)]
222+
[string] $DefaultBranch,
223+
224+
# The commit the workflow is resolving a release for.
225+
[Parameter(Mandatory)]
226+
[string] $CommitSha
227+
)
228+
229+
foreach ($candidate in ($PullRequest | Where-Object { $null -ne $_ })) {
230+
$isMergedToDefaultBranch = (
231+
$candidate.Base.Ref -eq $DefaultBranch -and
232+
-not [string]::IsNullOrWhiteSpace($candidate.merged_at)
233+
)
234+
if (-not $isMergedToDefaultBranch) { continue }
235+
if ($candidate.merge_commit_sha -eq $CommitSha) { continue }
236+
237+
"#$($candidate.Number) was merged into [$DefaultBranch] with merge commit [$($candidate.merge_commit_sha)]"
238+
}
239+
}
240+
241+
function Resolve-ReleasePullRequest {
242+
<#
243+
.SYNOPSIS
244+
Resolves the pull request whose version label drives the release for a commit.
245+
246+
.DESCRIPTION
247+
A push resolves the pull request associated with the pushed commit so a default-branch
248+
release honours the merged pull request's version label. A manual dispatch on the default
249+
branch is the documented recovery route for a failed or cancelled release run and targets
250+
the same merge commit, so it must resolve the same pull request. Excluding it left the pull
251+
request unresolved, and the version silently fell back to a patch bump through AutoPatching.
252+
253+
When no pull request is selected, the outcome depends on why. A commit pushed directly to
254+
the default branch has no label to honour, so the release proceeds with the default patch
255+
bump. A commit associated with a merged default-branch pull request that does not match it
256+
does carry a label, and applying a patch bump would publish a version nobody asked for. A
257+
PowerShell Gallery version cannot be reclaimed, so that case throws instead.
258+
259+
.OUTPUTS
260+
PSCustomObject with Resolved, indicating whether the lookup ran, and PullRequest, which is
261+
null when the commit has no associated release pull request.
262+
263+
.EXAMPLE
264+
Resolve-ReleasePullRequest -EventName workflow_dispatch -CommitSha $sha -DefaultBranch main `
265+
-IsManualDispatchToDefaultBranch $true -GetAssociatedPullRequest { param($Sha) $pulls }
266+
267+
Resolves the merged pull request for a recovery dispatch so its version label is honoured.
268+
#>
269+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '',
270+
Justification = 'Parameters are used inside a LogGroup script block.')]
271+
[CmdletBinding()]
272+
[OutputType([PSCustomObject])]
273+
param(
274+
# The name of the GitHub event that triggered the workflow.
275+
[Parameter(Mandatory)]
276+
[string] $EventName,
277+
278+
# The commit the workflow is resolving a release for.
279+
[Parameter()]
280+
[AllowEmptyString()]
281+
[AllowNull()]
282+
[string] $CommitSha,
283+
284+
# The repository default branch a release must target.
285+
[Parameter(Mandatory)]
286+
[string] $DefaultBranch,
287+
288+
# Whether the workflow was triggered by a push to the default branch.
289+
[Parameter()]
290+
[bool] $IsPushToDefaultBranch,
291+
292+
# Whether the workflow was manually dispatched against the default branch.
293+
[Parameter()]
294+
[bool] $IsManualDispatchToDefaultBranch,
295+
296+
# Returns the pull requests GitHub associates with a commit. Takes the commit SHA.
297+
[Parameter(Mandatory)]
298+
[scriptblock] $GetAssociatedPullRequest
299+
)
300+
301+
$isPush = $EventName -eq 'push'
302+
$shouldResolve = (
303+
($isPush -or $IsManualDispatchToDefaultBranch) -and
304+
-not [string]::IsNullOrWhiteSpace($CommitSha)
305+
)
306+
if (-not $shouldResolve) {
307+
return [pscustomobject]@{ Resolved = $false; PullRequest = $null }
308+
}
309+
310+
LogGroup "Resolve pull request for commit [$CommitSha]" {
311+
$associated = @((& $GetAssociatedPullRequest $CommitSha) | Where-Object { $null -ne $_ })
312+
$pullRequest = Select-PullRequestForPush -PullRequest $associated `
313+
-DefaultBranch $DefaultBranch `
314+
-CommitSha $CommitSha
315+
316+
if ($pullRequest) {
317+
Write-Host "Resolved pull request #$($pullRequest.Number) from commit [$CommitSha]."
318+
return [pscustomobject]@{ Resolved = $true; PullRequest = $pullRequest }
319+
}
320+
321+
# Only a release-bearing event can publish a wrong version. A push to a feature branch has
322+
# no release to get wrong, and its commit is legitimately claimed by an open pull request.
323+
$isReleaseEvent = $IsPushToDefaultBranch -or $IsManualDispatchToDefaultBranch
324+
$discarded = if ($isReleaseEvent) {
325+
@(Get-DiscardedReleasePullRequest -PullRequest $associated `
326+
-DefaultBranch $DefaultBranch `
327+
-CommitSha $CommitSha)
328+
} else {
329+
@()
330+
}
331+
if ($discarded.Count -gt 0) {
332+
throw (
333+
"Commit [$CommitSha] cannot be released because its version label cannot be determined. " +
334+
'The following merged pull request(s) are associated with it but none matches the commit ' +
335+
"being released: $($discarded -join '; '). " +
336+
'Refusing to fall back to a patch bump, because a wrong version published to the ' +
337+
'PowerShell Gallery cannot be reclaimed. Re-run the workflow against the merge commit ' +
338+
'of the pull request you intend to release.'
339+
)
340+
}
341+
342+
Write-Host "::notice::No pull request is associated with commit [$CommitSha]."
343+
[pscustomobject]@{ Resolved = $true; PullRequest = $null }
344+
}
345+
}
346+
183347
function Get-FilesFromGitTree {
184348
<#
185349
.SYNOPSIS

‎.github/actions/Get-PSModuleSettings/src/main.ps1‎

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -231,23 +231,29 @@ LogGroup 'Calculate Job Run Conditions:' {
231231
$isManualDispatchToDefaultBranch = $isManualDispatch -and $workflowRef -eq $defaultBranch
232232
$pullRequest = $eventData.PullRequest
233233

234-
if ($isPush -and $commitSha) {
235-
LogGroup "Resolve pull request for commit [$commitSha]" {
234+
# A manual dispatch on the default branch is the documented recovery route for a failed or
235+
# cancelled release run. It targets the same merge commit as the push it replaces, so it must
236+
# resolve the same pull request and honour the same version label. Gating this lookup on
237+
# $isPush alone left $pullRequest null for a dispatch, which silently downgraded a labelled
238+
# Major or Minor release to a Patch bump through the AutoPatching fallback.
239+
$resolveParams = @{
240+
EventName = $eventName
241+
CommitSha = $commitSha
242+
DefaultBranch = $defaultBranch
243+
IsPushToDefaultBranch = $isPushToDefaultBranch
244+
IsManualDispatchToDefaultBranch = $isManualDispatchToDefaultBranch
245+
GetAssociatedPullRequest = {
246+
param($Sha)
236247
$owner = $env:GITHUB_REPOSITORY_OWNER
237248
$repo = $env:GITHUB_REPOSITORY_NAME
238-
$response = Invoke-GitHubAPI -ApiEndpoint "/repos/$owner/$repo/commits/$commitSha/pulls" -Method GET
239-
$associatedPullRequests = @($response.Response)
240-
$pullRequest = Select-PullRequestForPush -PullRequest $associatedPullRequests `
241-
-DefaultBranch $defaultBranch `
242-
-CommitSha $commitSha
243-
244-
if ($pullRequest) {
245-
Write-Host "Resolved pull request #$($pullRequest.Number) from commit [$commitSha]."
246-
} else {
247-
Write-Host "::notice::No pull request is associated with commit [$commitSha]."
248-
}
249+
$response = Invoke-GitHubAPI -ApiEndpoint "/repos/$owner/$repo/commits/$Sha/pulls" -Method GET
250+
$response.Response
249251
}
250252
}
253+
$resolution = Resolve-ReleasePullRequest @resolveParams
254+
if ($resolution.Resolved) {
255+
$pullRequest = $resolution.PullRequest
256+
}
251257

252258
$pullRequestIsMerged = if ($null -eq $pullRequest) {
253259
$false

0 commit comments

Comments
 (0)