Files
OmniRoute/scripts/quality/select-impacted-tests.mjs
backryun d1d75fdbf4 ci(quality): cut PR gate wall time without dropping protection (#6716)
Collapse duplicate CI spend while keeping each gate's existence reason:

- quality.yml: TIA __RUN_ALL__ defers full unit to fast-unit 4-shard (#6781);
  path filters via classify-pr-changes; docs-gates split; draft skip
- ci.yml: wire docs/i18n/code path filters; ESLint JSON artifact for quality-gate;
  drop advisory typecheck:noimplicit; float actions/cache@v6
- TIA parity: memory/usage/combo/serial; **/*.test.mjs any depth; electron/bin
  no longer force unit __RUN_ALL__
- check:complexity-ratchets: one ESLint walk, ruleId-isolated baselines + cache
- check:api-docs-refs + lib/apiRoutes: shared API route inventory
- husky pre-push: intentionally light (gates live in pre-commit); CLAUDE.md +
  QUALITY_GATES.md docs synced
- collect-metrics / lint:json: path.resolve cache path; Windows-safe eslint bin
- env-doc allowlist for ESLINT_RESULTS_JSON / COMPLEXITY_ESLINT_REPORT
- release-green --full-ci expects check:api-docs-refs (not docs-symbols alone)

Tests: select-impacted, classify-pr-changes, api-routes lib, complexity-rule-count,
validate-release-green.

Reconciled after #6781 (fast-unit 2→4 shards) per maintainer request on #6716.

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
2026-07-11 08:03:11 -03:00

67 lines
2.7 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const HUB_RE = /(setupPolyfill|tsconfig|package\.json|package-lock\.json|\.env|vitest\.config|stryker\.conf)/;
// A changed file counts as a "run-it" test ONLY if it is a node:test unit file the TIA
// step can actually run via `node --test` — i.e. it mirrors the `npm run test:unit` glob.
// This EXCLUDES vitest files (`.test.tsx`, `tests/unit/autoCombo/**`), e2e and integration
// tests, and `src/**/__tests__`/`open-sse/**/__tests__`, which can't run under node:test.
// Keep in sync with package.json test:unit* braces + serial + dashboard + *.test.mjs.
const UNIT_SUBDIRS =
"api|auth|authz|build|cli|cli-helper|combo|compression|correctness|cors|dashboard|db|db-adapters|docs|gamification|guardrails|lib|mcp|memory|runtime|security|services|settings|shared|ui|usage|serial";
// .ts: top-level + UNIT_SUBDIRS (mirrors package.json brace globs).
// .mjs: package.json uses tests/unit/**/*.test.mjs (any depth under tests/unit).
const TEST_RE = new RegExp(
`^tests/unit/([^/]+\\.test\\.(ts|mjs)$|(${UNIT_SUBDIRS})/.*\\.test\\.(ts|mjs)$|.*\\.test\\.mjs$)`
);
export function selectImpacted({ changed, map }) {
const out = new Set();
for (const f of changed) {
if (HUB_RE.test(f)) return ["__RUN_ALL__"];
if (TEST_RE.test(f)) {
out.add(f);
continue;
}
// Impact map only indexes imports under src/ + open-sse/. electron/ and bin/
// are not unit-mapped; treating them as unmapped used to force __RUN_ALL__ and
// a full unit suite for pure CLI/desktop PRs. Package/smoke jobs cover those.
const isSource = f.startsWith("src/") || f.startsWith("open-sse/");
if (!isSource) continue;
const hits = map.sources[f];
if (!hits) return ["__RUN_ALL__"];
hits.forEach((t) => out.add(t));
}
return [...out].sort();
}
function changedFiles() {
const baseRef = process.env.GITHUB_BASE_REF;
const baseTarget = process.env.GITHUB_BASE_SHA || (baseRef ? `origin/${baseRef}` : "HEAD~1");
const stdout = execFileSync(
"git",
["diff", "--name-only", "--diff-filter=ACMR", `${baseTarget}...HEAD`],
{ cwd: ROOT, encoding: "utf8" }
);
return stdout
.split("\n")
.map((s) => s.trim())
.filter(Boolean);
}
if (import.meta.url === `file://${process.argv[1]}`) {
const mapPath = path.join(ROOT, "config/quality/test-impact-map.json");
let map;
try {
map = JSON.parse(fs.readFileSync(mapPath, "utf8"));
} catch {
console.log("__RUN_ALL__");
process.exit(0);
}
const sel = selectImpacted({ changed: changedFiles(), map });
process.stdout.write(sel.join("\n") + "\n");
}