
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
fs-hooks
This library allows you to work with the file system in Node.js by defining a tree of files and directories and a set of methods called hooks. A hook is a function that performs some action on a given file or directory from the tree, such as reading/writing to a file, creating/deleting a file/directory, etc. Any action that is often performed on files and directories and that could be abstracted away is a candidate for a hook.
npm install fs-hooks
pnpm add fs-hooks
yarn add fs-hooks
To use fs-hooks, you need to instantiate the FsHooks class by providing it two arguments:
import { FsHooks } from 'fs-hooks';
const fsHooks = new FsHooks('/path/to/tree/root', {
file1: 'File 1 data',
dir1: {
file2: 'File 2 data',
},
});
⚠️ It is important to create the tree in the file system, if it doesn't already exist, before using hooks - see here.
To register hooks, call the useHooks method on the created FsHooks instance. The method accepts an object that defines arbitrary file and directory hooks.
⚡ Learn more about hooks, how they work, and how to define them here.
This library exports a pre-defined object with some common hooks, called coreHooks exported from fs-hooks/core.
import { coreHooks } from 'fs-hooks/core';
/* statements */
const hooks = fsHooks.useHooks(coreHooks);
⚡ Learn about core hooks here.
The returned value from calling the useHooks method is a function that accepts a callback whose only argument is the tree, and whose return value is a property of that tree that you want to work with ("hook into"). You are now able to work with the tree by using hooks.
const file1 = hooks((root) => root.file1);
const dir1 = hooks((root) => root.dir1);
// file core hooks
file1.getPath();
file1.read();
file1.write('File 1 new data');
file1.clear();
// dir core hooks
dir1.getPath();
dir1.exists('file2');
dir1.dirCreate('new-dir');
dir1.dirDelete('new-dir');
dir1.fileRead('file2');
dir1.fileWrite('file2', 'File 2 new data');
dir1.fileClear('file2');
dir1.fileCreate('new-file');
dir1.fileDelete('new-file');
The tree object represents a structure of files and directories that you will be working with via hooks. Each property key is the name of the corresponding file or directory, and its value determines whether the property is a file or a directory:
string whose value is the initial content of the file. This content will be written when calling the createTree function (see here).TreeInterface and contains files and/or other directories (the tree itself is of type TreeInterface).For example:
const fsHooks = new FsHooks('/path/to/tree/root', {
file1: 'File 1 data',
'file2.html': getHtmlContent(), // returns a string
'file3.css': getCssContent(), // returns a string
dir1: {}, // empty directory
dir2: {
file5: 'File 5 data',
'file6.js': getJsContent(), // returns a string
dir3: {
file7: 'File 7 data',
'file8.sh': getBashContent(), // returns a string
},
},
});
If you are creating a standalone tree object and want to have type safety, use the TreeInterface type exported from fs-hooks:
import { FsHooks, type TreeInterface } from 'fs-hooks';
const tree = {
/* tree definition */
} satisfies TreeInterface;
const fsHooks = new FsHooks('/path/to/tree/root', tree);
⚠️ It is important to use the
satisfieskeyword instead of annotating the variable, otherwise you will not get the TypeScript autocompletion features when using the hooks! Make sure your TypeScript version supports it.
If the tree that was provided when instantiating the FsHooks class does not exist in the file system, it is important that you create it before using hooks. The tree can be created by calling the createTree function that accepts an FsHooks instance:
import { createTree } from 'fs-hooks';
// import an FsHooks object from your source files, e.g.
import fsHooks from './my-hooks.js';
createTree(fsHooks);
The createTree function traverses through the tree properties creating files and directories.
It returns an array of CreateTreeError errors.
function createTree(fsHooks: FsHooks<TreeInterface>): CreateTreeError[]
A hook is a function that performs some action on a given file or directory from the tree. You can create as many or as few hooks as you'd like for the same tree by registering them with the useHooks method on an FsHooks instance.
The useHooks method accepts an object that has 2 properties:
file - a function that takes a targetFile object of type FileTargetInterface and returns an object with file hooks.dir - a function that takes a targetDir object of type DirTargetInterface and returns an object with directory hooks.// describes targetFile
interface FileTargetInterface {
type: 'file';
path: string;
}
// describes targetDir
interface DirTargetInterface<Tree extends TreeInterface> {
type: 'dir';
children: ObjectTreeType<Tree>;
path: string;
}
type ObjectTreeType<Tree extends TreeInterface> = {
[key in keyof Tree]: Tree[key] extends string
? FileTargetInterface
: Tree[key] extends TreeInterface
? DirTargetInterface<Tree[key]>
: never;
};
Here's a small example of how to create your own hooks:
import fs from 'node:fs';
import path from 'node:path';
const fsHooks = new FsHooks('/root/path', {
dir1: {
dir2: {
file1: 'File 1 data',
},
},
});
const hooks = fsHooks.useHooks({
file: (targetFile) => ({
read() {
return fs.readFileSync(targetFile.path, 'utf-8');
},
/* other file hooks */
}),
dir: (targetDir) => ({
readFile(fileName: string) {
const filePath = path.resolve(targetDir.path, fileName);
return fs.readFileSync(filePath, 'utf-8');
},
/* other directory hooks */
}),
});
The file method accepts an argument of type FileTargetInterface and the dir method accepts an argument of type DirTargetInterface. These arguments are objects that represent the selected file or directory from the tree when you call the function returned from the useHooks method. Following the above example, when selecting a file and a directory like this:
const file1 = hooks((root) => root.dir1.dir2.file1);
const dir1 = hooks((root) => root.dir1);
the targetFile would have the following value:
{
type: 'file',
path: '/root/path/dir1/dir2/file1',
}
and targetDir would be as follows:
{
type: 'dir',
path: '/root/path/dir1',
children: {
dir2: {
type: 'dir',
path: '/root/path/dir1/dir2',
children: {
type: 'file',
path: '/root/path/dir1/dir2/file1',
}
}
},
}
The FsHooks class has static utility methods that help you create hooks that can be provided to the useHooks method. This could be useful when you want to export common hooks, or if some of your hooks need to return the hooks themselves, for example when creating a new file or directory.
import fs from 'node:fs';
import path from 'node:path';
import { FsHooks } from 'fs-hooks';
export const fileHooks = FsHooks.fileHooks((targetFile) => ({
read() {
return fs.readFileSync(targetFile.path, 'utf-8');
}
/* other file hooks */
}));
export const dirHooks = FsHooks.dirHooks((targetDir) => ({
createFile(fileName: string, data: string = '') {
const filePath = path.resolve(targetDir.path, fileName);
fs.writeFileSync(filePath, data);
// 👇 notice that it calls fileHooks
return fileHooks({
type: 'file',
path: filePath,
});
},
createDir(dirName: string) {
const dirPath = path.resolve(targetDir.path, dirName);
fs.mkdirSync(dirPath);
// 👇 notice that it calls dirHooks
return dirHooks({
type: 'dir',
path: dirPath,
children: {},
});
},
/* other directory hooks */
}));
This allows for the following scenario:
import { FsHooks } from 'fs-hooks';
const fileHooks = FsHooks.fileHooks((targetFile) => ({ /* file hooks */ }));
const dirHooks = FsHooks.dirHooks((targetDir) => ({ /* directory hooks */ }));
const fsHooks = new FsHooks('/path/to/tree/root', {
dir1: {
dir2: {
file1: 'File 1 data',
},
},
});
const hooks = fsHooks.useHooks({
file: fileHooks,
dir: dirHooks,
});
const root = hooks((root) => root);
const newFile1 = root.createFile('new-file1', 'New file 1 data');
const data1 = newFile1.read(); // New file 1 data
const dir1 = hooks((root) => root.dir1);
const newDir1 = dir1.createDir('new-dir1');
const newDir2 = newDir1.createDir('new-dir2');
const newDir3 = newDir2.createDir('new-dir3');
const newFile2 = newDir3.createFile('new-file2', 'New file 2 data');
const data2 = newFile2.read(); // New file 2 data
💡 The above example is how core hooks are built under the hood.
The library exports a set of common hooks called coreHooks from fs-hooks/core that can be provided to the useHooks method.
import { FsHooks } from 'fs-hooks';
import { coreHooks } from 'fs-hooks/core';
const fsHooks = new FsHooks('/path/to/tree/root', {
/* tree definition */
});
const hooks = fsHooks.useHooks(coreHooks);
getPath (file hook)Returns the path of the target file.
getPath(): string
const file = hooks((root) => root.file);
const filePath = file.getPath();
readReads the contents of the target file.
stringnull if the file cannot be readread(): string | null
const file = hooks((root) => root.file);
const fileData = file.read();
writeWrites data to the target file. Data can be of type string or NodeJS.ArrayBufferView, otherwise it gets stringified.
write<Data>(data: Data): void
const file = hooks((root) => root.file);
file.write('New file data');
clearClears the contents of the target file.
clear(): void
const file = hooks((root) => root.file);
file.clear();
getPath (directory hook)Returns the path of the target directory.
getPath(): string
const dir = hooks((root) => root);
const dirPath = dir.getPath();
existsChecks if a file or directory exists inside the target directory.
exists(name: string): boolean
const dir = hooks((root) => root);
const fileExists = dir.exists('some-file');
const dirExists = dir.exists('some-dir');
dirCreateCreates a new directory inside the target directory.
false if the directory could not be createddirCreate(dirName: string, recursive: boolean = false): DirHooks | false
const dir = hooks((root) => root);
const newDir = dir.dirCreate('new-dir');
// you can access all the directory hooks on the newDir
newDir.getPath();
// even create another new directory!
const anotherDir = newDir.dirCreate('foo');
In the above example,
newDirhas all the directory hooks just like accessing a tree directory with thehooksfunction.
To create a nested directory, set the recursive flag to true:
const dir = hooks((root) => root);
const newDir = dir.dirCreate('nested/new-dir', true);
dirDeleteDeletes a directory inside the target directory.
dirDelete(dirName: string): void
const dir = hooks((root) => root);
dir.dirDelete('some-dir');
fileCreateCreates a new file inside the target directory.
false if the file could not be createdfileCreate(fileName: string, data: unknown = ''): FileHooks | false
If the
dataargument is provided and the file already exists, the file will be overwritten. Data can be of typestringorNodeJS.ArrayBufferView, otherwise it gets stringified.
const dir = hooks((root) => root);
const newFile = dir.fileCreate('new-file', 'file data');
// you can access all the file hooks on the newFile
newFile.getPath();
newFile.read();
newFile.write('updated file data');
newFile.clear();
In the above example,
newFilehas all the file hooks just like accessing a tree file with thehooksfunction.
fileDeleteDeletes a file inside the target directory.
fileDelete(fileName: string): void
const dir = hooks((root) => root);
dir.fileDelete('some-file');
fileReadReads the contents of a file inside the target directory.
stringnull if the file cannot be readfileRead(fileName: string): string | null
const dir = hooks((root) => root);
const fileData = dir.fileRead('some-file');
fileWriteWrites new data to a file inside the target directory. Data can be of type string or NodeJS.ArrayBufferView, otherwise it gets stringified.
fileWrite<Data>(fileName: string, data: Data): void
const dir = hooks((root) => root);
dir.fileWrite('some-file', 'some data');
fileClearClears the contents of a file inside the target directory.
fileClear(fileName: string): void
const dir = hooks((root) => root);
dir.fileClear('some-file');
FAQs
Library for working with the file system in Node.js
We found that fs-hooks demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.