blob: 29285079451b15067c16d2c569bde701559a4f87 [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001var fs = require('fs');
2var path = require('path');
3var caller = require('./caller');
4var nodeModulesPaths = require('./node-modules-paths');
5var normalizeOptions = require('./normalize-options');
6var isCore = require('is-core-module');
7
8var realpathFS = fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath;
9
10var 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
20var 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
30var 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
37var 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
45var 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
53module.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};