#Requires -Version 5.1 <# .SYNOPSIS Meeting Assistant - Windows 一鍵打包腳本 .DESCRIPTION 在 Windows 環境下一鍵打包 Sidecar + Electron 成免安裝 exe .EXAMPLE .\scripts\build-all.ps1 .\scripts\build-all.ps1 -ApiUrl "http://192.168.1.100:8000/api" .\scripts\build-all.ps1 -SkipSidecar .\scripts\build-all.ps1 -Clean #> param( [string]$ApiUrl, [switch]$SkipSidecar, [switch]$Clean, [switch]$Help ) $ErrorActionPreference = "Stop" # 顏色輸出函數 function Write-Step { param($msg) Write-Host "[STEP] $msg" -ForegroundColor Cyan } function Write-OK { param($msg) Write-Host "[OK] $msg" -ForegroundColor Green } function Write-Warn { param($msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow } function Write-Err { param($msg) Write-Host "[ERROR] $msg" -ForegroundColor Red } # 路徑設定 $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $ProjectDir = Split-Path -Parent $ScriptDir $SidecarDir = Join-Path $ProjectDir "sidecar" $ClientDir = Join-Path $ProjectDir "client" $BuildDir = Join-Path $ProjectDir "build" function Show-Banner { Write-Host "" Write-Host "==========================================" -ForegroundColor Cyan Write-Host " Meeting Assistant - Windows Builder" -ForegroundColor Cyan Write-Host " 一鍵打包 Electron + Sidecar" -ForegroundColor Cyan Write-Host "==========================================" -ForegroundColor Cyan Write-Host "" } function Show-Help { Write-Host @" Meeting Assistant - Windows 一鍵打包腳本 用法: .\build-all.ps1 [參數] 參數: -ApiUrl URL 後端 API URL (預設: http://localhost:8000/api) -SkipSidecar 跳過 Sidecar 打包(如果已經打包過) -Clean 打包前清理所有建置目錄 -Help 顯示此幫助訊息 範例: .\build-all.ps1 # 完整打包 (使用預設 localhost) .\build-all.ps1 -ApiUrl "http://192.168.1.100:8000/api" # 指定後端 URL .\build-all.ps1 -ApiUrl "https://api.example.com/api" # 使用公司伺服器 .\build-all.ps1 -Clean # 清理後打包 .\build-all.ps1 -SkipSidecar # 僅打包 Electron "@ } function Update-Config { param([string]$NewApiUrl) Write-Step "更新設定檔..." $configPath = Join-Path $ClientDir "config.json" if (Test-Path $configPath) { $config = Get-Content $configPath -Raw | ConvertFrom-Json if ($NewApiUrl) { $config.apiBaseUrl = $NewApiUrl Write-Host " API URL: $NewApiUrl" -ForegroundColor Gray } $config | ConvertTo-Json -Depth 10 | Set-Content $configPath -Encoding UTF8 Write-OK "設定檔已更新" } else { Write-Warn "找不到 config.json,使用預設設定" } } function Test-Prerequisites { Write-Step "檢查建置環境..." # 檢查 Python try { $pyVersion = python --version 2>&1 Write-OK "Python: $pyVersion" } catch { Write-Err "Python 未安裝,請安裝 Python 3.10+" exit 1 } # 檢查 Node.js try { $nodeVersion = node --version 2>&1 Write-OK "Node.js: $nodeVersion" } catch { Write-Err "Node.js 未安裝,請安裝 Node.js 18+" exit 1 } # 檢查 npm try { $npmVersion = npm --version 2>&1 Write-OK "npm: $npmVersion" } catch { Write-Err "npm 未安裝" exit 1 } } function Clear-BuildDirs { Write-Step "清理建置目錄..." $dirsToClean = @( $BuildDir, (Join-Path $ClientDir "dist"), (Join-Path $SidecarDir "dist"), (Join-Path $SidecarDir "build") ) foreach ($dir in $dirsToClean) { if (Test-Path $dir) { Remove-Item -Recurse -Force $dir Write-Host " 已刪除: $dir" -ForegroundColor Gray } } # 刪除 spec 檔案 Get-ChildItem -Path $SidecarDir -Filter "*.spec" | Remove-Item -Force Write-OK "清理完成" } function Build-Sidecar { Write-Step "打包 Sidecar (Python → exe)..." Write-Host " 這可能需要 5-10 分鐘..." -ForegroundColor Gray Push-Location $SidecarDir try { # 建立/啟動虛擬環境 if (-not (Test-Path "venv")) { Write-Host " 建立虛擬環境..." -ForegroundColor Gray python -m venv venv } # 啟動虛擬環境並安裝依賴 & ".\venv\Scripts\Activate.ps1" Write-Host " 安裝依賴..." -ForegroundColor Gray pip install --upgrade pip -q pip install -r requirements.txt -q pip install pyinstaller -q # 建立 dist 目錄 if (-not (Test-Path "dist")) { New-Item -ItemType Directory -Path "dist" | Out-Null } Write-Host " 執行 PyInstaller..." -ForegroundColor Gray # PyInstaller 打包 pyinstaller ` --onedir ` --name transcriber ` --distpath dist ` --workpath build ` --noconfirm ` --clean ` --console ` --hidden-import=faster_whisper ` --hidden-import=ctranslate2 ` --hidden-import=huggingface_hub ` --hidden-import=tokenizers ` --hidden-import=onnxruntime ` --hidden-import=opencc ` --hidden-import=pydub ` --hidden-import=numpy ` --hidden-import=av ` --collect-data=onnxruntime ` --collect-data=faster_whisper ` transcriber.py if (Test-Path "dist\transcriber\transcriber.exe") { Write-OK "Sidecar 打包完成" } else { Write-Err "Sidecar 打包失敗" exit 1 } } finally { Pop-Location } } function Build-Electron { Write-Step "打包 Electron 應用..." Push-Location $ClientDir try { # 檢查 Sidecar 是否存在 $sidecarExe = Join-Path $SidecarDir "dist\transcriber\transcriber.exe" if (-not (Test-Path $sidecarExe)) { Write-Err "找不到 Sidecar: $sidecarExe" Write-Warn "請先執行完整打包或移除 -SkipSidecar 參數" exit 1 } # 總是執行 npm install 確保依賴完整 Write-Host " 安裝 npm 依賴..." -ForegroundColor Gray npm install if ($LASTEXITCODE -ne 0) { Write-Err "npm install 失敗" exit 1 } # 確認 electron-builder 已安裝 $electronBuilderPath = Join-Path $ClientDir "node_modules\.bin\electron-builder.cmd" if (-not (Test-Path $electronBuilderPath)) { Write-Err "electron-builder 未安裝" Write-Warn "請確認 package.json 中的 devDependencies 包含 electron-builder" exit 1 } # 建立 .env 如果不存在 if (-not (Test-Path ".env") -and (Test-Path ".env.example")) { Copy-Item ".env.example" ".env" Write-Warn "已建立 .env,請確認設定" } # 清理可能損壞的 electron-builder 快取(解決 symlink 問題) $ebCache = Join-Path $env:LOCALAPPDATA "electron-builder\Cache\winCodeSign" if (Test-Path $ebCache) { Write-Host " 清理 electron-builder 快取..." -ForegroundColor Gray Remove-Item -Recurse -Force $ebCache -ErrorAction SilentlyContinue } Write-Host " 執行 electron-builder..." -ForegroundColor Gray # 直接執行 node_modules 中的 electron-builder & $electronBuilderPath --win if ($LASTEXITCODE -ne 0) { Write-Err "electron-builder 執行失敗" Write-Warn "如果出現 symlink 錯誤,請嘗試以下方案:" Write-Warn " 1. 以系統管理員身分執行此腳本" Write-Warn " 2. 或啟用 Windows 開發人員模式(設定 > 更新與安全性 > 開發人員專用)" exit 1 } if (Test-Path "dist\*.exe") { Write-OK "Electron 打包完成" } else { Write-Err "Electron 打包失敗 - dist 目錄中找不到 exe 檔案" exit 1 } } finally { Pop-Location } } function Copy-Output { Write-Step "整合輸出..." if (-not (Test-Path $BuildDir)) { New-Item -ItemType Directory -Path $BuildDir | Out-Null } # 複製 exe 檔案 $exeFiles = Get-ChildItem -Path (Join-Path $ClientDir "dist") -Filter "*.exe" foreach ($file in $exeFiles) { Copy-Item $file.FullName -Destination $BuildDir Write-Host " 複製: $($file.Name)" -ForegroundColor Gray } Write-Host "" Write-Host "==========================================" -ForegroundColor Cyan Write-Host " 打包完成!" -ForegroundColor Green Write-Host "==========================================" -ForegroundColor Cyan Write-Host "" Write-Host " 輸出目錄: $BuildDir" -ForegroundColor White Write-Host "" # 列出輸出檔案 Get-ChildItem -Path $BuildDir | ForEach-Object { $size = [math]::Round($_.Length / 1MB, 2) Write-Host " $($_.Name) ($size MB)" -ForegroundColor Gray } Write-Host "" Write-Host " 使用說明:" -ForegroundColor Yellow Write-Host " 直接執行 .exe 檔案即可,無需安裝" -ForegroundColor Gray Write-Host "" } # 主程式 if ($Help) { Show-Help exit 0 } Show-Banner Test-Prerequisites if ($Clean) { Clear-BuildDirs } # Update config.json if API URL is specified if ($ApiUrl) { Update-Config -NewApiUrl $ApiUrl } if (-not $SkipSidecar) { Build-Sidecar } Build-Electron Copy-Output