{"id":10888,"library":"file-utils","title":"Synchronous File Utilities for Node.js CLIs","description":"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.","status":"maintenance","version":"0.2.2","language":"javascript","source_language":"en","source_url":"https://github.com/SBoudrias/file-utils","tags":["javascript","file","fs","utils","util","scaffold","tool","cli"],"install":[{"cmd":"npm install file-utils","lang":"bash","label":"npm"},{"cmd":"yarn add file-utils","lang":"bash","label":"yarn"},{"cmd":"pnpm add file-utils","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"This package is a CommonJS module. Direct named ESM imports like `import { createEnv }` will not work. Access methods like `createEnv` via the imported `fileUtils` object (e.g., `fileUtils.createEnv()`).","wrong":"import { createEnv } from 'file-utils';","symbol":"fileUtils","correct":"const fileUtils = require('file-utils');"},{"note":"While some bundlers might shim `import fileUtils from 'file-utils'` for CJS, the canonical Node.js way is `require()`. `createEnv` is a method on the main exported object.","wrong":"import fileUtils from 'file-utils';\nconst env = fileUtils.createEnv({ base: './temp' });","symbol":"createEnv","correct":"const fileUtils = require('file-utils');\nconst env = fileUtils.createEnv({ base: './temp' });"},{"note":"By default, file operations like `write` are synchronous. They become asynchronous ONLY if an asynchronous write filter is registered with the environment. If an async filter is present, you MUST handle `write` as an async operation.","wrong":"await env.write('path/to/file.txt', 'content');","symbol":"EnvInstance.write","correct":"env.write('path/to/file.txt', 'content');"}],"quickstart":{"code":"const fs = require('fs');\nconst path = require('path');\nconst fileUtils = require('file-utils');\n\n// Ensure a temporary directory exists for testing\nconst tempDir = path.join(__dirname, 'temp-file-utils-test');\nif (!fs.existsSync(tempDir)) {\n  fs.mkdirSync(tempDir);\n}\n\n// Create a scoped environment\nconst env = fileUtils.createEnv({\n  base: tempDir,\n  dest: path.join(tempDir, 'output') // Optional destination path\n});\n\nconst filePath = 'my-test-file.txt';\nconst fileContent = 'Hello, file-utils!\\nThis is a synchronous utility example.';\n\ntry {\n  // Write a file within the scoped base directory\n  env.write(filePath, fileContent);\n  console.log(`Successfully wrote to ${path.join(tempDir, filePath)}`);\n\n  // Read the file\n  const readContent = env.read(filePath);\n  console.log(`Successfully read from ${path.join(tempDir, filePath)}`);\n  console.log('Content:', readContent);\n\n  // Demonstrate a write filter (e.g., converting content to uppercase)\n  env.registerWriteFilter('upper-case', function(file) {\n    if (typeof file.contents === 'string') {\n      file.contents = file.contents.toUpperCase();\n    }\n    return file;\n  });\n\n  const filteredFilePath = 'my-filtered-file.txt';\n  env.write(filteredFilePath, 'This text will be uppercased by a filter.');\n  console.log(`Successfully wrote filtered content to ${path.join(tempDir, filteredFilePath)}`);\n  console.log('Filtered Content:', env.read(filteredFilePath));\n\n  // Clean up created files and the temporary directory\n  env.delete(filePath);\n  env.delete(filteredFilePath);\n  fs.rmdirSync(tempDir, { recursive: true });\n  console.log(`Cleaned up ${tempDir}`);\n\n} catch (error) {\n  console.error('An error occurred:', error.message);\n  // Ensure cleanup even on error\n  if (fs.existsSync(tempDir)) {\n    fs.rmdirSync(tempDir, { recursive: true });\n  }\n}","lang":"javascript","description":"This quickstart demonstrates how to create a scoped file utility environment, write and read files synchronously, register a write filter to modify content, and clean up resources."},"warnings":[{"fix":"Upgrade Node.js to version 0.10.0 or higher, or explicitly use 'file-utils@~0.1.0'.","message":"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.","severity":"breaking","affected_versions":">=0.2.0"},{"fix":"Use this library only for command-line tools or one-off scripts where synchronous operations are acceptable or desired. For server-side applications, opt for asynchronous file system utilities (e.g., Node.js built-in `fs.promises` or `fs` with callbacks).","message":"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.","severity":"gotcha","affected_versions":">=0.1.0"},{"fix":"Update filter functions to check `typeof file.contents` or `Buffer.isBuffer(file.contents)` and handle string and buffer content types appropriately (e.g., `file.contents.toString('utf8')` for buffers, or `Buffer.from(file.contents)` for strings if needed).","message":"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.","severity":"breaking","affected_versions":">=0.1.4"},{"fix":"If an async filter is used, ensure that calls to `env.write` and `env.copy` are handled asynchronously. For example, if `this.async()` is used in a filter, the consumer of `env.write` must treat it as a function returning a Promise or accepting a callback.","message":"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.","severity":"gotcha","affected_versions":">=0.1.0"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Ensure 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.","cause":"The Node.js process does not have sufficient permissions to read, write, or delete the specified file or directory.","error":"Error: EACCES: permission denied, open 'your/file/path'"},{"fix":"Install the package using `npm install file-utils` or `yarn add file-utils`. Ensure your `node_modules` directory is correctly set up.","cause":"The `file-utils` package is not installed or not resolvable from the current working directory or `NODE_PATH`.","error":"Error: Cannot find module 'file-utils'"},{"fix":"Modify 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.","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).","error":"TypeError: file.contents.replace is not a function"},{"fix":"Ensure 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 })`).","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.","error":"TypeError: Cannot read properties of undefined (reading 'async') OR Error: 'done' is not a function"}],"ecosystem":"npm"}