279

Is there an easier way to copy a folder and all its content without manually doing a sequence of fs.readir, fs.readfile, fs.writefile recursively?

I am just wondering if I'm missing a function which would ideally work like this:

fs.copy("/path/to/source/folder", "/path/to/destination/folder");

Regarding this historic question. Note that fs.cp and fs.cpSync can copy folders recursively and are available in Node v16+

lostsource
  • 21,070
  • 8
  • 66
  • 88
  • 4
    Is there a way to do this without any modules? Maybe a recursive function / code snip-it? – Sukima Aug 08 '13 at 17:52
  • @Sukima - See my answer [here](http://stackoverflow.com/a/21321485/552792). – jamesmortensen Jan 23 '14 at 23:31
  • what do you mean by "without manually" and without modules?. Are you asking for a native bash command or any Nodejs built-in library? if yes, there isn't such a thing yet. The options are to reinvent the wheel and create a recursive function or use a third party wheel – Soldeplata Saketos Sep 17 '21 at 13:12
  • 4
    nowadays it's simply **fs.cpSync** that's all it is, simple – Fattie Aug 16 '22 at 14:56

30 Answers30

109

It looks like ncp and wrench both are no longer maintained. Probably the best option is to use fs-extra

The Developer of Wrench directs users to use fs-extra as he has deprecated his library

copySync & moveSync both will copy and move folders even if they have files or subfolders and you can easily move or copy files using it

const fse = require('fs-extra');

const srcDir = `path/to/file`;
const destDir = `path/to/destination/directory`;
                                 
// To copy a folder or file, select overwrite accordingly
try {
  fse.copySync(srcDir, destDir, { overwrite: true|false })
  console.log('success!')
} catch (err) {
  console.error(err)
}

OR

// To Move a folder or file, select overwrite accordingly
try {
  fs.moveSync(srcDir, destDir, { overwrite: true|false })
  console.log('success!')
} catch (err) {
  console.error(err)
}
Nam G VU
  • 33,193
  • 69
  • 233
  • 372
Aryan
  • 3,338
  • 4
  • 18
  • 43
  • 6
    fse copySync does not take callbacks: https://github.com/jprichardson/node-fs-extra/blob/HEAD/docs/copy-sync.md. If you want to detect error you should do try-catch: `try { fse.copySync('/tmp/myfile', '/tmp/mynewfile'); console.log('success!');} catch (err) { console.error(err);}` – João Pimentel Ferreira Dec 30 '20 at 21:13
  • `copySync`'s arguments have changed, third arg is an options object, not a callback: https://github.com/jprichardson/node-fs-extra/blob/master/docs/copy-sync.md – mummybot Jun 10 '21 at 08:53
  • 1
    FYI node has experimental `fs.cp` https://nodejs.org/api/fs.html#fs_fspromises_cp_src_dest_options, which > "Asynchronously copies the entire directory structure from src to dest, including subdirectories and files." – Georgiy Bukharov Sep 06 '21 at 16:31
  • 1
    Hi @GeorgiyBukharov It will help but its still in experimental in Node v16.7.0 as of Now. It will be easy to use in future versions of node. – Aryan Sep 07 '21 at 08:15
  • just to clarify, copySync it accepts either opts object or function according to it's implementation. However the function as third parameter is treated as filter option, not a callback. – pdr0 Jan 21 '22 at 11:22
  • There's no reason to add a callback to a `_Sync` function--just use the main-line code. – ggorlen Apr 21 '22 at 15:19
92

Since Node v16.7.0 it is possible to use fs.cp or fs.cpSync function.

fs.cp(src, dest, {recursive: true}, (err) => {/* callback */});
// Or
fs.cpSync(src, dest, {recursive: true});

Current stability (in Node v19.8.1) is Experimental.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Jax-p
  • 7,225
  • 4
  • 28
  • 58
  • 1
    `node -e "const fs = require('fs');fs.cp(...);"` is super handy in package.json scripts – TamusJRoyce Feb 21 '23 at 15:51
  • @TamusJRoyce: If you like that, you'll love Node's the `-r` (`--require`) parameter. – Lee Goddard Mar 01 '23 at 08:47
  • I've only used -r with external scripts. How can -r be used with inline scripts? – TamusJRoyce Mar 06 '23 at 04:29
  • I want to use that, but, um, as the link says, "_Stability: 1 - Experimental. The feature is not subject to semantic versioning rules. Non-backward compatible changes or removal may occur in any future release. Use of the feature is not recommended in production environments._" – ruffin May 26 '23 at 02:03
87

This is my approach to solve this problem without any extra modules. Just using the built-in fs and path modules.

Note: This does use the read / write functions of fs, so it does not copy any meta data (time of creation, etc.). As of Node.js 8.5 there is a copyFileSync function available which calls the OS copy functions and therefore also copies meta data. I did not test them yet, but it should work to just replace them. (See https://nodejs.org/api/fs.html#fs_fs_copyfilesync_src_dest_flags)

var fs = require('fs');
var path = require('path');

function copyFileSync( source, target ) {

    var targetFile = target;

    // If target is a directory, a new file with the same name will be created
    if ( fs.existsSync( target ) ) {
        if ( fs.lstatSync( target ).isDirectory() ) {
            targetFile = path.join( target, path.basename( source ) );
        }
    }

    fs.writeFileSync(targetFile, fs.readFileSync(source));
}

function copyFolderRecursiveSync( source, target ) {
    var files = [];

    // Check if folder needs to be created or integrated
    var targetFolder = path.join( target, path.basename( source ) );
    if ( !fs.existsSync( targetFolder ) ) {
        fs.mkdirSync( targetFolder );
    }

    // Copy
    if ( fs.lstatSync( source ).isDirectory() ) {
        files = fs.readdirSync( source );
        files.forEach( function ( file ) {
            var curSource = path.join( source, file );
            if ( fs.lstatSync( curSource ).isDirectory() ) {
                copyFolderRecursiveSync( curSource, targetFolder );
            } else {
                copyFileSync( curSource, targetFolder );
            }
        } );
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Simon Zyx
  • 6,503
  • 1
  • 25
  • 37
  • it doesn't copy folders if they have space in their names – 31415926 Nov 13 '14 at 15:41
  • For me it does copy folders with spaces in their names. Maybe it was caused by the error corrected by @victor . As i am using this function quite regularly (in the current state, as i forgot to update the very same correction victor did), i am quite sure that it does work in general. – Simon Zyx Dec 08 '14 at 15:56
  • @SimonZyx, `fs.createReadStream` is not synchronous. Changing it to ` fs.writeFileSync(target, fs.readFileSync(source));` will make it so. – Zephyr was a Friend of Mine Nov 30 '15 at 17:47
  • this won't work if the source is a file, only works for directories. – vdegenne Apr 16 '18 at 07:01
  • 3
    This doesn't actually copy files. It reads them then writes them. That's not copying. Copying includes creation date as well as other meta data streams that both Windows and MacOS support and are not copied by this code. As of node 8.5 you should call `fs.copy` or `fs.copySync` as they actual calls the OS level copy functions in MacOS and Windows and so actually copy files. – gman Nov 08 '18 at 05:47
  • @gman: You are right that it does not copy the meta data, but it is still a copy in a sense of "it copies the content". I will include this in the answer. I don't see a `fs.copy` in the current node documentation (https://nodejs.org/api/fs.html). Are you sure you don't use fs-extra? This does copy the meta data, but does not use the os level copy (see https://github.com/jprichardson/node-fs-extra/blob/master/lib/copy/copy.js) – Simon Zyx Nov 19 '18 at 14:56
  • 1
    sorry it's [`fs.copyFile`](https://nodejs.org/api/fs.html#fs_fs_copyfile_src_dest_flags_callback) and if your dig through the node source you'll see on Mac and Windows they call the OS specific function to copy a file – gman Nov 20 '18 at 02:18
  • I know files have meta data, so using '''copyFile''' is a method that copies meta data. A question I have is do folders contain meta data? If they do, then what is a method of copying a folder keeping the meta data? – programmerRaj Jan 08 '20 at 03:11
78

Here's a function that recursively copies a directory and its contents to another directory:

const fs = require("fs")
const path = require("path")

/**
 * Look ma, it's cp -R.
 * @param {string} src  The path to the thing to copy.
 * @param {string} dest The path to the new copy.
 */
var copyRecursiveSync = function(src, dest) {
  var exists = fs.existsSync(src);
  var stats = exists && fs.statSync(src);
  var isDirectory = exists && stats.isDirectory();
  if (isDirectory) {
    fs.mkdirSync(dest);
    fs.readdirSync(src).forEach(function(childItemName) {
      copyRecursiveSync(path.join(src, childItemName),
                        path.join(dest, childItemName));
    });
  } else {
    fs.copyFileSync(src, dest);
  }
};
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Lindsey Simon
  • 1,001
  • 8
  • 10
  • 4
    Even if you would insert a real copy function you should not follow symbolic links (use `fs.lstatSync` instead of `fs.statSync`) – Simon Zyx Sep 25 '14 at 11:03
  • 3
    what might have caused this confusion is that fs.unlink deletes files, but fs.link doesn't copy but link. – Simon Zyx Sep 25 '14 at 11:27
  • 3
    @SimonSeyock: is right .. IT is `linking` not copying .. The issue is when you modify the content of linked file , the original file will change too. – Abdennour TOUMI Dec 08 '16 at 00:38
57

There are some modules that support copying folders with their content. The most popular would be wrench.js:

// Deep-copy an existing directory
wrench.copyDirSyncRecursive('directory_to_copy', 'location_where_copy_should_end_up');

An alternative would be node-fs-extra:

fs.copy('/tmp/mydir', '/tmp/mynewdir', function (err) {
  if (err) {
    console.error(err);
  } else {
    console.log("success!");
  }
}); // Copies directory, even if it has subdirectories or files
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
zemirco
  • 16,171
  • 8
  • 62
  • 96
  • 3
    wrench fails if directory to copy contains a symbolic link – DoubleMalt Feb 09 '13 at 13:29
  • 2
    it also fails on Windows if the directory already exists, [ncp](https://npmjs.org/package/ncp) worked right out of the bag. – blented Sep 15 '13 at 06:45
  • 6
    node-fs-extra worked for me. It inherits the original fs and I liked it's way of handling the process. Less code to update in the app. – dvdmn Jan 06 '14 at 16:07
  • 19
    Please note that `wrench` has been deprecated and should be replaced by `node-fs-extra` (https://github.com/jprichardson/node-fs-extra) – Ambidex Apr 21 '16 at 10:51
  • 1
    Wrench doesn't actually copy files. It reads them then writes them, then copies their date. That's not copying. Copying includes other meta data streams that both Windows and MacOS support and are not copied by wrench. – gman Nov 08 '18 at 05:43
39

This is how I would do it personally:

function copyFolderSync(from, to) {
    fs.mkdirSync(to);
    fs.readdirSync(from).forEach(element => {
        if (fs.lstatSync(path.join(from, element)).isFile()) {
            fs.copyFileSync(path.join(from, element), path.join(to, element));
        } else {
            copyFolderSync(path.join(from, element), path.join(to, element));
        }
    });
}

It works for folders and files.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 5
    This solution is terse and straightforward. This would be nearly exactly how I'd do it, so a +1 from me. You should improve your answer with comments in your code and describe why this solution is preferred over others and what drawbacks it may have. -- Also update what modules it requires. ("path", "fs") – Andrew Feb 25 '19 at 21:38
  • 3
    check if the folder exists at the top...will save lives ;-) if (!fs.existsSync(to)) fs.mkdirSync(to); – Tobias Mar 31 '20 at 21:59
  • This is a great snippet, no external dependencies needed. Node.js should be able to copy directories on its own without external add-ons, and it is, like this code shows. I wonder, would it be easy to extend this to work asynchronously? – Panu Logic May 14 '21 at 17:34
29

For a Linux/Unix OS, you can use the shell syntax

const shell = require('child_process').execSync;

const src = `/path/src`;
const dist = `/path/dist`;

shell(`mkdir -p ${dist}`);
shell(`cp -r ${src}/* ${dist}`);

That's it!

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Abdennour TOUMI
  • 87,526
  • 38
  • 249
  • 254
22

The fs-extra module works like a charm.

Install fs-extra:

$ npm install fs-extra

The following is the program to copy a source directory to a destination directory.

// Include the fs-extra package
var fs = require("fs-extra");

var source = 'folderA'
var destination = 'folderB'

// Copy the source folder to the destination
fs.copy(source, destination, function (err) {
    if (err){
        console.log('An error occurred while copying the folder.')
        return console.error(err)
    }
    console.log('Copy completed!')
});

References

fs-extra: https://www.npmjs.com/package/fs-extra

Example: Node.js Tutorial - Node.js Copy a Folder

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
arjun
  • 1,645
  • 1
  • 19
  • 19
18

This is pretty easy with Node.js 10:

const Path = require('path');
const FSP = require('fs').promises;

async function copyDir(src,dest) {
    const entries = await FSP.readdir(src, {withFileTypes: true});
    await FSP.mkdir(dest);
    for(let entry of entries) {
        const srcPath = Path.join(src, entry.name);
        const destPath = Path.join(dest, entry.name);
        if(entry.isDirectory()) {
            await copyDir(srcPath, destPath);
        } else {
            await FSP.copyFile(srcPath, destPath);
        }
    }
}

This assumes dest does not exist.

mpen
  • 272,448
  • 266
  • 850
  • 1,236
14

I know so many answers are already here, but no one answered it in a simple way.

Regarding fs-exra official documentation, you can do it very easy.

const fs = require('fs-extra')

// Copy file
fs.copySync('/tmp/myfile', '/tmp/mynewfile')

// Copy directory, even if it has subdirectories or files
fs.copySync('/tmp/mydir', '/tmp/mynewdir')

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Freddy Daniel
  • 602
  • 11
  • 25
  • make sure to set recursive option. fs.copySync('/tmp/mydir', '/tmp/mynewdir',{ recursive: true }) – Dheeraj Kumar Nov 07 '19 at 15:03
  • I can't find option`{ recursive: true }` from [github doc](https://github.com/jprichardson/node-fs-extra/blob/master/docs/copy-sync.md) you mentioned, Don't know is it work. – Freddy Daniel Nov 11 '19 at 11:32
  • I guess we are talking about fs-extra, but your github link points to node-fs-extra. Could be different library? – Dheeraj Kumar Nov 11 '19 at 11:36
  • @DheerajKumar, It shows node-fs-extra in github but fs-extra in [npm](https://www.npmjs.com/package/fs-extra). I don't know both are same please refer package from [npm](https://www.npmjs.com/package/fs-extra) – Freddy Daniel Nov 28 '19 at 04:55
  • Does fs-extra replace fs? – Matt May 08 '20 at 19:57
9

I created a small working example that copies a source folder to another destination folder in just a few steps (based on shift66's answer using ncp):

Step 1 - Install ncp module:

npm install ncp --save

Step 2 - create copy.js (modify the srcPath and destPath variables to whatever you need):

var path = require('path');
var ncp = require('ncp').ncp;

ncp.limit = 16;

var srcPath = path.dirname(require.main.filename); // Current folder
var destPath = '/path/to/destination/folder'; // Any destination folder

console.log('Copying files...');
ncp(srcPath, destPath, function (err) {
  if (err) {
    return console.error(err);
  }
  console.log('Copying files complete.');
});

Step 3 - run

node copy.js
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Shahar
  • 2,269
  • 1
  • 27
  • 34
7

The one with symbolic link support:

const path = require("path");
const {
  existsSync,
  mkdirSync,
  readdirSync,
  lstatSync,
  copyFileSync,
  symlinkSync,
  readlinkSync,
} = require("fs");

export function copyFolderSync(src, dest) {
  if (!existsSync(dest)) {
    mkdirSync(dest);
  }

  readdirSync(src).forEach((entry) => {
    const srcPath = path.join(src, entry);
    const destPath = path.join(dest, entry);
    const stat = lstatSync(srcPath);

    if (stat.isFile()) {
      copyFileSync(srcPath, destPath);
    } else if (stat.isDirectory()) {
      copyFolderSync(srcPath, destPath);
    } else if (stat.isSymbolicLink()) {
      symlinkSync(readlinkSync(srcPath), destPath);
    }
  });
}
allx
  • 282
  • 4
  • 5
5

Since Node v16.7.0:

import { cp } from 'fs/promises';
await cp(
  new URL('../path/to/src/', import.meta.url),
  new URL('../path/to/dest/', import.meta.url), {
    recursive: true,
  }
);

Note carefully the use of recursive: true. This prevents an ERR_FS_EISDIR error.

Read more on the Node Filesystem documentation

Benny Powers
  • 5,398
  • 4
  • 32
  • 55
4

Mallikarjun M, thank you!

fs-extra did the thing and it can even return a Promise if you do not provide a callback! :)

const path = require('path')
const fs = require('fs-extra')

let source = path.resolve( __dirname, 'folderA')
let destination = path.resolve( __dirname, 'folderB')

fs.copy(source, destination)
  .then(() => console.log('Copy completed!'))
  .catch( err => {
    console.log('An error occurred while copying the folder.')
    return console.error(err)
  })
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Luckylooke
  • 4,061
  • 4
  • 36
  • 49
3

Since I'm just building a simple Node.js script, I didn't want the users of the script to need to import a bunch of external modules and dependencies, so I put on my thinking cap and did a search for running commands from the Bash shell.

This Node.js code snippet recursively copies a folder called node-webkit.app to a folder called build:

child = exec("cp -r node-webkit.app build", function(error, stdout, stderr) {
    sys.print("stdout: " + stdout);
    sys.print("stderr: " + stderr);
    if(error !== null) {
        console.log("exec error: " + error);
    } else {

    }
});

Thanks to Lance Pollard at dzone for getting me started.

The above snippet is limited to Unix-based platforms, like macOS and Linux, but a similar technique may work for Windows.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jamesmortensen
  • 33,636
  • 11
  • 99
  • 120
  • 1
    A similar snippet does exist for windows, as referenced here: https://www.windows-commandline.com/copy-directory-command-line/. The command is `Xcopy /E /I `. Follow the reference to understand the command-prompt command. – Rik Nov 29 '20 at 18:02
  • You could use `which cp` and `where xcopy` to determine which to use. But this becomes lengthy and isn't really copying using node. – TamusJRoyce Feb 01 '23 at 17:49
3

I tried fs-extra and copy-dir to copy-folder-recursively. but I want it to

  1. work normally (copy-dir throws an unreasonable error)
  2. provide two arguments in filter: filepath and filetype (fs-extra does't tell filetype)
  3. have a dir-to-subdir check and a dir-to-file check

So I wrote my own:

// Node.js module for Node.js 8.6+
var path = require("path");
var fs = require("fs");

function copyDirSync(src, dest, options) {
  var srcPath = path.resolve(src);
  var destPath = path.resolve(dest);
  if(path.relative(srcPath, destPath).charAt(0) != ".")
    throw new Error("dest path must be out of src path");
  var settings = Object.assign(Object.create(copyDirSync.options), options);
  copyDirSync0(srcPath, destPath, settings);
  function copyDirSync0(srcPath, destPath, settings) {
    var files = fs.readdirSync(srcPath);
    if (!fs.existsSync(destPath)) {
      fs.mkdirSync(destPath);
    }else if(!fs.lstatSync(destPath).isDirectory()) {
      if(settings.overwrite)
        throw new Error(`Cannot overwrite non-directory '${destPath}' with directory '${srcPath}'.`);
      return;
    }
    files.forEach(function(filename) {
      var childSrcPath = path.join(srcPath, filename);
      var childDestPath = path.join(destPath, filename);
      var type = fs.lstatSync(childSrcPath).isDirectory() ? "directory" : "file";
      if(!settings.filter(childSrcPath, type))
        return;
      if (type == "directory") {
        copyDirSync0(childSrcPath, childDestPath, settings);
      } else {
        fs.copyFileSync(childSrcPath, childDestPath, settings.overwrite ? 0 : fs.constants.COPYFILE_EXCL);
        if(!settings.preserveFileDate)
          fs.futimesSync(childDestPath, Date.now(), Date.now());
      }
    });
  }
}
copyDirSync.options = {
  overwrite: true,
  preserveFileDate: true,
  filter: function(filepath, type) {
    return true;
  }
};

And a similar function, mkdirs, which is an alternative to mkdirp:

function mkdirsSync(dest) {
  var destPath = path.resolve(dest);
  mkdirsSync0(destPath);
  function mkdirsSync0(destPath) {
    var parentPath = path.dirname(destPath);
    if(parentPath == destPath)
      throw new Error(`cannot mkdir ${destPath}, invalid root`);
    if (!fs.existsSync(destPath)) {
      mkdirsSync0(parentPath);
      fs.mkdirSync(destPath);
    }else if(!fs.lstatSync(destPath).isDirectory()) {
      throw new Error(`cannot mkdir ${destPath}, a file already exists there`);
    }
  }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
fuweichin
  • 1,398
  • 13
  • 14
3

I wrote this function for both copying (copyFileSync) or moving (renameSync) files recursively between directories:

// Copy files
copyDirectoryRecursiveSync(sourceDir, targetDir);
// Move files
copyDirectoryRecursiveSync(sourceDir, targetDir, true);


function copyDirectoryRecursiveSync(source, target, move) {
    if (!fs.lstatSync(source).isDirectory())
        return;

    var operation = move ? fs.renameSync : fs.copyFileSync;
    fs.readdirSync(source).forEach(function (itemName) {
        var sourcePath = path.join(source, itemName);
        var targetPath = path.join(target, itemName);

        if (fs.lstatSync(sourcePath).isDirectory()) {
            fs.mkdirSync(targetPath);
            copyDirectoryRecursiveSync(sourcePath, targetPath);
        }
        else {
            operation(sourcePath, targetPath);
        }
    });
}
jvalanen
  • 2,809
  • 1
  • 16
  • 9
EladTal
  • 2,167
  • 1
  • 18
  • 10
3

Be careful when picking your package. Some packages like copy-dir does not support copying large files more than 0X1FFFFFE8 characters (about 537 MB) long.

It will throw some error like:

buffer.js:630 Uncaught Error: Cannot create a string longer than 0x1fffffe8 characters

I have experienced something like this in one of my projects. Ultimately, I had to change the package I was using and adjust a lot of code. I would say that this is not a very pleasant experience.

If multiple source and multiple destination copies are desired, you can use better-copy and write something like this:

// Copy from multiple source into a directory
bCopy(['/path/to/your/folder1', '/path/to/some/file.txt'], '/path/to/destination/folder');

Or even:

// Copy from multiple source into multiple destination
bCopy(['/path/to/your/folder1', '/path/to/some/file.txt'], ['/path/to/destination/folder', '/path/to/another/folder']);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Donovan P
  • 591
  • 5
  • 9
3

Use shelljs

npm i -D shelljs

const bash = require('shelljs');
bash.cp("-rf", "/path/to/source/folder", "/path/to/destination/folder");
Liakos
  • 512
  • 5
  • 10
3

TypeScript version

async function copyDir(source: string, destination: string): Promise<any> {
  const directoryEntries = await readdir(source, { withFileTypes: true });
  await mkdir(destination, { recursive: true });

  return Promise.all(
    directoryEntries.map(async (entry) => {
      const sourcePath = path.join(source, entry.name);
      const destinationPath = path.join(destination, entry.name);

      return entry.isDirectory()
        ? copyDir(sourcePath, destinationPath)
        : copyFile(sourcePath, destinationPath);
    })
  );
}
stpoa
  • 5,223
  • 2
  • 14
  • 26
1

This code will work just fine, recursively copying any folder to any location. But it is Windows only.

var child = require("child_process");
function copySync(from, to){
    from = from.replace(/\//gim, "\\");
    to = to.replace(/\//gim, "\\");
    child.exec("xcopy /y /q \"" + from + "\\*\" \"" + to + "\\\"");
}

It works perfectly for my textbased game for creating new players.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

If you are on Linux, and performance is not an issue, you may use the exec function from child_process module, to execute a Bash command:

const { exec } = require('child_process');
exec('cp -r source dest', (error, stdout, stderr) => {...});

In some cases, I found this solution cleaner than downloading an entire module or even using the fs module.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Emilio Grisolía
  • 1,183
  • 1
  • 9
  • 16
1

If you want to copy all contents of source directory recursively then you need to pass recursive option as true and try catch is documented way by fs-extra for sync

As fs-extra is complete replacement of fs so you don't need to import the base module

const fs = require('fs-extra');
let sourceDir = '/tmp/src_dir';
let destDir = '/tmp/dest_dir';
try {
  fs.copySync(sourceDir, destDir, { recursive: true })
  console.log('success!')
} catch (err) {
  console.error(err)
}

Ahsan.Amin
  • 736
  • 6
  • 14
1

For older node versions that don't have fs.cp, I'm using this in a pinch to avoid requiring a third-party library:

const fs = require("fs").promises;
const path = require("path");

const cp = async (src, dest) => {
  const lstat = await fs.lstat(src).catch(err => false);

  if (!lstat) {
    return;
  }
  else if (await lstat.isFile()) {
    await fs.copyFile(src, dest);
  }
  else if (await lstat.isDirectory()) {
    await fs.mkdir(dest).catch(err => {});

    for (const f of await fs.readdir(src)) {
      await cp(path.join(src, f), path.join(dest, f));
    }
  }
};

// sample usage
(async () => {
  const src = "foo";
  const dst = "bar";

  for (const f of await fs.readdir(src)) {
    await cp(path.join(src, f), path.join(dst, f));
  }
})();

Advantages (or differentiators) relative to existing answers:

  • async
  • ignores symbolic links
  • doesn't throw if a directory already exists (don't catch the mkdir throw if this is undesirable)
  • fairly succinct
ggorlen
  • 44,755
  • 7
  • 76
  • 106
1

This could be a possible solution using async generator function and iterating over with for await loop. This solution includes the possibility to filter out some directories passing them as an optional third array argument.

import path from 'path';
import { readdir, copy } from 'fs-extra';

async function* getFilesRecursive(srcDir: string, excludedDir?: PathLike[]): AsyncGenerator<string> {
  const directoryEntries: Dirent[] = await readdir(srcDir, { withFileTypes: true });
  if (!directoryEntries.length) yield srcDir; // If the directory is empty, return the directory path.
  for (const entry of directoryEntries) {
    const fileName = entry.name;
      const sourcePath = resolvePath(`${srcDir}/${fileName}`);
      if (entry.isDirectory()) {
        if (!excludedDir?.includes(sourcePath)) {
          yield* getFilesRecursive(sourcePath, excludedDir);
        }
      } else {
        yield sourcePath;
      }
  }
}

Then:

for await (const filePath of getFilesRecursive(path, ['dir1', 'dir2'])) {
   await copy(filePath, filePath.replace(path, path2));
}
Flavio Del Grosso
  • 490
  • 2
  • 7
  • 21
0

This is how I did it:

let fs = require('fs');
let path = require('path');

Then:

let filePath = // Your file path

let fileList = []
    var walkSync = function(filePath, filelist)
    {
        let files = fs.readdirSync(filePath);
        filelist = filelist || [];
        files.forEach(function(file)
        {
            if (fs.statSync(path.join(filePath, file)).isDirectory())
            {
                filelist = walkSync(path.join(filePath, file), filelist);
            }
            else
            {
                filelist.push(path.join(filePath, file));
            }
        });

        // Ignore hidden files
        filelist = filelist.filter(item => !(/(^|\/)\.[^\/\.]/g).test(item));

        return filelist;
    };

Then call the method:

This.walkSync(filePath, fileList)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
uyghurbeg
  • 176
  • 3
  • 8
0

The current top answer can be greatly simplified.

const path = require('path');
const fs = require('fs');

function recursiveCopySync(source, target) {
  if (fs.lstatSync(source).isDirectory()) {
    if (!fs.existsSync(target)) {
      fs.mkdirSync(target);
    }
    let files = fs.readdirSync(source);
    files.forEach((file) => {
      recursiveCopySync(path.join(source, file), path.join(target, file));
    });
  } else {
    if (fs.existsSync(source)) {
      fs.writeFileSync(target, fs.readFileSync(source));
    }
  }
}
solosodium
  • 723
  • 5
  • 15
0

Inline version

node -e "const fs=require('fs');const p=require('path');function copy(src, dest) {if (!fs.existsSync(src)) {return;} if (fs.statSync(src).isFile()) {fs.copyFileSync(src, dest);}else{fs.mkdirSync(dest, {recursive: true});fs.readdirSync(src).forEach(f=>copy(p.join(src, f), p.join(dest, f)));}}const args=Array.from(process.argv); copy(args[args.length-2], args[args.length-1]);" dist temp\dest

or node 16.x+

node -e "const fs=require('fs');const args=Array.from(process.argv); fs.cpSync(args[args.length-2], args[args.length-1], {recursive: true});" 

Tested on "node 14.20.0", but assuming it works on node 10.x?

From user8894303 and mpen's answers: https://stackoverflow.com/a/52338335/458321

Be sure to \" escape quotes if using in package.json script

package.json:

  "scripts": {
    "rmrf": "node -e \"const fs=require('fs/promises');const args=Array.from(process.argv); Promise.allSettled(args.map(a => fs.rm(a, { recursive: true, force: true })));\"",
    "cp": "node -e \"const fs=require('fs');const args=Array.from(process.argv);if (args.length>2){ fs.cpSync(args[args.length-2], args[args.length-1], {recursive: true});}else{console.log('args missing', args);}\""
    "copy": "node -e \"const fs=require('fs');const p=require('path');function copy(src, dest) {if (!fs.existsSync(src)) {return;} if (fs.statSync(src).isFile()) {fs.copyFileSync(src, dest);}else{fs.mkdirSync(dest, {recursive: true});fs.readdirSync(src).forEach(f=>copy(p.join(src, f), p.join(dest, f)));}}const args=Array.from(process.argv);if (args.length>2){copy(args[args.length-2], args[args.length-1]);}else{console.log('args missing', args);}\"",
    "mkdir": "node -e \"const fs=require('fs');const args=Array.from(process.argv);fs.mkdirSync(args[args.length-1],{recursive:true});\"",
    "clean": "npm run rmrf -- temp && npm run mkdir -- temp && npm run copy -- dist temp"
  }

note: rmrf script requires node 14.20.x or 12.20.x?

bonus:

deno eval "import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync } from 'node:fs';import { join } from 'node:path';function copy(src, dest) {if (!existsSync(src)) {return;} if (statSync(src).isFile()) {copyFileSync(src, dest);}else{mkdirSync(dest, {recursive: true});readdirSync(src).forEach(f=>copy(join(src, f), join(dest, f)));}}const args=Array.from(Deno.args);copy(args[0], args[1]);" dist temp\dest -- --allow-read --allow-write

deno support -> npm i deno-bin for deno-bin support in node

TamusJRoyce
  • 817
  • 1
  • 12
  • 25
  • native alternative to fs-copy-file-sync, copyfiles, fs-extra, etc... – TamusJRoyce Feb 01 '23 at 19:36
  • copy doesn't support glob matching yet. Neither does rmrf. – TamusJRoyce Feb 21 '23 at 15:48
  • Glad I made this answer. I have came back to it like 20 times or so. Not specifically above. Just "script-name": "`node -e \"[script]\" "` and now to pass arguments via `npm run script-name -- argument1 argument2` – TamusJRoyce Jun 20 '23 at 17:55
-2

Yes, ncp is cool though...

You might want/should promisify its function to make it super cool. While you're at it, add it to a tools file to reuse it.

Below is a working version which is Async and uses Promises.


File index.js

const {copyFolder} = require('./tools/');

return copyFolder(
    yourSourcePath,
    yourDestinationPath
)
.then(() => {
    console.log('-> Backup completed.')
}) .catch((err) => {
    console.log("-> [ERR] Could not copy the folder: ", err);
})

File tools.js

const ncp = require("ncp");

/**
 * Promise Version of ncp.ncp()
 *
 * This function promisifies ncp.ncp().
 * We take the asynchronous function ncp.ncp() with
 * callback semantics and derive from it a new function with
 * promise semantics.
 */
ncp.ncpAsync = function (sourcePath, destinationPath) {
  return new Promise(function (resolve, reject) {
      try {
          ncp.ncp(sourcePath, destinationPath, function(err){
              if (err) reject(err); else resolve();
          });
      } catch (err) {
          reject(err);
      }
  });
};

/**
 * Utility function to copy folders asynchronously using
 * the Promise returned by ncp.ncp().
 */
const copyFolder = (sourcePath, destinationPath) => {
    return ncp.ncpAsync(sourcePath, destinationPath, function (err) {
        if (err) {
            return console.error(err);
        }
    });
}
module.exports.copyFolder = copyFolder;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mick
  • 30,759
  • 16
  • 111
  • 130
-2

The easiest approach for this problem is to use only the 'fs' and 'Path' module and some logic...

All files in the root folder copy with the new name if you want to just set the version number, i.e., " var v = 'Your Directory Name'"

In the file name prefix with content added with the file name.

var fs = require('fs-extra');
var path = require('path');

var c = 0;
var i = 0;
var v = "1.0.2";
var copyCounter = 0;
var directoryCounter = 0;
var directoryMakerCounter = 0;
var recursionCounter = -1;
var Flag = false;
var directoryPath = [];
var directoryName = [];
var directoryFileName = [];
var fileName;
var directoryNameStorer;
var dc = 0;
var route;

if (!fs.existsSync(v)) {
    fs.mkdirSync(v);
}

var basePath = path.join(__dirname, v);


function walk(dir) {

    fs.readdir(dir, function(err, items) {

        items.forEach(function(file) {

            file = path.resolve(dir, file);

            fs.stat(file, function(err, stat) {

                if(stat && stat.isDirectory()) {
                    directoryNameStorer = path.basename(file);
                    route = file;
                    route = route.replace("gd", v);

                    directoryFileName[directoryCounter] = route;
                    directoryPath[directoryCounter] = file;
                    directoryName[directoryCounter] = directoryNameStorer;

                    directoryCounter++;
                    dc++;

                    if (!fs.existsSync(basePath + "/" + directoryName[directoryMakerCounter])) {
                        fs.mkdirSync(directoryFileName[directoryMakerCounter]);
                        directoryMakerCounter++;
                    }
                }
                else {
                    fileName = path.basename(file);
                    if(recursionCounter >= 0) {
                        fs.copyFileSync(file, directoryFileName[recursionCounter] + "/" + v + "_" + fileName, err => {
                            if(err) return console.error(err);
                        });
                        copyCounter++;
                    }
                    else {
                        fs.copyFileSync(file, v + "/" + v + "_" + fileName, err => {
                            if(err) return console.error(err);
                        });
                        copyCounter++;
                    }
                }
                if(copyCounter + dc == items.length && directoryCounter > 0 && recursionCounter < directoryMakerCounter-1) {
                    console.log("COPY COUNTER:             " + copyCounter);
                    console.log("DC COUNTER:               " + dc);
                    recursionCounter++;
                    dc = 0;
                    copyCounter = 0;
                    console.log("ITEM DOT LENGTH:          " + items.length);
                    console.log("RECURSION COUNTER:        " + recursionCounter);
                    console.log("DIRECOTRY MAKER COUNTER:  " + directoryMakerCounter);
                    console.log(": START RECURSION:        " + directoryPath[recursionCounter]);
                    walk(directoryPath[recursionCounter]); //recursive call to copy sub-folder
                }
            })
        })
    });
}

walk('./gd', function(err, data) { // Just pass the root directory which you want to copy
    if(err)
        throw err;
    console.log("done");
})
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
MM Furkan
  • 53
  • 1
  • 9