name: QMAI Multi-Platform Release on: push: tags: - "v*" workflow_dispatch: inputs: release_tag: description: "目标 Release Tag(手动触发时上传 Linux 版本到指定 Release)" required: true default: "v2.2.31" permissions: contents: write jobs: build-release: name: Build ${{ matrix.display_label }} if: github.event_name == 'push' runs-on: ${{ matrix.runner }} env: CARGO_PROFILE_RELEASE_LTO: "false" CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16" CARGO_PROFILE_RELEASE_OPT_LEVEL: "1" strategy: fail-fast: false matrix: include: - label: windows-x64 display_label: Windows x64 runner: windows-latest rust_targets: "" tauri_args: "--bundles nsis" pdfium_asset: "" pdfium_library: "" artifact_globs: | src-tauri/target/**/release/bundle/nsis/*.exe src-tauri/target/**/release/bundle/nsis/*.exe.sig - label: macos-aarch64 display_label: macOS Apple Silicon runner: macos-latest rust_targets: "aarch64-apple-darwin" tauri_args: "--target aarch64-apple-darwin --bundles dmg,app --no-sign" pdfium_asset: "pdfium-mac-arm64.tgz" pdfium_library: "libpdfium.dylib" artifact_globs: | src-tauri/target/**/release/bundle/dmg/*.dmg release-assets/*.app.tar.gz - label: macos-intel display_label: macOS Intel runner: macos-15-intel rust_targets: "x86_64-apple-darwin" tauri_args: "--target x86_64-apple-darwin --bundles dmg,app --no-sign" pdfium_asset: "pdfium-mac-x64.tgz" pdfium_library: "libpdfium.dylib" artifact_globs: | src-tauri/target/**/release/bundle/dmg/*.dmg release-assets/*.app.tar.gz - label: linux-x64 display_label: Linux x64 runner: ubuntu-22.04 rust_targets: "" tauri_args: "--bundles appimage,deb" pdfium_asset: "pdfium-linux-x64.tgz" pdfium_library: "libpdfium.so" artifact_globs: | src-tauri/target/**/release/bundle/appimage/*.AppImage src-tauri/target/**/release/bundle/deb/*.deb steps: - name: Checkout uses: actions/checkout@v4 - name: Install Rust stable if: matrix.rust_targets == '' uses: dtolnay/rust-toolchain@stable - name: Install Rust stable with targets if: matrix.rust_targets != '' uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.rust_targets }} - name: Rust cache uses: Swatinem/rust-cache@v2 with: workspaces: src-tauri - name: Install Linux dependencies if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y \ build-essential \ curl \ file \ libayatana-appindicator3-dev \ librsvg2-dev \ libssl-dev \ libwebkit2gtk-4.1-dev \ libxdo-dev \ patchelf \ protobuf-compiler \ wget \ xdg-utils - name: Install macOS dependencies if: runner.os == 'macOS' run: brew install protobuf - name: Install Windows dependencies if: runner.os == 'Windows' uses: arduino/setup-protoc@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install PDFium binary if: matrix.pdfium_asset != '' shell: bash run: | set -euo pipefail mkdir -p "$RUNNER_TEMP/pdfium" src-tauri/pdfium curl -L --fail --retry 5 --retry-delay 2 \ -o "$RUNNER_TEMP/pdfium.tgz" \ "https://github.com/bblanchon/pdfium-binaries/releases/latest/download/${{ matrix.pdfium_asset }}" tar -xzf "$RUNNER_TEMP/pdfium.tgz" -C "$RUNNER_TEMP/pdfium" cp "$RUNNER_TEMP/pdfium/lib/${{ matrix.pdfium_library }}" "src-tauri/pdfium/${{ matrix.pdfium_library }}" ls -la src-tauri/pdfium - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: npm - name: Install frontend dependencies run: npm ci - name: Prepare release notes shell: bash run: | set -euo pipefail version="$(node -p "require('./package.json').version")" mkdir -p release-assets node scripts/release-notes.mjs "$version" --out release-assets/release-notes.txt - name: Ensure GitHub Release exists env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | set -euo pipefail tag="${GITHUB_REF_NAME}" notes_path="release-assets/release-notes.txt" if ! gh release view "$tag" >/dev/null 2>&1; then gh release create "$tag" --title "QMAI $tag" --notes-file "$notes_path" --verify-tag || true fi gh release edit "$tag" --notes-file "$notes_path" - name: Build Tauri app env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: npx tauri build ${{ matrix.tauri_args }} - name: Publish Windows updater manifest if: matrix.label == 'windows-x64' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} shell: pwsh run: | $ErrorActionPreference = "Stop" $version = node -p "require('./package.json').version" $bundleDir = "src-tauri/target/release/bundle/nsis" $installer = Get-ChildItem $bundleDir -File -Filter "*.exe" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if (-not $installer) { throw "未找到 Windows 安装包" } $signaturePath = "$($installer.FullName).sig" if (-not (Test-Path $signaturePath)) { npx tauri signer sign $installer.FullName if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } if (-not (Test-Path $signaturePath)) { throw "Windows 安装包签名生成失败:$signaturePath" } $releaseDir = "release-assets" New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null $assetName = "QMaiWrite_$($version)_windows_X64$($installer.Extension)" $assetPath = Join-Path $releaseDir $assetName $assetSignaturePath = "$assetPath.sig" Copy-Item $installer.FullName $assetPath -Force $signature = (Get-Content $signaturePath -Raw).Trim() Copy-Item $signaturePath $assetSignaturePath -Force $notesPath = Join-Path $releaseDir "release-notes.txt" $notes = Get-Content -Path $notesPath -Raw -Encoding utf8 $latest = [ordered]@{ version = $version notes = "QMAI $version 发布版本" pub_date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ", [System.Globalization.CultureInfo]::InvariantCulture) platforms = [ordered]@{ "windows-x86_64" = [ordered]@{ signature = $signature url = "https://github.com/Mochocyang/QMAI/releases/latest/download/$assetName" } } } $latestPath = Join-Path $releaseDir "latest.json" $latest.notes = $notes $latest | ConvertTo-Json -Depth 5 | Set-Content -Path $latestPath -Encoding utf8 gh release edit "${{ github.ref_name }}" --notes-file $notesPath gh release upload "${{ github.ref_name }}" $assetPath $assetSignaturePath $latestPath --clobber - name: Package macOS release assets if: runner.os == 'macOS' shell: bash run: | set -euo pipefail version="$(node -p "require('./package.json').version")" if [ "${{ matrix.label }}" = "macos-intel" ]; then system_label="macOS_Intel" else system_label="macOS_AppleSilicon" fi mkdir -p release-assets dmg_path="$(find src-tauri/target -path '*/release/bundle/dmg/*.dmg' -print -quit)" if [ -n "$dmg_path" ]; then cp "$dmg_path" "release-assets/QMaiWrite_${version}_${system_label}.dmg" fi app_path="$(find src-tauri/target -path '*/release/bundle/macos/*.app' -print -quit)" if [ -z "$app_path" ]; then echo "No .app bundle found" exit 1 fi tar -C "$(dirname "$app_path")" -czf "release-assets/QMaiWrite_${version}_${system_label}.app.tar.gz" "$(basename "$app_path")" - name: Package Linux release assets if: runner.os == 'Linux' shell: bash run: | set -euo pipefail version="$(node -p "require('./package.json').version")" mkdir -p release-assets appimage_path="$(find src-tauri/target -path '*/release/bundle/appimage/*.AppImage' -print -quit)" if [ -n "$appimage_path" ]; then cp "$appimage_path" "release-assets/QMaiWrite_${version}_linux_X64.AppImage" fi deb_path="$(find src-tauri/target -path '*/release/bundle/deb/*.deb' -print -quit)" if [ -n "$deb_path" ]; then cp "$deb_path" "release-assets/QMaiWrite_${version}_linux_X64.deb" fi - name: Attach macOS assets to release if: runner.os == 'macOS' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: gh release upload "${{ github.ref_name }}" release-assets/QMaiWrite_*_macOS_* --clobber - name: Attach Linux assets to release if: runner.os == 'Linux' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: gh release upload "${{ github.ref_name }}" release-assets/QMaiWrite_*_linux_* --clobber - name: Build Windows portable if: matrix.label == 'windows-x64' shell: pwsh run: | $ErrorActionPreference = "Stop" $version = node -p "require('./package.json').version" node scripts/build-portable.mjs $portableExe = "release-portable/QMaiWrite.exe" if (-not (Test-Path $portableExe)) { throw "便携版 EXE 未生成:$portableExe" } $assetName = "QMaiWrite_${version}_windows_X64_portable.exe" $assetPath = "release-assets/$assetName" New-Item -ItemType Directory -Force -Path "release-assets" | Out-Null Copy-Item $portableExe $assetPath -Force - name: Attach Windows portable to release if: matrix.label == 'windows-x64' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: gh release upload "${{ github.ref_name }}" release-assets/QMaiWrite_*_portable.exe --clobber - name: Upload bundles as workflow artifacts if: failure() uses: actions/upload-artifact@v4 with: name: qmai-${{ matrix.label }} path: ${{ matrix.artifact_globs }} if-no-files-found: warn retention-days: 14 build-linux-only: name: Build Linux x64 (manual) if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-22.04 env: CARGO_PROFILE_RELEASE_LTO: "false" CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16" CARGO_PROFILE_RELEASE_OPT_LEVEL: "1" steps: - name: Checkout uses: actions/checkout@v4 - name: Install Rust stable uses: dtolnay/rust-toolchain@stable - name: Rust cache uses: Swatinem/rust-cache@v2 with: workspaces: src-tauri - name: Install Linux dependencies run: | sudo apt-get update sudo apt-get install -y \ build-essential \ curl \ file \ libayatana-appindicator3-dev \ librsvg2-dev \ libssl-dev \ libwebkit2gtk-4.1-dev \ libxdo-dev \ patchelf \ protobuf-compiler \ wget \ xdg-utils - name: Install PDFium binary shell: bash run: | set -euo pipefail mkdir -p "$RUNNER_TEMP/pdfium" src-tauri/pdfium curl -L --fail --retry 5 --retry-delay 2 \ -o "$RUNNER_TEMP/pdfium.tgz" \ "https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-x64.tgz" tar -xzf "$RUNNER_TEMP/pdfium.tgz" -C "$RUNNER_TEMP/pdfium" cp "$RUNNER_TEMP/pdfium/lib/libpdfium.so" "src-tauri/pdfium/libpdfium.so" ls -la src-tauri/pdfium - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: npm - name: Install frontend dependencies run: npm ci - name: Build Tauri app env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: npx tauri build --bundles appimage,deb - name: Package Linux release assets shell: bash run: | set -euo pipefail version="$(node -p "require('./package.json').version")" mkdir -p release-assets appimage_path="$(find src-tauri/target -path '*/release/bundle/appimage/*.AppImage' -print -quit)" if [ -n "$appimage_path" ]; then cp "$appimage_path" "release-assets/QMaiWrite_${version}_linux_X64.AppImage" fi deb_path="$(find src-tauri/target -path '*/release/bundle/deb/*.deb' -print -quit)" if [ -n "$deb_path" ]; then cp "$deb_path" "release-assets/QMaiWrite_${version}_linux_X64.deb" fi - name: Upload to target release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | set -euo pipefail TAG="${{ inputs.release_tag }}" echo "上传到 Release: $TAG" gh release upload "$TAG" release-assets/QMaiWrite_*_linux_* --clobber echo "上传完成!" - name: Upload bundles as workflow artifacts if: always() uses: actions/upload-artifact@v4 with: name: qmai-linux-x64 path: | src-tauri/target/**/release/bundle/appimage/*.AppImage src-tauri/target/**/release/bundle/deb/*.deb if-no-files-found: warn retention-days: 14