Desktop Release · v4.1.0 #25
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: Desktop Release | |
| run-name: Desktop Release · ${{ github.ref_name }} | |
| on: | |
| push: | |
| tags: ['v*'] | |
| concurrency: | |
| group: desktop-release-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| DOTNET_VERSION: '10.0.x' | |
| VELOPACK_VERSION: '1.2.0' | |
| DOTNET_CLI_TELEMETRY_OPTOUT: '1' | |
| DOTNET_NOLOGO: 'true' | |
| jobs: | |
| windows: | |
| name: Windows · ${{ matrix.rid }} | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| strategy: | |
| matrix: | |
| include: | |
| - rid: win-x64 | |
| - rid: win-arm64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v6 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: collection/collectors/Heartbeat.Collector.Browser/package-lock.json | |
| - name: Verify Browser Collector package payload and contracts | |
| shell: bash | |
| run: | | |
| npm ci --prefix collection/collectors/Heartbeat.Collector.Browser | |
| npm run build --prefix collection/collectors/Heartbeat.Collector.Browser | |
| node scripts/collector-contracts.mjs check | |
| - name: Resolve package version | |
| id: version | |
| shell: bash | |
| run: | | |
| TAG="${GITHUB_REF#refs/tags/v}" | |
| echo "version=$TAG" >> "$GITHUB_OUTPUT" | |
| - name: Build Windows app | |
| run: | | |
| dotnet publish collection/desktop/Heartbeat.Desktop.Windows/Heartbeat.Desktop.Windows.csproj ` | |
| -c Release ` | |
| -r ${{ matrix.rid }} ` | |
| --self-contained ` | |
| -p:Version=${{ steps.version.outputs.version }} ` | |
| -o publish | |
| - name: Install Velopack CLI | |
| run: dotnet tool install -g vpk --version ${{ env.VELOPACK_VERSION }} | |
| - name: Fetch Windows delta baseline | |
| id: delta_baseline | |
| continue-on-error: true | |
| run: | | |
| vpk download github ` | |
| --repoUrl https://github.com/${{ github.repository }} ` | |
| --token ${{ secrets.GITHUB_TOKEN }} ` | |
| --channel ${{ matrix.rid }} ` | |
| --outputDir releases | |
| - name: Package Windows release | |
| run: | | |
| vpk pack ` | |
| --packId Heartbeat ` | |
| --packVersion ${{ steps.version.outputs.version }} ` | |
| --packDir publish ` | |
| --mainExe Heartbeat.Desktop.Windows.exe ` | |
| --runtime ${{ matrix.rid }} ` | |
| --channel ${{ matrix.rid }} ` | |
| --outputDir releases | |
| - name: Verify Windows release | |
| shell: bash | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| CHANNEL="${{ matrix.rid }}" | |
| test -f "releases/assets.$CHANNEL.json" | |
| test -f "releases/releases.$CHANNEL.json" | |
| test -f "releases/RELEASES-$CHANNEL" | |
| test -f "releases/Heartbeat-$VERSION-$CHANNEL-full.nupkg" | |
| if [ "${{ steps.delta_baseline.outcome }}" = "success" ]; then | |
| test -f "releases/Heartbeat-$VERSION-$CHANNEL-delta.nupkg" | |
| else | |
| echo "::warning::Delta baseline unavailable for $CHANNEL; publishing a full-only release." | |
| fi | |
| test -f "releases/Heartbeat-$CHANNEL-Portable.zip" | |
| test -f "releases/Heartbeat-$CHANNEL-Setup.exe" | |
| - name: Upload Windows release artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: desktop-${{ matrix.rid }} | |
| path: | | |
| releases/assets.${{ matrix.rid }}.json | |
| releases/releases.${{ matrix.rid }}.json | |
| releases/RELEASES-${{ matrix.rid }} | |
| releases/Heartbeat-${{ steps.version.outputs.version }}-${{ matrix.rid }}-*.nupkg | |
| releases/Heartbeat-${{ matrix.rid }}-Portable.zip | |
| releases/Heartbeat-${{ matrix.rid }}-Setup.exe | |
| if-no-files-found: error | |
| compression-level: 0 | |
| retention-days: 1 | |
| macos: | |
| name: macOS · osx-arm64 | |
| runs-on: macos-15 | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v6 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: collection/collectors/Heartbeat.Collector.Browser/package-lock.json | |
| - name: Verify Browser Collector package payload and contracts | |
| run: | | |
| npm ci --prefix collection/collectors/Heartbeat.Collector.Browser | |
| npm run build --prefix collection/collectors/Heartbeat.Collector.Browser | |
| node scripts/collector-contracts.mjs check | |
| - name: Resolve package version | |
| id: version | |
| shell: bash | |
| run: | | |
| TAG="${GITHUB_REF#refs/tags/v}" | |
| echo "version=$TAG" >> "$GITHUB_OUTPUT" | |
| - name: Run macOS desktop tests | |
| run: dotnet test collection/desktop/Heartbeat.Desktop.Mac.Tests/Heartbeat.Desktop.Mac.Tests.csproj -c Release | |
| - name: Build macOS app | |
| run: | | |
| dotnet publish collection/desktop/Heartbeat.Desktop.Mac/Heartbeat.Desktop.Mac.csproj \ | |
| -c Release \ | |
| -r osx-arm64 \ | |
| --self-contained \ | |
| -p:Version=${{ steps.version.outputs.version }} \ | |
| -o publish-macos | |
| - name: Install Velopack CLI | |
| run: dotnet tool install -g vpk --version ${{ env.VELOPACK_VERSION }} | |
| - name: Fetch macOS delta baseline | |
| id: delta_baseline | |
| continue-on-error: true | |
| run: | | |
| vpk download github \ | |
| --repoUrl https://github.com/${{ github.repository }} \ | |
| --token ${{ secrets.GITHUB_TOKEN }} \ | |
| --channel osx-arm64-stable \ | |
| --outputDir releases-macos | |
| - name: Package unsigned macOS release | |
| run: | | |
| vpk pack \ | |
| --packId Heartbeat \ | |
| --packVersion ${{ steps.version.outputs.version }} \ | |
| --packDir publish-macos \ | |
| --mainExe Heartbeat.Desktop.Mac \ | |
| --packTitle Heartbeat \ | |
| --runtime osx-arm64 \ | |
| --channel osx-arm64-stable \ | |
| --bundleId com.shenxianovo.heartbeat \ | |
| --icon "$GITHUB_WORKSPACE/collection/desktop/Heartbeat.Desktop.Mac/heartbeat.icns" \ | |
| --instWelcome "$GITHUB_WORKSPACE/collection/desktop/Heartbeat.Desktop.Mac/macos/INSTALL.txt" \ | |
| --outputDir releases-macos | |
| - name: Constrain Setup to per-user installation | |
| shell: bash | |
| run: | | |
| SETUP_PATH="releases-macos/Heartbeat-osx-arm64-stable-Setup.pkg" | |
| SETUP_WORK="$RUNNER_TEMP/heartbeat-per-user-setup" | |
| mkdir -p "$SETUP_WORK" | |
| pkgutil --expand "$SETUP_PATH" "$SETUP_WORK/expanded" | |
| sed -i '' 's/enable_localSystem="true"/enable_localSystem="false"/' "$SETUP_WORK/expanded/Distribution" | |
| grep -q 'enable_currentUserHome="true"' "$SETUP_WORK/expanded/Distribution" | |
| grep -q 'enable_localSystem="false"' "$SETUP_WORK/expanded/Distribution" | |
| pkgutil --flatten "$SETUP_WORK/expanded" "$SETUP_WORK/per-user.pkg" | |
| mv "$SETUP_WORK/per-user.pkg" "$SETUP_PATH" | |
| - name: Verify unsigned macOS release | |
| shell: bash | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| test -f releases-macos/Heartbeat-osx-arm64-stable-Setup.pkg | |
| test -f releases-macos/Heartbeat-osx-arm64-stable-Portable.zip | |
| test -f releases-macos/releases.osx-arm64-stable.json | |
| test -f releases-macos/RELEASES-osx-arm64-stable | |
| test -f "releases-macos/Heartbeat-$VERSION-osx-arm64-stable-full.nupkg" | |
| if [ "${{ steps.delta_baseline.outcome }}" = "success" ]; then | |
| test -f "releases-macos/Heartbeat-$VERSION-osx-arm64-stable-delta.nupkg" | |
| else | |
| echo "::warning::Delta baseline unavailable for osx-arm64-stable; publishing a full-only release." | |
| fi | |
| VERIFY_DIR="$RUNNER_TEMP/heartbeat-release-verification" | |
| mkdir -p "$VERIFY_DIR/app" | |
| ditto -x -k releases-macos/Heartbeat-osx-arm64-stable-Portable.zip "$VERIFY_DIR/app" | |
| APP_PATH="$VERIFY_DIR/app/Heartbeat.app" | |
| APP_EXECUTABLE="$APP_PATH/Contents/MacOS/Heartbeat.Desktop.Mac" | |
| UPDATE_EXECUTABLE="$APP_PATH/Contents/MacOS/UpdateMac" | |
| test "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP_PATH/Contents/Info.plist")" = "com.shenxianovo.heartbeat" | |
| file "$APP_EXECUTABLE" | grep -q 'Mach-O 64-bit executable arm64' | |
| test -x "$UPDATE_EXECUTABLE" | |
| test -f "$APP_PATH/Contents/Resources/sq.version" | |
| codesign -d --verbose=4 "$APP_PATH" 2> "$VERIFY_DIR/signing.txt" || true | |
| ! grep -q 'Authority=Developer ID Application' "$VERIFY_DIR/signing.txt" | |
| pkgutil --check-signature releases-macos/Heartbeat-osx-arm64-stable-Setup.pkg > "$VERIFY_DIR/installer-signing.txt" 2>&1 || true | |
| ! grep -q 'Developer ID Installer' "$VERIFY_DIR/installer-signing.txt" | |
| pkgutil --expand releases-macos/Heartbeat-osx-arm64-stable-Setup.pkg "$VERIFY_DIR/pkg" | |
| grep -q 'enable_currentUserHome="true"' "$VERIFY_DIR/pkg/Distribution" | |
| grep -q 'enable_localSystem="false"' "$VERIFY_DIR/pkg/Distribution" | |
| grep -q 'id="com.shenxianovo.heartbeat"' "$VERIFY_DIR/pkg/Distribution" | |
| grep -q 'Heartbeat-.*-osx-arm64-stable-full.nupkg' releases-macos/releases.osx-arm64-stable.json | |
| - name: Upload macOS release artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: desktop-osx-arm64 | |
| path: | | |
| releases-macos/assets.osx-arm64-stable.json | |
| releases-macos/releases.osx-arm64-stable.json | |
| releases-macos/RELEASES-osx-arm64-stable | |
| releases-macos/Heartbeat-${{ steps.version.outputs.version }}-osx-arm64-stable-*.nupkg | |
| releases-macos/Heartbeat-osx-arm64-stable-Portable.zip | |
| releases-macos/Heartbeat-osx-arm64-stable-Setup.pkg | |
| if-no-files-found: error | |
| compression-level: 0 | |
| retention-days: 1 | |
| github-release: | |
| name: GitHub Release | |
| needs: [windows, macos] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download desktop release artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: desktop-* | |
| path: all-releases | |
| merge-multiple: true | |
| - name: Build release notes | |
| shell: bash | |
| run: | | |
| PREV_TAG=$(git describe --tags --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || echo "") | |
| if [ -n "$PREV_TAG" ]; then | |
| RANGE="${PREV_TAG}..${GITHUB_REF_NAME}" | |
| else | |
| RANGE="${GITHUB_REF_NAME}" | |
| fi | |
| NOTES_PATH="$RUNNER_TEMP/release-notes.md" | |
| TAG_REF="refs/heartbeat-release-tags/$GITHUB_REF_NAME" | |
| git fetch --force origin "refs/tags/$GITHUB_REF_NAME:$TAG_REF" | |
| if [ "$(git cat-file -t "$TAG_REF")" = "tag" ]; then | |
| git for-each-ref "$TAG_REF" --format='%(contents)' > "$NOTES_PATH" | |
| else | |
| printf 'Heartbeat %s\n' "$GITHUB_REF_NAME" > "$NOTES_PATH" | |
| fi | |
| COMMIT_COUNT=$(git rev-list --count "$RANGE") | |
| { | |
| echo "" | |
| echo "<details>" | |
| echo "<summary>提交明细(${COMMIT_COUNT} commits)</summary>" | |
| echo "" | |
| git log --pretty=format:"- %s" "$RANGE" | |
| echo "" | |
| echo "</details>" | |
| } >> "$NOTES_PATH" | |
| - name: Publish GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| files: all-releases/* | |
| tag_name: ${{ github.ref_name }} | |
| body_path: ${{ runner.temp }}/release-notes.md |