npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pwd-fs

v3.5.4

Published

Path-aware file system utilities with scoped working directories and recursive operations

Downloads

347

Readme

pwd-fs

License CI

pwd-fs is a path-aware wrapper around Node.js file system APIs.

It provides:

  • a dedicated working directory per instance
  • Promise-based async methods
  • matching synchronous variants via { sync: true }
  • recursive helpers for copy, remove, chmod, chown, and mkdir

Relative paths are resolved against pfs.pwd. Absolute paths are used as-is, so pfs.pwd is a convenience base path, not a sandbox.

Why Use It

Use pwd-fs when you want file system operations to be rooted at a specific working directory without manually calling path.resolve() before every operation.

It is especially useful for:

  • CLI tools that operate inside a project root
  • build or code generation scripts
  • isolated test fixtures
  • small automation tasks that need Promise-based file system helpers

Installation

npm install pwd-fs

Table of Contents

Quick Start

import { pfs } from 'pwd-fs';

await pfs.mkdir('./own/project'); // recursively create the directory

Common Recipes

Work inside a project directory

import { PoweredFileSystem } from 'pwd-fs';

const projectFs = new PoweredFileSystem('/workspace/my-project');

await projectFs.write('./.cache/build.txt', 'ok');
const exists = await projectFs.test('./.cache/build.txt');

Copy assets into a build directory

await pfs.mkdir('./dist');
await pfs.copy('./assets', './dist');

Result:

  • source: ./assets
  • destination directory: ./dist
  • created output: ./dist/assets

Empty a directory but keep it

await pfs.emptyDir('./cache');

Resolve a symlink target

const target = await pfs.readlink('./current');
const resolved = await pfs.realpath('./current');

Append to a file

await pfs.write('./app.log', 'first line\n');
await pfs.write('./app.log', 'second line\n', { flag: 'a' });

Read a file as a buffer

const raw = await pfs.read('./archive.bin', { encoding: null });

Remove a temporary directory recursively

if (await pfs.test('./tmp')) {
  await pfs.remove('./tmp');
}

Compatibility

  • package engines: Node.js >=18
  • module format: CommonJS package output with TypeScript declarations
  • platform notes:
    • chown() is effectively a no-op on Windows apart from path validation
    • chmod() behavior on Windows is limited by the platform
    • x access checks in test() do not have the same meaning on Windows as on Unix-like systems

Exports

import PoweredFileSystem, { pfs, bitmask } from 'pwd-fs';
  • default: PoweredFileSystem
  • pfs: default instance rooted at process.cwd()
  • bitmask(mode): helper that extracts standard permission bits from fs.Stats.mode

API

new PoweredFileSystem(pwd?)

Creates a new instance with pwd as the base directory for relative paths.

  • pwd?: string
  • default: process.cwd()
import { PoweredFileSystem } from 'pwd-fs';

const pfs = new PoweredFileSystem('./workspace');

pfs.pwd

Absolute base directory used to resolve relative paths.

pfs.resolve(src)

Resolves src against pfs.pwd.

const pfs = new PoweredFileSystem('/workspace/project');
const file = pfs.resolve('./src/index.ts');

Absolute paths are preserved:

pfs.resolve('/tmp/outside.txt'); // '/tmp/outside.txt'

pfs.constants

Access mode aliases used by pfs.test():

  • e: existence
  • r: readable
  • w: writable
  • x: executable

PoweredFileSystem.bitmask(mode)

Static alias for bitmask(mode).

const { mode } = await pfs.stat('./file.txt');
const permissions = PoweredFileSystem.bitmask(mode);

pfs.test(src, options?)

Checks whether a path is accessible.

test<T extends boolean = false>(
  src: string,
  options?: { sync?: T; flag?: 'e' | 'r' | 'w' | 'x' }
): T extends true ? boolean : Promise<boolean>
  • src: absolute or instance-relative path
  • flag: access check to perform
  • default flag: 'e'
const exists = await pfs.test('./notes.txt');
const writable = pfs.test('./notes.txt', { sync: true, flag: 'w' });

pfs.stat(src, options?)

