Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] add readPackage and readPackageSync #236

Merged
merged 1 commit into from
Feb 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -211,9 +232,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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it wasn't obvious - this is done to satisfy an eslint rule about parameter reassignment (which packageFilter does below)


if (pkg && opts.packageFilter) {
pkg = opts.packageFilter(pkg, pkgfile, dir);
Expand All @@ -239,11 +261,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, pkgdir);
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 isDirectory = opts.isDirectory || defaultIsDir;
var readFileSync = opts.readFileSync || fs.readFileSync;
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 @@ -133,11 +145,7 @@ module.exports = function resolveSync(x, options) {
return loadpkg(path.dirname(dir));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimenB Separately from this PR, it would also be nice if there was a way to short-circuit this recursive check for package.json file in every directory.

Copy link
Contributor Author

@SimenB SimenB Feb 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, like the findClosestPackageFile I mentioned in jestjs/jest#11034 (comment)? All FS calls can be cached, tho, so in theory it's just a bunch of Map.get or something, so shouldn't be too bad

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to a PR that enables that, for sure.

}

var body = readFileSync(pkgfile);

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

if (pkg && opts.packageFilter) {
pkg = opts.packageFilter(pkg, pkgfile, dir);
Expand All @@ -150,8 +158,7 @@ module.exports = function resolveSync(x, options) {
var pkgfile = path.join(isDirectory(x) ? maybeRealpathSync(realpathSync, x, opts) : x, '/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: 29 additions & 2 deletions readme.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ options are:

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

* 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
Expand Down Expand Up @@ -137,6 +140,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: false
}
Expand All @@ -155,14 +171,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 file exists and is a directory

* 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, 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 @@ -231,6 +251,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: false
}
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 @@ -140,3 +140,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'
);
});
});