diff options
-rw-r--r-- | lib/utils/gently-rm.js | 76 | ||||
-rw-r--r-- | test/tap/gently-rm-cmdshims.js | 157 |
2 files changed, 197 insertions, 36 deletions
diff --git a/lib/utils/gently-rm.js b/lib/utils/gently-rm.js index aeb6fdede..c73bb1bfc 100644 --- a/lib/utils/gently-rm.js +++ b/lib/utils/gently-rm.js @@ -14,6 +14,8 @@ var vacuum = require('fs-vacuum') var some = require('async-some') var asyncMap = require('slide').asyncMap var normalize = require('path').normalize +var readCmdShim = require('read-cmd-shim') +var iferr = require('iferr') function gentlyRm (target, gently, base, cb) { if (!cb) { @@ -107,38 +109,30 @@ function gentlyRm (target, gently, base, cb) { // the target isn't managed directly, but maybe it's a link... log.silly('gentlyRm', 'checking to see if', resolved, 'is a link') - lstat(resolved, function (er, stat) { + readLinkOrShim(resolved, function (er, link) { if (er) { // race conditions are common when unbuilding if (er.code === 'ENOENT') return cb(null) return cb(er) } - if (!stat.isSymbolicLink()) { + if (!link) { log.error('gentlyRm', resolved, 'is outside', parent, 'and not a link') return clobberFail(resolved, parent, cb) } // ...and maybe the link source, when read... log.silly('gentlyRm', resolved, 'is a link') - readlink(resolved, function (er, link) { - if (er) { - // race conditions are common when unbuilding - if (er.code === 'ENOENT') return cb(null) - return cb(er) - } - - // ...is inside the managed parent - var source = resolve(dirname(resolved), link) - if (isInside(source, parent)) { - log.silly('gentlyRm', source, 'symlink target', resolved, 'is inside', parent) - log.verbose('gentlyRm', 'vacuuming', resolved) - return vacuum(resolved, options, cb) - } - - log.error('gentlyRm', source, 'symlink target', resolved, 'is not controlled by npm', parent) - return clobberFail(target, parent, cb) - }) + // ...is inside the managed parent + var source = resolve(dirname(resolved), link) + if (isInside(source, parent)) { + log.silly('gentlyRm', source, 'symlink target', resolved, 'is inside', parent) + log.verbose('gentlyRm', 'vacuuming', resolved) + return vacuum(resolved, options, cb) + } + + log.error('gentlyRm', source, 'symlink target', resolved, 'is not controlled by npm', parent) + return clobberFail(target, parent, cb) }) }) }) @@ -177,23 +171,19 @@ function isManaged (target) { if (cached) return cb(null, cached) // otherwise, check the path - lstat(resolved, function (er, stat) { + readLinkOrShim(resolved, function (er, source) { if (er) return cb(er) // if it's not a link, cache & return the path itself - if (!stat.isSymbolicLink()) { + if (!source) { 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) - }) + resolved = resolve(resolved, source) + resolvedPaths[resolved] = resolved + cb(null, resolved) }) } } @@ -205,13 +195,27 @@ function clobberFail (target, root, cb) { return cb(er) } +function readLinkOrShim (path, cb) { + lstat(path, iferr(cb, function (stat) { + if (stat.isSymbolicLink()) { + readlink(path, cb) + } else { + readCmdShim(path, function (er, source) { + if (!er) return cb(null, source) + // lstat wouldn't return an error on these, so we don't either. + if (er.code === 'ENOTASHIM' || er.code === 'EISDIR') { + return cb(null, null) + } else { + return cb(er) + } + }) + } + })) +} + function follow (path, cb) { - lstat(path, function (er, stat) { - if (er) return cb(path) - if (!stat.isSymbolicLink()) return cb(path) - readlink(path, function (er, source) { - if (er) return cb(path) - cb(normalize(resolve(dirname(path), source))) - }) + readLinkOrShim(path, function (er, source) { + if (!source) return cb(path) + cb(normalize(resolve(dirname(path), source))) }) } diff --git a/test/tap/gently-rm-cmdshims.js b/test/tap/gently-rm-cmdshims.js new file mode 100644 index 000000000..ebe0b7d7a --- /dev/null +++ b/test/tap/gently-rm-cmdshims.js @@ -0,0 +1,157 @@ +'use strict' +var path = require('path') +var fs = require('graceful-fs') +var test = require('tap').test +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') +var npm = require('../../lib/npm.js') + +var work = path.join(__dirname, path.basename(__filename, '.js')) +var doremove = path.join(work, 'doremove') +var dontremove = path.join(work, 'dontremove') +var example_json = { + name: 'example', + version: '1.0.0', + bin: { + 'example': 'example.js' + } +} +var example_bin = + '#!/usr/bin/env node\n' + + 'true\n' + +// NOTE: if this were actually produced on windows it would be \ not / of +// course, buuut, path.resolve doesn't understand \ outside of windows =/ +var do_example_cmd = + '@IF EXIST "%~dp0\\node.exe" (\n' + + ' "%~dp0\\node.exe" "%~dp0\\../example/example.js" %*\n' + + ') ELSE (\n' + + ' @SETLOCAL\n' + + ' @SET PATHEXT=%PATHEXT:;.JS;=;%\n' + + ' node "%~dp0\\../example/example.js" %*\n' + + ')\n' + +var do_example_cygwin = + '#!/bin/sh\n' + + 'basedir=`dirname "$0"`\n' + + '\n' + + 'case `uname` in\n' + + ' *CYGWIN*) basedir=`cygpath -w "$basedir"`;;\n' + + 'esac\n' + + '\n' + + 'if [ -x "$basedir/node" ]; then\n' + + ' "$basedir/node" "$basedir/../example/example.js" "$@"\n' + + ' ret=$?\n' + + 'else\n' + + ' node "$basedir/../example/example.js" "$@"\n' + + ' ret=$?\n' + + 'fi\n' + + 'exit $ret\n' + +var dont_example_cmd = + '@IF EXIST "%~dp0\\node.exe" (\n' + + ' "%~dp0\\node.exe" "%~dp0\\../example-other/example.js" %*\n' + + ') ELSE (\n' + + ' @SETLOCAL\n' + + ' @SET PATHEXT=%PATHEXT:;.JS;=;%\n' + + ' node "%~dp0\\../example-other/example.js" %*\n' + + ')\n' + +var dont_example_cygwin = + '#!/bin/sh\n' + + 'basedir=`dirname "$0"`\n' + + '\n' + + 'case `uname` in\n' + + ' *CYGWIN*) basedir=`cygpath -w "$basedir"`;;\n' + + 'esac\n' + + '\n' + + 'if [ -x "$basedir/node" ]; then\n' + + ' "$basedir/node" "$basedir/../example-other/example.js" "$@"\n' + + ' ret=$?\n' + + 'else\n' + + ' node "$basedir/../example-other/example.js" "$@"\n' + + ' ret=$?\n' + + 'fi\n' + + 'exit $ret\n' + +function cleanup () { + rimraf.sync(work) +} + +var doremove_module = path.join(doremove, 'node_modules', 'example') +var doremove_example_cmd = path.join(doremove, 'node_modules', '.bin', 'example.cmd') +var doremove_example_cygwin = path.join(doremove, 'node_modules', '.bin', 'example') +var dontremove_module = path.join(dontremove, 'node_modules', 'example') +var dontremove_example_cmd = path.join(dontremove, 'node_modules', '.bin', 'example.cmd') +var dontremove_example_cygwin = path.join(dontremove, 'node_modules', '.bin', 'example') + +function setup () { + mkdirp.sync(doremove_module) + mkdirp.sync(path.join(doremove, 'node_modules', '.bin')) + fs.writeFileSync(path.join(doremove, 'node_modules', 'example', 'package.json'), JSON.stringify(example_json)) + fs.writeFileSync(path.join(doremove, 'node_modules', 'example', 'example.js'), JSON.stringify(example_bin)) + fs.writeFileSync(doremove_example_cmd, do_example_cmd) + fs.writeFileSync(doremove_example_cygwin, do_example_cygwin) + + mkdirp.sync(dontremove_module) + mkdirp.sync(path.join(dontremove, 'node_modules', '.bin')) + fs.writeFileSync(path.join(dontremove, 'node_modules', 'example', 'package.json'), JSON.stringify(example_json)) + fs.writeFileSync(path.join(dontremove, 'node_modules', 'example', 'example.js'), JSON.stringify(example_bin)) + fs.writeFileSync(dontremove_example_cmd, dont_example_cmd) + fs.writeFileSync(dontremove_example_cygwin, dont_example_cygwin) +} + +test('setup', function (t) { + cleanup() + setup() + npm.load({}, function () { + t.done() + }) +}) + +// Like slide.chain, but runs all commands even if they have errors, also +// throws away results. +function runAll (cmds, done) { + runNext() + function runNext () { + if (cmds.length === 0) return done() + var cmdline = cmds.shift() + var cmd = cmdline.shift() + cmdline.push(runNext) + cmd.apply(null, cmdline) + } +} + +test('remove-cmd-shims', function (t) { + t.plan(2) + + var gentlyRm = require('../../lib/utils/gently-rm.js') + runAll([ [gentlyRm, doremove_example_cmd, true, doremove_module], + [gentlyRm, doremove_example_cygwin, true, doremove_module] ], function () { + fs.stat(doremove_example_cmd, function (er) { + t.is(er && er.code, 'ENOENT', 'cmd-shim was removed') + }) + fs.stat(doremove_example_cygwin, function (er) { + t.is(er && er.code, 'ENOENT', 'cmd-shim cygwin script was removed') + }) + }) +}) + +test('dont-remove-cmd-shims', function (t) { + t.plan(2) + var gentlyRm = require('../../lib/utils/gently-rm.js') + runAll([ [gentlyRm, dontremove_example_cmd, true, dontremove_module], + [gentlyRm, dontremove_example_cygwin, true, dontremove_module] ], function () { + fs.stat(dontremove_example_cmd, function (er) { + t.is(er, null, 'cmd-shim was not removed') + }) + fs.stat(dontremove_example_cygwin, function (er) { + t.is(er, null, 'cmd-shim cygwin script was not removed') + }) + }) +}) + +test('cleanup', function (t) { + cleanup() + t.done() +}) |