chore(ci): speed up windows publish #160
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: Publish | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| env: | |
| APP_CARGO_TOML: src-tauri/Cargo.toml | |
| APP_NAME: miaoyu | |
| IS_PRERELEASE: ${{ contains(github.ref_name, '-pre') }} | |
| jobs: | |
| draft: | |
| name: Create Draft Release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.read_version.outputs.value }} | |
| release_id: ${{ steps.create_release.outputs.id }} | |
| release_url: ${{ steps.create_release.outputs.html_url }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v1 | |
| with: | |
| bun-version: latest | |
| - name: Extract version from git tag | |
| id: read_version | |
| run: | | |
| VERSION="${GITHUB_REF#refs/tags/}" | |
| VERSION="${VERSION#v}" | |
| echo "value=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Generate Changelog | |
| id: changelog | |
| env: | |
| OUTPUT: CHANGELOG-${{ steps.read_version.outputs.value }}.md | |
| run: | | |
| bunx --bun git-cliff --config cliff.toml --verbose --latest --strip header --output "$OUTPUT" | |
| { | |
| echo 'content<<EOF' | |
| cat "$OUTPUT" | |
| echo 'EOF' | |
| } >> "$GITHUB_OUTPUT" | |
| rm "$OUTPUT" | |
| - name: Create Release | |
| id: create_release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| draft: true | |
| prerelease: ${{ env.IS_PRERELEASE == 'true' }} | |
| tag_name: v${{ steps.read_version.outputs.value }} | |
| name: v${{ steps.read_version.outputs.value }} | |
| body: ${{ steps.changelog.outputs.content }} | |
| generate_release_notes: false | |
| build: | |
| name: Build ${{ matrix.settings.target }} | |
| needs: draft | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| settings: | |
| - target: x86_64-apple-darwin | |
| runner: macos-latest | |
| os: macOS | |
| - target: aarch64-apple-darwin | |
| runner: macos-latest | |
| os: macOS | |
| - target: x86_64-pc-windows-msvc | |
| runner: windows-latest | |
| os: Windows | |
| runs-on: ${{ matrix.settings.runner }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v1 | |
| with: | |
| bun-version: latest | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.settings.target }} | |
| - name: Rust Cache (non-Windows) | |
| if: runner.os != 'Windows' | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src-tauri | |
| key: ${{ matrix.settings.target }} | |
| - name: Enable Git long paths (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: git config --global core.longpaths true | |
| - name: Configure Cargo dirs (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path C:\c | Out-Null | |
| New-Item -ItemType Directory -Force -Path C:\t | Out-Null | |
| - name: Rust Cache (Windows; short dirs) | |
| if: runner.os == 'Windows' | |
| uses: Swatinem/rust-cache@v2 | |
| env: | |
| CARGO_HOME: C:\c | |
| CARGO_TARGET_DIR: C:\t | |
| with: | |
| workspaces: src-tauri | |
| key: ${{ matrix.settings.target }} | |
| - name: Clean previous Cargo git checkouts (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $paths = @( | |
| "$env:USERPROFILE\.cargo\git\checkouts", | |
| "C:\c\git\checkouts" | |
| ) | |
| foreach ($p in $paths) { | |
| if (Test-Path $p) { Remove-Item -Recurse -Force $p } | |
| } | |
| - name: Restore sccache (Windows) | |
| if: runner.os == 'Windows' | |
| uses: actions/cache@v4 | |
| with: | |
| path: C:\sccache | |
| key: sccache-${{ matrix.settings.target }}-${{ hashFiles('src-tauri/Cargo.lock') }} | |
| restore-keys: | | |
| sccache-${{ matrix.settings.target }}- | |
| - name: Setup sccache (Windows) | |
| if: runner.os == 'Windows' | |
| uses: mozilla-actions/sccache@v0.0.3 | |
| with: | |
| version: v0.5.4 | |
| - name: Create Apple API key file | |
| if: matrix.settings.os == 'macOS' | |
| run: printf '%s' "${{ secrets.APPLE_API_KEY_FILE }}" > api.p8 | |
| - name: Import signing certificates | |
| if: matrix.settings.os == 'macOS' | |
| uses: apple-actions/import-codesign-certs@v2 | |
| with: | |
| p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }} | |
| p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| - name: Verify signing identity | |
| if: matrix.settings.os == 'macOS' | |
| run: security find-identity -v -p codesigning "$RUNNER_TEMP/build.keychain" | |
| - name: Install dependencies | |
| if: runner.os != 'Windows' | |
| run: bun install | |
| - name: Install dependencies (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: bun install | |
| - name: Create .env file | |
| shell: bash | |
| run: | | |
| cd src-tauri | |
| { | |
| printf 'DEEPSEEK_API_KEY=%s\n' "${{ secrets.DEEPSEEK_API_KEY }}" | |
| printf 'MODELSCOPE_ACCESS_TOKEN=%s\n' "${{ secrets.MODELSCOPE_ACCESS_TOKEN }}" | |
| } > .env | |
| - name: Build Rust artifacts (non-Windows) | |
| if: runner.os != 'Windows' | |
| run: cargo build --manifest-path src-tauri/Cargo.toml --release --target ${{ matrix.settings.target }} --verbose | |
| - name: Build Rust artifacts (Windows; short dirs + git CLI) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| CARGO_NET_GIT_FETCH_WITH_CLI: true | |
| CARGO_HOME: C:\c | |
| CARGO_TARGET_DIR: C:\t | |
| CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS: "-Ctarget-feature=+crt-static" | |
| CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_RUSTFLAGS: "-Ctarget-feature=+crt-static" | |
| CARGO_PROFILE_RELEASE_LTO: off | |
| CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 16 | |
| SHERPA_BUILD_DEBUG: "1" | |
| SHERPA_BUILD_SHARED_LIBS: "1" | |
| RUSTC_WRAPPER: sccache | |
| SCCACHE_DIR: C:\sccache | |
| SCCACHE_CACHE_SIZE: 10G | |
| run: cargo build --manifest-path src-tauri/Cargo.toml --release --target ${{ matrix.settings.target }} --verbose | |
| - name: Build Tauri app (.app only) | |
| if: matrix.settings.os == 'macOS' | |
| env: | |
| CI: false | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} | |
| APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} | |
| APPLE_API_KEY_PATH: ${{ github.workspace }}/api.p8 | |
| APPLE_KEYCHAIN: ${{ runner.temp }}/build.keychain | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| run: | | |
| bun tauri build --target ${{ matrix.settings.target }} --bundles app --config src-tauri/tauri.prod.macos.json | |
| - name: Copy ORT/Sherpa dylibs into Frameworks and fix rpath (macOS) | |
| if: matrix.settings.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BUNDLE_ROOT="src-tauri/target/${{ matrix.settings.target }}/release/bundle/macos" | |
| APP_DIR=$(find "$BUNDLE_ROOT" -maxdepth 2 -type d -name "*.app" | head -n1) | |
| [[ -z "${APP_DIR:-}" ]] && { echo "ERROR: No .app under $BUNDLE_ROOT"; exit 1; } | |
| BIN_DIR="$APP_DIR/Contents/MacOS" | |
| FW_DIR="$APP_DIR/Contents/Frameworks" | |
| mkdir -p "$FW_DIR" | |
| CANDIDATES=("src-tauri/platform-libs/macos" "src-tauri/target/${{ matrix.settings.target }}/release" "$RUNNER_TEMP/miaoyu-libs/macos") | |
| find_one () { local pat="$1"; local out=""; for d in "${CANDIDATES[@]}"; do [[ -d "$d" ]] || continue; out=$(find "$d" -maxdepth 1 -type f -name "$pat" 2>/dev/null | head -n1 || true); [[ -n "$out" ]] && { echo "$out"; return 0; }; done; return 1; } | |
| ORT_VER_SRC=$(find_one "libonnxruntime.*.dylib" || true) | |
| SHERPA_SRC=$(find_one "libsherpa-onnx-c-api*.dylib" || true) | |
| ORT_BARE_SRC=$(find_one "libonnxruntime.dylib" || true) | |
| [[ -z "${ORT_VER_SRC:-}" ]] && { echo "ERROR: no versioned libonnxruntime.*.dylib found"; exit 1; } | |
| [[ -z "${SHERPA_SRC:-}" ]] && { echo "ERROR: no libsherpa-onnx-c-api*.dylib found"; exit 1; } | |
| cp -f "$ORT_VER_SRC" "$FW_DIR/"; cp -f "$SHERPA_SRC" "$FW_DIR/"; [[ -n "${ORT_BARE_SRC:-}" ]] && cp -f "$ORT_BARE_SRC" "$FW_DIR/" || true | |
| ORT_VER="$FW_DIR/$(basename "$ORT_VER_SRC")"; SHERPA="$FW_DIR/$(basename "$SHERPA_SRC")"; ORT_BARE="$FW_DIR/libonnxruntime.dylib" | |
| [[ -f "$ORT_BARE" ]] || ln -s "$(basename "$ORT_VER")" "$ORT_BARE" | |
| EXEC=$(find "$BIN_DIR" -type f -perm -111 | head -n1); [[ -z "${EXEC:-}" ]] && { echo "ERROR: No executable under $BIN_DIR"; exit 1; } | |
| install_name_tool -add_rpath "@loader_path/../Frameworks" "$EXEC" 2>/dev/null || true | |
| install_name_tool -id "@rpath/$(basename "$ORT_BARE")" "$ORT_VER" 2>/dev/null || true | |
| install_name_tool -id "@rpath/$(basename "$SHERPA")" "$SHERPA" 2>/dev/null || true | |
| OLD_REF=$(otool -L "$SHERPA" | awk '/onnxruntime.*dylib/ {print $1; exit}') | |
| if [[ -n "${OLD_REF:-}" && "$OLD_REF" != "@rpath/$(basename "$ORT_BARE")" ]]; then | |
| install_name_tool -change "$OLD_REF" "@rpath/$(basename "$ORT_BARE")" "$SHERPA" | |
| fi | |
| install_name_tool -change "libonnxruntime.dylib" "@rpath/$(basename "$ORT_BARE")" "$EXEC" 2>/dev/null || true | |
| install_name_tool -change "$(basename "$ORT_VER")" "@rpath/$(basename "$ORT_BARE")" "$EXEC" 2>/dev/null || true | |
| install_name_tool -change "$(basename "$SHERPA")" "@rpath/$(basename "$SHERPA")" "$EXEC" 2>/dev/null || true | |
| if security find-identity -v -p codesigning >/dev/null 2>&1; then | |
| codesign --force --timestamp --options runtime --deep --sign "${{ secrets.APPLE_SIGNING_IDENTITY }}" "$APP_DIR" | |
| fi | |
| otool -L "$SHERPA" || true; otool -L "$EXEC" || true; ls -lah "$FW_DIR" || true | |
| - name: Force re-sign with entitlements (macOS) | |
| if: matrix.settings.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BUNDLE_ROOT="src-tauri/target/${{ matrix.settings.target }}/release/bundle/macos" | |
| APP_DIR=$(find "$BUNDLE_ROOT" -maxdepth 2 -type d -name "*.app" | head -n1) | |
| [[ -z "${APP_DIR:-}" ]] && { echo "ERROR: No .app under $BUNDLE_ROOT"; exit 1; } | |
| ENT_PLIST="src-tauri/Entitlements.plist" | |
| [[ -f "$ENT_PLIST" ]] || { echo "Missing Entitlements.plist"; exit 1; } | |
| codesign --force --deep --timestamp --options runtime --entitlements "$ENT_PLIST" --sign "${{ secrets.APPLE_SIGNING_IDENTITY }}" "$APP_DIR" | |
| codesign -dv --entitlements :- "$APP_DIR" | tee verify-entitlements.log | |
| grep -E "audio-input|microphone|app-sandbox" verify-entitlements.log || { echo "::error::Entitlements missing from final signature"; exit 1; } | |
| - name: Create DMG (macOS) | |
| if: matrix.settings.os == 'macOS' | |
| run: | | |
| set -euo pipefail | |
| echo "🧹 Cleaning any previous DMG mounts..." | |
| MOUNTS=$(hdiutil info | awk '/妙语/ {print $1}' || true) | |
| if [[ -n "$MOUNTS" ]]; then | |
| echo "Detaching: $MOUNTS" | |
| while read -r m; do hdiutil detach "$m" -force || true; done <<< "$MOUNTS" | |
| fi | |
| APP_DIR=$(find src-tauri/target/${{ matrix.settings.target }}/release/bundle/macos -maxdepth 2 -type d -name "*.app" | head -n1) | |
| [[ -z "${APP_DIR:-}" ]] && { echo "ERROR: .app not found"; exit 1; } | |
| case "${{ matrix.settings.target }}" in | |
| aarch64-apple-darwin) ARCH_SUFFIX="aarch64" ;; | |
| x86_64-apple-darwin) ARCH_SUFFIX="x64" ;; | |
| *) ARCH_SUFFIX="${{ matrix.settings.target }}" ;; | |
| esac | |
| OUT_DIR="src-tauri/target/${{ matrix.settings.target }}/release/bundle/macos" | |
| OUT_DMG="$OUT_DIR/${{ env.APP_NAME }}_${{ needs.draft.outputs.version }}_${ARCH_SUFFIX}.dmg" | |
| echo "🧹 Cleaning old DMGs..." | |
| rm -f "$OUT_DIR"/*.dmg | |
| STAGE=$(mktemp -d -t miaoyu-stage) | |
| cp -R "$APP_DIR" "$STAGE/" | |
| ln -s /Applications "$STAGE/Applications" | |
| VOLNAME="妙语-${{ matrix.settings.target }}-$(date +%s)" | |
| echo "Creating DMG $OUT_DMG with volname=$VOLNAME ..." | |
| for i in {1..3}; do | |
| if hdiutil create -volname "$VOLNAME" -srcfolder "$STAGE" -ov -format ULMO "$OUT_DMG"; then | |
| break | |
| else | |
| echo "Retrying DMG creation in 5s ($i/3)..." | |
| sleep 5 | |
| fi | |
| done | |
| rm -rf "$STAGE" | |
| echo "DMG_OUT=$OUT_DMG" >> $GITHUB_ENV | |
| - name: Notarize & staple DMG (macOS) | |
| if: matrix.settings.os == 'macOS' | |
| shell: bash | |
| env: | |
| APPLE_API_KEY_PATH: ${{ github.workspace }}/api.p8 | |
| APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} | |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} | |
| run: | | |
| set -euo pipefail | |
| xcrun notarytool submit "$DMG_OUT" --key "$APPLE_API_KEY_PATH" --key-id "$APPLE_API_KEY" --issuer "$APPLE_API_ISSUER" --wait | |
| xcrun stapler staple "$DMG_OUT" | |
| - name: Sanity check DMG (Frameworks + Applications link) | |
| if: matrix.settings.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| MNT=$(mktemp -d) | |
| hdiutil attach "$DMG_OUT" -mountpoint "$MNT" -nobrowse -quiet | |
| APP_PATH=$(find "$MNT" -maxdepth 2 -type d -name "*.app" | head -n1) | |
| [[ -d "$APP_PATH/Contents/Frameworks" ]] || { echo "::error::No Frameworks in DMG app"; hdiutil detach "$MNT" -quiet; exit 1; } | |
| ls -la "$APP_PATH/Contents/Frameworks" || true | |
| test -e "$MNT/Applications" || { echo "::error::Applications link missing"; hdiutil detach "$MNT" -quiet; exit 1; } | |
| hdiutil detach "$MNT" -quiet | |
| - name: Stage runtime DLLs (Windows) | |
| if: matrix.settings.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| TARGET: ${{ matrix.settings.target }} # ← 明确传入 | |
| run: | | |
| $ErrorActionPreference = 'Stop' # ← fail-fast | |
| $dst = "src-tauri" | |
| New-Item -ItemType Directory -Force -Path $dst | Out-Null | |
| # 候选目录(把可能的 release/ 和 deps/ 都兜住) | |
| $candidates = @( | |
| "C:\t\$env:TARGET\release", | |
| "C:\t\$env:TARGET\release\deps", | |
| "src-tauri\target\$env:TARGET\release", | |
| "src-tauri\target\$env:TARGET\release\deps", | |
| "src-tauri\bin" | |
| ) | Where-Object { Test-Path $_ } | Select-Object -Unique | |
| Write-Host "Search candidates:" | |
| $candidates | ForEach-Object { Write-Host " - $_" } | |
| # 逐项收集;sherpa 用通配并重命名 | |
| $need = @( | |
| @{ pat = "onnxruntime.dll"; out = "onnxruntime.dll" }, | |
| @{ pat = "onnxruntime_providers_shared.dll"; out = "onnxruntime_providers_shared.dll" }, | |
| @{ pat = "sherpa-onnx-c-api*.dll"; out = "sherpa-onnx-c-api.dll" } | |
| ) | |
| foreach ($n in $need) { | |
| $hit = $null | |
| foreach ($dir in $candidates) { | |
| $hit = Get-ChildItem -Path $dir -File -Filter $n.pat -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| if ($hit) { break } | |
| } | |
| if (-not $hit) { throw "Missing required DLL: $($n.pat)" } | |
| $dstPath = Join-Path $dst $n.out | |
| Copy-Item $hit.FullName $dstPath -Force # ← 强制覆盖 | |
| Write-Host "✅ externalBin ready: $dstPath (from $($hit.FullName))" | |
| } | |
| - name: Build Tauri app (Windows) | |
| if: matrix.settings.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| CI: false | |
| 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 }} | |
| CARGO_NET_GIT_FETCH_WITH_CLI: true | |
| CARGO_HOME: C:\c | |
| CARGO_TARGET_DIR: C:\t | |
| CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS: "-Ctarget-feature=+crt-static" | |
| CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_RUSTFLAGS: "-Ctarget-feature=+crt-static" | |
| CARGO_PROFILE_RELEASE_LTO: off | |
| CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 16 | |
| SHERPA_BUILD_DEBUG: "1" | |
| SHERPA_BUILD_SHARED_LIBS: "1" | |
| RUSTC_WRAPPER: sccache | |
| SCCACHE_DIR: C:\sccache | |
| SCCACHE_CACHE_SIZE: 10G | |
| run: | | |
| bun tauri build --target ${{ matrix.settings.target }} --config src-tauri/tauri.prod.windows.json | |
| - name: sccache stats (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: sccache --show-stats | |
| - name: Remove Apple API key file | |
| if: matrix.settings.os == 'macOS' | |
| run: rm -f api.p8 | |
| - name: Upload release assets (macOS/Linux) | |
| if: runner.os != 'Windows' | |
| env: | |
| TAG_NAME: v${{ needs.draft.outputs.version }} | |
| TARGET: ${{ matrix.settings.target }} | |
| VERSION: ${{ needs.draft.outputs.version }} | |
| RELEASE_ID: ${{ needs.draft.outputs.release_id }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${DMG_OUT:-}" ] && [ -f "$DMG_OUT" ]; then | |
| gh release upload "$TAG_NAME" "$DMG_OUT" --clobber | |
| else | |
| bundle_dir="src-tauri/target/${TARGET}/release/bundle" | |
| find "$bundle_dir" -type f \( -name '*.dmg' -o -name '*.zip' -o -name '*.tar.gz' -o -name '*.pkg' -o -name '*.json' \) -print0 \ | |
| | xargs -0 -I {} gh release upload "$TAG_NAME" "{}" --clobber | |
| fi | |
| - name: Canonicalize Windows artifact filenames | |
| if: matrix.settings.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| VERSION: ${{ needs.draft.outputs.version }} | |
| TARGET: ${{ matrix.settings.target }} | |
| run: | | |
| $candidates = @( | |
| "C:\t\$env:TARGET\release\bundle", # 当设置了 CARGO_TARGET_DIR=C:\t(你现在的情况) | |
| "src-tauri\target\$env:TARGET\release\bundle" # 默认路径(未设置 CARGO_TARGET_DIR 时) | |
| ) | |
| $bundle = $null | |
| foreach ($c in $candidates) { | |
| if (Test-Path $c) { $bundle = (Resolve-Path $c).Path; break } | |
| } | |
| if (-not $bundle) { | |
| Write-Host "Tried bundle locations:" | |
| $candidates | ForEach-Object { Write-Host " - $_" } | |
| throw "Bundle directory not found in any known location." | |
| } | |
| Write-Host "Using bundle dir: $bundle" | |
| # 归一化架构名 | |
| $arch = if ($env:TARGET -match 'x86_64') { 'x64' } | |
| elseif ($env:TARGET -match 'aarch64') { 'aarch64' } | |
| else { $env:TARGET } | |
| # 递归找到 .exe / .msi 并重命名 | |
| Get-ChildItem -Path $bundle -Recurse -File | Where-Object { | |
| $_.Extension -in '.exe','.msi' | |
| } | ForEach-Object { | |
| $newName = "miaoyu_${env:VERSION}_${arch}$($_.Extension)" | |
| $newPath = Join-Path $_.DirectoryName $newName | |
| Move-Item -Force $_.FullName $newPath | |
| Write-Host "Renamed: $($_.Name) → $newName" | |
| } | |
| - name: Upload release assets (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| TAG_NAME: v${{ needs.draft.outputs.version }} | |
| TARGET: ${{ matrix.settings.target }} | |
| VERSION: ${{ needs.draft.outputs.version }} | |
| RELEASE_ID: ${{ needs.draft.outputs.release_id }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $candidates = @("C:/t/$env:TARGET/release/bundle","src-tauri/target/$env:TARGET/release/bundle") | |
| $bundle = $null | |
| foreach ($c in $candidates) { if (Test-Path $c) { $bundle = (Resolve-Path $c).Path; break } } | |
| if (-not $bundle) { | |
| Write-Host "Tried bundle locations:"; $candidates | ForEach-Object { Write-Host " - $_" } | |
| throw "Bundle directory not found in any known location." | |
| } | |
| Write-Host "Using bundle root: $bundle" | |
| Get-ChildItem -Path $bundle -Recurse -File | Where-Object { | |
| $_.Name -like '*.json' -or $_.Name -like '*.zip' -or $_.Name -like '*.msi' -or $_.Name -like '*.exe' | |
| } | ForEach-Object { | |
| Write-Host "Uploading $($_.FullName)" | |
| gh release upload $env:TAG_NAME $_.FullName --clobber | |
| } | |
| done: | |
| name: Publish Release | |
| needs: [draft, build] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Publish Release | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.repos.updateRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: ${{ needs.draft.outputs.release_id }}, | |
| draft: false | |
| }); | |
| - name: Summary | |
| env: | |
| IS_PRERELEASE: ${{ env.IS_PRERELEASE }} | |
| run: | | |
| TITLE=$([ "${IS_PRERELEASE}" = "true" ] && echo "Pre-release Published" || echo "Release Published") | |
| echo "### 🎉 ${TITLE}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Version: **v${{ needs.draft.outputs.version }}**" >> $GITHUB_STEP_SUMMARY | |
| echo "Release URL: ${{ needs.draft.outputs.release_url }}" >> $GITHUB_STEP_SUMMARY |