Skip to content

Commit b8d7def

Browse files
SimenBljharb
andcommitted
[New] add readPackage and readPackageSync
Co-authored-by: Simen Bekkhus <sbekkhus91@gmail.com> Co-authored-by: Jordan Harband <ljharb@gmail.com>
1 parent 4bece07 commit b8d7def

File tree

5 files changed

+221
-15
lines changed

5 files changed

+221
-15
lines changed

lib/async.js

+27-6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ var maybeRealpath = function maybeRealpath(realpath, x, opts, cb) {
4242
}
4343
};
4444

45+
var defaultReadPackage = function defaultReadPackage(readFile, pkgfile, cb) {
46+
readFile(pkgfile, function (readFileErr, body) {
47+
if (readFileErr) cb(readFileErr);
48+
else {
49+
try {
50+
var pkg = JSON.parse(body);
51+
cb(null, pkg);
52+
} catch (jsonErr) {
53+
cb(null);
54+
}
55+
}
56+
});
57+
};
58+
4559
var getPackageCandidates = function getPackageCandidates(x, start, opts) {
4660
var dirs = nodeModulesPaths(start, opts, x);
4761
for (var i = 0; i < dirs.length; i++) {
@@ -70,6 +84,13 @@ module.exports = function resolve(x, options, callback) {
7084
var isDirectory = opts.isDirectory || defaultIsDir;
7185
var readFile = opts.readFile || fs.readFile;
7286
var realpath = opts.realpath || defaultRealpath;
87+
var readPackage = opts.readPackage || defaultReadPackage;
88+
if (opts.readFile && opts.readPackage) {
89+
var conflictErr = new TypeError('`readFile` and `readPackage` are mutually exclusive.');
90+
return process.nextTick(function () {
91+
cb(conflictErr);
92+
});
93+
}
7394
var packageIterator = opts.packageIterator;
7495

7596
var extensions = opts.extensions || ['.js'];
@@ -211,9 +232,10 @@ module.exports = function resolve(x, options, callback) {
211232
// on err, ex is false
212233
if (!ex) return loadpkg(path.dirname(dir), cb);
213234

214-
readFile(pkgfile, function (err, body) {
235+
readPackage(readFile, pkgfile, function (err, pkgParam) {
215236
if (err) cb(err);
216-
try { var pkg = JSON.parse(body); } catch (jsonErr) {}
237+
238+
var pkg = pkgParam;
217239

218240
if (pkg && opts.packageFilter) {
219241
pkg = opts.packageFilter(pkg, pkgfile, dir);
@@ -239,11 +261,10 @@ module.exports = function resolve(x, options, callback) {
239261
if (err) return cb(err);
240262
if (!ex) return loadAsFile(path.join(x, 'index'), fpkg, cb);
241263

242-
readFile(pkgfile, function (err, body) {
264+
readPackage(readFile, pkgfile, function (err, pkgParam) {
243265
if (err) return cb(err);
244-
try {
245-
var pkg = JSON.parse(body);
246-
} catch (jsonErr) {}
266+
267+
var pkg = pkgParam;
247268

248269
if (pkg && opts.packageFilter) {
249270
pkg = opts.packageFilter(pkg, pkgfile, pkgdir);

lib/sync.js

+14-7
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ var maybeRealpathSync = function maybeRealpathSync(realpathSync, x, opts) {
4545
return x;
4646
};
4747

48+
var defaultReadPackageSync = function defaultReadPackageSync(readFileSync, pkgfile) {
49+
var body = readFileSync(pkgfile);
50+
try {
51+
var pkg = JSON.parse(body);
52+
return pkg;
53+
} catch (jsonErr) {}
54+
};
55+
4856
var getPackageCandidates = function getPackageCandidates(x, start, opts) {
4957
var dirs = nodeModulesPaths(start, opts, x);
5058
for (var i = 0; i < dirs.length; i++) {
@@ -63,6 +71,10 @@ module.exports = function resolveSync(x, options) {
6371
var isDirectory = opts.isDirectory || defaultIsDir;
6472
var readFileSync = opts.readFileSync || fs.readFileSync;
6573
var realpathSync = opts.realpathSync || defaultRealpathSync;
74+
var readPackageSync = opts.readPackageSync || defaultReadPackageSync;
75+
if (opts.readFileSync && opts.readPackageSync) {
76+
throw new TypeError('`readFileSync` and `readPackageSync` are mutually exclusive.');
77+
}
6678
var packageIterator = opts.packageIterator;
6779

6880
var extensions = opts.extensions || ['.js'];
@@ -133,11 +145,7 @@ module.exports = function resolveSync(x, options) {
133145
return loadpkg(path.dirname(dir));
134146
}
135147

136-
var body = readFileSync(pkgfile);
137-
138-
try {
139-
var pkg = JSON.parse(body);
140-
} catch (jsonErr) {}
148+
var pkg = readPackageSync(readFileSync, pkgfile);
141149

142150
if (pkg && opts.packageFilter) {
143151
pkg = opts.packageFilter(pkg, pkgfile, dir);
@@ -150,8 +158,7 @@ module.exports = function resolveSync(x, options) {
150158
var pkgfile = path.join(isDirectory(x) ? maybeRealpathSync(realpathSync, x, opts) : x, '/package.json');
151159
if (isFile(pkgfile)) {
152160
try {
153-
var body = readFileSync(pkgfile, 'UTF8');
154-
var pkg = JSON.parse(body);
161+
var pkg = readPackageSync(readFileSync, pkgfile);
155162
} catch (e) {}
156163

157164
if (pkg && opts.packageFilter) {

readme.markdown

+29-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ options are:
7171

7272
* opts.realpath - function to asynchronously resolve a potential symlink to its real path
7373

74-
* opts.realpath - function to asynchronously resolve a potential symlink to its real path
74+
* `opts.readPackage(readFile, pkgfile, cb)` - function to asynchronously read and parse a package.json file
75+
* readFile - the passed `opts.readFile` or `fs.readFile` if not specified
76+
* pkgfile - path to package.json
77+
* cb - callback
7578

7679
* `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field
7780
* pkg - package data
@@ -137,6 +140,19 @@ default `opts` values:
137140
else cb(null, realPathErr ? file : realPath);
138141
});
139142
},
143+
readPackage: function defaultReadPackage(readFile, pkgfile, cb) {
144+
readFile(pkgfile, function (readFileErr, body) {
145+
if (readFileErr) cb(readFileErr);
146+
else {
147+
try {
148+
var pkg = JSON.parse(body);
149+
cb(null, pkg);
150+
} catch (jsonErr) {
151+
cb(null);
152+
}
153+
}
154+
});
155+
},
140156
moduleDirectory: 'node_modules',
141157
preserveSymlinks: false
142158
}
@@ -155,14 +171,18 @@ options are:
155171

156172
* opts.includeCoreModules - set to `false` to exclude node core modules (e.g. `fs`) from the search
157173

158-
* opts.readFile - how to read files synchronously
174+
* opts.readFileSync - how to read files synchronously
159175

160176
* opts.isFile - function to synchronously test whether a file exists
161177

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

164180
* opts.realpathSync - function to synchronously resolve a potential symlink to its real path
165181

182+
* `opts.readPackageSync(readFileSync, pkgfile)` - function to synchronously read and parse a package.json file
183+
* readFileSync - the passed `opts.readFileSync` or `fs.readFileSync` if not specified
184+
* pkgfile - path to package.json
185+
166186
* `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field
167187
* pkg - package data
168188
* pkgfile - path to package.json
@@ -231,6 +251,13 @@ default `opts` values:
231251
}
232252
return file;
233253
},
254+
readPackageSync: function defaultReadPackageSync(readFileSync, pkgfile) {
255+
var body = readFileSync(pkgfile);
256+
try {
257+
var pkg = JSON.parse(body);
258+
return pkg;
259+
} catch (jsonErr) {}
260+
},
234261
moduleDirectory: 'node_modules',
235262
preserveSymlinks: false
236263
}

test/mock.js

+76
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,79 @@ test('symlinked', function (t) {
237237
t.equal(pkg, undefined);
238238
});
239239
});
240+
241+
test('readPackage', function (t) {
242+
t.plan(3);
243+
244+
var files = {};
245+
files[path.resolve('/foo/node_modules/bar/something-else.js')] = 'beep';
246+
files[path.resolve('/foo/node_modules/bar/package.json')] = JSON.stringify({
247+
main: './baz.js'
248+
});
249+
files[path.resolve('/foo/node_modules/bar/baz.js')] = 'boop';
250+
251+
var dirs = {};
252+
dirs[path.resolve('/foo')] = true;
253+
dirs[path.resolve('/foo/node_modules')] = true;
254+
255+
function opts(basedir) {
256+
return {
257+
basedir: path.resolve(basedir),
258+
isFile: function (file, cb) {
259+
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
260+
},
261+
isDirectory: function (dir, cb) {
262+
cb(null, !!dirs[path.resolve(dir)]);
263+
},
264+
'package': { main: 'bar' },
265+
readFile: function (file, cb) {
266+
cb(null, files[path.resolve(file)]);
267+
},
268+
realpath: function (file, cb) {
269+
cb(null, file);
270+
}
271+
};
272+
}
273+
274+
t.test('with readFile', function (st) {
275+
st.plan(3);
276+
277+
resolve('bar', opts('/foo'), function (err, res, pkg) {
278+
st.error(err);
279+
st.equal(res, path.resolve('/foo/node_modules/bar/baz.js'));
280+
st.equal(pkg && pkg.main, './baz.js');
281+
});
282+
});
283+
284+
var readPackage = function (readFile, file, cb) {
285+
var barPackage = path.join('bar', 'package.json');
286+
if (file.slice(-barPackage.length) === barPackage) {
287+
cb(null, { main: './something-else.js' });
288+
} else {
289+
cb(null, JSON.parse(files[path.resolve(file)]));
290+
}
291+
};
292+
293+
t.test('with readPackage', function (st) {
294+
st.plan(3);
295+
296+
var options = opts('/foo');
297+
delete options.readFile;
298+
options.readPackage = readPackage;
299+
resolve('bar', options, function (err, res, pkg) {
300+
st.error(err);
301+
st.equal(res, path.resolve('/foo/node_modules/bar/something-else.js'));
302+
st.equal(pkg && pkg.main, './something-else.js');
303+
});
304+
});
305+
306+
t.test('with readFile and readPackage', function (st) {
307+
st.plan(1);
308+
309+
var options = opts('/foo');
310+
options.readPackage = readPackage;
311+
resolve('bar', options, function (err) {
312+
st.throws(function () { throw err; }, TypeError, 'errors when both readFile and readPackage are provided');
313+
});
314+
});
315+
});

test/mock_sync.js

+75
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,78 @@ test('symlinked', function (t) {
140140
path.resolve('/foo/bar/symlinked/baz.js')
141141
);
142142
});
143+
144+
test('readPackageSync', function (t) {
145+
t.plan(3);
146+
147+
var files = {};
148+
files[path.resolve('/foo/node_modules/bar/something-else.js')] = 'beep';
149+
files[path.resolve('/foo/node_modules/bar/package.json')] = JSON.stringify({
150+
main: './baz.js'
151+
});
152+
files[path.resolve('/foo/node_modules/bar/baz.js')] = 'boop';
153+
154+
var dirs = {};
155+
dirs[path.resolve('/foo')] = true;
156+
dirs[path.resolve('/foo/node_modules')] = true;
157+
158+
function opts(basedir, useReadPackage) {
159+
return {
160+
basedir: path.resolve(basedir),
161+
isFile: function (file) {
162+
return Object.prototype.hasOwnProperty.call(files, path.resolve(file));
163+
},
164+
isDirectory: function (dir) {
165+
return !!dirs[path.resolve(dir)];
166+
},
167+
readFileSync: useReadPackage ? null : function (file) {
168+
return files[path.resolve(file)];
169+
},
170+
realpathSync: function (file) {
171+
return file;
172+
}
173+
};
174+
}
175+
t.test('with readFile', function (st) {
176+
st.plan(1);
177+
178+
st.equal(
179+
resolve.sync('bar', opts('/foo')),
180+
path.resolve('/foo/node_modules/bar/baz.js')
181+
);
182+
});
183+
184+
var readPackageSync = function (readFileSync, file) {
185+
if (file.indexOf(path.join('bar', 'package.json')) >= 0) {
186+
return { main: './something-else.js' };
187+
} else {
188+
return JSON.parse(files[path.resolve(file)]);
189+
}
190+
};
191+
192+
t.test('with readPackage', function (st) {
193+
st.plan(1);
194+
195+
var options = opts('/foo');
196+
delete options.readFileSync;
197+
options.readPackageSync = readPackageSync;
198+
199+
st.equal(
200+
resolve.sync('bar', options),
201+
path.resolve('/foo/node_modules/bar/something-else.js')
202+
);
203+
});
204+
205+
t.test('with readFile and readPackage', function (st) {
206+
st.plan(1);
207+
208+
var options = opts('/foo');
209+
options.readPackageSync = readPackageSync;
210+
st.throws(
211+
function () { resolve.sync('bar', options); },
212+
TypeError,
213+
'errors when both readFile and readPackage are provided'
214+
);
215+
});
216+
});
217+

0 commit comments

Comments
 (0)