Skip to content

Commit

Permalink
[New] add readPackage and readPackageSync (#236)
Browse files Browse the repository at this point in the history
Co-authored-by: Simen Bekkhus <[email protected]>
Co-authored-by: Jordan Harband <[email protected]>
  • Loading branch information
SimenB and ljharb committed Feb 11, 2021
1 parent 581a070 commit 77eba10
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 14 deletions.
33 changes: 27 additions & 6 deletions lib/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ var maybeRealpath = function maybeRealpath(realpath, x, opts, cb) {
}
};

var defaultReadPackage = function defaultReadPackage(readFile, pkgfile, cb) {
readFile(pkgfile, function (readFileErr, body) {
if (readFileErr) cb(readFileErr);
else {
try {
var pkg = JSON.parse(body);
cb(null, pkg);
} catch (jsonErr) {
cb(null);
}
}
});
};

var getPackageCandidates = function getPackageCandidates(x, start, opts) {
var dirs = nodeModulesPaths(start, opts, x);
for (var i = 0; i < dirs.length; i++) {
Expand Down Expand Up @@ -70,6 +84,13 @@ module.exports = function resolve(x, options, callback) {
var isDirectory = opts.isDirectory || defaultIsDir;
var readFile = opts.readFile || fs.readFile;
var realpath = opts.realpath || defaultRealpath;
var readPackage = opts.readPackage || defaultReadPackage;
if (opts.readFile && opts.readPackage) {
var conflictErr = new TypeError('`readFile` and `readPackage` are mutually exclusive.');
return process.nextTick(function () {
cb(conflictErr);
});
}
var packageIterator = opts.packageIterator;

var extensions = opts.extensions || ['.js'];
Expand Down Expand Up @@ -197,9 +218,10 @@ module.exports = function resolve(x, options, callback) {
// on err, ex is false
if (!ex) return loadpkg(path.dirname(dir), cb);

readFile(pkgfile, function (err, body) {
readPackage(readFile, pkgfile, function (err, pkgParam) {
if (err) cb(err);
try { var pkg = JSON.parse(body); } catch (jsonErr) {}

var pkg = pkgParam;

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

readFile(pkgfile, function (err, body) {
readPackage(readFile, pkgfile, function (err, pkgParam) {
if (err) return cb(err);
try {
var pkg = JSON.parse(body);
} catch (jsonErr) {}

var pkg = pkgParam;

if (pkg && opts.packageFilter) {
pkg = opts.packageFilter(pkg, pkgfile);
Expand Down
21 changes: 14 additions & 7 deletions lib/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ var maybeRealpathSync = function maybeRealpathSync(realpathSync, x, opts) {
return x;
};

var defaultReadPackageSync = function defaultReadPackageSync(readFileSync, pkgfile) {
var body = readFileSync(pkgfile);
try {
var pkg = JSON.parse(body);
return pkg;
} catch (jsonErr) {}
};

var getPackageCandidates = function getPackageCandidates(x, start, opts) {
var dirs = nodeModulesPaths(start, opts, x);
for (var i = 0; i < dirs.length; i++) {
Expand All @@ -63,6 +71,10 @@ module.exports = function resolveSync(x, options) {
var readFileSync = opts.readFileSync || fs.readFileSync;
var isDirectory = opts.isDirectory || defaultIsDir;
var realpathSync = opts.realpathSync || defaultRealpathSync;
var readPackageSync = opts.readPackageSync || defaultReadPackageSync;
if (opts.readFileSync && opts.readPackageSync) {
throw new TypeError('`readFileSync` and `readPackageSync` are mutually exclusive.');
}
var packageIterator = opts.packageIterator;

var extensions = opts.extensions || ['.js'];
Expand Down Expand Up @@ -127,11 +139,7 @@ module.exports = function resolveSync(x, options) {
return loadpkg(path.dirname(dir));
}

var body = readFileSync(pkgfile);

try {
var pkg = JSON.parse(body);
} catch (jsonErr) {}
var pkg = readPackageSync(readFileSync, pkgfile);

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

if (pkg && opts.packageFilter) {
Expand Down
31 changes: 30 additions & 1 deletion readme.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ options are:

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

* `opts.readPackage(readFile, pkgfile, cb)` - function to asynchronously read and parse a package.json file
* readFile - the passed `opts.readFile` or `fs.readFile` if not specified
* pkgfile - path to package.json
* cb - callback

* `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field
* pkg - package data
* pkgfile - path to package.json
Expand Down Expand Up @@ -137,6 +142,19 @@ default `opts` values:
else cb(null, realPathErr ? file : realPath);
});
},
readPackage: function defaultReadPackage(readFile, pkgfile, cb) {
readFile(pkgfile, function (readFileErr, body) {
if (readFileErr) cb(readFileErr);
else {
try {
var pkg = JSON.parse(body);
cb(null, pkg);
} catch (jsonErr) {
cb(null);
}
}
});
},
moduleDirectory: 'node_modules',
preserveSymlinks: true
}
Expand All @@ -155,14 +173,18 @@ options are:

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

* opts.readFile - how to read files synchronously
* opts.readFileSync - how to read files synchronously

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

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

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

* `opts.readPackageSync(readFileSync, pkgfile)` - function to synchronously read and parse a package.json file
* readFileSync - the passed `opts.readFileSync` or `fs.readFileSync` if not specified
* pkgfile - path to package.json

* `opts.packageFilter(pkg, dir)` - transform the parsed package.json contents before looking at the "main" field
* pkg - package data
* dir - directory for package.json (Note: the second argument will change to "pkgfile" in v2)
Expand Down Expand Up @@ -232,6 +254,13 @@ default `opts` values:
}
return file;
},
readPackageSync: function defaultReadPackageSync(readFileSync, pkgfile) {
var body = readFileSync(pkgfile);
try {
var pkg = JSON.parse(body);
return pkg;
} catch (jsonErr) {}
},
moduleDirectory: 'node_modules',
preserveSymlinks: true
}
Expand Down
76 changes: 76 additions & 0 deletions test/mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,79 @@ test('symlinked', function (t) {
t.equal(pkg, undefined);
});
});

test('readPackage', function (t) {
t.plan(3);

var files = {};
files[path.resolve('/foo/node_modules/bar/something-else.js')] = 'beep';
files[path.resolve('/foo/node_modules/bar/package.json')] = JSON.stringify({
main: './baz.js'
});
files[path.resolve('/foo/node_modules/bar/baz.js')] = 'boop';

var dirs = {};
dirs[path.resolve('/foo')] = true;
dirs[path.resolve('/foo/node_modules')] = true;

function opts(basedir) {
return {
basedir: path.resolve(basedir),
isFile: function (file, cb) {
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
},
isDirectory: function (dir, cb) {
cb(null, !!dirs[path.resolve(dir)]);
},
'package': { main: 'bar' },
readFile: function (file, cb) {
cb(null, files[path.resolve(file)]);
},
realpath: function (file, cb) {
cb(null, file);
}
};
}

t.test('with readFile', function (st) {
st.plan(3);

resolve('bar', opts('/foo'), function (err, res, pkg) {
st.error(err);
st.equal(res, path.resolve('/foo/node_modules/bar/baz.js'));
st.equal(pkg && pkg.main, './baz.js');
});
});

var readPackage = function (readFile, file, cb) {
var barPackage = path.join('bar', 'package.json');
if (file.slice(-barPackage.length) === barPackage) {
cb(null, { main: './something-else.js' });
} else {
cb(null, JSON.parse(files[path.resolve(file)]));
}
};

t.test('with readPackage', function (st) {
st.plan(3);

var options = opts('/foo');
delete options.readFile;
options.readPackage = readPackage;
resolve('bar', options, function (err, res, pkg) {
st.error(err);
st.equal(res, path.resolve('/foo/node_modules/bar/something-else.js'));
st.equal(pkg && pkg.main, './something-else.js');
});
});

t.test('with readFile and readPackage', function (st) {
st.plan(1);

var options = opts('/foo');
options.readPackage = readPackage;
resolve('bar', options, function (err) {
st.throws(function () { throw err; }, TypeError, 'errors when both readFile and readPackage are provided');
});
});
});
75 changes: 75 additions & 0 deletions test/mock_sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,78 @@ test('symlinked', function (t) {
path.resolve('/foo/bar/symlinked/baz.js')
);
});

test('readPackageSync', function (t) {
t.plan(3);

var files = {};
files[path.resolve('/foo/node_modules/bar/something-else.js')] = 'beep';
files[path.resolve('/foo/node_modules/bar/package.json')] = JSON.stringify({
main: './baz.js'
});
files[path.resolve('/foo/node_modules/bar/baz.js')] = 'boop';

var dirs = {};
dirs[path.resolve('/foo')] = true;
dirs[path.resolve('/foo/node_modules')] = true;

function opts(basedir, useReadPackage) {
return {
basedir: path.resolve(basedir),
isFile: function (file) {
return Object.prototype.hasOwnProperty.call(files, path.resolve(file));
},
isDirectory: function (dir) {
return !!dirs[path.resolve(dir)];
},
readFileSync: useReadPackage ? null : function (file) {
return files[path.resolve(file)];
},
realpathSync: function (file) {
return file;
}
};
}
t.test('with readFile', function (st) {
st.plan(1);

st.equal(
resolve.sync('bar', opts('/foo')),
path.resolve('/foo/node_modules/bar/baz.js')
);
});

var readPackageSync = function (readFileSync, file) {
if (file.indexOf(path.join('bar', 'package.json')) >= 0) {
return { main: './something-else.js' };
} else {
return JSON.parse(files[path.resolve(file)]);
}
};

t.test('with readPackage', function (st) {
st.plan(1);

var options = opts('/foo');
delete options.readFileSync;
options.readPackageSync = readPackageSync;

st.equal(
resolve.sync('bar', options),
path.resolve('/foo/node_modules/bar/something-else.js')
);
});

t.test('with readFile and readPackage', function (st) {
st.plan(1);

var options = opts('/foo');
options.readPackageSync = readPackageSync;
st.throws(
function () { resolve.sync('bar', options); },
TypeError,
'errors when both readFile and readPackage are provided'
);
});
});

0 comments on commit 77eba10

Please sign in to comment.