.github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
workflow_call:
inputs:
ref:
description: Commit or tag to check out and build.
required: false
default: ""
type: string
release_version:
description: Next stable patch version for the automatic release lane.
required: false
default: ""
type: string
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
name: Test and package (${{ matrix.platform }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 20
defaults:
run:
shell: ${{ matrix.shell }}
strategy:
fail-fast: false
matrix:
include:
- platform: windows-x64
runner: windows-latest
shell: pwsh
args: --win nsis --x64
artifact: |
release/*.exe
release/latest.yml
- platform: macos-arm64
runner: macos-15
shell: zsh -e {0}
args: --mac dmg --arm64
artifact: release/*.dmg
- platform: macos-x64
runner: macos-15-intel
shell: zsh -e {0}
args: --mac dmg --x64
artifact: release/*.dmg
- platform: linux-x64
runner: ubuntu-24.04
shell: bash
args: --linux AppImage --x64
artifact: |
release/*.AppImage
release/latest-linux.yml
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
ref: ${{ inputs.ref || github.sha }}
fetch-depth: 0
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
- name: Set build version
env:
EMC2_BUILD_REF: ${{ inputs.ref || github.ref }}
EMC2_RELEASE_VERSION: ${{ inputs.release_version }}
run: node scripts/set-build-version.mjs
- run: npm ci --no-audit --no-fund
- run: npm test
timeout-minutes: 8
- run: npm run build
- name: Check desktop startup
if: runner.os != 'Linux'
timeout-minutes: 1
run: npm run check:desktop -- --user-data-dir="${{ runner.temp }}/emc2-check"
- name: Create installer
if: runner.os != 'macOS'
run: npx --no-install electron-builder ${{ matrix.args }} --publish never
# hdiutil intermittently fails ("Resource busy" / create failed) on hosted macOS runners.
# Retry the DMG build up to 3 times, force-detaching stale volumes between attempts.
- name: Create installer (macOS, with hdiutil retry)
if: runner.os == 'macOS'
run: |
for attempt in 1 2 3; do
if npx --no-install electron-builder ${{ matrix.args }} --publish never; then
exit 0
fi
echo "::warning::electron-builder DMG attempt $attempt failed"
for volume in /Volumes/Emc*(N); do
hdiutil detach -force "$volume" || true
done
rm -f release/*.dmg(N) release/*.blockmap(N)
sleep $((attempt * 10))
done
echo "::error::electron-builder DMG failed after 3 attempts"
exit 1
- name: Verify updater metadata and installer integrity
if: runner.os != 'macOS'
run: node --import tsx scripts/check-release-assets.ts ${{ matrix.platform }}
- name: Upload installer
uses: actions/upload-artifact@v5
with:
name: installer-${{ matrix.platform }}
path: ${{ matrix.artifact }}
if-no-files-found: error
retention-days: 1
compression-level: 0
overwrite: true