diff --git a/doc/api/fs.md b/doc/api/fs.md index f0a9c9291e0603..bb496a90c90705 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -896,6 +896,8 @@ added: v16.7.0 * `preserveTimestamps` {boolean} When `true` timestamps from `src` will be preserved. **Default:** `false`. * `recursive` {boolean} copy directories recursively **Default:** `false` + * `absolute` {boolean} convert relative symlinks to absolute symlinks. + **Default:** `true` * Returns: {Promise} Fulfills with `undefined` upon success. Asynchronously copies the entire directory structure from `src` to `dest`, @@ -2119,6 +2121,8 @@ changes: * `preserveTimestamps` {boolean} When `true` timestamps from `src` will be preserved. **Default:** `false`. * `recursive` {boolean} copy directories recursively **Default:** `false` + * `absolute` {boolean} convert relative symlinks to absolute symlinks. + **Default:** `true` * `callback` {Function} Asynchronously copies the entire directory structure from `src` to `dest`, @@ -4875,6 +4879,8 @@ added: v16.7.0 * `preserveTimestamps` {boolean} When `true` timestamps from `src` will be preserved. **Default:** `false`. * `recursive` {boolean} copy directories recursively **Default:** `false` + * `absolute` {boolean} convert relative symlinks to absolute symlinks. + **Default:** `true` Synchronously copies the entire directory structure from `src` to `dest`, including subdirectories and files. diff --git a/lib/internal/fs/cp/cp-sync.js b/lib/internal/fs/cp/cp-sync.js index 497c8c57c317dd..574aea84d686d4 100644 --- a/lib/internal/fs/cp/cp-sync.js +++ b/lib/internal/fs/cp/cp-sync.js @@ -182,7 +182,7 @@ function getStats(destStat, src, dest, opts) { srcStat.isBlockDevice()) { return onFile(srcStat, destStat, src, dest, opts); } else if (srcStat.isSymbolicLink()) { - return onLink(destStat, src, dest); + return onLink(destStat, src, dest, opts); } else if (srcStat.isSocket()) { throw new ERR_FS_CP_SOCKET({ message: `cannot copy a socket file: ${dest}`, @@ -293,9 +293,9 @@ function copyDir(src, dest, opts) { } } -function onLink(destStat, src, dest) { +function onLink(destStat, src, dest, opts) { let resolvedSrc = readlinkSync(src); - if (!isAbsolute(resolvedSrc)) { + if (opts.absolute && !isAbsolute(resolvedSrc)) { resolvedSrc = resolve(dirname(src), resolvedSrc); } if (!destStat) { diff --git a/lib/internal/fs/cp/cp.js b/lib/internal/fs/cp/cp.js index 6dc212f2f6a5fc..4ed061879f1142 100644 --- a/lib/internal/fs/cp/cp.js +++ b/lib/internal/fs/cp/cp.js @@ -222,7 +222,7 @@ async function getStatsForCopy(destStat, src, dest, opts) { srcStat.isBlockDevice()) { return onFile(srcStat, destStat, src, dest, opts); } else if (srcStat.isSymbolicLink()) { - return onLink(destStat, src, dest); + return onLink(destStat, src, dest, opts); } else if (srcStat.isSocket()) { throw new ERR_FS_CP_SOCKET({ message: `cannot copy a socket file: ${dest}`, @@ -335,9 +335,9 @@ async function copyDir(src, dest, opts) { } } -async function onLink(destStat, src, dest) { +async function onLink(destStat, src, dest, opts) { let resolvedSrc = await readlink(src); - if (!isAbsolute(resolvedSrc)) { + if (opts.absolute && !isAbsolute(resolvedSrc)) { resolvedSrc = resolve(dirname(src), resolvedSrc); } if (!destStat) { diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 66b5e39b0c0fc8..e8d4da7c66e053 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -724,6 +724,7 @@ const defaultCpOptions = { force: true, preserveTimestamps: false, recursive: false, + absolute: true, }; const defaultRmOptions = { @@ -749,6 +750,7 @@ const validateCpOptions = hideStackFrames((options) => { validateBoolean(options.force, 'options.force'); validateBoolean(options.preserveTimestamps, 'options.preserveTimestamps'); validateBoolean(options.recursive, 'options.recursive'); + validateBoolean(options.absolute, 'options.absolute'); if (options.filter !== undefined) { validateFunction(options.filter, 'options.filter'); } diff --git a/test/parallel/test-fs-cp.mjs b/test/parallel/test-fs-cp.mjs index 804b5a1f4c322c..1a806383746e37 100644 --- a/test/parallel/test-fs-cp.mjs +++ b/test/parallel/test-fs-cp.mjs @@ -95,6 +95,39 @@ function nextdir() { } +// It resolves relative symlinks to absolute path when absolute is true +{ + const src = nextdir(); + mkdirSync(src, { recursive: true }); + writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); + symlinkSync('foo.js', join(src, 'bar.js')); + + const dest = nextdir(); + mkdirSync(dest, { recursive: true }); + + cpSync(src, dest, { recursive: true, absolute: true }); + const link = readlinkSync(join(dest, 'bar.js')); + assert.strictEqual(link, join(src, 'foo.js')); +} + + +// It copies relative symlink when absolute is false +{ + const src = nextdir(); + mkdirSync(src, { recursive: true }); + writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); + symlinkSync('foo.js', join(src, 'bar.js')); + + const dest = nextdir(); + mkdirSync(dest, { recursive: true }); + const destFile = join(dest, 'foo.js'); + + cpSync(src, dest, { recursive: true, absolute: false }); + const link = readlinkSync(join(dest, 'bar.js')); + assert.strictEqual(link, 'foo.js'); +} + + // It throws error when src and dest are identical. { const src = './test/fixtures/copy/kitchen-sink';