copyfiles vs cpx vs fs-extra vs ncp
File Copying Strategies in Node.js Build Pipelines
copyfilescpxfs-extrancpSimilar Packages:

File Copying Strategies in Node.js Build Pipelines

copyfiles, cpx, fs-extra, and ncp are all Node.js utilities designed to copy files and directories, but they serve different roles in a modern development workflow. fs-extra is a comprehensive drop-in replacement for the native fs module, offering robust promise-based APIs for copying, moving, and ensuring directory existence within JavaScript code. copyfiles and cpx are command-line tools optimized for build scripts; copyfiles focuses on simple glob patterns with flat or structured output, while cpx adds support for watching files and executing commands on change. ncp is an older, asynchronous copying tool that largely lacks modern features like Promise support and active maintenance, making it less suitable for new architectures.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
copyfiles0422-566 years agoMIT
cpx0526-3510 years agoMIT
fs-extra09,60059.3 kB1210 days agoMIT
ncp0681-7811 years agoMIT

File Copying Strategies in Node.js Build Pipelines

Moving assets, configuration files, and build artifacts is a daily task in frontend engineering. While the native Node.js fs module provides basic tools, it often requires verbose boilerplate for common tasks like recursive copying or ensuring directories exist. The ecosystem offers four distinct solutions: fs-extra, copyfiles, cpx, and ncp. Let's examine how they differ in architecture, usage, and reliability.

πŸ› οΈ Execution Context: Library vs. CLI Tool

The first major distinction is whether the tool is designed to be imported into your code or run as a shell command.

fs-extra is a JavaScript library. You import it directly into your scripts, giving you full programmatic control over error handling and flow.

// fs-extra: Used inside a .js file
const fse = require('fs-extra');

async function copyAssets() {
  try {
    await fse.copy('./src/assets', './dist/assets');
    console.log('Copy successful');
  } catch (err) {
    console.error('Copy failed', err);
  }
}

copyfiles, cpx, and ncp are primarily Command Line Interface (CLI) tools. You invoke them from your terminal or package.json scripts.

# copyfiles: Run in terminal or npm script
npx copyfiles -u 1 "src/assets/**/*" dist

# cpx: Run in terminal with watch mode
npx cpx "src/assets/**/*" dist --watch

# ncp: Run in terminal (legacy)
npx ncp ./src/assets ./dist/assets

πŸ”„ Watching and Development Workflow

In local development, you often need files to sync automatically when you save them. This is where the CLI tools diverge significantly.

cpx excels here with a built-in --watch flag. It keeps the process running and copies files instantly upon detection of changes. This is critical for dev servers that don't bundle static assets.

# cpx: Watches for changes and copies automatically
npx cpx "src/styles/**/*.css" dist/styles --watch

copyfiles does not support watching out of the box. You would need to wrap it with an external tool like nodemon or chokidar-cli to achieve similar behavior, adding unnecessary complexity to your setup.

# copyfiles: Runs once and exits (no watch mode)
npx copyfiles -u 1 "src/styles/**/*.css" dist
# To watch, you must add external tools: 
# nodemon --exec "copyfiles ..." --watch src/styles

fs-extra has no built-in watcher either. Since it is a library, you must implement the watching logic yourself using fs.watch or a third-party library, then trigger the copy method manually.

// fs-extra: Manual watch implementation required
const chokidar = require('chokidar');
const fse = require('fs-extra');

chokidar.watch('src/styles').on('change', path => {
  fse.copy(path, `dist/${path}`);
});

ncp also lacks watching capabilities and, being unmaintained, does not integrate well with modern event-driven workflows.

πŸ“‚ Path Handling and Glob Patterns

How each tool interprets file paths and directory structures affects how you configure your build scripts.

copyfiles uses a unique -u (up) flag to strip directory levels. This is powerful for flattening structures or moving files up a tier without complex rename logic.

# copyfiles: Strip 1 directory level (-u 1)
# src/images/logo.png -> dist/logo.png
npx copyfiles -u 1 "src/images/**/*" dist

