visionA/local-tool/scripts/bootstrap-windows.ps1
jim800121chen 8cd5751ce3 feat(local-tool): M8 重構 — Wails 控制台 + 瀏覽器 Web UI(R5 決策)
依 R5 五輪決策把 visionA-local 從「Wails 內嵌 Next.js」重構為「Wails
本機伺服器控制台 + 瀏覽器 Web UI」模式(類比 Docker Desktop / Ollama)。

程式碼變動
  - M8-1 砍 yt-dlp 全套(後端 resolver / URL handler / 前端 URL tab /
    Makefile vendor / installer / bootstrap / CI workflow,-555 行)
  - M8-2 砍 Mock 模式全套(driver/mock、mock_camera、Settings runtimeMode、
    VISIONA_MOCK 環境變數,-528 行)
  - M8-3 ffmpeg 從 GPL 切換到 LGPL 混合方案:Windows/Linux 用 BtbN 現成
    LGPL binary,macOS 自 build minimal decoder-only 進 git
    (vendor/ffmpeg/macos/ffmpeg 5.7MB + ffprobe 5.6MB,比 GPL 版省 85% 空間)
  - M8-4 Wails Server Controller:state machine、log ring buffer 2000 行、
    preferences.json atomic write、boot-id、Gin SkipPaths、shutdown 7+1 秒、
    notify_*.go 三平台 OS 通知、watchServer 改 Error state 不 os.Exit
  - M8-4b 啟動階段管線 R5-E:6 階段進度 event、20s soft / 60s hard timeout、
    stage 5/6 skip 規則、sentinel file、RestartStartupSequence 5 步驟
  - M8-5 Wails 控制台 vanilla HTML/JS/CSS(9 檔 ~2012 行)取代 M7-B splash:
    state 視覺、log panel、startup progress panel、Stage 6 manual CTA
    pulse、shutdown modal、Settings、Dark Mode、i18n 中英雙語
  - M8-6 上傳影片副檔名擴充(mp4/avi/mov/mpeg/mpg)
  - M8-7 Web UI Server Offline Overlay(role=alertdialog + focus trap +
    wsEverConnected 容錯 + Page Visibility)
  - M8-8 CORS middleware(127.0.0.1/localhost only + suffix attack 防護)+
    ws/origin.go 獨立 WebSocket CheckOrigin 避 package cycle
  - MAJ-4 server:shutdown-imminent WebSocket broadcast 機制
    (/ws/system endpoint + notifyShutdownImminent helper)
  - M8-9 Boot-ID + 瀏覽器 tab 自動重連(sessionStorage loop guard)

品質
  - ~105+ 新 unit test + race detector (-count=2) 全綠
  - 10 個 milestone 全部通過 Reviewer 審查
  - 三方 v2 + v2.1 文件(PRD / Design Spec / TDD)+ 交叉互審紀錄
    收錄在 .autoflow/

交付前待處理(M8-10)
  - 重跑 make payload-macos 把舊 GPL 77MB binary 換成新 LGPL
  - 三平台 end-to-end build 驗證

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:57:54 +08:00

