From 52348f19a5b0df711e5cbde3146db493c0da4c94 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Wed, 8 Apr 2020 00:56:02 +0200 Subject: [PATCH 1/3] [New] `sync`/`async`: add `realpath`/`realpathSync` options (#218) --- lib/async.js | 28 +++++++++++-------- lib/sync.js | 34 +++++++++++++---------- readme.markdown | 22 +++++++++++++++ test/mock.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++ test/mock_sync.js | 61 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 25 deletions(-) diff --git a/lib/async.js b/lib/async.js index 1c596511..06aa4588 100644 --- a/lib/async.js +++ b/lib/async.js @@ -5,7 +5,7 @@ var nodeModulesPaths = require('./node-modules-paths.js'); var normalizeOptions = require('./normalize-options.js'); var isCore = require('./is-core'); -var realpath = fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath; +var realpathFS = fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath; var defaultIsFile = function isFile(file, cb) { fs.stat(file, function (err, stat) { @@ -27,12 +27,16 @@ var defaultIsDir = function isDirectory(dir, cb) { }); }; -var maybeUnwrapSymlink = function maybeUnwrapSymlink(x, opts, cb) { +var defaultRealpath = function realpath(x, cb) { + realpathFS(x, function (realpathErr, realPath) { + if (realpathErr && realpathErr.code !== 'ENOENT') cb(realpathErr); + else cb(null, realpathErr ? x : realPath); + }); +}; + +var maybeRealpath = function maybeRealpath(realpath, x, opts, cb) { if (opts && opts.preserveSymlinks === false) { - realpath(x, function (realPathErr, realPath) { - if (realPathErr && realPathErr.code !== 'ENOENT') cb(realPathErr); - else cb(null, realPathErr ? x : realPath); - }); + realpath(x, cb); } else { cb(null, x); } @@ -65,6 +69,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 realpath = opts.realpath || defaultRealpath; var packageIterator = opts.packageIterator; var extensions = opts.extensions || ['.js']; @@ -76,7 +81,8 @@ module.exports = function resolve(x, options, callback) { // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory var absoluteStart = path.resolve(basedir); - maybeUnwrapSymlink( + maybeRealpath( + realpath, absoluteStart, opts, function (err, realStart) { @@ -98,7 +104,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 maybeRealpath(realpath, n, opts, function (err, realN) { if (err) { cb(err); } else { @@ -119,7 +125,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) { + maybeRealpath(realpath, d, opts, function (err, realD) { if (err) { cb(err); } else { @@ -183,7 +189,7 @@ module.exports = function resolve(x, options, callback) { } if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null); - maybeUnwrapSymlink(dir, opts, function (unwrapErr, pkgdir) { + maybeRealpath(realpath, 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) { @@ -211,7 +217,7 @@ module.exports = function resolve(x, options, callback) { fpkg = opts.package; } - maybeUnwrapSymlink(x, opts, function (unwrapErr, pkgdir) { + maybeRealpath(realpath, x, opts, function (unwrapErr, pkgdir) { if (unwrapErr) return cb(unwrapErr); var pkgfile = path.join(pkgdir, 'package.json'); isFile(pkgfile, function (err, ex) { diff --git a/lib/sync.js b/lib/sync.js index c73e14a5..da74e19d 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -5,7 +5,7 @@ var caller = require('./caller.js'); var nodeModulesPaths = require('./node-modules-paths.js'); var normalizeOptions = require('./normalize-options.js'); -var realpath = fs.realpathSync && typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync; +var realpathFS = fs.realpathSync && typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync; var defaultIsFile = function isFile(file) { try { @@ -27,19 +27,24 @@ var defaultIsDir = function isDirectory(dir) { return stat.isDirectory(); }; -var maybeUnwrapSymlink = function maybeUnwrapSymlink(x, opts) { - if (opts && opts.preserveSymlinks === false) { - try { - return realpath(x); - } catch (realPathErr) { - if (realPathErr.code !== 'ENOENT') { - throw realPathErr; - } +var defaultRealpathSync = function realpathSync(x) { + try { + return realpathFS(x); + } catch (realpathErr) { + if (realpathErr.code !== 'ENOENT') { + throw realpathErr; } } return x; }; +var maybeRealpathSync = function maybeRealpathSync(realpathSync, x, opts) { + if (opts && opts.preserveSymlinks === false) { + return realpathSync(x); + } + return x; +}; + var getPackageCandidates = function getPackageCandidates(x, start, opts) { var dirs = nodeModulesPaths(start, opts, x); for (var i = 0; i < dirs.length; i++) { @@ -57,6 +62,7 @@ module.exports = function resolveSync(x, options) { var isFile = opts.isFile || defaultIsFile; var readFileSync = opts.readFileSync || fs.readFileSync; var isDirectory = opts.isDirectory || defaultIsDir; + var realpathSync = opts.realpathSync || defaultRealpathSync; var packageIterator = opts.packageIterator; var extensions = opts.extensions || ['.js']; @@ -66,18 +72,18 @@ 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 = maybeRealpathSync(realpathSync, path.resolve(basedir), opts); if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { 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 maybeRealpathSync(realpathSync, m, opts); } else if (isCore(x)) { return x; } else { var n = loadNodeModulesSync(x, absoluteStart); - if (n) return maybeUnwrapSymlink(n, opts); + if (n) return maybeRealpathSync(realpathSync, n, opts); } var err = new Error("Cannot find module '" + x + "' from '" + parent + "'"); @@ -114,7 +120,7 @@ module.exports = function resolveSync(x, options) { } if ((/[/\\]node_modules[/\\]*$/).test(dir)) return; - var pkgfile = path.join(maybeUnwrapSymlink(dir, opts), 'package.json'); + var pkgfile = path.join(maybeRealpathSync(realpathSync, dir, opts), 'package.json'); if (!isFile(pkgfile)) { return loadpkg(path.dirname(dir)); @@ -135,7 +141,7 @@ module.exports = function resolveSync(x, options) { } function loadAsDirectorySync(x) { - var pkgfile = path.join(maybeUnwrapSymlink(x, opts), '/package.json'); + var pkgfile = path.join(maybeRealpathSync(realpathSync, x, opts), '/package.json'); if (isFile(pkgfile)) { try { var body = readFileSync(pkgfile, 'UTF8'); diff --git a/readme.markdown b/readme.markdown index 60c20d98..5e1aea33 100644 --- a/readme.markdown +++ b/readme.markdown @@ -61,6 +61,8 @@ options are: * opts.isDirectory - function to asynchronously test whether a directory exists +* opts.realpath - function to asynchronously resolve a potential symlink to its real path + * `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 @@ -119,6 +121,13 @@ default `opts` values: return cb(err); }); }, + realpath: function realpath(file, cb) { + var realpath = typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath; + realpath(file, function (realPathErr, realPath) { + if (realPathErr && realPathErr.code !== 'ENOENT') cb(realPathErr); + else cb(null, realPathErr ? file : realPath); + }); + }, moduleDirectory: 'node_modules', preserveSymlinks: true } @@ -141,6 +150,8 @@ options are: * opts.isDirectory - function to synchronously test whether a directory exists +* opts.realpathSync - function to synchronously resolve a potential symlink to its real path + * `opts.packageFilter(pkg, dir)` - transform the parsed package.json contents before looking at the "main" field * pkg - package data * dir - directory for package.json (Note: the second argument will change to "pkgfile" in v2) @@ -198,6 +209,17 @@ default `opts` values: } return stat.isDirectory(); }, + realpathSync: function realpathSync(file) { + try { + var realpath = typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync; + return realpath(file); + } catch (realPathErr) { + if (realPathErr.code !== 'ENOENT') { + throw realPathErr; + } + } + return file; + }, moduleDirectory: 'node_modules', preserveSymlinks: true } diff --git a/test/mock.js b/test/mock.js index d4f57a31..b9f17fe2 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)]); + }, + realpath: 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]); + }, + realpath: 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)]); + }, + realpath: 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)]); + }, + realpath: function (file, cb) { + cb(null, file); } }; } @@ -167,3 +179,61 @@ test('mock package from package', function (t) { t.equal(pkg && pkg.main, './baz.js'); }); }); + +test('symlinked', function (t) { + t.plan(4); + + var files = {}; + files[path.resolve('/foo/bar/baz.js')] = 'beep'; + files[path.resolve('/foo/bar/symlinked/baz.js')] = 'beep'; + + var dirs = {}; + dirs[path.resolve('/foo/bar')] = true; + dirs[path.resolve('/foo/bar/symlinked')] = true; + + function opts(basedir) { + return { + preserveSymlinks: false, + basedir: path.resolve(basedir), + isFile: function (file, cb) { + cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file))); + }, + isDirectory: function (dir, cb) { + cb(null, !!dirs[path.resolve(dir)]); + }, + readFile: function (file, cb) { + cb(null, files[path.resolve(file)]); + }, + realpath: function (file, cb) { + var resolved = path.resolve(file); + + if (resolved.indexOf('symlinked') >= 0) { + cb(null, resolved); + return; + } + + var ext = path.extname(resolved); + + if (ext) { + var dir = path.dirname(resolved); + var base = path.basename(resolved); + cb(null, path.join(dir, 'symlinked', base)); + } else { + cb(null, path.join(resolved, 'symlinked')); + } + } + }; + } + + resolve('./baz', opts('/foo/bar'), function (err, res, pkg) { + if (err) return t.fail(err); + t.equal(res, path.resolve('/foo/bar/symlinked/baz.js')); + t.equal(pkg, undefined); + }); + + resolve('./baz.js', opts('/foo/bar'), function (err, res, pkg) { + if (err) return t.fail(err); + t.equal(res, path.resolve('/foo/bar/symlinked/baz.js')); + t.equal(pkg, undefined); + }); +}); diff --git a/test/mock_sync.js b/test/mock_sync.js index af06ae11..fcf81144 100644 --- a/test/mock_sync.js +++ b/test/mock_sync.js @@ -22,6 +22,9 @@ test('mock', function (t) { }, readFileSync: function (file) { return files[path.resolve(file)]; + }, + realpathSync: function (file) { + return file; } }; } @@ -69,6 +72,9 @@ test('mock package', function (t) { }, readFileSync: function (file) { return files[path.resolve(file)]; + }, + realpathSync: function (file) { + return file; } }; } @@ -78,3 +84,58 @@ test('mock package', function (t) { path.resolve('/foo/node_modules/bar/baz.js') ); }); + +test('symlinked', function (t) { + t.plan(2); + + var files = {}; + files[path.resolve('/foo/bar/baz.js')] = 'beep'; + files[path.resolve('/foo/bar/symlinked/baz.js')] = 'beep'; + + var dirs = {}; + dirs[path.resolve('/foo/bar')] = true; + dirs[path.resolve('/foo/bar/symlinked')] = true; + + function opts(basedir) { + return { + preserveSymlinks: false, + basedir: path.resolve(basedir), + isFile: function (file) { + return Object.prototype.hasOwnProperty.call(files, path.resolve(file)); + }, + isDirectory: function (dir) { + return !!dirs[path.resolve(dir)]; + }, + readFileSync: function (file) { + return files[path.resolve(file)]; + }, + realpathSync: function (file) { + var resolved = path.resolve(file); + + if (resolved.indexOf('symlinked') >= 0) { + return resolved; + } + + var ext = path.extname(resolved); + + if (ext) { + var dir = path.dirname(resolved); + var base = path.basename(resolved); + return path.join(dir, 'symlinked', base); + } else { + return path.join(resolved, 'symlinked'); + } + } + }; + } + + t.equal( + resolve.sync('./baz', opts('/foo/bar')), + path.resolve('/foo/bar/symlinked/baz.js') + ); + + t.equal( + resolve.sync('./baz.js', opts('/foo/bar')), + path.resolve('/foo/bar/symlinked/baz.js') + ); +}); From 4d9e8e8a70bb8297e7c80983fc1ea9830c5ac3f9 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 22 Apr 2020 15:22:58 -0700 Subject: [PATCH 2/3] [Dev Deps] update `tape` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6abe9ccc..f0826d8b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "object-keys": "^1.1.1", "safe-publish-latest": "^1.1.4", "tap": "0.4.13", - "tape": "^5.0.0-next.4" + "tape": "^5.0.0-next.5" }, "license": "MIT", "author": { From 3a76ef8d2cc232fc4d4246e0748506258a104484 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 22 Apr 2020 15:27:21 -0700 Subject: [PATCH 3/3] v1.17.0 - [New] `sync`/`async`: add `realpath`/`realpathSync` options (#218) - [Dev Deps] update `tape` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0826d8b..b6d3bec1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "resolve", "description": "resolve like require.resolve() on behalf of files asynchronously and synchronously", - "version": "1.16.1", + "version": "1.17.0", "repository": { "type": "git", "url": "git://github.com/browserify/resolve.git"