1+ <#
2+ . Synopsis
3+ GitHub Action for Gradient
4+ . Description
5+ GitHub Action for Gradient. This will:
6+
7+ * Import Gradient
8+ * If `-Run` is provided, run that script
9+ * Otherwise, unless `-SkipScriptFile` is passed, run all *.Gradient.ps1 files beneath the workflow directory
10+ * If any `-ActionScript` was provided, run scripts from the action path that match a wildcard pattern.
11+
12+ If you will be making changes using the GitHubAPI, you should provide a -GitHubToken
13+
14+ If none is provided, and ENV:GITHUB_TOKEN is set, this will be used instead.
15+
16+ Any files changed can be outputted by the script.
17+ Those changes can be checked back into the repo if `-AutoCommit` is set.
18+ #>
19+
20+ param (
21+ # A PowerShell Script that uses Gradient.
22+ # Any files outputted from the script will be added to the repository.
23+ # If those files have a .Message attached to them, they will be committed with that message.
24+ [string ]
25+ $Run ,
26+
27+ # If set, will not process any files named *.Gradient.ps1
28+ [switch ]
29+ $SkipScriptFile ,
30+
31+ # A list of modules to be installed from the PowerShell gallery before scripts run.
32+ [string []]
33+ $InstallModule ,
34+
35+ # If provided, will commit any remaining changes made to the workspace with this commit message.
36+ [string ]
37+ $CommitMessage ,
38+
39+ # If provided, will checkout a new branch before making the changes.
40+ # If not provided, will use the current branch.
41+ [string ]
42+ $TargetBranch ,
43+
44+ # The name of one or more scripts to run, from this action's path.
45+ [string []]
46+ $ActionScript ,
47+
48+ # The github token to use for requests.
49+ [string ]
50+ $GitHubToken = ' {{ secrets.GITHUB_TOKEN }}' ,
51+
52+ # The user email associated with a git commit. If this is not provided, it will be set to the username@noreply.github.com.
53+ [string ]
54+ $UserEmail ,
55+
56+ # The user name associated with a git commit.
57+ [string ]
58+ $UserName ,
59+
60+ # If set, will not push any changes made to the repository.
61+ # (they will still be committed if `-AutoCommit` is passed)
62+ [switch ]
63+ $NoPush ,
64+
65+ # If set, will commit any changes made to the repository.
66+ # (this also implies `-NoPush`)
67+ [switch ]
68+ $AutoCommit
69+ )
70+
71+ $ErrorActionPreference = ' continue'
72+ " ::group::Parameters" | Out-Host
73+ [PSCustomObject ]$PSBoundParameters | Format-List | Out-Host
74+ " ::endgroup::" | Out-Host
75+
76+ $gitHubEventJson = [IO.File ]::ReadAllText($env: GITHUB_EVENT_PATH )
77+ $gitHubEvent =
78+ if ($env: GITHUB_EVENT_PATH ) {
79+ $gitHubEventJson | ConvertFrom-Json
80+ } else { $null }
81+ " ::group::Parameters" | Out-Host
82+ $gitHubEvent | Format-List | Out-Host
83+ " ::endgroup::" | Out-Host
84+
85+
86+ $anyFilesChanged = $false
87+ $ActionModuleName = ' Gradient'
88+ $actorInfo = $null
89+
90+
91+ $checkDetached = git symbolic- ref - q HEAD
92+ if ($LASTEXITCODE ) {
93+ " ::warning::On detached head, skipping action" | Out-Host
94+ exit 0
95+ }
96+
97+ function InstallActionModule {
98+ param ([string ]$ModuleToInstall )
99+ $moduleInWorkspace = Get-ChildItem - Path $env: GITHUB_WORKSPACE - Recurse - File |
100+ Where-Object Name -eq " $ ( $moduleToInstall ) .psd1" |
101+ Where-Object {
102+ $ (Get-Content $_.FullName - Raw) -match ' ModuleVersion'
103+ }
104+ if (-not $moduleInWorkspace ) {
105+ $availableModules = Get-Module - ListAvailable
106+ if ($availableModules.Name -notcontains $moduleToInstall ) {
107+ Install-Module $moduleToInstall - Scope CurrentUser - Force - AcceptLicense - AllowClobber
108+ }
109+ Import-Module $moduleToInstall - Force - PassThru | Out-Host
110+ } else {
111+ Import-Module $moduleInWorkspace.FullName - Force - PassThru | Out-Host
112+ }
113+ }
114+ function ImportActionModule {
115+ # region -InstallModule
116+ if ($InstallModule ) {
117+ " ::group::Installing Modules" | Out-Host
118+ foreach ($moduleToInstall in $InstallModule ) {
119+ InstallActionModule - ModuleToInstall $moduleToInstall
120+ }
121+ " ::endgroup::" | Out-Host
122+ }
123+ # endregion -InstallModule
124+
125+ if ($env: GITHUB_ACTION_PATH ) {
126+ $LocalModulePath = Join-Path $env: GITHUB_ACTION_PATH " $ActionModuleName .psd1"
127+ if (Test-path $LocalModulePath ) {
128+ Import-Module $LocalModulePath - Force - PassThru | Out-String
129+ } else {
130+ throw " Module '$ActionModuleName ' not found"
131+ }
132+ } elseif (-not (Get-Module $ActionModuleName )) {
133+ throw " Module '$ActionModuleName ' not found"
134+ }
135+
136+ " ::notice title=ModuleLoaded::$ActionModuleName Loaded from Path - $ ( $LocalModulePath ) " | Out-Host
137+ if ($env: GITHUB_STEP_SUMMARY ) {
138+ " # $ ( $ActionModuleName ) " |
139+ Out-File - Append - FilePath $env: GITHUB_STEP_SUMMARY
140+ }
141+ }
142+ function InitializeAction {
143+ # region Custom
144+ # endregion Custom
145+
146+ # Configure git based on the $env:GITHUB_ACTOR
147+ if (-not $UserName ) { $UserName = $env: GITHUB_ACTOR }
148+ if (-not $actorID ) { $actorID = $env: GITHUB_ACTOR_ID }
149+
150+ $actorInfo =
151+ Invoke-RestMethod - Uri " https://api.github.com/user/$actorID "
152+
153+
154+ if (-not $UserEmail ) { $UserEmail = " $UserName @noreply.github.com" }
155+ git config -- global user.email $UserEmail
156+ git config -- global user.name $actorInfo.name
157+
158+ # Pull down any changes
159+ git pull | Out-Host
160+
161+ if ($TargetBranch ) {
162+ " ::notice title=Expanding target branch string $targetBranch " | Out-Host
163+ $TargetBranch = $ExecutionContext.SessionState.InvokeCommand.ExpandString ($TargetBranch )
164+ " ::notice title=Checking out target branch::$targetBranch " | Out-Host
165+ git checkout - b $TargetBranch | Out-Host
166+ git pull | Out-Host
167+ }
168+ }
169+
170+ function InvokeActionModule {
171+ $myScriptStart = [DateTime ]::Now
172+ $myScript = $ExecutionContext.SessionState.PSVariable.Get (" Run" ).Value
173+ if ($myScript ) {
174+ Invoke-Expression - Command $myScript |
175+ . ProcessOutput |
176+ Out-Host
177+ return
178+ }
179+ $myScriptTook = [Datetime ]::Now - $myScriptStart
180+ $MyScriptFilesStart = [DateTime ]::Now
181+
182+ $myScriptList = @ ()
183+ $shouldSkip = $ExecutionContext.SessionState.PSVariable.Get (" SkipScriptFile" ).Value
184+ if ($shouldSkip ) {
185+ return
186+ }
187+ $scriptFiles = @ (
188+ Get-ChildItem - Recurse - Path $env: GITHUB_WORKSPACE |
189+ Where-Object Name -Match " \.$ ( $ActionModuleName ) \.ps1$"
190+ if ($ActionScript ) {
191+ if ($ActionScript -match ' ^\s{0,}/' -and $ActionScript -match ' /\s{0,}$' ) {
192+ $ActionScriptPattern = $ActionScript.Trim (' /' ).Trim() -as [regex ]
193+ if ($ActionScriptPattern ) {
194+ $ActionScriptPattern = [regex ]::new($ActionScript.Trim (' /' ).Trim(), ' IgnoreCase,IgnorePatternWhitespace' , [timespan ]::FromSeconds(0.5 ))
195+ Get-ChildItem - Recurse - Path $env: GITHUB_ACTION_PATH |
196+ Where-Object { $_.Name -Match " \.$ ( $ActionModuleName ) \.ps1$" -and $_.FullName -match $ActionScriptPattern }
197+ }
198+ } else {
199+ Get-ChildItem - Recurse - Path $env: GITHUB_ACTION_PATH |
200+ Where-Object Name -Match " \.$ ( $ActionModuleName ) \.ps1$" |
201+ Where-Object FullName -Like $ActionScript
202+ }
203+ }
204+ ) | Select-Object - Unique
205+ $scriptFiles |
206+ ForEach-Object - Begin {
207+ if ($env: GITHUB_STEP_SUMMARY ) {
208+ " ## $ActionModuleName Scripts" |
209+ Out-File - Append - FilePath $env: GITHUB_STEP_SUMMARY
210+ }
211+ } - Process {
212+ $myScriptList += $_.FullName.Replace ($env: GITHUB_WORKSPACE , ' ' ).TrimStart(' /' )
213+ $myScriptCount ++
214+ $scriptFile = $_
215+ if ($env: GITHUB_STEP_SUMMARY ) {
216+ " ### $ ( $scriptFile.Fullname -replace [Regex ]::Escape($env: GITHUB_WORKSPACE )) " |
217+ Out-File - Append - FilePath $env: GITHUB_STEP_SUMMARY
218+ }
219+ $scriptCmd = $ExecutionContext.SessionState.InvokeCommand.GetCommand ($scriptFile.FullName , ' ExternalScript' )
220+ foreach ($requiredModule in $CommandInfo.ScriptBlock.Ast.ScriptRequirements.RequiredModules ) {
221+ if ($requiredModule.Name -and
222+ (-not $requiredModule.MaximumVersion ) -and
223+ (-not $requiredModule.RequiredVersion )
224+ ) {
225+ InstallActionModule $requiredModule.Name
226+ }
227+ }
228+ Push-Location $scriptFile.Directory.Fullname
229+ $scriptFileOutputs = . $scriptCmd
230+ $scriptFileOutputs |
231+ . ProcessOutput |
232+ Out-Host
233+ Pop-Location
234+ }
235+
236+ $MyScriptFilesTook = [Datetime ]::Now - $MyScriptFilesStart
237+ $SummaryOfMyScripts = " $myScriptCount $ActionModuleName scripts took $ ( $MyScriptFilesTook.TotalSeconds ) seconds"
238+ $SummaryOfMyScripts |
239+ Out-Host
240+ if ($env: GITHUB_STEP_SUMMARY ) {
241+ $SummaryOfMyScripts |
242+ Out-File - Append - FilePath $env: GITHUB_STEP_SUMMARY
243+ }
244+ # region Custom
245+ # endregion Custom
246+ }
247+
248+ function OutError {
249+ $anyRuntimeExceptions = $false
250+ foreach ($err in $error ) {
251+ $errParts = @ (
252+ " ::error "
253+ @ (
254+ if ($err.InvocationInfo.ScriptName ) {
255+ " file=$ ( $err.InvocationInfo.ScriptName ) "
256+ }
257+ if ($err.InvocationInfo.ScriptLineNumber -ge 1 ) {
258+ " line=$ ( $err.InvocationInfo.ScriptLineNumber ) "
259+ if ($err.InvocationInfo.OffsetInLine -ge 1 ) {
260+ " col=$ ( $err.InvocationInfo.OffsetInLine ) "
261+ }
262+ }
263+ if ($err.CategoryInfo.Activity ) {
264+ " title=$ ( $err.CategoryInfo.Activity ) "
265+ }
266+ ) -join ' ,'
267+ " ::"
268+ $err.Exception.Message
269+ if ($err.CategoryInfo.Category -eq ' OperationStopped' -and
270+ $err.CategoryInfo.Reason -eq ' RuntimeException' ) {
271+ $anyRuntimeExceptions = $true
272+ }
273+ ) -join ' '
274+ $errParts | Out-Host
275+ if ($anyRuntimeExceptions ) {
276+ exit 1
277+ }
278+ }
279+ }
280+
281+ function PushActionOutput {
282+ if ($anyFilesChanged ) {
283+ " ::notice::$ ( $anyFilesChanged ) Files Changed" | Out-Host
284+ }
285+ if ($CommitMessage -or $anyFilesChanged ) {
286+ if ($CommitMessage ) {
287+ Get-ChildItem $env: GITHUB_WORKSPACE - Recurse |
288+ ForEach-Object {
289+ $gitStatusOutput = git status $_.Fullname - s
290+ if ($gitStatusOutput ) {
291+ git add $_.Fullname
292+ }
293+ }
294+
295+ git commit - m $ExecutionContext.SessionState.InvokeCommand.ExpandString ($CommitMessage )
296+ }
297+
298+ $checkDetached = git symbolic- ref - q HEAD
299+ if (-not $LASTEXITCODE -and -not $NoPush -and $AutoCommit ) {
300+ if ($TargetBranch -and $anyFilesChanged ) {
301+ " ::notice::Pushing Changes to $targetBranch " | Out-Host
302+ git push -- set-upstream origin $TargetBranch
303+ } elseif ($anyFilesChanged ) {
304+ " ::notice::Pushing Changes" | Out-Host
305+ git push
306+ }
307+ " Git Push Output: $ ( $gitPushed | Out-String ) "
308+ } else {
309+ " ::notice::Not pushing changes (on detached head)" | Out-Host
310+ $LASTEXITCODE = 0
311+ exit 0
312+ }
313+ }
314+ }
315+
316+ filter ProcessOutput {
317+ $out = $_
318+ $outItem = Get-Item - Path $out - ErrorAction Ignore
319+ if (-not $outItem -and $out -is [string ]) {
320+ $out | Out-Host
321+ if ($env: GITHUB_STEP_SUMMARY ) {
322+ " > $out " | Out-File - Append - FilePath $env: GITHUB_STEP_SUMMARY
323+ }
324+ return
325+ }
326+ $fullName , $shouldCommit =
327+ if ($out -is [IO.FileInfo ]) {
328+ $out.FullName , (git status $out.Fullname - s)
329+ } elseif ($outItem ) {
330+ $outItem.FullName , (git status $outItem.Fullname - s)
331+ }
332+ if ($shouldCommit -and $AutoCommit ) {
333+ " $fullName has changed, and should be committed" | Out-Host
334+ git add $fullName
335+ if ($out.Message ) {
336+ git commit - m " $ ( $out.Message ) " | Out-Host
337+ } elseif ($out.CommitMessage ) {
338+ git commit - m " $ ( $out.CommitMessage ) " | Out-Host
339+ } elseif ($gitHubEvent.head_commit.message ) {
340+ git commit - m " $ ( $gitHubEvent.head_commit.message ) " | Out-Host
341+ }
342+ $anyFilesChanged = $true
343+ }
344+ $out
345+ }
346+
347+ . ImportActionModule
348+ . InitializeAction
349+ . InvokeActionModule
350+ . PushActionOutput
351+ . OutError
0 commit comments