Skip to content

Commit 20bbb73

Browse files
Add GitHub Action pin updater
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent ae579b1 commit 20bbb73

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
[CmdletBinding(SupportsShouldProcess)]
2+
param(
3+
[Parameter()]
4+
[string]$Path = '.github'
5+
)
6+
7+
Set-StrictMode -Version Latest
8+
$ErrorActionPreference = 'Stop'
9+
10+
function Get-CommitShaForTag {
11+
[OutputType([string])]
12+
param(
13+
[Parameter(Mandatory)]
14+
[string]$Repository,
15+
16+
[Parameter(Mandatory)]
17+
[string]$Tag,
18+
19+
[Parameter(Mandatory)]
20+
[hashtable]$Headers
21+
)
22+
23+
$tagReference = Invoke-RestMethod -Headers $Headers -Uri "https://api.github.com/repos/$Repository/git/ref/tags/$Tag"
24+
while ($tagReference.object.type -eq 'tag') {
25+
$tagReference = Invoke-RestMethod -Headers $Headers -Uri "https://api.github.com/repos/$Repository/git/tags/$($tagReference.object.sha)"
26+
}
27+
28+
if ($tagReference.object.type -ne 'commit') {
29+
throw "Tag '$Tag' in '$Repository' does not resolve to a commit."
30+
}
31+
32+
return $tagReference.object.sha
33+
}
34+
35+
function Get-LatestActionPin {
36+
[OutputType([pscustomobject])]
37+
param(
38+
[Parameter(Mandatory)]
39+
[string]$Action,
40+
41+
[Parameter(Mandatory)]
42+
[hashtable]$Headers
43+
)
44+
45+
if ($Action -notmatch '^(?<owner>[^/]+)/(?<repository>[^/]+)(?:/.*)?$') {
46+
throw "'$Action' is not a GitHub Action repository reference."
47+
}
48+
49+
$repository = "$($Matches.owner)/$($Matches.repository)"
50+
$release = Invoke-RestMethod -Headers $Headers -Uri "https://api.github.com/repos/$repository/releases/latest"
51+
52+
return [pscustomobject]@{
53+
Repository = $repository
54+
Tag = $release.tag_name
55+
Sha = Get-CommitShaForTag -Repository $repository -Tag $release.tag_name -Headers $Headers
56+
}
57+
}
58+
59+
$root = (Resolve-Path -LiteralPath $Path).Path
60+
$token = $env:GITHUB_TOKEN
61+
if ([string]::IsNullOrWhiteSpace($token)) {
62+
$token = $env:GH_TOKEN
63+
}
64+
65+
$headers = @{
66+
Accept = 'application/vnd.github+json'
67+
'X-GitHub-Api-Version' = '2022-11-28'
68+
'User-Agent' = 'Process-PSModule-action-pin-updater'
69+
}
70+
if (-not [string]::IsNullOrWhiteSpace($token)) {
71+
$headers.Authorization = "Bearer $token"
72+
}
73+
74+
$linePattern = [regex]'(?m)^(?<prefix>[ \t]*uses:[ \t]*)(?<action>[^@\s]+)@(?<sha>[^\s#]+)(?:[ \t]*#.*)?(?<carriageReturn>\r?)$'
75+
$pins = @{}
76+
77+
Get-ChildItem -LiteralPath $root -Recurse -File -Include '*.yml', '*.yaml' | ForEach-Object {
78+
$file = $_
79+
$content = [System.IO.File]::ReadAllText($file.FullName)
80+
$lineMatches = $linePattern.Matches($content)
81+
if ($lineMatches.Count -eq 0) {
82+
return
83+
}
84+
85+
$updatedContent = $linePattern.Replace($content, {
86+
param($match)
87+
88+
$action = $match.Groups['action'].Value
89+
if ($action.StartsWith('./', [System.StringComparison]::Ordinal) -or $action.StartsWith('docker://', [System.StringComparison]::OrdinalIgnoreCase)) {
90+
return $match.Value
91+
}
92+
93+
if (-not $pins.ContainsKey($action)) {
94+
$pins[$action] = Get-LatestActionPin -Action $action -Headers $headers
95+
}
96+
97+
$pin = $pins[$action]
98+
return "$($match.Groups['prefix'].Value)$action@$($pin.Sha) # $($pin.Tag)$($match.Groups['carriageReturn'].Value)"
99+
})
100+
101+
if ($updatedContent -eq $content) {
102+
return
103+
}
104+
105+
$relativePath = [System.IO.Path]::GetRelativePath((Get-Location).Path, $file.FullName)
106+
$actionNames = $lineMatches | ForEach-Object { $_.Groups['action'].Value } | Sort-Object -Unique
107+
$target = "$relativePath ($($actionNames -join ', '))"
108+
if ($PSCmdlet.ShouldProcess($target, 'Update GitHub Action SHA pins and release tags')) {
109+
$hasUtf8Bom = $content.Length -gt 0 -and $content[0] -eq [char]0xFEFF
110+
[System.IO.File]::WriteAllText($file.FullName, $updatedContent, [System.Text.UTF8Encoding]::new($hasUtf8Bom))
111+
}
112+
113+
[pscustomobject]@{
114+
Path = $relativePath
115+
Actions = $actionNames -join ', '
116+
}
117+
}

0 commit comments

Comments
 (0)