| // 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 | |
| deps that don't meet a peer requirement are marked with peerInvalid: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>:ANY}} | |
| 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. | |
| */ | |
| try { | |
| var fs = require("graceful-fs") | |
| } catch (er) { | |
| var fs = require("fs") | |
| } | |
| var path = require("path") | |
| var asyncMap = require("slide").asyncMap | |
| var semver = require("semver") | |
| var readJson = require("read-package-json") | |
| var url = require("url") | |
| var util = require("util") | |
| var extend = require("util-extend") | |
| var debug = require("debuglog")("read-installed") | |
| var readdir = require("readdir-scoped-modules") | |
| // Sentinel catch-all version constraint used when a dependency is not | |
| // listed in the package.json file. | |
| var ANY = {} | |
| module.exports = readInstalled | |
| function readInstalled (folder, opts, cb) { | |
| if (typeof opts === 'function') { | |
| cb = opts | |
| opts = {} | |
| } else { | |
| opts = extend({}, opts) | |
| } | |
| if (typeof opts.depth !== 'number') | |
| opts.depth = Infinity | |
| opts.depth = Math.max(0, opts.depth) | |
| if (typeof opts.log !== 'function') | |
| opts.log = function () {} | |
| opts.dev = !!opts.dev | |
| opts.realpathSeen = {} | |
| opts.findUnmetSeen = [] | |
| readInstalled_(folder, null, null, null, 0, opts, function (er, obj) { | |
| if (er) return cb(er) | |
| // now obj has all the installed things, where they're installed | |
| // figure out the inheritance links, now that the object is built. | |
| resolveInheritance(obj, opts) | |
| obj.root = true | |
| unmarkExtraneous(obj, opts) | |
| cb(null, obj) | |
| }) | |
| } | |
| function readInstalled_ (folder, parent, name, reqver, depth, opts, cb) { | |
| var installed | |
| , obj | |
| , real | |
| , link | |
| , realpathSeen = opts.realpathSeen | |
| readdir(path.resolve(folder, "node_modules"), function (er, i) { | |
| // error indicates that nothing is installed here | |
| if (er) i = [] | |
| installed = i.filter(function (f) { return f.charAt(0) !== "." }) | |
| next() | |
| }) | |
| readJson(path.resolve(folder, "package.json"), function (er, data) { | |
| obj = copy(data) | |
| if (!parent) { | |
| obj = obj || true | |
| er = null | |
| } | |
| return next(er) | |
| }) | |
| fs.lstat(folder, function (er, st) { | |
| if (er) { | |
| if (!parent) real = true | |
| return next(er) | |
| } | |
| fs.realpath(folder, function (er, rp) { | |
| debug("realpath(%j) = %j", folder, rp) | |
| real = rp | |
| if (st.isSymbolicLink()) link = rp | |
| next(er) | |
| }) | |
| }) | |
| var errState = null | |
| , called = false | |
| function next (er) { | |
| if (errState) return | |
| if (er) { | |
| errState = er | |
| return cb(null, []) | |
| } | |
| debug('next', installed, obj && typeof obj, name, real) | |
| if (!installed || !obj || !real || called) return | |
| called = true | |
| if (realpathSeen[real]) return cb(null, realpathSeen[real]) | |
| if (obj === true) { | |
| obj = {dependencies:{}, path:folder} | |
| installed.forEach(function (i) { obj.dependencies[i] = ANY }) | |
| } | |
| if (name && obj.name !== name) obj.invalid = true | |
| obj.realName = name || obj.name | |
| obj.dependencies = obj.dependencies || {} | |
| // At this point, figure out what dependencies we NEED to get met | |
| obj._dependencies = copy(obj.dependencies) | |
| if (reqver === ANY) { | |
| // We were unable to determine the required version of this | |
| // dependency from the package.json file, but we now know its actual | |
| // version, so treat that version as the required version to avoid | |
| // marking the dependency as invalid below. See #40. | |
| reqver = obj.version; | |
| } | |
| // "foo":"http://blah" and "foo":"latest" are always presumed valid | |
| if (reqver | |
| && semver.validRange(reqver, true) | |
| && !semver.satisfies(obj.version, reqver, true)) { | |
| obj.invalid = true | |
| } | |
| // Mark as extraneous at this point. | |
| // This will be un-marked in unmarkExtraneous, where we mark as | |
| // not-extraneous everything that is required in some way from | |
| // the root object. | |
| obj.extraneous = true | |
| obj.path = obj.path || folder | |
| obj.realPath = real | |
| obj.link = link | |
| if (parent && !obj.link) obj.parent = parent | |
| realpathSeen[real] = obj | |
| obj.depth = depth | |
| //if (depth >= opts.depth) return cb(null, obj) | |
| asyncMap(installed, function (pkg, cb) { | |
| var rv = obj.dependencies[pkg] | |
| if (!rv && obj.devDependencies && opts.dev) | |
| rv = obj.devDependencies[pkg] | |
| if (depth > opts.depth) { | |
| obj.dependencies = {} | |
| return cb(null, obj) | |
| } | |
| readInstalled_( path.resolve(folder, "node_modules/"+pkg) | |
| , obj, pkg, obj.dependencies[pkg], depth + 1, opts | |
| , cb ) | |
| }, function (er, installedData) { | |
| if (er) return cb(er) | |
| installedData.forEach(function (dep) { | |
| obj.dependencies[dep.realName] = dep | |
| }) | |
| // any strings here are unmet things. however, if it's | |
| // optional, then that's fine, so just delete it. | |
| if (obj.optionalDependencies) { | |
| Object.keys(obj.optionalDependencies).forEach(function (dep) { | |
| if (typeof obj.dependencies[dep] === "string") { | |
| delete obj.dependencies[dep] | |
| } | |
| }) | |
| } | |
| return cb(null, obj) | |
| }) | |
| } | |
| } | |
| // starting from a root object, call findUnmet on each layer of children | |
| var riSeen = [] | |
| function resolveInheritance (obj, opts) { | |
| if (typeof obj !== "object") return | |
| if (riSeen.indexOf(obj) !== -1) return | |
| riSeen.push(obj) | |
| if (typeof obj.dependencies !== "object") { | |
| obj.dependencies = {} | |
| } | |
| Object.keys(obj.dependencies).forEach(function (dep) { | |
| findUnmet(obj.dependencies[dep], opts) | |
| }) | |
| Object.keys(obj.dependencies).forEach(function (dep) { | |
| if (typeof obj.dependencies[dep] === "object") { | |
| resolveInheritance(obj.dependencies[dep], opts) | |
| } else { | |
| debug("unmet dep! %s %s@%s", obj.name, dep, obj.dependencies[dep]) | |
| } | |
| }) | |
| findUnmet(obj, opts) | |
| } | |
| // find unmet deps by walking up the tree object. | |
| // No I/O | |
| function findUnmet (obj, opts) { | |
| var findUnmetSeen = opts.findUnmetSeen | |
| if (findUnmetSeen.indexOf(obj) !== -1) return | |
| findUnmetSeen.push(obj) | |
| debug("find unmet parent=%s obj=", obj.parent && obj.parent.name, obj.name || obj) | |
| var deps = obj.dependencies = obj.dependencies || {} | |
| debug(deps) | |
| Object.keys(deps) | |
| .filter(function (d) { return typeof deps[d] === "string" }) | |
| .forEach(function (d) { | |
| var found = findDep(obj, d) | |
| debug("finding dep %j", d, found && found.name || found) | |
| // "foo":"http://blah" and "foo":"latest" are always presumed valid | |
| if (typeof deps[d] === "string" && | |
| semver.validRange(deps[d], true) && | |
| found && | |
| !semver.satisfies(found.version, deps[d], true)) { | |
| // the bad thing will happen | |
| opts.log( "unmet dependency" | |
| , obj.path + " requires "+d+"@'"+deps[d] | |
| + "' but will load\n" | |
| + found.path+",\nwhich is version "+found.version ) | |
| found.invalid = true | |
| } | |
| if (found) { | |
| deps[d] = found | |
| } | |
| }) | |
| var peerDeps = obj.peerDependencies = obj.peerDependencies || {} | |
| Object.keys(peerDeps).forEach(function (d) { | |
| var dependency | |
| if (!obj.parent) { | |
| dependency = obj.dependencies[d] | |
| // read it as a missing dep | |
| if (!dependency) { | |
| obj.dependencies[d] = peerDeps[d] | |
| } | |
| } else { | |
| var r = obj.parent | |
| while (r && !dependency) { | |
| dependency = r.dependencies && r.dependencies[d] | |
| r = r.link ? null : r.parent | |
| } | |
| } | |
| if (!dependency) { | |
| // mark as a missing dep! | |
| obj.dependencies[d] = peerDeps[d] | |
| } else if (!semver.satisfies(dependency.version, peerDeps[d], true)) { | |
| dependency.peerInvalid = true | |
| } | |
| }) | |
| return obj | |
| } | |
| function unmarkExtraneous (obj, opts) { | |
| // Mark all non-required deps as extraneous. | |
| // start from the root object and mark as non-extraneous all modules | |
| // that haven't been previously flagged as extraneous then propagate | |
| // to all their dependencies | |
| obj.extraneous = false | |
| var deps = obj._dependencies || [] | |
| if (opts.dev && obj.devDependencies && (obj.root || obj.link)) { | |
| Object.keys(obj.devDependencies).forEach(function (k) { | |
| deps[k] = obj.devDependencies[k] | |
| }) | |
| } | |
| if (obj.peerDependencies) { | |
| Object.keys(obj.peerDependencies).forEach(function (k) { | |
| deps[k] = obj.peerDependencies[k] | |
| }) | |
| } | |
| debug("not extraneous", obj._id, deps) | |
| Object.keys(deps).forEach(function (d) { | |
| var dep = findDep(obj, d) | |
| if (dep && dep.extraneous) { | |
| unmarkExtraneous(dep, opts) | |
| } | |
| }) | |
| } | |
| // Find the one that will actually be loaded by require() | |
| // so we can make sure it's valid etc. | |
| function findDep (obj, d) { | |
| var r = obj | |
| , found = null | |
| while (r && !found) { | |
| // if r is a valid choice, then use that. | |
| // kinda weird if a pkg depends on itself, but after the first | |
| // iteration of this loop, it indicates a dep cycle. | |
| if (typeof r.dependencies[d] === "object") { | |
| found = r.dependencies[d] | |
| } | |
| if (!found && r.realName === d) found = r | |
| r = r.link ? null : r.parent | |
| } | |
| return found | |
| } | |
| function copy (obj) { | |
| if (!obj || typeof obj !== 'object') return obj | |
| if (Array.isArray(obj)) return obj.map(copy) | |
| var o = {} | |
| for (var i in obj) o[i] = copy(obj[i]) | |
| return o | |
| } |