pwd-fs
v3.5.4
Published
Path-aware file system utilities with scoped working directories and recursive operations
Downloads
347
Maintainers
Readme
pwd-fs
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-fsTable of Contents
- Quick Start
- Common Recipes
- Compatibility
- Exports
- API
new PoweredFileSystem(pwd?)pfs.pwdpfs.resolve(src)pfs.constantsPoweredFileSystem.bitmask(mode)pfs.test(src, options?)pfs.stat(src, options?)pfs.chmod(src, mode, options?)pfs.chown(src, options?)pfs.symlink(src, dest, options?)pfs.copy(src, dest, options?)pfs.rename(src, dest, options?)pfs.remove(src, options?)pfs.emptyDir(src, options?)pfs.read(src, options?)pfs.write(src, data, options?)pfs.append(src, data, options?)pfs.readdir(dir, options?)pfs.readlink(src, options?)pfs.realpath(src, options?)pfs.mkdir(dir, options?)- Sync Mode
- Error Behavior
- Umask Behavior
- Notes
- Platform Caveats
- When To Use Native
fs - Development
- License
Quick Start
import { pfs } from 'pwd-fs';
await pfs.mkdir('./own/project'); // recursively create the directoryCommon 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 validationchmod()behavior on Windows is limited by the platformxaccess checks intest()do not have the same meaning on Windows as on Unix-like systems
Exports
import PoweredFileSystem, { pfs, bitmask } from 'pwd-fs';default:PoweredFileSystempfs: default instance rooted atprocess.cwd()bitmask(mode): helper that extracts standard permission bits fromfs.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: existencer: readablew: writablex: 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 pathflag: 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>uidandgiddefault to0- when
uidorgidis0, 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: truereplaces an existing target entry with the same basenamefilter()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: nullto get aBuffer - 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 returnsfalsefor inaccessible or missing paths instead of rejecting or throwingread(),stat(),readdir(),chmod(),chown(),rename(), andremove()fail for missing pathsreadlink()andrealpath()fail for missing pathsread()fails when the target is a directoryreaddir()fails when the target is not a directoryemptyDir()fails when the target is not a directorywrite()fails when the target path points to a directorycopy()fails when the source does not existcopy()fails when the destination already contains an entry with the same basename as the source, unlessoverwrite: trueis usedcopy()fails when a directory is copied into itselfsymlink()fails when the destination already existsmkdir()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()returnslstat()dataremove()does not follow symbolic linksappend()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()orcreateWriteStream() - 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 testLicense
MIT
