name: Desktop release # Builds the menu-bar desktop app installers (Windows .exe + macOS .dmg) and, # on a version tag, attaches them to the GitHub Release. Closes #318 — the # dist:win build config already existed; this is the missing publish step. # # Triggers: # - workflow_dispatch manual build (artifacts only) # - push tags 'v*' build + attach the installers to the release # - pull_request (desktop) build-only smoke test so the Windows build can't # silently rot (only when desktop/CI files change) on: workflow_dispatch: push: tags: - 'v*' pull_request: paths: - 'desktop/**' - '.github/workflows/desktop-release.yml' permissions: contents: write jobs: build: name: Build (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: # Pinned to windows-2022 (VS 2022): node-gyp can't yet parse the VS "18" # toolset on windows-latest, which breaks the better-sqlite3 native build. - os: windows-2022 ebArgs: --win --x64 artifact: windows-installer glob: desktop/dist-electron/*.exe - os: macos-latest ebArgs: --mac --arm64 artifact: macos-dmg glob: desktop/dist-electron/*.dmg steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - name: Install workspace dependencies run: npm install - name: Install desktop dependencies run: npm --prefix desktop install - name: Build client + desktop bundles shell: bash run: | npm run build -w client npm --prefix desktop run build:all # Stage client/dist into desktop/ so electron-builder's extraResources # source stays inside desktop/ (no `../`). Belt-and-suspenders with the # root-hiding below; also makes local `desktop:dist:win` work on Windows. npm --prefix desktop run stage:client - name: Validate macOS signing credentials if: matrix.os == 'macos-latest' && startsWith(github.ref, 'refs/tags/') shell: bash env: CSC_LINK: ${{ secrets.MACOS_CSC_LINK }} APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_KEYCHAIN_PROFILE: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} run: | set -euo pipefail if [ -z "${CSC_LINK:-}" ]; then echo "Missing MACOS_CSC_LINK secret for signed macOS release builds." exit 1 fi has_api_key_creds=false if [ -n "${APPLE_API_KEY:-}" ] && [ -n "${APPLE_API_KEY_ID:-}" ] && [ -n "${APPLE_API_ISSUER:-}" ]; then has_api_key_creds=true fi has_apple_id_creds=false if [ -n "${APPLE_ID:-}" ] && [ -n "${APPLE_APP_SPECIFIC_PASSWORD:-}" ] && [ -n "${APPLE_TEAM_ID:-}" ]; then has_apple_id_creds=true fi has_keychain_creds=false if [ -n "${APPLE_KEYCHAIN_PROFILE:-}" ]; then has_keychain_creds=true fi if [ "$has_api_key_creds" != true ] && [ "$has_apple_id_creds" != true ] && [ "$has_keychain_creds" != true ]; then echo "Missing Apple notarization secrets. Provide APPLE_API_KEY/APPLE_API_KEY_ID/APPLE_API_ISSUER, APPLE_ID/APPLE_APP_SPECIFIC_PASSWORD/APPLE_TEAM_ID, or APPLE_KEYCHAIN_PROFILE." exit 1 fi - name: Package installer shell: bash env: # Pull-request and manual artifact builds stay unsigned. Tagged macOS # release builds must use Developer ID signing + notarization so the # downloaded DMG opens without Gatekeeper's "damaged" quarantine block. CSC_IDENTITY_AUTODISCOVERY: ${{ matrix.os == 'macos-latest' && startsWith(github.ref, 'refs/tags/') && 'true' || 'false' }} CSC_LINK: ${{ secrets.MACOS_CSC_LINK }} CSC_KEY_PASSWORD: ${{ secrets.MACOS_CSC_KEY_PASSWORD }} APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_KEYCHAIN: ${{ secrets.APPLE_KEYCHAIN }} APPLE_KEYCHAIN_PROFILE: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} run: | set -e # Run electron-builder with desktop/ as its own project root. Invoked # from the monorepo root (e.g. via `npm --prefix desktop run`), it walks # up, treats the repo root as the project, and on Windows pulls parent # files (.dockerignore, .env.example, …) into the asar — failing with # " must be under …/desktop/". Running from desktop/ and hiding the # root package.json (whose `workspaces` field triggers the upward scan) # makes desktop/ the detected project root. mv package.json "$RUNNER_TEMP/root-package.json" cd desktop npx --no-install electron-builder ${{ matrix.ebArgs }} - name: Upload installer artifact uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact }} path: ${{ matrix.glob }} if-no-files-found: error - name: Attach installer to release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: files: ${{ matrix.glob }}