2 // Walk through the file-system "database" of installed
3 // packages, and create a data object related to the
4 // installed versions of each package.
7 This will traverse through all node_modules folders,
8 resolving the dependencies object to the object corresponding to
9 the package that meets that dep, or just the version/range if
12 Assuming that you had this folder structure:
15 +-- package.json { name = "root" }
17 +-- foo {bar, baz, asdf}
23 where "foo" depends on bar, baz, and asdf, bar depends on baz,
24 and bar and baz are bundled with foo, whereas "asdf" is at
25 the higher level (sibling to foo), you'd get this object structure:
33 , path: "/path/to/node_modules/foo"
34 , parent: <Circular: root>
37 { parent: <Circular: foo>
38 , path: "/path/to/node_modules/foo/node_modules/bar"
40 , dependencies: { baz: <Circular: foo.dependencies.baz> }
43 , asdf: <Circular: asdf>
50 Unmet deps are left as strings.
51 Extraneous deps are marked with extraneous:true
52 deps that don't meet a requirement are marked with invalid:true
53 deps that don't meet a peer requirement are marked with peerInvalid:true
55 to READ(packagefolder, parentobj, name, reqver)
56 obj = read package.json
57 installed = ./node_modules/*
58 if parentobj is null, and no package.json
59 obj = {dependencies:{<installed>:ANY}}
60 deps = Object.keys(obj.dependencies)
61 obj.path = packagefolder
62 obj.parent = parentobj
63 if name, && obj.name !== name, obj.invalid = true
64 if reqver, && obj.version !satisfies reqver, obj.invalid = true
65 if !reqver && parentobj, obj.extraneous = true
66 for each folder in installed
67 obj.dependencies[folder] = READ(packagefolder+node_modules+folder,
68 obj, folder, obj.dependencies[folder])
69 # walk tree to find unmet deps
70 for each dep in obj.dependencies not in installed
73 if r.dependencies[dep]
74 if r.dependencies[dep].verion !satisfies obj.dependencies[dep]
76 r.dependencies[dep].invalid = true
77 obj.dependencies[dep] = r.dependencies[dep]
84 1. Find unmet deps in parent directories, searching as node does up
85 as far as the left-most node_modules folder.
86 2. Ignore anything in node_modules that isn't a package folder.
91 var fs = require("graceful-fs")
93 var fs = require("fs")
96 var path = require("path")
97 var asyncMap = require("slide").asyncMap
98 var semver = require("semver")
99 var readJson = require("read-package-json")
100 var url = require("url")
101 var util = require("util")
102 var extend = require("util-extend")
104 var debug = require("debuglog")("read-installed")
106 var readdir = require("readdir-scoped-modules")
108 // Sentinel catch-all version constraint used when a dependency is not
109 // listed in the package.json file.
112 module.exports = readInstalled
114 function readInstalled (folder, opts, cb) {
115 if (typeof opts === 'function') {
119 opts = extend({}, opts)
122 if (typeof opts.depth !== 'number')
123 opts.depth = Infinity
125 opts.depth = Math.max(0, opts.depth)
127 if (typeof opts.log !== 'function')
128 opts.log = function () {}
130 opts.dev = !!opts.dev
131 opts.realpathSeen = {}
132 opts.findUnmetSeen = []
135 readInstalled_(folder, null, null, null, 0, opts, function (er, obj) {
136 if (er) return cb(er)
137 // now obj has all the installed things, where they're installed
138 // figure out the inheritance links, now that the object is built.
139 resolveInheritance(obj, opts)
141 unmarkExtraneous(obj, opts)
146 function readInstalled_ (folder, parent, name, reqver, depth, opts, cb) {
151 , realpathSeen = opts.realpathSeen
153 readdir(path.resolve(folder, "node_modules"), function (er, i) {
154 // error indicates that nothing is installed here
156 installed = i.filter(function (f) { return f.charAt(0) !== "." })
160 readJson(path.resolve(folder, "package.json"), function (er, data) {
170 fs.lstat(folder, function (er, st) {
172 if (!parent) real = true
175 fs.realpath(folder, function (er, rp) {
176 debug("realpath(%j) = %j", folder, rp)
178 if (st.isSymbolicLink()) link = rp
191 debug('next', installed, obj && typeof obj, name, real)
192 if (!installed || !obj || !real || called) return
194 if (realpathSeen[real]) return cb(null, realpathSeen[real])
196 obj = {dependencies:{}, path:folder}
197 installed.forEach(function (i) { obj.dependencies[i] = ANY })
199 if (name && obj.name !== name) obj.invalid = true
200 obj.realName = name || obj.name
201 obj.dependencies = obj.dependencies || {}
203 // At this point, figure out what dependencies we NEED to get met
204 obj._dependencies = copy(obj.dependencies)
206 if (reqver === ANY) {
207 // We were unable to determine the required version of this
208 // dependency from the package.json file, but we now know its actual
209 // version, so treat that version as the required version to avoid
210 // marking the dependency as invalid below. See #40.
211 reqver = obj.version;
214 // "foo":"http://blah" and "foo":"latest" are always presumed valid
216 && semver.validRange(reqver, true)
217 && !semver.satisfies(obj.version, reqver, true)) {
221 // Mark as extraneous at this point.
222 // This will be un-marked in unmarkExtraneous, where we mark as
223 // not-extraneous everything that is required in some way from
225 obj.extraneous = true
227 obj.path = obj.path || folder
230 if (parent && !obj.link) obj.parent = parent
231 realpathSeen[real] = obj
233 //if (depth >= opts.depth) return cb(null, obj)
234 asyncMap(installed, function (pkg, cb) {
235 var rv = obj.dependencies[pkg]
236 if (!rv && obj.devDependencies && opts.dev)
237 rv = obj.devDependencies[pkg]
239 if (depth > opts.depth) {
240 obj.dependencies = {}
244 readInstalled_( path.resolve(folder, "node_modules/"+pkg)
245 , obj, pkg, obj.dependencies[pkg], depth + 1, opts
248 }, function (er, installedData) {
249 if (er) return cb(er)
250 installedData.forEach(function (dep) {
251 obj.dependencies[dep.realName] = dep
254 // any strings here are unmet things. however, if it's
255 // optional, then that's fine, so just delete it.
256 if (obj.optionalDependencies) {
257 Object.keys(obj.optionalDependencies).forEach(function (dep) {
258 if (typeof obj.dependencies[dep] === "string") {
259 delete obj.dependencies[dep]
268 // starting from a root object, call findUnmet on each layer of children
270 function resolveInheritance (obj, opts) {
271 if (typeof obj !== "object") return
272 if (riSeen.indexOf(obj) !== -1) return
274 if (typeof obj.dependencies !== "object") {
275 obj.dependencies = {}
277 Object.keys(obj.dependencies).forEach(function (dep) {
278 findUnmet(obj.dependencies[dep], opts)
280 Object.keys(obj.dependencies).forEach(function (dep) {
281 if (typeof obj.dependencies[dep] === "object") {
282 resolveInheritance(obj.dependencies[dep], opts)
284 debug("unmet dep! %s %s@%s", obj.name, dep, obj.dependencies[dep])
290 // find unmet deps by walking up the tree object.
292 function findUnmet (obj, opts) {
293 var findUnmetSeen = opts.findUnmetSeen
294 if (findUnmetSeen.indexOf(obj) !== -1) return
295 findUnmetSeen.push(obj)
296 debug("find unmet parent=%s obj=", obj.parent && obj.parent.name, obj.name || obj)
297 var deps = obj.dependencies = obj.dependencies || {}
301 .filter(function (d) { return typeof deps[d] === "string" })
302 .forEach(function (d) {
303 var found = findDep(obj, d)
304 debug("finding dep %j", d, found && found.name || found)
305 // "foo":"http://blah" and "foo":"latest" are always presumed valid
306 if (typeof deps[d] === "string" &&
307 semver.validRange(deps[d], true) &&
309 !semver.satisfies(found.version, deps[d], true)) {
310 // the bad thing will happen
311 opts.log( "unmet dependency"
312 , obj.path + " requires "+d+"@'"+deps[d]
313 + "' but will load\n"
314 + found.path+",\nwhich is version "+found.version )
322 var peerDeps = obj.peerDependencies = obj.peerDependencies || {}
323 Object.keys(peerDeps).forEach(function (d) {
327 dependency = obj.dependencies[d]
329 // read it as a missing dep
331 obj.dependencies[d] = peerDeps[d]
335 while (r && !dependency) {
336 dependency = r.dependencies && r.dependencies[d]
337 r = r.link ? null : r.parent
342 // mark as a missing dep!
343 obj.dependencies[d] = peerDeps[d]
344 } else if (!semver.satisfies(dependency.version, peerDeps[d], true)) {
345 dependency.peerInvalid = true
352 function unmarkExtraneous (obj, opts) {
353 // Mark all non-required deps as extraneous.
354 // start from the root object and mark as non-extraneous all modules
355 // that haven't been previously flagged as extraneous then propagate
356 // to all their dependencies
358 obj.extraneous = false
360 var deps = obj._dependencies || []
361 if (opts.dev && obj.devDependencies && (obj.root || obj.link)) {
362 Object.keys(obj.devDependencies).forEach(function (k) {
363 deps[k] = obj.devDependencies[k]
367 if (obj.peerDependencies) {
368 Object.keys(obj.peerDependencies).forEach(function (k) {
369 deps[k] = obj.peerDependencies[k]
373 debug("not extraneous", obj._id, deps)
374 Object.keys(deps).forEach(function (d) {
375 var dep = findDep(obj, d)
376 if (dep && dep.extraneous) {
377 unmarkExtraneous(dep, opts)
382 // Find the one that will actually be loaded by require()
383 // so we can make sure it's valid etc.
384 function findDep (obj, d) {
387 while (r && !found) {
388 // if r is a valid choice, then use that.
389 // kinda weird if a pkg depends on itself, but after the first
390 // iteration of this loop, it indicates a dep cycle.
391 if (typeof r.dependencies[d] === "object") {
392 found = r.dependencies[d]
394 if (!found && r.realName === d) found = r
395 r = r.link ? null : r.parent
400 function copy (obj) {
401 if (!obj || typeof obj !== 'object') return obj
402 if (Array.isArray(obj)) return obj.map(copy)
405 for (var i in obj) o[i] = copy(obj[i])