Returns fs.lstat() information for a path.

stat<T extends boolean = false>(
  src: string,
  options?: { sync?: T }
): T extends true ? Stats : Promise<Stats>

This method uses lstat, so symbolic links are reported as links instead of followed targets.

pfs.chmod(src, mode, options?)

Recursively applies permissions to a file or directory tree.

chmod<T extends boolean = false>(
  src: string,
  mode: number,
  options?: { sync?: T }
): T extends true ? void : Promise<void>
await pfs.chmod('./build', 0o755);

Note: on Windows, permission handling is limited by platform behavior.

pfs.chown(src, options?)

Recursively applies ownership to a file or directory tree.

chown<T extends boolean = false>(
  src: string,
  options?: { sync?: T; uid?: number; gid?: number }
): T extends true ? void : Promise<void>
  • uid and gid default to 0
  • when uid or gid is 0, the current value from the source entry is preserved
  • on Windows, ownership changes are not performed, but path validation still happens

pfs.symlink(src, dest, options?)

Creates a symbolic link from dest to src.

symlink<T extends boolean = false>(
  src: string,
  dest: string,
  options?: { sync?: T }
): T extends true ? void : Promise<void>
await pfs.symlink('./target.txt', './target-link.txt');

pfs.copy(src, dest, options?)

Copies src into the destination directory.

copy<T extends boolean = false>(
  src: string,
  dest: string,
  options?: {
    sync?: T;
    umask?: number;
    overwrite?: boolean;
    filter?: (src: string, dest: string) => boolean;
  }
): T extends true ? void : Promise<void>

Behavior:

  • copying a file creates dest/<basename(src)>
  • copying a directory creates dest/<basename(src)> recursively
  • the target must not already exist
  • overwrite: true replaces an existing target entry with the same basename
  • filter() can skip specific source entries during the copy
await pfs.copy('./assets', './dist');

This creates ./dist/assets, not a direct rename to ./dist.

await pfs.copy('./assets', './dist', {
  overwrite: true,
  filter: (src) => !src.endsWith('.map')
});

pfs.rename(src, dest, options?)

Renames or moves a file system entry.

rename<T extends boolean = false>(
  src: string,
  dest: string,
  options?: { sync?: T }
): T extends true ? void : Promise<void>

pfs.remove(src, options?)

Removes a file, directory, or symbolic link.

remove<T extends boolean = false>(
  src: string,
  options?: { sync?: T }
): T extends true ? void : Promise<void>

Behavior:

  • directories are removed recursively
  • symbolic links are unlinked without deleting the target

pfs.emptyDir(src, options?)

Removes all entries inside a directory while preserving the directory itself.

emptyDir<T extends boolean = false>(
  src: string,
  options?: { sync?: T }
): T extends true ? void : Promise<void>
await pfs.emptyDir('./tmp');

pfs.read(src, options?)

Reads a file.

read<T extends boolean = false>(
  src: string,
  options?: {
    sync?: T;
    encoding?: BufferEncoding | null;
    flag?: string;
  }
): T extends true ? string | Buffer : Promise<string | Buffer>
  • default encoding: 'utf8'
  • use encoding: null to get a Buffer
  • default flag: 'r'
const text = await pfs.read('./file.txt');
const buffer = pfs.read('./file.txt', { sync: true, encoding: null });

pfs.write(src, data, options?)

Writes a file and explicitly reapplies the computed mode.

write<T extends boolean = false>(
  src: string,
  data: Buffer | string,
  options?: {
    sync?: T;
    encoding?: BufferEncoding | null;
    umask?: number;
    flag?: string;
  }
): T extends true ? void : Promise<void>
  • default encoding: 'utf8'
  • default umask: 0o000
  • default flag: 'w'
  • use flag: 'a' to append
  • any valid Node.js string file flag is accepted, such as 'r', 'w', 'a', 'wx', or 'a+'
await pfs.write('./report.txt', 'generated output');
await pfs.write('./report.txt', '\nnext line', { flag: 'a' });

pfs.append(src, data, options?)

Deprecated wrapper around write(..., { flag: 'a' }).

append<T extends boolean = false>(
  src: string,
  data: Buffer | string,
  options?: {
    sync?: T;
    encoding?: BufferEncoding | null;
    umask?: number;
  }
): T extends true ? void : Promise<void>

Prefer:

await pfs.write('./file.txt', 'content', { flag: 'a' });

pfs.readdir(dir, options?)

Reads a directory and returns entry names.

readdir<T extends boolean = false>(
  dir: string,
  options?: { sync?: T; encoding?: BufferEncoding | null }
): T extends true ? string[] : Promise<string[]>
  • default encoding: 'utf8'

pfs.readlink(src, options?)

Reads the stored target path from a symbolic link.

readlink<T extends boolean = false>(
  src: string,
  options?: { sync?: T; encoding?: BufferEncoding }
): T extends true ? string : Promise<string>

pfs.realpath(src, options?)

Resolves a path to its canonical absolute location.

realpath<T extends boolean = false>(
  src: string,
  options?: { sync?: T; encoding?: BufferEncoding }
): T extends true ? string : Promise<string>

pfs.mkdir(dir, options?)

Creates a directory tree recursively.

mkdir<T extends boolean = false>(
  dir: string,
  options?: { sync?: T; umask?: number }
): T extends true ? void : Promise<void>
  • existing directories are accepted
  • default umask: 0o000
await pfs.mkdir('./public/assets/icons');

Sync Mode

Every API method supports a synchronous form through { sync: true }.

pfs.mkdir('./cache', { sync: true });
pfs.write('./cache/data.json', '{}', { sync: true });
const content = pfs.read('./cache/data.json', { sync: true });

Error Behavior

Most async methods reject with the underlying Node.js error. Their sync variants throw the same class of error synchronously.

Typical cases:

  • test() is the exception: it returns false for inaccessible or missing paths instead of rejecting or throwing
  • read(), stat(), readdir(), chmod(), chown(), rename(), and remove() fail for missing paths
  • readlink() and realpath() fail for missing paths
  • read() fails when the target is a directory
  • readdir() fails when the target is not a directory
  • emptyDir() fails when the target is not a directory
  • write() fails when the target path points to a directory
  • copy() fails when the source does not exist
  • copy() fails when the destination already contains an entry with the same basename as the source, unless overwrite: true is used
  • copy() fails when a directory is copied into itself
  • symlink() fails when the destination already exists
  • mkdir() accepts an existing directory, but fails when a path segment is a file

Practical pattern:

if (await pfs.test('./dist')) {
  await pfs.remove('./dist');
}

await pfs.mkdir('./dist');

Umask Behavior

copy(), write(), and mkdir() support umask.

Effective permissions:

| Umask | File mode | Directory mode | | --- | --- | --- | | 0o000 | 0o666 | 0o777 | | 0o022 | 0o644 | 0o755 | | 0o027 | 0o640 | 0o750 | | 0o077 | 0o600 | 0o700 |

Notes

  • Relative paths are resolved against pfs.pwd
  • Absolute paths are not constrained by pfs.pwd
  • stat() returns lstat() data
  • remove() does not follow symbolic links
  • append() is kept for backward compatibility and is deprecated

Platform Caveats

| Area | Unix-like systems | Windows | | --- | --- | --- | | chmod() | Recursive permission changes work as expected | Permission handling is limited by platform behavior | | chown() | Recursive ownership changes are applied | Ownership is not changed; only path validation is performed | | symlink() | Link type is inferred by the platform | The implementation resolves the source first and chooses file or junction explicitly | | test(..., { flag: 'x' }) | Uses executable access checks | Does not have the same semantics as Unix execute checks | | remove() on symlinks | Removes the link, not the target | Removes the link, not the target |

When To Use Native fs

Prefer native node:fs APIs directly when you need:

  • streams such as createReadStream() or createWriteStream()
  • advanced flags and options not exposed by this wrapper
  • very low-level control over file descriptors
  • exact parity with Node's callback-based APIs

Development

yarn install --frozen-lockfile
yarn lint
yarn build
yarn test

License

MIT