fix: use fixed width mode in screenshot archive tests #120
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| jobs: | |
| build-tauri: | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: macos-latest | |
| args: "--target aarch64-apple-darwin" | |
| target: aarch64-apple-darwin | |
| - platform: macos-latest | |
| args: "--target x86_64-apple-darwin" | |
| target: x86_64-apple-darwin | |
| - platform: ubuntu-22.04 | |
| args: "--target x86_64-unknown-linux-gnu" | |
| target: x86_64-unknown-linux-gnu | |
| - platform: windows-latest | |
| args: "" | |
| target: x86_64-pc-windows-msvc | |
| runs-on: ${{ matrix.platform }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: "npm" | |
| - name: Install Linux build dependencies | |
| if: startsWith(matrix.platform, 'ubuntu') | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libwebkit2gtk-4.1-dev \ | |
| libgtk-3-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| libfuse2 | |
| - name: Import macOS code signing certificate | |
| if: startsWith(matrix.platform, 'macos') | |
| env: | |
| MACOS_CODESIGN_P12: ${{ secrets.MACOS_CODESIGN_P12 }} | |
| MACOS_CODESIGN_PASSWORD: ${{ secrets.MACOS_CODESIGN_PASSWORD }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "$MACOS_CODESIGN_P12" ]]; then | |
| echo "::warning::MACOS_CODESIGN_P12 secret not set, skipping code signing import" | |
| exit 0 | |
| fi | |
| echo "$MACOS_CODESIGN_P12" | base64 -d > /tmp/codesign.p12 | |
| if [[ ! -s /tmp/codesign.p12 ]]; then | |
| echo "::warning::Decoded P12 is empty, skipping code signing import" | |
| rm -f /tmp/codesign.p12 | |
| exit 0 | |
| fi | |
| security create-keychain -p actions ci-keychain 2>/dev/null || true | |
| security unlock-keychain -p actions ci-keychain | |
| if ! security import /tmp/codesign.p12 \ | |
| -k ci-keychain \ | |
| -P "$MACOS_CODESIGN_PASSWORD" \ | |
| -T /usr/bin/codesign 2>&1; then | |
| echo "::warning::Certificate import failed, skipping code signing. Verify MACOS_CODESIGN_P12 and MACOS_CODESIGN_PASSWORD secrets." | |
| rm -f /tmp/codesign.p12 | |
| exit 0 | |
| fi | |
| security set-key-partition-list -S apple-tool:,apple:,codesign -s -k actions ci-keychain | |
| default_keychain="$(security default-keychain -d user | sed 's/["]//g')" | |
| security list-keychains -d user -s ci-keychain "$default_keychain" | |
| rm -f /tmp/codesign.p12 | |
| echo "MACOS_CODESIGN_AVAILABLE=1" >> "$GITHUB_ENV" | |
| echo "Code signing certificate imported" | |
| - name: Install frontend dependencies | |
| run: npm ci | |
| - name: Run frontend tests | |
| run: node --test | |
| - name: Build frontend assets for Rust tests | |
| run: npm run build | |
| - name: Run Rust tests | |
| run: cargo test --manifest-path src-tauri/Cargo.toml | |
| - name: Build application | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # CI 中统一使用 ad-hoc 身份避免固定证书导致的跨 runner 构建不稳定 | |
| if [[ "${{ matrix.platform }}" == macos* ]]; then | |
| export APPLE_SIGNING_IDENTITY="-" | |
| fi | |
| npm run tauri build -- ${{ matrix.args }} | |
| - name: List build output | |
| if: always() | |
| shell: bash | |
| run: | | |
| if [[ -d "target" ]]; then | |
| echo "=== target ===" | |
| find target -maxdepth 5 -type f \( -name "*.app" -o -name "*.dmg" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.tar.gz" -o -name "*.sig" \) 2>/dev/null || true | |
| else | |
| echo "No build output directory found in target." | |
| fi | |
| - name: Rename macOS updater assets | |
| if: startsWith(matrix.platform, 'macos') | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! -d "target" ]]; then | |
| echo "No build output, skipping rename" | |
| exit 0 | |
| fi | |
| normalize_name() { | |
| local filename="$1" | |
| printf '%s' "${filename// /_}" | |
| } | |
| if [[ "${{ matrix.target }}" == "aarch64-apple-darwin" ]]; then | |
| ARCH_SUFFIX="aarch64" | |
| else | |
| ARCH_SUFFIX="x64" | |
| fi | |
| find target -type f -path "*/release/bundle/macos/*.app.tar.gz" 2>/dev/null | while read -r file; do | |
| dir="$(dirname "$file")" | |
| base="$(basename "$file" ".app.tar.gz")" | |
| normalized_base="$(normalize_name "$base")" | |
| renamed="${dir}/${normalized_base}_${ARCH_SUFFIX}.app.tar.gz" | |
| mv "$file" "$renamed" | |
| if [[ -f "${file}.sig" ]]; then | |
| mv "${file}.sig" "${renamed}.sig" | |
| fi | |
| done | |
| find target -type f -path "*/release/bundle/dmg/*.dmg" 2>/dev/null | while read -r file; do | |
| dir="$(dirname "$file")" | |
| name="$(basename "$file")" | |
| normalized_name="$(normalize_name "$name")" | |
| renamed="${dir}/${normalized_name}" | |
| if [[ "$file" != "$renamed" ]]; then | |
| mv "$file" "$renamed" | |
| fi | |
| done | |
| - name: Rename Windows installer assets | |
| if: startsWith(matrix.platform, 'windows') | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! -d "target" ]]; then | |
| echo "No build output, skipping rename" | |
| exit 0 | |
| fi | |
| normalize_name() { | |
| local filename="$1" | |
| printf '%s' "${filename// /_}" | |
| } | |
| find target -type f -path "*/release/bundle/nsis/*.exe" 2>/dev/null | while read -r file; do | |
| dir="$(dirname "$file")" | |
| name="$(basename "$file")" | |
| normalized_name="$(normalize_name "$name")" | |
| renamed="${dir}/${normalized_name}" | |
| if [[ "$file" != "$renamed" ]]; then | |
| mv "$file" "$renamed" | |
| fi | |
| if [[ -f "${file}.sig" ]]; then | |
| mv "${file}.sig" "${renamed}.sig" | |
| fi | |
| done | |
| - name: Rename Linux bundle assets | |
| if: startsWith(matrix.platform, 'ubuntu') | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! -d "target" ]]; then | |
| echo "No build output, skipping rename" | |
| exit 0 | |
| fi | |
| normalize_name() { | |
| local filename="$1" | |
| printf '%s' "${filename// /_}" | |
| } | |
| find target -type f -path "*/release/bundle/appimage/*.AppImage" 2>/dev/null | while read -r file; do | |
| dir="$(dirname "$file")" | |
| name="$(basename "$file")" | |
| normalized_name="$(normalize_name "$name")" | |
| renamed="${dir}/${normalized_name}" | |
| if [[ "$file" != "$renamed" ]]; then | |
| mv "$file" "$renamed" | |
| fi | |
| done | |
| find target -type f -path "*/release/bundle/deb/*.deb" 2>/dev/null | while read -r file; do | |
| dir="$(dirname "$file")" | |
| name="$(basename "$file")" | |
| normalized_name="$(normalize_name "$name")" | |
| renamed="${dir}/${normalized_name}" | |
| if [[ "$file" != "$renamed" ]]; then | |
| mv "$file" "$renamed" | |
| fi | |
| done | |
| - name: Verify required artifacts | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! -d "target" ]]; then | |
| echo "Build output directory not found (target)." >&2 | |
| exit 1 | |
| fi | |
| require_file() { | |
| local pattern="$1" | |
| local label="$2" | |
| local match | |
| match="$(find target -type f -path "$pattern" 2>/dev/null | sort | head -n 1 || true)" | |
| if [[ -z "$match" ]]; then | |
| echo "缺少 ${label}: ${pattern}" >&2 | |
| echo "Available bundle files:" >&2 | |
| find target -type f -path "*/bundle/*" 2>/dev/null | head -50 >&2 || true | |
| exit 1 | |
| fi | |
| echo "${label}: ${match}" | |
| } | |
| case "${{ matrix.target }}" in | |
| aarch64-apple-darwin) | |
| require_file "*/release/bundle/macos/*_aarch64.app.tar.gz" "macOS aarch64 自动更新包" | |
| require_file "*/release/bundle/macos/*_aarch64.app.tar.gz.sig" "macOS aarch64 自动更新签名" | |
| require_file "*/release/bundle/dmg/*.dmg" "macOS aarch64 DMG" | |
| ;; | |
| x86_64-apple-darwin) | |
| require_file "*/release/bundle/macos/*_x64.app.tar.gz" "macOS x64 自动更新包" | |
| require_file "*/release/bundle/macos/*_x64.app.tar.gz.sig" "macOS x64 自动更新签名" | |
| require_file "*/release/bundle/dmg/*.dmg" "macOS x64 DMG" | |
| ;; | |
| x86_64-unknown-linux-gnu) | |
| require_file "*/release/bundle/appimage/*.AppImage" "Linux AppImage" | |
| require_file "*/release/bundle/deb/*.deb" "Linux DEB" | |
| ;; | |
| x86_64-pc-windows-msvc) | |
| require_file "*/release/bundle/nsis/*.exe" "Windows 安装包" | |
| require_file "*/release/bundle/nsis/*.sig" "Windows 安装包签名" | |
| ;; | |
| esac | |
| - name: Upload release assets | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-assets-${{ matrix.platform }}-${{ strategy.job-index }} | |
| path: | | |
| target/**/release/bundle/dmg/*.dmg | |
| target/**/release/bundle/dmg/*.sig | |
| target/**/release/bundle/macos/*.app.tar.gz | |
| target/**/release/bundle/macos/*.app.tar.gz.sig | |
| target/**/release/bundle/nsis/*.exe | |
| target/**/release/bundle/nsis/*.sig | |
| target/**/release/bundle/appimage/*.AppImage | |
| target/**/release/bundle/deb/*.deb | |
| if-no-files-found: error | |
| publish-release: | |
| needs: build-tauri | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: all-artifacts | |
| pattern: "release-assets-*" | |
| merge-multiple: true | |
| - name: Extract changelog | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| BODY=$(awk "/^## \[${VERSION}\]/{found=1; next} /^## \[/{if(found) exit} found{print}" CHANGELOG.md) | |
| if [[ -z "$BODY" ]]; then | |
| BODY="版本 ${VERSION} 更新" | |
| fi | |
| BODY="${BODY} | |
| ### macOS 安装说明 | |
| 首次打开可能提示「已损坏,无法打开」,请执行: | |
| \`\`\`bash | |
| sudo xattr -rd com.apple.quarantine '/Applications/Work Review.app' | |
| \`\`\`" | |
| printf '%s\n' "$BODY" > release_notes.md | |
| - name: Build updater.json from signatures | |
| shell: bash | |
| env: | |
| VERSION: ${{ github.ref_name }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| DOWNLOAD_BASE="https://github.com/${REPO}/releases/download/${VERSION}" | |
| VER="${VERSION#v}" | |
| NOTES="$(cat release_notes.md)" | |
| PUB_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| MAC_AARCH64_FILE="$(find all-artifacts -type f -name '*_aarch64.app.tar.gz' | sort | head -n 1 || true)" | |
| MAC_X64_FILE="$(find all-artifacts -type f -name '*_x64.app.tar.gz' | sort | head -n 1 || true)" | |
| WINDOWS_FILE="$(find all-artifacts -type f \( -name '*setup.exe' -o -name '*installer.exe' \) ! -name '*.sig' | sort | head -n 1 || true)" | |
| require_asset() { | |
| local asset_file="$1" | |
| local signature_file="$2" | |
| local label="$3" | |
| if [[ -z "$asset_file" || ! -f "$asset_file" ]]; then | |
| echo "缺少 ${label} 产物" >&2 | |
| exit 1 | |
| fi | |
| if [[ ! -f "$signature_file" ]]; then | |
| echo "缺少 ${label} 签名: ${signature_file}" >&2 | |
| exit 1 | |
| fi | |
| echo "${label}: ${asset_file}" | |
| } | |
| require_asset "$MAC_AARCH64_FILE" "${MAC_AARCH64_FILE}.sig" "darwin-aarch64" | |
| require_asset "$MAC_X64_FILE" "${MAC_X64_FILE}.sig" "darwin-x86_64" | |
| require_asset "$WINDOWS_FILE" "${WINDOWS_FILE}.sig" "windows-x86_64" | |
| json="$(jq -n \ | |
| --arg version "$VER" \ | |
| --arg notes "$NOTES" \ | |
| --arg pub_date "$PUB_DATE" \ | |
| '{version: $version, notes: $notes, pub_date: $pub_date, platforms: {}}')" | |
| add_platform() { | |
| local platform_key="$1" | |
| local asset_file="$2" | |
| local signature_file="$3" | |
| local base_url="$4" | |
| if [[ -z "$asset_file" || ! -f "$asset_file" || ! -f "$signature_file" ]]; then | |
| echo "skip ${platform_key}" | |
| return | |
| fi | |
| local signature | |
| signature="$(tr -d '\r\n' < "$signature_file")" | |
| local asset_name | |
| asset_name="$(basename "$asset_file")" | |
| variant_json="$(jq \ | |
| --arg key "$platform_key" \ | |
| --arg url "${base_url}/${asset_name}" \ | |
| --arg signature "$signature" \ | |
| '.platforms[$key] = { url: $url, signature: $signature }' \ | |
| <<<"$variant_json")" | |
| } | |
| # 为每个代理前缀生成独立的 updater JSON 文件 | |
| # 格式: "前缀 文件名"(前缀为空表示直连 GitHub) | |
| PROXY_VARIANTS=( | |
| " updater.json" | |
| "https://ghproxy.cn/ updater-ghproxy.json" | |
| "https://ghp.ci/ updater-ghp.json" | |
| ) | |
| for variant in "${PROXY_VARIANTS[@]}"; do | |
| proxy_prefix="${variant%% *}" | |
| output_file="${variant##* }" | |
| if [[ -z "$proxy_prefix" ]]; then | |
| VARIANT_BASE="${DOWNLOAD_BASE}" | |
| else | |
| VARIANT_BASE="${proxy_prefix}${DOWNLOAD_BASE}" | |
| fi | |
| variant_json="$json" | |
| add_platform "darwin-aarch64" "$MAC_AARCH64_FILE" "${MAC_AARCH64_FILE}.sig" "$VARIANT_BASE" | |
| add_platform "darwin-x86_64" "$MAC_X64_FILE" "${MAC_X64_FILE}.sig" "$VARIANT_BASE" | |
| add_platform "windows-x86_64" "$WINDOWS_FILE" "${WINDOWS_FILE}.sig" "$VARIANT_BASE" | |
| if [[ "$(jq '.platforms | length' <<<"$variant_json")" -ne 3 ]]; then | |
| echo "自动更新平台数量异常 (${output_file}),期望 3 个,实际为 $(jq '.platforms | length' <<<"$variant_json")" >&2 | |
| exit 1 | |
| fi | |
| printf '%s\n' "$variant_json" > "$output_file" | |
| echo "=== ${output_file} ===" | |
| cat "$output_file" | |
| done | |
| - name: Create or update GitHub Release | |
| uses: ncipollo/release-action@v1 | |
| with: | |
| allowUpdates: true | |
| name: "Work_Review ${{ github.ref_name }}" | |
| bodyFile: release_notes.md | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| artifacts: "all-artifacts/**/*,updater.json,updater-ghproxy.json,updater-ghp.json" | |
| artifactErrorsFailBuild: false | |
| replacesArtifacts: true | |
| makeLatest: legacy |