name: CI on: push: branches: [development, master] pull_request: branches: [development, master] workflow_dispatch: permissions: contents: read jobs: quality: name: Quality + Smoke runs-on: ubuntu-22.04 timeout-minutes: 45 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js v22 uses: actions/setup-node@v4 with: node-version: 22 - run: corepack enable - name: Install dependencies run: | for i in 1 2 3; do echo "yarn install attempt $i" yarn install --immutable && break sleep 5 [ "$i" = "3" ] && exit 1 done - name: Run Vitest run: yarn test --run - name: Collect coverage run: yarn test:coverage - name: Write coverage badge payload if: github.event_name == 'push' && github.ref == 'refs/heads/master' run: node scripts/write-coverage-badge.mjs - name: Upload coverage badge if: github.event_name == 'push' && github.ref == 'refs/heads/master' uses: actions/upload-artifact@v4 with: name: coverage-badge path: badges/coverage.json if-no-files-found: error - name: Lint run: yarn lint - name: Type check run: yarn type-check - name: Build React app run: yarn build env: CI: '' - name: Build preload script run: yarn build:preload - name: Verify build outputs run: | ls -la build/ || exit 1 ls -la build/electron/preload.cjs || exit 1 ls -la build/index.html || exit 1 - name: Verify forge config run: node -e "import('./forge.config.js').then(c => console.log('Config loaded, makers:', c.default.makers?.length || 0)).catch(e => { console.error(e); process.exit(1); })" - name: Run React Doctor run: yarn doctor - name: Install Chromium for smoke tests run: npx playwright install --with-deps chromium - name: Smoke test built web app run: node scripts/smoke-web-app.js --start-preview - name: Upload coverage summary if: always() uses: actions/upload-artifact@v4 with: name: coverage-summary path: coverage/coverage-summary.json if-no-files-found: warn publish-coverage-badge: name: Publish Coverage Badge runs-on: ubuntu-22.04 needs: quality if: github.event_name == 'push' && github.ref == 'refs/heads/master' permissions: contents: read pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Configure GitHub Pages uses: actions/configure-pages@v5 - name: Download coverage badge uses: actions/download-artifact@v4 with: name: coverage-badge path: ${{ runner.temp }}/coverage-badge - name: Prepare Pages artifact env: BADGE_SOURCE_PATH: ${{ runner.temp }}/coverage-badge/coverage.json PAGES_OUTPUT_PATH: ${{ runner.temp }}/github-pages run: | mkdir -p "${PAGES_OUTPUT_PATH}/badges" cp "${BADGE_SOURCE_PATH}" "${PAGES_OUTPUT_PATH}/badges/coverage.json" touch "${PAGES_OUTPUT_PATH}/.nojekyll" - name: Upload Pages artifact uses: actions/upload-pages-artifact@v4 with: path: ${{ runner.temp }}/github-pages - name: Deploy badge site id: deployment uses: actions/deploy-pages@v4 android-build: name: Android Build runs-on: ubuntu-22.04 needs: quality timeout-minutes: 45 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Java 17 uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '17' - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - name: Setup Node.js v22 uses: actions/setup-node@v4 with: node-version: 22 - run: corepack enable - name: Install dependencies run: | for i in 1 2 3; do echo "yarn install attempt $i" yarn install --immutable && break sleep 5 [ "$i" = "3" ] && exit 1 done - name: Build React app run: yarn build env: CI: '' - name: Sync Capacitor Android project run: npx cap sync android - name: Run Android unit tests and assemble debug APKs run: cd android && ./gradlew testGithubDebugUnitTest testFdroidDebugUnitTest assembleGithubDebug assembleFdroidDebug --stacktrace package-linux: name: Package Linux (Ubuntu) runs-on: ubuntu-22.04 needs: quality steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js v22 uses: actions/setup-node@v4 with: node-version: 22 - run: corepack enable - name: Cache electron binaries uses: actions/cache@v4 with: path: ~/.cache/electron key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }} restore-keys: ${{ runner.os }}-electron-v2- - name: Install dependencies run: | for i in 1 2 3; do echo "yarn install attempt $i" yarn install --immutable && break sleep 5 [ "$i" = "3" ] && exit 1 done - name: Build React app run: yarn build env: CI: '' - name: Build preload script run: yarn build:preload - name: Verify build outputs run: | ls -la build/ || exit 1 ls -la build/electron/preload.cjs || exit 1 ls -la build/index.html || exit 1 - name: Rebuild and verify Electron native modules timeout-minutes: 10 run: yarn electron:prepare-package - name: Package Electron app timeout-minutes: 30 run: | set -o pipefail yarn electron-forge package 2>&1 | tee forge-output.log ls -la out/ - name: Verify executable run: | ls -la out/ EXE=$(node scripts/find-forge-executable.js) echo "Found executable: $EXE" test -f "$EXE" && test -x "$EXE" && echo "Executable is valid" package-mac-intel: name: Package Mac (Intel) runs-on: macos-15-intel needs: quality steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js v22 uses: actions/setup-node@v4 with: node-version: 22 - run: corepack enable - name: Install setuptools for native modules run: pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true - name: Cache electron binaries uses: actions/cache@v4 with: path: ~/Library/Caches/electron key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }} restore-keys: ${{ runner.os }}-electron-v2- - name: Install dependencies run: | for i in 1 2 3; do echo "yarn install attempt $i" yarn install --immutable && break sleep 5 [ "$i" = "3" ] && exit 1 done - name: Build React app run: yarn build env: CI: '' - name: Build preload script run: yarn build:preload - name: Verify build outputs run: | ls -la build/ || exit 1 ls -la build/electron/preload.cjs || exit 1 ls -la build/index.html || exit 1 - name: Rebuild and verify Electron native modules timeout-minutes: 10 run: yarn electron:prepare-package - name: Package Electron app timeout-minutes: 30 run: | set -o pipefail yarn electron-forge package 2>&1 | tee forge-output.log ls -la out/ - name: Verify executable run: | ls -la out/ EXE=$(node scripts/find-forge-executable.js) echo "Found executable: $EXE" test -f "$EXE" && test -x "$EXE" && echo "Executable is valid" package-mac-arm: name: Package Mac (Apple Silicon) runs-on: macos-latest needs: quality steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js v22 uses: actions/setup-node@v4 with: node-version: 22 - run: corepack enable - name: Install setuptools for native modules run: pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true - name: Cache electron binaries uses: actions/cache@v4 with: path: ~/Library/Caches/electron key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }} restore-keys: ${{ runner.os }}-electron-v2- - name: Install dependencies run: | for i in 1 2 3; do echo "yarn install attempt $i" yarn install --immutable && break sleep 5 [ "$i" = "3" ] && exit 1 done - name: Build React app run: yarn build env: CI: '' - name: Build preload script run: yarn build:preload - name: Verify build outputs run: | ls -la build/ || exit 1 ls -la build/electron/preload.cjs || exit 1 ls -la build/index.html || exit 1 - name: Rebuild and verify Electron native modules timeout-minutes: 10 run: yarn electron:prepare-package - name: Package Electron app timeout-minutes: 30 run: | set -o pipefail yarn electron-forge package 2>&1 | tee forge-output.log ls -la out/ - name: Verify executable run: | ls -la out/ EXE=$(node scripts/find-forge-executable.js) echo "Found executable: $EXE" test -f "$EXE" && test -x "$EXE" && echo "Executable is valid" package-windows: name: Package Windows runs-on: windows-2022 needs: quality steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js v22 uses: actions/setup-node@v4 with: node-version: 22 - run: corepack enable - name: Resolve Yarn cache path id: yarn-cache shell: bash run: echo "dir=$(corepack yarn config get cacheFolder)" >> "$GITHUB_OUTPUT" - name: Cache Yarn package tarballs uses: actions/cache@v4 with: path: ${{ steps.yarn-cache.outputs.dir }} key: ${{ runner.os }}-yarn-v1-${{ hashFiles('yarn.lock', 'package.json', '.yarnrc.yml') }} restore-keys: ${{ runner.os }}-yarn-v1- - name: Cache electron binaries uses: actions/cache@v4 with: path: ~/AppData/Local/electron/Cache key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }} restore-keys: ${{ runner.os }}-electron-v2- - name: Install dependencies shell: bash run: | for i in 1 2 3; do echo "yarn install attempt $i" yarn install --immutable && break sleep 5 [ "$i" = "3" ] && exit 1 done - name: Build React app shell: bash run: yarn build env: CI: '' - name: Build preload script shell: bash run: yarn build:preload - name: Verify build outputs shell: bash run: | ls -la build/ || exit 1 ls -la build/electron/preload.cjs || exit 1 ls -la build/index.html || exit 1 - name: Rebuild and verify Electron native modules shell: bash timeout-minutes: 10 run: yarn electron:prepare-package - name: Package Electron app shell: bash timeout-minutes: 30 run: | set -o pipefail yarn electron-forge package 2>&1 | tee forge-output.log ls -la out/ - name: Verify executable shell: bash run: | ls -la out/ EXE=$(node scripts/find-forge-executable.js) echo "Found executable: $EXE" test -f "$EXE" && test -x "$EXE" && echo "Executable is valid"