hotfix(v3.0.10): Base64 '-or' 括号 bug + dry-run 实跑验证护栏

v3.0.9 Base64 脚本启动即报:
  Test-Path: A parameter cannot be found that matches parameter name 'or'.
  claude.exe not found

根因: 'Test-Path (Join-Path $p claude.ps1) -or (Test-Path ...)' 中 -or
被当成 Test-Path 的命名参数. PSParser 静态检查看合法, 运行时炸.

修复:
  F1: 括号修正 — $hasClaude 抽为独立变量, 三元 -or 每项带外括号
  F2: gen-launcher-bats.ps1 强制加 dry-run 实跑验证护栏
      解码后的 Base64 脚本必须被 pwsh -File 实跑到底部 __BW_DRYRUN_OK__
      才算通过. 检查 ErrorRecord / ParameterBindingException / 未知命令.
      任何未来 Base64 改动都被此验证拦截.

验证层级教训:
  PSParser = 抓语法   / 抓不到参数绑定错
  dry-run  = 抓运行时 / 抓不到业务逻辑
  smoke    = 抓业务   / 需要前两层通过

EXE 220160 → 220672 bytes (+512)
This commit is contained in:
bookworm 2026-04-24 22:48:08 +08:00
parent a3b4ff3a78
commit 609e82bac0
4 changed files with 41 additions and 6 deletions

View File

@ -48,7 +48,7 @@ trap {
}
# ─── 版本号 (每次更新递增, build.ps1 自动读取) ──────
$BWVersion = "3.0.9" # 根治: Claude 装完固化 npm prefix 到 User PATH + Base64 三层 PATH + 快捷方式前 claude 自验证
$BWVersion = "3.0.10" # hotfix: Base64 脚本 -or 括号导致 Test-Path 参数错 (gen-launcher-bats 加实跑 dry-run 防护)
# DryRun 模式日志标记
if ($DryRun) { $global:BWDryRun = $DryRun } else { $global:BWDryRun = $null }

View File

@ -29,7 +29,9 @@ $npmCandidates = @(
"$env:LOCALAPPDATA\npm"
)
foreach ($p in $npmCandidates) {
if ((Test-Path $p) -and (Test-Path (Join-Path $p 'claude.ps1') -or (Test-Path (Join-Path $p 'claude.cmd'))) -and ($env:Path -notlike "*$p*")) {
if (-not (Test-Path $p)) { continue }
$hasClaude = (Test-Path (Join-Path $p 'claude.ps1')) -or (Test-Path (Join-Path $p 'claude.cmd')) -or (Test-Path (Join-Path $p 'claude'))
if ($hasClaude -and ($env:Path -notlike "*$p*")) {
$env:Path = "$p;$env:Path"
}
}
@ -175,9 +177,42 @@ if %errorlevel% neq 0 (
Write-Host "[gen-launcher-bats] ✓ 启动Bookworm.bat ($((Get-Item $launchBat).Length) bytes)" -ForegroundColor Green
Write-Host "[gen-launcher-bats] ✓ 更新并启动Bookworm.bat ($((Get-Item $updateBat).Length) bytes)" -ForegroundColor Green
# ─── Round-trip 验证 ─────────────────────────────────────
# ─── Round-trip 验证 (v3.0.10: 除了 PARSE 还要 lint + 实跑不启动 claude) ────
$decoded = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($enc))
$err = $null
[void][System.Management.Automation.Language.Parser]::ParseInput($decoded, [ref]$null, [ref]$err)
if ($err) { throw "解码后脚本 PARSE ERR: $($err[0])" }
Write-Host "[gen-launcher-bats] ✓ Round-trip PARSE OK ($($decoded.Length) chars)" -ForegroundColor Green
Write-Host "[gen-launcher-bats] ✓ PARSE OK ($($decoded.Length) chars)" -ForegroundColor Green
# 实跑验证 (v3.0.10: 截断到 & claude 之前, 只跑 PATH 修复 + DPAPI 加载, 不启动 claude)
# 这能抓出 PARSE 通过但运行时报错的 bug (例如 -or 被当 Test-Path 参数)
$runnable = $decoded -replace '& claude --dangerously-skip-permissions', 'Write-Host "__BW_DRYRUN_OK__"'
$tmpPs1 = Join-Path $env:TEMP "bw-launcher-dryrun-$(Get-Random).ps1"
Set-Content -Path $tmpPs1 -Value $runnable -Encoding UTF8
try {
$dryRunOutput = (& pwsh -NoProfile -ExecutionPolicy Bypass -File $tmpPs1 2>&1 | Out-String)
# 只抓真正的 PS 错误 (ErrorRecord / cannot be found / parameter name / 等)
$errorPatterns = @(
'cannot be found that matches parameter name',
'A parameter cannot be found',
'CommandNotFoundException',
'ParameterBindingException',
'is not recognized as',
'cannot find.*because it does not exist',
'RuntimeException'
)
$hasError = $false
foreach ($pat in $errorPatterns) {
if ($dryRunOutput -match $pat) { $hasError = $true; break }
}
# 必须看到 dry-run 成功标记才算通过
$reachedEnd = $dryRunOutput -match '__BW_DRYRUN_OK__'
if ($hasError -or -not $reachedEnd) {
Write-Host "[gen-launcher-bats] ✗ 实跑验证失败:" -ForegroundColor Red
Write-Host $dryRunOutput -ForegroundColor DarkRed
throw "Base64 解码后脚本运行时错误 (hasError=$hasError, reachedEnd=$reachedEnd)"
}
Write-Host "[gen-launcher-bats] ✓ 实跑通过 (dry-run 到达 __BW_DRYRUN_OK__ 标记)" -ForegroundColor Green
} finally {
Remove-Item $tmpPs1 -Force -EA SilentlyContinue
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long