Build/GitHub/Actions/GradientAction.ps1
|
<# .Synopsis GitHub Action for Gradient .Description GitHub Action for Gradient. This will: * Import Gradient * If `-Run` is provided, run that script * Otherwise, unless `-SkipScriptFile` is passed, run all *.Gradient.ps1 files beneath the workflow directory * If any `-ActionScript` was provided, run scripts from the action path that match a wildcard pattern. If you will be making changes using the GitHubAPI, you should provide a -GitHubToken If none is provided, and ENV:GITHUB_TOKEN is set, this will be used instead. Any files changed can be outputted by the script. Those changes can be checked back into the repo if `-AutoCommit` is set. #> param( # A PowerShell Script that uses Gradient. # Any files outputted from the script will be added to the repository. # If those files have a .Message attached to them, they will be committed with that message. [string] $Run, # If set, will not process any files named *.Gradient.ps1 [switch] $SkipScriptFile, # A list of modules to be installed from the PowerShell gallery before scripts run. [string[]] $InstallModule, # If provided, will commit any remaining changes made to the workspace with this commit message. [string] $CommitMessage, # If provided, will checkout a new branch before making the changes. # If not provided, will use the current branch. [string] $TargetBranch, # The name of one or more scripts to run, from this action's path. [string[]] $ActionScript, # The github token to use for requests. [string] $GitHubToken = '{{ secrets.GITHUB_TOKEN }}', # The user email associated with a git commit. If this is not provided, it will be set to the username@noreply.github.com. [string] $UserEmail, # The user name associated with a git commit. [string] $UserName, # If set, will not push any changes made to the repository. # (they will still be committed if `-AutoCommit` is passed) [switch] $NoPush, # If set, will commit any changes made to the repository. # (this also implies `-NoPush`) [switch] $AutoCommit ) $ErrorActionPreference = 'continue' "::group::Parameters" | Out-Host [PSCustomObject]$PSBoundParameters | Format-List | Out-Host "::endgroup::" | Out-Host $gitHubEventJson = [IO.File]::ReadAllText($env:GITHUB_EVENT_PATH) $gitHubEvent = if ($env:GITHUB_EVENT_PATH) { $gitHubEventJson | ConvertFrom-Json } else { $null } "::group::Parameters" | Out-Host $gitHubEvent | Format-List | Out-Host "::endgroup::" | Out-Host $anyFilesChanged = $false $ActionModuleName = 'Gradient' $actorInfo = $null $checkDetached = git symbolic-ref -q HEAD if ($LASTEXITCODE) { "::warning::On detached head, skipping action" | Out-Host exit 0 } function InstallActionModule { param([string]$ModuleToInstall) $moduleInWorkspace = Get-ChildItem -Path $env:GITHUB_WORKSPACE -Recurse -File | Where-Object Name -eq "$($moduleToInstall).psd1" | Where-Object { $(Get-Content $_.FullName -Raw) -match 'ModuleVersion' } if (-not $moduleInWorkspace) { $availableModules = Get-Module -ListAvailable if ($availableModules.Name -notcontains $moduleToInstall) { Install-Module $moduleToInstall -Scope CurrentUser -Force -AcceptLicense -AllowClobber } Import-Module $moduleToInstall -Force -PassThru | Out-Host } else { Import-Module $moduleInWorkspace.FullName -Force -PassThru | Out-Host } } function ImportActionModule { #region -InstallModule if ($InstallModule) { "::group::Installing Modules" | Out-Host foreach ($moduleToInstall in $InstallModule) { InstallActionModule -ModuleToInstall $moduleToInstall } "::endgroup::" | Out-Host } #endregion -InstallModule if ($env:GITHUB_ACTION_PATH) { $LocalModulePath = Join-Path $env:GITHUB_ACTION_PATH "$ActionModuleName.psd1" if (Test-path $LocalModulePath) { Import-Module $LocalModulePath -Force -PassThru | Out-String } else { throw "Module '$ActionModuleName' not found" } } elseif (-not (Get-Module $ActionModuleName)) { throw "Module '$ActionModuleName' not found" } "::notice title=ModuleLoaded::$ActionModuleName Loaded from Path - $($LocalModulePath)" | Out-Host if ($env:GITHUB_STEP_SUMMARY) { "# $($ActionModuleName)" | Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY } } function InitializeAction { #region Custom #endregion Custom # Configure git based on the $env:GITHUB_ACTOR if (-not $UserName) { $UserName = $env:GITHUB_ACTOR } if (-not $actorID) { $actorID = $env:GITHUB_ACTOR_ID } $actorInfo = Invoke-RestMethod -Uri "https://api.github.com/user/$actorID" if (-not $UserEmail) { $UserEmail = "$UserName@noreply.github.com" } git config --global user.email $UserEmail git config --global user.name $actorInfo.name # Pull down any changes git pull | Out-Host if ($TargetBranch) { "::notice title=Expanding target branch string $targetBranch" | Out-Host $TargetBranch = $ExecutionContext.SessionState.InvokeCommand.ExpandString($TargetBranch) "::notice title=Checking out target branch::$targetBranch" | Out-Host git checkout -b $TargetBranch | Out-Host git pull | Out-Host } } function InvokeActionModule { $myScriptStart = [DateTime]::Now $myScript = $ExecutionContext.SessionState.PSVariable.Get("Run").Value if ($myScript) { Invoke-Expression -Command $myScript | . ProcessOutput | Out-Host return } $myScriptTook = [Datetime]::Now - $myScriptStart $MyScriptFilesStart = [DateTime]::Now $myScriptList = @() $shouldSkip = $ExecutionContext.SessionState.PSVariable.Get("SkipScriptFile").Value if ($shouldSkip) { return } $scriptFiles = @( Get-ChildItem -Recurse -Path $env:GITHUB_WORKSPACE | Where-Object Name -Match "\.$($ActionModuleName)\.ps1$" if ($ActionScript) { if ($ActionScript -match '^\s{0,}/' -and $ActionScript -match '/\s{0,}$') { $ActionScriptPattern = $ActionScript.Trim('/').Trim() -as [regex] if ($ActionScriptPattern) { $ActionScriptPattern = [regex]::new($ActionScript.Trim('/').Trim(), 'IgnoreCase,IgnorePatternWhitespace', [timespan]::FromSeconds(0.5)) Get-ChildItem -Recurse -Path $env:GITHUB_ACTION_PATH | Where-Object { $_.Name -Match "\.$($ActionModuleName)\.ps1$" -and $_.FullName -match $ActionScriptPattern } } } else { Get-ChildItem -Recurse -Path $env:GITHUB_ACTION_PATH | Where-Object Name -Match "\.$($ActionModuleName)\.ps1$" | Where-Object FullName -Like $ActionScript } } ) | Select-Object -Unique $scriptFiles | ForEach-Object -Begin { if ($env:GITHUB_STEP_SUMMARY) { "## $ActionModuleName Scripts" | Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY } } -Process { $myScriptList += $_.FullName.Replace($env:GITHUB_WORKSPACE, '').TrimStart('/') $myScriptCount++ $scriptFile = $_ if ($env:GITHUB_STEP_SUMMARY) { "### $($scriptFile.Fullname -replace [Regex]::Escape($env:GITHUB_WORKSPACE))" | Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY } $scriptCmd = $ExecutionContext.SessionState.InvokeCommand.GetCommand($scriptFile.FullName, 'ExternalScript') foreach ($requiredModule in $CommandInfo.ScriptBlock.Ast.ScriptRequirements.RequiredModules) { if ($requiredModule.Name -and (-not $requiredModule.MaximumVersion) -and (-not $requiredModule.RequiredVersion) ) { InstallActionModule $requiredModule.Name } } Push-Location $scriptFile.Directory.Fullname $scriptFileOutputs = . $scriptCmd $scriptFileOutputs | . ProcessOutput | Out-Host Pop-Location } $MyScriptFilesTook = [Datetime]::Now - $MyScriptFilesStart $SummaryOfMyScripts = "$myScriptCount $ActionModuleName scripts took $($MyScriptFilesTook.TotalSeconds) seconds" $SummaryOfMyScripts | Out-Host if ($env:GITHUB_STEP_SUMMARY) { $SummaryOfMyScripts | Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY } #region Custom #endregion Custom } function OutError { $anyRuntimeExceptions = $false foreach ($err in $error) { $errParts = @( "::error " @( if ($err.InvocationInfo.ScriptName) { "file=$($err.InvocationInfo.ScriptName)" } if ($err.InvocationInfo.ScriptLineNumber -ge 1) { "line=$($err.InvocationInfo.ScriptLineNumber)" if ($err.InvocationInfo.OffsetInLine -ge 1) { "col=$($err.InvocationInfo.OffsetInLine)" } } if ($err.CategoryInfo.Activity) { "title=$($err.CategoryInfo.Activity)" } ) -join ',' "::" $err.Exception.Message if ($err.CategoryInfo.Category -eq 'OperationStopped' -and $err.CategoryInfo.Reason -eq 'RuntimeException') { $anyRuntimeExceptions = $true } ) -join '' $errParts | Out-Host if ($anyRuntimeExceptions) { exit 1 } } } function PushActionOutput { if ($anyFilesChanged) { "::notice::$($anyFilesChanged) Files Changed" | Out-Host } if ($CommitMessage -or $anyFilesChanged) { if ($CommitMessage) { Get-ChildItem $env:GITHUB_WORKSPACE -Recurse | ForEach-Object { $gitStatusOutput = git status $_.Fullname -s if ($gitStatusOutput) { git add $_.Fullname } } git commit -m $ExecutionContext.SessionState.InvokeCommand.ExpandString($CommitMessage) } $checkDetached = git symbolic-ref -q HEAD if (-not $LASTEXITCODE -and -not $NoPush -and $AutoCommit) { if ($TargetBranch -and $anyFilesChanged) { "::notice::Pushing Changes to $targetBranch" | Out-Host git push --set-upstream origin $TargetBranch } elseif ($anyFilesChanged) { "::notice::Pushing Changes" | Out-Host git push } "Git Push Output: $($gitPushed | Out-String)" } else { "::notice::Not pushing changes (on detached head)" | Out-Host $LASTEXITCODE = 0 exit 0 } } } filter ProcessOutput { $out = $_ $outItem = Get-Item -Path $out -ErrorAction Ignore if (-not $outItem -and $out -is [string]) { $out | Out-Host if ($env:GITHUB_STEP_SUMMARY) { "> $out" | Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY } return } $fullName, $shouldCommit = if ($out -is [IO.FileInfo]) { $out.FullName, (git status $out.Fullname -s) } elseif ($outItem) { $outItem.FullName, (git status $outItem.Fullname -s) } if ($shouldCommit -and $AutoCommit) { "$fullName has changed, and should be committed" | Out-Host git add $fullName if ($out.Message) { git commit -m "$($out.Message)" | Out-Host } elseif ($out.CommitMessage) { git commit -m "$($out.CommitMessage)" | Out-Host } elseif ($gitHubEvent.head_commit.message) { git commit -m "$($gitHubEvent.head_commit.message)" | Out-Host } $anyFilesChanged = $true } $out } . ImportActionModule . InitializeAction . InvokeActionModule . PushActionOutput . OutError |