bookworm-boot/bw-launch.ps1

164 lines
6.3 KiB
PowerShell
Raw Permalink Normal View History

<#
.SYNOPSIS
Bookworm 启动 wrapper (v3.1.0 引入)
.DESCRIPTION
桌面 .lnk 调用此 wrapper 而非直调 claude.ps1, 让启动逻辑可热修复 ( wrapper 不需重 bake .lnk).
职责:
1. 动态查找 claude.ps1 真实路径 (npm config get prefix 兜底候选)
2. claude.ps1 stale (npm uninstall/换版本/ prefix) 弹清晰 GUI 引导, 不再"快捷方式失效"
3. 失败时不静默, 写日志 + GUI 弹窗
.lnk 的契约:
.lnk Args = -NoLogo -NoExit -ExecutionPolicy Bypass -File "<bw-launch.ps1 绝对路径>"
--dangerously-skip-permissions
$args[0..N] 转发给 claude.ps1
.NOTES
v3.1.0 (2026-04-25) 引入 wrapper 模式 (闭合 L4 局限: bake claude.ps1 路径 stale)
v3.1.2 (2026-04-26) nvm/fnm/volta shim 探测 (闭合 L9)
分发: Phase 7 安装时复制到 $BootDir\bw-launch.ps1
#>
$ErrorActionPreference = "Continue"
$bwLaunchLog = Join-Path $env:TEMP "bw-launch.log"
function Write-BwLaunchLog {
param([string]$Level, [string]$Msg)
try {
$line = "[$([DateTime]::Now.ToString('yyyy-MM-dd HH:mm:ss'))] [$Level] $Msg"
$line | Out-File -FilePath $bwLaunchLog -Append -Encoding UTF8 -EA SilentlyContinue
} catch {}
}
# v3.1.2 L12: log rotation — >1MB 归档, 保留最近 3 个 .bak
foreach ($logName in @("bw-launch.log", "bw-crash.log", "bw-phase4-validate.log", "bw-doctor.log")) {
$logPath = Join-Path $env:TEMP $logName
try {
if ((Test-Path $logPath) -and ((Get-Item $logPath -EA SilentlyContinue).Length -gt 1MB)) {
$ts = [DateTime]::Now.ToString('yyyyMMdd-HHmmss')
Copy-Item $logPath "$logPath.bak.$ts" -Force -EA Stop
Remove-Item $logPath -Force -EA Stop
# 保留最近 3 个 .bak, 删旧的
$baks = Get-ChildItem "$logPath.bak.*" -EA SilentlyContinue | Sort-Object LastWriteTime -Descending
if ($baks.Count -gt 3) { $baks | Select-Object -Skip 3 | Remove-Item -Force -EA SilentlyContinue }
}
} catch {}
}
function Show-LaunchError {
param([string]$Title, [string]$Body)
Write-BwLaunchLog "ERROR" "$Title :: $Body"
Write-Host ""
Write-Host " [!] $Title" -ForegroundColor Red
Write-Host ""
Write-Host $Body -ForegroundColor Yellow
Write-Host ""
Write-Host " 日志: $bwLaunchLog" -ForegroundColor Gray
Write-Host ""
try {
Add-Type -AssemblyName System.Windows.Forms -EA Stop
[System.Windows.Forms.MessageBox]::Show("$Body`n`n详情见: $bwLaunchLog", "Bookworm 启动失败 — $Title", 'OK', 'Error') | Out-Null
} catch {}
Read-Host "按回车关闭"
}
Write-BwLaunchLog "INFO" "bw-launch wrapper 启动 args=$($args -join ' ')"
# ── 1. PATH 三层重载 (即便桌面 .lnk 不依赖 PATH, 子 claude.ps1 调 node 仍依赖) ──
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
try {
$npmPrefix = (& npm config get prefix 2>$null | Select-Object -First 1).Trim()
if ($npmPrefix -and (Test-Path $npmPrefix) -and ($env:Path -notlike "*$npmPrefix*")) {
$env:Path = "$npmPrefix;$env:Path"
}
} catch {
Write-BwLaunchLog "WARN" "npm config get prefix 失败: $_"
}
foreach ($p in @("$env:APPDATA\npm", "$env:ProgramFiles\nodejs", "$env:LOCALAPPDATA\npm")) {
if ((Test-Path $p) -and ($env:Path -notlike "*$p*")) {
$env:Path = "$p;$env:Path"
}
}
# v3.1.2 L9: nvm/fnm/volta shim 探测 (版本管理器装的 node 不在默认 PATH)
$shimPaths = @()
if ($env:NVM_HOME) { $shimPaths += "$env:NVM_HOME"; $shimPaths += (Join-Path $env:NVM_HOME "nodejs") }
if ($env:NVM_SYMLINK) { $shimPaths += $env:NVM_SYMLINK }
if ($env:FNM_DIR) { $shimPaths += (Join-Path $env:FNM_DIR "aliases\default") }
if ($env:FNM_MULTISHELL_PATH) { $shimPaths += $env:FNM_MULTISHELL_PATH }
if ($env:VOLTA_HOME) { $shimPaths += (Join-Path $env:VOLTA_HOME "bin") }
foreach ($sp in $shimPaths) {
if ($sp -and (Test-Path $sp) -and ($env:Path -notlike "*$sp*")) {
$env:Path = "$sp;$env:Path"
Write-BwLaunchLog "INFO" "shim PATH 补充: $sp"
}
}
# ── 2. 动态定位 claude.ps1 ──
$claudePs1 = $null
# 优先 Get-Command (PATH 已重载)
$claudeCmd = Get-Command claude -ErrorAction SilentlyContinue
if ($claudeCmd -and $claudeCmd.Source -and $claudeCmd.Source.EndsWith('.ps1') -and (Test-Path $claudeCmd.Source)) {
$claudePs1 = $claudeCmd.Source
Write-BwLaunchLog "INFO" "claude.ps1 from Get-Command: $claudePs1"
}
# 兜底 npm config get prefix
if (-not $claudePs1) {
try {
$candidate = Join-Path $npmPrefix "claude.ps1"
if (Test-Path $candidate) {
$claudePs1 = $candidate
Write-BwLaunchLog "INFO" "claude.ps1 from npm prefix: $claudePs1"
}
} catch {}
}
# 最终硬编码兜底
if (-not $claudePs1) {
foreach ($p in @("$env:APPDATA\npm\claude.ps1", "$env:ProgramFiles\nodejs\claude.ps1", "$env:LOCALAPPDATA\npm\claude.ps1")) {
if (Test-Path $p) {
$claudePs1 = $p
Write-BwLaunchLog "INFO" "claude.ps1 from hardcoded: $claudePs1"
break
}
}
}
if (-not $claudePs1 -or -not (Test-Path $claudePs1)) {
$diag = "未找到 claude.ps1 文件.`n`n"
$diag += "诊断信息:`n"
$diag += " npm config get prefix: $(if ($npmPrefix) { $npmPrefix } else { '(查询失败)' })`n"
$diag += " PATH 中 npm/nodejs 片段:`n"
foreach ($p in (($env:Path -split ';') | Where-Object { $_ -match 'npm|nodejs' })) {
$diag += " $p`n"
}
$diag += "`n修复方案:`n"
$diag += " 方案 A: 命令行运行 npm i -g @anthropic-ai/claude-code`n"
$diag += " 方案 B: 重新双击 Bookworm-Setup.exe 修复安装"
Show-LaunchError "Claude Code 未找到" $diag
exit 1
}
# ── 3. 启动 claude.ps1 + 转发 $args (--dangerously-skip-permissions 等) ──
Write-BwLaunchLog "INFO" "调用 claude.ps1 args=$($args -join ' ')"
try {
& $claudePs1 @args
$exitCode = $LASTEXITCODE
Write-BwLaunchLog "INFO" "claude.ps1 退出码: $exitCode"
if ($exitCode -ne 0) {
Write-Host ""
Write-Host " [!] Claude 进程退出码: $exitCode" -ForegroundColor Yellow
Write-Host " 日志: $bwLaunchLog" -ForegroundColor Gray
}
exit $exitCode
} catch {
Show-LaunchError "Claude 启动异常" "异常信息: $($_.Exception.Message)"
exit 1
}