Skip to content

Commit be951d3

Browse files
SimenBljharb
authored andcommitted
[New] sync/async: add realpath/realpathSync options
1 parent 8238b8c commit be951d3

File tree

5 files changed

+190
-25
lines changed

5 files changed

+190
-25
lines changed

lib/async.js

+17-11
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) {
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) {
@@ -112,7 +118,7 @@ module.exports = function resolve(x, options, callback) {
112118
} else loadNodeModules(x, basedir, function (err, n, pkg) {
113119
if (err) cb(err);
114120
else if (n) {
115-
return maybeUnwrapSymlink(n, opts, function (err, realN) {
121+
return maybeRealpath(realpath, n, opts, function (err, realN) {
116122
if (err) {
117123
cb(err);
118124
} else {
@@ -133,7 +139,7 @@ module.exports = function resolve(x, options, callback) {
133139
else loadAsDirectory(res, function (err, d, pkg) {
134140
if (err) cb(err);
135141
else if (d) {
136-
maybeUnwrapSymlink(d, opts, function (err, realD) {
142+
maybeRealpath(realpath, d, opts, function (err, realD) {
137143
if (err) {
138144
cb(err);
139145
} else {
@@ -197,7 +203,7 @@ module.exports = function resolve(x, options, callback) {
197203
}
198204
if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null);
199205

200-
maybeUnwrapSymlink(dir, opts, function (unwrapErr, pkgdir) {
206+
maybeRealpath(realpath, dir, opts, function (unwrapErr, pkgdir) {
201207
if (unwrapErr) return loadpkg(path.dirname(dir), cb);
202208
var pkgfile = path.join(pkgdir, 'package.json');
203209
isFile(pkgfile, function (err, ex) {
@@ -225,7 +231,7 @@ module.exports = function resolve(x, options, callback) {
225231
fpkg = opts.package;
226232
}
227233

228-
maybeUnwrapSymlink(x, opts, function (unwrapErr, pkgdir) {
234+
maybeRealpath(realpath, x, opts, function (unwrapErr, pkgdir) {
229235
if (unwrapErr) return loadAsDirectory(path.dirname(x), fpkg, cb);
230236
var pkgfile = path.join(pkgdir, 'package.json');
231237
isFile(pkgfile, function (err, ex) {

lib/sync.js

+20-14
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) {
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) {
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 isDirectory = opts.isDirectory || defaultIsDir;
5964
var readFileSync = opts.readFileSync || fs.readFileSync;
65+
var realpathSync = opts.realpathSync || defaultRealpathSync;
6066
var packageIterator = opts.packageIterator;
6167

6268
var extensions = opts.extensions || ['.js'];
@@ -66,7 +72,7 @@ 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 (opts.basedir && !isDirectory(absoluteStart)) {
7278
var dirError = new TypeError('Provided basedir "' + opts.basedir + '" is not a directory' + (opts.preserveSymlinks ? '' : ', or a symlink to a directory'));
@@ -78,12 +84,12 @@ module.exports = function resolveSync(x, options) {
7884
var res = path.resolve(absoluteStart, x);
7985
if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/';
8086
var m = loadAsFileSync(res) || loadAsDirectorySync(res);
81-
if (m) return maybeUnwrapSymlink(m, opts);
87+
if (m) return maybeRealpathSync(realpathSync, m, opts);
8288
} else if (isCore(x)) {
8389
return x;
8490
} else {
8591
var n = loadNodeModulesSync(x, absoluteStart);
86-
if (n) return maybeUnwrapSymlink(n, opts);
92+
if (n) return maybeRealpathSync(realpathSync, n, opts);
8793
}
8894

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

123-
var pkgfile = path.join(isDirectory(dir) ? maybeUnwrapSymlink(dir, opts) : dir, 'package.json');
129+
var pkgfile = path.join(isDirectory(dir) ? maybeRealpathSync(realpathSync, dir, opts) : dir, 'package.json');
124130

125131
if (!isFile(pkgfile)) {
126132
return loadpkg(path.dirname(dir));
@@ -140,7 +146,7 @@ module.exports = function resolveSync(x, options) {
140146
}
141147

142148
function loadAsDirectorySync(x) {
143-
var pkgfile = path.join(isDirectory(x) ? maybeUnwrapSymlink(x, opts) : x, '/package.json');
149+
var pkgfile = path.join(isDirectory(x) ? maybeRealpathSync(realpathSync, x, opts) : x, '/package.json');
144150
if (isFile(pkgfile)) {
145151
try {
146152
var body = readFileSync(pkgfile, 'UTF8');

readme.markdown

+22
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ options are:
6161

6262
* opts.isDirectory - function to asynchronously test whether a file exists and is a directory
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
@@ -117,6 +119,13 @@ default `opts` values:
117119
return cb(err);
118120
});
119121
},
122+
realpath: function realpath(file, cb) {
123+
var realpath = typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath;
124+
realpath(file, function (realPathErr, realPath) {
125+
if (realPathErr && realPathErr.code !== 'ENOENT') cb(realPathErr);
126+
else cb(null, realPathErr ? file : realPath);
127+
});
128+
},
120129
moduleDirectory: 'node_modules',
121130
preserveSymlinks: false
122131
}
@@ -139,6 +148,8 @@ options are:
139148

140149
* opts.isDirectory - function to synchronously test whether a file exists and is a directory
141150

151+
* opts.realpathSync - function to synchronously resolve a potential symlink to its real path
152+
142153
* `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field
143154
* pkg - package data
144155
* pkgfile - path to package.json
@@ -195,6 +206,17 @@ default `opts` values:
195206
}
196207
return stat.isDirectory();
197208
},
209+
realpathSync: function realpathSync(file) {
210+
try {
211+
var realpath = typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync;
212+
return realpath(file);
213+
} catch (realPathErr) {
214+
if (realPathErr.code !== 'ENOENT') {
215+
throw realPathErr;
216+
}
217+
}
218+
return file;
219+
},
198220
moduleDirectory: 'node_modules',
199221
preserveSymlinks: false
200222
}

test/mock.js

+70
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)