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/* 7This will traverse through all node_modules folders, 8resolving the dependencies object to the object corresponding to 9the package that meets that dep, or just the version/range if 10unmet. 11 12Assuming 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 23where "foo" depends on bar, baz, and asdf, bar depends on baz, 24and bar and baz are bundled with foo, whereas "asdf" is at 25the 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 50Unmet deps are left as strings. 51Extraneous deps are marked with extraneous:true 52deps that don't meet a requirement are marked with invalid:true 53deps that don't meet a peer requirement are marked with peerInvalid:true 54 55to READ(packagefolder, parentobj, name, reqver) 56obj = read package.json 57installed = ./node_modules/* 58if parentobj is null, and no package.json 59 obj = {dependencies:{<installed>:ANY}} 60deps = Object.keys(obj.dependencies) 61obj.path = packagefolder 62obj.parent = parentobj 63if name, && obj.name !== name, obj.invalid = true 64if reqver, && obj.version !satisfies reqver, obj.invalid = true 65if !reqver && parentobj, obj.extraneous = true 66for 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 70for 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 80return obj 81 82 83TODO: 841. Find unmet deps in parent directories, searching as node does up 85as far as the left-most node_modules folder. 862. Ignore anything in node_modules that isn't a package folder. 87 88*/ 89 90try { 91 var fs = require("graceful-fs") 92} catch (er) { 93 var fs = require("fs") 94} 95 96var path = require("path") 97var asyncMap = require("slide").asyncMap 98var semver = require("semver") 99var readJson = require("read-package-json") 100var url = require("url") 101var util = require("util") 102var extend = require("util-extend") 103 104var debug = require("debuglog")("read-installed") 105 106var readdir = require("readdir-scoped-modules") 107 108// Sentinel catch-all version constraint used when a dependency is not 109// listed in the package.json file. 110var ANY = {} 111 112module.exports = readInstalled 113 114function readInstalled (folder, opts, cb) { 115 if (typeof opts === 'function') { 116 cb = opts 117 opts = {} 118 } else { 119 opts = extend({}, opts) 120 } 121 122 if (typeof opts.depth !== 'number') 123 opts.depth = Infinity 124 125 opts.depth = Math.max(0, opts.depth) 126 127 if (typeof opts.log !== 'function') 128 opts.log = function () {} 129 130 opts.dev = !!opts.dev 131 opts.realpathSeen = {} 132 opts.findUnmetSeen = [] 133 134 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) 140 obj.root = true 141 unmarkExtraneous(obj, opts) 142 cb(null, obj) 143 }) 144} 145 146function readInstalled_ (folder, parent, name, reqver, depth, opts, cb) { 147 var installed 148 , obj 149 , real 150 , link 151 , realpathSeen = opts.realpathSeen 152 153 readdir(path.resolve(folder, "node_modules"), function (er, i) { 154 // error indicates that nothing is installed here 155 if (er) i = [] 156 installed = i.filter(function (f) { return f.charAt(0) !== "." }) 157 next() 158 }) 159 160 readJson(path.resolve(folder, "package.json"), function (er, data) { 161 obj = copy(data) 162 163 if (!parent) { 164 obj = obj || true 165 er = null 166 } 167 return next(er) 168 }) 169 170 fs.lstat(folder, function (er, st) { 171 if (er) { 172 if (!parent) real = true 173 return next(er) 174 } 175 fs.realpath(folder, function (er, rp) { 176 debug("realpath(%j) = %j", folder, rp) 177 real = rp 178 if (st.isSymbolicLink()) link = rp 179 next(er) 180 }) 181 }) 182 183 var errState = null 184 , called = false 185 function next (er) { 186 if (errState) return 187 if (er) { 188 errState = er 189 return cb(null, []) 190 } 191 debug('next', installed, obj && typeof obj, name, real) 192 if (!installed || !obj || !real || called) return 193 called = true 194 if (realpathSeen[real]) return cb(null, realpathSeen[real]) 195 if (obj === true) { 196 obj = {dependencies:{}, path:folder} 197 installed.forEach(function (i) { obj.dependencies[i] = ANY }) 198 } 199 if (name && obj.name !== name) obj.invalid = true 200 obj.realName = name || obj.name 201 obj.dependencies = obj.dependencies || {} 202 203 // At this point, figure out what dependencies we NEED to get met 204 obj._dependencies = copy(obj.dependencies) 205 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; 212 } 213 214 // "foo":"http://blah" and "foo":"latest" are always presumed valid 215 if (reqver 216 && semver.validRange(reqver, true) 217 && !semver.satisfies(obj.version, reqver, true)) { 218 obj.invalid = true 219 } 220 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 224 // the root object. 225 obj.extraneous = true 226 227 obj.path = obj.path || folder 228 obj.realPath = real 229 obj.link = link 230 if (parent && !obj.link) obj.parent = parent 231 realpathSeen[real] = obj 232 obj.depth = depth 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] 238 239 if (depth > opts.depth) { 240 obj.dependencies = {} 241 return cb(null, obj) 242 } 243 244 readInstalled_( path.resolve(folder, "node_modules/"+pkg) 245 , obj, pkg, obj.dependencies[pkg], depth + 1, opts 246 , cb ) 247 248 }, function (er, installedData) { 249 if (er) return cb(er) 250 installedData.forEach(function (dep) { 251 obj.dependencies[dep.realName] = dep 252 }) 253 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] 260 } 261 }) 262 } 263 return cb(null, obj) 264 }) 265 } 266} 267 268// starting from a root object, call findUnmet on each layer of children 269var riSeen = [] 270function resolveInheritance (obj, opts) { 271 if (typeof obj !== "object") return 272 if (riSeen.indexOf(obj) !== -1) return 273 riSeen.push(obj) 274 if (typeof obj.dependencies !== "object") { 275 obj.dependencies = {} 276 } 277 Object.keys(obj.dependencies).forEach(function (dep) { 278 findUnmet(obj.dependencies[dep], opts) 279 }) 280 Object.keys(obj.dependencies).forEach(function (dep) { 281 if (typeof obj.dependencies[dep] === "object") { 282 resolveInheritance(obj.dependencies[dep], opts) 283 } else { 284 debug("unmet dep! %s %s@%s", obj.name, dep, obj.dependencies[dep]) 285 } 286 }) 287 findUnmet(obj, opts) 288} 289 290// find unmet deps by walking up the tree object. 291// No I/O 292function 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 || {} 298 299 debug(deps) 300 Object.keys(deps) 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) && 308 found && 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 ) 315 found.invalid = true 316 } 317 if (found) { 318 deps[d] = found 319 } 320 }) 321 322 var peerDeps = obj.peerDependencies = obj.peerDependencies || {} 323 Object.keys(peerDeps).forEach(function (d) { 324 var dependency 325 326 if (!obj.parent) { 327 dependency = obj.dependencies[d] 328 329 // read it as a missing dep 330 if (!dependency) { 331 obj.dependencies[d] = peerDeps[d] 332 } 333 } else { 334 var r = obj.parent 335 while (r && !dependency) { 336 dependency = r.dependencies && r.dependencies[d] 337 r = r.link ? null : r.parent 338 } 339 } 340 341 if (!dependency) { 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 346 } 347 }) 348 349 return obj 350} 351 352function 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 357 358 obj.extraneous = false 359 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] 364 }) 365 } 366 367 if (obj.peerDependencies) { 368 Object.keys(obj.peerDependencies).forEach(function (k) { 369 deps[k] = obj.peerDependencies[k] 370 }) 371 } 372 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) 378 } 379 }) 380} 381 382// Find the one that will actually be loaded by require() 383// so we can make sure it's valid etc. 384function findDep (obj, d) { 385 var r = obj 386 , found = null 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] 393 } 394 if (!found && r.realName === d) found = r 395 r = r.link ? null : r.parent 396 } 397 return found 398} 399 400function copy (obj) { 401 if (!obj || typeof obj !== 'object') return obj 402 if (Array.isArray(obj)) return obj.map(copy) 403 404 var o = {} 405 for (var i in obj) o[i] = copy(obj[i]) 406 return o 407} 408