cpx relies on standard glob patterns similar to chokidar. It preserves the directory structure relative to the glob match by default, which feels more natural for mirroring folders.

# cpx: Preserves structure relative to match
# src/images/logo.png -> dist/images/logo.png
npx cpx "src/images/**/*" dist

fs-extra requires explicit source and destination paths. It does not parse glob patterns natively; you must combine it with a library like globby if you need to copy multiple disjointed files based on patterns.

// fs-extra: Explicit paths only (no native glob)
const fse = require('fs-extra');

// Copies entire directory tree
await fse.copy('./src/images', './dist/images');

// For globs, you need extra code:
// const files = await globby('src/**/*.png');
// await Promise.all(files.map(f => fse.copy(f, `dist/${f}`)));

ncp handles simple directory-to-directory copying but struggles with complex glob patterns without significant wrapper code.

# ncp: Simple directory copy only
npx ncp ./src/images ./dist/images

βš™οΈ API Modernity and Promise Support

Modern Node.js development relies heavily on async/await. The level of native Promise support varies wildly among these packages.

fs-extra was built with Promises in mind. Every method returns a Promise if no callback is provided, making it seamless to use in modern async functions.

// fs-extra: Native Promise support
async function migrate() {
  await fse.ensureDir('./dist/data'); // Creates dir if missing
  await fse.copy('./data.json', './dist/data/config.json');
}

copyfiles and cpx are CLI tools, so Promise support is irrelevant for direct usage. However, if you try to require them as modules in Node.js code, their APIs are often callback-based or return streams, which can be awkward to integrate into modern promise chains without wrapping.

ncp is strictly callback-based. It does not support Promises natively. You must use util.promisify to make it work with await, which adds friction and potential for errors.

// ncp: Requires manual promisification
const { promisify } = require('util');
const ncp = require('ncp');
const ncpPromise = promisify(ncp.ncp);

// Verbose compared to fs-extra
await ncpPromise('./src', './dist');

⚠️ Maintenance and Reliability

Trust is a key factor in architectural decisions. You need to know the tool will work with future Node.js versions.

fs-extra is actively maintained, widely adopted in the enterprise, and considered the de facto standard for file operations in the Node.js ecosystem. It handles edge cases (like permissions and open files) robustly.

cpx and copyfiles are stable CLI utilities. While their update frequency is lower than libraries, they solve specific, narrow problems effectively and have no known critical deprecation warnings.

ncp is effectively deprecated. It has not seen significant updates in years, lacks active maintenance, and its feature set is entirely superseded by fs-extra. Using it introduces unnecessary risk and technical debt.

πŸ“Š Summary: Key Differences

Featurefs-extracpxcopyfilesncp
TypeLibrary (JS)CLI ToolCLI ToolCLI Tool / Library
Promise Supportβœ… NativeN/A (CLI)N/A (CLI)❌ (Needs promisify)
Watch Mode❌ (Manual setup)βœ… Built-in❌ (External needed)❌
Glob Patterns❌ (Needs globby)βœ… Nativeβœ… Native❌ (Limited)
Path Flattening❌ (Manual logic)βŒβœ… (-u flag)❌
Status🟒 Active Standard🟒 Stable🟒 StableπŸ”΄ Legacy/Unmaintained

πŸ’‘ The Big Picture

fs-extra is your go-to solution for programmatic control. If you are writing build scripts, plugins, or server-side logic where error handling and directory creation matter, this is the only professional choice. It replaces the native fs module entirely.

cpx is the specialist for development workflows. If your npm run dev script needs to sync static assets while you code, cpx saves you from configuring complex watchers. It is the pragmatic choice for "copy and watch" scenarios.

copyfiles serves well for simple, one-time build steps. If you just need to move a folder of images to a dist directory during a production build and don't need watching, its lightweight nature and path-stripping flags are convenient.

ncp should be avoided. There is no compelling reason to use it in a modern stack when fs-extra offers better safety, Promise support, and active maintenance.

Final Thought: For most frontend architectures, a combination of fs-extra (for complex Node scripts) and cpx (for dev-server asset syncing) provides the most robust and maintainable file handling strategy.

