Skip to content

Commit 4107b45

Browse files
committed
fs: add recursive copy method
Introduces recursive copy method, based on fs-extra implementation Refs: nodejs/tooling#98
1 parent f65d748 commit 4107b45

File tree

16 files changed

+1255
-1
lines changed

16 files changed

+1255
-1
lines changed

Diff for: LICENSE

+25
Original file line numberDiff line numberDiff line change
@@ -1584,3 +1584,28 @@ The externally maintained libraries used by Node.js are:
15841584
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
15851585
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15861586
"""
1587+
1588+
- fs-extra, located at lib/internal/fs/copy, is licensed as follows:
1589+
"""
1590+
(The MIT License)
1591+
1592+
Copyright (c) 2011-2021 JP Richardson
1593+
1594+
Permission is hereby granted, free of charge, to any person obtaining a copy
1595+
of this software and associated documentation files (the 'Software'), to deal
1596+
in the Software without restriction, including without limitation the rights
1597+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1598+
copies of the Software, and to permit persons to whom the Software is
1599+
furnished to do so, subject to the following conditions:
1600+
1601+
The above copyright notice and this permission notice shall be included in
1602+
all copies or substantial portions of the Software.
1603+
1604+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1605+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1606+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1607+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1608+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1609+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1610+
THE SOFTWARE.
1611+
"""

Diff for: doc/api/errors.md

+69
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,74 @@ added: v14.0.0
11111111
Used when a feature that is not available
11121112
to the current platform which is running Node.js is used.
11131113

1114+
<a id="ERR_FS_COPY_DIR_TO_NON_DIR"></a>
1115+
### `ERR_FS_COPY_DIR_TO_NON_DIR`
1116+
<!--
1117+
added: REPLACEME
1118+
-->
1119+
1120+
An attempt was made to copy a directory to a non-directory (file, symlink,
1121+
etc.) using [`fs.copy()`][].
1122+
1123+
<a id="ERR_FS_COPY_EEXIST"></a>
1124+
### `ERR_FS_COPY_EEXIST`
1125+
<!--
1126+
added: REPLACEME
1127+
-->
1128+
1129+
An attempt was made to copy over a file that already existed with
1130+
[`fs.copy()`][], with the `overwrite` and `errorOnExist` set to `true`.
1131+
1132+
<a id="ERR_FS_COPY_FIFO_PIPE"></a>
1133+
### `ERR_FS_COPY_FIFO_PIPE`
1134+
<!--
1135+
added: REPLACEME
1136+
-->
1137+
1138+
An attempt was made to copy a named pipe with [`fs.copy()`][].
1139+
1140+
<a id="ERR_FS_COPY_NON_DIR_TO_DIR"></a>
1141+
### `ERR_FS_COPY_NON_DIR_TO_DIR`
1142+
<!--
1143+
added: REPLACEME
1144+
-->
1145+
1146+
An attempt was made to copy a non-directory (file, symlink, etc.) to a directory
1147+
using [`fs.copy()`][].
1148+
1149+
<a id="ERR_FS_COPY_SOCKET"></a>
1150+
### `ERR_FS_COPY_SOCKET`
1151+
<!--
1152+
added: REPLACEME
1153+
-->
1154+
1155+
An attempt was made to copy to a socket with [`fs.copy()`][].
1156+
1157+
<a id="ERR_FS_COPY_SYMLINK_TO_SUBDIRECTORY"></a>
1158+
### `ERR_FS_COPY_SYMLINK_TO_SUBDIRECTORY`
1159+
<!--
1160+
added: REPLACEME
1161+
-->
1162+
1163+
When using [`fs.copy()`][], a symlink in `dest` pointed to a subdirectory
1164+
of `src`.
1165+
1166+
<a id="ERR_FS_COPY_TO_SUBDIRECTORY"></a>
1167+
### `ERR_FS_COPY_TO_SUBDIRECTORY`
1168+
<!--
1169+
added: REPLACEME
1170+
-->
1171+
1172+
When using [`fs.copy()`][], `dest` pointed to a subfolder in `src`.
1173+
1174+
<a id="ERR_FS_COPY_UNKNOWN"></a>
1175+
### `ERR_FS_COPY_UNKNOWN`
1176+
<!--
1177+
added: REPLACEME
1178+
-->
1179+
1180+
An attempt was made to copy to an unknown file type with [`fs.copy()`][].
1181+
11141182
<a id="ERR_FS_EISDIR"></a>
11151183
### `ERR_FS_EISDIR`
11161184

@@ -2818,6 +2886,7 @@ The native call from `process.cpuUsage` could not be processed.
28182886
[`dgram.remoteAddress()`]: dgram.md#dgram_socket_remoteaddress
28192887
[`errno`(3) man page]: https://man7.org/linux/man-pages/man3/errno.3.html
28202888
[`fs.Dir`]: fs.md#fs_class_fs_dir
2889+
[`fs.copy()`]: fs.md#fs_fs_copy_src_dest_options_callback
28212890
[`fs.readFileSync`]: fs.md#fs_fs_readfilesync_path_options
28222891
[`fs.readdir`]: fs.md#fs_fs_readdir_path_options_callback
28232892
[`fs.symlink()`]: fs.md#fs_fs_symlink_target_path_type_callback

Diff for: doc/api/fs.md

+27
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,33 @@ through any other `fs` operation may lead to undefined behavior.
17521752
17531753
See the POSIX close(2) documentation for more detail.
17541754
1755+
### `fs.copy(src, dest[, options], callback)`
1756+
<!-- YAML
1757+
added: REPLACEME
1758+
-->
1759+
1760+
* `src` {string|Buffer|URL} source path to copy.
1761+
* `dest` {string|Buffer|URL} destination path to copy to.
1762+
* `options` {Object|Function}
1763+
* `dereference` {boolean} dereference symlinks. **Default:** `false`.
1764+
* `errorOnExist` {boolean} when `overwrite` is `false`, and the destination
1765+
exists, throw an error. **Default:** `false`.
1766+
* `filter` {Function} Function to filter copied files/directories. Return
1767+
`true` to copy the item, `false` to ignore it. **Default:** `undefined`
1768+
* `overwrite` {boolean} overwrite existing file or directory. _The copy
1769+
operation will ignore errors if you set this to false and the destination
1770+
exists. Use the `errorOnExist` option to change this behavior.
1771+
**Default:** `true`.
1772+
* `preserveTimestamps` {boolean} When `true` timestamps from `src` will
1773+
be preserved. **Default:** `false`.
1774+
* `callback` {Function}
1775+
1776+
Asynchronously copies the entire directory structure from `src` to `dest`,
1777+
including subdirectories and files.
1778+
1779+
If a function is provided for `options`, it will be used as the `filter`
1780+
parameter.
1781+
17551782
### `fs.copyFile(src, dest[, mode], callback)`
17561783
<!-- YAML
17571784
added: v8.5.0

Diff for: lib/fs.js

+60
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ const {
107107
stringToSymlinkType,
108108
toUnixTimestamp,
109109
validateBufferArray,
110+
validateCopyOptions,
110111
validateOffsetLengthRead,
111112
validateOffsetLengthWrite,
112113
validatePath,
@@ -145,6 +146,8 @@ let truncateWarn = true;
145146
let fs;
146147

147148
// Lazy loaded
149+
let copyFn;
150+
let copySyncFn;
148151
let promises = null;
149152
let ReadStream;
150153
let WriteStream;
@@ -1075,6 +1078,12 @@ function ftruncateSync(fd, len = 0) {
10751078
handleErrorFromBinding(ctx);
10761079
}
10771080

1081+
function lazyLoadCopy() {
1082+
if (copyFn === undefined) {
1083+
({ copyFn } = require('internal/fs/copy/copy'));
1084+
({ copySyncFn } = require('internal/fs/copy/copy-sync'));
1085+
}
1086+
}
10781087

10791088
function lazyLoadRimraf() {
10801089
if (rimraf === undefined)
@@ -2741,6 +2750,55 @@ function mkdtempSync(prefix, options) {
27412750
return result;
27422751
}
27432752

2753+
/**
2754+
* Asynchronously copies `src` to `dest`. `src` can be a file, directory, or
2755+
* symlink. The contents of directories will be copied recursively.
2756+
* @param {string | Buffer | URL} src
2757+
* @param {string | Buffer | URL} dest
2758+
* @param {Object} [options]
2759+
* @param {() => any} callback
2760+
* @returns {void}
2761+
*/
2762+
function copy(src, dest, options, callback) {
2763+
if (typeof options === 'function' && !callback) {
2764+
callback = options;
2765+
options = {};
2766+
} else if (typeof options === 'function') {
2767+
options = { filter: options };
2768+
}
2769+
callback = makeCallback(callback);
2770+
options = options || {};
2771+
validateCopyOptions(options);
2772+
src = getValidatedPath(src);
2773+
dest = getValidatedPath(dest);
2774+
src = pathModule._makeLong(src);
2775+
dest = pathModule._makeLong(dest);
2776+
lazyLoadCopy();
2777+
copyFn(src, dest, options, callback);
2778+
}
2779+
2780+
/**
2781+
* Synchronously copies `src` to `dest`. `src` can be a file, directory, or
2782+
* symlink. The contents of directories will be copied recursively.
2783+
* @param {string | Buffer | URL} src
2784+
* @param {string | Buffer | URL} dest
2785+
* @param {Object} [options]
2786+
* @returns {void}
2787+
*/
2788+
function copySync(src, dest, options) {
2789+
if (typeof options === 'function') {
2790+
options = { filter: options };
2791+
}
2792+
options = options || {};
2793+
validateCopyOptions(options);
2794+
src = getValidatedPath(src);
2795+
dest = getValidatedPath(dest);
2796+
src = pathModule._makeLong(src);
2797+
dest = pathModule._makeLong(dest);
2798+
lazyLoadCopy();
2799+
copySyncFn(src, dest, options);
2800+
}
2801+
27442802
/**
27452803
* Asynchronously copies `src` to `dest`. By
27462804
* default, `dest` is overwritten if it already exists.
@@ -2852,6 +2910,8 @@ module.exports = fs = {
28522910
chmodSync,
28532911
close,
28542912
closeSync,
2913+
copy,
2914+
copySync,
28552915
copyFile,
28562916
copyFileSync,
28572917
createReadStream,

Diff for: lib/internal/errors.js

+12
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,18 @@ E('ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
923923
'The feature %s is unavailable on the current platform' +
924924
', which is being used to run Node.js',
925925
TypeError);
926+
E('ERR_FS_COPY_DIR_TO_NON_DIR',
927+
'Cannot overwrite directory with non-directory', SystemError);
928+
E('ERR_FS_COPY_EEXIST', 'Target already exists', SystemError);
929+
E('ERR_FS_COPY_FIFO_PIPE', 'Cannot copy a FIFO pipe', SystemError);
930+
E('ERR_FS_COPY_NON_DIR_TO_DIR',
931+
'Cannot overwrite non-directory with directory', SystemError);
932+
E('ERR_FS_COPY_SOCKET', 'Cannot copy a socket file', SystemError);
933+
E('ERR_FS_COPY_SYMLINK_TO_SUBDIRECTORY',
934+
'Cannot overwrite symlink in subdirectory of self', SystemError);
935+
E('ERR_FS_COPY_TO_SUBDIRECTORY',
936+
'Cannot copy to a subdirectory of self', SystemError);
937+
E('ERR_FS_COPY_UNKNOWN', 'Cannot copy a unknown file type', SystemError);
926938
E('ERR_FS_EISDIR', 'Path is a directory', SystemError);
927939
E('ERR_FS_FILE_TOO_LARGE', 'File size (%s) is greater than 2 GB', RangeError);
928940
E('ERR_FS_INVALID_SYMLINK_TYPE',

0 commit comments

Comments
 (0)