diff --git a/make-tester-zip.ps1 b/make-tester-zip.ps1 new file mode 100644 index 0000000..b3b2ee9 --- /dev/null +++ b/make-tester-zip.ps1 @@ -0,0 +1,92 @@ +# 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