name: build-apps

on:
  pull_request:
  push:
    branches:
      - main
    tags:
      - 'v[0-9]+.[0-9]+.[0-9]+'
  schedule:
    - cron: '0 4 * * *'
  # Daily at 04:00 AM UTC
  # Will checkout the last commit from the default branch (main as of 2023-10-04)

env:
  IS_RELEASE: ${{ github.ref_type == 'tag' }}
  IS_NIGHTLY: ${{ github.event_name == 'schedule' }}

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

jobs:
  prepare-files:
    runs-on: ubuntu-22.04  # seperate job on Ubuntu for easy string manipulations (compared to Windows)
    outputs:
      version: ${{ steps.export_version.outputs.version }}
      notes: ${{ steps.export_notes.outputs.notes }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'yarn'

      - run: yarn install

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: './src/wasm-lib'

      # TODO: see if we can fetch from main instead if no diff at src/wasm-lib
      - name: Run build:wasm
        run: "yarn build:wasm"

      - name: Set nightly version, product name, release notes, and icons
        if: ${{ env.IS_NIGHTLY == 'true' }}
        run: yarn files:flip-to-nightly

      - name: Set release version
        if: ${{ env.IS_RELEASE == 'true' }}
        run: |
          export VERSION=${GITHUB_REF_NAME#v}
          yarn files:set-version

      - uses: actions/upload-artifact@v4
        with:
          name: prepared-files
          path: |
            package.json
            electron-builder.yml
            src/wasm-lib/pkg/wasm_lib*
            release-notes.md
            assets/icon.ico
            assets/icon.png

      - id: export_version
        run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"

      - id: export_notes
        run: echo "notes=`cat release-notes.md`" >> "$GITHUB_OUTPUT"

      - name: Prepare electron-builder.yml file for updater test
        if: ${{ env.IS_RELEASE == 'true' }}
        run: |
          yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test"' electron-builder.yml

      - uses: actions/upload-artifact@v4
        if: ${{ env.IS_RELEASE == 'true' }}
        with:
          name: prepared-files-updater-test
          path: |
            electron-builder.yml


  build-apps:
    needs: [prepare-files]
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: macos-14
            platform: mac
          - os: windows-2022
            platform: win
          - os: ubuntu-22.04
            platform: linux
    runs-on: ${{ matrix.os }}
    env:
      VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/download-artifact@v4
        name: prepared-files

      - name: Copy prepared files
        run: |
          ls -R prepared-files
          cp prepared-files/package.json package.json
          cp prepared-files/electron-builder.yml electron-builder.yml
          cp prepared-files/src/wasm-lib/pkg/wasm_lib_bg.wasm public
          mkdir src/wasm-lib/pkg
          cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
          cp prepared-files/release-notes.md release-notes.md
          cp prepared-files/assets/icon.ico assets/icon.ico
          cp prepared-files/assets/icon.png assets/icon.png

      - name: Sync node version and setup cache
        uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'yarn' # Set this to npm, yarn or pnpm.

      - name: yarn install
        # Windows is picky sometimes and fails on fetch. Step takes about ~30s
        uses: nick-fields/retry@v3.0.0
        with:
          timeout_minutes: 2
          max_attempts: 3
          command: yarn install

      - name: Prepare certificate and variables (Windows only)
        if: ${{ (env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true') && matrix.os == 'windows-2022' }}
        run: |
          echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
          cat /d/Certificate_pkcs12.p12
          echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
          echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
          echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
          echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
          echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
          echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
          echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
          echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
        shell: bash

      - name: Setup certicate with SSM KSP (Windows only)
        if: ${{ (env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true') && matrix.os == 'windows-2022' }}
        run: |
          curl -X GET  https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
          msiexec /i smtools-windows-x64.msi /quiet /qn
          smksp_registrar.exe list
          smctl.exe keypair ls
          C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
          smksp_cert_sync.exe
        shell: cmd

      - name: Build the app (debug)
        if: ${{ env.IS_RELEASE == 'false' && env.IS_NIGHTLY == 'false' }}
        # electron-builder doesn't have a concept of release vs debug,
        # this is just not doing any codesign or release yml generation, and points to dev infra
        run: yarn tronb:package:dev

      - name: Build the app (release)
        if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
        env:
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
          CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
          WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
          DEBUG: "electron-notarize*"
        # TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
        uses: nick-fields/retry@v3.0.0
        with:
          timeout_minutes: 10
          max_attempts: 3
          command: yarn tronb:package:prod

      - name: List artifacts in out/
        run: ls -R out

      - uses: actions/upload-artifact@v4
        with:
          name: out-arm64-${{ matrix.platform }}
          # first two will pick both Zoo Modeling App-$VERSION-arm64-win.exe and Zoo Modeling App-$VERSION-win.exe
          path: |
            out/*-${{ env.VERSION_NO_V }}-win.*
            out/*-${{ env.VERSION_NO_V }}-arm64-win.*
            out/*-arm64-mac.*
            out/*-arm64-linux.*

      - uses: actions/upload-artifact@v4
        with:
          name: out-x64-${{ matrix.platform }}
          path: |
            out/*-x64-win.*
            out/*-x64-mac.*
            out/*-x86_64-linux.*

      - uses: actions/upload-artifact@v4
        if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
        with:
          name: out-yml-${{ matrix.platform }}
          path: |
            out/latest*.yml

      # TODO: add the 'Build for Mac TestFlight (nightly)' stage back

      # The steps below are for updater-test builds, only on release

      - uses: actions/download-artifact@v4
        if: ${{ env.IS_RELEASE == 'true' }}
        name: prepared-files-updater-test

      - name: Copy updated electron-builder.yml file for updater test
        if: ${{ env.IS_RELEASE == 'true' }}
        run: |
          ls -R prepared-files-updater-test
          cp prepared-files-updater-test/electron-builder.yml electron-builder.yml

      - name: Build the app (updater-test)
        if: ${{ env.IS_RELEASE == 'true' }}
        env:
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
          CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
          WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
          DEBUG: "electron-notarize*"
        # TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
        uses: nick-fields/retry@v3.0.0
        with:
          timeout_minutes: 10
          max_attempts: 3
          command: yarn tronb:package:prod

      - uses: actions/upload-artifact@v4
        if: ${{ env.IS_RELEASE == 'true' }}
        with:
          name: updater-test-arm64-${{ matrix.platform }}
          path: |
            out/*-arm64-win.exe
            out/*-arm64-mac.dmg
            out/*-arm64-linux.AppImage

      - uses: actions/upload-artifact@v4
        if: ${{ env.IS_RELEASE == 'true' }}
        with:
          name: updater-test-x64-${{ matrix.platform }}
          path: |
            out/*-x64-win.exe
            out/*-x64-mac.dmg
            out/*-x86_64-linux.AppImage


  upload-apps-release:
    runs-on: ubuntu-22.04
    permissions:
      contents: write
    if: ${{ github.ref_type == 'tag' || github.event_name == 'schedule' }}
    env:
      VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
      VERSION: ${{ format('v{0}', needs.prepare-files.outputs.version) }}
    needs: [prepare-files, build-apps]
    steps:
      - uses: actions/checkout@v4

      - uses: actions/download-artifact@v4
        with:
          name: out-arm64-win
          path: out

      - uses: actions/download-artifact@v4
        with:
          name: out-x64-win
          path: out

      - uses: actions/download-artifact@v4
        with:
          name: out-yml-win
          path: out

      - uses: actions/download-artifact@v4
        with:
          name: out-arm64-mac
          path: out

      - uses: actions/download-artifact@v4
        with:
          name: out-x64-mac
          path: out

      - uses: actions/download-artifact@v4
        with:
          name: out-yml-mac
          path: out

      - uses: actions/download-artifact@v4
        with:
          name: out-arm64-linux
          path: out

      - uses: actions/download-artifact@v4
        with:
          name: out-x64-linux
          path: out

      - uses: actions/download-artifact@v4
        with:
          name: out-yml-linux
          path: out

      - name: Generate the download static endpoint
        env:
          NOTES: ${{ needs.prepare-files.outputs.notes }}
          PUB_DATE: ${{ github.event.repository.updated_at }}
          WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
          URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
        run: |
          RELEASE_DIR=https://${WEBSITE_DIR}
          jq --null-input \
            --arg version "${VERSION}" \
            --arg pub_date "${PUB_DATE}" \
            --arg notes "${NOTES}" \
            --arg mac_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-mac.dmg" \
            --arg mac_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-mac.dmg" \
            --arg windows_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-win.exe" \
            --arg windows_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-win.exe" \
            --arg linux_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-linux.AppImage" \
            --arg linux_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x86_64-linux.AppImage" \
            '{
              "version": $version,
              "pub_date": $pub_date,
              "notes": $notes,
              "platforms": {
                "dmg-arm64": {
                  "url": $mac_arm64_url
                },
                "dmg-x64": {
                  "url": $mac_x64_url
                },
                "exe-arm64": {
                  "url": $windows_arm64_url
                },
                "exe-x64": {
                  "url": $windows_x64_url
                },
                "appimage-arm64": {
                  "url": $linux_arm64_url
                },
                "appimage-x64": {
                  "url": $linux_x64_url
                }
              }
            }' > out/last_download.json
            cat out/last_download.json

      - uses: actions/upload-artifact@v4
        with:
          name: out-download-json
          path: out/last_download.json

      - name: List artifacts
        run: "ls -R out"

      - name: Set more complete nightly release notes
        if: ${{ env.IS_NIGHTLY == 'true' }}
        run: |
          # Note: preferred going this way instead of a full clone in the checkout step,
          # see https://github.com/actions/checkout/issues/1471
          git fetch --prune --unshallow --tags
          export TAG="nightly-${VERSION}"
          export PREVIOUS_TAG=$(git describe --tags --match="nightly-v[0-9]*" --abbrev=0)
          export NOTES=$(./scripts/get-nightly-changelog.sh)
          yarn files:set-notes

      - name: Authenticate to Google Cloud
        if: ${{ env.IS_NIGHTLY == 'true' }}
        uses: 'google-github-actions/auth@v2.1.8'
        with:
          credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'

      - name: Set up Google Cloud SDK
        if: ${{ env.IS_NIGHTLY == 'true' }}
        uses: google-github-actions/setup-gcloud@v2.1.4
        with:
          project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}

      - name: Upload nightly files to public bucket
        if: ${{ env.IS_NIGHTLY == 'true' }}
        uses: google-github-actions/upload-cloud-storage@v2.2.2
        with:
          path: out
          glob: '*'
          parent: false
          destination: 'dl.kittycad.io/releases/modeling-app/nightly'

      - name: Invalidate bucket cache on latest*.yml and last_download.json files
        if: ${{ env.IS_NIGHTLY == 'true' }}
        run: yarn files:invalidate-bucket:nightly

      - name: Tag nightly commit
        if: ${{ env.IS_NIGHTLY == 'true' }}
        uses: actions/github-script@v7
        with:
          script: |
            const { VERSION } = process.env          
            const { owner, repo } = context.repo
            const { sha } = context
            const ref = `refs/tags/nightly-${VERSION}`
            github.rest.git.createRef({ owner, repo, sha, ref })
