Synchronous File Utilities for Node.js CLIs
file-utils is a Node.js library offering a set of synchronous file system utilities, derived from Grunt.file. It is primarily designed for command-line interface tools and user utilities, with explicit warnings against its use in Node.js server environments due to its blocking I/O nature. The package enables the creation of scoped file environments (`createEnv`) that automatically prefix paths for file operations, providing isolated contexts for managing files. It also supports "write filters" and "validation filters" which can modify file content/paths or control write actions, respectively. Filters can be asynchronous, which subsequently makes the `write` and `copy` methods asynchronous. The current stable version is 0.2.2, with its latest release focusing on internal cleanup and import performance improvements. Its release cadence is infrequent, and major changes between 0.1.x and 0.2.x primarily involved Node.js version support and the handling of file content types within filters.
Common errors
-
Error: EACCES: permission denied, open 'your/file/path'
cause The Node.js process does not have sufficient permissions to read, write, or delete the specified file or directory.fixEnsure the user running the Node.js script has read/write/delete permissions for the target files and directories. On Linux/macOS, check file permissions with `ls -l` and use `chmod` to grant access if necessary. -
Error: Cannot find module 'file-utils'
cause The `file-utils` package is not installed or not resolvable from the current working directory or `NODE_PATH`.fixInstall the package using `npm install file-utils` or `yarn add file-utils`. Ensure your `node_modules` directory is correctly set up. -
TypeError: file.contents.replace is not a function
cause A write filter attempted to call a string method (`replace`) on `file.contents` when it was a Buffer object (e.g., for a binary file).fixModify the filter to check the type of `file.contents` before processing. If it's a Buffer, convert it to a string first (e.g., `file.contents.toString('utf8')`) if string manipulation is intended, or handle it as a Buffer directly. -
TypeError: Cannot read properties of undefined (reading 'async') OR Error: 'done' is not a function
cause An asynchronous write filter tried to call `this.async()` or use a callback without the correct context, or `this.async()` was called in a synchronous filter expecting it to work.fixEnsure that `this.async()` is only called within an asynchronous filter function where `this` refers to the correct context provided by file-utils. The filter function itself needs to explicitly handle its asynchronous nature and pass the result to the provided callback (e.g., `done({ path, contents })`).
Warnings
- breaking Node.js 0.8 support was dropped in version 0.2.0. Users on older Node.js runtimes must remain on the ~0.1.0 series.
- gotcha file-utils operations are explicitly synchronous. The library warns against its use in Node.js server environments, as synchronous I/O can block the event loop, severely impacting performance and responsiveness.
- breaking The type of `file.contents` passed to write filters changed. In 0.1.3 it was normalized to `String`, but in 0.1.4 it was reverted to be `String` for text files and `Buffer` for binary files. Filters must handle both possibilities.
- gotcha Registering an asynchronous write filter will change the `env.write` and `env.copy` methods to also run asynchronously. This requires callers to adopt an asynchronous pattern (e.g., using `await` or callbacks) for those operations.
Install
-
npm install file-utils -
yarn add file-utils -
pnpm add file-utils
Imports
- fileUtils
import { createEnv } from 'file-utils';const fileUtils = require('file-utils'); - createEnv
import fileUtils from 'file-utils'; const env = fileUtils.createEnv({ base: './temp' });const fileUtils = require('file-utils'); const env = fileUtils.createEnv({ base: './temp' }); - EnvInstance.write
await env.write('path/to/file.txt', 'content');env.write('path/to/file.txt', 'content');
Quickstart
const fs = require('fs');
const path = require('path');
const fileUtils = require('file-utils');
// Ensure a temporary directory exists for testing
const tempDir = path.join(__dirname, 'temp-file-utils-test');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir);
}
// Create a scoped environment
const env = fileUtils.createEnv({
base: tempDir,
dest: path.join(tempDir, 'output') // Optional destination path
});
const filePath = 'my-test-file.txt';
const fileContent = 'Hello, file-utils!\nThis is a synchronous utility example.';
try {
// Write a file within the scoped base directory
env.write(filePath, fileContent);
console.log(`Successfully wrote to ${path.join(tempDir, filePath)}`);
// Read the file
const readContent = env.read(filePath);
console.log(`Successfully read from ${path.join(tempDir, filePath)}`);
console.log('Content:', readContent);
// Demonstrate a write filter (e.g., converting content to uppercase)
env.registerWriteFilter('upper-case', function(file) {
if (typeof file.contents === 'string') {
file.contents = file.contents.toUpperCase();
}
return file;
});
const filteredFilePath = 'my-filtered-file.txt';
env.write(filteredFilePath, 'This text will be uppercased by a filter.');
console.log(`Successfully wrote filtered content to ${path.join(tempDir, filteredFilePath)}`);
console.log('Filtered Content:', env.read(filteredFilePath));
// Clean up created files and the temporary directory
env.delete(filePath);
env.delete(filteredFilePath);
fs.rmdirSync(tempDir, { recursive: true });
console.log(`Cleaned up ${tempDir}`);
} catch (error) {
console.error('An error occurred:', error.message);
// Ensure cleanup even on error
if (fs.existsSync(tempDir)) {
fs.rmdirSync(tempDir, { recursive: true });
}
}