# .github/workflows/js-tests.yml
name: JS Tests

on:
  workflow_call:

jobs:
  workspaces:
    name: List npm workspaces
    runs-on: ubuntu-latest
    timeout-minutes: 20
    outputs:
      checks: ${{ steps.set-matrix.outputs.checks }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
        with:
          node-version: 26
          cache: npm

      - name: grab npm 12
        run: |
          # No-op once the bundled npm is already 12.x — saves ~5-15s/job and
          # keeps the installed major aligned with the npm12 cache-key tag.
          npm --version | grep -q '^12\.' || npm i -g npm@12

      # ``setup-node``'s ``cache: npm`` only caches the ~/.npm tarball cache;
      # every job still re-extracts the full workspace node_modules and reruns
      # postinstalls (including the Electron binary fetch). Cache the installed
      # tree itself, keyed on the lockfile, and skip ``npm ci`` on an exact
      # hit. No restore-keys: a partial hit would leave a stale tree, so
      # anything but an exact lockfile match reinstalls from scratch.
      # The discovery job installs with --ignore-scripts, so its tree differs
      # from the check jobs' — hence the distinct ``-noscripts`` key.
      - name: Restore node_modules
        id: node-modules-cache
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: |
            node_modules
            apps/*/node_modules
            ui-tui/node_modules
            ui-tui/packages/*/node_modules
            tests-js/node_modules
            web/node_modules
          key: node-modules-noscripts-${{ runner.os }}-node26-npm12-${{ hashFiles('package-lock.json') }}

      - uses: ./.github/actions/retry
        if: steps.node-modules-cache.outputs.cache-hit != 'true'
        with:
          command: npm ci --ignore-scripts
      - id: set-matrix
        run: |
          node -e '
          const { execSync } = require("child_process");
          const pkgs = JSON.parse(execSync("npm query .workspace", { encoding: "utf-8" }));
          if (pkgs.length === 0) {
            console.error("::error::Workspace discovery produced an empty package list — refusing to emit a zero-length matrix (would skip all JS/TS checks silently).");
            process.exit(1);
          }
          const checks = [];
          for (const pkg of pkgs) {
            const scripts = pkg.scripts || {};
            const subs = Object.keys(scripts).filter(s => /^check:.+$/.test(s));
            if (subs.length > 0) {
              for (const script of subs) {
                checks.push({ package: pkg.location, script });
              }
            } else if (scripts.check) {
              checks.push({ package: pkg.location, script: "check" });
            }
          }
          if (checks.length === 0) {
            console.error("::error::No check scripts found in any workspace package.");
            process.exit(1);
          }
          process.stdout.write("checks=" + JSON.stringify(checks) + "\n");
          ' >> "$GITHUB_OUTPUT"

  check:
    name: ${{ matrix.package }} / ${{ matrix.script }}
    needs: workspaces
    runs-on: ubuntu-latest
    timeout-minutes: 20
    strategy:
      matrix:
        include: ${{ fromJson(needs.workspaces.outputs.checks) }}
      fail-fast: false # report all failures, not just the first one
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
        with:
          node-version: 26
          cache: npm

      - name: grab npm 12
        run: |
          # No-op once the bundled npm is already 12.x — saves ~5-15s/job and
          # keeps the installed major aligned with the npm12 cache-key tag.
          npm --version | grep -q '^12\.' || npm i -g npm@12

      # Same rationale as the discovery job's cache above, but this ``npm ci``
      # runs WITH install scripts, so the tree includes postinstall artifacts
      # (electron's postinstall unpacks its binary into node_modules/electron/
      # dist, which lives inside the cached tree — the ~/.cache/electron
      # download cache is deliberately NOT cached: with npm ci skipped on hit
      # it would never be read, only inflate the archive).
      - name: Restore node_modules
        id: node-modules-cache
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: |
            node_modules
            apps/*/node_modules
            ui-tui/node_modules
            ui-tui/packages/*/node_modules
            tests-js/node_modules
            web/node_modules
          key: node-modules-scripts-${{ runner.os }}-node26-npm12-${{ hashFiles('package-lock.json') }}

      - uses: ./.github/actions/retry
        if: steps.node-modules-cache.outputs.cache-hit != 'true'
        with:
          command: npm ci
      - run: npm run --prefix ${{ matrix.package }} ${{ matrix.script }}
