2012-03-19 16:59:57 +01:00
|
|
|
// A copy of npm/lib/utils/read-installed.js
|
|
|
|
// that is hacked to not cache everything :)
|
|
|
|
|
|
|
|
// Walk through the file-system "database" of installed
|
|
|
|
// packages, and create a data object related to the
|
|
|
|
// installed versions of each package.
|
|
|
|
|
|
|
|
/*
|
|
|
|
This will traverse through all node_modules folders,
|
|
|
|
resolving the dependencies object to the object corresponding to
|
|
|
|
the package that meets that dep, or just the version/range if
|
|
|
|
unmet.
|
|
|
|
|
|
|
|
Assuming that you had this folder structure:
|
|
|
|
|
|
|
|
/path/to
|
|
|
|
+-- package.json { name = "root" }
|
|
|
|
`-- node_modules
|
|
|
|
+-- foo {bar, baz, asdf}
|
|
|
|
| +-- node_modules
|
|
|
|
| +-- bar { baz }
|
|
|
|
| `-- baz
|
|
|
|
`-- asdf
|
|
|
|
|
|
|
|
where "foo" depends on bar, baz, and asdf, bar depends on baz,
|
|
|
|
and bar and baz are bundled with foo, whereas "asdf" is at
|
|
|
|
the higher level (sibling to foo), you'd get this object structure:
|
|
|
|
|
|
|
|
{ <package.json data>
|
|
|
|
, path: "/path/to"
|
|
|
|
, parent: null
|
|
|
|
, dependencies:
|
|
|
|
{ foo :
|
|
|
|
{ version: "1.2.3"
|
|
|
|
, path: "/path/to/node_modules/foo"
|
|
|
|
, parent: <Circular: root>
|
|
|
|
, dependencies:
|
|
|
|
{ bar:
|
|
|
|
{ parent: <Circular: foo>
|
|
|
|
, path: "/path/to/node_modules/foo/node_modules/bar"
|
|
|
|
, version: "2.3.4"
|
|
|
|
, dependencies: { baz: <Circular: foo.dependencies.baz> }
|
|
|
|
}
|
|
|
|
, baz: { ... }
|
|
|
|
, asdf: <Circular: asdf>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
, asdf: { ... }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Unmet deps are left as strings.
|
|
|
|
Extraneous deps are marked with extraneous:true
|
|
|
|
deps that don't meet a requirement are marked with invalid:true
|
|
|
|
|
|
|
|
to READ(packagefolder, parentobj, name, reqver)
|
|
|
|
obj = read package.json
|
|
|
|
installed = ./node_modules/*
|
|
|
|
if parentobj is null, and no package.json
|
|
|
|
obj = {dependencies:{<installed>:"*"}}
|
|
|
|
deps = Object.keys(obj.dependencies)
|
|
|
|
obj.path = packagefolder
|
|
|
|
obj.parent = parentobj
|
|
|
|
if name, && obj.name !== name, obj.invalid = true
|
|
|
|
if reqver, && obj.version !satisfies reqver, obj.invalid = true
|
|
|
|
if !reqver && parentobj, obj.extraneous = true
|
|
|
|
for each folder in installed
|
|
|
|
obj.dependencies[folder] = READ(packagefolder+node_modules+folder,
|
|
|
|
obj, folder, obj.dependencies[folder])
|
|
|
|
# walk tree to find unmet deps
|
|
|
|
for each dep in obj.dependencies not in installed
|
|
|
|
r = obj.parent
|
|
|
|
while r
|
|
|
|
if r.dependencies[dep]
|
|
|
|
if r.dependencies[dep].verion !satisfies obj.dependencies[dep]
|
|
|
|
WARN
|
|
|
|
r.dependencies[dep].invalid = true
|
|
|
|
obj.dependencies[dep] = r.dependencies[dep]
|
|
|
|
r = null
|
|
|
|
else r = r.parent
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
|
|
|
TODO:
|
|
|
|
1. Find unmet deps in parent directories, searching as node does up
|
|
|
|
as far as the left-most node_modules folder.
|
|
|
|
2. Ignore anything in node_modules that isn't a package folder.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
const npm = require('npm/lib/npm.js');
|
|
|
|
const fs = require('graceful-fs');
|
|
|
|
const path = require('path');
|
|
|
|
const asyncMap = require('slide').asyncMap;
|
|
|
|
const semver = require('semver');
|
|
|
|
const log = require('log4js').getLogger('pluginfw');
|
2012-10-28 18:34:20 +01:00
|
|
|
|
|
|
|
function readJson(file, callback) {
|
2020-11-23 13:24:19 -05:00
|
|
|
fs.readFile(file, (er, buf) => {
|
|
|
|
if (er) {
|
2012-10-28 18:34:20 +01:00
|
|
|
callback(er);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
2020-11-23 13:24:19 -05:00
|
|
|
callback(null, JSON.parse(buf.toString()));
|
|
|
|
} catch (er) {
|
|
|
|
callback(er);
|
2012-10-28 18:34:20 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
});
|
2012-10-28 18:34:20 +01:00
|
|
|
}
|
2012-03-19 16:59:57 +01:00
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
module.exports = readInstalled;
|
2012-03-19 16:59:57 +01:00
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
function readInstalled(folder, cb) {
|
2012-03-19 16:59:57 +01:00
|
|
|
/* This is where we clear the cache, these three lines are all the
|
|
|
|
* new code there is */
|
|
|
|
rpSeen = {};
|
|
|
|
riSeen = [];
|
2020-11-23 13:24:19 -05:00
|
|
|
const fuSeen = [];
|
2012-03-19 16:59:57 +01:00
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
const d = npm.config.get('depth');
|
|
|
|
readInstalled_(folder, null, null, null, 0, d, (er, obj) => {
|
|
|
|
if (er) return cb(er);
|
2012-03-19 16:59:57 +01:00
|
|
|
// now obj has all the installed things, where they're installed
|
|
|
|
// figure out the inheritance links, now that the object is built.
|
2020-11-23 13:24:19 -05:00
|
|
|
resolveInheritance(obj);
|
|
|
|
cb(null, obj);
|
|
|
|
});
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
var rpSeen = {};
|
|
|
|
function readInstalled_(folder, parent, name, reqver, depth, maxDepth, cb) {
|
|
|
|
let installed,
|
|
|
|
obj,
|
|
|
|
real,
|
|
|
|
link;
|
2012-03-19 16:59:57 +01:00
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
fs.readdir(path.resolve(folder, 'node_modules'), (er, i) => {
|
2012-03-19 16:59:57 +01:00
|
|
|
// error indicates that nothing is installed here
|
2020-11-23 13:24:19 -05:00
|
|
|
if (er) i = [];
|
|
|
|
installed = i.filter((f) => f.charAt(0) !== '.');
|
|
|
|
next();
|
|
|
|
});
|
2012-03-19 16:59:57 +01:00
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
readJson(path.resolve(folder, 'package.json'), (er, data) => {
|
|
|
|
obj = copy(data);
|
2012-03-19 16:59:57 +01:00
|
|
|
|
|
|
|
if (!parent) {
|
2020-11-23 13:24:19 -05:00
|
|
|
obj = obj || true;
|
|
|
|
er = null;
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
return next(er);
|
|
|
|
});
|
2012-03-19 16:59:57 +01:00
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
fs.lstat(folder, (er, st) => {
|
2012-03-19 16:59:57 +01:00
|
|
|
if (er) {
|
2020-11-23 13:24:19 -05:00
|
|
|
if (!parent) real = true;
|
|
|
|
return next(er);
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
fs.realpath(folder, (er, rp) => {
|
|
|
|
real = rp;
|
|
|
|
if (st.isSymbolicLink()) link = rp;
|
|
|
|
next(er);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
let errState = null;
|
|
|
|
let called = false;
|
|
|
|
function next(er) {
|
|
|
|
if (errState) return;
|
2012-03-19 16:59:57 +01:00
|
|
|
if (er) {
|
2020-11-23 13:24:19 -05:00
|
|
|
errState = er;
|
|
|
|
return cb(null, []);
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
if (!installed || !obj || !real || called) return;
|
|
|
|
called = true;
|
|
|
|
if (rpSeen[real]) return cb(null, rpSeen[real]);
|
2012-03-19 16:59:57 +01:00
|
|
|
if (obj === true) {
|
2020-11-23 13:24:19 -05:00
|
|
|
obj = {dependencies: {}, path: folder};
|
|
|
|
installed.forEach((i) => { obj.dependencies[i] = '*'; });
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
if (name && obj.name !== name) obj.invalid = true;
|
|
|
|
obj.realName = name || obj.name;
|
|
|
|
obj.dependencies = obj.dependencies || {};
|
2012-03-19 16:59:57 +01:00
|
|
|
|
|
|
|
// "foo":"http://blah" is always presumed valid
|
2020-11-23 13:24:19 -05:00
|
|
|
if (reqver &&
|
|
|
|
semver.validRange(reqver) &&
|
|
|
|
!semver.satisfies(obj.version, reqver)) {
|
|
|
|
obj.invalid = true;
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
if (parent &&
|
|
|
|
!(name in parent.dependencies) &&
|
|
|
|
!(name in (parent.devDependencies || {}))) {
|
|
|
|
obj.extraneous = true;
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
obj.path = obj.path || folder;
|
|
|
|
obj.realPath = real;
|
|
|
|
obj.link = link;
|
|
|
|
if (parent && !obj.link) obj.parent = parent;
|
|
|
|
rpSeen[real] = obj;
|
|
|
|
obj.depth = depth;
|
|
|
|
if (depth >= maxDepth) return cb(null, obj);
|
|
|
|
asyncMap(installed, (pkg, cb) => {
|
|
|
|
let rv = obj.dependencies[pkg];
|
|
|
|
if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg];
|
|
|
|
readInstalled_(path.resolve(folder, `node_modules/${pkg}`)
|
|
|
|
, obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
|
|
|
|
, cb);
|
|
|
|
}, (er, installedData) => {
|
|
|
|
if (er) return cb(er);
|
|
|
|
installedData.forEach((dep) => {
|
|
|
|
obj.dependencies[dep.realName] = dep;
|
|
|
|
});
|
2012-03-19 16:59:57 +01:00
|
|
|
|
|
|
|
// any strings here are unmet things. however, if it's
|
|
|
|
// optional, then that's fine, so just delete it.
|
|
|
|
if (obj.optionalDependencies) {
|
2020-11-23 13:24:19 -05:00
|
|
|
Object.keys(obj.optionalDependencies).forEach((dep) => {
|
|
|
|
if (typeof obj.dependencies[dep] === 'string') {
|
|
|
|
delete obj.dependencies[dep];
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
});
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
return cb(null, obj);
|
|
|
|
});
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// starting from a root object, call findUnmet on each layer of children
|
2020-11-23 13:24:19 -05:00
|
|
|
var riSeen = [];
|
|
|
|
function resolveInheritance(obj) {
|
|
|
|
if (typeof obj !== 'object') return;
|
|
|
|
if (riSeen.indexOf(obj) !== -1) return;
|
|
|
|
riSeen.push(obj);
|
|
|
|
if (typeof obj.dependencies !== 'object') {
|
|
|
|
obj.dependencies = {};
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
Object.keys(obj.dependencies).forEach((dep) => {
|
|
|
|
findUnmet(obj.dependencies[dep]);
|
|
|
|
});
|
|
|
|
Object.keys(obj.dependencies).forEach((dep) => {
|
|
|
|
resolveInheritance(obj.dependencies[dep]);
|
|
|
|
});
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// find unmet deps by walking up the tree object.
|
|
|
|
// No I/O
|
2020-11-23 13:24:19 -05:00
|
|
|
const fuSeen = [];
|
|
|
|
function findUnmet(obj) {
|
2021-02-04 02:04:59 -05:00
|
|
|
if (typeof obj !== 'object') return;
|
2020-11-23 13:24:19 -05:00
|
|
|
if (fuSeen.indexOf(obj) !== -1) return;
|
|
|
|
fuSeen.push(obj);
|
|
|
|
const deps = obj.dependencies = obj.dependencies || {};
|
2012-03-19 16:59:57 +01:00
|
|
|
Object.keys(deps)
|
2020-11-23 13:24:19 -05:00
|
|
|
.filter((d) => typeof deps[d] === 'string')
|
|
|
|
.forEach((d) => {
|
|
|
|
let r = obj.parent;
|
|
|
|
let found = null;
|
|
|
|
while (r && !found && typeof deps[d] === 'string') {
|
2012-03-19 16:59:57 +01:00
|
|
|
// if r is a valid choice, then use that.
|
2020-11-23 13:24:19 -05:00
|
|
|
found = r.dependencies[d];
|
|
|
|
if (!found && r.realName === d) found = r;
|
2012-03-19 16:59:57 +01:00
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
if (!found) {
|
|
|
|
r = r.link ? null : r.parent;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (typeof deps[d] === 'string' &&
|
|
|
|
!semver.satisfies(found.version, deps[d])) {
|
2012-03-19 16:59:57 +01:00
|
|
|
// the bad thing will happen
|
2020-11-23 13:24:19 -05:00
|
|
|
log.warn(`${obj.path} requires ${d}@'${deps[d]
|
|
|
|
}' but will load\n${
|
|
|
|
found.path},\nwhich is version ${found.version}`
|
|
|
|
, 'unmet dependency');
|
|
|
|
found.invalid = true;
|
|
|
|
}
|
|
|
|
deps[d] = found;
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
});
|
|
|
|
return obj;
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
function copy(obj) {
|
|
|
|
if (!obj || typeof obj !== 'object') return obj;
|
|
|
|
if (Array.isArray(obj)) return obj.map(copy);
|
2012-03-19 16:59:57 +01:00
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
const o = {};
|
|
|
|
for (const i in obj) o[i] = copy(obj[i]);
|
|
|
|
return o;
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (module === require.main) {
|
2020-11-23 13:24:19 -05:00
|
|
|
const util = require('util');
|
|
|
|
console.error('testing');
|
|
|
|
|
|
|
|
let called = 0;
|
2021-02-04 00:28:22 -05:00
|
|
|
npm.load({}, (err) => {
|
|
|
|
if (err != null) throw err;
|
|
|
|
readInstalled(process.cwd(), (er, map) => {
|
|
|
|
console.error(called++);
|
|
|
|
if (er) return console.error(er.stack || er.message);
|
|
|
|
cleanup(map);
|
|
|
|
console.error(util.inspect(map, true, 10, true));
|
|
|
|
});
|
2020-11-23 13:24:19 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
const seen = [];
|
|
|
|
function cleanup(map) {
|
|
|
|
if (seen.indexOf(map) !== -1) return;
|
|
|
|
seen.push(map);
|
|
|
|
for (var i in map) {
|
|
|
|
switch (i) {
|
|
|
|
case '_id':
|
|
|
|
case 'path':
|
|
|
|
case 'extraneous': case 'invalid':
|
|
|
|
case 'dependencies': case 'name':
|
|
|
|
continue;
|
|
|
|
default: delete map[i];
|
|
|
|
}
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
const dep = map.dependencies;
|
2012-03-19 16:59:57 +01:00
|
|
|
if (dep) {
|
2020-11-23 13:24:19 -05:00
|
|
|
for (var i in dep) {
|
|
|
|
if (typeof dep[i] === 'object') {
|
|
|
|
cleanup(dep[i]);
|
|
|
|
}
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
return map;
|
2012-03-19 16:59:57 +01:00
|
|
|
}
|
|
|
|
}
|