name: Release (macOS)

# Builds + signs + notarizes the macOS DMGs (arm64 + x64) and uploads them to the
# GitHub Release matching electron/package.json's version. This is the macOS half
# of the unified release: it triggers on the SAME `v*` tag as
# release-windows.yml, so one tag fans out to two parallel platform jobs that
# both check out the same commit. Because each build stamps build-info.json from
# `git rev-parse HEAD`, the SHA in the shipped DMG and EXE are identical.
#
# Required repository secrets (Settings -> Secrets and variables -> Actions):
#   APPLE_ID                      Apple Developer account email (notarization)
#   APPLE_APP_SPECIFIC_PASSWORD   app-specific password for that Apple ID
#   APPLE_TEAM_ID                 Apple Developer Team ID
#   CSC_LINK                      base64-encoded Developer ID Application .p12
#   CSC_KEY_PASSWORD              password for that .p12
#   EVS_ACCOUNT_NAME              castlabs EVS account name (Widevine VMP signing; free signup)
#   EVS_PASSWD                    password for that EVS account
#   GOOGLE_OAUTH_CLIENT_ID        shipped in production .env (Google OAuth)
#   GOOGLE_OAUTH_CLIENT_SECRET    shipped in production .env (Google OAuth)
#
# NOTE: untested in CI as of authoring. Verify the secrets above are present and
# do one dry run with workflow_dispatch publish=false before relying on a tag.

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      publish:
        description: 'Publish to GitHub Releases (otherwise artifact only)'
        required: true
        default: 'false'
        type: choice
        options:
          - 'false'
          - 'true'

permissions:
  contents: write

jobs:
  # Release-readiness gate (mirrors release-windows): the dogfood loop must have validated 12 consecutive clean runs per platform OR the v* tag halts before any DMG is built.
  release-gate:
    if: false  # bypassed through v1.2.75: dogfood loop not yet at 12 clean runs, shipping Mac now (mirrors release-windows); remove this line to re-arm the gate
    runs-on: ubuntu-latest
    permissions: { contents: read, actions: read }
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20.18.1' }
      - name: Fetch latest preflight-tunings from dogfood
        env:
          GH_TOKEN: ${{ github.token }}
        shell: bash
        run: |
          set -e
          run_id=$(gh run list --workflow dogfood.yml --branch eric/lock --limit 1 --json databaseId --jq '.[0].databaseId' || true)
          if [ -z "$run_id" ]; then echo "no dogfood runs yet; release cannot proceed"; exit 1; fi
          gh run download "$run_id" --name preflight-tunings --dir scripts/ci/ || { echo "no preflight-tunings artifact"; exit 1; }
      - name: Verify release readiness
        shell: bash
        run: node scripts/ci/verify-release-readiness.js

  build-macos:
    runs-on: macos-latest
    timeout-minutes: 90

    env:
      APPLE_ID:                   ${{ secrets.APPLE_ID }}
      APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
      APPLE_TEAM_ID:              ${{ secrets.APPLE_TEAM_ID }}
      CSC_LINK:                   ${{ secrets.CSC_LINK }}
      CSC_KEY_PASSWORD:           ${{ secrets.CSC_KEY_PASSWORD }}
      EVS_ACCOUNT_NAME:           ${{ secrets.EVS_ACCOUNT_NAME }}
      EVS_PASSWD:                 ${{ secrets.EVS_PASSWD }}
      PUBLISH_INPUT:              ${{ github.event.inputs.publish }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          # Exact pin to match the bundled runtime + the Windows job.
          node-version: '20.18.1'

      - name: Setup Python (for building bundled python-env)
        uses: actions/setup-python@v5
        with:
          python-version: '3.13'

      # Widevine VMP signing tool. The afterPack hook invokes `castlabs_evs.vmp
      # sign-pkg` with the EVS_* secrets; without this the build aborts (publish
      # path sets VMP_REQUIRE_SIGN=1) rather than ship a DMG with dead Spotify DRM.
      - name: Install castlabs-evs (Widevine VMP signing)
        if: ${{ env.APPLE_ID != '' }}
        shell: bash
        run: python3 -m pip install --upgrade castlabs-evs

      - name: Build app
        # Skip (green) when Apple signing secrets aren't in CI: Mac ships via local
        # publish.sh, so a secret-less CI run should no-op, not fail red.
        if: ${{ env.APPLE_ID != '' }}
        shell: bash
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GOOGLE_OAUTH_CLIENT_ID:     ${{ secrets.GOOGLE_OAUTH_CLIENT_ID }}
          GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.GOOGLE_OAUTH_CLIENT_SECRET }}
        run: |
          set -euo pipefail
          should_publish=false
          if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then should_publish=true; fi
          if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" && "$PUBLISH_INPUT" == "true" ]]; then should_publish=true; fi

          version="$(node -p "require('./electron/package.json').version")"
          if [[ "$version" == *-* ]]; then
            # Do NOT set EP_PRE_RELEASE: the GH pre-release checkbox hides the release
            # from the releases.atom feed electron-updater reads, so even experimental
            # clients can't discover it (matches release-windows.yml fix).
            echo "Version $version is EXPERIMENTAL (semver-suffix channel; NOT a GH pre-release)"
          else
            echo "Version $version is STABLE"
          fi

          if $should_publish; then
            echo "Build mode: PUBLISH"
            bash scripts/build-app.sh --publish
          else
            echo "Build mode: SIGN (artifact only)"
            bash scripts/build-app.sh --sign
          fi

      # Gatekeeper gate: after build-app.sh signs + notarizes, prove the shipped
      # .app is codesign-valid (--deep --strict), Gatekeeper-accepted (spctl
      # --assess), and carries a stapled notarization ticket. An app that built but
      # didn't notarize launches to a Gatekeeper block on every user's Mac, so that
      # must fail the release here. --require-signed exits non-zero unless all hold.
      # NOTE: like the rest of this workflow, this mac path is unverified locally
      # (no Mac on hand); first exercise it via workflow_dispatch publish=false.
      - name: Verify the shipped app is signed + notarized
        if: ${{ env.APPLE_ID != '' }}
        shell: bash
        run: |
          set -euo pipefail
          node scripts/ci/verify-signature.js --require-signed

      - name: Upload artifact (non-publish runs)
        if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish != 'true'
        uses: actions/upload-artifact@v4
        with:
          name: openswarm-macos
          path: |
            electron/dist/*.dmg
            electron/dist/latest-mac.yml
          if-no-files-found: error
          retention-days: 14
