name: ESLint Code Quality Checks on: pull_request: branches: - main - dev - dev-staging - release/* paths: - 'api/**' - 'client/**' - 'packages/**' - '.github/workflows/eslint-ci.yml' - '!**.md' jobs: eslint_checks: name: Run ESLint Linting runs-on: ubuntu-latest permissions: contents: read security-events: write actions: read steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Node.js 24.16.0 uses: actions/setup-node@v4 with: node-version: '24.16.0' cache: npm - name: Install dependencies run: npm ci # Run ESLint on changed files within the api/, client/, and packages/ directories. - name: Run ESLint on changed files run: | # Extract the base commit SHA from the pull_request event payload. BASE_SHA=$(jq --raw-output .pull_request.base.sha "$GITHUB_EVENT_PATH") echo "Base commit SHA: $BASE_SHA" # Get changed files (only JS/TS files in api/, client/, or packages/) mapfile -d '' -t CHANGED_FILES < <( git diff -z --name-only --diff-filter=ACMRTUXB "$BASE_SHA" HEAD | grep -zE '^(api|client|packages)/.*\.(js|jsx|ts|tsx)$' || true ) # Debug output echo "Changed files:" printf '%s\n' "${CHANGED_FILES[@]}" # Ensure there are files to lint before running ESLint if [[ ${#CHANGED_FILES[@]} -eq 0 ]]; then echo "No matching files changed. Skipping ESLint." exit 0 fi # Run ESLint # --no-warn-ignored: changed files under config-ignored paths # (e.g. packages/data-schemas/misc/**) must not fail --max-warnings=0 npx eslint --no-error-on-unmatched-pattern \ --config eslint.config.mjs \ --no-warn-ignored \ --max-warnings=0 \ -- "${CHANGED_FILES[@]}" # Run Prettier --check on the same set of changed files to catch # formatting drift in PRs that bypassed the local pre-commit hook # (e.g. GitHub UI edit-and-merge, `git commit --no-verify`). - name: Run Prettier --check on changed files run: | BASE_SHA=$(jq --raw-output .pull_request.base.sha "$GITHUB_EVENT_PATH") mapfile -d '' -t CHANGED_FILES < <( git diff -z --name-only --diff-filter=ACMRTUXB "$BASE_SHA" HEAD | grep -zE '^(api|client|packages)/.*\.(js|jsx|ts|tsx)$' || true ) if [[ ${#CHANGED_FILES[@]} -eq 0 ]]; then echo "No matching files changed. Skipping Prettier." exit 0 fi echo "Files to check:" printf '%s\n' "${CHANGED_FILES[@]}" # `prettier --check` exits non-zero if any file would be reformatted. # Suggest the local fix in the failure message so contributors aren't # left guessing how to resolve. if ! npx prettier --check --no-error-on-unmatched-pattern -- "${CHANGED_FILES[@]}"; then echo "" echo "::error::Prettier formatting drift detected. Fix locally with:" echo "::error:: npx prettier --write " echo "::error::Or rely on the lint-staged pre-commit hook (do not bypass with --no-verify)." exit 1 fi # Verify import ordering on the same set of changed files. The script # only sorts files under known source roots, so unrelated changed files # (configs, etc.) are ignored. Matches the lint-staged pre-commit hook. - name: Check import sorting on changed files run: | BASE_SHA=$(jq --raw-output .pull_request.base.sha "$GITHUB_EVENT_PATH") mapfile -d '' -t CHANGED_FILES < <( git diff -z --name-only --diff-filter=ACMRTUXB "$BASE_SHA" HEAD | grep -zE '^(api|client|packages)/.*\.(js|jsx|ts|tsx)$' || true ) if [[ ${#CHANGED_FILES[@]} -eq 0 ]]; then echo "No matching files changed. Skipping import-sort check." exit 0 fi echo "Files to check:" printf '%s\n' "${CHANGED_FILES[@]}" # `--check` lists offending files and exits non-zero without writing. if ! node scripts/sort-imports.mts --check "${CHANGED_FILES[@]}"; then echo "" echo "::error::Import order drift detected. Fix locally with:" echo "::error:: npm run sort-imports" echo "::error::For specific files:" echo "::error:: npm run sort-imports -- packages/api/src/app/metrics.ts packages/api/src/rum/proxy.ts" echo "::error::To check without writing files:" echo "::error:: npm run sort-imports:check" echo "::error::Or rely on the lint-staged pre-commit hook (do not bypass with --no-verify)." exit 1 fi