1 var fs = require('fs');
2 var path = require('path');
3 var caller = require('./caller.js');
4 var nodeModulesPaths = require('./node-modules-paths.js');
5 var normalizeOptions = require('./normalize-options.js');
6 var isCore = require('./is-core');
8 var realpathFS = fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath;
10 var defaultIsFile = function isFile(file, cb) {
11 fs.stat(file, function (err, stat) {
13 return cb(null, stat.isFile() || stat.isFIFO());
15 if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false);
20 var defaultIsDir = function isDirectory(dir, cb) {
21 fs.stat(dir, function (err, stat) {
23 return cb(null, stat.isDirectory());
25 if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false);
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);
37 var maybeRealpath = function maybeRealpath(realpath, x, opts, cb) {
38 if (opts && opts.preserveSymlinks === false) {
45 var getPackageCandidates = function getPackageCandidates(x, start, opts) {
46 var dirs = nodeModulesPaths(start, opts, x);
47 for (var i = 0; i < dirs.length; i++) {
48 dirs[i] = path.join(dirs[i], x);
53 module.exports = function resolve(x, options, callback) {
56 if (typeof options === 'function') {
60 if (typeof x !== 'string') {
61 var err = new TypeError('Path must be a string.');
62 return process.nextTick(function () {
67 opts = normalizeOptions(x, opts);
69 var isFile = opts.isFile || defaultIsFile;
70 var isDirectory = opts.isDirectory || defaultIsDir;
71 var readFile = opts.readFile || fs.readFile;
72 var realpath = opts.realpath || defaultRealpath;
73 var packageIterator = opts.packageIterator;
75 var extensions = opts.extensions || ['.js'];
76 var basedir = opts.basedir || path.dirname(caller());
77 var parent = opts.filename || basedir;
79 opts.paths = opts.paths || [];
81 // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory
82 var absoluteStart = path.resolve(basedir);
88 function (err, realStart) {
95 function init(basedir) {
96 if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) {
97 res = path.resolve(basedir, x);
98 if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/';
99 if ((/\/$/).test(x) && res === basedir) {
100 loadAsDirectory(res, opts.package, onfile);
101 } else loadAsFile(res, opts.package, onfile);
102 } else if (isCore(x)) {
104 } else loadNodeModules(x, basedir, function (err, n, pkg) {
107 return maybeRealpath(realpath, n, opts, function (err, realN) {
111 cb(null, realN, pkg);
115 var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'");
116 moduleError.code = 'MODULE_NOT_FOUND';
122 function onfile(err, m, pkg) {
124 else if (m) cb(null, m, pkg);
125 else loadAsDirectory(res, function (err, d, pkg) {
128 maybeRealpath(realpath, d, opts, function (err, realD) {
132 cb(null, realD, pkg);
136 var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'");
137 moduleError.code = 'MODULE_NOT_FOUND';
143 function loadAsFile(x, thePackage, callback) {
144 var loadAsFilePackage = thePackage;
146 if (typeof loadAsFilePackage === 'function') {
147 cb = loadAsFilePackage;
148 loadAsFilePackage = undefined;
151 var exts = [''].concat(extensions);
152 load(exts, x, loadAsFilePackage);
154 function load(exts, x, loadPackage) {
155 if (exts.length === 0) return cb(null, undefined, loadPackage);
156 var file = x + exts[0];
158 var pkg = loadPackage;
159 if (pkg) onpkg(null, pkg);
160 else loadpkg(path.dirname(file), onpkg);
162 function onpkg(err, pkg_, dir) {
164 if (err) return cb(err);
165 if (dir && pkg && opts.pathFilter) {
166 var rfile = path.relative(dir, file);
167 var rel = rfile.slice(0, rfile.length - exts[0].length);
168 var r = opts.pathFilter(pkg, x, rel);
170 [''].concat(extensions.slice()),
171 path.resolve(dir, r),
177 function onex(err, ex) {
178 if (err) return cb(err);
179 if (ex) return cb(null, file, pkg);
180 load(exts.slice(1), x, pkg);
185 function loadpkg(dir, cb) {
186 if (dir === '' || dir === '/') return cb(null);
187 if (process.platform === 'win32' && (/^\w:[/\\]*$/).test(dir)) {
190 if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null);
192 maybeRealpath(realpath, dir, opts, function (unwrapErr, pkgdir) {
193 if (unwrapErr) return loadpkg(path.dirname(dir), cb);
194 var pkgfile = path.join(pkgdir, 'package.json');
195 isFile(pkgfile, function (err, ex) {
196 // on err, ex is false
197 if (!ex) return loadpkg(path.dirname(dir), cb);
199 readFile(pkgfile, function (err, body) {
201 try { var pkg = JSON.parse(body); } catch (jsonErr) {}
203 if (pkg && opts.packageFilter) {
204 pkg = opts.packageFilter(pkg, pkgfile);
212 function loadAsDirectory(x, loadAsDirectoryPackage, callback) {
214 var fpkg = loadAsDirectoryPackage;
215 if (typeof fpkg === 'function') {
220 maybeRealpath(realpath, x, opts, function (unwrapErr, pkgdir) {
221 if (unwrapErr) return cb(unwrapErr);
222 var pkgfile = path.join(pkgdir, 'package.json');
223 isFile(pkgfile, function (err, ex) {
224 if (err) return cb(err);
225 if (!ex) return loadAsFile(path.join(x, 'index'), fpkg, cb);
227 readFile(pkgfile, function (err, body) {
228 if (err) return cb(err);
230 var pkg = JSON.parse(body);
233 if (pkg && opts.packageFilter) {
234 pkg = opts.packageFilter(pkg, pkgfile);
237 if (pkg && pkg.main) {
238 if (typeof pkg.main !== 'string') {
239 var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string');
240 mainError.code = 'INVALID_PACKAGE_MAIN';
241 return cb(mainError);
243 if (pkg.main === '.' || pkg.main === './') {
246 loadAsFile(path.resolve(x, pkg.main), pkg, function (err, m, pkg) {
247 if (err) return cb(err);
248 if (m) return cb(null, m, pkg);
249 if (!pkg) return loadAsFile(path.join(x, 'index'), pkg, cb);
251 var dir = path.resolve(x, pkg.main);
252 loadAsDirectory(dir, pkg, function (err, n, pkg) {
253 if (err) return cb(err);
254 if (n) return cb(null, n, pkg);
255 loadAsFile(path.join(x, 'index'), pkg, cb);
261 loadAsFile(path.join(x, '/index'), pkg, cb);
267 function processDirs(cb, dirs) {
268 if (dirs.length === 0) return cb(null, undefined);
271 isDirectory(path.dirname(dir), isdir);
273 function isdir(err, isdir) {
274 if (err) return cb(err);
275 if (!isdir) return processDirs(cb, dirs.slice(1));
276 loadAsFile(dir, opts.package, onfile);
279 function onfile(err, m, pkg) {
280 if (err) return cb(err);
281 if (m) return cb(null, m, pkg);
282 loadAsDirectory(dir, opts.package, ondir);
285 function ondir(err, n, pkg) {
286 if (err) return cb(err);
287 if (n) return cb(null, n, pkg);
288 processDirs(cb, dirs.slice(1));
291 function loadNodeModules(x, start, cb) {
292 var thunk = function () { return getPackageCandidates(x, start, opts); };
295 packageIterator ? packageIterator(x, start, thunk, opts) : thunk()