Build/GitHub/Actions/RoughDraftAction.ps1

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

    * Run all *.RoughDraft.ps1 files beneath the workflow directory
    * Run a .RoughDraftScript parameter.

    Any files changed can be outputted by the script, and those changes can be checked back into the repo.
    Make sure to use the "persistCredentials" option with checkout.
#>


param(
# A PowerShell Script that uses RoughDraft.
# 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]
$RoughDraftScript,

# If set, will not process any files named *.RoughDraft.ps1
[switch]
$SkipRoughDraftPS1,

# A list of modules to be installed from the PowerShell gallery before scripts run.
[string[]]
$InstallModule = @("ugit"),

# A list of installation arguments.
[string[]]
$FFMpegInstallArgument,

# If provided, will commit any remaining changes made to the workspace with this commit message.
[string]
$CommitMessage,

# The user email associated with a git commit.
[string]
$UserEmail,

# The user name associated with a git commit.
[string]
$UserName
)

if (-not $env:GITHUB_WORKSPACE) { throw "No GitHub workspace" }
$anyFilesChanged = $false
$moduleName = 'RoughDraft'
$actorInfo = $null

"::group::Parameters" | Out-Host
[PSCustomObject]$PSBoundParameters | Format-List | Out-Host
"::endgroup::" | Out-Host

function ImportActionModule {
    #region -InstallModule
    if ($InstallModule) {
        "::group::Installing Modules" | Out-Host
        foreach ($moduleToInstall in $InstallModule) {
            $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) {
                Install-Module $moduleToInstall -Scope CurrentUser -Force
                Import-Module $moduleToInstall -Force -PassThru | Out-Host
            }
        }
        "::endgroup::" | Out-Host
    }
    #endregion -InstallModule

    if ($env:GITHUB_ACTION_PATH) {
        $LocalModulePath = Join-Path $env:GITHUB_ACTION_PATH "$moduleName.psd1"
        if (Test-path $LocalModulePath) {
            Import-Module $LocalModulePath -Force -PassThru | Out-String
        } else {
            throw "Module '$moduleName' not found"
        }
    } elseif (-not (Get-Module $moduleName)) {    
        throw "Module '$ModuleName' not found"
    }

    "::notice title=ModuleLoaded::$ModuleName Loaded from Path - $($LocalModulePath)" | Out-Host
    if ($env:GITHUB_STEP_SUMMARY) {
        "# $($moduleName)" |
            Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
    }
}
function InitializeAction {
    #region Custom
    if ($PSVersionTable.Platform -eq 'Unix') {
        $ffMpegInPath =  $ExecutionContext.SessionState.InvokeCommand.GetCommand('ffmpeg', 'Application')
        if (-not $ffMpegInPath -and $env:GITHUB_WORKFLOW) {
            "::group::Installing FFMpeg" | Out-Host
            sudo apt update | Out-Host
            sudo apt install ffmpeg @FFMpegInstallArgument | Out-Host
            "::endgroup::" | Out-Host
        }
    }
    #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
}

function InvokeActionModule {
    $myScriptStart = [DateTime]::Now
    $myScript = $ExecutionContext.SessionState.PSVariable.Get("${ModuleName}Script").Value
    if ($myScript) {
        Invoke-Expression -Command $myScript |
            . ProcessOutput |
            Out-Host
    }
    $myScriptTook = [Datetime]::Now - $myScriptStart
    $MyScriptFilesStart = [DateTime]::Now

    $myScriptList  = @()
    $shouldSkip = $ExecutionContext.SessionState.PSVariable.Get("Skip${ModuleName}PS1").Value
    if (-not $shouldSkip) {
        Get-ChildItem -Recurse -Path $env:GITHUB_WORKSPACE |
            Where-Object Name -Match "\.$($moduleName)\.ps1$" |            
            ForEach-Object -Begin {
                if ($env:GITHUB_STEP_SUMMARY) {
                    "## $ModuleName 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)" |
                        Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
                }            
                $scriptFileOutputs = . $scriptFile.FullName
                if ($env:GITHUB_STEP_SUMMARY) {
                    "$(@($scriptFileOutputs).Length) Outputs" |                    
                        Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
                    "$(@($scriptFileOutputs).Length) Outputs" | Out-Host
                }
                $scriptFileOutputs |
                    . ProcessOutput  | 
                    Out-Host
            }
    }
    
    $MyScriptFilesTook = [Datetime]::Now - $MyScriptFilesStart
    $SummaryOfMyScripts = "$myScriptCount $moduleName scripts took $($MyScriptFilesTook.TotalSeconds) seconds" 
    $SummaryOfMyScripts | 
        Out-Host
    if ($env:GITHUB_STEP_SUMMARY) {
        $SummaryOfMyScripts | 
            Out-File -Append -FilePath $env:GITHUB_STEP_SUMMARY
    }
}

function PushActionOutput {
    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) {
            "::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) {
        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