1var fs = require('fs'); 2var getHomedir = require('./homedir'); 3var path = require('path'); 4var caller = require('./caller'); 5var nodeModulesPaths = require('./node-modules-paths'); 6var normalizeOptions = require('./normalize-options'); 7var isCore = require('is-core-module'); 8 9var realpathFS = process.platform !== 'win32' && fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath; 10 11var homedir = getHomedir(); 12var defaultPaths = function () { 13 return [ 14 path.join(homedir, '.node_modules'), 15 path.join(homedir, '.node_libraries') 16 ]; 17}; 18 19var defaultIsFile = function isFile(file, cb) { 20 fs.stat(file, function (err, stat) { 21 if (!err) { 22 return cb(null, stat.isFile() || stat.isFIFO()); 23 } 24 if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false); 25 return cb(err); 26 }); 27}; 28 29var defaultIsDir = function isDirectory(dir, cb) { 30 fs.stat(dir, function (err, stat) { 31 if (!err) { 32 return cb(null, stat.isDirectory()); 33 } 34 if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false); 35 return cb(err); 36 }); 37}; 38 39var defaultRealpath = function realpath(x, cb) { 40 realpathFS(x, function (realpathErr, realPath) { 41 if (realpathErr && realpathErr.code !== 'ENOENT') cb(realpathErr); 42 else cb(null, realpathErr ? x : realPath); 43 }); 44}; 45 46var maybeRealpath = function maybeRealpath(realpath, x, opts, cb) { 47 if (opts && opts.preserveSymlinks === false) { 48 realpath(x, cb); 49 } else { 50 cb(null, x); 51 } 52}; 53 54var defaultReadPackage = function defaultReadPackage(readFile, pkgfile, cb) { 55 readFile(pkgfile, function (readFileErr, body) { 56 if (readFileErr) cb(readFileErr); 57 else { 58 try { 59 var pkg = JSON.parse(body); 60 cb(null, pkg); 61 } catch (jsonErr) { 62 cb(null); 63 } 64 } 65 }); 66}; 67 68var getPackageCandidates = function getPackageCandidates(x, start, opts) { 69 var dirs = nodeModulesPaths(start, opts, x); 70 for (var i = 0; i < dirs.length; i++) { 71 dirs[i] = path.join(dirs[i], x); 72 } 73 return dirs; 74}; 75 76module.exports = function resolve(x, options, callback) { 77 var cb = callback; 78 var opts = options; 79 if (typeof options === 'function') { 80 cb = opts; 81 opts = {}; 82 } 83 if (typeof x !== 'string') { 84 var err = new TypeError('Path must be a string.'); 85 return process.nextTick(function () { 86 cb(err); 87 }); 88 } 89 90 opts = normalizeOptions(x, opts); 91 92 var isFile = opts.isFile || defaultIsFile; 93 var isDirectory = opts.isDirectory || defaultIsDir; 94 var readFile = opts.readFile || fs.readFile; 95 var realpath = opts.realpath || defaultRealpath; 96 var readPackage = opts.readPackage || defaultReadPackage; 97 if (opts.readFile && opts.readPackage) { 98 var conflictErr = new TypeError('`readFile` and `readPackage` are mutually exclusive.'); 99 return process.nextTick(function () { 100 cb(conflictErr); 101 }); 102 } 103 var packageIterator = opts.packageIterator; 104 105 var extensions = opts.extensions || ['.js']; 106 var includeCoreModules = opts.includeCoreModules !== false; 107 var basedir = opts.basedir || path.dirname(caller()); 108 var parent = opts.filename || basedir; 109 110 opts.paths = opts.paths || defaultPaths(); 111 112 // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory 113 var absoluteStart = path.resolve(basedir); 114 115 maybeRealpath( 116 realpath, 117 absoluteStart, 118 opts, 119 function (err, realStart) { 120 if (err) cb(err); 121 else init(realStart); 122 } 123 ); 124 125 var res; 126 function init(basedir) { 127 if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { 128 res = path.resolve(basedir, x); 129 if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/'; 130 if ((/\/$/).test(x) && res === basedir) { 131 loadAsDirectory(res, opts.package, onfile); 132 } else loadAsFile(res, opts.package, onfile); 133 } else if (includeCoreModules && isCore(x)) { 134 return cb(null, x); 135 } else loadNodeModules(x, basedir, function (err, n, pkg) { 136 if (err) cb(err); 137 else if (n) { 138 return maybeRealpath(realpath, n, opts, function (err, realN) { 139 if (err) { 140 cb(err); 141 } else { 142 cb(null, realN, pkg); 143 } 144 }); 145 } else { 146 var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); 147 moduleError.code = 'MODULE_NOT_FOUND'; 148 cb(moduleError); 149 } 150 }); 151 } 152 153 function onfile(err, m, pkg) { 154 if (err) cb(err); 155 else if (m) cb(null, m, pkg); 156 else loadAsDirectory(res, function (err, d, pkg) { 157 if (err) cb(err); 158 else if (d) { 159 maybeRealpath(realpath, d, opts, function (err, realD) { 160 if (err) { 161 cb(err); 162 } else { 163 cb(null, realD, pkg); 164 } 165 }); 166 } else { 167 var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); 168 moduleError.code = 'MODULE_NOT_FOUND'; 169 cb(moduleError); 170 } 171 }); 172 } 173 174 function loadAsFile(x, thePackage, callback) { 175 var loadAsFilePackage = thePackage; 176 var cb = callback; 177 if (typeof loadAsFilePackage === 'function') { 178 cb = loadAsFilePackage; 179 loadAsFilePackage = undefined; 180 } 181 182 var exts = [''].concat(extensions); 183 load(exts, x, loadAsFilePackage); 184 185 function load(exts, x, loadPackage) { 186 if (exts.length === 0) return cb(null, undefined, loadPackage); 187 var file = x + exts[0]; 188 189 var pkg = loadPackage; 190 if (pkg) onpkg(null, pkg); 191 else loadpkg(path.dirname(file), onpkg); 192 193 function onpkg(err, pkg_, dir) { 194 pkg = pkg_; 195 if (err) return cb(err); 196 if (dir && pkg && opts.pathFilter) { 197 var rfile = path.relative(dir, file); 198 var rel = rfile.slice(0, rfile.length - exts[0].length); 199 var r = opts.pathFilter(pkg, x, rel); 200 if (r) return load( 201 [''].concat(extensions.slice()), 202 path.resolve(dir, r), 203 pkg 204 ); 205 } 206 isFile(file, onex); 207 } 208 function onex(err, ex) { 209 if (err) return cb(err); 210 if (ex) return cb(null, file, pkg); 211 load(exts.slice(1), x, pkg); 212 } 213 } 214 } 215 216 function loadpkg(dir, cb) { 217 if (dir === '' || dir === '/') return cb(null); 218 if (process.platform === 'win32' && (/^\w:[/\\]*$/).test(dir)) { 219 return cb(null); 220 } 221 if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null); 222 223 maybeRealpath(realpath, dir, opts, function (unwrapErr, pkgdir) { 224 if (unwrapErr) return loadpkg(path.dirname(dir), cb); 225 var pkgfile = path.join(pkgdir, 'package.json'); 226 isFile(pkgfile, function (err, ex) { 227 // on err, ex is false 228 if (!ex) return loadpkg(path.dirname(dir), cb); 229 230 readPackage(readFile, pkgfile, function (err, pkgParam) { 231 if (err) cb(err); 232 233 var pkg = pkgParam; 234 235 if (pkg && opts.packageFilter) { 236 pkg = opts.packageFilter(pkg, pkgfile); 237 } 238 cb(null, pkg, dir); 239 }); 240 }); 241 }); 242 } 243 244 function loadAsDirectory(x, loadAsDirectoryPackage, callback) { 245 var cb = callback; 246 var fpkg = loadAsDirectoryPackage; 247 if (typeof fpkg === 'function') { 248 cb = fpkg; 249 fpkg = opts.package; 250 } 251 252 maybeRealpath(realpath, x, opts, function (unwrapErr, pkgdir) { 253 if (unwrapErr) return cb(unwrapErr); 254 var pkgfile = path.join(pkgdir, 'package.json'); 255 isFile(pkgfile, function (err, ex) { 256 if (err) return cb(err); 257 if (!ex) return loadAsFile(path.join(x, 'index'), fpkg, cb); 258 259 readPackage(readFile, pkgfile, function (err, pkgParam) { 260 if (err) return cb(err); 261 262 var pkg = pkgParam; 263 264 if (pkg && opts.packageFilter) { 265 pkg = opts.packageFilter(pkg, pkgfile); 266 } 267 268 if (pkg && pkg.main) { 269 if (typeof pkg.main !== 'string') { 270 var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); 271 mainError.code = 'INVALID_PACKAGE_MAIN'; 272 return cb(mainError); 273 } 274 if (pkg.main === '.' || pkg.main === './') { 275 pkg.main = 'index'; 276 } 277 loadAsFile(path.resolve(x, pkg.main), pkg, function (err, m, pkg) { 278 if (err) return cb(err); 279 if (m) return cb(null, m, pkg); 280 if (!pkg) return loadAsFile(path.join(x, 'index'), pkg, cb); 281 282 var dir = path.resolve(x, pkg.main); 283 loadAsDirectory(dir, pkg, function (err, n, pkg) { 284 if (err) return cb(err); 285 if (n) return cb(null, n, pkg); 286 loadAsFile(path.join(x, 'index'), pkg, cb); 287 }); 288 }); 289 return; 290 } 291 292 loadAsFile(path.join(x, '/index'), pkg, cb); 293 }); 294 }); 295 }); 296 } 297 298 function processDirs(cb, dirs) { 299 if (dirs.length === 0) return cb(null, undefined); 300 var dir = dirs[0]; 301 302 isDirectory(path.dirname(dir), isdir); 303 304 function isdir(err, isdir) { 305 if (err) return cb(err); 306 if (!isdir) return processDirs(cb, dirs.slice(1)); 307 loadAsFile(dir, opts.package, onfile); 308 } 309 310 function onfile(err, m, pkg) { 311 if (err) return cb(err); 312 if (m) return cb(null, m, pkg); 313 loadAsDirectory(dir, opts.package, ondir); 314 } 315 316 function ondir(err, n, pkg) { 317 if (err) return cb(err); 318 if (n) return cb(null, n, pkg); 319 processDirs(cb, dirs.slice(1)); 320 } 321 } 322 function loadNodeModules(x, start, cb) { 323 var thunk = function () { return getPackageCandidates(x, start, opts); }; 324 processDirs( 325 cb, 326 packageIterator ? packageIterator(x, start, thunk, opts) : thunk() 327 ); 328 } 329}; 330