feat(v3.0.8): Phase 1 引导性大升级 (PS7 硬核+per-dep 弹窗+总结+Claude 诊断)

用户反馈: v3.0.7 EXE 安装没有检测和引导 PS7/Claude Code. 根因:

B1: PS7 标记为'可选', 装失败只 Log-Info, 用户滚动错过.
    但启动器 bat 强依赖 pwsh, 降级 5.1 体验差 (粘贴多行命令被拆/Base64 截断).
B2: Core 依赖装失败只 Log-Fail 到 GUI, 末尾才一次 manualGuide.
B3: Claude Code 失败没具体诊断 ('npm 安装失败' 模糊无法报障).

修复:
  F1: PS7 升硬核 (最终检查含 pwsh, 缺失 exit 1 + manualGuide 含 winget+MSI)
  F2: foreach 内 Core 失败立即弹 Warning 专属窗 (winget/MSI/EXE/npm 全方案)
  F3: Phase 1 末尾总结 MessageBox (就绪/新装/失败分组, 只在有动作时弹)
  F4: Claude 失败写诊断到 bw-crash.log (node -v/npm -v/npm prefix/PATH)

EXE 212480 → 217088 bytes (+4608)
UX 对比: v3.0.7 用户滚动错过关键信息; v3.0.8 每步都有专属弹窗或总结
This commit is contained in:
bookworm 2026-04-24 22:13:02 +08:00
parent 5d16b7cfcf
commit 9fc7f1c9fa

View File

