diff --git a/lib/node-modules-paths.js b/lib/node-modules-paths.js index 7ed66f3c..a4bde6a9 100644 --- a/lib/node-modules-paths.js +++ b/lib/node-modules-paths.js @@ -1,4 +1,5 @@ var path = require('path'); +var fs = require('fs'); var parse = path.parse || require('path-parse'); module.exports = function nodeModulesPaths(start, opts) { @@ -10,6 +11,16 @@ module.exports = function nodeModulesPaths(start, opts) { // resolving against the process' current working directory var absoluteStart = path.resolve(start); + if (opts && opts.preserveSymlinks === false) { + try { + absoluteStart = fs.realpathSync(absoluteStart); + } catch (err) { + if (err.code !== 'ENOENT') { + throw err; + } + } + } + var prefix = '/'; if (/^([A-Za-z]:)/.test(absoluteStart)) { prefix = ''; diff --git a/readme.markdown b/readme.markdown index db0d69f8..1bb67d43 100644 --- a/readme.markdown +++ b/readme.markdown @@ -73,6 +73,11 @@ node_modules recursive walk (probably don't use this) * opts.moduleDirectory - directory (or directories) in which to recursively look for modules. default: `"node_modules"` +* opts.preserveSymlinks - if true, doesn't resolve `basedir` to real path before resolving. +This is the way Node resolves dependencies when executed with the [--preserve-symlinks](https://nodejs.org/api/all.html#cli_preserve_symlinks) flag. +**Note:** this property is currently `true` by default but it will be changed to +`false` in the next major version because *Node's resolution algorithm does not preserve symlinks by default*. + default `opts` values: ``` javascript @@ -88,7 +93,8 @@ default `opts` values: else cb(null, stat.isFile()) }); }, - moduleDirectory: 'node_modules' + moduleDirectory: 'node_modules', + preserveSymlinks: true } ``` @@ -115,6 +121,11 @@ node_modules recursive walk (probably don't use this) * opts.moduleDirectory - directory (or directories) in which to recursively look for modules. default: `"node_modules"` +* opts.preserveSymlinks - if true, doesn't resolve `basedir` to real path before resolving. +This is the way Node resolves dependencies when executed with the [--preserve-symlinks](https://nodejs.org/api/all.html#cli_preserve_symlinks) flag. +**Note:** this property is currently `true` by default but it will be changed to +`false` in the next major version because *Node's resolution algorithm does not preserve symlinks by default*. + default `opts` values: ``` javascript @@ -127,7 +138,8 @@ default `opts` values: try { return fs.statSync(file).isFile() } catch (e) { return false } }, - moduleDirectory: 'node_modules' + moduleDirectory: 'node_modules', + preserveSymlinks: true } ```` diff --git a/test/resolver/symlinked/.gitignore b/test/resolver/symlinked/.gitignore new file mode 100644 index 00000000..f23f8925 --- /dev/null +++ b/test/resolver/symlinked/.gitignore @@ -0,0 +1 @@ +symlink diff --git a/test/resolver/symlinked/_/.gitignore b/test/resolver/symlinked/_/.gitignore new file mode 100644 index 00000000..cf4bab9d --- /dev/null +++ b/test/resolver/symlinked/_/.gitignore @@ -0,0 +1 @@ +!node_modules diff --git a/test/resolver/symlinked/_/node_modules/foo.js b/test/resolver/symlinked/_/node_modules/foo.js new file mode 100644 index 00000000..e69de29b diff --git a/test/resolver/symlinked/_/symlink_target/.gitkeep b/test/resolver/symlinked/_/symlink_target/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/symlinks.js b/test/symlinks.js new file mode 100644 index 00000000..544a0237 --- /dev/null +++ b/test/symlinks.js @@ -0,0 +1,54 @@ +var path = require('path'); +var fs = require('fs'); +var test = require('tape'); +var resolve = require('../'); + +var symlinkDir = path.join(__dirname, 'resolver', 'symlinked', 'symlink'); +try { + fs.unlinkSync(symlinkDir); +} catch (err) {} +try { + fs.symlinkSync('./_/symlink_target', symlinkDir, 'dir'); +} catch (err) { + // if fails then it is probably on Windows and lets try to create a junction + fs.symlinkSync(path.join(__dirname, 'resolver', 'symlinked', '_', 'symlink_target') + '\\', symlinkDir, 'junction'); +} + +test('symlink', function (t) { + t.plan(1); + + resolve('foo', { basedir: symlinkDir, preserveSymlinks: false }, function (err, res, pkg) { + if (err) t.fail(err); + t.equal(res, path.join(__dirname, 'resolver', 'symlinked', '_', 'node_modules', 'foo.js')); + }); +}); + +test('sync symlink when preserveSymlinks = true', function (t) { + t.plan(4); + + resolve('foo', { basedir: symlinkDir }, function (err, res, pkg) { + t.ok(err, 'there is an error'); + t.notOk(res, 'no result'); + + t.equal(err && err.code, 'MODULE_NOT_FOUND', 'error code matches require.resolve'); + t.equal( + err && err.message, + 'Cannot find module \'foo\' from \'' + symlinkDir + '\'', + 'can not find nonexistent module' + ); + }); +}); + +test('sync symlink', function (t) { + var start = new Date(); + t.equal(resolve.sync('foo', { basedir: symlinkDir, preserveSymlinks: false }), path.join(__dirname, 'resolver', 'symlinked', '_', 'node_modules', 'foo.js')); + t.ok(new Date() - start < 50, 'resolve.sync timedout'); + t.end(); +}); + +test('sync symlink when preserveSymlinks = true', function (t) { + t.throws(function () { + resolve.sync('foo', { basedir: symlinkDir }); + }, /Cannot find module 'foo'/); + t.end(); +});