/** * Shared filesystem helpers. */ import { lstatSync, readdirSync, statSync } from 'node:fs' import { join } from 'node:path' /** * Recursively collect files matching an extension filter. * Single sort at the end — no intermediate sorting during recursion. */ export function walkDir(dir: string, extensions: string[]): string[] { const results: string[] = [] collect(dir, extensions, results) return results.sort() } function collect(dir: string, extensions: string[], out: string[]) { let entries try { entries = readdirSync(dir, { withFileTypes: true }) } catch { return } for (const entry of entries) { const full = join(dir, entry.name) if (entry.isDirectory()) { collect(full, extensions, out) } else if (extensions.some((ext) => entry.name.endsWith(ext))) { out.push(full) } } } /** Check if path is a directory — single syscall, no throw. */ export function isDir(path: string): boolean { try { return statSync(path).isDirectory() } catch { return false } } /** Check if path is a file — single syscall, no throw. */ export function isFile(path: string): boolean { try { return statSync(path).isFile() } catch { return false } } /** Check if path is a symbolic link — uses lstat, no throw. */ export function isSymlink(path: string): boolean { try { return lstatSync(path).isSymbolicLink() } catch { return false } }