mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* ci: upgrade GitHub Actions to Node.js 24-compatible versions Node.js 20 actions are deprecated and will be forced to Node.js 24 starting June 2, 2026. Bump all affected actions: - actions/checkout v4 -> v6 - actions/setup-node v4 -> v6 - actions/cache v4 -> v5 - actions/upload-artifact v4 -> v6 - docker/setup-qemu-action v3 -> v4 - docker/setup-buildx-action v3 -> v4 - docker/login-action v3 -> v4 - docker/metadata-action v5 -> v6 - docker/build-push-action v6 -> v7 * ci: pin all GitHub Actions to full commit SHAs Replace floating @vN tags with immutable SHA refs for supply-chain security. Tags can be moved; SHAs cannot. Each ref includes a # vN comment for readability. Also pins actions/cache@v5, actions/setup-go@v5, and all docker/* actions that were previously using floating tags.
527 lines
20 KiB
YAML
527 lines
20 KiB
YAML
name: 'Build Desktop App'
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
variant:
|
|
description: 'App variant'
|
|
required: true
|
|
default: 'full'
|
|
type: choice
|
|
options:
|
|
- full
|
|
- tech
|
|
draft:
|
|
description: 'Create as draft release'
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
concurrency:
|
|
group: desktop-build-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
|
|
|
jobs:
|
|
build-tauri:
|
|
permissions:
|
|
contents: write
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: 'macos-14'
|
|
args: '--target aarch64-apple-darwin'
|
|
node_target: 'aarch64-apple-darwin'
|
|
label: 'macOS-ARM64'
|
|
timeout: 180
|
|
- platform: 'macos-latest'
|
|
args: '--target x86_64-apple-darwin'
|
|
node_target: 'x86_64-apple-darwin'
|
|
label: 'macOS-x64'
|
|
timeout: 180
|
|
- platform: 'windows-latest'
|
|
args: ''
|
|
node_target: 'x86_64-pc-windows-msvc'
|
|
label: 'Windows-x64'
|
|
timeout: 120
|
|
- platform: 'ubuntu-24.04'
|
|
args: ''
|
|
node_target: 'x86_64-unknown-linux-gnu'
|
|
label: 'Linux-x64'
|
|
timeout: 120
|
|
- platform: 'ubuntu-24.04-arm'
|
|
args: '--target aarch64-unknown-linux-gnu'
|
|
node_target: 'aarch64-unknown-linux-gnu'
|
|
label: 'Linux-ARM64'
|
|
timeout: 120
|
|
|
|
runs-on: ${{ matrix.platform }}
|
|
name: Build (${{ matrix.label }})
|
|
timeout-minutes: ${{ matrix.timeout }}
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Start job timer
|
|
shell: bash
|
|
run: echo "JOB_START_EPOCH=$(date +%s)" >> "$GITHUB_ENV"
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
|
with:
|
|
node-version: '22'
|
|
cache: 'npm'
|
|
|
|
- name: Install Rust stable
|
|
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7
|
|
with:
|
|
toolchain: stable
|
|
targets: ${{ contains(matrix.platform, 'macos') && 'aarch64-apple-darwin,x86_64-apple-darwin' || (matrix.label == 'Linux-ARM64' && 'aarch64-unknown-linux-gnu' || '') }}
|
|
|
|
- name: Rust cache
|
|
uses: swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db
|
|
with:
|
|
workspaces: './src-tauri -> target'
|
|
cache-on-failure: true
|
|
|
|
- name: Install Linux system dependencies
|
|
if: contains(matrix.platform, 'ubuntu')
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
libwebkit2gtk-4.1-dev \
|
|
libappindicator3-dev \
|
|
librsvg2-dev \
|
|
patchelf \
|
|
gstreamer1.0-plugins-base \
|
|
gstreamer1.0-plugins-good \
|
|
gstreamer1.0-plugins-bad \
|
|
gstreamer1.0-plugins-ugly \
|
|
gstreamer1.0-libav \
|
|
gstreamer1.0-gl
|
|
|
|
- name: Install frontend dependencies
|
|
run: npm ci
|
|
|
|
- name: Check version consistency
|
|
run: npm run version:check
|
|
|
|
- name: Bundle Node.js runtime
|
|
shell: bash
|
|
env:
|
|
NODE_VERSION: '22.14.0'
|
|
NODE_TARGET: ${{ matrix.node_target }}
|
|
run: bash scripts/download-node.sh --target "$NODE_TARGET"
|
|
|
|
- name: Verify bundled Node.js payload
|
|
shell: bash
|
|
run: |
|
|
if [ "${{ matrix.node_target }}" = "x86_64-pc-windows-msvc" ]; then
|
|
test -f src-tauri/sidecar/node/node.exe
|
|
ls -lh src-tauri/sidecar/node/node.exe
|
|
else
|
|
test -f src-tauri/sidecar/node/node
|
|
test -x src-tauri/sidecar/node/node
|
|
ls -lh src-tauri/sidecar/node/node
|
|
fi
|
|
|
|
# ── Detect whether Apple signing secrets are configured ──
|
|
- name: Check Apple signing secrets
|
|
if: contains(matrix.platform, 'macos')
|
|
id: apple-signing
|
|
shell: bash
|
|
run: |
|
|
if [ -n "${{ secrets.APPLE_CERTIFICATE }}" ] && [ -n "${{ secrets.APPLE_CERTIFICATE_PASSWORD }}" ] && [ -n "${{ secrets.KEYCHAIN_PASSWORD }}" ]; then
|
|
echo "available=true" >> $GITHUB_OUTPUT
|
|
echo "Apple signing secrets detected"
|
|
else
|
|
echo "available=false" >> $GITHUB_OUTPUT
|
|
echo "No Apple signing secrets — building unsigned"
|
|
fi
|
|
|
|
# ── macOS Code Signing (only when secrets are valid) ──
|
|
- name: Import Apple Developer Certificate
|
|
if: contains(matrix.platform, 'macos') && steps.apple-signing.outputs.available == 'true'
|
|
env:
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
|
run: |
|
|
printf '%s' "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
|
|
CERT_SIZE=$(wc -c < certificate.p12 | tr -d ' ')
|
|
if [ "$CERT_SIZE" -lt 100 ]; then
|
|
echo "::warning::Certificate file too small ($CERT_SIZE bytes) — likely invalid. Skipping signing."
|
|
echo "SKIP_SIGNING=true" >> $GITHUB_ENV
|
|
exit 0
|
|
fi
|
|
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
security default-keychain -s build.keychain
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
security set-keychain-settings -t 3600 -u build.keychain
|
|
security import certificate.p12 -k build.keychain \
|
|
-P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign || {
|
|
echo "::warning::Certificate import failed — building unsigned"
|
|
echo "SKIP_SIGNING=true" >> $GITHUB_ENV
|
|
exit 0
|
|
}
|
|
security set-key-partition-list -S apple-tool:,apple:,codesign: \
|
|
-s -k "$KEYCHAIN_PASSWORD" build.keychain
|
|
|
|
CERT_INFO=$(security find-identity -v -p codesigning build.keychain \
|
|
| grep "Developer ID Application" || true)
|
|
if [ -n "$CERT_INFO" ]; then
|
|
CERT_ID=$(echo "$CERT_INFO" | head -1 | awk -F'"' '{print $2}')
|
|
echo "APPLE_SIGNING_IDENTITY=$CERT_ID" >> $GITHUB_ENV
|
|
echo "Certificate imported: $CERT_ID"
|
|
else
|
|
echo "::warning::No Developer ID certificate found in keychain — building unsigned"
|
|
echo "SKIP_SIGNING=true" >> $GITHUB_ENV
|
|
fi
|
|
|
|
# ── Determine variant ──
|
|
- name: Set build variant
|
|
shell: bash
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
echo "BUILD_VARIANT=${{ github.event.inputs.variant }}" >> $GITHUB_ENV
|
|
else
|
|
echo "BUILD_VARIANT=full" >> $GITHUB_ENV
|
|
fi
|
|
|
|
# ── Build with tauri-action ──
|
|
# Signed builds: only when Apple signing secrets are valid and imported
|
|
# Unsigned builds: fallback when no signing (Windows always uses this path)
|
|
|
|
# ── Build: Full variant (signed) ──
|
|
- name: Build Tauri app (full, signed)
|
|
if: env.BUILD_VARIANT == 'full' && steps.apple-signing.outputs.available == 'true' && env.SKIP_SIGNING != 'true'
|
|
uses: tauri-apps/tauri-action@79c624843491f12ae9d63592534ed49df3bc4adb
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VITE_VARIANT: full
|
|
VITE_DESKTOP_RUNTIME: '1'
|
|
VITE_WS_API_URL: https://worldmonitor.app
|
|
CONVEX_URL: ${{ secrets.CONVEX_URL }}
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
with:
|
|
tagName: v__VERSION__
|
|
releaseName: 'World Monitor v__VERSION__'
|
|
releaseBody: 'See changelog below.'
|
|
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.draft) }}
|
|
prerelease: false
|
|
args: ${{ matrix.args }}
|
|
retryAttempts: 1
|
|
|
|
# ── Build: Full variant (unsigned — no Apple certs) ──
|
|
- name: Build Tauri app (full, unsigned)
|
|
if: env.BUILD_VARIANT == 'full' && (steps.apple-signing.outputs.available != 'true' || env.SKIP_SIGNING == 'true')
|
|
uses: tauri-apps/tauri-action@79c624843491f12ae9d63592534ed49df3bc4adb
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VITE_VARIANT: full
|
|
VITE_DESKTOP_RUNTIME: '1'
|
|
VITE_WS_API_URL: https://worldmonitor.app
|
|
CONVEX_URL: ${{ secrets.CONVEX_URL }}
|
|
with:
|
|
tagName: v__VERSION__
|
|
releaseName: 'World Monitor v__VERSION__'
|
|
releaseBody: 'See changelog below.'
|
|
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.draft) }}
|
|
prerelease: false
|
|
args: ${{ matrix.args }}
|
|
retryAttempts: 1
|
|
|
|
# ── Build: Tech variant (signed) ──
|
|
- name: Build Tauri app (tech, signed)
|
|
if: env.BUILD_VARIANT == 'tech' && steps.apple-signing.outputs.available == 'true' && env.SKIP_SIGNING != 'true'
|
|
uses: tauri-apps/tauri-action@79c624843491f12ae9d63592534ed49df3bc4adb
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VITE_VARIANT: tech
|
|
VITE_DESKTOP_RUNTIME: '1'
|
|
VITE_WS_API_URL: https://worldmonitor.app
|
|
CONVEX_URL: ${{ secrets.CONVEX_URL }}
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
with:
|
|
tagName: v__VERSION__-tech
|
|
releaseName: 'Tech Monitor v__VERSION__'
|
|
releaseBody: 'See changelog below.'
|
|
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.draft) }}
|
|
prerelease: false
|
|
tauriScript: npx tauri
|
|
args: --config src-tauri/tauri.tech.conf.json ${{ matrix.args }}
|
|
retryAttempts: 1
|
|
|
|
# ── Build: Tech variant (unsigned — no Apple certs) ──
|
|
- name: Build Tauri app (tech, unsigned)
|
|
if: env.BUILD_VARIANT == 'tech' && (steps.apple-signing.outputs.available != 'true' || env.SKIP_SIGNING == 'true')
|
|
uses: tauri-apps/tauri-action@79c624843491f12ae9d63592534ed49df3bc4adb
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VITE_VARIANT: tech
|
|
VITE_DESKTOP_RUNTIME: '1'
|
|
VITE_WS_API_URL: https://worldmonitor.app
|
|
CONVEX_URL: ${{ secrets.CONVEX_URL }}
|
|
with:
|
|
tagName: v__VERSION__-tech
|
|
releaseName: 'Tech Monitor v__VERSION__'
|
|
releaseBody: 'See changelog below.'
|
|
releaseDraft: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.draft) }}
|
|
prerelease: false
|
|
tauriScript: npx tauri
|
|
args: --config src-tauri/tauri.tech.conf.json ${{ matrix.args }}
|
|
retryAttempts: 1
|
|
|
|
- name: Verify signed macOS bundle + embedded runtime
|
|
if: contains(matrix.platform, 'macos') && steps.apple-signing.outputs.available == 'true' && env.SKIP_SIGNING != 'true'
|
|
shell: bash
|
|
run: |
|
|
APP_PATH=$(find src-tauri/target -type d -path '*/bundle/macos/*.app' | head -1)
|
|
if [ -z "$APP_PATH" ]; then
|
|
echo "::error::No macOS .app bundle found after build."
|
|
exit 1
|
|
fi
|
|
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
|
NODE_PATH=$(find "$APP_PATH/Contents/Resources" -type f -path '*/sidecar/node/node' | head -1)
|
|
if [ -z "$NODE_PATH" ]; then
|
|
echo "::error::Bundled Node runtime missing from app resources."
|
|
exit 1
|
|
fi
|
|
echo "Verified signed app bundle and embedded Node runtime: $NODE_PATH"
|
|
|
|
- name: Strip GPU libraries from AppImage
|
|
if: contains(matrix.platform, 'ubuntu')
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
APPIMAGETOOL_VERSION: '1.9.1'
|
|
APPIMAGETOOL_SHA256_X86_64: 'ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0'
|
|
APPIMAGETOOL_SHA256_AARCH64: 'f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158'
|
|
run: |
|
|
# --- Deterministic artifact selection ---
|
|
mapfile -t IMAGES < <(find src-tauri/target -path '*/bundle/appimage/*.AppImage')
|
|
if [ ${#IMAGES[@]} -eq 0 ]; then
|
|
echo "No AppImage found, skipping GPU lib strip"
|
|
exit 0
|
|
fi
|
|
if [ ${#IMAGES[@]} -gt 1 ]; then
|
|
echo "::error::Found ${#IMAGES[@]} AppImage files — expected exactly 1"
|
|
printf ' %s\n' "${IMAGES[@]}"
|
|
exit 1
|
|
fi
|
|
APPIMAGE="${IMAGES[0]}"
|
|
echo "Stripping bundled GPU/Wayland libraries from: $APPIMAGE"
|
|
chmod +x "$APPIMAGE"
|
|
|
|
# --- Clean extraction ---
|
|
rm -rf squashfs-root
|
|
"$APPIMAGE" --appimage-extract
|
|
|
|
# --- Strip GPU/Wayland libs (14 named patterns + DRI drivers) ---
|
|
GPU_LIBS=(
|
|
'libEGL.so*'
|
|
'libEGL_mesa.so*'
|
|
'libGLX.so*'
|
|
'libGLX_mesa.so*'
|
|
'libGLdispatch.so*'
|
|
'libGLESv2.so*'
|
|
'libGL.so*'
|
|
'libOpenGL.so*'
|
|
'libglapi.so*'
|
|
'libgbm.so*'
|
|
'libwayland-client.so*'
|
|
'libwayland-server.so*'
|
|
'libwayland-cursor.so*'
|
|
'libwayland-egl.so*'
|
|
)
|
|
REMOVED=0
|
|
for pattern in "${GPU_LIBS[@]}"; do
|
|
while IFS= read -r -d '' f; do
|
|
rm -f "$f"
|
|
echo " Removed: ${f#squashfs-root/}"
|
|
((REMOVED++)) || true
|
|
done < <(find squashfs-root -name "$pattern" -print0)
|
|
done
|
|
# Mesa DRI drivers
|
|
while IFS= read -r -d '' f; do
|
|
rm -f "$f"
|
|
echo " Removed DRI: ${f#squashfs-root/}"
|
|
((REMOVED++)) || true
|
|
done < <(find squashfs-root -path '*/dri/*_dri.so' -print0)
|
|
|
|
echo "Stripped $REMOVED GPU/Wayland library files"
|
|
if [ "$REMOVED" -eq 0 ]; then
|
|
echo "::error::No GPU libraries found to strip — build may have changed"
|
|
exit 1
|
|
fi
|
|
|
|
# --- Download and verify appimagetool (pinned to 1.9.1) ---
|
|
TOOL_ARCH=${{ matrix.label == 'Linux-ARM64' && 'aarch64' || 'x86_64' }}
|
|
TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/${APPIMAGETOOL_VERSION}/appimagetool-${TOOL_ARCH}.AppImage"
|
|
wget -q "$TOOL_URL" -O /tmp/appimagetool
|
|
EXPECTED_SHA=$([ "$TOOL_ARCH" = "x86_64" ] && echo "$APPIMAGETOOL_SHA256_X86_64" || echo "$APPIMAGETOOL_SHA256_AARCH64")
|
|
ACTUAL_SHA=$(sha256sum /tmp/appimagetool | awk '{print $1}')
|
|
if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
|
|
echo "::error::appimagetool SHA256 mismatch! Expected: $EXPECTED_SHA Got: $ACTUAL_SHA"
|
|
exit 1
|
|
fi
|
|
echo "appimagetool SHA256 verified: $ACTUAL_SHA"
|
|
chmod +x /tmp/appimagetool
|
|
|
|
# --- Repackage to temp path, then atomic mv ---
|
|
APPIMAGE_TMP="${APPIMAGE}.stripped.tmp"
|
|
ARCH=$TOOL_ARCH /tmp/appimagetool --appimage-extract-and-run squashfs-root "$APPIMAGE_TMP"
|
|
mv -f "$APPIMAGE_TMP" "$APPIMAGE"
|
|
|
|
# --- Post-repack verification: ALL banned patterns ---
|
|
rm -rf squashfs-root
|
|
"$APPIMAGE" --appimage-extract
|
|
BANNED=""
|
|
for pattern in "${GPU_LIBS[@]}"; do
|
|
found=$(find squashfs-root -name "$pattern" -print 2>/dev/null)
|
|
if [ -n "$found" ]; then BANNED+="$found"$'\n'; fi
|
|
done
|
|
found=$(find squashfs-root -path '*/dri/*_dri.so' -print 2>/dev/null)
|
|
if [ -n "$found" ]; then BANNED+="$found"$'\n'; fi
|
|
rm -rf squashfs-root
|
|
if [ -n "$BANNED" ]; then
|
|
echo "::error::Banned GPU libs still present after repack:"
|
|
echo "$BANNED"
|
|
exit 1
|
|
fi
|
|
echo "Post-repack verification passed — no banned GPU libs"
|
|
|
|
# --- Re-upload stripped AppImage to GitHub Release ---
|
|
VERSION=$(node -p "require('./package.json').version")
|
|
if [ "$BUILD_VARIANT" = "tech" ]; then
|
|
TAG_NAME="v${VERSION}-tech"
|
|
else
|
|
TAG_NAME="v${VERSION}"
|
|
fi
|
|
echo "Computed release tag: $TAG_NAME"
|
|
if gh release view "$TAG_NAME" &>/dev/null; then
|
|
echo "Re-uploading stripped AppImage to release $TAG_NAME"
|
|
gh release upload "$TAG_NAME" "$APPIMAGE" --clobber
|
|
echo "Replaced release asset: $(basename "$APPIMAGE")"
|
|
else
|
|
echo "::warning::Release $TAG_NAME not found — skipping re-upload"
|
|
fi
|
|
|
|
rm -f /tmp/appimagetool
|
|
|
|
- name: Smoke-test AppImage (Linux)
|
|
if: contains(matrix.platform, 'ubuntu')
|
|
shell: bash
|
|
run: |
|
|
sudo apt-get install -y xvfb imagemagick
|
|
APPIMAGE=$(find src-tauri/target -path '*/bundle/appimage/*.AppImage' | head -1)
|
|
if [ -z "$APPIMAGE" ]; then
|
|
echo "::error::No AppImage found after build"
|
|
exit 1
|
|
fi
|
|
chmod +x "$APPIMAGE"
|
|
# Start Xvfb with known display number
|
|
Xvfb :99 -screen 0 1440x900x24 &
|
|
export DISPLAY=:99
|
|
sleep 2
|
|
# Launch AppImage under virtual framebuffer
|
|
"$APPIMAGE" --no-sandbox &
|
|
APP_PID=$!
|
|
# Wait for app to render
|
|
sleep 15
|
|
# Screenshot the virtual display
|
|
import -window root screenshot.png || true
|
|
# Verify app is still running (didn't crash)
|
|
if kill -0 $APP_PID 2>/dev/null; then
|
|
echo "✅ AppImage launched successfully"
|
|
kill $APP_PID || true
|
|
else
|
|
echo "❌ AppImage crashed during startup"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Upload smoke test screenshot
|
|
if: contains(matrix.platform, 'ubuntu')
|
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
|
with:
|
|
name: linux-smoke-test-screenshot-${{ matrix.label }}
|
|
path: screenshot.png
|
|
if-no-files-found: warn
|
|
|
|
- name: Cleanup Apple signing materials
|
|
if: always() && contains(matrix.platform, 'macos')
|
|
shell: bash
|
|
run: |
|
|
rm -f certificate.p12
|
|
security delete-keychain build.keychain || true
|
|
|
|
- name: Report build duration
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
if [ -z "${JOB_START_EPOCH:-}" ]; then
|
|
echo "::warning::JOB_START_EPOCH missing; duration unavailable."
|
|
exit 0
|
|
fi
|
|
END_EPOCH=$(date +%s)
|
|
ELAPSED=$((END_EPOCH - JOB_START_EPOCH))
|
|
MINUTES=$((ELAPSED / 60))
|
|
SECONDS=$((ELAPSED % 60))
|
|
echo "Build duration for ${{ matrix.label }}: ${MINUTES}m ${SECONDS}s"
|
|
|
|
# ── Update release notes with changelog after all builds complete ──
|
|
update-release-notes:
|
|
needs: build-tauri
|
|
if: always() && contains(needs.build-tauri.result, 'success')
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Generate and update release notes
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
shell: bash
|
|
run: |
|
|
VERSION=$(jq -r .version src-tauri/tauri.conf.json)
|
|
TAG="v${VERSION}"
|
|
PREV_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "")
|
|
|
|
if [ -z "$PREV_TAG" ]; then
|
|
COMMITS="Initial release"
|
|
else
|
|
COMMITS=$(git log "${PREV_TAG}..${TAG}" --oneline --no-merges | sed 's/^[a-f0-9]*//' | sed 's/^ /- /')
|
|
fi
|
|
|
|
BODY=$(cat <<NOTES
|
|
## What's Changed
|
|
|
|
${COMMITS}
|
|
|
|
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG:-initial}...${TAG}
|
|
NOTES
|
|
)
|
|
|
|
gh release edit "$TAG" --notes "$BODY"
|
|
echo "Updated release notes for $TAG"
|