module/nodejs-wrapper.psm1

#Requires -PSEdition Core
#Requires -Version 7.2
Import-Module -Name (
    @(
        'internal\new-random-token'
    ) |
        ForEach-Object -Process { Join-Path -Path $PSScriptRoot -ChildPath "$_.psm1" }
) -Prefix 'GitHubActions' -Scope 'Local'
[Boolean]$IsTested = $False
[Boolean]$ResultDependencies = $False
[Boolean]$ResultEnvironment = $False
[SemVer]$NodeJsMinimumVersion = [SemVer]::Parse('14.15.0')
[SemVer]$NpmMinimumVersion = [SemVer]::Parse('6.14.8')
[SemVer]$PnpmMinimumVersion = [SemVer]::Parse('7.28.0')
[RegEx]$SemVerRegEx = 'v?\d+\.\d+\.\d+'
[String]$WrapperRoot = Join-Path -Path $PSScriptRoot -ChildPath 'nodejs-wrapper'
[String]$WrapperMetaPath = Join-Path -Path $WrapperRoot -ChildPath 'package.json'
[String]$WrapperScriptPath = Join-Path -Path $WrapperRoot -ChildPath 'lib' -AdditionalChildPath @('main.js')
<#
.SYNOPSIS
GitHub Actions - Internal - Convert From Base64 String To Utf8 String
.PARAMETER InputObject
String that need decode from base64.
.OUTPUTS
[String] An decoded string.
#>

Function Convert-FromBase64StringToUtf8String {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)][Alias('Input', 'Object')][String]$InputObject
    )
    Process {
        [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($InputObject)) |
            Write-Output
    }
}
<#
.SYNOPSIS
GitHub Actions - Internal - Convert From Utf8 String To Base64 String
.PARAMETER InputObject
String that need encode to base64.
.OUTPUTS
[String] An encoded string.
#>

Function Convert-FromUtf8StringToBase64String {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)][Alias('Input', 'Object')][String]$InputObject
    )
    Process {
        [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($InputObject)) |
            Write-Output
    }
}
<#
.SYNOPSIS
GitHub Actions - Invoke NodeJS Wrapper
.DESCRIPTION
Invoke NodeJS wrapper.
.PARAMETER Name
Name of the NodeJS wrapper.
.PARAMETER Argument
Arguments of the NodeJS wrapper.
.OUTPUTS
[PSCustomObject] Result of the NodeJS wrapper.
[PSCustomObject[]] Result of the NodeJS wrapper.
#>

Function Invoke-NodeJsWrapper {
    [CmdletBinding()]
    [OutputType(([PSCustomObject], [PSCustomObject[]]))]
    Param (
        [Parameter(Mandatory = $True, Position = 0)][String]$Name,
        [Parameter(Mandatory = $True, Position = 1)][Alias('Arguments')][Hashtable]$Argument,
        [Alias('Debug')][Switch]$LocalDebug
    )
    If (!$LocalDebug.IsPresent) {
        If (!(Test-NodeJsEnvironment)) {
            Write-Error -Message 'This function depends and requires to invoke with the compatible NodeJS environment!' -Category 'ResourceUnavailable'
            Return
        }
        If (!(Test-Path -LiteralPath $WrapperMetaPath -PathType 'Leaf')) {
            Write-Error -Message 'Wrapper meta is missing!' -Category 'ResourceUnavailable'
            Return
        }
        If (!(Test-Path -LiteralPath $WrapperScriptPath -PathType 'Leaf')) {
            Write-Error -Message 'Wrapper script is missing!' -Category 'ResourceUnavailable'
            Return
        }
    }
    [String]$ResultSeparator = "=====$(New-GitHubActionsRandomToken -Length 32)====="
    Try {
        [String[]]$Result = Invoke-Expression -Command "node --no-deprecation --no-warnings `"$WrapperScriptPath`" $(Convert-FromUtf8StringToBase64String -InputObject $Name) $(
            $Argument |
                ConvertTo-Json -Depth 100 -Compress |
                Convert-FromUtf8StringToBase64String
        ) $(Convert-FromUtf8StringToBase64String -InputObject $ResultSeparator)"

        [UInt32[]]$ResultSkipIndexes = @()
        For ([UInt32]$ResultIndex = 0; $ResultIndex -ilt $Result.Count; $ResultIndex++) {
            [String]$ResultLine = $Result[$ResultIndex]
            If ($ResultLine -imatch '^::.+?::.*$') {
                Write-Host -Object $ResultLine
                $ResultSkipIndexes += $ResultIndex
            }
            If ($ResultLine -ieq $ResultSeparator) {
                $ResultSkipIndexes += @($ResultIndex..($Result.Count - 1))
                Break
            }
        }
        If ($LASTEXITCODE -ine 0) {
            Throw "Unexpected exit code ``$LASTEXITCODE``! $(
                $Result |
                    Select-Object -SkipIndex $ResultSkipIndexes |
                    Join-String -Separator "`n"
            )"

        }
        $Result[$Result.Count - 1] |
            Convert-FromBase64StringToUtf8String |
            ConvertFrom-Json -Depth 100 |
            Write-Output
    }
    Catch {
        Write-Error -Message "Unable to successfully invoke NodeJS wrapper (``$Name``): $_" -Category 'InvalidData'
    }
}
<#
.SYNOPSIS
GitHub Actions - Test NodeJS Environment
.DESCRIPTION
Test the current machine whether has compatible NodeJS environment; Test result always cache for reuse.
.PARAMETER Retest
Whether to redo this test by ignore the cached test result.
.PARAMETER ReinstallDependencies
Whether to force reinstall dependencies even though available.
.OUTPUTS
[Boolean] Test result.
#>

