diff --git a/.github/scripts/Expose-TestData.ps1 b/.github/scripts/Expose-TestData.ps1 deleted file mode 100644 index 6897c2a3..00000000 --- a/.github/scripts/Expose-TestData.ps1 +++ /dev/null @@ -1,138 +0,0 @@ -if ([string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_DATA)) { - Write-Output 'No test data was provided by the calling workflow.' - return -} -try { - $data = $env:PSMODULE_TEST_DATA | ConvertFrom-Json -ErrorAction Stop -} catch { - throw "The 'TestData' secret must be valid JSON with 'secrets' and/or 'variables' maps." -} -if ($null -eq $data -or $data -isnot [pscustomobject]) { - throw "The 'TestData' secret must be a JSON object with 'secrets' and/or 'variables' maps." -} -$allowedTopLevelKeys = @('secrets', 'variables') -foreach ($propertyName in $data.PSObject.Properties.Name) { - if ($allowedTopLevelKeys -notcontains $propertyName) { - throw "The 'TestData' secret only supports 'secrets' and 'variables' maps." - } -} -$reservedNames = @('CI', 'HOME', 'PATH', 'PWD', 'SHELL', 'PSMODULE_TEST_DATA') -$reservedPrefixes = @('GITHUB_', 'RUNNER_', 'ACTIONS_') -function Assert-EnvironmentName { - <# - .SYNOPSIS - Validates that a TestData key can safely be written to GITHUB_ENV. - #> - param([string] $Name) - if ($Name -notmatch '^[A-Za-z_][A-Za-z0-9_]*$') { - throw 'TestData keys must be valid environment variable names.' - } - $normalized = $Name.ToUpperInvariant() - if ($reservedNames -contains $normalized) { - throw 'TestData keys must not override reserved environment variables.' - } - foreach ($prefix in $reservedPrefixes) { - if ($normalized.StartsWith($prefix)) { - throw 'TestData keys must not override reserved environment variables.' - } - } -} -function Assert-Map { - <# - .SYNOPSIS - Validates that a TestData section is a JSON object map. - #> - param( - [object] $Map, - [string] $Name - ) - if ($null -eq $Map) { return } - if ($Map -isnot [pscustomobject]) { - throw "The 'TestData.$Name' value must be a JSON object." - } -} -function Get-EnvironmentValue { - <# - .SYNOPSIS - Converts a scalar TestData value to an environment variable value. - #> - param( - [object] $Value, - [string] $Name - ) - if ($null -eq $Value) { return '' } - if ( - $Value -is [pscustomobject] -or - ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) - ) { - throw "Values in 'TestData.$Name' must be scalar values." - } - return [string]$Value -} -function Add-EnvFromMap { - <# - .SYNOPSIS - Writes validated TestData entries to GITHUB_ENV. - #> - param( - [object] $Map, - [string] $Name, - [switch] $Mask - ) - Assert-Map -Map $Map -Name $Name - if ($null -eq $Map) { return } - $count = 0 - foreach ($item in $Map.PSObject.Properties) { - $name = $item.Name - Assert-EnvironmentName -Name $name - $value = Get-EnvironmentValue -Value $item.Value -Name $Name - if ($Mask) { - foreach ($line in ($value -split "`n")) { - $line = $line.TrimEnd("`r") - if ($line.Length -gt 0) { - Write-Output "::add-mask::$line" - } - } - } - do { - $delimiter = "GHENV_$([guid]::NewGuid().ToString('N'))" - } while ($value.Contains($delimiter)) - Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter" -Encoding utf8 - Add-Content -Path $env:GITHUB_ENV -Value $value -Encoding utf8 - Add-Content -Path $env:GITHUB_ENV -Value $delimiter -Encoding utf8 - $count++ - } - if ($count -gt 0) { - if ($Mask) { - Write-Output "Exposed $count secret value(s) as environment variables." - } else { - Write-Output "Exposed $count variable value(s) as environment variables." - } - } -} - -Assert-Map -Map $data.secrets -Name 'secrets' -Assert-Map -Map $data.variables -Name 'variables' - -$secretNames = @() -if ($null -ne $data.secrets) { - $secretNames = @($data.secrets.PSObject.Properties.Name) -} -$variableNames = @() -if ($null -ne $data.variables) { - $variableNames = @($data.variables.PSObject.Properties.Name) -} -$secretNameSet = [System.Collections.Generic.HashSet[string]]::new( - [System.StringComparer]::OrdinalIgnoreCase -) -foreach ($secretName in $secretNames) { - [void] $secretNameSet.Add($secretName) -} -foreach ($variableName in $variableNames) { - if ($secretNameSet.Contains($variableName)) { - throw 'TestData keys must not be duplicated across secrets and variables.' - } -} - -Add-EnvFromMap -Map $data.secrets -Name 'secrets' -Mask -Add-EnvFromMap -Map $data.variables -Name 'variables' diff --git a/.github/workflows/AfterAll-ModuleLocal.yml b/.github/workflows/AfterAll-ModuleLocal.yml index 61c06181..c17ab029 100644 --- a/.github/workflows/AfterAll-ModuleLocal.yml +++ b/.github/workflows/AfterAll-ModuleLocal.yml @@ -31,12 +31,15 @@ jobs: persist-credentials: false fetch-depth: 0 + - name: Install-PSModuleHelpers + uses: PSModule/Install-PSModuleHelpers@8bfb84d557755c67d9b5643efe573bdcae4c1a4a # v1.0.9 + - name: Expose caller-provided test data shell: pwsh env: PSMODULE_TEST_DATA: ${{ secrets.TestData }} run: | - ./.github/scripts/Expose-TestData.ps1 + Import-TestData - name: Run AfterAll Teardown Scripts if: always() diff --git a/.github/workflows/BeforeAll-ModuleLocal.yml b/.github/workflows/BeforeAll-ModuleLocal.yml index 8eddb3c8..7ba1c7fc 100644 --- a/.github/workflows/BeforeAll-ModuleLocal.yml +++ b/.github/workflows/BeforeAll-ModuleLocal.yml @@ -31,12 +31,15 @@ jobs: persist-credentials: false fetch-depth: 0 + - name: Install-PSModuleHelpers + uses: PSModule/Install-PSModuleHelpers@8bfb84d557755c67d9b5643efe573bdcae4c1a4a # v1.0.9 + - name: Expose caller-provided test data shell: pwsh env: PSMODULE_TEST_DATA: ${{ secrets.TestData }} run: | - ./.github/scripts/Expose-TestData.ps1 + Import-TestData - name: Run BeforeAll Setup Scripts uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0 diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index 720b10b9..27d13905 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -23,7 +23,7 @@ jobs: fetch-depth: 0 - name: Install-PSModuleHelpers - uses: PSModule/Install-PSModuleHelpers@68e8ca76be679bfcb7099aed8cffa911c4ab72af # v1.0.8 + uses: PSModule/Install-PSModuleHelpers@8bfb84d557755c67d9b5643efe573bdcae4c1a4a # v1.0.9 - name: Download docs artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/Plan.yml b/.github/workflows/Plan.yml index 3a25e0f4..1f449b53 100644 --- a/.github/workflows/Plan.yml +++ b/.github/workflows/Plan.yml @@ -85,7 +85,7 @@ jobs: ImportantFilePatterns: ${{ inputs.ImportantFilePatterns }} - name: Resolve-Version - uses: PSModule/Resolve-PSModuleVersion@d53326c7687d20a7949d457fccd71223856b1890 # v1.1.0 + uses: PSModule/Resolve-PSModuleVersion@8d1dac7f326a45ba08060c1e24a5dd6f6f00b3ab # v1.1.4 id: Resolve-Version env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/Test-ModuleLocal.yml b/.github/workflows/Test-ModuleLocal.yml index fa534551..95933dd1 100644 --- a/.github/workflows/Test-ModuleLocal.yml +++ b/.github/workflows/Test-ModuleLocal.yml @@ -36,13 +36,6 @@ jobs: persist-credentials: false fetch-depth: 0 - - name: Expose caller-provided test data - shell: pwsh - env: - PSMODULE_TEST_DATA: ${{ secrets.TestData }} - run: | - ./.github/scripts/Expose-TestData.ps1 - - name: Download module artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -50,7 +43,14 @@ jobs: path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/module - name: Install-PSModuleHelpers - uses: PSModule/Install-PSModuleHelpers@68e8ca76be679bfcb7099aed8cffa911c4ab72af # v1.0.8 + uses: PSModule/Install-PSModuleHelpers@8bfb84d557755c67d9b5643efe573bdcae4c1a4a # v1.0.9 + + - name: Expose caller-provided test data + shell: pwsh + env: + PSMODULE_TEST_DATA: ${{ secrets.TestData }} + run: | + Import-TestData - name: Import-Module id: import-module diff --git a/README.md b/README.md index 3e4500be..6ba8395b 100644 --- a/README.md +++ b/README.md @@ -489,7 +489,8 @@ Notes: Names must match `^[A-Za-z_][A-Za-z0-9_]*$` and must not override reserved variables such as `PATH`, `CI`, `GITHUB_*`, `RUNNER_*` or `ACTIONS_*`. - The `TestData` validation, masking and environment export logic is shared by the ModuleLocal workflows - through `.github/scripts/Expose-TestData.ps1`. + through the [`PSModule/Install-PSModuleHelpers`](https://github.com/PSModule/Install-PSModuleHelpers) + action, which installs the `Import-TestData` command each workflow runs to expose the values. - Reference secrets as `"${{ secrets. }}"` (quoted, directly) rather than `toJSON(secrets.)`. The direct form keeps CodeQL's *excessive secrets exposure* check happy and works for single-line secret values. It cannot carry values that contain `"`, `\` or newlines, so