Files
WRNexusJS/packages/router/src/scan.ts
T
2026-07-12 15:55:18 +05:30

60 lines
1.8 KiB
TypeScript

/**
* Filesystem scanning for the file-based router.
*
* Scanning happens once at startup. We build a table of routes from the files
* that exist on disk; request paths are later matched against that table.
* Crucially, request input is NEVER turned into a file path — this is what
* makes the router immune to path-traversal.
*/
import { existsSync, readdirSync, statSync } from "node:fs";
import { join, relative, sep } from "node:path";
/** Extensions we are willing to load as route modules. */
const ALLOWED_EXTENSIONS = [".ts", ".tsx", ".wrn"] as const;
export interface ScannedFile {
/** Absolute path to the file on disk. */
file: string;
/** Path relative to the scanned base directory, using forward slashes. */
rel: string;
}
function hasAllowedExtension(name: string): boolean {
return ALLOWED_EXTENSIONS.some((ext) => name.endsWith(ext));
}
/** Hidden files/dirs (dotfiles) and underscore-prefixed files are ignored. */
function isIgnored(name: string): boolean {
return name.startsWith(".") || name.startsWith("_");
}
/**
* Recursively collect allowed route files under `baseDir`.
* Returns [] if the directory does not exist (a route kind may be unused).
*/
export function scanDir(baseDir: string): ScannedFile[] {
if (!existsSync(baseDir)) return [];
const out: ScannedFile[] = [];
const walk = (dir: string): void => {
for (const entry of readdirSync(dir)) {
if (isIgnored(entry)) continue;
const abs = join(dir, entry);
const stats = statSync(abs);
if (stats.isDirectory()) {
walk(abs);
} else if (stats.isFile() && hasAllowedExtension(entry)) {
out.push({
file: abs,
rel: relative(baseDir, abs).split(sep).join("/"),
});
}
}
};
walk(baseDir);
return out;
}