test(announcements): update release version expectations #24
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*" | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: release-${{ github.ref_name }} | |
| cancel-in-progress: true | |
| jobs: | |
| ci: | |
| name: CI | |
| uses: ./.github/workflows/ci.yml | |
| validate: | |
| name: Validate release version | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "20" | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: 1.95.0 | |
| - name: Validate release version | |
| run: node scripts/validate-release-version.mjs | |
| - name: Test Node scripts | |
| run: >- | |
| node --test | |
| scripts/dev.node.mjs | |
| scripts/generate-updater-manifest.node.mjs | |
| scripts/build-fast.node.mjs | |
| scripts/verify-bundle-marker.node.mjs | |
| - name: Validate updater signing secret | |
| shell: bash | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$TAURI_SIGNING_PRIVATE_KEY" ]; then | |
| printf 'Missing required repository secret: TAURI_SIGNING_PRIVATE_KEY\n' >&2 | |
| exit 1 | |
| fi | |
| release: | |
| needs: [ci, validate] | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: windows-2022 | |
| rust-target: x86_64-pc-windows-msvc | |
| - os: windows-11-arm | |
| arch: arm64 | |
| rust-target: aarch64-pc-windows-msvc | |
| - os: ubuntu-22.04 | |
| rust-target: x86_64-unknown-linux-gnu | |
| - os: ubuntu-22.04-arm | |
| arch: arm64 | |
| rust-target: aarch64-unknown-linux-gnu | |
| - os: macos-14 | |
| rust-target: universal-apple-darwin | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: 1.95.0 | |
| - name: Add Windows ARM64 target | |
| if: runner.os == 'Windows' && matrix.arch == 'arm64' | |
| shell: pwsh | |
| run: rustup target add aarch64-pc-windows-msvc | |
| - name: Add macOS targets | |
| if: runner.os == 'macOS' | |
| run: | | |
| rustup target add aarch64-apple-darwin x86_64-apple-darwin | |
| - name: Cache Rust build | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src-tauri -> target | |
| shared-key: release-${{ runner.os }}-${{ runner.arch }}-${{ matrix.rust-target }} | |
| key: ${{ runner.arch }}-${{ matrix.rust-target }} | |
| cache-on-failure: true | |
| - 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 \ | |
| rpm \ | |
| flatpak \ | |
| flatpak-builder \ | |
| elfutils \ | |
| xdg-utils | |
| # 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 | |
| if: runner.os != 'Windows' || matrix.arch != 'arm64' | |
| uses: pnpm/action-setup@v6 | |
| with: | |
| version: 10.12.3 | |
| run_install: false | |
| - name: Setup Node.js (Windows ARM64) | |
| if: runner.os == 'Windows' && matrix.arch == 'arm64' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "20" | |
| - name: Setup pnpm (Windows ARM64) | |
| if: runner.os == 'Windows' && matrix.arch == 'arm64' | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| corepack enable | |
| corepack prepare pnpm@10.12.3 --activate | |
| node --version | |
| pnpm --version | |
| - name: Setup Node.js | |
| if: runner.os != 'Windows' || matrix.arch != 'arm64' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "20" | |
| cache: pnpm | |
| cache-dependency-path: pnpm-lock.yaml | |
| - name: Setup pnpm cache (Windows ARM64) | |
| if: runner.os == 'Windows' && matrix.arch == 'arm64' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "20" | |
| cache: pnpm | |
| cache-dependency-path: pnpm-lock.yaml | |
| - name: Setup LLVM for Windows ARM64 | |
| if: runner.os == 'Windows' && matrix.arch == 'arm64' | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $llvmRoot = 'C:\Program Files\LLVM' | |
| if (-not (Test-Path $llvmRoot)) { | |
| throw "LLVM not found at $llvmRoot" | |
| } | |
| $llvmBin = Join-Path $llvmRoot 'bin' | |
| "LIBCLANG_PATH=$llvmBin" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| "CLANG_PATH=$(Join-Path $llvmBin 'clang.exe')" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| $llvmBin | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8 | |
| - name: Install frontend deps | |
| run: pnpm install --frozen-lockfile | |
| - name: Build Tauri App (macOS, ad-hoc signed) | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| timeout-minutes: 60 | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| run: pnpm tauri build --target universal-apple-darwin | |
| - name: Build Tauri App (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| timeout-minutes: 60 | |
| env: | |
| WINDOWS_RELEASE_ARCH: ${{ matrix.arch || 'x86_64' }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| if ($env:WINDOWS_RELEASE_ARCH -eq 'arm64') { | |
| pnpm tauri build --target aarch64-pc-windows-msvc --bundles msi | |
| } else { | |
| pnpm tauri build --bundles msi | |
| } | |
| - name: Build Tauri App (Linux) | |
| if: runner.os == 'Linux' | |
| timeout-minutes: 60 | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| run: pnpm tauri build --bundles appimage,deb,rpm | |
| - name: Verify Linux DEB bundle marker | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| DEB=$(find src-tauri/target/release/bundle -type f -name "*.deb" -print -quit) | |
| if [ -z "$DEB" ]; then | |
| echo "No .deb found for bundle marker verification" >&2 | |
| exit 1 | |
| fi | |
| EXTRACT_DIR=$(mktemp -d) | |
| trap 'rm -rf "$EXTRACT_DIR"' EXIT | |
| dpkg-deb --extract "$DEB" "$EXTRACT_DIR" | |
| DEB_BINARY=$(find "$EXTRACT_DIR/usr/bin" -maxdepth 1 -type f -print -quit) | |
| if [ -z "$DEB_BINARY" ]; then | |
| echo "No executable found in $DEB" >&2 | |
| exit 1 | |
| fi | |
| node scripts/verify-bundle-marker.mjs "$DEB_BINARY" deb | |
| - name: Prepare macOS Assets | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -euxo pipefail | |
| mkdir -p release-assets | |
| VERSION="${GITHUB_REF_NAME}" # e.g., v3.5.0 | |
| # Locate bundle artifacts | |
| APP_PATH="" | |
| TAR_GZ="" | |
| for path in \ | |
| "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" \ | |
| "src-tauri/target/release/bundle/macos"; do | |
| if [ -d "$path" ]; then | |
| [ -z "$APP_PATH" ] && APP_PATH=$(find "$path" -maxdepth 1 -name "*.app" -type d | head -1 || true) | |
| [ -z "$TAR_GZ" ] && TAR_GZ=$(find "$path" -maxdepth 1 -name "*.tar.gz" -type f | head -1 || true) | |
| fi | |
| done | |
| if [ -z "$APP_PATH" ]; then | |
| echo "❌ No .app found" >&2 | |
| exit 1 | |
| fi | |
| if [ -z "$TAR_GZ" ] || [ ! -f "$TAR_GZ.sig" ]; then | |
| echo "No signed macOS updater artifact found" >&2 | |
| exit 1 | |
| fi | |
| NEW_TAR_GZ="StackFerry-${VERSION}-macOS.tar.gz" | |
| cp "$TAR_GZ" "release-assets/$NEW_TAR_GZ" | |
| cp "$TAR_GZ.sig" "release-assets/$NEW_TAR_GZ.sig" | |
| NEW_ZIP="StackFerry-${VERSION}-macOS.zip" | |
| ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "release-assets/$NEW_ZIP" | |
| echo "macOS zip ready: $NEW_ZIP" | |
| HOMEBREW_NO_AUTO_UPDATE=1 brew install create-dmg | |
| NEW_DMG="StackFerry-${VERSION}-macOS.dmg" | |
| DMG_STAGE_DIR="$RUNNER_TEMP/dmg-stage" | |
| rm -rf "$DMG_STAGE_DIR" | |
| mkdir -p "$DMG_STAGE_DIR" | |
| ditto "$APP_PATH" "$DMG_STAGE_DIR/StackFerry.app" | |
| create-dmg \ | |
| --volname "StackFerry" \ | |
| --background "src-tauri/icons/dmg-background.png" \ | |
| --window-size 660 400 \ | |
| --window-pos 200 120 \ | |
| --icon-size 80 \ | |
| --icon "StackFerry.app" 180 220 \ | |
| --hide-extension "StackFerry.app" \ | |
| --app-drop-link 480 220 \ | |
| --no-internet-enable \ | |
| "release-assets/$NEW_DMG" \ | |
| "$DMG_STAGE_DIR" | |
| rm -rf "$DMG_STAGE_DIR" | |
| echo "✅ Styled DMG created: $NEW_DMG" | |
| - name: Verify macOS artifacts | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| APP_PATH="" | |
| for path in \ | |
| "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" \ | |
| "src-tauri/target/release/bundle/macos"; do | |
| if [ -d "$path" ]; then | |
| [ -z "$APP_PATH" ] && APP_PATH=$(find "$path" -maxdepth 1 -name "*.app" -type d | head -1 || true) | |
| fi | |
| done | |
| if [ -z "$APP_PATH" ]; then | |
| echo "❌ No .app found for verification" >&2 | |
| exit 1 | |
| fi | |
| echo "=== Verifying .app: $APP_PATH ===" | |
| codesign --verify --deep --strict --verbose=2 "$APP_PATH" | |
| echo "✅ codesign verification passed" | |
| DMG_PATH=$(find release-assets -maxdepth 1 -name "*.dmg" -type f | head -1 || true) | |
| if [ -n "$DMG_PATH" ]; then | |
| echo "=== Verifying .dmg: $DMG_PATH ===" | |
| hdiutil verify "$DMG_PATH" | |
| echo "✅ .dmg integrity verification passed" | |
| else | |
| echo "❌ No .dmg found for verification" >&2 | |
| exit 1 | |
| fi | |
| - name: Prepare Windows Assets | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| WINDOWS_RELEASE_ARCH: ${{ matrix.arch || 'x86_64' }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| New-Item -ItemType Directory -Force -Path release-assets | Out-Null | |
| $VERSION = $env:GITHUB_REF_NAME # e.g., v3.5.0 | |
| $isArm64 = $env:WINDOWS_RELEASE_ARCH -eq 'arm64' | |
| $targetRoot = if ($isArm64) { 'src-tauri/target/aarch64-pc-windows-msvc/release' } else { 'src-tauri/target/release' } | |
| $assetSuffix = if ($isArm64) { '-arm64' } else { '' } | |
| # 打包 MSI 安装器 | |
| $msi = Get-ChildItem -Path (Join-Path $targetRoot 'bundle/msi') -Recurse -Include *.msi -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| if ($null -eq $msi) { | |
| # 兜底:全局搜索 .msi | |
| $msi = Get-ChildItem -Path (Join-Path $targetRoot 'bundle') -Recurse -Include *.msi -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| } | |
| if ($null -ne $msi) { | |
| $dest = "StackFerry-$VERSION-Windows$assetSuffix.msi" | |
| Copy-Item $msi.FullName (Join-Path release-assets $dest) | |
| $signature = "$($msi.FullName).sig" | |
| if (-not (Test-Path $signature)) { | |
| throw "No updater signature found for $($msi.FullName)" | |
| } | |
| Copy-Item $signature (Join-Path release-assets "$dest.sig") | |
| Write-Host "Installer copied: $dest" | |
| } elseif ($isArm64) { | |
| throw 'No Windows ARM64 MSI installer found' | |
| } else { | |
| Write-Warning 'No Windows MSI installer found' | |
| } | |
| # 绿色版(portable):仅可执行文件打 zip(不参与 Updater) | |
| $exeCandidates = if ($isArm64) { | |
| @('src-tauri/target/aarch64-pc-windows-msvc/release/stackferry.exe') | |
| } else { | |
| @( | |
| 'src-tauri/target/release/stackferry.exe', | |
| 'src-tauri/target/x86_64-pc-windows-msvc/release/stackferry.exe' | |
| ) | |
| } | |
| $exePath = $exeCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 | |
| if ($null -ne $exePath) { | |
| $portableDir = 'release-assets/StackFerry-Portable' | |
| New-Item -ItemType Directory -Force -Path $portableDir | Out-Null | |
| Copy-Item $exePath $portableDir | |
| $portableIniPath = Join-Path $portableDir 'portable.ini' | |
| $portableContent = if ($isArm64) { | |
| @( | |
| '# StackFerry portable ARM64 build marker', | |
| 'portable=true', | |
| 'arch=arm64' | |
| ) | |
| } else { | |
| @( | |
| '# StackFerry portable build marker', | |
| 'portable=true' | |
| ) | |
| } | |
| $portableContent | Set-Content -Path $portableIniPath -Encoding UTF8 | |
| $portableZip = "release-assets/StackFerry-$VERSION-Windows$assetSuffix-Portable.zip" | |
| Compress-Archive -Path "$portableDir/*" -DestinationPath $portableZip -Force | |
| Remove-Item -Recurse -Force $portableDir | |
| Write-Host "Windows portable zip created: StackFerry-$VERSION-Windows$assetSuffix-Portable.zip" | |
| } elseif ($isArm64) { | |
| throw 'Portable ARM64 exe not found' | |
| } else { | |
| Write-Warning 'Portable exe not found' | |
| } | |
| - name: Prepare Linux Assets | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| env: | |
| TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| run: | | |
| set -euxo pipefail | |
| mkdir -p release-assets | |
| VERSION="${GITHUB_REF_NAME}" # e.g., v3.5.0 | |
| ARCH="${{ matrix.arch || 'x86_64' }}" | |
| copy_signed_artifact() { | |
| local artifact="$1" | |
| local destination="$2" | |
| if [ ! -f "$artifact.sig" ]; then | |
| pnpm tauri signer sign "$artifact" | |
| fi | |
| if [ ! -s "$artifact.sig" ]; then | |
| echo "No updater signature found for $artifact" >&2 | |
| exit 1 | |
| fi | |
| cp "$artifact" "release-assets/$destination" | |
| cp "$artifact.sig" "release-assets/$destination.sig" | |
| } | |
| # AppImage | |
| APPIMAGE=$(find src-tauri/target/release/bundle -name "*.AppImage" | head -1 || true) | |
| if [ -n "$APPIMAGE" ]; then | |
| NEW_APPIMAGE="StackFerry-${VERSION}-Linux-${ARCH}.AppImage" | |
| copy_signed_artifact "$APPIMAGE" "$NEW_APPIMAGE" | |
| echo "AppImage copied: $NEW_APPIMAGE" | |
| else | |
| echo "No AppImage found under target/release/bundle" >&2 | |
| exit 1 | |
| fi | |
| # deb updater artifact | |
| DEB=$(find src-tauri/target/release/bundle -name "*.deb" | head -1 || true) | |
| if [ -n "$DEB" ]; then | |
| NEW_DEB="StackFerry-${VERSION}-Linux-${ARCH}.deb" | |
| copy_signed_artifact "$DEB" "$NEW_DEB" | |
| echo "Deb package copied: $NEW_DEB" | |
| else | |
| echo "No .deb found under target/release/bundle" >&2 | |
| exit 1 | |
| fi | |
| # rpm updater artifact | |
| RPM=$(find src-tauri/target/release/bundle -name "*.rpm" | head -1 || true) | |
| if [ -n "$RPM" ]; then | |
| NEW_RPM="StackFerry-${VERSION}-Linux-${ARCH}.rpm" | |
| copy_signed_artifact "$RPM" "$NEW_RPM" | |
| echo "RPM package copied: $NEW_RPM" | |
| else | |
| echo "No .rpm found under target/release/bundle" >&2 | |
| exit 1 | |
| fi | |
| - name: List prepared assets | |
| shell: bash | |
| run: | | |
| ls -la release-assets || true | |
| - name: Upload release artifacts to workflow | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-assets-${{ runner.os }}-${{ matrix.arch || runner.arch }} | |
| path: release-assets/* | |
| if-no-files-found: error | |
| - 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 | |
| publish-release: | |
| name: Publish GitHub Release | |
| runs-on: ubuntu-22.04 | |
| needs: release | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "20" | |
| - name: Download built release artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: release-assets-* | |
| path: release-assets | |
| merge-multiple: true | |
| - name: List downloaded release artifacts | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ls -la release-assets | |
| - name: Generate release notes | |
| id: release-notes | |
| shell: pwsh | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| git fetch origin --tags --force | |
| $tag = "${{ github.ref_name }}" | |
| $stableTagPattern = '^v\d+\.\d+\.\d+$' | |
| $previousTag = git tag --list --sort=-version:refname | | |
| Where-Object { | |
| $_ -match $stableTagPattern -and $_ -ne $tag | |
| } | | |
| Where-Object { | |
| git merge-base --is-ancestor $_ $tag 2>$null | |
| $LASTEXITCODE -eq 0 | |
| } | | |
| Select-Object -First 1 | |
| if ($previousTag) { | |
| $range = "$previousTag..$tag" | |
| } else { | |
| $range = $tag | |
| } | |
| $repository = "${{ github.repository }}" | |
| $commitRows = @(git log $range --format="%H%x09%h%x09%s") | |
| function Get-GitHubMention([string]$commitHash) { | |
| $headers = @{ | |
| Accept = "application/vnd.github+json" | |
| Authorization = "Bearer $env:GITHUB_TOKEN" | |
| "X-GitHub-Api-Version" = "2022-11-28" | |
| "User-Agent" = "StackFerry-release-workflow" | |
| } | |
| try { | |
| $commit = Invoke-RestMethod -Headers $headers -Uri "https://api.github.com/repos/$repository/commits/$commitHash" | |
| if ($commit.author -and $commit.author.login) { | |
| return "@$($commit.author.login)" | |
| } | |
| } catch { | |
| } | |
| return $null | |
| } | |
| $body = [System.Collections.Generic.List[string]]::new() | |
| function Add-MarkdownBody([string]$path) { | |
| if (-not (Test-Path $path)) { | |
| return | |
| } | |
| $content = @(Get-Content $path) | |
| if ($content.Count -eq 0) { | |
| return | |
| } | |
| $startIndex = 0 | |
| if ($content[0] -match '^#\s+') { | |
| $startIndex = 1 | |
| while ($startIndex -lt $content.Count -and [string]::IsNullOrWhiteSpace($content[$startIndex])) { | |
| $startIndex++ | |
| } | |
| } | |
| for ($index = $startIndex; $index -lt $content.Count; $index++) { | |
| $body.Add([string]$content[$index]) | |
| } | |
| } | |
| Add-MarkdownBody "RELEASE.md" | |
| if ($body.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($body[$body.Count - 1])) { | |
| $body.Add("") | |
| } | |
| $body.Add("---") | |
| $body.Add("") | |
| $body.Add("### Commits") | |
| $body.Add("") | |
| foreach ($row in $commitRows) { | |
| if ([string]::IsNullOrWhiteSpace($row)) { | |
| continue | |
| } | |
| $parts = $row -split "`t", 3 | |
| if ($parts.Count -lt 3) { | |
| continue | |
| } | |
| $fullHash = $parts[0] | |
| $shortHash = $parts[1] | |
| $subject = $parts[2] | |
| $mention = Get-GitHubMention $fullHash | |
| if ($mention) { | |
| $body.Add("- ${subject}(${shortHash}) $mention") | |
| } else { | |
| $body.Add("- ${subject}(${shortHash})") | |
| } | |
| } | |
| $notesPath = Join-Path $env:RUNNER_TEMP "release-notes.md" | |
| $body | Set-Content $notesPath -Encoding utf8 | |
| "path=$notesPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Generate updater manifest | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| published_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| node scripts/generate-updater-manifest.mjs \ | |
| --assets-dir release-assets \ | |
| --tag "$GITHUB_REF_NAME" \ | |
| --repository "$GITHUB_REPOSITORY" \ | |
| --published-at "$published_at" | |
| - name: Upload Release Assets | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: StackFerry ${{ github.ref_name }} | |
| prerelease: ${{ contains(github.ref_name, '-') }} | |
| body_path: ${{ steps.release-notes.outputs.path }} | |
| files: release-assets/* | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |