Recursive File Copy Utility
recursive-copy is a robust and flexible utility for copying files and directories within a Node.js environment. It is currently stable at version 2.0.14 and appears to be actively maintained, with a focus on reliability and advanced use cases for filesystem operations. Key features include recursive directory copying, sophisticated filtering using functions, regular expressions, or globs, dynamic file renaming, and stream-based content transformation. It differentiates itself by integrating with `graceful-fs` and `mkdirp` to handle common filesystem errors, automatically filters out OS junk files by default, and provides an event-driven interface alongside traditional callback and promise-based APIs. This makes it suitable for complex build processes or file manipulation tasks where fine-grained control and error handling are crucial, offering a more feature-rich alternative to basic `fs.copyFile` or `fs.cp` methods.
Common errors
-
EPERM: operation not permitted, open 'destination/path/file.txt'
cause The Node.js process does not have sufficient write permissions to create or modify files in the specified destination directory.fixEnsure the process has write access to the `dest` directory and its parent folders. This might require changing directory permissions (`chmod`) or running the application with administrative privileges. -
ENOENT: no such file or directory, stat 'source/path/non-existent-file.txt'
cause The source path (`src`) provided to `copy()` does not exist or is inaccessible.fixVerify that the `src` argument points to an actual, existing file or directory on the file system and that the Node.js process has read access to it. -
TypeError: Expected a 'Buffer' or 'string', but got undefined
cause This error typically occurs within a custom `transform` stream if it attempts to pass an invalid or `undefined` chunk to the next stream stage, or doesn't properly handle `null` (end-of-stream) chunks.fixReview your `transform` function. Ensure it always emits `Buffer` or `string` data for file content, and correctly handles `done(null, chunk)` or `done(null, null)` for the end of a stream.
Warnings
- gotcha By default, `recursive-copy` will NOT overwrite existing files in the destination. If a file with the same name exists at the destination, the copy operation for that specific file will be skipped without an error, unless `overwrite: true` is specified.
- gotcha Files starting with a dot (e.g., `.env`, `.gitkeep`, `.DS_Store`) are not copied by default. This can lead to unexpected omissions if not explicitly configured.
- gotcha Symbolic links are copied as symbolic links by default (`expand: false`). If you intend to copy the *content* of the files or directories that symlinks point to, this behavior must be changed.
- gotcha Incorrectly configured `filter` options (regex, glob, or function) can lead to files being unexpectedly included or excluded. Glob patterns are relative to the `src` directory, which can be a common source of error.
- gotcha File system permissions errors (EPERM) are common when the Node.js process lacks the necessary write privileges for the destination directory, or read privileges for the source. While `graceful-fs` helps, it cannot circumvent OS-level permission restrictions.
Install
-
npm install recursive-copy -
yarn add recursive-copy -
pnpm add recursive-copy
Imports
- copy
import { copy } from 'recursive-copy';import copy from 'recursive-copy';
- copy.events
import { events } from 'recursive-copy';import copy from 'recursive-copy'; const { COPY_FILE_START, ERROR } = copy.events; - Options
import copy, { type Options } from 'recursive-copy';
Quickstart
import copy from 'recursive-copy';
import path from 'path';
import fs from 'fs';
const sourceDir = path.join(process.cwd(), 'temp_src');
const destDir = path.join(process.cwd(), 'temp_dest');
// Create dummy source files for demonstration
fs.mkdirSync(sourceDir, { recursive: true });
fs.writeFileSync(path.join(sourceDir, 'file1.txt'), 'Hello world!');
fs.writeFileSync(path.join(sourceDir, '.dotfile'), 'Hidden content.');
fs.mkdirSync(path.join(sourceDir, 'subdir'), { recursive: true });
fs.writeFileSync(path.join(sourceDir, 'subdir', 'file2.js'), 'console.log("JS file");');
async function runCopyExample() {
try {
console.log(`Copying from ${sourceDir} to ${destDir}...`);
const results = await copy(sourceDir, destDir, {
overwrite: true, // Overwrite if destination files exist
dot: true, // Copy dotfiles
filter: ['**/*', '!*.log'] // Copy all files except .log files
});
console.info(`Copied ${results.length} files successfully.`);
results.forEach(op => console.log(` - ${op.src} -> ${op.dest}`));
} catch (error) {
console.error('Copy failed: ' + error.message);
} finally {
// Clean up temporary directories
fs.rmSync(sourceDir, { recursive: true, force: true });
fs.rmSync(destDir, { recursive: true, force: true });
console.log('Temporary directories cleaned up.');
}
}
runCopyExample();