Backend: - Add setup-backend.sh/bat for one-click backend setup - Fix test_auth.py mock settings (JWT_EXPIRE_HOURS) - Fix test_excel_export.py TEMPLATE_DIR reference Frontend: - Add config.json for runtime API URL configuration - Add init.js and settings.js for config loading - Update main.js to load config from external file - Update api.js to use dynamic API_BASE_URL - Update all pages to initialize config before API calls - Update package.json with extraResources for config Build: - Add build-client.sh/bat for packaging Electron + Sidecar - Add build-all.ps1 PowerShell script with -ApiUrl parameter - Add GitHub Actions workflow for Windows builds - Add scripts/README.md documentation This allows IT to configure backend URL without rebuilding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
474 lines
12 KiB
Bash
Executable File
474 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Meeting Assistant Client - 打包腳本
|
|
# 將 Electron 應用與 Python Sidecar 打包成免安裝 exe
|
|
#
|
|
# 依賴:
|
|
# - Node.js 18+
|
|
# - Python 3.10+
|
|
# - PyInstaller (會自動安裝)
|
|
#
|
|
|
|
set -e
|
|
|
|
# 顏色定義
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
# 專案路徑
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
CLIENT_DIR="$PROJECT_DIR/client"
|
|
SIDECAR_DIR="$PROJECT_DIR/sidecar"
|
|
BUILD_DIR="$PROJECT_DIR/build"
|
|
|
|
# 預設配置
|
|
TARGET_PLATFORM="win" # win, mac, linux
|
|
SKIP_SIDECAR=false
|
|
CLEAN_BUILD=false
|
|
|
|
# 函數:印出訊息
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[OK]${NC} $1"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
log_step() {
|
|
echo -e "${CYAN}[STEP]${NC} $1"
|
|
}
|
|
|
|
# 函數:顯示標題
|
|
show_banner() {
|
|
echo ""
|
|
echo -e "${CYAN}=========================================="
|
|
echo " Meeting Assistant Client Builder"
|
|
echo " 打包 Electron + Sidecar 為免安裝執行檔"
|
|
echo -e "==========================================${NC}"
|
|
echo ""
|
|
}
|
|
|
|
# 函數:檢查環境
|
|
check_environment() {
|
|
log_step "檢查建置環境..."
|
|
|
|
local all_ok=true
|
|
|
|
# 檢查 Node.js
|
|
if command -v node &> /dev/null; then
|
|
local node_version=$(node --version)
|
|
log_success "Node.js: $node_version"
|
|
else
|
|
log_error "Node.js 未安裝"
|
|
all_ok=false
|
|
fi
|
|
|
|
# 檢查 npm
|
|
if command -v npm &> /dev/null; then
|
|
local npm_version=$(npm --version)
|
|
log_success "npm: $npm_version"
|
|
else
|
|
log_error "npm 未安裝"
|
|
all_ok=false
|
|
fi
|
|
|
|
# 檢查 Python
|
|
local python_cmd=""
|
|
for cmd in python3 python; do
|
|
if command -v $cmd &> /dev/null; then
|
|
local version=$($cmd -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
|
local major=$($cmd -c 'import sys; print(sys.version_info.major)')
|
|
local minor=$($cmd -c 'import sys; print(sys.version_info.minor)')
|
|
|
|
if [ "$major" -ge 3 ] && [ "$minor" -ge 10 ]; then
|
|
python_cmd=$cmd
|
|
log_success "Python: $version ($cmd)"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [ -z "$python_cmd" ]; then
|
|
log_error "需要 Python 3.10+"
|
|
all_ok=false
|
|
fi
|
|
|
|
if [ "$all_ok" = false ]; then
|
|
log_error "環境檢查失敗,請安裝缺少的依賴"
|
|
exit 1
|
|
fi
|
|
|
|
echo "$python_cmd"
|
|
}
|
|
|
|
# 函數:清理建置目錄
|
|
clean_build() {
|
|
log_step "清理建置目錄..."
|
|
|
|
rm -rf "$BUILD_DIR"
|
|
rm -rf "$CLIENT_DIR/dist"
|
|
rm -rf "$SIDECAR_DIR/dist"
|
|
rm -rf "$SIDECAR_DIR/build"
|
|
rm -rf "$SIDECAR_DIR/*.spec"
|
|
|
|
log_success "建置目錄已清理"
|
|
}
|
|
|
|
# 函數:設置 Sidecar 虛擬環境
|
|
setup_sidecar_venv() {
|
|
local python_cmd=$1
|
|
|
|
log_step "設置 Sidecar 建置環境..."
|
|
|
|
cd "$SIDECAR_DIR"
|
|
|
|
# 創建或使用現有虛擬環境
|
|
if [ ! -d "venv" ]; then
|
|
log_info "創建虛擬環境..."
|
|
$python_cmd -m venv venv
|
|
fi
|
|
|
|
# 啟動虛擬環境
|
|
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
|
|
source venv/Scripts/activate
|
|
else
|
|
source venv/bin/activate
|
|
fi
|
|
|
|
# 安裝依賴
|
|
log_info "安裝 Sidecar 依賴..."
|
|
pip install --upgrade pip -q
|
|
pip install -r requirements.txt -q
|
|
|
|
# 安裝 PyInstaller
|
|
log_info "安裝 PyInstaller..."
|
|
pip install pyinstaller -q
|
|
|
|
log_success "Sidecar 建置環境就緒"
|
|
}
|
|
|
|
# 函數:打包 Sidecar
|
|
build_sidecar() {
|
|
log_step "打包 Sidecar (Python → 獨立執行檔)..."
|
|
|
|
cd "$SIDECAR_DIR"
|
|
|
|
# 啟動虛擬環境
|
|
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
|
|
source venv/Scripts/activate
|
|
else
|
|
source venv/bin/activate
|
|
fi
|
|
|
|
# 確保 dist 目錄存在
|
|
mkdir -p dist
|
|
|
|
# PyInstaller 打包參數
|
|
local pyinstaller_args=(
|
|
--onedir # 打包成目錄 (比 onefile 啟動快)
|
|
--name transcriber # 輸出名稱
|
|
--distpath dist # 輸出目錄
|
|
--workpath build # 工作目錄
|
|
--specpath . # spec 檔案位置
|
|
--noconfirm # 覆蓋現有檔案
|
|
--clean # 清理暫存檔
|
|
--log-level WARN # 日誌等級
|
|
)
|
|
|
|
# 根據平台添加特定參數
|
|
if [[ "$TARGET_PLATFORM" == "win" ]]; then
|
|
pyinstaller_args+=(--console) # Windows 保留控制台 (用於 stdio 通訊)
|
|
fi
|
|
|
|
# 收集 faster-whisper 的隱藏導入
|
|
pyinstaller_args+=(
|
|
--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
|
|
)
|
|
|
|
# 收集 onnxruntime 資料
|
|
pyinstaller_args+=(
|
|
--collect-data=onnxruntime
|
|
--collect-data=faster_whisper
|
|
)
|
|
|
|
log_info "執行 PyInstaller..."
|
|
log_info "這可能需要幾分鐘..."
|
|
|
|
pyinstaller "${pyinstaller_args[@]}" transcriber.py
|
|
|
|
if [ -d "dist/transcriber" ]; then
|
|
log_success "Sidecar 打包完成: $SIDECAR_DIR/dist/transcriber"
|
|
else
|
|
log_error "Sidecar 打包失敗"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# 函數:安裝前端依賴
|
|
setup_client() {
|
|
log_step "設置前端建置環境..."
|
|
|
|
cd "$CLIENT_DIR"
|
|
|
|
if [ ! -d "node_modules" ]; then
|
|
log_info "安裝前端依賴..."
|
|
npm install
|
|
else
|
|
log_info "前端依賴已安裝"
|
|
fi
|
|
|
|
# 確保 .env 存在
|
|
if [ ! -f ".env" ]; then
|
|
if [ -f ".env.example" ]; then
|
|
cp .env.example .env
|
|
log_warn "已創建 .env 檔案,請確認設定"
|
|
fi
|
|
fi
|
|
|
|
log_success "前端建置環境就緒"
|
|
}
|
|
|
|
# 函數:打包 Electron
|
|
build_electron() {
|
|
log_step "打包 Electron 應用..."
|
|
|
|
cd "$CLIENT_DIR"
|
|
|
|
# 根據目標平台設置建置參數
|
|
local build_args=()
|
|
|
|
case $TARGET_PLATFORM in
|
|
win)
|
|
build_args+=(--win)
|
|
log_info "目標平台: Windows (Portable)"
|
|
;;
|
|
mac)
|
|
build_args+=(--mac)
|
|
log_info "目標平台: macOS (DMG)"
|
|
;;
|
|
linux)
|
|
build_args+=(--linux)
|
|
log_info "目標平台: Linux (AppImage)"
|
|
;;
|
|
*)
|
|
log_error "不支援的平台: $TARGET_PLATFORM"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
log_info "執行 electron-builder..."
|
|
npm run build -- "${build_args[@]}"
|
|
|
|
if [ -d "dist" ]; then
|
|
log_success "Electron 打包完成"
|
|
log_info "輸出目錄: $CLIENT_DIR/dist"
|
|
else
|
|
log_error "Electron 打包失敗"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# 函數:整合輸出
|
|
finalize_build() {
|
|
log_step "整合建置輸出..."
|
|
|
|
mkdir -p "$BUILD_DIR"
|
|
|
|
# 複製 Electron 輸出
|
|
if [ -d "$CLIENT_DIR/dist" ]; then
|
|
cp -r "$CLIENT_DIR/dist/"* "$BUILD_DIR/" 2>/dev/null || true
|
|
fi
|
|
|
|
# 顯示結果
|
|
echo ""
|
|
echo -e "${CYAN}=========================================="
|
|
echo " 建置完成"
|
|
echo -e "==========================================${NC}"
|
|
echo ""
|
|
echo " 輸出目錄: $BUILD_DIR"
|
|
echo ""
|
|
|
|
# 列出輸出檔案
|
|
if command -v tree &> /dev/null; then
|
|
tree -L 2 "$BUILD_DIR" 2>/dev/null || ls -la "$BUILD_DIR"
|
|
else
|
|
ls -la "$BUILD_DIR"
|
|
fi
|
|
|
|
echo ""
|
|
log_success "打包完成!"
|
|
echo ""
|
|
|
|
# 平台特定說明
|
|
case $TARGET_PLATFORM in
|
|
win)
|
|
echo " Windows 使用說明:"
|
|
echo " 1. 找到 dist/ 中的 .exe 檔案"
|
|
echo " 2. 直接執行即可,無需安裝"
|
|
echo ""
|
|
;;
|
|
mac)
|
|
echo " macOS 使用說明:"
|
|
echo " 1. 打開 dist/ 中的 .dmg 檔案"
|
|
echo " 2. 將應用拖到 Applications 資料夾"
|
|
echo ""
|
|
;;
|
|
linux)
|
|
echo " Linux 使用說明:"
|
|
echo " 1. 賦予 .AppImage 執行權限: chmod +x *.AppImage"
|
|
echo " 2. 直接執行 AppImage 檔案"
|
|
echo ""
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# 函數:顯示幫助
|
|
show_help() {
|
|
echo ""
|
|
echo "Meeting Assistant Client - 打包腳本"
|
|
echo ""
|
|
echo "用法: $0 [命令] [選項]"
|
|
echo ""
|
|
echo "命令:"
|
|
echo " build 完整建置 (Sidecar + Electron)"
|
|
echo " sidecar 僅打包 Sidecar"
|
|
echo " electron 僅打包 Electron (需先打包 Sidecar)"
|
|
echo " clean 清理建置目錄"
|
|
echo " help 顯示此幫助訊息"
|
|
echo ""
|
|
echo "選項:"
|
|
echo " --platform PLATFORM 目標平台: win, mac, linux (預設: win)"
|
|
echo " --skip-sidecar 跳過 Sidecar 打包"
|
|
echo " --clean 建置前先清理"
|
|
echo ""
|
|
echo "範例:"
|
|
echo " $0 build # 完整建置 Windows 版本"
|
|
echo " $0 build --platform linux # 建置 Linux 版本"
|
|
echo " $0 sidecar # 僅打包 Sidecar"
|
|
echo " $0 electron --skip-sidecar # 僅打包 Electron"
|
|
echo ""
|
|
echo "注意:"
|
|
echo " - Windows 打包需在 Windows 環境執行"
|
|
echo " - macOS 打包需在 macOS 環境執行"
|
|
echo " - Linux 打包可在 Linux 或 WSL 環境執行"
|
|
echo ""
|
|
}
|
|
|
|
# 主程式
|
|
main() {
|
|
local command=${1:-"help"}
|
|
|
|
# 解析參數
|
|
shift || true
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--platform)
|
|
TARGET_PLATFORM="$2"
|
|
shift 2
|
|
;;
|
|
--skip-sidecar)
|
|
SKIP_SIDECAR=true
|
|
shift
|
|
;;
|
|
--clean)
|
|
CLEAN_BUILD=true
|
|
shift
|
|
;;
|
|
*)
|
|
log_error "未知參數: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
case $command in
|
|
build)
|
|
show_banner
|
|
|
|
local python_cmd=$(check_environment)
|
|
|
|
if [ "$CLEAN_BUILD" = true ]; then
|
|
clean_build
|
|
fi
|
|
|
|
if [ "$SKIP_SIDECAR" = false ]; then
|
|
setup_sidecar_venv "$python_cmd"
|
|
build_sidecar
|
|
fi
|
|
|
|
setup_client
|
|
build_electron
|
|
finalize_build
|
|
;;
|
|
sidecar)
|
|
show_banner
|
|
local python_cmd=$(check_environment)
|
|
|
|
if [ "$CLEAN_BUILD" = true ]; then
|
|
rm -rf "$SIDECAR_DIR/dist"
|
|
rm -rf "$SIDECAR_DIR/build"
|
|
fi
|
|
|
|
setup_sidecar_venv "$python_cmd"
|
|
build_sidecar
|
|
;;
|
|
electron)
|
|
show_banner
|
|
check_environment > /dev/null
|
|
|
|
# 檢查 Sidecar 是否已打包
|
|
if [ ! -d "$SIDECAR_DIR/dist/transcriber" ] && [ "$SKIP_SIDECAR" = false ]; then
|
|
log_warn "Sidecar 尚未打包"
|
|
log_info "請先執行: $0 sidecar"
|
|
log_info "或使用 --skip-sidecar 跳過"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$CLEAN_BUILD" = true ]; then
|
|
rm -rf "$CLIENT_DIR/dist"
|
|
fi
|
|
|
|
setup_client
|
|
build_electron
|
|
finalize_build
|
|
;;
|
|
clean)
|
|
clean_build
|
|
;;
|
|
help|--help|-h)
|
|
show_help
|
|
;;
|
|
*)
|
|
log_error "未知命令: $command"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# 執行主程式
|
|
main "$@"
|