Skip to content

Commit 52348f1

Browse files
SimenBljharb
authored andcommitted
[New] sync/async: add realpath/realpathSync options (#218)
1 parent ac3eb2f commit 52348f1

File tree

5 files changed

+190
-25
lines changed

5 files changed

+190
-25
lines changed

lib/async.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ var nodeModulesPaths = require('./node-modules-paths.js');
55
var normalizeOptions = require('./normalize-options.js');
66
var isCore = require('./is-core');
77

8-
var realpath = fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath;
8+
var realpathFS = fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath;
99

1010
var defaultIsFile = function isFile(file, cb) {
1111
fs.stat(file, function (err, stat) {
@@ -27,12 +27,16 @@ var defaultIsDir = function isDirectory(dir, cb) {
2727
});
2828
};
2929

30-
var maybeUnwrapSymlink = function maybeUnwrapSymlink(x, opts, cb) {
30+
var defaultRealpath = function realpath(x, cb) {
31+
realpathFS(x, function (realpathErr, realPath) {
32+
if (realpathErr && realpathErr.code !== 'ENOENT') cb(realpathErr);
33+
else cb(null, realpathErr ? x : realPath);
34+
});
35+
};
36+
37+
var maybeRealpath = function maybeRealpath(realpath, x, opts, cb) {
3138
if (opts && opts.preserveSymlinks === false) {
32-
realpath(x, function (realPathErr, realPath) {
33-
if (realPathErr && realPathErr.code !== 'ENOENT') cb(realPathErr);
34-
else cb(null, realPathErr ? x : realPath);
35-
});
39+
realpath(x, cb);
3640
} else {
3741
cb(null, x);
3842
}
@@ -65,6 +69,7 @@ module.exports = function resolve(x, options, callback) {
6569
var isFile = opts.isFile || defaultIsFile;
6670
var isDirectory = opts.isDirectory || defaultIsDir;
6771
var readFile = opts.readFile || fs.readFile;
72+
var realpath = opts.realpath || defaultRealpath;
6873
var packageIterator = opts.packageIterator;
6974

7075
var extensions = opts.extensions || ['.js'];
@@ -76,7 +81,8 @@ module.exports = function resolve(x, options, callback) {
7681
// ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory
7782
var absoluteStart = path.resolve(basedir);
7883

79-
maybeUnwrapSymlink(
84+
maybeRealpath(
85+
realpath,
8086
absoluteStart,
8187
opts,
8288
function (err, realStart) {
@@ -98,7 +104,7 @@ module.exports = function resolve(x, options, callback) {
98104
} else loadNodeModules(x, basedir, function (err, n, pkg) {
99105
if (err) cb(err);
100106
else if (n) {
101-
return maybeUnwrapSymlink(n, opts, function (err, realN) {
107+
return maybeRealpath(realpath, n, opts, function (err, realN) {
102108
if (err) {
103109
cb(err);
104110
} else {
@@ -119,7 +125,7 @@ module.exports = function resolve(x, options, callback) {
119125
else loadAsDirectory(res, function (err, d, pkg) {
120126
if (err) cb(err);
121127
else if (d) {
122-
maybeUnwrapSymlink(d, opts, function (err, realD) {
128+
maybeRealpath(realpath, d, opts, function (err, realD) {
123129
if (err) {
124130
cb(err);
125131
} else {
@@ -183,7 +189,7 @@ module.exports = function resolve(x, options, callback) {
183189
}
184190
if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null);
185191

186-
maybeUnwrapSymlink(dir, opts, function (unwrapErr, pkgdir) {
192+
maybeRealpath(realpath, dir, opts, function (unwrapErr, pkgdir) {
187193
if (unwrapErr) return loadpkg(path.dirname(dir), cb);
188194
var pkgfile = path.join(pkgdir, 'package.json');
189195
isFile(pkgfile, function (err, ex) {
@@ -211,7 +217,7 @@ module.exports = function resolve(x, options, callback) {
211217
fpkg = opts.package;
212218
}
213219

214-
maybeUnwrapSymlink(x, opts, function (unwrapErr, pkgdir) {
220+
maybeRealpath(realpath, x, opts, function (unwrapErr, pkgdir) {
215221
if (unwrapErr) return cb(unwrapErr);
216222
var pkgfile = path.join(pkgdir, 'package.json');
217223
isFile(pkgfile, function (err, ex) {

lib/sync.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ var caller = require('./caller.js');
55
var nodeModulesPaths = require('./node-modules-paths.js');
66
var normalizeOptions = require('./normalize-options.js');
77

8-
var realpath = fs.realpathSync && typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync;
8+
var realpathFS = fs.realpathSync && typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync;
99

1010
var defaultIsFile = function isFile(file) {
1111
try {
@@ -27,19 +27,24 @@ var defaultIsDir = function isDirectory(dir) {
2727
return stat.isDirectory();
2828
};
2929

30-
var maybeUnwrapSymlink = function maybeUnwrapSymlink(x, opts) {
31-
if (opts && opts.preserveSymlinks === false) {
32-
try {
33-
return realpath(x);
34-
} catch (realPathErr) {
35-
if (realPathErr.code !== 'ENOENT') {
36-
throw realPathErr;
37-
}
30+
var defaultRealpathSync = function realpathSync(x) {
31+
try {
32+
return realpathFS(x);
33+
} catch (realpathErr) {
34+
if (realpathErr.code !== 'ENOENT') {
35+
throw realpathErr;
3836
}
3937
}
4038
return x;
4139
};
4240

41+
var maybeRealpathSync = function maybeRealpathSync(realpathSync, x, opts) {
42+
if (opts && opts.preserveSymlinks === false) {
43+
return realpathSync(x);
44+
}
45+
return x;
46+
};
47+
4348
var getPackageCandidates = function getPackageCandidates(x, start, opts) {
4449
var dirs = nodeModulesPaths(start, opts, x);
4550
for (var i = 0; i < dirs.length; i++) {
@@ -57,6 +62,7 @@ module.exports = function resolveSync(x, options) {
5762
var isFile = opts.isFile || defaultIsFile;
5863
var readFileSync = opts.readFileSync || fs.readFileSync;
5964
var isDirectory = opts.isDirectory || defaultIsDir;
65+
var realpathSync = opts.realpathSync || defaultRealpathSync;
6066
var packageIterator = opts.packageIterator;
6167

6268
var extensions = opts.extensions || ['.js'];
@@ -66,18 +72,18 @@ module.exports = function resolveSync(x, options) {
6672
opts.paths = opts.paths || [];
6773

6874
// ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory
69-
var absoluteStart = maybeUnwrapSymlink(path.resolve(basedir), opts);
75+
var absoluteStart = maybeRealpathSync(realpathSync, path.resolve(basedir), opts);
7076

7177
if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) {
7278
var res = path.resolve(absoluteStart, x);
7379
if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/';
7480
var m = loadAsFileSync(res) || loadAsDirectorySync(res);
75-
if (m) return maybeUnwrapSymlink(m, opts);
81+
if (m) return maybeRealpathSync(realpathSync, m, opts);
7682
} else if (isCore(x)) {
7783
return x;
7884
} else {
7985
var n = loadNodeModulesSync(x, absoluteStart);
80-
if (n) return maybeUnwrapSymlink(n, opts);
86+
if (n) return maybeRealpathSync(realpathSync, n, opts);
8187
}
8288

8389
var err = new Error("Cannot find module '" + x + "' from '" + parent + "'");
@@ -114,7 +120,7 @@ module.exports = function resolveSync(x, options) {
114120
}
115121
if ((/[/\\]node_modules[/\\]*$/).test(dir)) return;
116122

117-
var pkgfile = path.join(maybeUnwrapSymlink(dir, opts), 'package.json');
123+
var pkgfile = path.join(maybeRealpathSync(realpathSync, dir, opts), 'package.json');
118124

119125
if (!isFile(pkgfile)) {
120126
return loadpkg(path.dirname(dir));
@@ -135,7 +141,7 @@ module.exports = function resolveSync(x, options) {
135141
}
136142

137143
function loadAsDirectorySync(x) {
138-
var pkgfile = path.join(maybeUnwrapSymlink(x, opts), '/package.json');
144+
var pkgfile = path.join(maybeRealpathSync(realpathSync, x, opts), '/package.json');
139145
if (isFile(pkgfile)) {
140146
try {
141147
var body = readFileSync(pkgfile, 'UTF8');

readme.markdown

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ options are:
6161

6262
* opts.isDirectory - function to asynchronously test whether a directory exists
6363

64+
* opts.realpath - function to asynchronously resolve a potential symlink to its real path
65+
6466
* `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field
6567
* pkg - package data
6668
* pkgfile - path to package.json
@@ -119,6 +121,13 @@ default `opts` values:
119121
return cb(err);
120122
});
121123
},
124+
realpath: function realpath(file, cb) {
125+
var realpath = typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath;
126+
realpath(file, function (realPathErr, realPath) {
127+
if (realPathErr && realPathErr.code !== 'ENOENT') cb(realPathErr);
128+
else cb(null, realPathErr ? file : realPath);
129+
});
130+
},
122131
moduleDirectory: 'node_modules',
123132
preserveSymlinks: true
124133
}
@@ -141,6 +150,8 @@ options are:
141150

142151
* opts.isDirectory - function to synchronously test whether a directory exists
143152

153+
* opts.realpathSync - function to synchronously resolve a potential symlink to its real path
154+
144155
* `opts.packageFilter(pkg, dir)` - transform the parsed package.json contents before looking at the "main" field
145156
* pkg - package data
146157
* dir - directory for package.json (Note: the second argument will change to "pkgfile" in v2)
@@ -198,6 +209,17 @@ default `opts` values:
198209
}
199210
return stat.isDirectory();
200211
},
212+
realpathSync: function realpathSync(file) {
213+
try {
214+
var realpath = typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync;
215+
return realpath(file);
216+
} catch (realPathErr) {
217+
if (realPathErr.code !== 'ENOENT') {
218+
throw realPathErr;
219+
}
220+
}
221+
return file;
222+
},
201223
moduleDirectory: 'node_modules',
202224
preserveSymlinks: true
203225
}

test/mock.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ test('mock', function (t) {
2222
},
2323
readFile: function (file, cb) {
2424
cb(null, files[path.resolve(file)]);
25+
},
26+
realpath: function (file, cb) {
27+
cb(null, file);
2528
}
2629
};
2730
}
@@ -70,6 +73,9 @@ test('mock from package', function (t) {
7073
'package': { main: 'bar' },
7174
readFile: function (file, cb) {
7275
cb(null, files[file]);
76+
},
77+
realpath: function (file, cb) {
78+
cb(null, file);
7379
}
7480
};
7581
}
@@ -121,6 +127,9 @@ test('mock package', function (t) {
121127
},
122128
readFile: function (file, cb) {
123129
cb(null, files[path.resolve(file)]);
130+
},
131+
realpath: function (file, cb) {
132+
cb(null, file);
124133
}
125134
};
126135
}
@@ -157,6 +166,9 @@ test('mock package from package', function (t) {
157166
'package': { main: 'bar' },
158167
readFile: function (file, cb) {
159168
cb(null, files[path.resolve(file)]);
169+
},
170+
realpath: function (file, cb) {
171+
cb(null, file);
160172
}
161173
};
162174
}
@@ -167,3 +179,61 @@ test('mock package from package', function (t) {
167179
t.equal(pkg && pkg.main, './baz.js');
168180
});
169181
});
182+
183+
test('symlinked', function (t) {
184+
t.plan(4);
185+
186+
var files = {};
187+
files[path.resolve('/foo/bar/baz.js')] = 'beep';
188+
files[path.resolve('/foo/bar/symlinked/baz.js')] = 'beep';
189+
190+
var dirs = {};
191+
dirs[path.resolve('/foo/bar')] = true;
192+
dirs[path.resolve('/foo/bar/symlinked')] = true;
193+
194+
function opts(basedir) {
195+
return {
196+
preserveSymlinks: false,
197+
basedir: path.resolve(basedir),
198+
isFile: function (file, cb) {
199+
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
200+
},
201+
isDirectory: function (dir, cb) {
202+
cb(null, !!dirs[path.resolve(dir)]);
203+
},
204+
readFile: function (file, cb) {
205+
cb(null, files[path.resolve(file)]);
206+
},
207+
realpath: function (file, cb) {
208+
var resolved = path.resolve(file);
209+
210+
if (resolved.indexOf('symlinked') >= 0) {
211+
cb(null, resolved);
212+
return;
213+
}
214+
215+
var ext = path.extname(resolved);
216+
217+
if (ext) {
218+
var dir = path.dirname(resolved);
219+
var base = path.basename(resolved);
220+
cb(null, path.join(dir, 'symlinked', base));
221+
} else {
222+
cb(null, path.join(resolved, 'symlinked'));
223+
}
224+
}
225+
};
226+
}
227+
228+
resolve('./baz', opts('/foo/bar'), function (err, res, pkg) {
229+
if (err) return t.fail(err);
230+
t.equal(res, path.resolve('/foo/bar/symlinked/baz.js'));
231+
t.equal(pkg, undefined);
232+
});
233+
234+
resolve('./baz.js', opts('/foo/bar'), function (err, res, pkg) {
235+
if (err) return t.fail(err);
236+
t.equal(res, path.resolve('/foo/bar/symlinked/baz.js'));
237+
t.equal(pkg, undefined);
238+
});
239+
});

0 commit comments

Comments
 (0)