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
Individual filesystem entry objects providing lazy-loaded metadata, type checking, and path manipulation methods. Path objects implement the Node.js Dirent interface with extensive caching and cross-platform support.
Path objects are created through PathScurry operations and represent individual filesystem entries.
abstract class PathBase implements Dirent {
/** Base filename or directory name */
readonly name: string;
/** True if this Path is the current working directory */
isCWD: boolean;
/** Parent directory path (for Dirent compatibility) */
readonly parentPath: string;
/** Deprecated alias for parentPath */
readonly path: string;
/** Root Path entry for this filesystem */
readonly root: PathBase;
/** Collection of all known roots */
readonly roots: { [k: string]: PathBase };
/** Parent Path object (undefined for root) */
readonly parent?: PathBase;
/** Whether path comparisons are case-insensitive */
readonly nocase: boolean;
/** Path separator for generating paths */
abstract readonly sep: string;
/** Path separator(s) for parsing paths */
abstract readonly splitSep: string | RegExp;
}Usage Examples:
import { PathScurry } from "path-scurry";
const pw = new PathScurry();
const file = pw.cwd.resolve("package.json");
console.log(`Name: ${file.name}`); // "package.json"
console.log(`Parent: ${file.parentPath}`); // "/path/to/project"
console.log(`Is CWD: ${file.isCWD}`); // false
console.log(`Case sensitive: ${!file.nocase}`); // platform dependent
console.log(`Separator: ${file.sep}`); // "/" or "\\"Navigate the filesystem tree and resolve relative paths.
/**
* Resolve a path relative to this Path object
* @param path - Path to resolve (optional)
* @returns Resolved Path object
*/
resolve(path?: string): PathBase;
/**
* Get relative path from PathScurry cwd to this path
* @returns Relative path string
*/
relative(): string;
/**
* Get relative path using POSIX separators
* @returns Relative path string with forward slashes
*/
relativePosix(): string;
/**
* Get fully resolved absolute path
* @returns Absolute path string
*/
fullpath(): string;
/**
* Get fully resolved absolute path with POSIX separators
* @returns Absolute path string with forward slashes
*/
fullpathPosix(): string;
/**
* Get depth of this path within the directory tree
* @returns Depth level (root = 0)
*/
depth(): number;
/**
* Get or create child path (internal method)
* @param pathPart - Child path component (no separators allowed)
* @param opts - Path creation options
* @returns Child Path object
* @internal
*/
child(pathPart: string, opts?: PathOpts): PathBase;Usage Examples:
const pw = new PathScurry("/home/user");
const srcDir = pw.cwd.resolve("project/src");
// Path resolution
const indexFile = srcDir.resolve("index.js");
console.log(indexFile.fullpath()); // "/home/user/project/src/index.js"
// Relative paths
console.log(indexFile.relative()); // "project/src/index.js"
console.log(indexFile.relativePosix()); // "project/src/index.js" (always forward slashes)
// Path depth
console.log(pw.cwd.depth()); // depends on /home/user depth from root
console.log(indexFile.depth()); // cwd depth + 3
// Cross-platform paths
console.log(indexFile.fullpath()); // native separators
console.log(indexFile.fullpathPosix()); // always forward slashesDetermine the type of filesystem entry with multiple checking methods.
/**
* Get the type of this filesystem entry
* @returns Type string describing the entry
*/
getType(): Type;
/**
* Check if path matches specific type
* @param type - Type to check against
* @returns True if path is of specified type
*/
isType(type: Type): boolean;
/**
* Check if path is a regular file
*/
isFile(): boolean;
/**
* Check if path is a directory
*/
isDirectory(): boolean;
/**
* Check if path is a symbolic link
*/
isSymbolicLink(): boolean;
/**
* Check if path type is unknown or undetermined
*/
isUnknown(): boolean;
/**
* Check if path is a FIFO pipe
*/
isFIFO(): boolean;
/**
* Check if path is a character device
*/
isCharacterDevice(): boolean;
/**
* Check if path is a block device
*/
isBlockDevice(): boolean;
/**
* Check if path is a socket
*/
isSocket(): boolean;
type Type = 'Unknown' | 'FIFO' | 'CharacterDevice' | 'Directory' |
'BlockDevice' | 'File' | 'SymbolicLink' | 'Socket';Usage Examples:
const pw = new PathScurry();
for await (const entry of pw.iterate("./")) {
const type = entry.getType();
console.log(`${entry.name}: ${type}`);
// Specific type checks
if (entry.isFile()) {
console.log(` File size: ${entry.size || 'unknown'}`);
} else if (entry.isDirectory()) {
const children = await entry.readdir();
console.log(` Contains ${children.length} entries`);
} else if (entry.isSymbolicLink()) {
const target = await entry.readlink();
console.log(` Links to: ${target?.fullpath() || 'unresolvable'}`);
}
// Generic type checking
if (entry.isType('Directory')) {
console.log(` This is also a directory`);
}
}Safe path name comparison handling unicode normalization and case sensitivity.
/**
* Check if path name matches given string
* Handles case sensitivity and unicode normalization correctly
* @param name - Name to compare against
* @returns True if names match
*/
isNamed(name: string): boolean;Usage Examples:
const pw = new PathScurry();
const file = pw.cwd.resolve("café.txt");
// NEVER do this - unsafe due to unicode normalization
if (file.name === "café.txt") { /* WRONG */ }
// ALWAYS use isNamed for safety
if (file.isNamed("café.txt")) {
console.log("Names match safely");
}
// Case sensitivity handling
const winPw = new PathScurryWin32();
const winFile = winPw.cwd.resolve("MyFile.TXT");
console.log(winFile.isNamed("myfile.txt")); // true on case-insensitive systems
console.log(winFile.isNamed("MyFile.TXT")); // always truePerform filesystem operations directly on Path objects.
/**
* Read directory contents (async)
* @returns Promise resolving to array of child Path objects
*/
readdir(): Promise<PathBase[]>;
/**
* Read directory contents (sync)
* @returns Array of child Path objects
*/
readdirSync(): PathBase[];
/**
* Node-style callback interface for reading directory
* @param cb - Callback receiving (error, entries)
* @param allowZalgo - Allow immediate callback execution
*/
readdirCB(
cb: (er: NodeJS.ErrnoException | null, entries: PathBase[]) => any,
allowZalgo?: boolean
): void;
/**
* Get file statistics (async)
* @returns Promise resolving to this Path with populated metadata
*/
lstat(): Promise<PathBase | undefined>;
/**
* Get file statistics (sync)
* @returns This Path with populated metadata or undefined
*/
lstatSync(): PathBase | undefined;
/**
* Read symbolic link target (async)
* @returns Promise resolving to target Path object
*/
readlink(): Promise<PathBase | undefined>;
/**
* Read symbolic link target (sync)
* @returns Target Path object or undefined
*/
readlinkSync(): PathBase | undefined;
/**
* Resolve to canonical path (async)
* @returns Promise resolving to canonical Path object
*/
realpath(): Promise<PathBase | undefined>;
/**
* Resolve to canonical path (sync)
* @returns Canonical Path object or undefined
*/
realpathSync(): PathBase | undefined;Usage Examples:
const pw = new PathScurry();
const dir = pw.cwd.resolve("./src");
// Directory operations
if (dir.canReaddir()) {
const files = await dir.readdir();
console.log(`Directory contains ${files.length} entries`);
// Process each file
for (const file of files) {
if (file.isFile()) {
const stats = await file.lstat();
if (stats) {
console.log(`${file.name}: ${stats.size} bytes`);
}
}
}
}
// Symlink handling
const link = pw.cwd.resolve("./symlink-to-config");
if (link.canReadlink()) {
const target = await link.readlink();
if (target) {
console.log(`Symlink points to: ${target.fullpath()}`);
const realPath = await target.realpath();
console.log(`Real path: ${realPath?.fullpath()}`);
}
}
// Callback-style directory reading
dir.readdirCB((err, entries) => {
if (err) {
console.error("Failed to read directory:", err);
return;
}
console.log("Directory entries:", entries.map(e => e.name));
});Check cache status and path state without performing filesystem operations.
/**
* Get cached lstat result without filesystem access
* @returns Cached Path object or undefined if not cached
*/
lstatCached(): PathBase | undefined;
/**
* Get cached readlink result without filesystem access
* @returns Cached target Path or undefined if not cached
*/
readlinkCached(): PathBase | undefined;
/**
* Get cached realpath result without filesystem access
* @returns Cached real Path or undefined if not cached
*/
realpathCached(): PathBase | undefined;
/**
* Get cached directory contents without filesystem access
* @returns Array of cached child entries (may be empty)
*/
readdirCached(): PathBase[];
/**
* Check if directory contents have been successfully loaded
* @returns True if readdir has been called and succeeded
*/
calledReaddir(): boolean;
/**
* Check if this path can likely be read as a directory
* @returns True if directory reading might succeed
*/
canReaddir(): boolean;
/**
* Check if readlink operation might succeed
* @returns True if path might be a readable symbolic link
*/
canReadlink(): boolean;
/**
* Check if path is known to not exist
* @returns True if previous operations determined non-existence
*/
isENOENT(): boolean;Usage Examples:
const pw = new PathScurry();
const file = pw.cwd.resolve("./might-not-exist.txt");
// Check cache status before expensive operations
const cachedStats = file.lstatCached();
if (cachedStats) {
console.log("Already have file stats");
console.log(`File type: ${cachedStats.getType()}`);
console.log(`File size: ${cachedStats.size}`);
} else {
console.log("Need to call lstat() to get file info");
const stats = await file.lstat();
if (stats) {
console.log("File exists and stats loaded");
} else {
console.log("File does not exist or cannot be accessed");
}
}
// Check directory cache
const dir = pw.cwd.resolve("./some-directory");
if (dir.calledReaddir()) {
const cached = dir.readdirCached();
console.log(`Directory has ${cached.length} cached entries`);
} else if (dir.canReaddir()) {
console.log("Directory can be read but hasn't been yet");
const entries = await dir.readdir();
console.log(`Directory loaded with ${entries.length} entries`);
} else {
console.log("Directory cannot be read");
}
// Check existence
if (file.isENOENT()) {
console.log("File is known to not exist");
} else {
console.log("File existence status unknown or file exists");
}Use specific Path implementations for cross-platform development.
/**
* Windows-specific Path implementation
*/
class PathWin32 extends PathBase {
sep: '\\';
splitSep: RegExp; // matches both '/' and '\\'
}
/**
* POSIX-specific Path implementation
*/
class PathPosix extends PathBase {
sep: '/';
splitSep: '/';
}Usage Examples:
import { PathWin32, PathPosix } from "path-scurry";
// Force Windows path behavior
const winScurry = new PathScurryWin32("/project");
const winPath = winScurry.cwd.resolve("src\\file.js");
console.log(winPath.fullpath()); // "C:\\project\\src\\file.js"
console.log(winPath.fullpathPosix()); // "//?/C:/project/src/file.js"
// Force POSIX path behavior
const posixScurry = new PathScurryPosix("/project");
const posixPath = posixScurry.cwd.resolve("src/file.js");
console.log(posixPath.fullpath()); // "/project/src/file.js"
console.log(posixPath.fullpathPosix()); // "/project/src/file.js"Path objects support walking operations for subtree traversal.
/**
* Check if this directory should be walked based on filters
* @param dirs - Set of already-visited directories (cycle prevention)
* @param walkFilter - Optional filter function
* @returns True if directory should be traversed
*/
shouldWalk(
dirs: Set<PathBase | undefined>,
walkFilter?: (e: PathBase) => boolean
): boolean;Usage Example:
const pw = new PathScurry();
const visitedDirs = new Set<PathBase>();
async function walkDirectory(dir: PathBase) {
if (!dir.shouldWalk(visitedDirs, (entry) => !entry.name.startsWith("."))) {
return;
}
visitedDirs.add(dir);
const entries = await dir.readdir();
for (const entry of entries) {
if (entry.isFile()) {
console.log(`File: ${entry.fullpath()}`);
} else if (entry.isDirectory()) {
await walkDirectory(entry);
}
}
}
const rootDir = pw.cwd.resolve("./src");
await walkDirectory(rootDir);Install with Tessl CLI
npx tessl i tessl/npm-path-scurry