@ -48,7 +48,7 @@ trap {
}
# ─── 版本号 (每次更新递增, build.ps1 自动读取) ──────
$BWVersion = "3.0.7" # hotfix: 顶层 trap 防闪退 + Phase 5.5 regex 字面替换 + 可选步骤 try-catch 降级
$BWVersion = "3.0.8" # UX: PS7 升硬核 + per-dep 失败弹窗 + Phase 1 总结 + Claude 诊断增强
# DryRun 模式日志标记
if ($DryRun) { $global:BWDryRun = $DryRun } else { $global:BWDryRun = $null }
@ -939,11 +939,35 @@ foreach ($dep in $deps) {
elseif (-not $hasWinget) {
if ($dep.Core) {
Log-Fail "$($dep.Name) 需要手动安装 (winget 不可用)"
Show-MsgBox "$($dep.Name) 未安装且 winget 不可用。`n请手动安装后重新运行。`n`nNode.js: https://nodejs.org`nGit: https://git-scm.com" "缺少依赖" "OK" "Error"
# 失败专属弹窗由下面统一的 per-dep 失败检查处理
} else {
Log-Info "$($dep.Name) 未安装 (可选, 不影响核心功能)"
}
}
# v3.0.8: per-dep 失败立即弹专属 Warning (不阻断继续下一个 dep, Phase 1 末尾统一判断 exit)
# 场景: 3 种安装路径全挂 + Core=true → 给用户**具体**该 dep 的手动方案, 而非等到末尾总弹窗
if ($dep.Core -and -not (Test-Cmd $dep.Cmd)) {
$depFailMsg = "[$($dep.Name)] 自动安装失败`n`n"
$depFailMsg += "请手动安装 (任选一种):`n"
if ($dep.WingetId) {
$depFailMsg += "`n方式 A (winget): `n winget install --id $($dep.WingetId) --accept-source-agreements --accept-package-agreements`n"
}
if ($dep.MsiUrl) {
$depFailMsg += "`n方式 B (MSI 直链):`n 浏览器打开: $($dep.MsiUrl)`n 下载后双击安装, 全部选默认`n"
}
if ($dep.ExeUrl) {
$depFailMsg += "`n方式 B (EXE 直链):`n 浏览器打开: $($dep.ExeUrl)`n 下载后双击安装, 全部选默认`n"
}
if ($dep.NpmPkg) {
$depFailMsg += "`n方式 A (需 Node.js 就绪):`n 命令行: npm i -g $($dep.NpmPkg)`n"
}
if ($dep.ManualUrl) {
$depFailMsg += "`n参考下载页: $($dep.ManualUrl)`n"
}
$depFailMsg += "`n【装完后】重新双击 Bookworm-Setup.exe 即可继续 (已装的依赖会自动跳过)"
Show-MsgBox $depFailMsg "[$($dep.Name)] 需手动安装" "OK" "Warning"
}
}
}
@ -973,11 +997,33 @@ if ((Test-Cmd "git") -and -not (Test-Cmd "bash")) {
}
# Claude Code 依赖 npm, 需要在 Node.js 安装后再检查
# v3.0.8: 失败时写诊断到 bw-crash.log (node -v / npm -v / npm prefix / PATH 片段)
if (-not (Test-Cmd "claude") -and (Test-Cmd "npm")) {
Log-Info "安装 Claude Code..."
$r = Run-CmdWithUI "npm" @("i", "-g", "@anthropic-ai/claude-code") "安装 Claude Code (首次约 2 分钟)" 180000
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
if (Test-Cmd "claude") { Log-OK "Claude Code 安装成功" } else { Log-Fail "Claude Code 安装失败" }
# 补 npm 全局路径到 PATH (Windows 默认 %APPDATA%\npm)
$npmGlobal = "$env:APPDATA\npm"
if ((Test-Path $npmGlobal) -and ($env:Path -notlike "*$npmGlobal*")) { $env:Path = "$npmGlobal;$env:Path" }
if (Test-Cmd "claude") {
Log-OK "Claude Code 安装成功"
} else {
Log-Fail "Claude Code 安装失败"
# v3.0.8: 写具体诊断到 crash log, 便于用户报障 (不走 Show-MsgBox 避免双重打扰, 由 Phase 1 末尾统一弹)
try {
$nodeV = try { (& node --version 2>$null) } catch { "(node 不可用)" }
$npmV = try { (& npm --version 2>$null) } catch { "(npm 不可用)" }
$npmPrefix = try { (& npm config get prefix 2>$null) } catch { "(获取失败)" }
$pathNpm = ($env:Path -split ';') | Where-Object { $_ -match 'npm|nodejs' } | Out-String
$diag = "[$([DateTime]::Now.ToString('s'))] CLAUDE_INSTALL_FAIL`n"
$diag += " node -v : $nodeV`n"
$diag += " npm -v : $npmV`n"
$diag += " npm prefix : $npmPrefix`n"
$diag += " PATH (npm 相关):`n$pathNpm`n"
$diag | Out-File -FilePath $global:BWCrashLog -Append -Encoding UTF8 -EA SilentlyContinue
Bw-Log "INFO" "Claude Code 诊断已写入: $global:BWCrashLog"
} catch {}
}
}
# uv (Python 包管理器, 可选依赖) - 完全静默, 失败不阻断不弹窗
@ -1050,12 +1096,45 @@ if (Test-Cmd "uv") {
$opensslCmd = Find-OpenSSL
if ($opensslCmd) { Log-OK "OpenSSL: $opensslCmd" } else { Log-Warn "OpenSSL 未找到 (凭证解密可能失败)" }
# 最终检查 (仅 Node.js + Git + Claude Code 为硬性依赖, PowerShell 7 可选)
if (-not (Test-Cmd "node") -or -not (Test-Cmd "git") -or -not (Test-Cmd "claude")) {
# v3.0.8: Phase 1 总结弹窗 — 让用户一眼看懂全局, 避免 GUI 进度条滚动中遗漏关键信息
$coreList = @("Node.js", "Git", "PowerShell 7", "Claude Code")
$coreCmds = @{ "Node.js" = "node"; "Git" = "git"; "PowerShell 7" = "pwsh"; "Claude Code" = "claude" }
$summaryReady = @() # 已就绪 (本次未重装)
$summaryNew = @() # 本次自动安装成功
$summaryFail = @() # 本次自动安装失败
foreach ($name in $coreList) {
$cmd = $coreCmds[$name]
if (Test-Cmd $cmd) {
if ($installed -contains $name) { $summaryNew += $name }
else { $summaryReady += $name }
} else {
$summaryFail += $name
}
}
# 只在发生过自动安装时弹总结 (纯就绪无新动作不打扰用户)
if ($summaryNew.Count -gt 0 -or $summaryFail.Count -gt 0) {
$sum = "[Phase 1] 依赖环境检查完成`n`n"
if ($summaryReady.Count -gt 0) { $sum += "[OK] 已就绪: $($summaryReady -join ', ')`n" }
if ($summaryNew.Count -gt 0) { $sum += "[INSTALLED] 本次自动安装: $($summaryNew -join ', ')`n" }
if ($summaryFail.Count -gt 0) {
$sum += "[FAIL] 需手动处理: $($summaryFail -join ', ')`n"
$sum += "`n失败项已在前面弹窗给出手动方案; 装完重跑 EXE 即可继续."
} else {
$sum += "`n所有核心依赖就绪, 即将进入 Phase 2 网络诊断."
}
$icon = if ($summaryFail.Count -gt 0) { "Warning" } else { "Information" }
Show-MsgBox $sum "Phase 1 总结 — v$BWVersion" "OK" $icon
}
# v3.0.8: PS7 升为硬核依赖 (启动器 bat 强依赖 pwsh 的 -EncodedCommand + STA runspace 正确性,
# PS5.1 降级路径体验差: 粘贴多行命令被拆分 / Base64 可能被截断 / WinForms 兼容不一致)
# 最终检查 (Node.js + Git + Claude Code + PowerShell 7 全部为硬性依赖)
if (-not (Test-Cmd "node") -or -not (Test-Cmd "git") -or -not (Test-Cmd "claude") -or -not (Test-Cmd "pwsh")) {
$missing = @()
if (-not (Test-Cmd "node")) { $missing += "Node.js" }
if (-not (Test-Cmd "git")) { $missing += "Git" }
if (-not (Test-Cmd "claude")) { $missing += "Claude Code" }
if (-not (Test-Cmd "pwsh")) { $missing += "PowerShell 7" }
# v3.0.5: 清除桌面僵尸快捷方式, 防用户点击空快捷方式触发 'claude.exe not found'
try {
@ -1086,6 +1165,12 @@ if (-not (Test-Cmd "node") -or -not (Test-Cmd "git") -or -not (Test-Cmd "claude"
$manualGuide += "3. Claude Code (需先装好 Node.js)`n"
$manualGuide += " 命令行执行: npm i -g @anthropic-ai/claude-code`n`n"
}
if ($missing -contains "PowerShell 7") {
$manualGuide += "4. PowerShell 7 (启动器 bat + Base64 启动链路必需)`n"
$manualGuide += " 方式 A: winget install --id Microsoft.PowerShell`n"
$manualGuide += " 方式 B: 下载 MSI https://github.com/PowerShell/PowerShell/releases/latest`n"
$manualGuide += " 选 PowerShell-7.x.x-win-x64.msi → 双击安装`n`n"
}
$manualGuide += "【完成后】`n"
$manualGuide += " 重新双击 Bookworm-Setup.exe 即可继续安装`n"
$manualGuide += " (已装成功的依赖会被自动跳过)`n`n"
@ -1097,9 +1182,6 @@ if (-not (Test-Cmd "node") -or -not (Test-Cmd "git") -or -not (Test-Cmd "claude"
Show-MsgBox $manualGuide "安装中断 — 手动安装指引" "OK" "Error"
exit 1
}
if (-not (Test-Cmd "pwsh")) {
Log-Info "PowerShell 7 未安装 (可选, 用系统 PowerShell 5.1 替代)"
}
# 定位 pwsh.exe 完整路径 (供后续 settings.json 配置使用)
$PwshPath = (Get-Command pwsh -ErrorAction SilentlyContinue).Source