1
+ <#
2
+ . Synopsis
3
+ GitHub Action for Turtle
4
+ . Description
5
+ GitHub Action for Turtle. This will:
6
+
7
+ * Import Turtle
8
+ * If `-Run` is provided, run that script
9
+ * Otherwise, unless `-SkipScriptFile` is passed, run all *.Turtle.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
+ If none is provided, and ENV:GITHUB_TOKEN is set, this will be used instead.
14
+ Any files changed can be outputted by the script, and those changes can be checked back into the repo.
15
+ Make sure to use the "persistCredentials" option with checkout.
16
+ #>
17
+
18
+ param (
19
+ # A PowerShell Script that uses Turtle.
20
+ # Any files outputted from the script will be added to the repository.
21
+ # If those files have a .Message attached to them, they will be committed with that message.
22
+ [string ]
23
+ $Run ,
24
+
25
+ # If set, will not process any files named *.Turtle.ps1
26
+ [switch ]
27
+ $SkipScriptFile ,
28
+
29
+ # A list of modules to be installed from the PowerShell gallery before scripts run.
30
+ [string []]
31
+ $InstallModule ,
32
+
33
+ # If provided, will commit any remaining changes made to the workspace with this commit message.
34
+ [string ]
35
+ $CommitMessage ,
36
+
37
+ # If provided, will checkout a new branch before making the changes.
38
+ # If not provided, will use the current branch.
39
+ [string ]
40
+ $TargetBranch ,
41
+
42
+ # The name of one or more scripts to run, from this action's path.
43
+ [string []]
44
+ $ActionScript ,
45
+
46
+ # The github token to use for requests.
47
+ [string ]
48
+ $GitHubToken = ' {{ secrets.GITHUB_TOKEN }}' ,
49
+
50
+ # The user email associated with a git commit. If this is not provided, it will be set to the [email protected] .
51
+ [string ]
52
+ $UserEmail ,
53
+
54
+ # The user name associated with a git commit.
55
+ [string ]
56
+ $UserName ,
57
+
58
+ # If set, will not push any changes made to the repository.
59
+ # (they will still be committed unless `-NoCommit` is passed)
60
+ [switch ]
61
+ $NoPush ,
62
+
63
+ # If set, will not commit any changes made to the repository.
64
+ # (this also implies `-NoPush`)
65
+ [switch ]
66
+ $NoCommit
67
+ )
68
+
69
+ $ErrorActionPreference = ' continue'
70
+ " ::group::Parameters" | Out-Host
71
+ [PSCustomObject ]$PSBoundParameters | Format-List | Out-Host
72
+ " ::endgroup::" | Out-Host
73
+
74
+ $gitHubEventJson = [IO.File ]::ReadAllText($env: GITHUB_EVENT_PATH )
75
+ $gitHubEvent =
76
+ if ($env: GITHUB_EVENT_PATH ) {
77
+ $gitHubEventJson | ConvertFrom-Json
78
+ } else { $null }
79
+ " ::group::Parameters" | Out-Host
80
+ $gitHubEvent | Format-List | Out-Host
81
+ " ::endgroup::" | Out-Host
82
+
83
+
84
+ $anyFilesChanged = $false
85
+ $ActionModuleName = ' Turtle'
86
+ $actorInfo = $null
87
+
88
+
89
+ $checkDetached = git symbolic- ref - q HEAD
90
+ if ($LASTEXITCODE ) {
91
+ " ::warning::On detached head, skipping action" | Out-Host
92
+ exit 0
93
+ }
94
+
95
+ function InstallActionModule {
96
+ param ([string ]$ModuleToInstall )
97
+ $moduleInWorkspace = Get-ChildItem - Path $env: GITHUB_WORKSPACE - Recurse - File |
98
+ Where-Object Name -eq " $ ( $moduleToInstall ) .psd1" |
99
+ Where-Object {
100
+ $ (Get-Content $_.FullName - Raw) -match ' ModuleVersion'
101
+ }
102
+ if (-not $moduleInWorkspace ) {
103
+ $availableModules = Get-Module - ListAvailable
104
+ if ($availableModules.Name -notcontains $moduleToInstall ) {
105
+ Install-Module $moduleToInstall - Scope CurrentUser - Force - AcceptLicense - AllowClobber
106
+ }
107
+ Import-Module $moduleToInstall - Force - PassThru | Out-Host
108
+ } else {
109
+ Import-Module $moduleInWorkspace.FullName - Force - PassThru | Out-Host
110
+ }
111
+ }
112
+ function ImportActionModule {
113
+ # region -InstallModule
114
+ if ($InstallModule ) {
115
+ " ::group::Installing Modules" | Out-Host
116
+ foreach ($moduleToInstall in $InstallModule ) {
117
+ InstallActionModule - ModuleToInstall $moduleToInstall
118
+ }
119
+ " ::endgroup::" | Out-Host
120
+ }
121
+ # endregion -InstallModule
122
+
123
+ if ($env: GITHUB_ACTION_PATH ) {
124
+ $LocalModulePath = Join-Path $env: GITHUB_ACTION_PATH " $ActionModuleName .psd1"
125
+ if (Test-path $LocalModulePath ) {
126
+ Import-Module $LocalModulePath - Force - PassThru | Out-String
127
+ } else {
128
+ throw " Module '$ActionModuleName ' not found"
129
+ }
130
+ } elseif (-not (Get-Module $ActionModuleName )) {
131
+ throw " Module '$ActionModuleName ' not found"
132
+ }
133
+
134
+ " ::notice title=ModuleLoaded::$ActionModuleName Loaded from Path - $ ( $LocalModulePath ) " | Out-Host
135
+ if ($env: GITHUB_STEP_SUMMARY ) {
136
+ " # $ ( $ActionModuleName ) " |
137
+ Out-File - Append - FilePath $env: GITHUB_STEP_SUMMARY
138
+ }
139
+ }
140
+ function InitializeAction {
141
+ # region Custom
142
+ # endregion Custom
143
+
144
+ # Configure git based on the $env:GITHUB_ACTOR
145
+ if (-not $UserName ) { $UserName = $env: GITHUB_ACTOR }
146
+ if (-not $actorID ) { $actorID = $env: GITHUB_ACTOR_ID }
147
+ $actorInfo =
148
+ if ($GitHubToken -notmatch ' ^\{{2}' -and $GitHubToken -notmatch ' \}{2}$' ) {
149
+ Invoke-RestMethod - Uri " https://api.github.com/user/$actorID " - Headers @ { Authorization = " token $GitHubToken " }
150
+ } else {
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 -not $noCommit ) {
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 -not $NoCommit ) {
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