import { readdirSync } from "node:fs"; import { join, relative } from "node:path"; import type { ConditionalRule } from "./loader"; import { matchesAny } from "./matcher"; const MAX_FILES = 10_000; const COMMON_IGNORED_DIRS = new Set([ ".cache", ".git", ".hg", ".next", ".parcel-cache", ".svn", ".turbo", ".yarn", "build", "coverage", "dist", "node_modules", "out", "target", ]); function normalizeRelativePath(path: string): string { return path.replace(/\\/g, "/"); } function shouldSkipDirectory(relativePath: string, name: string): boolean { const normalized = normalizeRelativePath(relativePath); if (normalized === ".pi/.cache" || normalized.startsWith(".pi/.cache/")) return true; return COMMON_IGNORED_DIRS.has(name); } export function prescanWorkingTree(cwd: string, rules: ConditionalRule[]): string[] { const activeRules = rules.filter((rule) => rule.when === "always-if-present"); if (activeRules.length === 0) return []; const found = new Set(); let visitedFiles = 0; function walk(dir: string): void { if (visitedFiles >= MAX_FILES) return; let entries; try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; } for (const entry of entries) { if (visitedFiles >= MAX_FILES) return; const absolute = join(dir, entry.name); const relativePath = normalizeRelativePath(relative(cwd, absolute)); if (entry.isDirectory()) { if (!entry.isSymbolicLink() && !shouldSkipDirectory(relativePath, entry.name)) walk(absolute); continue; } if (!entry.isFile()) continue; visitedFiles += 1; if (activeRules.some((rule) => matchesAny(relativePath, rule.globs))) found.add(relativePath); } } walk(cwd); return Array.from(found); }