@@ -1185,3 +1185,164 @@ function Set-GitHubLogGroup {
11851185 Write-Host ' ::endgroup::'
11861186}
11871187
1188+ function Import-TestData {
1189+ <#
1190+ . SYNOPSIS
1191+ Exposes caller-provided TestData as environment variables for later steps in the job.
1192+
1193+ . DESCRIPTION
1194+ Reads the `PSMODULE_TEST_DATA` environment variable, which is expected to contain a single-line
1195+ JSON object with optional `secrets` and `variables` maps. Each entry is validated and written to
1196+ the file referenced by `GITHUB_ENV` so it becomes available as `$env:<name>` in subsequent steps
1197+ of the same job. Values under `secrets` are masked in the logs via `::add-mask::`; values under
1198+ `variables` are not. When no test data is provided the function is a no-op.
1199+
1200+ . EXAMPLE
1201+ Import-TestData
1202+
1203+ Reads `$env:PSMODULE_TEST_DATA` and exposes its `secrets` and `variables` entries as environment
1204+ variables for the following steps in the job.
1205+ #>
1206+ [CmdletBinding ()]
1207+ param ()
1208+
1209+ if ([string ]::IsNullOrWhiteSpace($env: PSMODULE_TEST_DATA )) {
1210+ Write-Output ' No test data was provided by the calling workflow.'
1211+ return
1212+ }
1213+ try {
1214+ $data = $env: PSMODULE_TEST_DATA | ConvertFrom-Json - ErrorAction Stop
1215+ } catch {
1216+ throw " The 'TestData' secret must be valid JSON with 'secrets' and/or 'variables' maps."
1217+ }
1218+ if ($null -eq $data -or $data -isnot [pscustomobject ]) {
1219+ throw " The 'TestData' secret must be a JSON object with 'secrets' and/or 'variables' maps."
1220+ }
1221+ $allowedTopLevelKeys = @ (' secrets' , ' variables' )
1222+ foreach ($propertyName in $data.PSObject.Properties.Name ) {
1223+ if ($allowedTopLevelKeys -notcontains $propertyName ) {
1224+ throw " The 'TestData' secret only supports 'secrets' and 'variables' maps."
1225+ }
1226+ }
1227+ $reservedNames = @ (' CI' , ' HOME' , ' PATH' , ' PWD' , ' SHELL' , ' PSMODULE_TEST_DATA' )
1228+ $reservedPrefixes = @ (' GITHUB_' , ' RUNNER_' , ' ACTIONS_' )
1229+ function Assert-EnvironmentName {
1230+ <#
1231+ . SYNOPSIS
1232+ Validates that a TestData key can safely be written to GITHUB_ENV.
1233+ #>
1234+ param ([string ] $Name )
1235+ if ($Name -notmatch ' ^[A-Za-z_][A-Za-z0-9_]*$' ) {
1236+ throw ' TestData keys must be valid environment variable names.'
1237+ }
1238+ $normalized = $Name.ToUpperInvariant ()
1239+ if ($reservedNames -contains $normalized ) {
1240+ throw ' TestData keys must not override reserved environment variables.'
1241+ }
1242+ foreach ($prefix in $reservedPrefixes ) {
1243+ if ($normalized.StartsWith ($prefix )) {
1244+ throw ' TestData keys must not override reserved environment variables.'
1245+ }
1246+ }
1247+ }
1248+ function Assert-Map {
1249+ <#
1250+ . SYNOPSIS
1251+ Validates that a TestData section is a JSON object map.
1252+ #>
1253+ param (
1254+ [object ] $Map ,
1255+ [string ] $Name
1256+ )
1257+ if ($null -eq $Map ) { return }
1258+ if ($Map -isnot [pscustomobject ]) {
1259+ throw " The 'TestData.$Name ' value must be a JSON object."
1260+ }
1261+ }
1262+ function Get-EnvironmentValue {
1263+ <#
1264+ . SYNOPSIS
1265+ Converts a scalar TestData value to an environment variable value.
1266+ #>
1267+ param (
1268+ [object ] $Value ,
1269+ [string ] $Name
1270+ )
1271+ if ($null -eq $Value ) { return ' ' }
1272+ if (
1273+ $Value -is [pscustomobject ] -or
1274+ ($Value -is [System.Collections.IEnumerable ] -and $Value -isnot [string ])
1275+ ) {
1276+ throw " Values in 'TestData.$Name ' must be scalar values."
1277+ }
1278+ return [string ]$Value
1279+ }
1280+ function Add-EnvFromMap {
1281+ <#
1282+ . SYNOPSIS
1283+ Writes validated TestData entries to GITHUB_ENV.
1284+ #>
1285+ param (
1286+ [object ] $Map ,
1287+ [string ] $Name ,
1288+ [switch ] $Mask
1289+ )
1290+ Assert-Map - Map $Map - Name $Name
1291+ if ($null -eq $Map ) { return }
1292+ $count = 0
1293+ foreach ($item in $Map.PSObject.Properties ) {
1294+ $name = $item.Name
1295+ Assert-EnvironmentName - Name $name
1296+ $value = Get-EnvironmentValue - Value $item.Value - Name $Name
1297+ if ($Mask ) {
1298+ foreach ($line in ($value -split " `n " )) {
1299+ $line = $line.TrimEnd (" `r " )
1300+ if ($line.Length -gt 0 ) {
1301+ Write-Output " ::add-mask::$line "
1302+ }
1303+ }
1304+ }
1305+ do {
1306+ $delimiter = " GHENV_$ ( [guid ]::NewGuid().ToString(' N' )) "
1307+ } while ($value.Contains ($delimiter ))
1308+ Add-Content - Path $env: GITHUB_ENV - Value " $name <<$delimiter " - Encoding utf8
1309+ Add-Content - Path $env: GITHUB_ENV - Value $value - Encoding utf8
1310+ Add-Content - Path $env: GITHUB_ENV - Value $delimiter - Encoding utf8
1311+ $count ++
1312+ }
1313+ if ($count -gt 0 ) {
1314+ if ($Mask ) {
1315+ Write-Output " Exposed $count secret value(s) as environment variables."
1316+ } else {
1317+ Write-Output " Exposed $count variable value(s) as environment variables."
1318+ }
1319+ }
1320+ }
1321+
1322+ Assert-Map - Map $data.secrets - Name ' secrets'
1323+ Assert-Map - Map $data.variables - Name ' variables'
1324+
1325+ $secretNames = @ ()
1326+ if ($null -ne $data.secrets ) {
1327+ $secretNames = @ ($data.secrets.PSObject.Properties.Name )
1328+ }
1329+ $variableNames = @ ()
1330+ if ($null -ne $data.variables ) {
1331+ $variableNames = @ ($data.variables.PSObject.Properties.Name )
1332+ }
1333+ $secretNameSet = [System.Collections.Generic.HashSet [string ]]::new(
1334+ [System.StringComparer ]::OrdinalIgnoreCase
1335+ )
1336+ foreach ($secretName in $secretNames ) {
1337+ [void ] $secretNameSet.Add ($secretName )
1338+ }
1339+ foreach ($variableName in $variableNames ) {
1340+ if ($secretNameSet.Contains ($variableName )) {
1341+ throw ' TestData keys must not be duplicated across secrets and variables.'
1342+ }
1343+ }
1344+
1345+ Add-EnvFromMap - Map $data.secrets - Name ' secrets' - Mask
1346+ Add-EnvFromMap - Map $data.variables - Name ' variables'
1347+ }
1348+
0 commit comments