GeezerVison is showing github.com — fs: support copy of relative links with cp and cpSync · nodejs/node@214d176
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/fs.md +21Lines changed: 21 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -876,6 +876,11 @@ try { | 876 | 876 |
| 877 | 877 | <!-- YAML | 878 | 878 | added: v16.7.0 | | 879 | +changes: | | 880 | + - version: REPLACEME | | 881 | + pr-url: https://github.com/nodejs/node/pull/41819 | | 882 | + description: Accepts an additional `verbatimSymlinks` option to specify | | 883 | + whether to perform path resolution for symlinks. | 879 | 884 | --> | 880 | 885 |
| 881 | 886 | > Stability: 1 - Experimental | @@ -896,6 +901,8 @@ added: v16.7.0 | 896 | 901 | * `preserveTimestamps` {boolean} When `true` timestamps from `src` will | 897 | 902 | be preserved. **Default:** `false`. | 898 | 903 | * `recursive` {boolean} copy directories recursively **Default:** `false` | | 904 | + * `verbatimSymlinks` {boolean} When `true`, path resolution for symlinks will | | 905 | + be skipped. **Default:** `false` | 899 | 906 | * Returns: {Promise} Fulfills with `undefined` upon success. | 900 | 907 |
| 901 | 908 | Asynchronously copies the entire directory structure from `src` to `dest`, | @@ -2063,6 +2070,11 @@ copyFile('source.txt', 'destination.txt', constants.COPYFILE_EXCL, callback); | 2063 | 2070 |
| 2064 | 2071 | <!-- YAML | 2065 | 2072 | added: v16.7.0 | | 2073 | +changes: | | 2074 | + - version: REPLACEME | | 2075 | + pr-url: https://github.com/nodejs/node/pull/41819 | | 2076 | + description: Accepts an additional `verbatimSymlinks` option to specify | | 2077 | + whether to perform path resolution for symlinks. | 2066 | 2078 | --> | 2067 | 2079 |
| 2068 | 2080 | > Stability: 1 - Experimental | @@ -2083,6 +2095,8 @@ added: v16.7.0 | 2083 | 2095 | * `preserveTimestamps` {boolean} When `true` timestamps from `src` will | 2084 | 2096 | be preserved. **Default:** `false`. | 2085 | 2097 | * `recursive` {boolean} copy directories recursively **Default:** `false` | | 2098 | + * `verbatimSymlinks` {boolean} When `true`, path resolution for symlinks will | | 2099 | + be skipped. **Default:** `false` | 2086 | 2100 | * `callback` {Function} | 2087 | 2101 |
| 2088 | 2102 | Asynchronously copies the entire directory structure from `src` to `dest`, | @@ -4646,6 +4660,11 @@ copyFileSync('source.txt', 'destination.txt', constants.COPYFILE_EXCL); | 4646 | 4660 |
| 4647 | 4661 | <!-- YAML | 4648 | 4662 | added: v16.7.0 | | 4663 | +changes: | | 4664 | + - version: REPLACEME | | 4665 | + pr-url: https://github.com/nodejs/node/pull/41819 | | 4666 | + description: Accepts an additional `verbatimSymlinks` option to specify | | 4667 | + whether to perform path resolution for symlinks. | 4649 | 4668 | --> | 4650 | 4669 |
| 4651 | 4670 | > Stability: 1 - Experimental | @@ -4665,6 +4684,8 @@ added: v16.7.0 | 4665 | 4684 | * `preserveTimestamps` {boolean} When `true` timestamps from `src` will | 4666 | 4685 | be preserved. **Default:** `false`. | 4667 | 4686 | * `recursive` {boolean} copy directories recursively **Default:** `false` | | 4687 | + * `verbatimSymlinks` {boolean} When `true`, path resolution for symlinks will | | 4688 | + be skipped. **Default:** `false` | 4668 | 4689 |
| 4669 | 4690 | Synchronously copies the entire directory structure from `src` to `dest`, | 4670 | 4691 | including subdirectories and files. | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/fs/cp/cp-sync.js +3-3Lines changed: 3 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -182,7 +182,7 @@ function getStats(destStat, src, dest, opts) { | 182 | 182 | srcStat.isBlockDevice()) { | 183 | 183 | return onFile(srcStat, destStat, src, dest, opts); | 184 | 184 | } else if (srcStat.isSymbolicLink()) { | 185 | | - return onLink(destStat, src, dest); | | 185 | + return onLink(destStat, src, dest, opts); | 186 | 186 | } else if (srcStat.isSocket()) { | 187 | 187 | throw new ERR_FS_CP_SOCKET({ | 188 | 188 | message: `cannot copy a socket file: ${dest}`, | @@ -293,9 +293,9 @@ function copyDir(src, dest, opts) { | 293 | 293 | } | 294 | 294 | } | 295 | 295 |
| 296 | | -function onLink(destStat, src, dest) { | | 296 | +function onLink(destStat, src, dest, opts) { | 297 | 297 | let resolvedSrc = readlinkSync(src); | 298 | | - if (!isAbsolute(resolvedSrc)) { | | 298 | + if (!opts.verbatimSymlinks && !isAbsolute(resolvedSrc)) { | 299 | 299 | resolvedSrc = resolve(dirname(src), resolvedSrc); | 300 | 300 | } | 301 | 301 | if (!destStat) { | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/fs/cp/cp.js +3-3Lines changed: 3 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -222,7 +222,7 @@ async function getStatsForCopy(destStat, src, dest, opts) { | 222 | 222 | srcStat.isBlockDevice()) { | 223 | 223 | return onFile(srcStat, destStat, src, dest, opts); | 224 | 224 | } else if (srcStat.isSymbolicLink()) { | 225 | | - return onLink(destStat, src, dest); | | 225 | + return onLink(destStat, src, dest, opts); | 226 | 226 | } else if (srcStat.isSocket()) { | 227 | 227 | throw new ERR_FS_CP_SOCKET({ | 228 | 228 | message: `cannot copy a socket file: ${dest}`, | @@ -335,9 +335,9 @@ async function copyDir(src, dest, opts) { | 335 | 335 | } | 336 | 336 | } | 337 | 337 |
| 338 | | -async function onLink(destStat, src, dest) { | | 338 | +async function onLink(destStat, src, dest, opts) { | 339 | 339 | let resolvedSrc = await readlink(src); | 340 | | - if (!isAbsolute(resolvedSrc)) { | | 340 | + if (!opts.verbatimSymlinks && !isAbsolute(resolvedSrc)) { | 341 | 341 | resolvedSrc = resolve(dirname(src), resolvedSrc); | 342 | 342 | } | 343 | 343 | if (!destStat) { | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/fs/utils.js +6Lines changed: 6 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -29,6 +29,7 @@ const { | 29 | 29 | codes: { | 30 | 30 | ERR_FS_EISDIR, | 31 | 31 | ERR_FS_INVALID_SYMLINK_TYPE, | | 32 | + ERR_INCOMPATIBLE_OPTION_PAIR, | 32 | 33 | ERR_INVALID_ARG_TYPE, | 33 | 34 | ERR_INVALID_ARG_VALUE, | 34 | 35 | ERR_OUT_OF_RANGE | @@ -724,6 +725,7 @@ const defaultCpOptions = { | 724 | 725 | force: true, | 725 | 726 | preserveTimestamps: false, | 726 | 727 | recursive: false, | | 728 | + verbatimSymlinks: false, | 727 | 729 | }; | 728 | 730 |
| 729 | 731 | const defaultRmOptions = { | @@ -749,6 +751,10 @@ const validateCpOptions = hideStackFrames((options) => { | 749 | 751 | validateBoolean(options.force, 'options.force'); | 750 | 752 | validateBoolean(options.preserveTimestamps, 'options.preserveTimestamps'); | 751 | 753 | validateBoolean(options.recursive, 'options.recursive'); | | 754 | + validateBoolean(options.verbatimSymlinks, 'options.verbatimSymlinks'); | | 755 | + if (options.dereference === true && options.verbatimSymlinks === true) { | | 756 | + throw new ERR_INCOMPATIBLE_OPTION_PAIR('dereference', 'verbatimSymlinks'); | | 757 | + } | 752 | 758 | if (options.filter !== undefined) { | 753 | 759 | validateFunction(options.filter, 'options.filter'); | 754 | 760 | } | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-fs-cp.mjs +71Lines changed: 71 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -95,6 +95,77 @@ function nextdir() { | 95 | 95 | } | 96 | 96 |
| 97 | 97 |
| | 98 | +// It throws error when verbatimSymlinks is not a boolean. | | 99 | +{ | | 100 | + const src = './test/fixtures/copy/kitchen-sink'; | | 101 | + [1, [], {}, null, 1n, undefined, null, Symbol(), '', () => {}] | | 102 | + .forEach((verbatimSymlinks) => { | | 103 | + assert.throws( | | 104 | + () => cpSync(src, src, { verbatimSymlinks }), | | 105 | + { code: 'ERR_INVALID_ARG_TYPE' } | | 106 | + ); | | 107 | + }); | | 108 | +} | | 109 | + | | 110 | + | | 111 | +// It throws an error when both dereference and verbatimSymlinks are enabled. | | 112 | +{ | | 113 | + const src = './test/fixtures/copy/kitchen-sink'; | | 114 | + assert.throws( | | 115 | + () => cpSync(src, src, { dereference: true, verbatimSymlinks: true }), | | 116 | + { code: 'ERR_INCOMPATIBLE_OPTION_PAIR' } | | 117 | + ); | | 118 | +} | | 119 | + | | 120 | + | | 121 | +// It resolves relative symlinks to their absolute path by default. | | 122 | +{ | | 123 | + const src = nextdir(); | | 124 | + mkdirSync(src, { recursive: true }); | | 125 | + writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); | | 126 | + symlinkSync('foo.js', join(src, 'bar.js')); | | 127 | + | | 128 | + const dest = nextdir(); | | 129 | + mkdirSync(dest, { recursive: true }); | | 130 | + | | 131 | + cpSync(src, dest, { recursive: true }); | | 132 | + const link = readlinkSync(join(dest, 'bar.js')); | | 133 | + assert.strictEqual(link, join(src, 'foo.js')); | | 134 | +} | | 135 | + | | 136 | + | | 137 | +// It resolves relative symlinks when verbatimSymlinks is false. | | 138 | +{ | | 139 | + const src = nextdir(); | | 140 | + mkdirSync(src, { recursive: true }); | | 141 | + writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); | | 142 | + symlinkSync('foo.js', join(src, 'bar.js')); | | 143 | + | | 144 | + const dest = nextdir(); | | 145 | + mkdirSync(dest, { recursive: true }); | | 146 | + | | 147 | + cpSync(src, dest, { recursive: true, verbatimSymlinks: false }); | | 148 | + const link = readlinkSync(join(dest, 'bar.js')); | | 149 | + assert.strictEqual(link, join(src, 'foo.js')); | | 150 | +} | | 151 | + | | 152 | + | | 153 | +// It does not resolve relative symlinks when verbatimSymlinks is true. | | 154 | +{ | | 155 | + const src = nextdir(); | | 156 | + mkdirSync(src, { recursive: true }); | | 157 | + writeFileSync(join(src, 'foo.js'), 'foo', 'utf8'); | | 158 | + symlinkSync('foo.js', join(src, 'bar.js')); | | 159 | + | | 160 | + const dest = nextdir(); | | 161 | + mkdirSync(dest, { recursive: true }); | | 162 | + | | 163 | + cpSync(src, dest, { recursive: true, verbatimSymlinks: true }); | | 164 | + const link = readlinkSync(join(dest, 'bar.js')); | | 165 | + assert.strictEqual(link, 'foo.js'); | | 166 | +} | | 167 | + | | 168 | + | 98 | 169 | // It throws error when src and dest are identical. | 99 | 170 | { | 100 | 171 | const src = './test/fixtures/copy/kitchen-sink'; | |
|