test: share runtime scan filters and cached test scans

This commit is contained in:
Peter Steinberger
2026-02-22 12:44:24 +01:00
parent 6dd36a6b77
commit 29cc7f431f
5 changed files with 80 additions and 103 deletions

View File

@@ -2,6 +2,18 @@ import fs from "node:fs/promises";
import path from "node:path";
export const DEFAULT_REPO_SCAN_SKIP_DIR_NAMES = new Set([".git", "dist", "node_modules"]);
export const DEFAULT_RUNTIME_SOURCE_ROOTS = ["src", "extensions"] as const;
export const DEFAULT_RUNTIME_SOURCE_EXTENSIONS = [".ts", ".tsx"] as const;
export const RUNTIME_SOURCE_SKIP_PATTERNS = [
/\.test\.tsx?$/,
/\.test-helpers\.tsx?$/,
/\.test-utils\.tsx?$/,
/\.e2e\.tsx?$/,
/\.d\.ts$/,
/\/(?:__tests__|tests)\//,
/\/[^/]*test-helpers(?:\.[^/]+)?\.tsx?$/,
/\/[^/]*test-utils(?:\.[^/]+)?\.tsx?$/,
] as const;
export type RepoFileScanOptions = {
roots: readonly string[];
@@ -10,10 +22,15 @@ export type RepoFileScanOptions = {
skipHiddenDirectories?: boolean;
shouldIncludeFile?: (relativePath: string) => boolean;
};
export type RuntimeSourceScanOptions = {
roots?: readonly string[];
extensions?: readonly string[];
};
type PendingDir = {
absolutePath: string;
};
const runtimeSourceScanCache = new Map<string, Promise<Array<string>>>();
function shouldSkipDirectory(
name: string,
@@ -29,6 +46,18 @@ function hasAllowedExtension(fileName: string, extensions: readonly string[]): b
return extensions.some((extension) => fileName.endsWith(extension));
}
function normalizeRelativePath(relativePath: string): string {
return relativePath.replaceAll("\\", "/");
}
function toSortedUnique(values: readonly string[]): Array<string> {
return [...new Set(values)].toSorted();
}
function getRuntimeScanCacheKey(repoRoot: string, roots: readonly string[]): string {
return `${repoRoot}::${toSortedUnique(roots).join(",")}`;
}
export async function listRepoFiles(
repoRoot: string,
options: RepoFileScanOptions,
@@ -76,3 +105,34 @@ export async function listRepoFiles(
files.sort((a, b) => a.localeCompare(b));
return files;
}
export function shouldSkipRuntimeSourcePath(relativePath: string): boolean {
const normalizedPath = normalizeRelativePath(relativePath);
return RUNTIME_SOURCE_SKIP_PATTERNS.some((pattern) => pattern.test(normalizedPath));
}
export async function listRuntimeSourceFiles(
repoRoot: string,
options: RuntimeSourceScanOptions = {},
): Promise<Array<string>> {
const roots = options.roots ?? DEFAULT_RUNTIME_SOURCE_ROOTS;
const requestedExtensions = toSortedUnique(
options.extensions ?? DEFAULT_RUNTIME_SOURCE_EXTENSIONS,
);
const cacheKey = getRuntimeScanCacheKey(repoRoot, roots);
let pending = runtimeSourceScanCache.get(cacheKey);
if (!pending) {
pending = listRepoFiles(repoRoot, {
roots,
extensions: DEFAULT_RUNTIME_SOURCE_EXTENSIONS,
skipHiddenDirectories: true,
shouldIncludeFile: (relativePath) => !shouldSkipRuntimeSourcePath(relativePath),
});
runtimeSourceScanCache.set(cacheKey, pending);
}
const files = await pending;
return files.filter((filePath) =>
requestedExtensions.some((extension) => filePath.endsWith(extension)),
);
}