Files
RemSound/make-tester-zip.ps1
EdnunpandClaude Opus 4.8 71fb065233 Add make-tester-zip.ps1 for clean portable copies to hand to testers
Local checkpoint - NOT for public release.

Produces a zip that mirrors the publish folder (every current code fix, program files,
pdb, all runtimes, sounds, readme, Tolk, install scripts) with NONE of the developer's
personal state (no profiles/config/logs/recordings). Same safety approach as
build-release.ps1: publishes into a fresh never-run folder, then scans both the staged
files AND the finished zip and aborts if any personal data is present. Not a public
release (no tag/notes/GitHub) - a private portable copy for external testers.

Runs the build-and-test gate first by default (don't ship testers a broken build);
-SkipGate to bypass, -Output to pick the path (defaults to D:\Dropbox\remsound.zip).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 11:27:08 +01:00

93 lines
4.8 KiB
PowerShell

# make-tester-zip.ps1 - build a clean, portable RemSound copy to hand to external testers.
#
# What it produces
# ----------------
# A zip that MIRRORS the publish folder (every current code fix, the program files, pdb symbols,
# all runtimes, default sounds, readme, Tolk, install scripts) but with NONE of your personal state -
# no profiles, config, logs or recordings. A tester unzips it, runs RemSound.exe, and it builds its own
# fresh config on first launch.
#
# How it stays clean
# ------------------
# * It ALWAYS publishes into a fresh, empty folder the app has never run in, so there is nothing
# personal to leak (same principle as build-release.ps1).
# * It then SCANS the staged files AND the finished zip and ABORTS (deletes the zip, exits non-zero)
# if anything that could carry personal data is present.
# Unlike build-release.ps1 this is NOT a public release: no tag, no version/notes checks, no GitHub -
# it's a private portable copy for a couple of testers. Nothing is stripped, so it matches exactly what
# you test with locally.
#
# Usage:
# powershell -ExecutionPolicy Bypass -File make-tester-zip.ps1 # -> D:\Dropbox\remsound.zip, runs the test gate first
# powershell -ExecutionPolicy Bypass -File make-tester-zip.ps1 -Output C:\path\to\build.zip
# powershell -ExecutionPolicy Bypass -File make-tester-zip.ps1 -SkipGate # skip the build-and-test gate (faster; use when you just ran it)
param(
[string]$Output = 'D:\Dropbox\remsound.zip',
[switch]$SkipGate
)
$ErrorActionPreference = 'Stop'
$repo = $PSScriptRoot
$proj = Join-Path $repo 'src\RemSound.App\RemSound.App.csproj'
$staging = Join-Path ([System.IO.Path]::GetTempPath()) ("remsound-testers-" + [guid]::NewGuid().ToString('N'))
# 0. Don't hand testers a broken build - run the build-and-test gate first (unless told to skip). The
# gate publishes + tests its OWN throwaway copy, so it doesn't touch the clean staging folder below.
$gate = Join-Path $repo 'run-tests.ps1'
if (-not $SkipGate -and (Test-Path $gate)) {
Write-Host "Running the build-and-test gate first..." -ForegroundColor Cyan
& $gate
if ($LASTEXITCODE -ne 0) { throw "build-and-test gate FAILED - not making a tester zip. Fix the failures, or re-run with -SkipGate to override." }
}
# 1. Fresh Release publish = exactly what deploy-test puts in the publish folder (program files, pdb,
# all runtimes, default sounds, readme, Tolk, install scripts). Fresh = never run = no user state.
Write-Host "Publishing Release to a fresh folder: $staging" -ForegroundColor Cyan
& dotnet publish $proj -c Release -o $staging --nologo -v q
if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed ($LASTEXITCODE)" }
# 2. SAFETY SCAN - the same rule build-release.ps1 uses. Nothing that could carry personal data may ship.
$forbiddenFolders = @('logs','profiles','recordings','config','user settings and logs')
function Test-Forbidden([string]$path) {
$p = $path.Replace('\','/')
foreach ($f in $forbiddenFolders) { if ($p -match "(^|/)$f/") { return $true } }
if ($p -match '\.log$') { return $true }
if ($p -match '(^|/)(remsound\.config\.json|global config\.json)$') { return $true }
return $false
}
$bad = @()
Get-ChildItem -Path $staging -Recurse -Force | ForEach-Object {
$rel = $_.FullName.Substring($staging.Length).TrimStart('\','/')
if (Test-Forbidden $rel) { $bad += $rel }
}
if ($bad.Count -gt 0) {
Remove-Item -LiteralPath $staging -Recurse -Force
throw "ABORT - staged copy contains files that must not ship: $($bad -join ', ')"
}
Write-Host "Scan clean: no profiles / config / logs / recordings present." -ForegroundColor Green
# 3. Zip it.
$outDir = Split-Path -Parent $Output
if ($outDir -and -not (Test-Path -LiteralPath $outDir)) { New-Item -ItemType Directory -Path $outDir -Force | Out-Null }
if (Test-Path -LiteralPath $Output) { Remove-Item -LiteralPath $Output -Force }
Compress-Archive -Path (Join-Path $staging '*') -DestinationPath $Output -CompressionLevel Optimal -Force
# 4. SAFETY SCAN again, on the finished zip - belt and braces.
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($Output)
try {
$leaked = @($zip.Entries | Where-Object { Test-Forbidden $_.FullName })
$entryCount = $zip.Entries.Count
} finally { $zip.Dispose() }
Remove-Item -LiteralPath $staging -Recurse -Force
if ($leaked.Count -gt 0) {
Remove-Item -LiteralPath $Output -Force
throw "ABORT - finished zip contains forbidden entries: $($leaked.FullName -join ', ')"
}
$mb = [math]::Round((Get-Item -LiteralPath $Output).Length / 1MB, 2)
Write-Host ""
Write-Host "OK - tester zip ready: $Output ($mb MB, $entryCount entries)." -ForegroundColor Green
Write-Host " Mirrors the publish folder; verified no profiles / config / logs / recordings." -ForegroundColor Green