mv (Node.js)
The `mv` package by `andrewrk` provides a robust utility for moving files and directories in Node.js, specifically addressing the limitation of `fs.rename` which cannot perform operations across different devices or file systems. It first attempts a standard `fs.rename` and, upon failure (e.g., cross-device boundary), transparently falls back to a copy-then-unlink strategy. For files, this involves piping data, and for directories, it utilizes a recursive copy (`ncp`) followed by removal of the source (`rimraf`). It supports options like automatically creating destination parent directories (`mkdirp: true`) and preventing overwrites (`clobber: false`). The current stable version, `2.1.1`, was released in 2015. Given its age and lack of recent updates, this package is considered abandoned. Developers are advised to consider actively maintained alternatives, such as `move-file`, which offer modern Promise-based APIs and enhanced ESM support, if possible.
Common errors
-
Error: EEXIST: file already exists, 'dest/file'
cause Attempting to move a file or directory to a path that already exists when `clobber: false` option is used.fixEither remove the existing destination file/directory, set `clobber: true` (which is the default behavior if not specified) to overwrite, or handle the `EEXIST` error explicitly in your callback. -
Error: ENOENT: no such file or directory, open 'source/file'
cause The source file or directory specified for the move operation does not exist at the given path.fixVerify that the `source` path is correct and that the file/directory actually exists before calling `mv`. Use `fs.existsSync()` or `fs.promises.access()` to check. -
Error: EACCES: permission denied, rename 'source/file' -> 'dest/file' (or EPERM)
cause The Node.js process lacks the necessary read, write, or execute permissions for the source, destination, or an intermediate directory.fixEnsure the Node.js process has appropriate file system permissions for the paths involved. Run the process with elevated privileges if necessary (e.g., `sudo node your_script.js` on Linux/macOS, or 'Run as Administrator' on Windows), or adjust directory/file permissions. -
TypeError: mv is not a function
cause The `mv` function was not correctly imported or required, or a different module or object without an `mv` method is being referenced.fixEnsure `const mv = require('mv');` is correctly placed and executed. If using ESM, make sure to use `import mv from 'mv';` (for CommonJS default export) or dynamic `import('mv')`.
Warnings
- breaking This package is officially abandoned and has not been updated since 2015 (v2.1.1). It is not recommended for new projects and should be replaced in existing ones to mitigate potential issues.
- breaking The internal dependencies `mkdirp`, `ncp`, and `rimraf` used by `mv` are likely very old versions (e.g., `mkdirp@0.5.1`, `rimraf@2.4.0`). These old versions are known to have security vulnerabilities (CVEs) and may not behave correctly with newer Node.js versions or file system features.
- gotcha `mv` uses an asynchronous, callback-based API. Modern Node.js development predominantly uses Promises and `async/await`. Mixing callback-based code with Promises can lead to 'callback hell' or unhandled promise rejections if not carefully managed.
- gotcha When `mv` performs a cross-device move, it defaults to a copy-then-unlink strategy. For very large files or directories, this operation can be significantly slower and more resource-intensive (doubling disk space usage temporarily) than a direct `fs.rename`.
- gotcha Using the `clobber: false` option will cause `mv` to return an error with `err.code === 'EEXIST'` if the destination file or directory already exists. It does not provide an option to automatically skip the move without an error.
Install
-
npm install mv -
yarn add mv -
pnpm add mv
Imports
- mv
import { mv } from 'mv';const mv = require('mv'); - mv (ESM interop)
import { mv } from 'mv';import mv from 'mv';
- mv (dynamic import)
const mv = await import('mv').then(mod => mod.default || mod);
Quickstart
const mv = require('mv');
const fs = require('fs');
const path = require('path');
async function runExample() {
const sourceFile = path.join(__dirname, 'source.txt');
const destFile = path.join(__dirname, 'dest', 'new-name.txt');
const sourceDir = path.join(__dirname, 'old-dir');
const destDir = path.join(__dirname, 'target', 'new-dir');
// Ensure directories exist for testing
fs.mkdirSync(path.join(__dirname, 'dest'), { recursive: true });
fs.mkdirSync(sourceDir, { recursive: true });
fs.writeFileSync(sourceFile, 'Hello from source file!');
fs.writeFileSync(path.join(sourceDir, 'file-in-dir.txt'), 'Content in directory!');
console.log('Moving a file...');
await new Promise((resolve, reject) => {
mv(sourceFile, destFile, function(err) {
if (err) return reject(err);
console.log('File moved successfully to', destFile);
resolve();
});
});
console.log('\nMoving a directory and creating parent path...');
await new Promise((resolve, reject) => {
mv(sourceDir, destDir, { mkdirp: true }, function(err) {
if (err) return reject(err);
console.log('Directory moved successfully to', destDir);
resolve();
});
});
const existingFile = path.join(__dirname, 'existing.txt');
fs.writeFileSync(existingFile, 'This file exists.');
const clashTarget = path.join(__dirname, 'existing.txt'); // Intentionally clash
fs.writeFileSync(clashTarget, 'This will be clobbered unless clobber:false.');
console.log('\nAttempting to move file with clobber: false (expecting EEXIST error)...');
await new Promise((resolve) => {
mv(sourceFile, clashTarget, { clobber: false }, function(err) {
if (err) {
console.error('Expected error when clobber: false:', err.code, err.message);
// Clean up potentially clobbered file for next run if it wasn't the exact clash
if (fs.existsSync(clashTarget)) fs.unlinkSync(clashTarget);
resolve();
} else {
console.log('File moved without error when clobber: false (unexpected for this example).');
resolve();
}
});
}).catch(console.error);
// Clean up created files/dirs
if (fs.existsSync(sourceFile)) fs.unlinkSync(sourceFile);
if (fs.existsSync(destFile)) fs.unlinkSync(destFile);
if (fs.existsSync(sourceDir)) fs.rmSync(sourceDir, { recursive: true, force: true });
if (fs.existsSync(destDir)) fs.rmSync(destDir, { recursive: true, force: true });
if (fs.existsSync(path.join(__dirname, 'dest'))) fs.rmdirSync(path.join(__dirname, 'dest'));
if (fs.existsSync(path.join(__dirname, 'target'))) fs.rmdirSync(path.join(__dirname, 'target'));
if (fs.existsSync(existingFile)) fs.unlinkSync(existingFile);
}
runExample().catch(console.error);