CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-metro-memory-fs

A memory-based implementation of Node.js fs module for testing purposes

Overall
score

96%

Overview
Eval results
Files

stats-permissions.mddocs/

Stats and Permissions

File statistics, permission checking, and metadata operations with full compatibility for Node.js Stats objects. Provides comprehensive file system metadata access and permission management.

Capabilities

File Statistics

Get detailed information about files and directories including size, permissions, and timestamps.

/**
 * Synchronously get file statistics (follows symbolic links)
 * @param path - File or directory path
 * @returns Stats object with file information
 */
statSync(path: string | Buffer): Stats;

/**
 * Synchronously get file statistics (does not follow symbolic links)
 * @param path - File or directory path
 * @returns Stats object (for symlinks, returns symlink stats not target stats)
 */
lstatSync(path: string | Buffer): Stats;

/**
 * Asynchronously get file statistics (follows symbolic links)
 * @param path - File or directory path
 * @param callback - Completion callback with stats
 */
stat(path: string | Buffer, callback: (err?: Error, stats?: Stats) => void): void;

/**
 * Asynchronously get file statistics (does not follow symbolic links)
 * @param path - File or directory path
 * @param callback - Completion callback with stats
 */
lstat(path: string | Buffer, callback: (err?: Error, stats?: Stats) => void): void;

interface Stats {
  /** File size in bytes */
  size: number;
  /** File mode (permissions and type) */
  mode: number;
  /** User ID of file owner */
  uid: number;
  /** Group ID of file owner */
  gid: number;
  /** Device ID */
  dev: number;
  /** Inode number */
  ino: number;
  /** Number of hard links */
  nlink: number;
  /** Device ID (for special files) */
  rdev: number;
  /** Block size for I/O operations */
  blksize: number;
  /** Number of 512-byte blocks allocated */
  blocks: number;
  
  // Timestamps
  /** Access time in milliseconds */
  atimeMs: number;
  /** Modification time in milliseconds */
  mtimeMs: number;
  /** Status change time in milliseconds */
  ctimeMs: number;
  /** Birth time in milliseconds */
  birthtimeMs: number;
  
  /** Access time as Date object */
  atime: Date;
  /** Modification time as Date object */
  mtime: Date;
  /** Status change time as Date object */
  ctime: Date;
  /** Birth time as Date object */
  birthtime: Date;
  
  // Type checking methods
  /** Check if this is a regular file */
  isFile(): boolean;
  /** Check if this is a directory */
  isDirectory(): boolean;
  /** Check if this is a symbolic link (lstat only) */
  isSymbolicLink(): boolean;
  /** Check if this is a block device */
  isBlockDevice(): boolean;
  /** Check if this is a character device */
  isCharacterDevice(): boolean;
  /** Check if this is a FIFO pipe */
  isFIFO(): boolean;
  /** Check if this is a socket */
  isSocket(): boolean;
}

Usage Examples:

// Get file statistics
const stats = fs.statSync('/document.txt');
console.log('File size:', stats.size, 'bytes');
console.log('Is file:', stats.isFile());
console.log('Is directory:', stats.isDirectory());
console.log('Last modified:', stats.mtime);
console.log('File mode:', stats.mode.toString(8)); // Octal representation

// Check file type
if (stats.isFile()) {
  console.log('This is a regular file');
} else if (stats.isDirectory()) {
  console.log('This is a directory');
}

// Compare stat vs lstat for symlinks
fs.symlinkSync('/target.txt', '/link.txt');
const fileStats = fs.statSync('/link.txt');   // Stats of target file
const linkStats = fs.lstatSync('/link.txt');  // Stats of symlink itself

console.log('Target file size:', fileStats.size);
console.log('Link target path length:', linkStats.size);
console.log('Is target file:', fileStats.isFile());          // true
console.log('Is link symbolic:', linkStats.isSymbolicLink()); // true

// Async stats
fs.stat('/async-file.txt', (err, stats) => {
  if (!err) {
    console.log('File info:', {
      size: stats.size,
      modified: stats.mtime,
      isFile: stats.isFile()
    });
  }
});

// Promise-based stats
const stats = await fs.promises.stat('/promise-file.txt');
console.log('File size:', stats.size);

