Build/New-PublishStaging.ps1
|
function New-PublishStaging { <# .SYNOPSIS Builds the folder Publish-Module packs: a clean copy of the module, never a secret. .DESCRIPTION Publish-Module packs every file of the folder it is given into the public package. The module folder is also where a developer keeps .env (NUGET_API_KEY, AI provider keys), so publishing the folder itself, or a plain copy of it, would ship those keys to the PowerShell Gallery. The staging folder holds only the files git tracks in the module folder (working-tree content, so an uncommitted version bump is included). .env, test results and other untracked or ignored files never get in. Outside a git checkout every file is copied. Either way, files named like a secret (.env, .env.*, *.apikey, *.secret, secrets.json; .env.example excepted) are removed, and the function throws if one is still there. The staging folder is named after the module, as Publish-Module requires. The caller removes its parent folder (Remove-PublishStaging) after publishing. .OUTPUTS System.String - full path of the staging folder (<temp>/aicr-publish-<guid>/<ModuleName>) #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$ModuleRoot, [Parameter(Mandatory)][string]$ModuleName ) $secretPattern = '^(\.env(\..+)?|.+\.apikey|.+\.secret|secrets\.json)$' $allowed = @('.env.example') $stagingRoot = Join-Path ([System.IO.Path]::GetTempPath()) "aicr-publish-$([guid]::NewGuid().ToString('N'))" $staging = Join-Path $stagingRoot $ModuleName New-Item -ItemType Directory -Path $staging -Force | Out-Null $tracked = @(git -C $ModuleRoot -c core.quotepath=off ls-files --cached -- . 2>$null) if ($LASTEXITCODE -eq 0 -and $tracked.Count -gt 0) { foreach ($relative in $tracked) { $source = Join-Path $ModuleRoot $relative if (-not (Test-Path -LiteralPath $source -PathType Leaf)) { continue } # deleted, not committed $target = Join-Path $staging $relative New-Item -ItemType Directory -Path (Split-Path $target -Parent) -Force | Out-Null Copy-Item -LiteralPath $source -Destination $target -Force } Write-Host " ✓ Staged $($tracked.Count) git-tracked file(s) (untracked and ignored files, such as .env, stay out)" -ForegroundColor Green } else { Get-ChildItem -LiteralPath $ModuleRoot -Force | Copy-Item -Destination $staging -Recurse -Force Write-Host " ⚠️ Not a git checkout: staged every file of $ModuleRoot" -ForegroundColor Yellow } $secrets = @(Get-ChildItem -LiteralPath $staging -Recurse -Force -File | Where-Object { $_.Name -match $secretPattern -and $_.Name -notin $allowed }) foreach ($file in $secrets) { Write-Host " ✓ Left out of the package: $($file.FullName.Substring($staging.Length + 1))" -ForegroundColor Yellow Remove-Item -LiteralPath $file.FullName -Force } $left = @(Get-ChildItem -LiteralPath $staging -Recurse -Force -File | Where-Object { $_.Name -match $secretPattern -and $_.Name -notin $allowed }) if ($left.Count -gt 0) { Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue throw "Refusing to publish: secret-like file(s) still in the staging folder: $($left.Name -join ', ')" } return $staging } function Remove-PublishStaging { [CmdletBinding()] param([string]$StagingPath) if (-not $StagingPath) { return } $root = Split-Path $StagingPath -Parent if ($root -and (Split-Path $root -Leaf) -like 'aicr-publish-*' -and (Test-Path -LiteralPath $root)) { Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue } } |