.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:
workflows:
name: Validate workflows
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ inputs.ref || github.sha }}
- name: Run actionlint
shell: bash
run: |
directory="$(mktemp -d)"
trap 'rm -rf "$directory"' EXIT
curl --fail --silent --show-error --location \
https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz \
--output "$directory/actionlint.tar.gz"
echo "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 $directory/actionlint.tar.gz" | sha256sum --check --strict
tar -xzf "$directory/actionlint.tar.gz" -C "$directory" actionlint
"$directory/actionlint" -color
build:
needs: workflows
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ inputs.ref || github.sha }}
fetch-depth: 0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: .nvmrc
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
# The serial suite takes about seven minutes on hosted Intel macOS runners.
timeout-minutes: 12
- 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@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: installer-${{ matrix.platform }}
path: ${{ matrix.artifact }}
if-no-files-found: error
retention-days: 1
compression-level: 0
overwrite: true