fs-extra
Advanced tools
Comparing version 6.0.1 to 7.0.0
@@ -0,1 +1,9 @@ | ||
7.0.0 / 2018-07-16 | ||
------------------ | ||
- **BREAKING:** Refine `copy*()` handling of symlinks to properly detect symlinks that point to the same file. ([#582](https://github.com/jprichardson/node-fs-extra/pull/582)) | ||
- Fix bug with copying write-protected directories ([#600](https://github.com/jprichardson/node-fs-extra/pull/600)) | ||
- Universalify `fs.lchmod()` ([#596](https://github.com/jprichardson/node-fs-extra/pull/596)) | ||
- Add `engines` field to `package.json` ([#580](https://github.com/jprichardson/node-fs-extra/pull/580)) | ||
6.0.1 / 2018-05-09 | ||
@@ -2,0 +10,0 @@ ------------------ |
@@ -9,3 +9,2 @@ 'use strict' | ||
const notExist = Symbol('notExist') | ||
const existsReg = Symbol('existsReg') | ||
@@ -27,3 +26,3 @@ function copySync (src, dest, opts) { | ||
const resolvedDest = checkPaths(src, dest) | ||
const destStat = checkPaths(src, dest) | ||
@@ -34,24 +33,23 @@ if (opts.filter && !opts.filter(src, dest)) return | ||
if (!fs.existsSync(destParent)) mkdirpSync(destParent) | ||
return startCopy(resolvedDest, src, dest, opts) | ||
return startCopy(destStat, src, dest, opts) | ||
} | ||
function startCopy (resolvedDest, src, dest, opts) { | ||
function startCopy (destStat, src, dest, opts) { | ||
if (opts.filter && !opts.filter(src, dest)) return | ||
return getStats(resolvedDest, src, dest, opts) | ||
return getStats(destStat, src, dest, opts) | ||
} | ||
function getStats (resolvedDest, src, dest, opts) { | ||
function getStats (destStat, src, dest, opts) { | ||
const statSync = opts.dereference ? fs.statSync : fs.lstatSync | ||
const st = statSync(src) | ||
const srcStat = statSync(src) | ||
if (st.isDirectory()) return onDir(st, resolvedDest, src, dest, opts) | ||
else if (st.isFile() || | ||
st.isCharacterDevice() || | ||
st.isBlockDevice()) return onFile(st, resolvedDest, src, dest, opts) | ||
else if (st.isSymbolicLink()) return onLink(resolvedDest, src, dest, opts) | ||
if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts) | ||
else if (srcStat.isFile() || | ||
srcStat.isCharacterDevice() || | ||
srcStat.isBlockDevice()) return onFile(srcStat, destStat, src, dest, opts) | ||
else if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts) | ||
} | ||
function onFile (srcStat, resolvedDest, src, dest, opts) { | ||
if (resolvedDest === notExist) return copyFile(srcStat, src, dest, opts) | ||
else if (resolvedDest === existsReg) return mayCopyFile(srcStat, src, dest, opts) | ||
function onFile (srcStat, destStat, src, dest, opts) { | ||
if (destStat === notExist) return copyFile(srcStat, src, dest, opts) | ||
return mayCopyFile(srcStat, src, dest, opts) | ||
@@ -101,19 +99,5 @@ } | ||
function onDir (srcStat, resolvedDest, src, dest, opts) { | ||
if (resolvedDest === notExist) { | ||
if (isSrcSubdir(src, dest)) { | ||
throw new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`) | ||
} | ||
return mkDirAndCopy(srcStat, src, dest, opts) | ||
} else if (resolvedDest === existsReg) { | ||
if (isSrcSubdir(src, dest)) { | ||
throw new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`) | ||
} | ||
return mayCopyDir(src, dest, opts) | ||
} | ||
return copyDir(src, dest, opts) | ||
} | ||
function mayCopyDir (src, dest, opts) { | ||
if (!fs.statSync(dest).isDirectory()) { | ||
function onDir (srcStat, destStat, src, dest, opts) { | ||
if (destStat === notExist) return mkDirAndCopy(srcStat, src, dest, opts) | ||
if (destStat && !destStat.isDirectory()) { | ||
throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`) | ||
@@ -125,5 +109,5 @@ } | ||
function mkDirAndCopy (srcStat, src, dest, opts) { | ||
fs.mkdirSync(dest, srcStat.mode) | ||
fs.chmodSync(dest, srcStat.mode) | ||
return copyDir(src, dest, opts) | ||
fs.mkdirSync(dest) | ||
copyDir(src, dest, opts) | ||
return fs.chmodSync(dest, srcStat.mode) | ||
} | ||
@@ -138,7 +122,7 @@ | ||
const destItem = path.join(dest, item) | ||
const resolvedDest = checkPaths(srcItem, destItem) | ||
return startCopy(resolvedDest, srcItem, destItem, opts) | ||
const destStat = checkPaths(srcItem, destItem) | ||
return startCopy(destStat, srcItem, destItem, opts) | ||
} | ||
function onLink (resolvedDest, src, dest, opts) { | ||
function onLink (destStat, src, dest, opts) { | ||
let resolvedSrc = fs.readlinkSync(src) | ||
@@ -150,11 +134,21 @@ | ||
if (resolvedDest === notExist || resolvedDest === existsReg) { | ||
// if dest already exists, fs throws error anyway, | ||
// so no need to guard against it here. | ||
if (destStat === notExist) { | ||
return fs.symlinkSync(resolvedSrc, dest) | ||
} else { | ||
let resolvedDest | ||
try { | ||
resolvedDest = fs.readlinkSync(dest) | ||
} catch (err) { | ||
// dest exists and is a regular file or directory, | ||
// Windows may throw UNKNOWN error. If dest already exists, | ||
// fs throws error anyway, so no need to guard against it here. | ||
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return fs.symlinkSync(resolvedSrc, dest) | ||
throw err | ||
} | ||
if (opts.dereference) { | ||
resolvedDest = path.resolve(process.cwd(), resolvedDest) | ||
} | ||
if (pathsAreIdentical(resolvedSrc, resolvedDest)) return | ||
if (isSrcSubdir(resolvedSrc, resolvedDest)) { | ||
throw new Error(`Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.`) | ||
} | ||
@@ -177,51 +171,31 @@ // prevent copy if src is a subdir of dest since unlinking | ||
// return true if dest is a subdir of src, otherwise false. | ||
// extract dest base dir and check if that is the same as src basename. | ||
function isSrcSubdir (src, dest) { | ||
const srcArray = path.resolve(src).split(path.sep) | ||
const destArray = path.resolve(dest).split(path.sep) | ||
return srcArray.reduce((acc, current, i) => { | ||
return acc && destArray[i] === current | ||
}, true) | ||
return srcArray.reduce((acc, current, i) => acc && destArray[i] === current, true) | ||
} | ||
// check if dest exists and is a symlink. | ||
function checkDest (dest) { | ||
let resolvedPath | ||
function checkStats (src, dest) { | ||
const srcStat = fs.statSync(src) | ||
let destStat | ||
try { | ||
resolvedPath = fs.readlinkSync(dest) | ||
destStat = fs.statSync(dest) | ||
} catch (err) { | ||
if (err.code === 'ENOENT') return notExist | ||
// dest exists and is a regular file or directory, Windows may throw UNKNOWN error. | ||
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return existsReg | ||
if (err.code === 'ENOENT') return {srcStat, destStat: notExist} | ||
throw err | ||
} | ||
return resolvedPath // dest exists and is a symlink | ||
return {srcStat, destStat} | ||
} | ||
function pathsAreIdentical (src, dest) { | ||
const os = process.platform | ||
const resolvedSrc = path.resolve(src) | ||
const resolvedDest = path.resolve(dest) | ||
// case-insensitive paths | ||
if (os === 'darwin' || os === 'win32') { | ||
return resolvedSrc.toLowerCase() === resolvedDest.toLowerCase() | ||
} | ||
return resolvedSrc === resolvedDest | ||
} | ||
function checkPaths (src, dest) { | ||
const resolvedDest = checkDest(dest) | ||
if (resolvedDest === notExist || resolvedDest === existsReg) { | ||
if (pathsAreIdentical(src, dest)) throw new Error('Source and destination must not be the same.') | ||
return resolvedDest | ||
} else { | ||
// check resolved dest path if dest is a symlink | ||
if (pathsAreIdentical(src, resolvedDest)) throw new Error('Source and destination must not be the same.') | ||
return resolvedDest | ||
const {srcStat, destStat} = checkStats(src, dest) | ||
if (destStat.ino && destStat.ino === srcStat.ino) { | ||
throw new Error('Source and destination must not be the same.') | ||
} | ||
if (srcStat.isDirectory() && isSrcSubdir(src, dest)) { | ||
throw new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`) | ||
} | ||
return destStat | ||
} | ||
module.exports = copySync |
@@ -10,3 +10,2 @@ 'use strict' | ||
const notExist = Symbol('notExist') | ||
const existsReg = Symbol('existsReg') | ||
@@ -33,17 +32,17 @@ function copy (src, dest, opts, cb) { | ||
checkPaths(src, dest, (err, resolvedDest) => { | ||
checkPaths(src, dest, (err, destStat) => { | ||
if (err) return cb(err) | ||
if (opts.filter) return handleFilter(checkParentDir, resolvedDest, src, dest, opts, cb) | ||
return checkParentDir(resolvedDest, src, dest, opts, cb) | ||
if (opts.filter) return handleFilter(checkParentDir, destStat, src, dest, opts, cb) | ||
return checkParentDir(destStat, src, dest, opts, cb) | ||
}) | ||
} | ||
function checkParentDir (resolvedDest, src, dest, opts, cb) { | ||
function checkParentDir (destStat, src, dest, opts, cb) { | ||
const destParent = path.dirname(dest) | ||
pathExists(destParent, (err, dirExists) => { | ||
if (err) return cb(err) | ||
if (dirExists) return startCopy(resolvedDest, src, dest, opts, cb) | ||
if (dirExists) return startCopy(destStat, src, dest, opts, cb) | ||
mkdirp(destParent, err => { | ||
if (err) return cb(err) | ||
return startCopy(resolvedDest, src, dest, opts, cb) | ||
return startCopy(destStat, src, dest, opts, cb) | ||
}) | ||
@@ -53,11 +52,6 @@ }) | ||
function startCopy (resolvedDest, src, dest, opts, cb) { | ||
if (opts.filter) return handleFilter(getStats, resolvedDest, src, dest, opts, cb) | ||
return getStats(resolvedDest, src, dest, opts, cb) | ||
} | ||
function handleFilter (onInclude, resolvedDest, src, dest, opts, cb) { | ||
function handleFilter (onInclude, destStat, src, dest, opts, cb) { | ||
Promise.resolve(opts.filter(src, dest)).then(include => { | ||
if (include) { | ||
if (resolvedDest) return onInclude(resolvedDest, src, dest, opts, cb) | ||
if (destStat) return onInclude(destStat, src, dest, opts, cb) | ||
return onInclude(src, dest, opts, cb) | ||
@@ -69,18 +63,22 @@ } | ||
function getStats (resolvedDest, src, dest, opts, cb) { | ||
function startCopy (destStat, src, dest, opts, cb) { | ||
if (opts.filter) return handleFilter(getStats, destStat, src, dest, opts, cb) | ||
return getStats(destStat, src, dest, opts, cb) | ||
} | ||
function getStats (destStat, src, dest, opts, cb) { | ||
const stat = opts.dereference ? fs.stat : fs.lstat | ||
stat(src, (err, st) => { | ||
stat(src, (err, srcStat) => { | ||
if (err) return cb(err) | ||
if (st.isDirectory()) return onDir(st, resolvedDest, src, dest, opts, cb) | ||
else if (st.isFile() || | ||
st.isCharacterDevice() || | ||
st.isBlockDevice()) return onFile(st, resolvedDest, src, dest, opts, cb) | ||
else if (st.isSymbolicLink()) return onLink(resolvedDest, src, dest, opts, cb) | ||
if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts, cb) | ||
else if (srcStat.isFile() || | ||
srcStat.isCharacterDevice() || | ||
srcStat.isBlockDevice()) return onFile(srcStat, destStat, src, dest, opts, cb) | ||
else if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts, cb) | ||
}) | ||
} | ||
function onFile (srcStat, resolvedDest, src, dest, opts, cb) { | ||
if (resolvedDest === notExist) return copyFile(srcStat, src, dest, opts, cb) | ||
else if (resolvedDest === existsReg) return mayCopyFile(srcStat, src, dest, opts, cb) | ||
function onFile (srcStat, destStat, src, dest, opts, cb) { | ||
if (destStat === notExist) return copyFile(srcStat, src, dest, opts, cb) | ||
return mayCopyFile(srcStat, src, dest, opts, cb) | ||
@@ -130,13 +128,6 @@ } | ||
function onDir (srcStat, resolvedDest, src, dest, opts, cb) { | ||
if (resolvedDest === notExist) { | ||
if (isSrcSubdir(src, dest)) { | ||
return cb(new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`)) | ||
} | ||
return mkDirAndCopy(srcStat, src, dest, opts, cb) | ||
} else if (resolvedDest === existsReg) { | ||
if (isSrcSubdir(src, dest)) { | ||
return cb(new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`)) | ||
} | ||
return mayCopyDir(src, dest, opts, cb) | ||
function onDir (srcStat, destStat, src, dest, opts, cb) { | ||
if (destStat === notExist) return mkDirAndCopy(srcStat, src, dest, opts, cb) | ||
if (destStat && !destStat.isDirectory()) { | ||
return cb(new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`)) | ||
} | ||
@@ -146,18 +137,8 @@ return copyDir(src, dest, opts, cb) | ||
function mayCopyDir (src, dest, opts, cb) { | ||
fs.stat(dest, (err, st) => { | ||
if (err) return cb(err) | ||
if (!st.isDirectory()) { | ||
return cb(new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`)) | ||
} | ||
return copyDir(src, dest, opts, cb) | ||
}) | ||
} | ||
function mkDirAndCopy (srcStat, src, dest, opts, cb) { | ||
fs.mkdir(dest, srcStat.mode, err => { | ||
fs.mkdir(dest, err => { | ||
if (err) return cb(err) | ||
fs.chmod(dest, srcStat.mode, err => { | ||
copyDir(src, dest, opts, err => { | ||
if (err) return cb(err) | ||
return copyDir(src, dest, opts, cb) | ||
return fs.chmod(dest, srcStat.mode, cb) | ||
}) | ||
@@ -183,5 +164,5 @@ }) | ||
const destItem = path.join(dest, item) | ||
checkPaths(srcItem, destItem, (err, resolvedDest) => { | ||
checkPaths(srcItem, destItem, (err, destStat) => { | ||
if (err) return cb(err) | ||
startCopy(resolvedDest, srcItem, destItem, opts, err => { | ||
startCopy(destStat, srcItem, destItem, opts, err => { | ||
if (err) return cb(err) | ||
@@ -193,3 +174,3 @@ return copyDirItems(items, src, dest, opts, cb) | ||
function onLink (resolvedDest, src, dest, opts, cb) { | ||
function onLink (destStat, src, dest, opts, cb) { | ||
fs.readlink(src, (err, resolvedSrc) => { | ||
@@ -202,18 +183,24 @@ if (err) return cb(err) | ||
if (resolvedDest === notExist || resolvedDest === existsReg) { | ||
// if dest already exists, fs throws error anyway, | ||
// so no need to guard against it here. | ||
if (destStat === notExist) { | ||
return fs.symlink(resolvedSrc, dest, cb) | ||
} else { | ||
if (opts.dereference) { | ||
resolvedDest = path.resolve(process.cwd(), resolvedDest) | ||
} | ||
if (pathsAreIdentical(resolvedSrc, resolvedDest)) return cb() | ||
fs.readlink(dest, (err, resolvedDest) => { | ||
if (err) { | ||
// dest exists and is a regular file or directory, | ||
// Windows may throw UNKNOWN error. If dest already exists, | ||
// fs throws error anyway, so no need to guard against it here. | ||
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return fs.symlink(resolvedSrc, dest, cb) | ||
return cb(err) | ||
} | ||
if (opts.dereference) { | ||
resolvedDest = path.resolve(process.cwd(), resolvedDest) | ||
} | ||
if (isSrcSubdir(resolvedSrc, resolvedDest)) { | ||
return cb(new Error(`Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.`)) | ||
} | ||
// prevent copy if src is a subdir of dest since unlinking | ||
// dest in this case would result in removing src contents | ||
// and therefore a broken symlink would be created. | ||
fs.stat(dest, (err, st) => { | ||
if (err) return cb(err) | ||
if (st.isDirectory() && isSrcSubdir(resolvedDest, resolvedSrc)) { | ||
// do not copy if src is a subdir of dest since unlinking | ||
// dest in this case would result in removing src contents | ||
// and therefore a broken symlink would be created. | ||
if (destStat.isDirectory() && isSrcSubdir(resolvedDest, resolvedSrc)) { | ||
return cb(new Error(`Cannot overwrite '${resolvedDest}' with '${resolvedSrc}'.`)) | ||
@@ -235,49 +222,32 @@ } | ||
// return true if dest is a subdir of src, otherwise false. | ||
// extract dest base dir and check if that is the same as src basename. | ||
function isSrcSubdir (src, dest) { | ||
const srcArray = path.resolve(src).split(path.sep) | ||
const destArray = path.resolve(dest).split(path.sep) | ||
return srcArray.reduce((acc, current, i) => { | ||
return acc && destArray[i] === current | ||
}, true) | ||
return srcArray.reduce((acc, current, i) => acc && destArray[i] === current, true) | ||
} | ||
// check if dest exists and is a symlink. | ||
function checkDest (dest, cb) { | ||
fs.readlink(dest, (err, resolvedPath) => { | ||
if (err) { | ||
if (err.code === 'ENOENT') return cb(null, notExist) | ||
// dest exists and is a regular file or directory, Windows may throw UNKNOWN error. | ||
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return cb(null, existsReg) | ||
return cb(err) | ||
} | ||
return cb(null, resolvedPath) // dest exists and is a symlink | ||
function checkStats (src, dest, cb) { | ||
fs.stat(src, (err, srcStat) => { | ||
if (err) return cb(err) | ||
fs.stat(dest, (err, destStat) => { | ||
if (err) { | ||
if (err.code === 'ENOENT') return cb(null, {srcStat, destStat: notExist}) | ||
return cb(err) | ||
} | ||
return cb(null, {srcStat, destStat}) | ||
}) | ||
}) | ||
} | ||
function pathsAreIdentical (src, dest) { | ||
const os = process.platform | ||
const resolvedSrc = path.resolve(src) | ||
const resolvedDest = path.resolve(dest) | ||
// case-insensitive paths | ||
if (os === 'darwin' || os === 'win32') { | ||
return resolvedSrc.toLowerCase() === resolvedDest.toLowerCase() | ||
} | ||
return resolvedSrc === resolvedDest | ||
} | ||
function checkPaths (src, dest, cb) { | ||
checkDest(dest, (err, resolvedDest) => { | ||
checkStats(src, dest, (err, stats) => { | ||
if (err) return cb(err) | ||
if (resolvedDest === notExist || resolvedDest === existsReg) { | ||
if (pathsAreIdentical(src, dest)) return cb(new Error('Source and destination must not be the same.')) | ||
return cb(null, resolvedDest) | ||
} else { | ||
// check resolved dest path if dest is a symlink | ||
if (pathsAreIdentical(src, resolvedDest)) return cb(new Error('Source and destination must not be the same.')) | ||
return cb(null, resolvedDest) | ||
const {srcStat, destStat} = stats | ||
if (destStat.ino && destStat.ino === srcStat.ino) { | ||
return cb(new Error('Source and destination must not be the same.')) | ||
} | ||
if (srcStat.isDirectory() && isSrcSubdir(src, dest)) { | ||
return cb(new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`)) | ||
} | ||
return cb(null, destStat) | ||
}) | ||
@@ -284,0 +254,0 @@ } |
@@ -22,2 +22,3 @@ 'use strict' | ||
'lchown', | ||
'lchmod', | ||
'link', | ||
@@ -24,0 +25,0 @@ 'lstat', |
{ | ||
"name": "fs-extra", | ||
"version": "6.0.1", | ||
"version": "7.0.0", | ||
"description": "fs-extra contains methods that aren't included in the vanilla Node.js fs package. Such as mkdir -p, cp -r, and rm -rf.", | ||
"engines": { | ||
"node": ">=6 <7 || >=8" | ||
}, | ||
"homepage": "https://github.com/jprichardson/node-fs-extra", | ||
@@ -6,0 +9,0 @@ "repository": { |
@@ -117,2 +117,3 @@ Node.js: fs-extra | ||
- [ensureSymlink](docs/ensureSymlink.md) | ||
- [mkdirp](docs/ensureDir.md) | ||
- [mkdirs](docs/ensureDir.md) | ||
@@ -135,2 +136,3 @@ - [move](docs/move.md) | ||
- [ensureSymlinkSync](docs/ensureSymlink-sync.md) | ||
- [mkdirpSync](docs/ensureDir-sync.md) | ||
- [mkdirsSync](docs/ensureDir-sync.md) | ||
@@ -137,0 +139,0 @@ - [moveSync](docs/move-sync.md) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
262
148793
1593