Function Test-NodeJsEnvironment {
    [CmdletBinding(HelpUri = 'https://github.com/hugoalh-studio/ghactions-toolkit-powershell/wiki/api_function_testgithubactionsnodejsenvironment')]
    [OutputType([Boolean])]
    Param (
        [Alias('Redo')][Switch]$Retest,
        [Alias('Reinstall', 'ReinstallDependency', 'ReinstallPackage', 'ReinstallPackages')][Switch]$ReinstallDependencies
    )
    If ($IsTested -and !$Retest.IsPresent -and !$ReinstallDependencies.IsPresent) {
        Write-Verbose -Message 'Previously tested NodeJS environment; Return previous result.'
        Write-Output -InputObject ($ResultDependencies -and $ResultEnvironment)
        Return
    }
    $Script:IsTested = $False
    If ($ReinstallDependencies.IsPresent) {
        $Script:ResultDependencies = $False
    }
    If ($Retest.IsPresent) {
        $Script:ResultEnvironment = $False
    }
    If (!$ResultEnvironment) {
        Try {
            Try {
                $Null = Get-Command -Name 'node' -CommandType 'Application' -ErrorAction 'Stop'# `Get-Command` will throw error when nothing is found.
            }
            Catch {
                Throw 'Unable to find NodeJS!'
            }
            Try {
                [String]$NodeJsVersionStdOut = node --no-deprecation --no-warnings --version |
                    Join-String -Separator "`n"
                If (
                    $NodeJsVersionStdOut -inotmatch $SemVerRegEx -or
                    $NodeJsMinimumVersion -igt [SemVer]::Parse(($Matches[0] -ireplace '^v', ''))
                ) {
                    Throw
                }
            }
            Catch {
                Throw 'NodeJS is not match the requirement!'
            }
            Try {
                $Null = Get-Command -Name 'npm' -CommandType 'Application' -ErrorAction 'Stop'# `Get-Command` will throw error when nothing is found.
            }
            Catch {
                Throw 'Unable to find NPM!'
            }
            Try {
                [String]$NpmVersionStdOut = npm --version |
                    Join-String -Separator "`n"
                If (
                    $NpmVersionStdOut -inotmatch $SemVerRegEx -or
                    $NpmMinimumVersion -igt [SemVer]::Parse(($Matches[0] -ireplace '^v', ''))
                ) {
                    Throw
                }
            }
            Catch {
                Throw 'NPM is not match the requirement!'
            }
        }
        Catch {
            Write-Verbose -Message $_
            $Script:IsTested = $True
            $Script:ResultEnvironment = $False
            Write-Output -InputObject ($ResultDependencies -and $ResultEnvironment)
            Return
        }
    }
    $Script:ResultEnvironment = $True
    If (!$ResultDependencies) {
        Try {
            Try {
                $Null = Get-Command -Name 'pnpm' -CommandType 'Application' -ErrorAction 'Stop'# `Get-Command` will throw error when nothing is found.
                [String]$PnpmVersionStdOut = pnpm --version |
                    Join-String -Separator "`n"
                If (
                    $PnpmVersionStdOut -inotmatch $SemVerRegEx -or
                    $PnpmMinimumVersion -igt [SemVer]::Parse(($Matches[0] -ireplace '^v', ''))
                ) {
                    Throw
                }
            }
            Catch {
                Try {
                    $Null = npm install --global pnpm@latest
                }
                Catch {
                    Throw 'Unable to install PNPM!'
                }
            }
            Try {
                $CurrentWorkingDirectory = Get-Location
                $Null = Set-Location -LiteralPath $WrapperRoot
                Try {
                    $Null = pnpm install
                }
                Catch {
                    Throw 'Unable to install NodeJS wrapper API dependencies!'
                }
                Finally {
                    Set-Location -LiteralPath $CurrentWorkingDirectory.Path
                }
            }
            Catch {
                Throw $_
            }
        }
        Catch {
            Write-Verbose -Message $_
            $Script:IsTested = $True
            $Script:ResultDependencies = $False
            Write-Output -InputObject ($ResultDependencies -and $ResultEnvironment)
            Return
        }
    }
    $Script:IsTested = $True
    $Script:ResultDependencies = $True
    Write-Output -InputObject ($ResultDependencies -and $ResultEnvironment)
}
Export-ModuleMember -Function @(
    'Invoke-NodeJsWrapper',
    'Test-NodeJsEnvironment'
)