name: Release on: push: tags: - 'v*' permissions: contents: write jobs: release: runs-on: ${{ matrix.os }} strategy: matrix: include: - os: windows-latest - os: ubuntu-latest - os: macos-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Setup Rust uses: dtolnay/rust-toolchain@stable - name: Add macOS targets if: runner.os == 'macOS' run: | rustup target add aarch64-apple-darwin x86_64-apple-darwin - name: Install Linux system deps if: runner.os == 'Linux' shell: bash run: | set -euxo pipefail sudo apt-get update # Core build tools and pkg-config sudo apt-get install -y --no-install-recommends \ build-essential \ pkg-config \ curl \ wget \ file \ patchelf \ libssl-dev # GTK/GLib stack for gdk-3.0, glib-2.0, gio-2.0 sudo apt-get install -y --no-install-recommends \ libgtk-3-dev \ librsvg2-dev \ libayatana-appindicator3-dev # WebKit2GTK (version differs across Ubuntu images; try 4.1 then 4.0) sudo apt-get install -y --no-install-recommends libwebkit2gtk-4.1-dev \ || sudo apt-get install -y --no-install-recommends libwebkit2gtk-4.0-dev # libsoup also changed major version; prefer 3.0 with fallback to 2.4 sudo apt-get install -y --no-install-recommends libsoup-3.0-dev \ || sudo apt-get install -y --no-install-recommends libsoup2.4-dev - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: 10.12.3 run_install: false - name: Get pnpm store directory id: pnpm-store shell: bash run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT - name: Setup pnpm cache uses: actions/cache@v3 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: ${{ runner.os }}-pnpm-store- - name: Install frontend deps run: pnpm install --frozen-lockfile - name: Prepare Tauri signing key shell: bash run: | # 调试:检查 Secret 是否存在 if [ -z "${{ secrets.TAURI_PRIVATE_KEY }}" ]; then echo "❌ TAURI_PRIVATE_KEY Secret 为空或不存在" >&2 echo "请检查 GitHub 仓库 Settings > Secrets and variables > Actions" >&2 exit 1 fi RAW="${{ secrets.TAURI_PRIVATE_KEY }}" # 如果是原始两行(以 untrusted comment: 开头) if echo "$RAW" | head -n1 | grep -q '^untrusted comment:'; then printf '%s' "$RAW" > "$RUNNER_TEMP/tauri.key" echo "✅ 使用原始格式密钥" # 否则尝试当作 Base64 解码恢复两行 elif printf '%s' "$RAW" | base64 -d > "$RUNNER_TEMP/tauri.key" 2>/dev/null \ && head -n1 "$RUNNER_TEMP/tauri.key" | grep -q '^untrusted comment:'; then echo "✅ 成功解码 Base64 格式密钥" else echo "❌ TAURI_SIGNING_PRIVATE_KEY 格式不对:需要两行文本且首行是 'untrusted comment:'" >&2 echo "密钥前10个字符: $(echo "$RAW" | head -c 10)..." >&2 exit 1 fi echo "✅ Tauri signing key prepared" - name: Build Tauri App (macOS) if: runner.os == 'macOS' env: TAURI_SIGNING_PRIVATE_KEY_PATH: ${{ runner.temp }}/tauri.key TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: pnpm tauri build --target universal-apple-darwin - name: Build Tauri App (Windows) if: runner.os == 'Windows' env: TAURI_SIGNING_PRIVATE_KEY_PATH: ${{ runner.temp }}/tauri.key TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: pnpm tauri build - name: Build Tauri App (Linux) if: runner.os == 'Linux' env: TAURI_SIGNING_PRIVATE_KEY_PATH: ${{ runner.temp }}/tauri.key TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: pnpm tauri build - name: Prepare macOS Assets if: runner.os == 'macOS' shell: bash run: | set -euxo pipefail mkdir -p release-assets echo "Looking for .app bundle..." APP_PATH="" for path in \ "src-tauri/target/release/bundle/macos" \ "src-tauri/target/universal-apple-darwin/release/bundle/macos" \ "src-tauri/target/aarch64-apple-darwin/release/bundle/macos" \ "src-tauri/target/x86_64-apple-darwin/release/bundle/macos"; do if [ -d "$path" ]; then APP_PATH=$(find "$path" -name "*.app" -type d | head -1) [ -n "$APP_PATH" ] && break fi done if [ -z "$APP_PATH" ]; then echo "No .app found" >&2 exit 1 fi APP_DIR=$(dirname "$APP_PATH") APP_NAME=$(basename "$APP_PATH") cd "$APP_DIR" # 使用 ditto 打包更兼容资源分叉 ditto -c -k --sequesterRsrc --keepParent "$APP_NAME" "CC-Switch-macOS.zip" mv "CC-Switch-macOS.zip" "$GITHUB_WORKSPACE/release-assets/" echo "macOS zip ready" - name: Prepare Windows Assets if: runner.os == 'Windows' shell: pwsh run: | $ErrorActionPreference = 'Stop' New-Item -ItemType Directory -Force -Path release-assets | Out-Null # 安装器(优先 NSIS,其次 MSI) $installer = Get-ChildItem -Path 'src-tauri/target/release/bundle' -Recurse -Include *.exe,*.msi -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match '\\bundle\\(nsis|msi)\\' } | Select-Object -First 1 if ($null -ne $installer) { $dest = if ($installer.Extension -ieq '.msi') { 'CC-Switch-Setup.msi' } else { 'CC-Switch-Setup.exe' } Copy-Item $installer.FullName (Join-Path release-assets $dest) Write-Host "Installer copied: $dest" } else { Write-Warning 'No Windows installer found' } # 绿色版(portable):仅可执行文件 $exeCandidates = @( 'src-tauri/target/release/cc-switch.exe', 'src-tauri/target/x86_64-pc-windows-msvc/release/cc-switch.exe' ) $exePath = $exeCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 if ($null -ne $exePath) { $portableDir = 'release-assets/CC-Switch-Portable' New-Item -ItemType Directory -Force -Path $portableDir | Out-Null Copy-Item $exePath $portableDir Compress-Archive -Path "$portableDir/*" -DestinationPath 'release-assets/CC-Switch-Windows-Portable.zip' -Force Remove-Item -Recurse -Force $portableDir Write-Host 'Windows portable zip created' } else { Write-Warning 'Portable exe not found' } - name: Prepare Linux Assets if: runner.os == 'Linux' shell: bash run: | set -euxo pipefail mkdir -p release-assets # 仅上传安装包(deb) DEB=$(find src-tauri/target/release/bundle -name "*.deb" | head -1 || true) if [ -n "$DEB" ]; then cp "$DEB" release-assets/ echo "Deb package copied" else echo "No .deb found" >&2 exit 1 fi - name: List prepared assets shell: bash run: | ls -la release-assets || true - name: Collect Signatures shell: bash run: | # 查找并复制签名文件到 release-assets find src-tauri/target -name "*.sig" -type f 2>/dev/null | while read sig; do cp "$sig" release-assets/ || true done echo "Collected signatures:" ls -la release-assets/*.sig || echo "No signatures found" - name: Upload Release Assets uses: softprops/action-gh-release@v1 with: tag_name: ${{ github.ref_name }} name: CC Switch ${{ github.ref_name }} body: | ## CC Switch ${{ github.ref_name }} Claude Code 供应商切换工具 ### 下载 - macOS: `CC-Switch-macOS.zip`(解压即用) - Windows: `CC-Switch-Setup.exe` 或 `CC-Switch-Setup.msi`(安装版);`CC-Switch-Windows-Portable.zip`(绿色版) - Linux: `*.deb`(Debian/Ubuntu 安装包) --- 提示:macOS 如遇“已损坏”提示,可在终端执行:`xattr -cr "/Applications/CC Switch.app"` files: release-assets/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: List generated bundles (debug) if: always() shell: bash run: | echo "Listing bundles in src-tauri/target..." find src-tauri/target -maxdepth 4 -type f -name "*.*" 2>/dev/null || true