Copy files easily via JavaScript or the CLI and cross-platform usage using cli-nano dependency for the CLI.
The library is very similar to the copyfiles package, at least from the outside; however it is quite different internally. It uses native NodeJS as much as possible and so as a lot less dependencies (just 2 instead of 7), which makes this package a lot smaller compared to the original copyfiles project (1.8kB instead of 27.6kB gzip). The options are nearly the same (except for --soft, which is not implemented), there's also some new options that were added in this project (mainly the rename and dry-run features, see below).
Note: there is 1 noticeable difference with
copyfilespackage, all the CLI options must be provided as suffix and after the source/target directories command (the originalcopyfilesproject has them as prefix).
This mean calling:copyfiles source target [options]instead ofcopyfiles [options] source targetThe JS API is also different since the destination is the 2nd function argument instead of the first argument.
Note
This project now requires Node.JS >= 22.17.0 so that we can use the native fs.glob and decrease the projet size. If you can't update your Node.JS just yet, then just stick with native-copyfiles: ^1.3.7 until you can. The version 2.0 bumped Node requirement and changed the JS API arguments (see below).
native-copyfiles supports advanced glob patterns, including:
- Brace expansion: e.g.
src/*.{js,ts} - Negation: e.g.
['src/**/*.js', '!src/**/*.test.js'] - Extended wildcards: e.g.
**/*.js,*bar?.js - Dotfiles: Use
-a/--allto include files starting with a dot
This makes it easier to match complex sets of files for copying, similar to Bash or advanced glob libraries.
npm install native-copyfiles -D Usage: copyfiles <inFile..> <outDirectory> [options]
Positionals:
inFile Source file(s) [string|string[]]
outDirectory Destination directory [string]
Options:
-u, --up slice a path off the bottom of the paths [number]
-a, --all include files & directories begining with a dot (.) [boolean]
-d, --dry-run show what would be copied, without actually copying anything [boolean]
-f, --flat flatten the output [boolean]
-e, --exclude pattern or glob to exclude (may be passed multiple times) [string|string[]]
-E, --error throw error if nothing is copied [boolean]
-V, --verbose print more information to console [boolean]
-F, --follow follow symbolic links [boolean]
-s, --stat show statistics after execution (time + files/folders count) [boolean]
-v, --version show version number [boolean]
-h, --help show help [boolean]
Note
Options must be provided after the command directories as suffix (the original project references them as prefix)
Copy some files, give it a bunch of arguments (which can include advanced globs), the last argument being the "out" directory (which will be created when necessary). Note: on Windows globs must be double quoted, everybody else can quote however they please.
copyfiles foo foobar foo/bar/*.js outyou 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.
Brace expansion:
copyfiles "src/*.{js,ts}" outNegation:
copyfiles "src/**/*.js" out -e "**/*.test.js"Dotfiles:
copyfiles -a ".*.env" outIf all the files are in a folder that you don't want in the path out path, ex:
copyfiles something/*.js outwhich would put all the JS files in "out/something", you can use the --up (or -u) option
copyfiles something/*.js out -u 1which would put all the JS files in out
you can also just do -f which will flatten all the output into one directory, so in the end we'll have files "./foo/a.txt" and "./foo/bar/b.txt"
copyfiles ./foo/*.txt ./foo/bar/*.txt out -fwill put "a.txt" and "b.txt" into out
if your terminal doesn't support globstars then you can quote them
copyfiles ./foo/**/*.txt out -fhowever this does not work by default on a Mac, but the following does:
copyfiles "./foo/**/*.txt" out -fYou 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, and if we want to exclude all files ending in ".test.js" you could do
copyfiles "**/*.test.js" -f "./foo/**/*.js" out -eNote
By default the .git/ and node_modules/ directories will be excluded (when using globs). If you provide your own --exclude option, it will override the defaults and only use your patterns.
Other options include
-aor--allwhich includes files that start with a dot.-For--followwhich follows symbolic links
You can copy and rename a single file by specifying the source file and the destination filename (not just a directory). For example, to copy input/.env_publish to output/.env:
copyfiles input/.env_publish output/.envThis will copy and rename the file in one step. You can use this for any filename, not just files starting with a dot:
copyfiles input/original.txt output/renamed.txtIf the destination path is a directory, the file will be copied into that directory as usual. If the destination path is a filename, the file will be copied and renamed.
You can use a wildcard (*) in the destination to rename files dynamically. For example, to copy all .css files and change their extension to .scss:
copyfiles "input/**/*.css" "output/*.scss"This will copy:
input/foo.css→output/foo.scssinput/bar/baz.css→output/bar/baz.scss
The * in the destination is replaced with the base filename from the source.
You can combine this with --flat or --up to control the output structure.
For advanced renaming, you can use the rename callback option in the API.
This function receives the source and destination path and should return the new destination path of each file being processed.
Example: Change extension to .scss using a callback
import { copyfiles } from 'native-copyfiles';
copyfiles(['input/**/*.css', 'output'], {
flat: true,
rename: (src, dest) => dest.replace(/\.css$/, '.scss')
}, (err) => {
// All files like input/foo.css → output/foo.scss
});Example: Prefix all filenames with renamed- but keep the extension
copyfiles(['input/**/*.css', 'output'], {
up: 1,
rename: (src, dest) => dest.replace(/([^/\\]+)\.css$/, 'renamed-$1.css')
}, (err) => {
// input/foo.css → output/renamed-foo.css
// input/bar/baz.css → output/bar/renamed-baz.css
});The rename callback gives you full control over the output filename and path.
Tip: You can use either the wildcard approach or the
renamecallback, or even combine them for advanced scenarios!
Note
If you use both a destination wildcard approach (e.g. output/*.ext) and a rename callback, the wildcard change is applied first and then the rename callback is executed last on the computed destination path. This allows you to combine both features for advanced renaming scenarios.
import { copyfiles } from 'native-copyfiles';
copyfiles(sources, destination, opt, callback);- first argument is a string or an array of source paths
- second argument is the destination path
- third argument (
opt) is the "options" argument - and finally the last argument is a callback function that will be executed after the copy process
{
verbose: boolean; // print more information to console
up: number; // slice a path off the bottom of the paths
exclude: string; // exclude pattern
all: boolean; // include dot files
dryRun: boolean; // show what would be copied, without actually copying anything
follow: boolean; // follow symlinked directories when expanding ** patterns
error: boolean; // raise errors if no files copied
stat: boolean; // show statistics after execution (time + files or folders count)
rename: (src, dest) => string; // callback to transform the destination filename(s)
}Warning
Version 2.0 changed the JS API and moved the destination as the 2nd argument (which is different compared to v1.0 which previously had its destination inside the 1st argument array as the last element which was super confusing).