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