Permission Management

Change file and directory permissions with support for both numeric and symbolic modes.

/**
 * Synchronously change file permissions (follows symbolic links)
 * @param path - File or directory path
 * @param mode - New file mode (permissions)
 */
chmodSync(path: string | Buffer, mode: number | string): void;

/**
 * Synchronously change file permissions (does not follow symbolic links)
 * @param path - File or directory path
 * @param mode - New file mode (permissions)
 */
lchmodSync(path: string | Buffer, mode: number | string): void;

/**
 * Asynchronously change file permissions (follows symbolic links)
 * @param path - File or directory path
 * @param mode - New file mode (permissions)
 * @param callback - Completion callback
 */
chmod(path: string | Buffer, mode: number | string, callback?: (err?: Error) => void): void;

/**
 * Asynchronously change file permissions (does not follow symbolic links)
 * @param path - File or directory path
 * @param mode - New file mode (permissions)
 * @param callback - Completion callback
 */
lchmod(path: string | Buffer, mode: number | string, callback?: (err?: Error) => void): void;

Permission Modes:

  • Numeric: 0o755, 0o644, 0o600 (octal notation)
  • String: "755", "644", "600" (octal string)
  • Common patterns:
    • 0o755 - rwxr-xr-x (executable file)
    • 0o644 - rw-r--r-- (regular file)
    • 0o600 - rw------- (private file)
    • 0o777 - rwxrwxrwx (fully accessible)

Usage Examples:

// Set file permissions using numeric mode
fs.chmodSync('/script.sh', 0o755); // Make executable
fs.chmodSync('/config.json', 0o644); // Read-write for owner, read-only for others
fs.chmodSync('/private.key', 0o600); // Owner only

// Set permissions using string mode
fs.chmodSync('/public.txt', '644');
fs.chmodSync('/secure.txt', '600');

// Change directory permissions
fs.chmodSync('/uploads', 0o755); // Directory with execute permission

// Use lchmod for symbolic links (change link permissions, not target)
fs.symlinkSync('/target.txt', '/link.txt');
fs.lchmodSync('/link.txt', 0o644); // Change link permissions only

// Async permission changes
fs.chmod('/async-file.txt', 0o755, (err) => {
  if (!err) {
    console.log('Permissions changed successfully');
  }
});

// Promise-based permission changes
await fs.promises.chmod('/promise-file.txt', 0o644);

// Check permissions after changing
const stats = fs.statSync('/script.sh');
console.log('File mode:', (stats.mode & 0o777).toString(8)); // Get permission bits

File Renaming and Moving

Rename or move files and directories within the filesystem.

/**
 * Synchronously rename/move a file or directory
 * @param oldPath - Current path
 * @param newPath - New path
 */
renameSync(oldPath: string | Buffer, newPath: string | Buffer): void;

/**
 * Asynchronously rename/move a file or directory
 * @param oldPath - Current path
 * @param newPath - New path
 * @param callback - Completion callback
 */
rename(oldPath: string | Buffer, newPath: string | Buffer, callback?: (err?: Error) => void): void;

Usage Examples:

// Rename a file
fs.writeFileSync('/old-name.txt', 'content');
fs.renameSync('/old-name.txt', '/new-name.txt');

// Move file to different directory
fs.mkdirSync('/archive');
fs.renameSync('/document.txt', '/archive/document.txt');

// Rename directory
fs.mkdirSync('/old-folder');
fs.renameSync('/old-folder', '/new-folder');

// Move and rename simultaneously
fs.renameSync('/src/temp.js', '/build/compiled.js');

// Async rename
fs.rename('/temp.txt', '/permanent.txt', (err) => {
  if (!err) {
    console.log('File renamed successfully');
  }
});

// Promise-based rename
await fs.promises.rename('/source.txt', '/destination.txt');

// Safe rename with existence check
function safeRename(oldPath, newPath) {
  if (!fs.existsSync(oldPath)) {
    throw new Error(`Source file does not exist: ${oldPath}`);
  }
  
  if (fs.existsSync(newPath)) {
    throw new Error(`Destination already exists: ${newPath}`);
  }
  
  fs.renameSync(oldPath, newPath);
}