How to Choose: copyfiles vs cpx vs fs-extra vs ncp

  • copyfiles:

    Choose copyfiles when you need a lightweight, zero-dependency CLI tool for simple file copying tasks in your package.json scripts. It is ideal for one-off build steps where you need to flatten directory structures or move specific file types without writing custom Node.js code. Avoid it if you need file watching or complex transformation logic during the copy process.

  • cpx:

    Choose cpx if your workflow requires copying files that update frequently, such as during local development with a bundler. Its built-in --watch mode makes it superior to copyfiles for keeping asset folders in sync without restarting your dev server. It is also the better choice if you need to run a command automatically after files are copied.

  • fs-extra:

    Choose fs-extra when you need to perform file operations programmatically within your application or build scripts rather than via shell commands. It is the industry standard for reliable file handling because it adds promise support, ensureDir, and atomic operations to the native fs module. Use this for complex logic where you need to verify paths, handle errors gracefully, or chain multiple file system actions.

  • ncp:

    Do not choose ncp for new projects. While it was once a popular asynchronous copier, it is no longer actively maintained and lacks native Promise support, forcing you to wrap it in utilities like util.promisify. Its functionality is fully covered and surpassed by fs-extra, which offers better error handling and modern API design.

README for copyfiles

copyfiles Build Status

copy files easily

Install

npm install copyfiles -g

Command Line

  Usage: copyfiles [options] inFile [more files ...] outDirectory

  Options:
    -u, --up       slice a path off the bottom of the paths               [number]
    -a, --all      include files & directories begining with a dot (.)   [boolean]
    -f, --flat     flatten the output                                    [boolean]
    -e, --exclude  pattern or glob to exclude (may be passed multiple times)
    -E, --error    throw error if nothing is copied                      [boolean]
    -V, --verbose  print more information to console                     [boolean]
    -s, --soft     do not overwrite destination files if they exist      [boolean]
    -F, --follow   follow symbolink links                                [boolean]
    -v, --version  Show version number                                   [boolean]
    -h, --help     Show help                                             [boolean]

copy some files, give it a bunch of arguments, (which can include globs), the last one is the out directory (which it will create if necessary). Note: on windows globs must be double quoted, everybody else can quote however they please.

copyfiles foo foobar foo/bar/*.js out

you now have a directory called out, with the files foo and foobar in it, it also has a directory named foo with a directory named bar in it that has all the files from foo/bar that match the glob.

If all the files are in a folder that you don't want in the path out path, ex:

copyfiles something/*.js out

which would put all the js files in out/something, you can use the --up (or -u) option

copyfiles -u 1 something/*.js out

which would put all the js files in out

you can also just do -f which will flatten all the output into one directory, so with files ./foo/a.txt and ./foo/bar/b.txt

copyfiles -f ./foo/*.txt ./foo/bar/*.txt out

will put a.txt and b.txt into out

if your terminal doesn't support globstars then you can quote them

copyfiles -f ./foo/**/*.txt out

does not work by default on a mac

but

copyfiles -f "./foo/**/*.txt" out

does.

You could quote globstars as a part of input:

copyfiles some.json "./some_folder/*.json" ./dist/ && echo 'JSON files copied.'

You can use the -e option to exclude some files from the pattern, so to exclude all all files ending in .test.js you could do

copyfiles -e "**/*.test.js" -f ./foo/**/*.js out

Other options include

  • -a or --all which includes files that start with a dot.
  • -s or --soft to soft copy, which will not overwrite existing files.
  • -F or --follow which follows symbolinks

copyup

also creates a copyup command which is identical to copyfiles but -up defaults to 1

Programic API

var copyfiles = require('copyfiles');

copyfiles([paths], opt, callback);

takes an array of paths, last one is the destination path, also takes an optional argument which the -u option if a number, otherwise if it's true it's the flat option or if it is an object it is a hash of the various options (the long version e.g. up, all, flat, exclude, error, verbose, follow, and soft)

Tilde support for home directory

when the src/dest path start with tilde for home directory under windows, please make sure -u or -f is added in options or use copyup command. if not you will get Error: Illegal characters in path.