Extremely high performant utility for building tools that read the file system, minimizing filesystem and path string munging operations to the greatest degree possible
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive directory tree traversal with multiple interfaces including async/await, streaming, and synchronous options. Provides advanced filtering, symlink following, and performance optimization for large directory structures.
Walk directory trees asynchronously, collecting all results into arrays.
/**
* Walk directory tree asynchronously, returning all entries as array
* @param entry - Starting directory (defaults to current working directory)
* @param opts - Walking options
* @returns Promise resolving to array of all found entries
*/
walk(): Promise<PathBase[]>;
walk(opts: WalkOptionsWithFileTypesTrue | WalkOptionsWithFileTypesUnset): Promise<PathBase[]>;
walk(opts: WalkOptionsWithFileTypesFalse): Promise<string[]>;
walk(opts: WalkOptions): Promise<string[] | PathBase[]>;
walk(entry: string | PathBase): Promise<PathBase[]>;
walk(entry: string | PathBase, opts: WalkOptionsWithFileTypesTrue | WalkOptionsWithFileTypesUnset): Promise<PathBase[]>;
walk(entry: string | PathBase, opts: WalkOptionsWithFileTypesFalse): Promise<string[]>;
walk(entry: string | PathBase, opts: WalkOptions): Promise<PathBase[] | string[]>;Usage Examples:
import { PathScurry } from "path-scurry";
const pw = new PathScurry();
// Basic tree walk
const allEntries = await pw.walk();
console.log(`Found ${allEntries.length} total entries`);
// Walk specific directory
const srcEntries = await pw.walk("./src");
const jsFiles = srcEntries.filter(entry =>
entry.isFile() && entry.name.endsWith(".js")
);
// Get paths as strings
const pathStrings = await pw.walk("./dist", { withFileTypes: false });
console.log("All paths:", pathStrings);
// Walk with filtering
const textFiles = await pw.walk("./docs", {
filter: (entry) => entry.isFile() && /\.(md|txt)$/.test(entry.name)
});
// Follow symbolic links
const allWithSymlinks = await pw.walk({
follow: true,
filter: (entry) => !entry.name.startsWith(".")
});Walk directory trees synchronously with identical API to async version.
/**
* Walk directory tree synchronously, returning all entries as array
* @param entry - Starting directory (defaults to current working directory)
* @param opts - Walking options
* @returns Array of all found entries
*/
walkSync(): PathBase[];
walkSync(opts: WalkOptionsWithFileTypesTrue | WalkOptionsWithFileTypesUnset): PathBase[];
walkSync(opts: WalkOptionsWithFileTypesFalse): string[];
walkSync(opts: WalkOptions): string[] | PathBase[];
walkSync(entry: string | PathBase): PathBase[];
walkSync(entry: string | PathBase, opts: WalkOptionsWithFileTypesUnset | WalkOptionsWithFileTypesTrue): PathBase[];
walkSync(entry: string | PathBase, opts: WalkOptionsWithFileTypesFalse): string[];
walkSync(entry: string | PathBase, opts: WalkOptions): PathBase[] | string[];Usage Examples:
const pw = new PathScurry("/project");
// Synchronous walk
const entries = pw.walkSync("src");
const totalSize = entries
.filter(e => e.isFile())
.reduce((sum, e) => sum + (e.size || 0), 0);
console.log(`Total size: ${totalSize} bytes`);
// Process build directory
const buildFiles = pw.walkSync("./dist", { withFileTypes: false });
const htmlFiles = buildFiles.filter(path => path.endsWith(".html"));
console.log(`Found ${htmlFiles.length} HTML files`);Stream entries as they're discovered for memory-efficient processing of large directory trees.
/**
* Create stream that emits entries as they're discovered
* @param entry - Starting directory (defaults to current working directory)
* @param opts - Walking options
* @returns Minipass stream emitting Path objects or strings
*/
stream(): Minipass<PathBase>;
stream(opts: WalkOptionsWithFileTypesTrue | WalkOptionsWithFileTypesUnset): Minipass<PathBase>;
stream(opts: WalkOptionsWithFileTypesFalse): Minipass<string>;
stream(opts: WalkOptions): Minipass<string | PathBase>;
stream(entry: string | PathBase): Minipass<PathBase>;
stream(entry: string | PathBase, opts: WalkOptionsWithFileTypesUnset | WalkOptionsWithFileTypesTrue): Minipass<PathBase>;
stream(entry: string | PathBase, opts: WalkOptionsWithFileTypesFalse): Minipass<string>;
stream(entry: string | PathBase, opts: WalkOptions): Minipass<string> | Minipass<PathBase>;
/**
* Synchronous version of stream interface
*/
streamSync(): Minipass<PathBase>;
streamSync(opts: WalkOptionsWithFileTypesTrue | WalkOptionsWithFileTypesUnset): Minipass<PathBase>;
streamSync(opts: WalkOptionsWithFileTypesFalse): Minipass<string>;
streamSync(opts: WalkOptions): Minipass<string | PathBase>;Usage Examples:
const pw = new PathScurry();
// Stream processing
const stream = pw.stream("./large-directory");
let fileCount = 0;
stream.on("data", (entry) => {
if (entry.isFile()) {
fileCount++;
if (fileCount % 1000 === 0) {
console.log(`Processed ${fileCount} files so far...`);
}
}
});
stream.on("end", () => {
console.log(`Total files processed: ${fileCount}`);
});
// Pipeline example with filtering
const filteredStream = pw.stream({
filter: (entry) => entry.isFile() && entry.name.endsWith(".log")
});
filteredStream.on("data", (logFile) => {
console.log(`Found log file: ${logFile.fullpath()}`);
});
// Synchronous stream (completes in single tick if fully consumed)
const syncStream = pw.streamSync("./config");
const configFiles: string[] = [];
syncStream.on("data", (entry) => {
if (entry.isFile()) {
configFiles.push(entry.fullpath());
}
});
syncStream.on("end", () => {
console.log("Config files:", configFiles);
});Use async iterators for for await loops and functional programming patterns.
/**
* Create async iterator for directory walking
* @param entry - Starting directory (defaults to current working directory)
* @param options - Walking options
* @returns AsyncGenerator yielding Path objects or strings
*/
iterate(): AsyncGenerator<PathBase, void, void>;
iterate(opts: WalkOptionsWithFileTypesTrue | WalkOptionsWithFileTypesUnset): AsyncGenerator<PathBase, void, void>;
iterate(opts: WalkOptionsWithFileTypesFalse): AsyncGenerator<string, void, void>;
iterate(opts: WalkOptions): AsyncGenerator<string | PathBase, void, void>;
iterate(entry: string | PathBase): AsyncGenerator<PathBase, void, void>;
iterate(entry: string | PathBase, opts: WalkOptionsWithFileTypesTrue | WalkOptionsWithFileTypesUnset): AsyncGenerator<PathBase, void, void>;
iterate(entry: string | PathBase, opts: WalkOptionsWithFileTypesFalse): AsyncGenerator<string, void, void>;
iterate(entry: string | PathBase, opts: WalkOptions): AsyncGenerator<PathBase | string, void, void>;
/**
* Default async iterator (alias for iterate())
*/
[Symbol.asyncIterator](): AsyncGenerator<PathBase, void, void>;Usage Examples:
const pw = new PathScurry();
// For-await-of loop
for await (const entry of pw) {
if (entry.isFile() && entry.name.endsWith(".ts")) {
console.log(`TypeScript file: ${entry.relative()}`);
}
}
// Explicit iteration
const iterator = pw.iterate("./src", { withFileTypes: false });
for await (const path of iterator) {
if (path.includes("test")) {
console.log(`Test file: ${path}`);
}
}
// Functional processing
const results = [];
for await (const entry of pw.iterate({ filter: e => e.isFile() })) {
if (entry.size && entry.size > 1024 * 1024) { // Files > 1MB
results.push({
path: entry.fullpath(),
size: entry.size,
modified: entry.mtime
});
}
}Use synchronous iterators for for of loops.
/**
* Create synchronous iterator for directory walking
* @param entry - Starting directory (defaults to current working directory)
* @param opts - Walking options
* @returns Generator yielding Path objects or strings
*/
iterateSync(): Generator<PathBase, void, void>;
iterateSync(opts: WalkOptionsWithFileTypesTrue | WalkOptionsWithFileTypesUnset): Generator<PathBase, void, void>;
iterateSync(opts: WalkOptionsWithFileTypesFalse): Generator<string, void, void>;
iterateSync(opts: WalkOptions): Generator<string | PathBase, void, void>;
iterateSync(entry: string | PathBase): Generator<PathBase, void, void>;
iterateSync(entry: string | PathBase, opts: WalkOptionsWithFileTypesTrue | WalkOptionsWithFileTypesUnset): Generator<PathBase, void, void>;
iterateSync(entry: string | PathBase, opts: WalkOptionsWithFileTypesFalse): Generator<string, void, void>;
iterateSync(entry: string | PathBase, opts: WalkOptions): Generator<PathBase | string, void, void>;
/**
* Default sync iterator (alias for iterateSync())
*/
[Symbol.iterator](): Generator<PathBase, void, void>;Usage Examples:
const pw = new PathScurry();
// Synchronous for-of loop
for (const entry of pw) {
if (entry.isDirectory() && entry.name.startsWith(".")) {
console.log(`Hidden directory: ${entry.name}`);
}
}
// Process specific directory
const packages = [];
for (const entry of pw.iterateSync("./node_modules")) {
if (entry.isDirectory() && entry.parent?.name === "node_modules") {
packages.push(entry.name);
}
}
console.log(`Found ${packages.length} packages`);Configure walking behavior with comprehensive options.
interface WalkOptions {
/**
* Return Path objects (true) or path strings (false)
* @default true
*/
withFileTypes?: boolean;
/**
* Follow symbolic links to directories
* @default false
*/
follow?: boolean;
/**
* Filter function to include/exclude entries from results
* Does not prevent directory traversal
*/
filter?: (entry: PathBase) => boolean;
/**
* Filter function to control which directories are traversed
* Does not affect result inclusion
*/
walkFilter?: (entry: PathBase) => boolean;
}Advanced Usage Examples:
const pw = new PathScurry();
// Complex filtering
const results = await pw.walk({
// Only include source files in results
filter: (entry) => {
if (entry.isFile()) {
return /\.(js|ts|jsx|tsx)$/.test(entry.name);
}
return true; // Include directories in results too
},
// Don't traverse hidden or node_modules directories
walkFilter: (entry) => {
if (entry.isDirectory()) {
const name = entry.name;
return !name.startsWith(".") && name !== "node_modules";
}
return true;
},
// Follow symlinks but be careful of cycles
follow: true,
// Return as Path objects for rich metadata
withFileTypes: true
});
// Find large files while avoiding certain directories
const largeFiles = [];
for await (const entry of pw.iterate({
filter: (entry) => entry.isFile() && (entry.size || 0) > 10 * 1024 * 1024,
walkFilter: (entry) => entry.name !== "node_modules" && !entry.name.startsWith(".")
})) {
largeFiles.push({
path: entry.fullpath(),
size: entry.size,
sizeInMB: Math.round((entry.size || 0) / (1024 * 1024))
});
}
console.log("Large files (>10MB):", largeFiles);walk, walkSync): Memory-intensive but convenient for small/medium treesstream, streamSync): Memory-efficient, ideal for large treesiterate, iterateSync): Good balance, supports functional patternsreadlink callsInstall with Tessl CLI
npx tessl i tessl/npm-path-scurry