.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