mirror of https://github.com/nodejs/node.git
173 lines
5.0 KiB
JavaScript
173 lines
5.0 KiB
JavaScript
// only remove the thing if it's a symlink into a specific folder.
|
|
// This is a very common use-case of npm's, but not so common elsewhere.
|
|
|
|
module.exports = gentlyRm
|
|
|
|
var npm = require("../npm.js")
|
|
, log = require("npmlog")
|
|
, resolve = require("path").resolve
|
|
, dirname = require("path").dirname
|
|
, lstat = require("graceful-fs").lstat
|
|
, readlink = require("graceful-fs").readlink
|
|
, isInside = require("path-is-inside")
|
|
, vacuum = require("fs-vacuum")
|
|
, some = require("async-some")
|
|
, asyncMap = require("slide").asyncMap
|
|
, normalize = require("path").normalize
|
|
|
|
function gentlyRm (path, gently, base, cb) {
|
|
if (!cb) {
|
|
cb = base
|
|
base = undefined
|
|
}
|
|
|
|
if (!cb) {
|
|
cb = gently
|
|
gently = false
|
|
}
|
|
|
|
// never rm the root, prefix, or bin dirs.
|
|
// just a safety precaution.
|
|
var prefixes = [
|
|
npm.dir, npm.root, npm.bin, npm.prefix,
|
|
npm.globalDir, npm.globalRoot, npm.globalBin, npm.globalPrefix
|
|
]
|
|
|
|
var resolved = normalize(resolve(path))
|
|
if (prefixes.indexOf(resolved) !== -1) {
|
|
log.verbose("gentlyRm", resolved, "is part of npm and can't be removed")
|
|
return cb(new Error("May not delete: "+resolved))
|
|
}
|
|
|
|
var options = {log : log.silly.bind(log, "gentlyRm")}
|
|
if (npm.config.get("force") || !gently) options.purge = true
|
|
if (base) options.base = normalize(base)
|
|
|
|
if (!gently) {
|
|
log.verbose("gentlyRm", "vacuuming", resolved)
|
|
return vacuum(resolved, options, cb)
|
|
}
|
|
|
|
var parent = options.base = normalize(base ? base : npm.prefix)
|
|
log.verbose("gentlyRm", "verifying that", parent, "is managed by npm")
|
|
some(prefixes, isManaged(parent), function (er, matched) {
|
|
if (er) return cb(er)
|
|
|
|
if (!matched) {
|
|
log.verbose("gentlyRm", parent, "is not managed by npm")
|
|
return clobberFail(resolved, parent, cb)
|
|
}
|
|
|
|
log.silly("gentlyRm", parent, "is managed by npm")
|
|
|
|
if (isInside(resolved, parent)) {
|
|
log.silly("gentlyRm", resolved, "is under", parent)
|
|
log.verbose("gentlyRm", "vacuuming", resolved, "up to", parent)
|
|
return vacuum(resolved, options, cb)
|
|
}
|
|
|
|
log.silly("gentlyRm", resolved, "is not under", parent)
|
|
log.silly("gentlyRm", "checking to see if", resolved, "is a link")
|
|
lstat(resolved, function (er, stat) {
|
|
if (er) {
|
|
if (er.code === "ENOENT") return cb(null)
|
|
return cb(er)
|
|
}
|
|
|
|
if (!stat.isSymbolicLink()) {
|
|
log.verbose("gentlyRm", resolved, "is outside", parent, "and not a link")
|
|
return clobberFail(resolved, parent, cb)
|
|
}
|
|
|
|
log.silly("gentlyRm", resolved, "is a link")
|
|
readlink(resolved, function (er, link) {
|
|
if (er) {
|
|
if (er.code === "ENOENT") return cb(null)
|
|
return cb(er)
|
|
}
|
|
|
|
var source = resolve(dirname(resolved), link)
|
|
if (isInside(source, parent)) {
|
|
log.silly("gentlyRm", source, "inside", parent)
|
|
log.verbose("gentlyRm", "vacuuming", resolved)
|
|
return vacuum(resolved, options, cb)
|
|
}
|
|
|
|
log.silly("gentlyRm", "checking to see if", source, "is managed by npm")
|
|
some(prefixes, isManaged(source), function (er, matched) {
|
|
if (er) return cb(er)
|
|
|
|
if (matched) {
|
|
log.silly("gentlyRm", source, "is under", matched)
|
|
log.verbose("gentlyRm", "removing", resolved)
|
|
vacuum(resolved, options, cb)
|
|
}
|
|
|
|
log.verbose("gentlyRm", source, "is not managed by npm")
|
|
return clobberFail(path, parent, cb)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
var resolvedPaths = {}
|
|
function isManaged (target) {
|
|
return function predicate (path, cb) {
|
|
if (!path) {
|
|
log.verbose("isManaged", "no path")
|
|
return cb(null, false)
|
|
}
|
|
|
|
asyncMap([path, target], resolveSymlink, function (er, results) {
|
|
if (er) {
|
|
if (er.code === "ENOENT") return cb(null, false)
|
|
|
|
return cb(er)
|
|
}
|
|
|
|
var path = results[0]
|
|
var target = results[1]
|
|
var inside = isInside(target, path)
|
|
log.silly("isManaged", target, inside ? "is" : "is not", "inside", path)
|
|
|
|
return cb(null, inside && path)
|
|
})
|
|
}
|
|
|
|
function resolveSymlink (toResolve, cb) {
|
|
var resolved = resolve(toResolve)
|
|
|
|
// if the path has already been memoized, return immediately
|
|
var cached = resolvedPaths[resolved]
|
|
if (cached) return cb(null, cached)
|
|
|
|
// otherwise, check the path
|
|
lstat(resolved, function (er, stat) {
|
|
if (er) return cb(er)
|
|
|
|
// if it's not a link, cache & return the path itself
|
|
if (!stat.isSymbolicLink()) {
|
|
resolvedPaths[resolved] = resolved
|
|
return cb(null, resolved)
|
|
}
|
|
|
|
// otherwise, cache & return the link's source
|
|
readlink(resolved, function (er, source) {
|
|
if (er) return cb(er)
|
|
|
|
resolved = resolve(resolved, source)
|
|
resolvedPaths[resolved] = resolved
|
|
cb(null, resolved)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
function clobberFail (p, g, cb) {
|
|
var er = new Error("Refusing to delete: "+p+" not in "+g)
|
|
er.code = "EEXIST"
|
|
er.path = p
|
|
return cb(er)
|
|
}
|