<# .SYNOPSIS 一键打包脚本。 .DESCRIPTION 默认产出「绿色免安装版」到 dist\qiweimanager\(不需要 NSIS,解压即用): 1. 定位 Go / Wails 工具链并注入 PATH。 2. 32 位编译 helper.exe;编译 silk 音频解码器。 3. wails build 生成主程序 qiweimanager.exe(前端一并编译)。 4. 汇总主程序 + helper + DLL + 工具 + 默认配置到 dist 目录。 指定 -Installer 时,额外调用现有的 scripts\package-windows.ps1 走 NSIS 生成安装包 (需本机已安装 NSIS)。 .PARAMETER Installer 额外生成 NSIS 安装包(委托 package-windows.ps1,需要 makensis)。 .PARAMETER SkipTests 跳过 go test。 .PARAMETER GoPath / WailsPath 手动指定工具路径(自动探测失败时使用)。 .EXAMPLE powershell -ExecutionPolicy Bypass -File scripts\build.ps1 powershell -ExecutionPolicy Bypass -File scripts\build.ps1 -Installer #> [CmdletBinding()] param( [switch]$Installer, [switch]$SkipTests, [string]$GoPath, [string]$WailsPath ) $ErrorActionPreference = "Stop" $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $repoRoot = (Resolve-Path (Join-Path $scriptDir "..")).Path Set-Location $repoRoot function Write-Step([string]$msg) { Write-Host "==> $msg" -ForegroundColor Cyan } function Write-Ok([string]$msg) { Write-Host " $msg" -ForegroundColor Green } function Write-Warn2([string]$msg){ Write-Host " $msg" -ForegroundColor Yellow } function Resolve-Go { param([string]$Hint) $candidates = @() if ($Hint) { $candidates += $Hint } $cmd = Get-Command go.exe -ErrorAction SilentlyContinue if ($cmd) { $candidates += $cmd.Source } $candidates += "$env:LOCALAPPDATA\Programs\Go\bin\go.exe", "C:\Program Files\Go\bin\go.exe", "C:\Go\bin\go.exe" foreach ($c in $candidates) { if ($c -and (Test-Path $c)) { return (Resolve-Path $c).Path } } throw "未找到 Go。请安装 Go 1.24+ 后重试,或用 -GoPath 指定。" } function Resolve-Wails { param([string]$Hint) $candidates = @() if ($Hint) { $candidates += $Hint } $cmd = Get-Command wails.exe -ErrorAction SilentlyContinue if ($cmd) { $candidates += $cmd.Source } $candidates += "$env:USERPROFILE\go\bin\wails.exe" foreach ($c in $candidates) { if ($c -and (Test-Path $c)) { return (Resolve-Path $c).Path } } throw "未找到 Wails CLI。请先运行 scripts\dev.ps1 安装,或用 -WailsPath 指定。" } function Copy-Required { param([string]$Source, [string]$Destination) if (-not (Test-Path -LiteralPath $Source)) { throw "缺少资源: $Source" } $dir = Split-Path -Parent $Destination if ($dir -and -not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null } Copy-Item -LiteralPath $Source -Destination $Destination -Force } # ---- 委托 NSIS 安装包构建 ---- if ($Installer) { Write-Step "委托 package-windows.ps1 生成 NSIS 安装包" $pkg = Join-Path $scriptDir "package-windows.ps1" $pkgArgs = @{} if ($SkipTests) { $pkgArgs["SkipTests"] = $true } if ($WailsPath) { $pkgArgs["WailsPath"] = $WailsPath } & $pkg @pkgArgs return } # ---- 工具链 ---- Write-Step "定位 Go / Wails 工具链" $go = Resolve-Go -Hint $GoPath $goBin = Split-Path -Parent $go $env:PATH = "$goBin;$env:USERPROFILE\go\bin;$env:PATH" $wails = Resolve-Wails -Hint $WailsPath Write-Ok "Go: $go" Write-Ok "Wails: $wails" $goCache = Join-Path $repoRoot ".gocache" New-Item -ItemType Directory -Force -Path $goCache | Out-Null $env:GOCACHE = $goCache $binDir = Join-Path $repoRoot "build\bin" $distDir = Join-Path $repoRoot "dist\qiweimanager" # ---- 测试 ---- if (-not $SkipTests) { Write-Step "运行 Go 测试 (go test ./...)" & $go test ./... Write-Ok "测试通过" } # ---- 结束运行中的旧进程,避免覆盖失败 ---- $running = Get-Process -Name "qiweimanager", "qiweimanager-dev", "helper", "helper_auto_reply" -ErrorAction SilentlyContinue foreach ($p in $running) { Write-Warn2 "结束运行中的进程: $($p.ProcessName) ($($p.Id))"; Stop-Process -Id $p.Id -Force } New-Item -ItemType Directory -Force -Path $binDir | Out-Null # ---- 32 位 helper.exe ---- Write-Step "编译 32 位 helper.exe" $helperOut = Join-Path $binDir "helper.exe" $oldArch = $env:GOARCH try { $env:GOARCH = "386" Push-Location (Join-Path $repoRoot "helper") & $go build -trimpath -ldflags "-H windowsgui -s -w" -o $helperOut . if ($LASTEXITCODE -ne 0) { throw "helper.exe 编译失败 (exit $LASTEXITCODE)" } } finally { Pop-Location if ($null -eq $oldArch) { Remove-Item Env:GOARCH -ErrorAction SilentlyContinue } else { $env:GOARCH = $oldArch } } Write-Ok "helper.exe 完成" # ---- silk 音频解码器(尽力而为,需要 cgo/gcc;缺失则跳过,仅影响语音消息转码)---- Write-Step "编译 silk 音频解码器" $silkOut = Join-Path $binDir "tools\audio\silkdecode.exe" $silkBuilt = $false $hasGcc = [bool](Get-Command gcc -ErrorAction SilentlyContinue) if (-not $hasGcc) { Write-Warn2 "未检测到 gcc,silkdecode 依赖 cgo 无法编译,跳过。语音消息自动转码功能在此构建中不可用。" Write-Warn2 "如需启用:安装 MinGW-w64(如 winget install -e --id mstorsjo.llvm-mingw 或 choco install mingw),再重新打包。" } else { New-Item -ItemType Directory -Force -Path (Split-Path $silkOut) | Out-Null Push-Location (Join-Path $repoRoot "tools\audio\silkdecode") try { $oldCgo = $env:CGO_ENABLED $env:CGO_ENABLED = "1" & $go build -trimpath -ldflags "-s -w" -o $silkOut . if ($LASTEXITCODE -eq 0 -and (Test-Path $silkOut)) { $silkBuilt = $true; Write-Ok "silkdecode.exe 完成" } else { Write-Warn2 "silkdecode 编译失败,已跳过(不影响主程序)。" } } finally { Pop-Location if ($null -eq $oldCgo) { Remove-Item Env:CGO_ENABLED -ErrorAction SilentlyContinue } else { $env:CGO_ENABLED = $oldCgo } } } # ---- 主程序 + 前端 ---- Write-Step "构建主程序 (wails build)" & $wails build -trimpath -webview2 embed if ($LASTEXITCODE -ne 0) { throw "wails build 失败 (exit $LASTEXITCODE)" } $mainExe = Join-Path $binDir "qiweimanager.exe" if (-not (Test-Path $mainExe)) { throw "主程序未生成: $mainExe" } Write-Ok "qiweimanager.exe 完成" # ---- 汇总绿色免安装版 ---- $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $distDirWithTime = Join-Path $repoRoot "dist\qiweimanager_$timestamp" Write-Step "汇总绿色免安装版到 dist\qiweimanager_$timestamp" if (Test-Path $distDirWithTime) { Remove-Item $distDirWithTime -Recurse -Force } New-Item -ItemType Directory -Force -Path $distDirWithTime | Out-Null New-Item -ItemType Directory -Force -Path (Join-Path $distDirWithTime "config\knowledge") | Out-Null New-Item -ItemType Directory -Force -Path (Join-Path $distDirWithTime "config\materials") | Out-Null $mainExeWithTime = "qiweimanager_$timestamp.exe" Copy-Required -Source $mainExe -Destination (Join-Path $distDirWithTime $mainExeWithTime) Copy-Required -Source $helperOut -Destination (Join-Path $distDirWithTime "helper.exe") if ($silkBuilt) { Copy-Required -Source $silkOut -Destination (Join-Path $distDirWithTime "tools\audio\silkdecode.exe") } else { Write-Warn2 "dist 不含 silkdecode.exe(语音转码功能不可用)。" } Copy-Required -Source (Join-Path $repoRoot "Helper_4.1.33.6009.dll") -Destination (Join-Path $distDirWithTime "Helper_4.1.33.6009.dll") Copy-Required -Source (Join-Path $repoRoot "Loader_4.1.33.6009.dll") -Destination (Join-Path $distDirWithTime "Loader_4.1.33.6009.dll") Copy-Item -LiteralPath (Join-Path $repoRoot "requestdata") -Destination (Join-Path $distDirWithTime "requestdata") -Recurse -Force Copy-Item -LiteralPath (Join-Path $repoRoot "eventdata") -Destination (Join-Path $distDirWithTime "eventdata") -Recurse -Force # 默认配置(不含真实密钥) $defaultConfig = @' { "callbackConfig": { "callbackUrl": "", "callbackToken": "", "httpPort": "10001", "enableCallback": false, "enableCloudAuth": false, "fileUploadUrl": "", "deviceCode": "" }, "lastUpdated": 0 } '@ $utf8NoBom = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText((Join-Path $distDirWithTime "config\config.json"), $defaultConfig, $utf8NoBom) [System.IO.File]::WriteAllText((Join-Path $distDirWithTime "config\client_status.json"), "{}", $utf8NoBom) [System.IO.File]::WriteAllText((Join-Path $distDirWithTime "config\materials\materials.json"), '{"materials":[]}', $utf8NoBom) Write-Host "" Write-Step "打包完成" Write-Ok "绿色免安装版: $distDirWithTime" Write-Ok "主程序: $mainExeWithTime" Write-Ok "直接进入该目录双击 $mainExeWithTime 即可运行。" Write-Host " 如需 NSIS 安装包,请安装 NSIS 后运行: scripts\build.ps1 -Installer" -ForegroundColor DarkGray