diff --git a/lib/async.js b/lib/async.js index ed7cc7c1..762c0038 100644 --- a/lib/async.js +++ b/lib/async.js @@ -42,6 +42,20 @@ var maybeRealpath = function maybeRealpath(realpath, x, opts, cb) { } }; +var defaultReadPkg = function defaultReadPkg(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++) { @@ -70,6 +84,7 @@ module.exports = function resolve(x, options, callback) { var isDirectory = opts.isDirectory || defaultIsDir; var readFile = opts.readFile || fs.readFile; var realpath = opts.realpath || defaultRealpath; + var readPkg = opts.readPkg || defaultReadPkg; var packageIterator = opts.packageIterator; var extensions = opts.extensions || ['.js']; @@ -211,9 +226,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) { + readPkg(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, dir); @@ -239,11 +255,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) { + readPkg(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); diff --git a/lib/sync.js b/lib/sync.js index b7c5c380..b85551fe 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -45,6 +45,14 @@ var maybeRealpathSync = function maybeRealpathSync(realpathSync, x, opts) { return x; }; +var defaultReadPkgSync = function defaultReadPkgSync(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++) { @@ -63,6 +71,7 @@ module.exports = function resolveSync(x, options) { var isDirectory = opts.isDirectory || defaultIsDir; var readFileSync = opts.readFileSync || fs.readFileSync; var realpathSync = opts.realpathSync || defaultRealpathSync; + var readPkgSync = opts.readPkgSync || defaultReadPkgSync; var packageIterator = opts.packageIterator; var extensions = opts.extensions || ['.js']; @@ -133,10 +142,8 @@ module.exports = function resolveSync(x, options) { return loadpkg(path.dirname(dir)); } - var body = readFileSync(pkgfile); - try { - var pkg = JSON.parse(body); + var pkg = readPkgSync(readFileSync, pkgfile); } catch (jsonErr) {} if (pkg && opts.packageFilter) { @@ -150,8 +157,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 = readPkgSync(readFileSync, pkgfile); } catch (e) {} if (pkg && opts.packageFilter) { diff --git a/readme.markdown b/readme.markdown index 2503eb5e..1e00372f 100644 --- a/readme.markdown +++ b/readme.markdown @@ -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.readPkg(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 @@ -137,6 +140,19 @@ default `opts` values: else cb(null, realPathErr ? file : realPath); }); }, + readPkg: function defaultReadPkg(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 } @@ -155,7 +171,7 @@ 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 @@ -163,6 +179,10 @@ options are: * opts.realpathSync - function to synchronously resolve a potential symlink to its real path +* `opts.readPkgSync(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 @@ -231,6 +251,13 @@ default `opts` values: } return file; }, + readPkgSync: function readPkgSync(readFileSync, pkgfile) { + var body = readFileSync(pkgfile); + try { + var pkg = JSON.parse(body); + return pkg; + } catch (jsonErr) {} + }, moduleDirectory: 'node_modules', preserveSymlinks: false } diff --git a/test/mock.js b/test/mock.js index b9f17fe2..b3e17001 100644 --- a/test/mock.js +++ b/test/mock.js @@ -237,3 +237,49 @@ test('symlinked', function (t) { t.equal(pkg, undefined); }); }); + +test('readPkg', function (t) { + t.plan(2); + + 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' + }); + + 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); + }, + readPkg: function (readFile, file, cb) { + if (file.indexOf('bar/package.json') >= 0) { + cb(null, { main: './something-else.js' }); + } else { + cb(null, JSON.parse(files[path.resolve(file)])); + } + } + }; + } + + resolve('bar', opts('/foo'), function (err, res, pkg) { + if (err) return t.fail(err); + t.equal(res, path.resolve('/foo/node_modules/bar/something-else.js')); + t.equal(pkg && pkg.main, './something-else.js'); + }); +}); diff --git a/test/mock_sync.js b/test/mock_sync.js index ef2bc6d3..46a98883 100644 --- a/test/mock_sync.js +++ b/test/mock_sync.js @@ -140,3 +140,48 @@ test('symlinked', function (t) { path.resolve('/foo/bar/symlinked/baz.js') ); }); + +test('readPkgSync', function (t) { + t.plan(1); + + 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' + }); + + 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) { + return Object.prototype.hasOwnProperty.call(files, path.resolve(file)); + }, + isDirectory: function (dir) { + return !!dirs[path.resolve(dir)]; + }, + readFileSync: function (file) { + return files[path.resolve(file)]; + }, + realpathSync: function (file) { + return file; + }, + readPkgSync: function (readFileSync, file) { + if (file.indexOf('bar/package.json') >= 0) { + return { main: './something-else.js' }; + } else { + return JSON.parse(files[path.resolve(file)]); + } + } + }; + } + + t.equal( + resolve.sync('bar', opts('/foo')), + path.resolve('/foo/node_modules/bar/something-else.js') + ); +}); +