A TypeScript library abstracting the Node filesystem APIs with virtual filesystems and cross-platform path handling
File mode constants, error factories, and statistics utilities that provide essential building blocks for filesystem operations.
The @yarnpkg/fslib package provides three key utility modules:
Standard POSIX file mode constants for working with file types and permissions.
import { constants } from '@yarnpkg/fslib';
// File type mask and specific types
const S_IFMT = constants.S_IFMT; // 0o170000 - File type mask
const S_IFDIR = constants.S_IFDIR; // 0o040000 - Directory
const S_IFREG = constants.S_IFREG; // 0o100000 - Regular file
const S_IFLNK = constants.S_IFLNK; // 0o120000 - Symbolic link
// Safe timestamp constant
const SAFE_TIME = constants.SAFE_TIME; // 456789000 (1984-06-22T21:50:00.000Z)import { constants, type Stats } from '@yarnpkg/fslib';
// Check file type from stats
function getFileType(stats: Stats): string {
const mode = stats.mode;
if ((mode & constants.S_IFMT) === constants.S_IFDIR) {
return 'directory';
} else if ((mode & constants.S_IFMT) === constants.S_IFREG) {
return 'file';
} else if ((mode & constants.S_IFMT) === constants.S_IFLNK) {
return 'symlink';
} else {
return 'other';
}
}
// Usage with filesystem operations
import { xfs } from '@yarnpkg/fslib';
const stats = await xfs.statPromise('/path/to/item' as PortablePath);
const type = getFileType(stats);
console.log(`Item type: ${type}`);
// Check if item is a directory
const isDirectory = (stats.mode & constants.S_IFMT) === constants.S_IFDIR;Predefined portable path and filename constants for common filesystem items.
import { PortablePath, Filename } from '@yarnpkg/fslib';
// Portable path constants
const root = PortablePath.root; // '/' as PortablePath
const dot = PortablePath.dot; // '.' as PortablePath
const parent = PortablePath.parent; // '..' as PortablePath
// Common filename constants
const home = Filename.home; // '~' as Filename
const nodeModules = Filename.nodeModules; // 'node_modules' as Filename
const packageJson = Filename.manifest; // 'package.json' as Filename
const yarnLock = Filename.lockfile; // 'yarn.lock' as Filename
const env = Filename.env; // '.env' as Filename
// Yarn-specific constants
const virtual = Filename.virtual; // '__virtual__' as Filename
const pnpCjs = Filename.pnpCjs; // '.pnp.cjs' as Filename
const pnpData = Filename.pnpData; // '.pnp.data.json' as Filename
const pnpLoader = Filename.pnpEsmLoader; // '.pnp.loader.mjs' as Filename
const yarnrc = Filename.rc; // '.yarnrc.yml' as Filenameimport { ppath, PortablePath, Filename } from '@yarnpkg/fslib';
// Build common paths
const projectRoot = '/my/project' as PortablePath;
const nodeModulesPath = ppath.join(projectRoot, Filename.nodeModules);
const packageJsonPath = ppath.join(projectRoot, Filename.manifest);
const yarnLockPath = ppath.join(projectRoot, Filename.lockfile);
// Work with Yarn-specific paths
const virtualPath = ppath.join(nodeModulesPath, Filename.virtual);
const pnpPath = ppath.join(projectRoot, Filename.pnpCjs);
// Navigate using path constants
const parentDir = ppath.join(projectRoot, PortablePath.parent);
const currentDir = ppath.join(projectRoot, PortablePath.dot);Factory functions for creating filesystem-specific errors with appropriate error codes and messages.
import { errors } from '@yarnpkg/fslib';
// File and directory errors
const busyError = errors.EBUSY('Resource is busy');
const notFoundError = errors.ENOENT('File not found: /missing/file.txt');
const notDirError = errors.ENOTDIR('Not a directory: /file.txt');
const isDirError = errors.EISDIR('Is a directory: /some/directory');
const existsError = errors.EEXIST('File already exists: /existing/file.txt');
// Permission and access errors
const accessError = errors.EBADF('Bad file descriptor');
const invalidError = errors.EINVAL('Invalid argument provided');
const readOnlyError = errors.EROFS('Read-only filesystem');
const notEmptyError = errors.ENOTEMPTY('Directory not empty: /directory');
// Operation errors
const notSupportedError = errors.EOPNOTSUPP('Operation not supported');
const notImplementedError = errors.ENOSYS('Function not implemented', 'custom reason');
const dirClosedError = errors.ERR_DIR_CLOSED();import { errors, type FakeFS, type PortablePath } from '@yarnpkg/fslib';
// Custom filesystem implementation with proper error handling
class CustomFS extends BasePortableFakeFS {
private files = new Map<string, Buffer>();
async readFilePromise(p: PortablePath, encoding?: BufferEncoding | null): Promise<Buffer | string> {
if (!this.files.has(p)) {
throw errors.ENOENT(`No such file: ${p}`);
}
const content = this.files.get(p)!;
return encoding && encoding !== 'buffer' ? content.toString(encoding) : content;
}
async writeFilePromise(p: PortablePath, content: string | Buffer): Promise<void> {
if (this.isReadOnly) {
throw errors.EROFS(`Cannot write to read-only filesystem: ${p}`);
}
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
this.files.set(p, buffer);
}
async mkdirPromise(p: PortablePath, options?: MkdirOptions): Promise<void> {
if (this.files.has(p)) {
throw errors.EEXIST(`Directory already exists: ${p}`);
}
if (!options?.recursive) {
const parent = ppath.dirname(p);
if (parent !== p && !this.files.has(parent)) {
throw errors.ENOENT(`Parent directory does not exist: ${parent}`);
}
}
this.files.set(p, Buffer.alloc(0)); // Mark as directory
}
}import { errors, xfs, type PortablePath } from '@yarnpkg/fslib';
// Robust error handling in filesystem operations
async function safeFileRead(path: PortablePath): Promise<string | null> {
try {
return await xfs.readFilePromise(path, 'utf8');
} catch (error) {
switch (error.code) {
case 'ENOENT':
console.log(`File not found: ${path}`);
return null;
case 'EACCES':
console.error(`Access denied: ${path}`);
throw errors.EACCES(`Cannot read file: ${path}`);
case 'EISDIR':
console.error(`Expected file but found directory: ${path}`);
throw errors.EISDIR(`Cannot read directory as file: ${path}`);
default:
console.error(`Unexpected error reading ${path}:`, error.message);
throw error;
}
}
}
// Conditional file operations based on error types
async function ensureFileExists(path: PortablePath, defaultContent: string): Promise<void> {
try {
await xfs.statPromise(path);
// File exists, nothing to do
} catch (error) {
if (error.code === 'ENOENT') {
// File doesn't exist, create it
await xfs.writeFilePromise(path, defaultContent);
} else {
// Other error, re-throw
throw error;
}
}
}Utilities for working with file statistics, creating default stat objects, and comparing file metadata.
import { statUtils, type Stats, type BigIntStats, type Dirent } from '@yarnpkg/fslib';
// Directory entry with filesystem type information
class DirEntry<T extends Path> implements Dirent<T> {
constructor(
public name: Filename,
public path: T,
private stats: Stats
) {}
isFile(): boolean { return this.stats.isFile(); }
isDirectory(): boolean { return this.stats.isDirectory(); }
isBlockDevice(): boolean { return this.stats.isBlockDevice(); }
isCharacterDevice(): boolean { return this.stats.isCharacterDevice(); }
isSymbolicLink(): boolean { return this.stats.isSymbolicLink(); }
isFIFO(): boolean { return this.stats.isFIFO(); }
isSocket(): boolean { return this.stats.isSocket(); }
}
// File statistics entry (extends Node.js Stats)
class StatEntry implements Stats {
dev: number;
ino: number;
mode: number;
nlink: number;
uid: number;
gid: number;
rdev: number;
size: number;
blksize: number;
blocks: number;
atimeMs: number;
mtimeMs: number;
ctimeMs: number;
birthtimeMs: number;
atime: Date;
mtime: Date;
ctime: Date;
birthtime: Date;
crc?: number; // Optional CRC checksum
// Standard Stats methods...
}import { statUtils, constants } from '@yarnpkg/fslib';
// Default file mode
const DEFAULT_MODE = statUtils.DEFAULT_MODE; // S_IFREG | 0o644
// Create default statistics objects
const defaultStats = statUtils.makeDefaultStats();
const emptyStats = statUtils.makeEmptyStats();
// Working with stats objects
const stats1 = statUtils.makeDefaultStats();
stats1.size = 1024;
stats1.mode = constants.S_IFREG | 0o755;
const stats2 = statUtils.makeDefaultStats();
stats2.size = 1024;
stats2.mode = constants.S_IFREG | 0o755;
// Compare statistics objects
const areEqual = statUtils.areStatsEqual(stats1, stats2);
console.log('Stats are equal:', areEqual);
// Clear statistics (mutates object)
statUtils.clearStats(stats1);
console.log('Cleared size:', stats1.size); // 0import { statUtils, type Stats, type BigIntStats } from '@yarnpkg/fslib';
// Convert regular Stats to BigIntStats
const regularStats = statUtils.makeDefaultStats();
regularStats.size = 1024;
regularStats.mtimeMs = Date.now();
const bigIntStats: BigIntStats = statUtils.convertToBigIntStats(regularStats);
console.log('BigInt size:', bigIntStats.size); // 1024n
console.log('BigInt mtime:', bigIntStats.mtimeMs); // BigInt versionimport { statUtils, constants, type Stats } from '@yarnpkg/fslib';
// Create file statistics
function createFileStats(size: number, mtime: Date): Stats {
const stats = statUtils.makeDefaultStats();
stats.size = size;
stats.mode = constants.S_IFREG | 0o644; // Regular file, rw-r--r--
stats.mtime = mtime;
stats.mtimeMs = mtime.getTime();
stats.atime = mtime;
stats.atimeMs = mtime.getTime();
stats.ctime = mtime;
stats.ctimeMs = mtime.getTime();
return stats;
}
// Create directory statistics
function createDirectoryStats(mtime: Date): Stats {
const stats = statUtils.makeDefaultStats();
stats.size = 0;
stats.mode = constants.S_IFDIR | 0o755; // Directory, rwxr-xr-x
stats.mtime = mtime;
stats.mtimeMs = mtime.getTime();
stats.atime = mtime;
stats.atimeMs = mtime.getTime();
stats.ctime = mtime;
stats.ctimeMs = mtime.getTime();
return stats;
}
// Usage
const fileStats = createFileStats(2048, new Date());
const dirStats = createDirectoryStats(new Date());
console.log('Is file:', fileStats.isFile()); // true
console.log('Is directory:', dirStats.isDirectory()); // trueimport {
constants, errors, statUtils, xfs, ppath,
type PortablePath, type Stats
} from '@yarnpkg/fslib';
// Enhanced file information utility
async function getFileInfo(path: PortablePath): Promise<{
path: PortablePath;
type: string;
size: number;
mode: string;
exists: boolean;
readable: boolean;
writable: boolean;
}> {
try {
const stats = await xfs.statPromise(path);
// Determine file type using constants
let type: string;
if ((stats.mode & constants.S_IFMT) === constants.S_IFDIR) {
type = 'directory';
} else if ((stats.mode & constants.S_IFMT) === constants.S_IFREG) {
type = 'file';
} else if ((stats.mode & constants.S_IFMT) === constants.S_IFLNK) {
type = 'symlink';
} else {
type = 'other';
}
// Format mode as octal string
const mode = (stats.mode & 0o777).toString(8).padStart(3, '0');
// Test access permissions
let readable = false;
let writable = false;
try {
await xfs.accessPromise(path, constants.R_OK);
readable = true;
} catch {}
try {
await xfs.accessPromise(path, constants.W_OK);
writable = true;
} catch {}
return {
path,
type,
size: stats.size,
mode,
exists: true,
readable,
writable
};
} catch (error) {
if (error.code === 'ENOENT') {
return {
path,
type: 'none',
size: 0,
mode: '000',
exists: false,
readable: false,
writable: false
};
}
throw error;
}
}
// Usage
const info = await getFileInfo('/path/to/file.txt' as PortablePath);
console.log(`${info.path}: ${info.type} (${info.mode}) ${info.size} bytes`);import { constants, statUtils, type Stats } from '@yarnpkg/fslib';
// Use safe timestamp for reproducible builds
function createReproducibleStats(size: number): Stats {
const stats = statUtils.makeDefaultStats();
stats.size = size;
stats.mode = constants.S_IFREG | 0o644;
// Use safe timestamp for reproducibility
const safeTime = new Date(constants.SAFE_TIME * 1000);
stats.mtime = safeTime;
stats.mtimeMs = constants.SAFE_TIME * 1000;
stats.atime = safeTime;
stats.atimeMs = constants.SAFE_TIME * 1000;
stats.ctime = safeTime;
stats.ctimeMs = constants.SAFE_TIME * 1000;
return stats;
}
// Compare file timestamps safely
function compareFileTimestamps(stats1: Stats, stats2: Stats): number {
const time1 = Math.floor(stats1.mtimeMs / 1000);
const time2 = Math.floor(stats2.mtimeMs / 1000);
if (time1 < time2) return -1;
if (time1 > time2) return 1;
return 0;
}import { errors, type PortablePath } from '@yarnpkg/fslib';
// Enhanced error factory with context
class FileSystemError extends Error {
constructor(
public code: string,
message: string,
public path?: PortablePath,
public operation?: string
) {
super(message);
this.name = 'FileSystemError';
}
}
// Create contextual errors
function createContextualError(
operation: string,
path: PortablePath,
originalError: Error
): FileSystemError {
const message = `${operation} failed for ${path}: ${originalError.message}`;
return new FileSystemError(originalError.code || 'UNKNOWN', message, path, operation);
}
// Usage in filesystem operations
async function safeOperation<T>(
operation: string,
path: PortablePath,
fn: () => Promise<T>
): Promise<T> {
try {
return await fn();
} catch (error) {
throw createContextualError(operation, path, error);
}
}
// Example usage
try {
await safeOperation('read', '/missing/file.txt' as PortablePath, async () => {
return await xfs.readFilePromise('/missing/file.txt' as PortablePath, 'utf8');
});
} catch (error) {
console.error(`Operation failed: ${error.operation} on ${error.path}`);
console.error(`Error: ${error.message}`);
}Install with Tessl CLI
npx tessl i tessl/npm-yarnpkg--fslib