diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df9fd3a..9da756c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Test with Pester - run: | - Install-Module -Name Pester -Force -SkipPublisherCheck -MinimumVersion '5.0' - Import-Module Pester -MinimumVersion '5.0' - Invoke-Pester -CI + run: ./test.ps1 - name: Invoke action env: TEMP: ${{ runner.temp }} @@ -234,18 +231,20 @@ jobs: # Invoke-ActionNoCommandsBlock - name: Invoke-ActionNoCommandsBlock test (act) - uses: ./ - with: - script: | + id: Invoke-ActionNoCommandsBlock + shell: pwsh + run: | + Import-Module ./lib/GitHubActionsCore -Force Invoke-ActionNoCommandsBlock -GenerateToken { - Set-ActionVariable nocmdvar nocmd + Set-ActionOutput testout testval } - name: Invoke-ActionNoCommandsBlock test (assert) uses: ./ with: script: | - if ($env:nocmdvar -ne $null) { - throw "Invoke-ActionNoCommandsBlock failed.`n$env:nocmdvar" + $result = '${{ steps.Invoke-ActionNoCommandsBlock.outputs.testout }}' + if ($result) { + throw "Invoke-ActionNoCommandsBlock failed.`n$result" } # Write-Action -Debug, -Info, -Warning, -Error and Grouping are not testable in any sensible manner diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ad70c3..653565d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- `Send-ActionFileCommand` cmdlet that handles sending commands to [Environment Files] instead of console output ([#8]). + +### Changed +- `Add-ActionPath` and `Set-ActionVariable` are updated for [Environment Files] Actions Runner change ([#8]). + +[Environment Files]: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#environment-files +[#8]: https://github.com/Amadevus/pwsh-script/pull/8 + ## [2.0.0] - 2020-09-10 ### Changed diff --git a/docs/GitHubActionsCore/README.md b/docs/GitHubActionsCore/README.md index 1b6e914..20147f3 100644 --- a/docs/GitHubActionsCore/README.md +++ b/docs/GitHubActionsCore/README.md @@ -9,6 +9,7 @@ | [Invoke-ActionGroup](Invoke-ActionGroup.md) | Executes the argument script block within an output group. Equivalent of `core.group(name, func)`. | | [Invoke-ActionNoCommandsBlock](Invoke-ActionNoCommandsBlock.md) | Invokes a scriptblock that won't result in any output interpreted as a workflow command. Useful for printing arbitrary text that may contain command-like text. No quivalent in `@actions/core` package. | | [Send-ActionCommand](Send-ActionCommand.md) | Sends a command to the hosting Workflow/Action context. Equivalent to `core.issue(cmd, msg)`/`core.issueCommand(cmd, props, msg)`. | +| [Send-ActionFileCommand](Send-ActionFileCommand.md) | Sends a command to an Action Environment File. Equivalent to `core.issueFileCommand(cmd, msg)`. | | [Set-ActionCommandEcho](Set-ActionCommandEcho.md) | Enables or disables the echoing of commands into stdout for the rest of the step. Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. Equivalent of `core.setCommandEcho(enabled)`. | | [Set-ActionFailed](Set-ActionFailed.md) | Sets an action status to failed. When the action exits it will be with an exit code of 1. Equivalent of `core.setFailed(message)`. | | [Set-ActionOutput](Set-ActionOutput.md) | Sets the value of an output. Equivalent of `core.setOutput(name, value)`. | diff --git a/docs/GitHubActionsCore/Send-ActionFileCommand.md b/docs/GitHubActionsCore/Send-ActionFileCommand.md new file mode 100644 index 0000000..7c04e79 --- /dev/null +++ b/docs/GitHubActionsCore/Send-ActionFileCommand.md @@ -0,0 +1,72 @@ +# Send-ActionFileCommand +``` + +NAME + Send-ActionFileCommand + +SYNOPSIS + Sends a command to an Action Environment File. + Equivalent to `core.issueFileCommand(cmd, msg)`. + + +SYNTAX + Send-ActionFileCommand [-Command] [-Message] [] + + +DESCRIPTION + Appends given message to an Action Environment File. + + +PARAMETERS + -Command + Command (environment file variable suffix) to send message for. + + Required? true + Position? 1 + Default value + Accept pipeline input? false + Accept wildcard characters? false + + -Message + Message to append. + + Required? true + Position? 2 + Default value + Accept pipeline input? true (ByValue) + Accept wildcard characters? false + + + This cmdlet supports the common parameters: Verbose, Debug, + ErrorAction, ErrorVariable, WarningAction, WarningVariable, + OutBuffer, PipelineVariable, and OutVariable. For more information, see + about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). + +INPUTS + +OUTPUTS + + -------------------------- EXAMPLE 1 -------------------------- + + PS>Send-ActionFileCommand ENV 'myvar=value' + + + + + + + -------------------------- EXAMPLE 2 -------------------------- + + PS>'myvar=value', 'myvar2=novalue' | Send-ActionFileCommand ENV + + + + + + + +RELATED LINKS + https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#environment-files + +``` + diff --git a/lib/GitHubActionsCore/GitHubActionsCore.Tests.ps1 b/lib/GitHubActionsCore/GitHubActionsCore.Tests.ps1 index 35380c6..30541d6 100644 --- a/lib/GitHubActionsCore/GitHubActionsCore.Tests.ps1 +++ b/lib/GitHubActionsCore/GitHubActionsCore.Tests.ps1 @@ -1,36 +1,34 @@ -Set-StrictMode -Version Latest +#Requires -Module @{ ModuleName = 'Pester'; ModuleVersion = '5.1' } BeforeAll { - Get-Module GitHubActionsCore | Remove-Module - Import-Module $PSScriptRoot + Import-Module $PSScriptRoot -Force } Describe 'Get-ActionInput' { + BeforeEach { + $env:INPUT_TEST_INPUT = $null + } Context "When requested input doesn't exist" { It "Should return empty string" { - $result = Get-ActionInput ([Guid]::NewGuid()) + $result = Get-ActionInput TEST_INPUT $result | Should -Be '' } It "Given Required switch, it throws" { { - Get-ActionInput ([Guid]::NewGuid()) -Required + Get-ActionInput TEST_INPUT -Required } | Should -Throw } } Context "When requested input exists" { It "Should return it's value" { - Mock Get-ChildItem { @{Value = 'test value' } } { - $Path -eq 'Env:INPUT_TEST_INPUT' - } -ModuleName GitHubActionsCore + $env:INPUT_TEST_INPUT = 'test value' $result = Get-ActionInput TEST_INPUT $result | Should -Be 'test value' } It "Should trim the returned value" { - Mock Get-ChildItem { @{Value = "`n test value `n `n" } } { - $Path -eq 'Env:INPUT_TEST_INPUT' - } -ModuleName GitHubActionsCore + $env:INPUT_TEST_INPUT = "`n test value `n `n" $result = Get-ActionInput TEST_INPUT @@ -39,15 +37,16 @@ Describe 'Get-ActionInput' { } Context "When input name contains spaces" { It "Should replace them with underscores" { - Mock Get-ChildItem { @{Value = 'value' } } { - $Path -eq 'Env:INPUT_TEST_INPUT' - } -ModuleName GitHubActionsCore + $env:INPUT_TEST_INPUT = 'test value' $result = Get-ActionInput 'test input' - $result | Should -Be 'value' + $result | Should -Be 'test value' } } + AfterEach { + $env:INPUT_TEST_INPUT = $null + } } Describe 'Set-ActionOutput' { BeforeAll { @@ -75,56 +74,131 @@ Describe 'Add-ActionSecret' { } Describe 'Set-ActionVariable' { BeforeAll { - Mock Write-Host { } -ModuleName GitHubActionsCore + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Used in AfterAll')] + $oldGithubEnv = $env:GITHUB_ENV + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Used in AfterAll')] + $oldTestVar = $env:TESTVAR } BeforeEach { - Remove-Item "Env:my-var" -ErrorAction SilentlyContinue + $env:GITHUB_ENV = $null + $env:TESTVAR = $null + } + AfterAll { + $env:GITHUB_ENV = $oldGithubEnv + $env:TESTVAR = $oldTestVar } - It "Given value '', sends command with '' and sets env var to ''" -TestCases @( + Context "Given value ''" -Foreach @( @{ Value = ''; ExpectedCmd = ''; ExpectedEnv = $null } @{ Value = 'test value'; ExpectedCmd = 'test value'; ExpectedEnv = 'test value' } @{ Value = 'A % B'; ExpectedCmd = 'A %25 B'; ExpectedEnv = 'A % B' } @{ Value = [ordered]@{ a = '1x'; b = '2y' }; ExpectedCmd = '{"a":"1x","b":"2y"}'; ExpectedEnv = '{"a":"1x","b":"2y"}' } ) { - Set-ActionVariable 'my-var' $Value - - ${env:my-var} | Should -Be $ExpectedEnv - Should -Invoke Write-Host -ParameterFilter { - $Object -eq "::set-env name=my-var::$ExpectedCmd" - } -ModuleName GitHubActionsCore - } - AfterEach { - Remove-Item "Env:my-var" -ErrorAction SilentlyContinue + Context "When GITHUB_ENV not set" { + BeforeAll { + Mock Write-Host { } -ModuleName GitHubActionsCore + } + It "Sends command with '' and sets env var to ''" { + Set-ActionVariable TESTVAR $Value + + $env:TESTVAR | Should -Be $ExpectedEnv + Should -Invoke Write-Host -ParameterFilter { + $Object -eq "::set-env name=TESTVAR::$ExpectedCmd" + } -ModuleName GitHubActionsCore + } + It "Sends command with '' and doesn't set env var due to -SkipLocal" { + Set-ActionVariable TESTVAR $Value -SkipLocal + + $env:TESTVAR | Should -BeNullOrEmpty + Should -Invoke Write-Host -ParameterFilter { + $Object -eq "::set-env name=TESTVAR::$ExpectedCmd" + } -ModuleName GitHubActionsCore + } + } + Context "When GITHUB_ENV is set" { + BeforeEach { + $testPath = 'TestDrive:/env-cmd.env' + Set-Content $testPath '' -NoNewline + $env:GITHUB_ENV = $testPath + } + It "Appends command file with formatted command and sets env var to ''" { + Set-ActionVariable TESTVAR $Value + + $env:TESTVAR | Should -Be $ExpectedEnv + $eol = [System.Environment]::NewLine + Get-Content $testPath -Raw + | Should -BeExactly "TESTVAR<<_GitHubActionsFileCommandDelimeter_${eol}$ExpectedEnv${eol}_GitHubActionsFileCommandDelimeter_${eol}" + } + It "Appends command file with formatted command and doesn't set env var due to -SkipLocal" { + Set-ActionVariable TESTVAR $Value -SkipLocal + + $env:TESTVAR | Should -BeNullOrEmpty + $eol = [System.Environment]::NewLine + Get-Content $testPath -Raw + | Should -BeExactly "TESTVAR<<_GitHubActionsFileCommandDelimeter_${eol}$ExpectedEnv${eol}_GitHubActionsFileCommandDelimeter_${eol}" + } + } } } Describe 'Add-ActionPath' { BeforeAll { - Mock Write-Host { } -ModuleName GitHubActionsCore + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Used in AfterAll')] + $oldPath = $env:PATH + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Used in AfterAll')] + $oldGithubPath = $env:GITHUB_PATH } BeforeEach { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Used in AfterEach')] - $prevPath = [System.Environment]::GetEnvironmentVariable('PATH') + $env:PATH = $oldPath + $env:GITHUB_PATH = $null } - It "Sends appropriate workflow command to host and prepends PATH" { - Add-ActionPath 'test path' - - $env:PATH | Should -BeLike "test path$([System.IO.Path]::PathSeparator)*" -Because 'PATH should be also prepended in current scope' - Should -Invoke Write-Host -ParameterFilter { - $Object -eq '::add-path::test path' - } -ModuleName GitHubActionsCore + AfterAll { + $env:PATH = $oldPath + $env:GITHUB_PATH = $oldGithubPath } - It "Given SkipLocal switch, sends command but doesn't change PATH" { - $path = $env:PATH - - Add-ActionPath 'test path' -SkipLocal - - $env:PATH | Should -Be $path -Because "PATH shouldn't be modified" - Should -Invoke Write-Host -ParameterFilter { - $Object -eq '::add-path::test path' - } -ModuleName GitHubActionsCore + Context "When GITHUB_PATH is not set" { + BeforeAll { + Mock Write-Host { } -ModuleName GitHubActionsCore + } + It "Sends appropriate workflow command to host and prepends PATH" { + Add-ActionPath 'test path' + + $env:PATH | Should -BeLike "test path$([System.IO.Path]::PathSeparator)*" -Because 'PATH should be also prepended in current scope' + Should -Invoke Write-Host -ParameterFilter { + $Object -eq '::add-path::test path' + } -ModuleName GitHubActionsCore + } + It "Given SkipLocal switch, sends command but doesn't change PATH" { + $path = $env:PATH + + Add-ActionPath 'test path' -SkipLocal + + $env:PATH | Should -Be $path -Because "PATH shouldn't be modified" + Should -Invoke Write-Host -ParameterFilter { + $Object -eq '::add-path::test path' + } -ModuleName GitHubActionsCore + } } - AfterEach { - [System.Environment]::SetEnvironmentVariable('PATH', $prevPath) + Context "When GITHUB_PATH is set" { + BeforeEach { + $testPath = 'TestDrive:/path-cmd.env' + Set-Content $testPath '' -NoNewline + $env:GITHUB_PATH = $testPath + } + It "Sends appropriate workflow command to command file and prepends PATH" { + Add-ActionPath 'test path' + + $env:PATH | Should -BeLike "test path$([System.IO.Path]::PathSeparator)*" -Because 'PATH should be also prepended in current scope' + Get-Content $testPath -Raw + | Should -BeExactly "test path$([System.Environment]::NewLine)" + } + It "Sends appropriate workflow command to command file but doesn't change PATH due to -SkipLocal" { + $path = $env:PATH + + Add-ActionPath 'test path' -SkipLocal + + $env:PATH | Should -Be $path -Because "PATH shouldn't be modified" + Get-Content $testPath -Raw + | Should -BeExactly "test path$([System.Environment]::NewLine)" + } } } Describe 'Set-ActionCommandEcho' { @@ -144,6 +218,8 @@ Describe 'Set-ActionCommandEcho' { } Describe 'Set-ActionFailed' { BeforeAll { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Used in AfterAll')] + $oldExitCode = [System.Environment]::ExitCode Mock Write-Host { } -ModuleName GitHubActionsCore } BeforeEach { @@ -162,16 +238,21 @@ Describe 'Set-ActionFailed' { $Object -eq "::error::$Expected" } -ModuleName GitHubActionsCore } - AfterEach { - [System.Environment]::ExitCode = 0 + AfterAll { + [System.Environment]::ExitCode = $oldExitCode } } Describe 'Get-ActionIsDebug' { BeforeAll { Mock Write-Host { } -ModuleName GitHubActionsCore + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Used in AfterAll')] + $oldEnvRunnerDebug = $env:RUNNER_DEBUG } BeforeEach { - Remove-Item Env:RUNNER_DEBUG -ErrorAction SilentlyContinue + $env:RUNNER_DEBUG = $null + } + AfterAll { + $env:RUNNER_DEBUG = $oldEnvRunnerDebug } It 'Given env var is not set, return false' { $result = Get-ActionIsDebug @@ -192,9 +273,6 @@ Describe 'Get-ActionIsDebug' { $result | Should -BeExactly $Expected } - AfterEach { - Remove-Item Env:RUNNER_DEBUG -ErrorAction SilentlyContinue - } } Describe 'Write-ActionError' { BeforeAll { @@ -334,4 +412,59 @@ Describe 'Send-ActionCommand' { $Object -eq $Expected } -ModuleName GitHubActionsCore } -} \ No newline at end of file +} + +Describe 'Send-ActionFileCommand' { + BeforeAll { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'Used in AfterAll')] + $oldGithubTestcmd = $env:GITHUB_TESTCMD + } + BeforeEach { + $testPath = 'TestDrive:/testcmd.env' + $env:GITHUB_TESTCMD = $testPath + } + AfterAll { + $env:GITHUB_TESTCMD = $oldGithubTestcmd + } + Context "When file doesn't exist" { + BeforeEach { + Remove-Item $testPath -Force -ea:Ignore + } + It "Given no command ('') throws" -TestCases @( + @{ Value = $null } + @{ Value = '' } + ) { + { + Send-ActionFileCommand -Command $Value -Message 'foobar' + } | Should -Throw "Cannot validate argument on parameter 'Command'. The argument is null or empty.*" + } + It "Given command for which env var doesn't exist" { + $env:GITHUB_TESTCMD = $null + { + Send-ActionFileCommand -Command TESTCMD -Message 'foobar' + } | Should -Throw 'Unable to find environment variable for file command TESTCMD' + } + It "Given command for which file doesn't exist" { + { + Send-ActionFileCommand -Command TESTCMD -Message 'foobar' + } | Should -Throw 'Missing file at path: *testcmd.env' + } + } + Context 'When file exists' { + BeforeEach { + Set-Content $testPath '' -NoNewline + } + It "Given a command with message '' writes '' to a file" -TestCases @( + @{ Msg = ''; Expected = $null } + @{ Msg = 'a'; Expected = $null } + @{ Msg = "a `r `n b : c % d"; Expected = $null } + @{ Msg = 1; Expected = $null } + @{ Msg = $true; Expected = 'true' } + @{ Msg = [ordered]@{ a = 1; b = $false }; Expected = '{"a":1,"b":false}' } + ) { + Send-ActionFileCommand TESTCMD -Message $Msg + + Get-Content $testPath -Raw | Should -BeExactly (($Expected ?? "$Msg") + [System.Environment]::NewLine) + } + } +} diff --git a/lib/GitHubActionsCore/GitHubActionsCore.psm1 b/lib/GitHubActionsCore/GitHubActionsCore.psm1 index 2c1136a..7d2f275 100644 --- a/lib/GitHubActionsCore/GitHubActionsCore.psm1 +++ b/lib/GitHubActionsCore/GitHubActionsCore.psm1 @@ -37,15 +37,23 @@ function Set-ActionVariable { [switch]$SkipLocal ) $convertedValue = ConvertTo-ActionCommandValue $Value - ## To take effect only in the current action/step + ## To take effect in the current action/step if (-not $SkipLocal) { [System.Environment]::SetEnvironmentVariable($Name, $convertedValue) } ## To take effect for all subsequent actions/steps - Send-ActionCommand set-env @{ - name = $Name - } -Message $convertedValue + if ($env:GITHUB_ENV) { + $delimiter = '_GitHubActionsFileCommandDelimeter_' + $eol = [System.Environment]::NewLine + $commandValue = "$name<<${delimiter}${eol}${convertedValue}${eol}${delimiter}" + Send-ActionFileCommand -Command ENV -Message $commandValue + } + else { + Send-ActionCommand set-env @{ + name = $Name + } -Message $convertedValue + } } <# @@ -93,7 +101,7 @@ function Add-ActionPath { [switch]$SkipLocal ) - ## To take effect only in the current action/step + ## To take effect in the current action/step if (-not $SkipLocal) { $oldPath = [System.Environment]::GetEnvironmentVariable('PATH') $newPath = "$Path$([System.IO.Path]::PathSeparator)$oldPath" @@ -101,7 +109,12 @@ function Add-ActionPath { } ## To take effect for all subsequent actions/steps - Send-ActionCommand add-path $Path + if ($env:GITHUB_PATH) { + Send-ActionFileCommand -Command PATH -Message $Path + } + else { + Send-ActionCommand -Command add-path -Message $Path + } } ## Used to identify inputs from env vars in Action/Workflow context @@ -539,6 +552,47 @@ function Send-ActionCommand { Write-Host $cmdStr } +<# +.SYNOPSIS +Sends a command to an Action Environment File. +Equivalent to `core.issueFileCommand(cmd, msg)`. +.DESCRIPTION +Appends given message to an Action Environment File. +.PARAMETER Command +Command (environment file variable suffix) to send message for. +.PARAMETER Message +Message to append. +.EXAMPLE +PS> Send-ActionFileCommand ENV 'myvar=value' +.EXAMPLE +PS> 'myvar=value', 'myvar2=novalue' | Send-ActionFileCommand ENV +.LINK +https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#environment-files +#> +function Send-ActionFileCommand { + [CmdletBinding()] + param ( + [Parameter(Position = 0, Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$Command, + + [Parameter(Position = 1, Mandatory, ValueFromPipeline)] + [psobject]$Message + ) + begin { + $filePath = [System.Environment]::GetEnvironmentVariable("GITHUB_$Command") + if (-not $filePath) { + throw "Unable to find environment variable for file command $Command" + } + if (-not (Test-Path $filePath -PathType Leaf)) { + throw "Missing file at path: $filePath" + } + } + process { + ConvertTo-ActionCommandValue $Message | Out-File -FilePath $filePath -Append + } +} + ########################################################################### ## Internal Implementation ########################################################################### @@ -669,6 +723,7 @@ Get-ActionIsDebug, Invoke-ActionGroup, Invoke-ActionNoCommandsBlock, Send-ActionCommand, +Send-ActionFileCommand, Set-ActionCommandEcho, Set-ActionFailed, Set-ActionOutput, diff --git a/test.ps1 b/test.ps1 new file mode 100644 index 0000000..e2b72a3 --- /dev/null +++ b/test.ps1 @@ -0,0 +1,12 @@ +[CmdletBinding()] +param ( + [Parameter()] + [switch] + $CI = ($env:CI -eq 'true') +) + +if (Get-Module Pester | ? Version -LT '5.1') { + Install-Module -Name Pester -Force -SkipPublisherCheck -MinimumVersion '5.1' -PassThru + | Import-Module Pester -MinimumVersion '5.1' +} +Invoke-Pester -CI:$CI \ No newline at end of file