Build/GitHub/Action/WaveAction.ps1

<#
.Synopsis
    GitHub Action for Wave
.Description
    GitHub Action for Wave. This will:

    * Import Wave
    * If `-Run` is provided, run that script
    * Otherwise, unless `-SkipScriptFile` is passed, run all *.wave.ps1 files beneath the workflow directory
      * If any `-ActionScript` was provided, run scripts from the action path that match a wildcard pattern.
#>


param(
# A PowerShell Script that uses Wave.
# Any files outputted from the script will be added to the repository.
[string]
$Run,

# The Script File Pattern.
# Scripts in the repository that match this pattern will be run.
[string]
$ScriptFilePattern = '\.wave?\.ps1$',

# If set, will not process any files that match the script file pattern.
[switch]
$SkipScriptFile,

# A list of modules to be installed from the PowerShell gallery before scripts run.
[string[]]
$InstallModule,

# 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 }}'
)

$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 = 'Wave'
$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 InvokeActionModule {
    $myScriptStart = [DateTime]::Now
    $myScript = $ExecutionContext.SessionState.PSVariable.Get("Run").Value
    if ($myScript) {
        Invoke-Expression -Command $myScript |            
            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 Extension -eq .ps1 |
            Where-Object Name -Match $ScriptFilePattern
        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)s?\.ps1$" |
                    Where-Object FullName -Like $ActionScript
            }
        }
    ) | Select-Object -Unique)

    "Found $($scriptFiles.Length) script files matching '$($ScriptFilePattern)'" | Out-Host
    $scriptFiles | Out-Host

    $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 |                
                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
        }
    }
}

. ImportActionModule
. InvokeActionModule
. OutError