From 321e33addddce3eeb39877b462e00483fe2a2590 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 10 Jul 2026 22:15:28 +0200 Subject: [PATCH 1/3] Expose TestData via the Helpers module's Import-TestData command Replace the caller-shipped ./.github/scripts/Expose-TestData.ps1 with a call to Import-TestData, which is provided by PSModule/Install-PSModuleHelpers. All three ModuleLocal workflows (BeforeAll/Test/AfterAll) now install the helpers and run Import-TestData to expose the caller's secrets/variables to the test jobs; BeforeAll/AfterAll take the Install-PSModuleHelpers dependency they previously lacked. The helpers ref is temporarily @feat/import-testdata for end-to-end testing. --- .github/scripts/Expose-TestData.ps1 | 138 -------------------- .github/workflows/AfterAll-ModuleLocal.yml | 5 +- .github/workflows/BeforeAll-ModuleLocal.yml | 5 +- .github/workflows/Test-ModuleLocal.yml | 16 +-- README.md | 3 +- 5 files changed, 18 insertions(+), 149 deletions(-) delete mode 100644 .github/scripts/Expose-TestData.ps1 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..157969c9 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@feat/import-testdata # E2E: temporary branch ref (Import-TestData not yet released); pin to a released SHA before merge + - 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..85e4952b 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@feat/import-testdata # E2E: temporary branch ref (Import-TestData not yet released); pin to a released SHA before merge + - 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/Test-ModuleLocal.yml b/.github/workflows/Test-ModuleLocal.yml index fa534551..ca017850 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@feat/import-testdata # E2E: temporary branch ref (Import-TestData not yet released); pin to a released SHA before merge + + - 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 From 5bec498d86a837ff44ffbb87224a8eb0a8c1a332 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 10 Jul 2026 22:50:31 +0200 Subject: [PATCH 2/3] test: point Resolve-Version at the always-evaluate-labels branch for E2E Temporarily reference PSModule/Resolve-PSModuleVersion@feat/always-evaluate-version-labels so the pipeline previews the label-driven bump (e.g. minor) even on a pull_request event. Pin to a released SHA before merge. --- .github/workflows/Plan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Plan.yml b/.github/workflows/Plan.yml index 3a25e0f4..b4a6fd44 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@feat/always-evaluate-version-labels # E2E: temp branch ref (always-evaluate-labels); pin to a released SHA before merge id: Resolve-Version env: GH_TOKEN: ${{ github.token }} From d028861699d084d4f2ca1cde72db5d89566e99fe Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 11 Jul 2026 00:12:14 +0200 Subject: [PATCH 3/3] Pin Install-PSModuleHelpers to v1.0.9 and Resolve-PSModuleVersion to v1.1.4 Replaces the temporary E2E branch refs with released commit SHAs now that Import-TestData (IPH v1.0.9) and the always-evaluate-labels fix (Resolve v1.1.4) are released. Also bumps Build-Site's Install-PSModuleHelpers pin to v1.0.9 so the action is uniform across the repo. --- .github/workflows/AfterAll-ModuleLocal.yml | 2 +- .github/workflows/BeforeAll-ModuleLocal.yml | 2 +- .github/workflows/Build-Site.yml | 2 +- .github/workflows/Plan.yml | 2 +- .github/workflows/Test-ModuleLocal.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/AfterAll-ModuleLocal.yml b/.github/workflows/AfterAll-ModuleLocal.yml index 157969c9..c17ab029 100644 --- a/.github/workflows/AfterAll-ModuleLocal.yml +++ b/.github/workflows/AfterAll-ModuleLocal.yml @@ -32,7 +32,7 @@ jobs: fetch-depth: 0 - name: Install-PSModuleHelpers - uses: PSModule/Install-PSModuleHelpers@feat/import-testdata # E2E: temporary branch ref (Import-TestData not yet released); pin to a released SHA before merge + uses: PSModule/Install-PSModuleHelpers@8bfb84d557755c67d9b5643efe573bdcae4c1a4a # v1.0.9 - name: Expose caller-provided test data shell: pwsh diff --git a/.github/workflows/BeforeAll-ModuleLocal.yml b/.github/workflows/BeforeAll-ModuleLocal.yml index 85e4952b..7ba1c7fc 100644 --- a/.github/workflows/BeforeAll-ModuleLocal.yml +++ b/.github/workflows/BeforeAll-ModuleLocal.yml @@ -32,7 +32,7 @@ jobs: fetch-depth: 0 - name: Install-PSModuleHelpers - uses: PSModule/Install-PSModuleHelpers@feat/import-testdata # E2E: temporary branch ref (Import-TestData not yet released); pin to a released SHA before merge + uses: PSModule/Install-PSModuleHelpers@8bfb84d557755c67d9b5643efe573bdcae4c1a4a # v1.0.9 - name: Expose caller-provided test data shell: pwsh 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 b4a6fd44..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@feat/always-evaluate-version-labels # E2E: temp branch ref (always-evaluate-labels); pin to a released SHA before merge + 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 ca017850..95933dd1 100644 --- a/.github/workflows/Test-ModuleLocal.yml +++ b/.github/workflows/Test-ModuleLocal.yml @@ -43,7 +43,7 @@ jobs: path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/module - name: Install-PSModuleHelpers - uses: PSModule/Install-PSModuleHelpers@feat/import-testdata # E2E: temporary branch ref (Import-TestData not yet released); pin to a released SHA before merge + uses: PSModule/Install-PSModuleHelpers@8bfb84d557755c67d9b5643efe573bdcae4c1a4a # v1.0.9 - name: Expose caller-provided test data shell: pwsh