name: Build & Test

on:
  push:
    branches:
      - master
      - v[0-9]+.[0-9]+.x-staging
      - v[0-9]+.[0-9]+.x
    paths-ignore:
      - README.md
      - .github/**
      - '!.github/workflows/build.yml'
      - '!.github/actions/build-rust/action.yml'
      - '!.github/actions/setup-node/action.yml'
  pull_request:
  merge_group:
    branches:
      - master
      - v[0-9]+.[0-9]+.x-staging
      - v[0-9]+.[0-9]+.x
    paths-ignore:
      - README.md
      - .github/**
      - '!.github/workflows/build.yml'
      - '!.github/actions/build-rust/action.yml'
      - '!.github/actions/setup-node/action.yml'

env:
  DEBUG: napi:*
  APP_NAME: affine
  COVERAGE: true
  MACOSX_DEPLOYMENT_TARGET: '10.13'
  NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    environment: development

    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node
      - name: Run checks
        run: |
          yarn i18n-codegen gen
          yarn typecheck
          yarn lint --max-warnings=0
          yarn circular

  build-docs:
    name: Build Docs
    runs-on: ubuntu-latest
    environment: development

    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node
      - run: yarn nx build @affine/docs
        env:
          NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

  build-storybook:
    name: Build Storybook
    runs-on: ubuntu-latest
    environment: development

    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node
      - run: yarn nx build @affine/storybook
        env:
          NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
      - name: Upload storybook artifact
        uses: actions/upload-artifact@v3
        with:
          name: storybook
          path: ./apps/storybook/storybook-static
          if-no-files-found: error

  build-web:
    name: Build @affine/web
    runs-on: ubuntu-latest
    environment: development
    env:
      RELEASE_VERSION: canary

    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node
      - name: Build Web
        run: yarn nx build @affine/web
      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: next-js
          path: ./apps/web/.next
          if-no-files-found: error

  build-web-desktop:
    name: Build @affine/web (Desktop)
    runs-on: ubuntu-latest
    environment: development
    env:
      ENABLE_BOOKMARK_OPERATION: true
      RELEASE_VERSION: canary

    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node

      - name: Export static resources
        run: yarn nx export @affine/web

      - name: Upload static resources artifact
        uses: actions/upload-artifact@v3
        with:
          name: next-js-static
          path: ./apps/web/out
          if-no-files-found: error

  server-test:
    name: Server Test
    runs-on: ubuntu-latest
    environment: development
    services:
      postgres:
        image: postgres
        env:
          POSTGRES_PASSWORD: affine
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node
      - name: Initialize database
        run: |
          psql -h localhost -U postgres -c "CREATE DATABASE affine;"
          psql -h localhost -U postgres -c "CREATE USER affine WITH PASSWORD 'affine';"
          psql -h localhost -U postgres -c "ALTER USER affine WITH SUPERUSER;"
        env:
          PGPASSWORD: affine
      - name: Generate prisma client
        run: |
          yarn exec prisma generate
          yarn exec prisma db push
        working-directory: apps/server
        env:
          DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
      - name: Run init-db script
        run: yarn exec ts-node-esm ./scripts/init-db.ts
        working-directory: apps/server
        env:
          DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
      - name: Run server tests
        run: yarn nx test:coverage @affine/server
        env:
          DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
      - name: Upload server test coverage results
        uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./apps/server/.coverage/lcov.info
          flags: server-test
          name: affine
          fail_ci_if_error: true

  storybook-test:
    name: Storybook Test
    runs-on: ubuntu-latest
    environment: development
    needs: [build-storybook]
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node
        with:
          playwright-install: true
      - name: Download storybook artifact
        uses: actions/download-artifact@v3
        with:
          name: storybook
          path: ./apps/storybook/storybook-static
      - name: Run storybook tests
        working-directory: ./apps/storybook
        run: |
          yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"

  e2e-test:
    name: E2E Test
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    environment: development
    needs: [build-web, build-storybook]
    services:
      octobase:
        image: ghcr.io/toeverything/cloud-self-hosted:nightly-latest
        ports:
          - 3000:3000
        env:
          SIGN_KEY: 'test123'
          RUST_LOG: 'debug'
          JWST_DEV: '1'

    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node
        with:
          playwright-install: true
      - name: Download artifact
        uses: actions/download-artifact@v3
        with:
          name: next-js
          path: ./apps/web/.next

      - name: Download storybook artifact
        uses: actions/download-artifact@v3
        with:
          name: storybook
          path: ./apps/storybook/storybook-static

      - name: Wait for Octobase Ready
        run: |
          node ./scripts/wait-3000-healthz.mjs

      - name: Run playwright tests
        run: yarn e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
        env:
          COVERAGE: true

      - name: Collect code coverage report
        run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov

      - name: Upload e2e test coverage results
        uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./.coverage/lcov.info
          flags: e2etest
          name: affine
          fail_ci_if_error: true

      - name: Upload test results
        if: ${{ failure() }}
        uses: actions/upload-artifact@v3
        with:
          name: test-results-e2e-${{ matrix.shard }}
          path: ./test-results
          if-no-files-found: ignore

  e2e-migration-test:
    name: E2E Migration Test
    runs-on: ubuntu-latest
    environment: development

    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node
        with:
          playwright-install: true

      - name: Unzip
        run: yarn unzip
        working-directory: ./tests/affine-legacy/0.7.0-canary.18

      - name: Run legacy playwright tests
        run: yarn e2e --forbid-only
        working-directory: ./tests/affine-legacy/0.7.0-canary.18

  desktop-test:
    name: Desktop Test
    runs-on: ${{ matrix.spec.os }}
    environment: development
    strategy:
      fail-fast: false
      # all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
      matrix:
        spec:
          - {
              os: macos-latest,
              platform: macos,
              arch: x64,
              target: x86_64-apple-darwin,
              test: true,
            }
          - {
              os: macos-latest,
              platform: macos,
              arch: arm64,
              target: aarch64-apple-darwin,
              test: false,
            }
          - {
              os: ubuntu-latest,
              platform: linux,
              arch: x64,
              target: x86_64-unknown-linux-gnu,
              test: true,
            }
          - {
              os: windows-latest,
              platform: windows,
              arch: x64,
              target: x86_64-pc-windows-msvc,
              test: true,
            }
    needs: [build-web-desktop]
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node
        with:
          playwright-install: true
      - name: Build AFFiNE native
        uses: ./.github/actions/build-rust
        with:
          target: ${{ matrix.spec.target }}
          nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
      - name: Run unit tests
        if: ${{ matrix.spec.test }}
        shell: bash
        run: yarn nx test @affine/monorepo
        env:
          NATIVE_TEST: 'true'
      - name: Build layers
        run: yarn workspace @affine/electron build
      - name: Download static resource artifact
        uses: actions/download-artifact@v3
        with:
          name: next-js-static
          path: ./apps/electron/resources/web-static

      - name: Run desktop tests
        if: ${{ matrix.spec.test && matrix.spec.os == 'ubuntu-latest' }}
        run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn workspace @affine/electron test
        env:
          COVERAGE: true

      - name: Run desktop tests
        if: ${{ matrix.spec.test && matrix.spec.os != 'ubuntu-latest' }}
        run: yarn workspace @affine/electron test
        env:
          COVERAGE: true

      - name: Collect code coverage report
        if: ${{ matrix.spec.test }}
        run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov

      - name: Upload e2e test coverage results
        if: ${{ matrix.spec.test }}
        uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./.coverage/lcov.info
          flags: e2etest-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
          name: affine
          fail_ci_if_error: true

      - name: Upload test results
        if: ${{ failure() }}
        uses: actions/upload-artifact@v3
        with:
          name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
          path: ./test-results
          if-no-files-found: ignore

  unit-test:
    name: Unit Test
    runs-on: ubuntu-latest
    environment: development
    services:
      octobase:
        image: ghcr.io/toeverything/cloud-self-hosted:nightly-latest
        ports:
          - 3000:3000
        env:
          SIGN_KEY: 'test123'
          RUST_LOG: 'debug'
          JWST_DEV: '1'

    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: ./.github/actions/setup-node

      - name: Unit Test
        run: yarn nx test:coverage @affine/monorepo

      - name: Upload unit test coverage results
        uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./.coverage/store/lcov.info
          flags: unittest
          name: affine
          fail_ci_if_error: true

  build-docker:
    if: github.ref == 'refs/heads/master'
    name: Build Docker
    needs:
      - build-web-desktop
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Download next static
        uses: actions/download-artifact@v3
        with:
          name: next-js-static
          path: ./apps/web/out
      - name: Setup Git short hash
        run: |
          echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          logout: false
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Build front Dockerfile
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          pull: true
          platforms: linux/amd64,linux/arm64
          provenance: true
          file: .github/deployment/front/Dockerfile
          tags: ghcr.io/toeverything/affine-front:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-front:latest

      - name: Setup Node.js
        uses: ./.github/actions/setup-node
        with:
          package-install: false

      - name: Install Node.js dependencies
        run: yarn workspaces focus @affine/server --production

      - name: Build graphql Dockerfile
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          pull: true
          platforms: linux/amd64,linux/arm64
          provenance: true
          file: .github/deployment/node/Dockerfile
          tags: ghcr.io/toeverything/affine-graphql:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-graphql:latest
