Files
iptvnator/tools/coverage/tier-a-external-references.mjs
T
4grayandClaude Opus 5.5 e8902f472a perf(ci): skip unit coverage on PRs that cannot reach it and persist the Jest cache (#1711)
Pull requests whose changes cannot reach any Tier A test (allowlist checked against declared Tier A inputs and an AST scan of cross-project reads) skip the unit coverage suite; master pushes always run it. Jest's transform cache is persisted with actions/cache: PRs restore only, master pushes start empty and save. Paired CI runs: Tier A 9m04s cold -> 6m09s warm. Nx Cloud is intentionally not used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 07:50:07 +02:00

72 lines
3.1 KiB
JavaScript

/**
* Lists repository files outside a Tier A project that its code refers to by
* path: relative module specifiers and path strings such as
* `'../../../../../.github/workflows/build-and-make.yaml'` or
* `'tools/embedded-mpv/stage-runtime.mjs'`. The unit-coverage scope rule must
* never treat these as skippable; unit-coverage-scope.test.mjs asserts that.
*
* String literals are read from the TypeScript AST, so paths mentioned in
* comments do not count. Imports of other apps/libs code are project-graph
* edges and are left to Nx; node_modules is ignored.
*/
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import path from 'node:path';
import ts from 'typescript';
const SOURCE_FILE = /\.(ts|mts|cts|js|mjs|cjs)$/;
const WORKSPACE_PATH = /^(?:\.github|tools|docs|patches|scripts|snap|resources|build)\/|^[\w.-]+\.(?:json|ya?ml|md|js|cjs|mjs)$/;
function listSources(directory, out = []) {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
if (entry.name === 'node_modules') continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) listSources(fullPath, out);
else if (SOURCE_FILE.test(entry.name)) out.push(fullPath);
}
return out;
}
function stringLiterals(fileName, text) {
const source = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, false);
const literals = [];
const visit = (node) => {
if (ts.isStringLiteralLike(node)) literals.push(node.text);
ts.forEachChild(node, visit);
};
visit(source);
return literals;
}
function isOtherProjectCode(relative) {
return /^(apps|libs)\//.test(relative) && !/\.(json|ya?ml|md)$/.test(relative);
}
/** Returns Map<workspace-relative file, Set<referencing file>>. */
export function tierAExternalReferences({ workspaceRoot, tierAProjects }) {
const references = new Map();
const record = (target, from) => {
if (!references.has(target)) references.set(target, new Set());
references.get(target).add(path.relative(workspaceRoot, from));
};
for (const project of tierAProjects) {
const projectRoot = path.resolve(workspaceRoot, project.root);
for (const file of listSources(path.resolve(workspaceRoot, project.sourceRoot))) {
for (const literal of stringLiterals(file, readFileSync(file, 'utf8'))) {
let absolute = null;
if (literal.startsWith('../')) {
absolute = path.resolve(path.dirname(file), literal);
} else if (WORKSPACE_PATH.test(literal)) {
absolute = path.resolve(workspaceRoot, literal);
}
if (!absolute || !existsSync(absolute)) continue;
const relative = path.relative(workspaceRoot, absolute);
if (relative.startsWith('..') || relative.startsWith('node_modules')) continue;
if (absolute === projectRoot || absolute.startsWith(projectRoot + path.sep)) continue;
if (isOtherProjectCode(relative)) continue;
record(relative.split(path.sep).join('/'), file);
}
}
}
return references;
}