Permission Constants and Checking

Work with file permission constants and check specific permissions.

// File mode constants (available via fs.constants)
interface FileConstants {
  // File type constants
  S_IFMT: number;   // File type mask
  S_IFREG: number;  // Regular file
  S_IFDIR: number;  // Directory
  S_IFLNK: number;  // Symbolic link
  S_IFCHR: number;  // Character device
  S_IFBLK: number;  // Block device
  S_IFIFO: number;  // FIFO pipe
  S_IFSOCK: number; // Socket
  
  // Permission constants
  S_IRWXU: number;  // User read, write, execute (0o700)
  S_IRUSR: number;  // User read (0o400)
  S_IWUSR: number;  // User write (0o200)
  S_IXUSR: number;  // User execute (0o100)
  
  S_IRWXG: number;  // Group read, write, execute (0o070)
  S_IRGRP: number;  // Group read (0o040)
  S_IWGRP: number;  // Group write (0o020)
  S_IXGRP: number;  // Group execute (0o010)
  
  S_IRWXO: number;  // Other read, write, execute (0o007)
  S_IROTH: number;  // Other read (0o004)
  S_IWOTH: number;  // Other write (0o002)
  S_IXOTH: number;  // Other execute (0o001)
}

Usage Examples:

// Check specific permissions using constants
const stats = fs.statSync('/file.txt');
const mode = stats.mode;

// Check if user can read
if (mode & fs.constants.S_IRUSR) {
  console.log('User can read');
}

// Check if group can write
if (mode & fs.constants.S_IWGRP) {
  console.log('Group can write');
}

// Check if others can execute
if (mode & fs.constants.S_IXOTH) {
  console.log('Others can execute');
}

// Extract permission bits
const permissions = mode & 0o777;
console.log('Permissions:', permissions.toString(8));

// Check file type using constants
const fileType = mode & fs.constants.S_IFMT;
if (fileType === fs.constants.S_IFREG) {
  console.log('Regular file');
} else if (fileType === fs.constants.S_IFDIR) {
  console.log('Directory');
} else if (fileType === fs.constants.S_IFLNK) {
  console.log('Symbolic link');
}

// Permission checker utility
function checkPermissions(path) {
  const stats = fs.statSync(path);
  const mode = stats.mode;
  
  return {
    isReadable: !!(mode & fs.constants.S_IRUSR),
    isWritable: !!(mode & fs.constants.S_IWUSR),
    isExecutable: !!(mode & fs.constants.S_IXUSR),
    permissions: (mode & 0o777).toString(8),
    type: stats.isFile() ? 'file' : 
          stats.isDirectory() ? 'directory' :
          stats.isSymbolicLink() ? 'symlink' : 'other'
  };
}

const info = checkPermissions('/script.sh');
console.log('File info:', info);

Advanced Statistics Operations

Advanced patterns for working with file statistics and metadata.

// Compare file timestamps
function compareFiles(path1, path2) {
  const stats1 = fs.statSync(path1);
  const stats2 = fs.statSync(path2);
  
  return {
    newerFile: stats1.mtime > stats2.mtime ? path1 : path2,
    sizeDifference: stats1.size - stats2.size,
    sameSize: stats1.size === stats2.size,
    sameMtime: stats1.mtime.getTime() === stats2.mtime.getTime()
  };
}

// Find files by size
function findFilesBySize(directory, minSize, maxSize) {
  const files = [];
  const entries = fs.readdirSync(directory, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = `${directory}/${entry.name}`;
    
    if (entry.isFile()) {
      const stats = fs.statSync(fullPath);
      if (stats.size >= minSize && stats.size <= maxSize) {
        files.push({
          path: fullPath,
          size: stats.size
        });
      }
    } else if (entry.isDirectory()) {
      files.push(...findFilesBySize(fullPath, minSize, maxSize));
    }
  }
  
  return files;
}

// Get directory size recursively
function getDirectorySize(dirPath) {
  let totalSize = 0;
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = `${dirPath}/${entry.name}`;
    
    if (entry.isFile()) {
      const stats = fs.statSync(fullPath);
      totalSize += stats.size;
    } else if (entry.isDirectory()) {
      totalSize += getDirectorySize(fullPath);
    }
  }
  
  return totalSize;
}

