Skip to content

Desktop Release · v4.1.0 #25

Desktop Release · v4.1.0

Desktop Release · v4.1.0 #25

Workflow file for this run

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