CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-path-scurry

Extremely high performant utility for building tools that read the file system, minimizing filesystem and path string munging operations to the greatest degree possible

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

path-objects.mddocs/

Path Objects

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.

Capabilities

Path Object Creation and Properties

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 "\\"

Path Navigation and Resolution

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 slashes

File Type Checking

Determine 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`);
  }
}

Name Matching and Comparison

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 true

Filesystem Operations

Perform 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));
});

Caching and Status Methods

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");
}

Platform-Specific Path Classes

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"

Walking and Traversal

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

docs

directory-operations.md

directory-walking.md

filesystem-metadata.md

index.md

path-objects.md

path-resolution.md

tile.json