247 lines
9.9 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# visionA-local — Windows 10/11 x86_64 一鍵 build
#
# 使用方式PowerShell 以「系統管理員」開啟):
# git clone https://github.com/jim800121/visionA.git
# cd visionA\local-tool
# powershell -ExecutionPolicy Bypass -File scripts\bootstrap-windows.ps1
#
# 環境變數:
# $env:VISIONA_TARGET 預設 exe可設 wails-windows / payload-windows
$ErrorActionPreference = 'Stop'
$Target = if ($env:VISIONA_TARGET) { $env:VISIONA_TARGET } else { 'exe' }
function Log($msg) { Write-Host "==> $msg" -ForegroundColor Cyan }
function Fail($msg) { Write-Host "!!! $msg" -ForegroundColor Red; exit 1 }
# 必須以系統管理員身份執行winget 需要)
$isAdmin = ([Security.Principal.WindowsPrincipal] `
[Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole] 'Administrator')
if (-not $isAdmin) { Fail '請以系統管理員身份執行 PowerShell 再跑此腳本' }
if (-not (Test-Path 'Makefile') -or -not (Test-Path 'visiona-local')) {
Fail '請先 cd 到 local-tool\ 目錄再執行此腳本'
}
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
Fail 'winget 未安裝。請先從 Microsoft Store 安裝「App Installer」'
}
function Ensure-Winget($id) {
$installed = winget list --id $id -e 2>$null | Select-String $id
if (-not $installed) {
Log "安裝 $id"
winget install -e --id $id --accept-source-agreements --accept-package-agreements
} else {
Log "$id 已安裝,跳過"
}
}
Log '[1/4] 安裝系統套件'
Ensure-Winget 'Git.Git'
Ensure-Winget 'GoLang.Go'
Ensure-Winget 'OpenJS.NodeJS.LTS'
Ensure-Winget 'Python.Python.3.12'
Ensure-Winget 'JRSoftware.InnoSetup'
Ensure-Winget 'MSYS2.MSYS2'
# 重新載入 PATHwinget 裝完目前 session 拿不到)
$env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + `
[System.Environment]::GetEnvironmentVariable('Path','User')
$env:Path += ";$HOME\go\bin;C:\msys64\usr\bin;C:\msys64\mingw64\bin"
Log '[2/4] 安裝 pnpm'
if (-not (Get-Command pnpm -ErrorAction SilentlyContinue)) {
npm i -g pnpm
}
Log '[3/4] 安裝 Wails CLI + 確認 MSYS2 make'
if (-not (Get-Command wails -ErrorAction SilentlyContinue)) {
go install github.com/wailsapp/wails/v2/cmd/wails@latest
}
wails doctor
if (-not (Test-Path 'C:\msys64\usr\bin\make.exe')) {
Log '安裝 MSYS2 make'
& 'C:\msys64\usr\bin\bash.exe' -lc 'pacman -Sy --noconfirm make'
}
Log "[4/4] 開始 buildtarget=$Target"
Log 'ffmpeg 使用 LGPL v3 buildv2 TDD §3BtbN n7.1 LGPL'
# 讓 MSYS2 bash 繼承 Windows PATH才找得到 go / pnpm / python / wails
$env:MSYS2_PATH_TYPE = 'inherit'
$env:CHERE_INVOKING = '1'
# 找真實的 Python避開 Microsoft Store 的 WindowsApps stub
$realPython = $null
$pyCandidates = @(
"$env:LOCALAPPDATA\Programs\Python\Python312\python.exe",
"$env:LOCALAPPDATA\Programs\Python\Python313\python.exe",
"$env:ProgramFiles\Python312\python.exe",
"$env:ProgramFiles\Python313\python.exe"
)
foreach ($p in $pyCandidates) {
if (Test-Path $p) { $realPython = $p; break }
}
if (-not $realPython) {
# 試 py launcher
$pyLauncher = Get-Command py -ErrorAction SilentlyContinue
if ($pyLauncher) { $realPython = 'py -3' }
}
if (-not $realPython) {
Fail '找不到真實 Pythonwinget 可能沒裝成功,或 PATH 沒更新)。請重開 PowerShell 再試一次'
}
Log "偵測到 Python: $realPython"
# 把 Windows 路徑轉成 MSYS2 bash 能吃的格式
function Convert-ToMsysPath($winPath) {
if ($winPath -match '^[A-Za-z]:\\') {
return '/' + $winPath.Substring(0,1).ToLower() + '/' + ($winPath.Substring(3) -replace '\\','/')
}
return $winPath
}
$msysPython = Convert-ToMsysPath $realPython
# 找 Inno Setup Compiler (ISCC.exe)
# 重要:檔名是 ISCC.exe大寫winget 裝在 Program Files (x86) 不在 PATH 裡
function Find-Iscc {
# 1. 固定路徑system + user scope
$candidates = @(
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
"$env:ProgramFiles\Inno Setup 6\ISCC.exe",
"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe",
"$env:USERPROFILE\AppData\Local\Programs\Inno Setup 6\ISCC.exe",
"${env:ProgramFiles(x86)}\Inno Setup 5\ISCC.exe"
)
foreach ($p in $candidates) {
if ($p -and (Test-Path $p)) { return $p }
}
# 2. 登錄檔 Uninstall keyHKLM + HKCU 都看)
$regPaths = @(
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1'
)
foreach ($rp in $regPaths) {
$item = Get-ItemProperty $rp -ErrorAction SilentlyContinue
if ($item) {
if ($item.InstallLocation) {
$p = Join-Path $item.InstallLocation 'ISCC.exe'
if (Test-Path $p) { return $p }
}
if ($item.'Inno Setup: App Path') {
$p = Join-Path $item.'Inno Setup: App Path' 'ISCC.exe'
if (Test-Path $p) { return $p }
}
}
}
# 3. 遞迴掃 Program Files + user AppData
$scanRoots = @(
"${env:ProgramFiles(x86)}",
"$env:ProgramFiles",
"$env:LOCALAPPDATA\Programs"
) | Where-Object { $_ -and (Test-Path $_) }
foreach ($root in $scanRoots) {
$found = Get-ChildItem -Path $root -Filter 'ISCC.exe' -Recurse `
-ErrorAction SilentlyContinue -Force |
Select-Object -First 1
if ($found) { return $found.FullName }
}
return $null
}
$isccPath = Find-Iscc
if (-not $isccPath) {
Log 'Inno Setup 偵測不到,用 winget 重裝一次'
winget install -e --id JRSoftware.InnoSetup --accept-source-agreements --accept-package-agreements --force
$isccPath = Find-Iscc
}
if ($isccPath) {
Log "偵測到 Inno Setup: $isccPath"
$msysIsccDir = Convert-ToMsysPath (Split-Path $isccPath -Parent)
$msysIsccExe = Convert-ToMsysPath $isccPath
} else {
Log 'WARN: 仍然找不到 Inno Setupiscc.exemake exe 步驟會失敗'
$msysIsccDir = $null
$msysIsccExe = $null
}
# Makefile 需要 bash + make透過 MSYS2 bash 執行
# 將 Windows 路徑 C:\foo\bar 轉成 MSYS2 路徑 /c/foo/bar
$projectPath = (Get-Location).Path
$msysPath = '/' + $projectPath.Substring(0,1).ToLower() + '/' + `
($projectPath.Substring(3) -replace '\\','/')
$bashParts = @(
"cd '$msysPath'",
"export VISIONA_PYTHON='$msysPython'"
)
if ($msysIsccDir) {
$bashParts += "export PATH=`"$msysIsccDir`":`$PATH"
}
if ($msysIsccExe) {
$bashParts += "export ISCC=`"$msysIsccExe`""
}
# Build 模式:
# VISIONA_FAST=1 → 前置產物齊全時跳過 vendor/payload/wails只重跑 isccdebug iteration 用)
# 預設 → 每次 clean buildwails build / server binary / frontend embed 全重做)
# 保留 vendor/ 快取Python runtime / wheels / ffmpeg以免重下
$fastPath = (Test-Path 'visiona-local\build\bin\visiona-local.exe') -and `
(Test-Path 'payload\windows\bin\visiona-local-server.exe') -and `
(Test-Path 'payload\windows\bin\ffmpeg.exe') -and `
(Test-Path 'payload\windows\python\python.tar.gz')
if ($env:VISIONA_FAST -eq '1' -and $fastPath -and ($Target -eq 'exe' -or -not $env:VISIONA_TARGET)) {
Log 'FAST PATHVISIONA_FAST=1 + 前置產物齊全,跳過 vendor/payload/wails只重跑 iscc'
$bashParts += 'make exe-only'
} else {
Log '預設 clean build每次重做 wails + server binary + frontend embedvendor cache 保留)'
# 清 wails / frontend / server/web/out但不清 vendor/(避免重下 Python / wheels / ffmpeg
$bashParts += 'rm -rf visiona-local/build/bin visiona-local/build/windows/Resources'
$bashParts += 'rm -rf frontend/out frontend/.next server/web/out'
$bashParts += 'rm -rf payload/windows/bin/visiona-local-server.exe'
$bashParts += 'rm -rf dist/visiona-local-*-windows-x64.exe'
$bashParts += 'make vendor-python-windows vendor-wheels-windows vendor-ffmpeg-windows'
$bashParts += 'make payload-windows'
switch ($Target) {
'payload-windows' { }
'wails-windows' { $bashParts += 'make wails-windows' }
default { $bashParts += 'make wails-windows'; $bashParts += 'make exe' }
}
}
# 把 bash 指令寫成 script 檔再執行,避開 PS → bash 多層 quoting 地獄
# (尤其是含空格的路徑如 "C:\Program Files (x86)\Inno Setup 6\"
$bashScriptLines = @('#!/usr/bin/env bash', 'set -e', '')
foreach ($line in $bashParts) {
$bashScriptLines += $line
}
$bashScriptContent = ($bashScriptLines -join "`n") + "`n"
$tmpScript = Join-Path (Get-Location) '.visiona-build.sh'
# 用 UTF8 無 BOM + LF 換行bash 才吃得下
[System.IO.File]::WriteAllText($tmpScript, $bashScriptContent, (New-Object System.Text.UTF8Encoding $false))
Log "bash 執行腳本內容:"
Get-Content $tmpScript | ForEach-Object { Write-Host " $_" }
$msysScript = Convert-ToMsysPath $tmpScript
& 'C:\msys64\usr\bin\bash.exe' -l $msysScript
$buildRc = $LASTEXITCODE
Remove-Item $tmpScript -ErrorAction SilentlyContinue
if ($buildRc -ne 0) { Fail "build 失敗exit code=$buildRc" }
# 驗證 dist 真的有 .exe 產出exe target 是 phonymake 成功 != 產物存在)
if ($Target -eq 'exe' -or $Target -eq 'default' -or -not $Target) {
$exeFiles = Get-ChildItem dist -Filter 'visiona-local-*-windows-x64.exe' -ErrorAction SilentlyContinue
if (-not $exeFiles) {
Fail "make exe 沒報錯,但 dist\ 下沒有 visiona-local-*-windows-x64.exe。往上捲看 iscc 輸出找原因,或直接跑:
& `"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe`" installer\windows\visiona-local.iss"
}
}
Log '完成 ✅'
Log "產出位置:$(Join-Path (Get-Location) 'dist')"
Get-ChildItem dist -ErrorAction SilentlyContinue | Format-Table Name, Length