diff --git a/lib/async.js b/lib/async.js index fc6d6839..031601a6 100644 --- a/lib/async.js +++ b/lib/async.js @@ -25,12 +25,16 @@ var defaultIsDir = function isDirectory(dir, cb) { }); }; -var maybeUnwrapSymlink = function maybeUnwrapSymlink(x, opts, cb) { +var defaultUnwrapSymlink = function unwrapSymlink(x, cb) { + fs.realpath(x, function (realPathErr, realPath) { + if (realPathErr && realPathErr.code !== 'ENOENT') cb(realPathErr); + else cb(null, realPathErr ? x : realPath); + }); +}; + +var maybeUnwrapSymlink = function maybeUnwrapSymlink(unwrap, x, opts, cb) { if (!opts || !opts.preserveSymlinks) { - fs.realpath(x, function (realPathErr, realPath) { - if (realPathErr && realPathErr.code !== 'ENOENT') cb(realPathErr); - else cb(null, realPathErr ? x : realPath); - }); + unwrap(x, cb); } else { cb(null, x); } @@ -63,6 +67,7 @@ module.exports = function resolve(x, options, callback) { var isFile = opts.isFile || defaultIsFile; var isDirectory = opts.isDirectory || defaultIsDir; var readFile = opts.readFile || fs.readFile; + var unwrapSymlink = opts.unwrapSymlink || defaultUnwrapSymlink; var packageIterator = opts.packageIterator; var extensions = opts.extensions || ['.js']; @@ -75,6 +80,7 @@ module.exports = function resolve(x, options, callback) { var absoluteStart = path.resolve(basedir); maybeUnwrapSymlink( + unwrapSymlink, absoluteStart, opts, function (err, realStart) { @@ -110,7 +116,7 @@ module.exports = function resolve(x, options, callback) { } else loadNodeModules(x, basedir, function (err, n, pkg) { if (err) cb(err); else if (n) { - return maybeUnwrapSymlink(n, opts, function (err, realN) { + return maybeUnwrapSymlink(unwrapSymlink, n, opts, function (err, realN) { if (err) { cb(err); } else { @@ -131,7 +137,7 @@ module.exports = function resolve(x, options, callback) { else loadAsDirectory(res, function (err, d, pkg) { if (err) cb(err); else if (d) { - maybeUnwrapSymlink(d, opts, function (err, realD) { + maybeUnwrapSymlink(unwrapSymlink, d, opts, function (err, realD) { if (err) { cb(err); } else { @@ -195,7 +201,7 @@ module.exports = function resolve(x, options, callback) { } if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null); - maybeUnwrapSymlink(dir, opts, function (unwrapErr, pkgdir) { + maybeUnwrapSymlink(unwrapSymlink, dir, opts, function (unwrapErr, pkgdir) { if (unwrapErr) return loadpkg(path.dirname(dir), cb); var pkgfile = path.join(pkgdir, 'package.json'); isFile(pkgfile, function (err, ex) { @@ -223,7 +229,7 @@ module.exports = function resolve(x, options, callback) { fpkg = opts.package; } - maybeUnwrapSymlink(x, opts, function (unwrapErr, pkgdir) { + maybeUnwrapSymlink(unwrapSymlink, x, opts, function (unwrapErr, pkgdir) { if (unwrapErr) return loadAsDirectory(path.dirname(x), fpkg, cb); var pkgfile = path.join(pkgdir, 'package.json'); isFile(pkgfile, function (err, ex) { diff --git a/lib/sync.js b/lib/sync.js index 11c2a559..a8f9b67f 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -25,19 +25,24 @@ var defaultIsDir = function isDirectory(dir) { return stat.isDirectory(); }; -var maybeUnwrapSymlink = function maybeUnwrapSymlink(x, opts) { - if (!opts || !opts.preserveSymlinks) { - try { - return fs.realpathSync(x); - } catch (realPathErr) { - if (realPathErr.code !== 'ENOENT') { - throw realPathErr; - } +var defaultUnwrapSymlink = function unwrapSymlink(x) { + try { + return fs.realpathSync(x); + } catch (realPathErr) { + if (realPathErr.code !== 'ENOENT') { + throw realPathErr; } } return x; }; +var maybeUnwrapSymlink = function maybeUnwrapSymlink(unwrap, x, opts) { + if (!opts || !opts.preserveSymlinks) { + return unwrap(x); + } + return x; +}; + var getPackageCandidates = function getPackageCandidates(x, start, opts) { var dirs = nodeModulesPaths(start, opts, x); for (var i = 0; i < dirs.length; i++) { @@ -55,6 +60,7 @@ module.exports = function resolveSync(x, options) { var isFile = opts.isFile || defaultIsFile; var isDirectory = opts.isDirectory || defaultIsDir; var readFileSync = opts.readFileSync || fs.readFileSync; + var unwrapSymlink = opts.unwrapSymlink || defaultUnwrapSymlink; var packageIterator = opts.packageIterator; var extensions = opts.extensions || ['.js']; @@ -64,7 +70,7 @@ module.exports = function resolveSync(x, options) { opts.paths = opts.paths || []; // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory - var absoluteStart = maybeUnwrapSymlink(path.resolve(basedir), opts); + var absoluteStart = maybeUnwrapSymlink(unwrapSymlink, path.resolve(basedir), opts); if (opts.basedir && !isDirectory(absoluteStart)) { var dirError = new TypeError('Provided basedir "' + opts.basedir + '" is not a directory' + (opts.preserveSymlinks ? '' : ', or a symlink to a directory')); @@ -76,12 +82,12 @@ module.exports = function resolveSync(x, options) { var res = path.resolve(absoluteStart, x); if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/'; var m = loadAsFileSync(res) || loadAsDirectorySync(res); - if (m) return maybeUnwrapSymlink(m, opts); + if (m) return maybeUnwrapSymlink(unwrapSymlink, m, opts); } else if (isCore(x)) { return x; } else { var n = loadNodeModulesSync(x, absoluteStart); - if (n) return maybeUnwrapSymlink(n, opts); + if (n) return maybeUnwrapSymlink(unwrapSymlink, n, opts); } var err = new Error("Cannot find module '" + x + "' from '" + parent + "'"); @@ -118,7 +124,7 @@ module.exports = function resolveSync(x, options) { } if ((/[/\\]node_modules[/\\]*$/).test(dir)) return; - var pkgfile = path.join(isDirectory(dir) ? maybeUnwrapSymlink(dir, opts) : dir, 'package.json'); + var pkgfile = path.join(isDirectory(dir) ? maybeUnwrapSymlink(unwrapSymlink, dir, opts) : dir, 'package.json'); if (!isFile(pkgfile)) { return loadpkg(path.dirname(dir)); @@ -138,7 +144,7 @@ module.exports = function resolveSync(x, options) { } function loadAsDirectorySync(x) { - var pkgfile = path.join(isDirectory(x) ? maybeUnwrapSymlink(x, opts) : x, '/package.json'); + var pkgfile = path.join(isDirectory(x) ? maybeUnwrapSymlink(unwrapSymlink, x, opts) : x, '/package.json'); if (isFile(pkgfile)) { try { var body = readFileSync(pkgfile, 'UTF8'); diff --git a/readme.markdown b/readme.markdown index cdea6778..1d61bbde 100644 --- a/readme.markdown +++ b/readme.markdown @@ -61,6 +61,8 @@ options are: * opts.isDirectory - function to asynchronously test whether a file exists and is a directory +* opts.unwrapSymlink - function to asynchronously unwrap a potential symlink + * `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field * pkg - package data * pkgfile - path to package.json @@ -117,6 +119,12 @@ default `opts` values: return cb(err); }); }, + unwrapSymlink: function unwrapSymlink(file, cb) { + fs.realpath(file, function (realPathErr, realPath) { + if (realPathErr && realPathErr.code !== 'ENOENT') cb(realPathErr); + else cb(null, realPathErr ? file : realPath); + }); + }, moduleDirectory: 'node_modules', preserveSymlinks: false } @@ -139,6 +147,8 @@ options are: * opts.isDirectory - function to synchronously test whether a file exists and is a directory +* opts.unwrapSymlink - function to synchronously unwrap a potential symlink + * `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field * pkg - package data * pkgfile - path to package.json @@ -195,6 +205,16 @@ default `opts` values: } return stat.isDirectory(); }, + defaultUnwrapSymlink: function unwrapSymlink(file) { + try { + return fs.realpathSync(file); + } catch (realPathErr) { + if (realPathErr.code !== 'ENOENT') { + throw realPathErr; + } + } + return file; + }, moduleDirectory: 'node_modules', preserveSymlinks: false } diff --git a/test/mock.js b/test/mock.js index d4f57a31..5d423dc8 100644 --- a/test/mock.js +++ b/test/mock.js @@ -22,6 +22,9 @@ test('mock', function (t) { }, readFile: function (file, cb) { cb(null, files[path.resolve(file)]); + }, + unwrapSymlink: function (file, cb) { + cb(null, file); } }; } @@ -70,6 +73,9 @@ test('mock from package', function (t) { 'package': { main: 'bar' }, readFile: function (file, cb) { cb(null, files[file]); + }, + unwrapSymlink: function (file, cb) { + cb(null, file); } }; } @@ -121,6 +127,9 @@ test('mock package', function (t) { }, readFile: function (file, cb) { cb(null, files[path.resolve(file)]); + }, + unwrapSymlink: function (file, cb) { + cb(null, file); } }; } @@ -157,6 +166,9 @@ test('mock package from package', function (t) { 'package': { main: 'bar' }, readFile: function (file, cb) { cb(null, files[path.resolve(file)]); + }, + unwrapSymlink: function (file, cb) { + cb(null, file); } }; } diff --git a/test/mock_sync.js b/test/mock_sync.js index 50812968..e99bf7ae 100644 --- a/test/mock_sync.js +++ b/test/mock_sync.js @@ -23,6 +23,9 @@ test('mock', function (t) { }, readFileSync: function (file) { return files[path.resolve(file)]; + }, + unwrapSymlink: function (file) { + return file; } }; } @@ -70,6 +73,9 @@ test('mock package', function (t) { }, readFileSync: function (file) { return files[path.resolve(file)]; + }, + unwrapSymlink: function (file) { + return file; } }; }