Repository navigation
Expand file tree
/
Copy pathGet-PSModuleSettings.Helpers.psm1
More file actions
315 lines (268 loc) · 12 KB
/
Copy pathGet-PSModuleSettings.Helpers.psm1
File metadata and controls
315 lines (268 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
function Resolve-WorkflowEventRouting {
<#
.SYNOPSIS
Resolves release and execution routing from normalized GitHub event state.
#>
[CmdletBinding()]
[OutputType([PSCustomObject])]
param(
[Parameter(Mandatory)]
[string] $EventName,
[Parameter()]
[string] $EventAction,
[Parameter()]
[bool] $PullRequestIsMerged,
[Parameter()]
[bool] $PullRequestIsClosed,
[Parameter()]
[bool] $IsTargetDefaultBranch,
[Parameter()]
[bool] $IsPushToDefaultBranch,
[Parameter()]
[bool] $IsManualDispatchToDefaultBranch,
[Parameter()]
[bool] $HasImportantChanges,
[Parameter()]
[bool] $HasPrereleaseLabel
)
$isPR = $EventName -eq 'pull_request'
$isPush = $EventName -eq 'push'
$isManualDispatch = $EventName -eq 'workflow_dispatch'
$isActivePR = $isPR -and -not $PullRequestIsClosed
$isOpenOrUpdatedPR = $isActivePR -and $EventAction -in @('opened', 'reopened', 'synchronize', 'labeled', 'unlabeled')
$isOpenOrLabeledPR = $isActivePR -and $EventAction -in @('opened', 'reopened', 'synchronize', 'labeled')
$isClosedPR = $isPR -and $EventAction -eq 'closed'
$isAbandonedPR = $isClosedPR -and -not $PullRequestIsMerged
$isMergedPR = $isClosedPR -and $PullRequestIsMerged
$shouldPrerelease = $isOpenOrLabeledPR -and $HasPrereleaseLabel -and $HasImportantChanges
$shouldRelease = (
($IsPushToDefaultBranch -or $IsManualDispatchToDefaultBranch) -and
$HasImportantChanges
)
[pscustomobject]@{
IsPR = $isPR
IsPush = $isPush
IsManualDispatch = $isManualDispatch
IsOpenOrUpdatedPR = $isOpenOrUpdatedPR
IsOpenOrLabeledPR = $isOpenOrLabeledPR
IsClosedPR = $isClosedPR
IsAbandonedPR = $isAbandonedPR
IsMergedPR = $isMergedPR
IsTargetDefaultBranch = $IsTargetDefaultBranch
IsPushToDefaultBranch = $IsPushToDefaultBranch
IsManualDispatchToDefaultBranch = $IsManualDispatchToDefaultBranch
ShouldPrerelease = $shouldPrerelease
ReleaseType = if ($shouldRelease) {
'Release'
} elseif ($shouldPrerelease) {
'Prerelease'
} else {
'None'
}
ShouldRunBuildTest = (
-not $isClosedPR -and
((-not $isPR) -or (-not $PullRequestIsClosed)) -and
$HasImportantChanges
)
ShouldCleanupEvent = $isClosedPR
ShouldRunCleanup = $isClosedPR -or $shouldRelease
}
}
function Select-PullRequestForPush {
<#
.SYNOPSIS
Selects the merged default-branch pull request associated with a pushed commit.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '',
Justification = 'Parameter is used inside a Sort-Object script block.')]
[CmdletBinding()]
[OutputType([PSCustomObject])]
param(
[Parameter()]
[object[]] $PullRequest,
[Parameter(Mandatory)]
[string] $DefaultBranch,
[Parameter(Mandatory)]
[string] $CommitSha
)
$PullRequest |
Where-Object {
$_.Base.Ref -eq $DefaultBranch -and
-not [string]::IsNullOrWhiteSpace($_.merged_at) -and
$_.merge_commit_sha -eq $CommitSha
} |
Sort-Object -Property @{ Expression = { $_.merged_at }; Descending = $true } |
Select-Object -First 1
}
function Get-DiscardedReleasePullRequest {
<#
.SYNOPSIS
Reports merged default-branch pull requests whose version label would be discarded.
.DESCRIPTION
Select-PullRequestForPush returns nothing both when a commit has no associated pull request
and when every associated pull request fails its criteria. Only the first case is safe: a
commit pushed directly to the default branch has no label to honour, so the direct-release
path applies the default patch bump.
The unsafe case is a pull request that was merged into the default branch, and therefore
carries the version label that was meant to drive the release, but was not selected because
its merge commit does not match the commit being released. Falling back to a patch bump
there publishes a version nobody asked for, and a PowerShell Gallery version cannot be
reclaimed, so the caller must fail instead.
Pull requests that are not merged are ignored. The GitHub commit association endpoint also
returns open pull requests whose branch contains the commit, which is expected and carries
no release intent.
.OUTPUTS
String, one description per merged pull request that was rejected. Nothing when the
associated pull requests carry no release intent.
.EXAMPLE
Get-DiscardedReleasePullRequest -PullRequest $associated -DefaultBranch main -CommitSha $sha
Returns '#412 was merged into [main] with merge commit [abc123]'.
#>
[CmdletBinding()]
[OutputType([string])]
param(
# The pull requests the GitHub API associated with the commit.
[Parameter()]
[object[]] $PullRequest,
# The repository default branch a release must target.
[Parameter(Mandatory)]
[string] $DefaultBranch,
# The commit the workflow is resolving a release for.
[Parameter(Mandatory)]
[string] $CommitSha
)
foreach ($candidate in ($PullRequest | Where-Object { $null -ne $_ })) {
$isMergedToDefaultBranch = (
$candidate.Base.Ref -eq $DefaultBranch -and
-not [string]::IsNullOrWhiteSpace($candidate.merged_at)
)
if (-not $isMergedToDefaultBranch) { continue }
if ($candidate.merge_commit_sha -eq $CommitSha) { continue }
"#$($candidate.Number) was merged into [$DefaultBranch] with merge commit [$($candidate.merge_commit_sha)]"
}
}
function Resolve-ReleasePullRequest {
<#
.SYNOPSIS
Resolves the pull request whose version label drives the release for a commit.
.DESCRIPTION
A push resolves the pull request associated with the pushed commit so a default-branch
release honours the merged pull request's version label. A manual dispatch on the default
branch is the documented recovery route for a failed or cancelled release run and targets
the same merge commit, so it must resolve the same pull request. Excluding it left the pull
request unresolved, and the version silently fell back to a patch bump through AutoPatching.
When no pull request is selected, the outcome depends on why. A commit pushed directly to
the default branch has no label to honour, so the release proceeds with the default patch
bump. A commit associated with a merged default-branch pull request that does not match it
does carry a label, and applying a patch bump would publish a version nobody asked for. A
PowerShell Gallery version cannot be reclaimed, so that case throws instead.
.OUTPUTS
PSCustomObject with Resolved, indicating whether the lookup ran, and PullRequest, which is
null when the commit has no associated release pull request.
.EXAMPLE
Resolve-ReleasePullRequest -EventName workflow_dispatch -CommitSha $sha -DefaultBranch main `
-IsManualDispatchToDefaultBranch $true -GetAssociatedPullRequest { param($Sha) $pulls }
Resolves the merged pull request for a recovery dispatch so its version label is honoured.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '',
Justification = 'Parameters are used inside a LogGroup script block.')]
[CmdletBinding()]
[OutputType([PSCustomObject])]
param(
# The name of the GitHub event that triggered the workflow.
[Parameter(Mandatory)]
[string] $EventName,
# The commit the workflow is resolving a release for.
[Parameter()]
[AllowEmptyString()]
[AllowNull()]
[string] $CommitSha,
# The repository default branch a release must target.
[Parameter(Mandatory)]
[string] $DefaultBranch,
# Whether the workflow was triggered by a push to the default branch.
[Parameter()]
[bool] $IsPushToDefaultBranch,
# Whether the workflow was manually dispatched against the default branch.
[Parameter()]
[bool] $IsManualDispatchToDefaultBranch,
# Returns the pull requests GitHub associates with a commit. Takes the commit SHA.
[Parameter(Mandatory)]
[scriptblock] $GetAssociatedPullRequest
)
$isPush = $EventName -eq 'push'
$shouldResolve = (
($isPush -or $IsManualDispatchToDefaultBranch) -and
-not [string]::IsNullOrWhiteSpace($CommitSha)
)
if (-not $shouldResolve) {
return [pscustomobject]@{ Resolved = $false; PullRequest = $null }
}
LogGroup "Resolve pull request for commit [$CommitSha]" {
$associated = @((& $GetAssociatedPullRequest $CommitSha) | Where-Object { $null -ne $_ })
$pullRequest = Select-PullRequestForPush -PullRequest $associated `
-DefaultBranch $DefaultBranch `
-CommitSha $CommitSha
if ($pullRequest) {
Write-Host "Resolved pull request #$($pullRequest.Number) from commit [$CommitSha]."
return [pscustomobject]@{ Resolved = $true; PullRequest = $pullRequest }
}
# Only a release-bearing event can publish a wrong version. A push to a feature branch has
# no release to get wrong, and its commit is legitimately claimed by an open pull request.
$isReleaseEvent = $IsPushToDefaultBranch -or $IsManualDispatchToDefaultBranch
$discarded = if ($isReleaseEvent) {
@(Get-DiscardedReleasePullRequest -PullRequest $associated `
-DefaultBranch $DefaultBranch `
-CommitSha $CommitSha)
} else {
@()
}
if ($discarded.Count -gt 0) {
throw (
"Commit [$CommitSha] cannot be released because its version label cannot be determined. " +
'The following merged pull request(s) are associated with it but none matches the commit ' +
"being released: $($discarded -join '; '). " +
'Refusing to fall back to a patch bump, because a wrong version published to the ' +
'PowerShell Gallery cannot be reclaimed. Re-run the workflow against the merge commit ' +
'of the pull request you intend to release.'
)
}
Write-Host "::notice::No pull request is associated with commit [$CommitSha]."
[pscustomobject]@{ Resolved = $true; PullRequest = $null }
}
}
function Get-FilesFromGitTree {
<#
.SYNOPSIS
Returns the files contained in a complete Git tree response.
#>
[CmdletBinding()]
[OutputType([string])]
param(
[Parameter(Mandatory)]
[PSCustomObject] $Tree
)
if ($Tree.truncated) {
throw 'Cannot determine changed files because the Git tree response was truncated.'
}
$Tree.tree |
Where-Object { $_.type -eq 'blob' } |
Select-Object -ExpandProperty path
}
function Get-FilesFromGitHubComparison {
<#
.SYNOPSIS
Returns files from a complete GitHub compare response.
#>
[CmdletBinding()]
[OutputType([string])]
param(
[Parameter(Mandatory)]
[PSCustomObject] $Comparison
)
$files = @($Comparison.files | Where-Object { $null -ne $_ })
if ($files.Count -ge 300) {
throw 'Cannot determine changed files because the GitHub compare response reached its 300-file limit.'
}
$files | Select-Object -ExpandProperty filename
}