npm: Upgrade to 1.3.17
[platform/upstream/nodejs.git] / deps / npm / node_modules / read-installed / read-installed.js
1
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.
5
6 /*
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
10 unmet.
11
12 Assuming that you had this folder structure:
13
14 /path/to
15 +-- package.json { name = "root" }
16 `-- node_modules
17     +-- foo {bar, baz, asdf}
18     | +-- node_modules
19     |   +-- bar { baz }
20     |   `-- baz
21     `-- asdf
22
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:
26
27 { <package.json data>
28 , path: "/path/to"
29 , parent: null
30 , dependencies:
31   { foo :
32     { version: "1.2.3"
33     , path: "/path/to/node_modules/foo"
34     , parent: <Circular: root>
35     , dependencies:
36       { bar:
37         { parent: <Circular: foo>
38         , path: "/path/to/node_modules/foo/node_modules/bar"
39         , version: "2.3.4"
40         , dependencies: { baz: <Circular: foo.dependencies.baz> }
41         }
42       , baz: { ... }
43       , asdf: <Circular: asdf>
44       }
45     }
46   , asdf: { ... }
47   }
48 }
49
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
54
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
71   r = obj.parent
72   while r
73     if r.dependencies[dep]
74       if r.dependencies[dep].verion !satisfies obj.dependencies[dep]
75         WARN
76         r.dependencies[dep].invalid = true
77       obj.dependencies[dep] = r.dependencies[dep]
78       r = null
79     else r = r.parent
80 return obj
81
82
83 TODO:
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.
87
88 */
89
90 try {
91   var fs = require("graceful-fs")
92 } catch (er) {
93   var fs = require("fs")
94 }
95
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
102 module.exports = readInstalled
103
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')
108       depth = arguments[i]
109     else if (typeof arguments[i] === 'function')
110       log = arguments[i]
111   }
112   cb = arguments[i]
113
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)
119     cb(null, obj)
120   })
121 }
122
123 var rpSeen = {}
124 function readInstalled_ (folder, parent, name, reqver, depth, maxDepth, cb) {
125   var installed
126     , obj
127     , real
128     , link
129
130   fs.readdir(path.resolve(folder, "node_modules"), function (er, i) {
131     // error indicates that nothing is installed here
132     if (er) i = []
133     installed = i.filter(function (f) { return f.charAt(0) !== "." })
134     next()
135   })
136
137   readJson(path.resolve(folder, "package.json"), function (er, data) {
138     obj = copy(data)
139
140     if (!parent) {
141       obj = obj || true
142       er = null
143     }
144     return next(er)
145   })
146
147   fs.lstat(folder, function (er, st) {
148     if (er) {
149       if (!parent) real = true
150       return next(er)
151     }
152     fs.realpath(folder, function (er, rp) {
153       //console.error("realpath(%j) = %j", folder, rp)
154       real = rp
155       if (st.isSymbolicLink()) link = rp
156       next(er)
157     })
158   })
159
160   var errState = null
161     , called = false
162   function next (er) {
163     if (errState) return
164     if (er) {
165       errState = er
166       return cb(null, [])
167     }
168     //console.error('next', installed, obj && typeof obj, name, real)
169     if (!installed || !obj || !real || called) return
170     called = true
171     if (rpSeen[real]) return cb(null, rpSeen[real])
172     if (obj === true) {
173       obj = {dependencies:{}, path:folder}
174       installed.forEach(function (i) { obj.dependencies[i] = "*" })
175     }
176     if (name && obj.name !== name) obj.invalid = true
177     obj.realName = name || obj.name
178     obj.dependencies = obj.dependencies || {}
179
180     // "foo":"http://blah" is always presumed valid
181     if (reqver
182         && semver.validRange(reqver, true)
183         && !semver.satisfies(obj.version, reqver, true)) {
184       obj.invalid = true
185     }
186
187     if (parent
188         && !(name in parent.dependencies)
189         && !(name in (parent.devDependencies || {}))) {
190       obj.extraneous = true
191     }
192     obj.path = obj.path || folder
193     obj.realPath = real
194     obj.link = link
195     if (parent && !obj.link) obj.parent = parent
196     rpSeen[real] = obj
197     obj.depth = depth
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
214           } else {
215             obj.dependencies[pkg] = depData.version
216           }
217           cb(null, obj)
218         })
219       }
220
221       readInstalled_( path.resolve(folder, "node_modules/"+pkg)
222                     , obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
223                     , cb )
224
225     }, function (er, installedData) {
226       if (er) return cb(er)
227       installedData.forEach(function (dep) {
228         obj.dependencies[dep.realName] = dep
229       })
230
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]
237           }
238         })
239       }
240       return cb(null, obj)
241     })
242   }
243 }
244
245 // starting from a root object, call findUnmet on each layer of children
246 var riSeen = []
247 function resolveInheritance (obj, log) {
248   if (typeof obj !== "object") return
249   if (riSeen.indexOf(obj) !== -1) return
250   riSeen.push(obj)
251   if (typeof obj.dependencies !== "object") {
252     obj.dependencies = {}
253   }
254   Object.keys(obj.dependencies).forEach(function (dep) {
255     findUnmet(obj.dependencies[dep], log)
256   })
257   Object.keys(obj.dependencies).forEach(function (dep) {
258     resolveInheritance(obj.dependencies[dep], log)
259   })
260   findUnmet(obj, log)
261 }
262
263 // find unmet deps by walking up the tree object.
264 // No I/O
265 var fuSeen = []
266 function findUnmet (obj, log) {
267   if (fuSeen.indexOf(obj) !== -1) return
268   fuSeen.push(obj)
269   //console.error("find unmet", obj.name, obj.parent && obj.parent.name)
270   var deps = obj.dependencies = obj.dependencies || {}
271
272   //console.error(deps)
273   Object.keys(deps)
274     .filter(function (d) { return typeof deps[d] === "string" })
275     .forEach(function (d) {
276       //console.error("find unmet", obj.name, d, deps[d])
277       var r = obj.parent
278         , found = null
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
283
284         if (!found) {
285           r = r.link ? null : r.parent
286           continue
287         }
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]
294              +"' but will load\n"
295              +found.path+",\nwhich is version "+found.version
296              )
297           found.invalid = true
298         } else {
299           found.extraneous = false
300         }
301         deps[d] = found
302       }
303
304     })
305
306   var peerDeps = obj.peerDependencies = obj.peerDependencies || {}
307   Object.keys(peerDeps).forEach(function (d) {
308     var dependency
309
310     if (!obj.parent) {
311       dependency = obj.dependencies[d]
312
313       // read it as a missing dep
314       if (!dependency) {
315         obj.dependencies[d] = peerDeps[d]
316       }
317     } else {
318       dependency = obj.parent.dependencies && obj.parent.dependencies[d]
319     }
320
321     if (!dependency) return
322
323     dependency.extraneous = false
324
325     if (!semver.satisfies(dependency.version, peerDeps[d], true)) {
326       dependency.peerInvalid = true
327     }
328   })
329
330   return obj
331 }
332
333 function copy (obj) {
334   if (!obj || typeof obj !== 'object') return obj
335   if (Array.isArray(obj)) return obj.map(copy)
336
337   var o = {}
338   for (var i in obj) o[i] = copy(obj[i])
339   return o
340 }