// File age utilities
function getFileAge(path) {
  const stats = fs.statSync(path);
  const now = new Date();
  const ageMs = now.getTime() - stats.mtime.getTime();
  
  return {
    milliseconds: ageMs,
    seconds: Math.floor(ageMs / 1000),
    minutes: Math.floor(ageMs / (1000 * 60)),
    hours: Math.floor(ageMs / (1000 * 60 * 60)),
    days: Math.floor(ageMs / (1000 * 60 * 60 * 24))
  };
}

// Find old files
function findOldFiles(directory, daysOld) {
  const oldFiles = [];
  const cutoffTime = Date.now() - (daysOld * 24 * 60 * 60 * 1000);
  
  function scanDirectory(dir) {
    const entries = fs.readdirSync(dir, { withFileTypes: true });
    
    for (const entry of entries) {
      const fullPath = `${dir}/${entry.name}`;
      
      if (entry.isFile()) {
        const stats = fs.statSync(fullPath);
        if (stats.mtime.getTime() < cutoffTime) {
          oldFiles.push({
            path: fullPath,
            age: getFileAge(fullPath),
            size: stats.size
          });
        }
      } else if (entry.isDirectory()) {
        scanDirectory(fullPath);
      }
    }
  }
  
  scanDirectory(directory);
  return oldFiles;
}

Permission Management Utilities

Advanced permission management and security utilities.

// Set secure permissions for different file types
function setSecurePermissions(path) {
  const stats = fs.statSync(path);
  
  if (stats.isDirectory()) {
    fs.chmodSync(path, 0o755); // rwxr-xr-x for directories
  } else if (stats.isFile()) {
    // Check if file should be executable
    const content = fs.readFileSync(path, 'utf8');
    if (content.startsWith('#!') || path.endsWith('.sh')) {
      fs.chmodSync(path, 0o755); // rwxr-xr-x for scripts
    } else {
      fs.chmodSync(path, 0o644); // rw-r--r-- for regular files
    }
  }
}

// Mass permission update
function updatePermissionsRecursively(directory, fileMode, dirMode) {
  const entries = fs.readdirSync(directory, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = `${directory}/${entry.name}`;
    
    if (entry.isFile()) {
      fs.chmodSync(fullPath, fileMode);
    } else if (entry.isDirectory()) {
      fs.chmodSync(fullPath, dirMode);
      updatePermissionsRecursively(fullPath, fileMode, dirMode);
    }
  }
}

// Permission validator
function validatePermissions(path, expectedMode) {
  const stats = fs.statSync(path);
  const actualMode = stats.mode & 0o777;
  const expected = typeof expectedMode === 'string' ? 
    parseInt(expectedMode, 8) : expectedMode;
  
  return {
    isValid: actualMode === expected,
    actual: actualMode.toString(8),
    expected: expected.toString(8),
    needsUpdate: actualMode !== expected
  };
}

// Usage examples
const validation = validatePermissions('/script.sh', 0o755);
if (!validation.isValid) {
  console.log(`Permission mismatch: expected ${validation.expected}, got ${validation.actual}`);
  fs.chmodSync('/script.sh', 0o755);
}

Error Handling

Common statistics and permissions errors:

  • ENOENT - File or directory doesn't exist
  • EPERM - Operation not permitted (insufficient permissions)
  • EACCES - Access denied
  • EISDIR - Is a directory (when file expected)
  • ENOTDIR - Not a directory (when directory expected)
// Safe stat operation
function safeStat(path) {
  try {
    return fs.statSync(path);
  } catch (err) {
    if (err.code === 'ENOENT') {
      return null; // File doesn't exist
    }
    throw err; // Re-throw other errors
  }
}

// Safe permission change
function safeChmod(path, mode) {
  try {
    fs.chmodSync(path, mode);
    return true;
  } catch (err) {
    if (err.code === 'EPERM') {
      console.warn(`Permission denied changing mode for ${path}`);
      return false;
    }
    throw err;
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-metro-memory-fs

docs

directory-operations.md

file-descriptors.md

file-operations.md

file-watching.md

index.md

stats-permissions.md

streams.md

symbolic-links.md

tile.json