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>:"*"}}
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")
102 module.exports = readInstalled
104 function readInstalled (folder, depth_, log_, cb_) {
105 var depth = Infinity, log = function () {}, cb
106 for (var i = 1; i < arguments.length - 1; i++) {
107 if (typeof arguments[i] === 'number')
109 else if (typeof arguments[i] === 'function')
114 readInstalled_(folder, null, null, null, 0, depth, function (er, obj) {
115 if (er) return cb(er)
116 // now obj has all the installed things, where they're installed
117 // figure out the inheritance links, now that the object is built.
118 resolveInheritance(obj, log)
124 function readInstalled_ (folder, parent, name, reqver, depth, maxDepth, cb) {
130 fs.readdir(path.resolve(folder, "node_modules"), function (er, i) {
131 // error indicates that nothing is installed here
133 installed = i.filter(function (f) { return f.charAt(0) !== "." })
137 readJson(path.resolve(folder, "package.json"), function (er, data) {
147 fs.lstat(folder, function (er, st) {
149 if (!parent) real = true
152 fs.realpath(folder, function (er, rp) {
153 //console.error("realpath(%j) = %j", folder, rp)
155 if (st.isSymbolicLink()) link = rp
168 //console.error('next', installed, obj && typeof obj, name, real)
169 if (!installed || !obj || !real || called) return
171 if (rpSeen[real]) return cb(null, rpSeen[real])
173 obj = {dependencies:{}, path:folder}
174 installed.forEach(function (i) { obj.dependencies[i] = "*" })
176 if (name && obj.name !== name) obj.invalid = true
177 obj.realName = name || obj.name
178 obj.dependencies = obj.dependencies || {}
180 // "foo":"http://blah" is always presumed valid
182 && semver.validRange(reqver, true)
183 && !semver.satisfies(obj.version, reqver, true)) {
188 && !(name in parent.dependencies)
189 && !(name in (parent.devDependencies || {}))) {
190 obj.extraneous = true
192 obj.path = obj.path || folder
195 if (parent && !obj.link) obj.parent = parent
198 //if (depth >= maxDepth) return cb(null, obj)
199 asyncMap(installed, function (pkg, cb) {
200 var rv = obj.dependencies[pkg]
201 if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg]
202 if (depth >= maxDepth) {
203 // just try to get the version number
204 var pkgfolder = path.resolve(folder, "node_modules", pkg)
205 , jsonFile = path.resolve(pkgfolder, "package.json")
206 return readJson(jsonFile, function (er, depData) {
207 // already out of our depth, ignore errors
208 if (er || !depData || !depData.version) return cb(null, obj)
209 if (depth === maxDepth) {
210 // edge case, ignore dependencies
211 depData.dependencies = {}
212 depData.peerDependencies = {}
213 obj.dependencies[pkg] = depData
215 obj.dependencies[pkg] = depData.version
221 readInstalled_( path.resolve(folder, "node_modules/"+pkg)
222 , obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
225 }, function (er, installedData) {
226 if (er) return cb(er)
227 installedData.forEach(function (dep) {
228 obj.dependencies[dep.realName] = dep
231 // any strings here are unmet things. however, if it's
232 // optional, then that's fine, so just delete it.
233 if (obj.optionalDependencies) {
234 Object.keys(obj.optionalDependencies).forEach(function (dep) {
235 if (typeof obj.dependencies[dep] === "string") {
236 delete obj.dependencies[dep]
245 // starting from a root object, call findUnmet on each layer of children
247 function resolveInheritance (obj, log) {
248 if (typeof obj !== "object") return
249 if (riSeen.indexOf(obj) !== -1) return
251 if (typeof obj.dependencies !== "object") {
252 obj.dependencies = {}
254 Object.keys(obj.dependencies).forEach(function (dep) {
255 findUnmet(obj.dependencies[dep], log)
257 Object.keys(obj.dependencies).forEach(function (dep) {
258 resolveInheritance(obj.dependencies[dep], log)
263 // find unmet deps by walking up the tree object.
266 function findUnmet (obj, log) {
267 if (fuSeen.indexOf(obj) !== -1) return
269 //console.error("find unmet", obj.name, obj.parent && obj.parent.name)
270 var deps = obj.dependencies = obj.dependencies || {}
272 //console.error(deps)
274 .filter(function (d) { return typeof deps[d] === "string" })
275 .forEach(function (d) {
276 //console.error("find unmet", obj.name, d, deps[d])
279 while (r && !found && typeof deps[d] === "string") {
280 // if r is a valid choice, then use that.
281 found = r.dependencies[d]
282 if (!found && r.realName === d) found = r
285 r = r.link ? null : r.parent
288 if ( typeof deps[d] === "string"
289 // url deps presumed innocent.
290 && !url.parse(deps[d]).protocol
291 && !semver.satisfies(found.version, deps[d], true)) {
292 // the bad thing will happen
293 log("unmet dependency", obj.path + " requires "+d+"@'"+deps[d]
295 +found.path+",\nwhich is version "+found.version
299 found.extraneous = false
306 var peerDeps = obj.peerDependencies = obj.peerDependencies || {}
307 Object.keys(peerDeps).forEach(function (d) {
311 dependency = obj.dependencies[d]
313 // read it as a missing dep
315 obj.dependencies[d] = peerDeps[d]
318 dependency = obj.parent.dependencies && obj.parent.dependencies[d]
321 if (!dependency) return
323 dependency.extraneous = false
325 if (!semver.satisfies(dependency.version, peerDeps[d], true)) {
326 dependency.peerInvalid = true
333 function copy (obj) {
334 if (!obj || typeof obj !== 'object') return obj
335 if (Array.isArray(obj)) return obj.map(copy)
338 for (var i in obj) o[i] = copy(obj[i])