name: PR checks
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
permissions:
  contents: read
jobs:
  rust_lint:
    runs-on: ubuntu-latest
    env:
      RUSTFLAGS: '-C target-cpu=skylake'
      SCCACHE_GHA_ENABLED: 'true'
      RUSTC_WRAPPER: sccache
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          submodules: 'true'
      - name: setup sccache
        uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
      - name: Install minimal stable with clippy and rustfmt
        uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
        with:
          toolchain: stable
          override: true
          components: rustfmt, clippy
      - name: Cache apt packages
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: /var/cache/apt/archives
          key: apt-rust-lint-${{ runner.os }}
      - name: Install system dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y pkg-config libfontconfig-dev libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libsoup-3.0-dev
      - name: Format check
        working-directory: apps/readest-app/src-tauri
        run: cargo fmt --check
      - name: Clippy Check
        working-directory: apps/readest-app/src-tauri
        run: cargo clippy -p Readest --no-deps -- -D warnings
      - name: Unit tests
        working-directory: apps/readest-app/src-tauri
        run: cargo test -p Readest --lib

  build_web_app:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          submodules: 'true'

      - name: setup pnpm
        uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6

      - name: setup node
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: 24
          cache: pnpm

      - name: install Dependencies
        working-directory: apps/readest-app
        run: |
          pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors

      - name: run format check
        run: |
          pnpm format:check || (pnpm format && git diff && exit 1)

      # pnpm lint here is web-only (tsgo + biome). The koplugin syntax check
      # (lint:lua) runs in the test_extensions job, which installs LuaJIT only
      # when the koplugin sources changed.
      - name: run lint
        working-directory: apps/readest-app
        run: |
          pnpm lint

      - name: build the web app
        working-directory: apps/readest-app
        run: |
          pnpm build-web && pnpm check:all

      - name: cache playwright browsers
        id: playwright-cache
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}

      - name: install playwright browsers
        working-directory: apps/readest-app
        run: |
          if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = 'true' ]; then
            npx playwright install-deps chromium
          else
            npx playwright install --with-deps chromium
          fi

      - name: run web e2e tests
        id: web_e2e
        working-directory: apps/readest-app
        run: pnpm test:e2e:web

      - name: upload e2e report
        if: ${{ failure() && steps.web_e2e.outcome == 'failure' }}
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: playwright-report
          path: apps/readest-app/playwright-report/
          retention-days: 7

  # The jsdom unit suite is the slowest part of the PR checks, so it is split
  # across two parallel shards (vitest --shard). The browser tests need
  # Playwright and run only on shard 1; koplugin + browser-extension tests
  # moved to the test_extensions job.
  test_web_app:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2]
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          submodules: 'true'

      - name: setup pnpm
        uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6

      - name: setup node
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: 24
          cache: pnpm

      - name: install Dependencies
        working-directory: apps/readest-app
        run: |
          pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors

      # Playwright is only needed by the browser tests, which run on shard 1.
      - name: cache playwright browsers
        if: matrix.shard == 1
        id: playwright-cache
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}

      - name: install playwright browsers
        if: matrix.shard == 1
        working-directory: apps/readest-app
        run: |
          if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = 'true' ]; then
            npx playwright install-deps chromium
          else
            npx playwright install --with-deps chromium
          fi

      - name: run web unit tests (shard ${{ matrix.shard }}/2)
        working-directory: apps/readest-app
        run: pnpm test:pr:web:unit --shard=${{ matrix.shard }}/2

      - name: run web browser tests
        if: matrix.shard == 1
        working-directory: apps/readest-app
        run: pnpm test:browser

  # Browser-extension tests + build always run. The koplugin lint + Lua tests
  # (and the ~45s LuaJIT/busted install they need) only run when the koplugin
  # sources changed, so most PRs skip that cost entirely.
  test_extensions:
    runs-on: ubuntu-latest
    # pull-requests: read lets dorny/paths-filter list a PR's changed files
    # via the REST API (the top-level grant is contents: read only).
    permissions:
      contents: read
      pull-requests: read
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          submodules: 'true'

      - name: detect koplugin changes
        id: changes
        uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4
        with:
          filters: |
            koplugin:
              - 'apps/readest.koplugin/**'

      - name: setup pnpm
        uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6

      - name: setup node
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: 24
          cache: pnpm

      - name: install Dependencies
        working-directory: apps/readest-app
        run: |
          pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors

      - name: run extension tests
        working-directory: apps/readest-app
        run: pnpm test:extension

      - name: build browser extension
        working-directory: apps/readest-app
        run: pnpm build-browser-ext

      - name: cache apt packages
        if: steps.changes.outputs.koplugin == 'true'
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: /var/cache/apt/archives
          key: apt-test-koplugin-${{ runner.os }}

      - name: install LuaJIT + busted (for koplugin lint + tests)
        if: steps.changes.outputs.koplugin == 'true'
        run: |
          sudo apt-get update
          # luajit  — pnpm lint:lua + pnpm test:lua
          # luarocks/libsqlite3-dev — required to build lsqlite3complete
          sudo apt-get install -y luajit luarocks libsqlite3-dev
          # Install busted + the SQLite binding the LibraryStore specs use,
          # both pinned to Lua 5.1 (LuaJIT-compatible). System-wide install
          # so `luarocks --lua-version=5.1 path` (sourced by
          # scripts/test-koplugin.mjs) picks them up.
          sudo luarocks --lua-version=5.1 install busted
          sudo luarocks --lua-version=5.1 install lsqlite3complete

      - name: lint koplugin
        if: steps.changes.outputs.koplugin == 'true'
        working-directory: apps/readest-app
        run: pnpm lint:lua

      - name: run koplugin tests
        if: steps.changes.outputs.koplugin == 'true'
        working-directory: apps/readest-app
        run: pnpm test:lua

  build_tauri_app:
    runs-on: ubuntu-latest
    env:
      SCCACHE_GHA_ENABLED: 'true'
      RUSTC_WRAPPER: sccache
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          submodules: 'true'

      - name: setup pnpm
        uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6

      - name: setup node
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
        with:
          node-version: 24
          cache: pnpm

      # The tauri tests run `next dev`, whose Turbopack cache lives in
      # `.next/dev/cache` (a different path from the `next build` cache).
      - name: cache Turbopack dev cache
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: apps/readest-app/.next/dev/cache
          key: turbo-dev-tauri-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
          restore-keys: |
            turbo-dev-tauri-${{ runner.os }}-

      - name: install Dependencies
        working-directory: apps/readest-app
        run: |
          pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors

      - name: setup sccache
        uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10

      - name: install Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
        with:
          toolchain: stable
          # Disable this action's built-in rust-cache so the explicit
          # Swatinem/rust-cache below is the single cache (it's the one
          # configured with cache-workspace-crates for the vendored tauri fork).
          cache: false

      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          # cache-workspace-crates caches the path/workspace crates too: the
          # vendored tauri fork (packages/tauri, packages/tauri-plugins, wired
          # via [patch.crates-io]) and the local src-tauri/plugins/*. These are
          # workspace members that rust-cache prunes by default, so the whole
          # tauri stack — plus every crates.io plugin that depends on the
          # patched `tauri` — rebuilt on every run. They change only
          # sporadically (pinned submodules), so caching them is a big win.
          # The key is bumped (-ws) so the old workspace-crate-less cache is
          # invalidated and the next run repopulates it with the workspace
          # crates included.
          key: tauri-cargo-ws
          cache-all-crates: 'true'
          cache-workspace-crates: 'true'

      - name: Cache apt packages
        uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
        with:
          path: /var/cache/apt/archives
          key: apt-tauri-${{ runner.os }}
      - name: install system dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y pkg-config libfontconfig-dev libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libsoup-3.0-dev xvfb

      - name: run tauri tests
        working-directory: apps/readest-app
        run: xvfb-run pnpm test:pr:tauri
