Functions/GitHub/New-GitHubAction.ps1

function New-GitHubAction {
    <#
    .Synopsis
        Creates a new GitHub action
    .Example
        
        New-GitHubAction -Job TestPowerShellOnLinux
    .Link
        New-GitHubWorkflow
    .Link
        Import-BuildStep
    .Link
        Convert-BuildStep
    .Link
        Expand-BuildStep
    #>

    [CmdletBinding(PositionalBinding=$false)]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification = "Does not change state")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSPossibleIncorrectComparisonWithNull", "", Justification = "Explicitly checking for null (0 is ok)")]
    [OutputType([string])]
    param(
        # The name of the action.
        [Parameter(Mandatory,Position=0)]
        [string]
        $Name,

        # A description of the action.
        [Parameter(Mandatory,Position=0)]
        [string]
        $Description,

        # The git hub action steps.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Management.Automation.ArgumentCompleter({
            # While we don't want to restrict the steps here, we _do_ want to be able to suggest steps that are built-in.
            $psDevOpsModule = Get-Module PSDevOps
            if ($psDevOpsModule) {
                & $psDevOpsModule {
                    $script:ComponentMetaData.GitHubAction.Values | 
                        Where-Object Type -eq Action |
                        Select-object -ExpandProperty Name 
                }
            }
        })]
        [PSObject[]]$Action,

        # The DockerImage used for a GitHub Action.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$DockerImage,

        # The NodeJS main script used for a GitHub Action.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$NodeJSScript,

        # The git hub action inputs.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Collections.IDictionary]$ActionInput,

        # The git hub action outputs.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Collections.IDictionary]$ActionOutput,

        # Optional changes to a component.
        # A table of additional settings to apply wherever a part is used.
        # For example -Option @{RunPester=@{env=@{"SYSTEM_ACCESSTOKEN"='$(System.AccessToken)'}}
        [Collections.IDictionary]
        $Option,

        # The name of parameters that should be excluded.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('ExcludeParameters')]
        [string[]]
        $ExcludeParameter,

        # The name of parameters that should be referred to uniquely.
        # For instance, if converting function foo($bar) {} and -UniqueParameter is 'bar'
        # The build parameter would be foo_bar.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('UniqueParameters')]
        [string[]]
        $UniqueParameter,

        # A collection of default parameters.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Collections.IDictionary]
        $DefaultParameter = @{},

        # If set, will output the created objects instead of creating YAML.
        [switch]
        $PassThru,

        # A list of build scripts. Each build script will run as a step in the action.
        [string[]]
        $BuildScript,

        # The icon used for branding. By default, a terminal icon.
        [string]
        $Icon = "terminal",

        # The color used for branding. By default, blue.
        [ValidateSet('white', 'yellow', 'blue', 'green', 'orange', 'red', 'purple', 'gray-dark')]
        [string]
        $Color = 'blue',

        # If provided, will output to a given path and return a file.
        [string]
        $OutputPath
    )   

    begin {
        
        $expandBuildStepCmd  = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Expand-BuildStep','Function')        
        $workflowOptions = @{}
        $mynoun = "GitHubAction"
        $expandGitHubBuildStep = @{
            BuildSystem = $mynoun
            SingleItemName = 'On','Name'
            DictionaryItemName = 'Jobs', 'Inputs','Outputs'
            BuildOption = $workflowOptions
        }
        $DoNotExpandParameters = 'InputObject', 'BuildScript', 'DockerImage', 'NodeJSScript', 'Icon', 'Color', 'ActionInput', 'ActionOutput'
    }

    process {

        $myParams = [Ordered]@{ } + $PSBoundParameters

        $stepsByType = [Ordered]@{
            Name = $Name
            Description = $Description
        }
                
        
        $ThingNames = $script:ComponentNames.$mynoun
        foreach ($kv in $myParams.GetEnumerator()) {
            if ($ThingNames[$kv.Key]) {
                $stepsByType[$kv.Key] = $kv.Value
            } elseif ($DoNotExpandParameters -notcontains $kv.Key) {
                $workflowOptions[$kv.Key] = $kv.Value
            }
        }

        
        #region Map InputObject
        if ($InputObject -is [Collections.IDictionary]) {
            foreach ($key in $InputObject.Keys) {
                $stepsByType[$key] = $InputObject.$key
            }
        }

        elseif ($InputObject) {
            foreach ($property in $InputObject.psobject.properties) {
                $stepsByType[$property.name] = $InputObject.$key
            }
        }

        $stepsByType["branding"] = [Ordered]@{
            icon = $Icon
            color = $Color
        }

        #endregion Map InputObject

        #region Expand Input
        $expandSplat = @{} + $PSBoundParameters
        foreach ($k in @($expandSplat.Keys)) {
            if (-not $expandBuildStepCmd.Parameters[$k]) {
                $expandSplat.Remove($k)
            }
        }

        if ($ActionInput) {
            $stepsByType.inputs = $ActionInput
        }
        if ($ActionOutput) {
            $stepsByType.outputs = $ActionOutput
        }


        #endregion Expand Input
        $yamlToBe = Expand-BuildStep -StepMap $stepsByType @expandSplat @expandGitHubBuildStep -InputParameter @{'*'='*'}
        if ($yamlToBe.actions.run) {
            $actionSteps = $yamlToBe.actions
            $yamlToBe.runs = [Ordered]@{
                using = "composite"
                steps = @($actionSteps)
            }
            $yamlToBe.Remove('actions')            
        }
        elseif ($DockerImage) {
            $yamlToBe.runs = [Ordered]@{
                using = "docker"
                image = $DockerImage 
            }
        } 
        elseif ($NodeJSScript) {
            $yamlToBe.runs = [Ordered]@{
                using = "node12"
                main = $NodeJSScript
            }
        }

        $yamlToBe.Remove('On')

        
        
        if ($BuildScript) {
            if (-not $yamlToBe.steps) {
                $yamlToBe.steps = [Ordered]@{}
            }
            $yamlToBe.steps = [Ordered]@{
                steps = @(
                    Get-Item $BuildScript -ErrorAction SilentlyContinue |
                        Convert-BuildStep -BuildSystem GitHubWorkflow
                )
            }
        }

        if ($Environment) {
            if (-not $yamlToBe.env) {
                $yamlToBe.env = [Ordered]@{}
            }
            foreach ($kv in $Environment.GetEnumerator()) {
                $yamlToBe.env[$kv.Key] = $kv.Value
            }
        }

        if ($PassThru) {
            $yamlToBe
        } 
        elseif ($OutputPath) {
            @($yamlToBe | & $toYaml -Indent -2) -join '' -replace "$([Environment]::NewLine * 2)", [Environment]::NewLine |
                Set-Content -Path $OutputPath

            Get-Item -Path $OutputPath
        }
        else {
            @($yamlToBe | & $toYaml -Indent -2) -join '' -replace 
                "$([Environment]::NewLine * 2)", [Environment]::NewLine
        }
    }
}