Skip to content

Commit 77eba10

Browse files
SimenBljharb
andcommitted
[New] add readPackage and readPackageSync (#236)
Co-authored-by: Simen Bekkhus <[email protected]> Co-authored-by: Jordan Harband <[email protected]>
1 parent 581a070 commit 77eba10

File tree

5 files changed

+222
-14
lines changed

5 files changed

+222
-14
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'];
@@ -197,9 +218,10 @@ module.exports = function resolve(x, options, callback) {
197218
// on err, ex is false
198219
if (!ex) return loadpkg(path.dirname(dir), cb);
199220

200-
readFile(pkgfile, function (err, body) {
221+
readPackage(readFile, pkgfile, function (err, pkgParam) {
201222
if (err) cb(err);
202-
try { var pkg = JSON.parse(body); } catch (jsonErr) {}
223+
224+
var pkg = pkgParam;
203225

204226
if (pkg && opts.packageFilter) {
205227
pkg = opts.packageFilter(pkg, pkgfile);
@@ -225,11 +247,10 @@ module.exports = function resolve(x, options, callback) {
225247
if (err) return cb(err);
226248
if (!ex) return loadAsFile(path.join(x, 'index'), fpkg, cb);
227249

228-
readFile(pkgfile, function (err, body) {
250+
readPackage(readFile, pkgfile, function (err, pkgParam) {
229251
if (err) return cb(err);
230-
try {
231-
var pkg = JSON.parse(body);
232-
} catch (jsonErr) {}
252+
253+
var pkg = pkgParam;
233254

234255
if (pkg && opts.packageFilter) {
235256
pkg = opts.packageFilter(pkg, pkgfile);

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 readFileSync = opts.readFileSync || fs.readFileSync;
6472
var isDirectory = opts.isDirectory || defaultIsDir;
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'];
@@ -127,11 +139,7 @@ module.exports = function resolveSync(x, options) {
127139
return loadpkg(path.dirname(dir));
128140
}
129141

130-
var body = readFileSync(pkgfile);
131-
132-
try {
133-
var pkg = JSON.parse(body);
134-
} catch (jsonErr) {}
142+
var pkg = readPackageSync(readFileSync, pkgfile);
135143

136144
if (pkg && opts.packageFilter) {
137145
// v2 will pass pkgfile
@@ -145,8 +153,7 @@ module.exports = function resolveSync(x, options) {
145153
var pkgfile = path.join(maybeRealpathSync(realpathSync, x, opts), '/package.json');
146154
if (isFile(pkgfile)) {
147155
try {
148-
var body = readFileSync(pkgfile, 'UTF8');
149-
var pkg = JSON.parse(body);
156+
var pkg = readPackageSync(readFileSync, pkgfile);
150157
} catch (e) {}
151158

152159
if (pkg && opts.packageFilter) {

readme.markdown

+30-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ options are:
7171

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

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
78+
7479
* `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field
7580
* pkg - package data
7681
* pkgfile - path to package.json
@@ -137,6 +142,19 @@ default `opts` values:
137142
else cb(null, realPathErr ? file : realPath);
138143
});
139144
},
145+
readPackage: function defaultReadPackage(readFile, pkgfile, cb) {
146+
readFile(pkgfile, function (readFileErr, body) {
147+
if (readFileErr) cb(readFileErr);
148+
else {
149+
try {
150+
var pkg = JSON.parse(body);
151+
cb(null, pkg);
152+
} catch (jsonErr) {
153+
cb(null);
154+
}
155+
}
156+
});
157+
},
140158
moduleDirectory: 'node_modules',
141159
preserveSymlinks: true
142160
}
@@ -155,14 +173,18 @@ options are:
155173

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

158-
* opts.readFile - how to read files synchronously
176+
* opts.readFileSync - how to read files synchronously
159177

160178
* opts.isFile - function to synchronously test whether a file exists
161179

162180
* opts.isDirectory - function to synchronously test whether a directory exists
163181

164182
* opts.realpathSync - function to synchronously resolve a potential symlink to its real path
165183

184+
* `opts.readPackageSync(readFileSync, pkgfile)` - function to synchronously read and parse a package.json file
185+
* readFileSync - the passed `opts.readFileSync` or `fs.readFileSync` if not specified
186+
* pkgfile - path to package.json
187+
166188
* `opts.packageFilter(pkg, dir)` - transform the parsed package.json contents before looking at the "main" field
167189
* pkg - package data
168190
* dir - directory for package.json (Note: the second argument will change to "pkgfile" in v2)
@@ -232,6 +254,13 @@ default `opts` values:
232254
}
233255
return file;
234256
},
257+
readPackageSync: function defaultReadPackageSync(readFileSync, pkgfile) {
258+
var body = readFileSync(pkgfile);
259+
try {
260+
var pkg = JSON.parse(body);
261+
return pkg;
262+
} catch (jsonErr) {}
263+
},
235264
moduleDirectory: 'node_modules',
236265
preserveSymlinks: true
237266
}

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

0 commit comments

Comments
 (0)