UNPKG

36.7 kBJavaScriptView Raw
1// npm install <pkg> <pkg> <pkg>
2//
3// See doc/install.md for more description
4
5// Managing contexts...
6// there's a lot of state associated with an "install" operation, including
7// packages that are already installed, parent packages, current shrinkwrap, and
8// so on. We maintain this state in a "context" object that gets passed around.
9// every time we dive into a deeper node_modules folder, the "family" list that
10// gets passed along uses the previous "family" list as its __proto__. Any
11// "resolved precise dependency" things that aren't already on this object get
12// added, and then that's passed to the next generation of installation.
13
14module.exports = install
15
16install.usage = "npm install"
17 + "\nnpm install <pkg>"
18 + "\nnpm install <pkg>@<tag>"
19 + "\nnpm install <pkg>@<version>"
20 + "\nnpm install <pkg>@<version range>"
21 + "\nnpm install <folder>"
22 + "\nnpm install <tarball file>"
23 + "\nnpm install <tarball url>"
24 + "\nnpm install <git:// url>"
25 + "\nnpm install <github username>/<github project>"
26 + "\n\nCan specify one or more: npm install ./foo.tgz bar@stable /some/folder"
27 + "\nIf no argument is supplied and ./npm-shrinkwrap.json is "
28 + "\npresent, installs dependencies specified in the shrinkwrap."
29 + "\nOtherwise, installs dependencies from ./package.json."
30
31install.completion = function (opts, cb) {
32 // install can complete to a folder with a package.json, or any package.
33 // if it has a slash, then it's gotta be a folder
34 // if it starts with https?://, then just give up, because it's a url
35 // for now, not yet implemented.
36 mapToRegistry("-/short", npm.config, function (er, uri, auth) {
37 if (er) return cb(er)
38
39 var options = { auth : auth }
40 npm.registry.get(uri, options, function (er, pkgs) {
41 if (er) return cb()
42 if (!opts.partialWord) return cb(null, pkgs)
43
44 var name = npa(opts.partialWord).name
45 pkgs = pkgs.filter(function (p) {
46 return p.indexOf(name) === 0
47 })
48
49 if (pkgs.length !== 1 && opts.partialWord === name) {
50 return cb(null, pkgs)
51 }
52
53 mapToRegistry(pkgs[0], npm.config, function (er, uri) {
54 if (er) return cb(er)
55
56 npm.registry.get(uri, options, function (er, d) {
57 if (er) return cb()
58 return cb(null, Object.keys(d["dist-tags"] || {})
59 .concat(Object.keys(d.versions || {}))
60 .map(function (t) {
61 return pkgs[0] + "@" + t
62 }))
63 })
64 })
65 })
66 })
67}
68
69var npm = require("./npm.js")
70 , semver = require("semver")
71 , readJson = require("read-package-json")
72 , readInstalled = require("read-installed")
73 , log = require("npmlog")
74 , path = require("path")
75 , fs = require("graceful-fs")
76 , writeFileAtomic = require("write-file-atomic")
77 , cache = require("./cache.js")
78 , asyncMap = require("slide").asyncMap
79 , chain = require("slide").chain
80 , url = require("url")
81 , mkdir = require("mkdirp")
82 , lifecycle = require("./utils/lifecycle.js")
83 , archy = require("archy")
84 , npmInstallChecks = require("npm-install-checks")
85 , sortedObject = require("sorted-object")
86 , mapToRegistry = require("./utils/map-to-registry.js")
87 , npa = require("npm-package-arg")
88 , inflight = require("inflight")
89 , locker = require("./utils/locker.js")
90 , lock = locker.lock
91 , unlock = locker.unlock
92
93function install (args, cb_) {
94 var hasArguments = !!args.length
95
96 function cb (er, installed) {
97 if (er) return cb_(er)
98
99 findPeerInvalid(where, function (er, problem) {
100 if (er) return cb_(er)
101
102 if (problem) {
103 var peerInvalidError = new Error("The package " + problem.name +
104 " does not satisfy its siblings' peerDependencies requirements!")
105 peerInvalidError.code = "EPEERINVALID"
106 peerInvalidError.packageName = problem.name
107 peerInvalidError.peersDepending = problem.peersDepending
108 return cb(peerInvalidError)
109 }
110
111 var tree = treeify(installed || [])
112 , pretty = prettify(tree, installed).trim()
113
114 if (pretty) console.log(pretty)
115 save(where, installed, tree, pretty, hasArguments, cb_)
116 })
117 }
118
119 // the /path/to/node_modules/..
120 var where = path.resolve(npm.dir, "..")
121
122 // internal api: install(where, what, cb)
123 if (arguments.length === 3) {
124 where = args
125 args = [].concat(cb_) // pass in [] to do default dep-install
126 cb_ = arguments[2]
127 log.verbose("install", "where, what", [where, args])
128 }
129
130 if (!npm.config.get("global")) {
131 args = args.filter(function (a) {
132 return path.resolve(a) !== where
133 })
134 }
135
136 mkdir(where, function (er) {
137 if (er) return cb(er)
138 // install dependencies locally by default,
139 // or install current folder globally
140 if (!args.length) {
141 var opt = { dev: npm.config.get("dev") || !npm.config.get("production") }
142
143 if (npm.config.get("global")) args = ["."]
144 else return readDependencies(null, where, opt, function (er, data) {
145 if (er) {
146 log.error("install", "Couldn't read dependencies")
147 return cb(er)
148 }
149 var deps = Object.keys(data.dependencies || {})
150 log.verbose("install", "where, deps", [where, deps])
151
152 // FIXME: Install peerDependencies as direct dependencies, but only at
153 // the top level. Should only last until peerDependencies are nerfed to
154 // no longer implicitly install themselves.
155 var peers = []
156 Object.keys(data.peerDependencies || {}).forEach(function (dep) {
157 if (!data.dependencies[dep]) {
158 log.verbose(
159 "install",
160 "peerDependency", dep, "wasn't going to be installed; adding"
161 )
162 peers.push(dep)
163 }
164 })
165 log.verbose("install", "where, peers", [where, peers])
166
167 var context = { family: {}
168 , ancestors: {}
169 , explicit: false
170 , parent: data
171 , root: true
172 , wrap: null }
173
174 if (data.name === path.basename(where) &&
175 path.basename(path.dirname(where)) === "node_modules") {
176 // Only include in ancestry if it can actually be required.
177 // Otherwise, it does not count.
178 context.family[data.name] =
179 context.ancestors[data.name] = data.version
180 }
181
182 installManyTop(deps.map(function (dep) {
183 var target = data.dependencies[dep]
184 return dep + "@" + target
185 }).concat(peers.map(function (dep) {
186 var target = data.peerDependencies[dep]
187 return dep + "@" + target
188 })), where, context, function(er, results) {
189 if (er || npm.config.get("production")) return cb(er, results)
190 lifecycle(data, "prepublish", where, function(er) {
191 return cb(er, results)
192 })
193 })
194 })
195 }
196
197 // initial "family" is the name:version of the root, if it's got
198 // a package.json file.
199 var jsonFile = path.resolve(where, "package.json")
200 readJson(jsonFile, log.warn, function (er, data) {
201 if (er
202 && er.code !== "ENOENT"
203 && er.code !== "ENOTDIR") return cb(er)
204 if (er) data = null
205 var context = { family: {}
206 , ancestors: {}
207 , explicit: true
208 , parent: data
209 , root: true
210 , wrap: null }
211 if (data && data.name === path.basename(where) &&
212 path.basename(path.dirname(where)) === "node_modules") {
213 context.family[data.name] = context.ancestors[data.name] = data.version
214 }
215 var fn = npm.config.get("global") ? installMany : installManyTop
216 fn(args, where, context, cb)
217 })
218 })
219}
220
221function findPeerInvalid (where, cb) {
222 readInstalled(where, { log: log.warn, dev: true }, function (er, data) {
223 if (er) return cb(er)
224
225 cb(null, findPeerInvalid_(data.dependencies, []))
226 })
227}
228
229function findPeerInvalid_ (packageMap, fpiList) {
230 if (fpiList.indexOf(packageMap) !== -1)
231 return undefined
232
233 fpiList.push(packageMap)
234
235 for (var packageName in packageMap) {
236 var pkg = packageMap[packageName]
237
238 if (pkg.peerInvalid) {
239 var peersDepending = {}
240 for (var peerName in packageMap) {
241 var peer = packageMap[peerName]
242 if (peer.peerDependencies && peer.peerDependencies[packageName]) {
243 peersDepending[peer.name + "@" + peer.version] =
244 peer.peerDependencies[packageName]
245 }
246 }
247 return { name: pkg.name, peersDepending: peersDepending }
248 }
249
250 if (pkg.dependencies) {
251 var invalid = findPeerInvalid_(pkg.dependencies, fpiList)
252 if (invalid)
253 return invalid
254 }
255 }
256
257 return null
258}
259
260// reads dependencies for the package at "where". There are several cases,
261// depending on our current state and the package's configuration:
262//
263// 1. If "context" is specified, then we examine the context to see if there's a
264// shrinkwrap there. In that case, dependencies are read from the shrinkwrap.
265// 2. Otherwise, if an npm-shrinkwrap.json file is present, dependencies are
266// read from there.
267// 3. Otherwise, dependencies come from package.json.
268//
269// Regardless of which case we fall into, "cb" is invoked with a first argument
270// describing the full package (as though readJson had been used) but with
271// "dependencies" read as described above. The second argument to "cb" is the
272// shrinkwrap to use in processing this package's dependencies, which may be
273// "wrap" (in case 1) or a new shrinkwrap (in case 2).
274function readDependencies (context, where, opts, cb) {
275 var wrap = context ? context.wrap : null
276
277 readJson( path.resolve(where, "package.json")
278 , log.warn
279 , function (er, data) {
280 if (er && er.code === "ENOENT") er.code = "ENOPACKAGEJSON"
281 if (er) return cb(er)
282
283 if (opts && opts.dev) {
284 if (!data.dependencies) data.dependencies = {}
285 Object.keys(data.devDependencies || {}).forEach(function (k) {
286 if (data.dependencies[k]) {
287 log.warn("package.json", "Dependency '%s' exists in both dependencies " +
288 "and devDependencies, using '%s@%s' from dependencies",
289 k, k, data.dependencies[k])
290 } else {
291 data.dependencies[k] = data.devDependencies[k]
292 }
293 })
294 }
295
296 if (!npm.config.get("optional") && data.optionalDependencies) {
297 Object.keys(data.optionalDependencies).forEach(function (d) {
298 delete data.dependencies[d]
299 })
300 }
301
302 // User has opted out of shrinkwraps entirely
303 if (npm.config.get("shrinkwrap") === false)
304 return cb(null, data, null)
305
306 if (wrap) {
307 log.verbose("readDependencies: using existing wrap", [where, wrap])
308 var rv = {}
309 Object.keys(data).forEach(function (key) {
310 rv[key] = data[key]
311 })
312 rv.dependencies = {}
313 Object.keys(wrap).forEach(function (key) {
314 log.verbose("from wrap", [key, wrap[key]])
315 rv.dependencies[key] = readWrap(wrap[key])
316 })
317 log.verbose("readDependencies returned deps", rv.dependencies)
318 return cb(null, rv, wrap)
319 }
320
321 var wrapfile = path.resolve(where, "npm-shrinkwrap.json")
322
323 fs.readFile(wrapfile, "utf8", function (er, wrapjson) {
324 if (er) return cb(null, data, null)
325
326 log.verbose("readDependencies", "npm-shrinkwrap.json is overriding dependencies")
327 var newwrap
328 try {
329 newwrap = JSON.parse(wrapjson)
330 } catch (ex) {
331 return cb(ex)
332 }
333
334 log.info("shrinkwrap", "file %j", wrapfile)
335 var rv = {}
336 Object.keys(data).forEach(function (key) {
337 rv[key] = data[key]
338 })
339 rv.dependencies = {}
340 Object.keys(newwrap.dependencies || {}).forEach(function (key) {
341 rv.dependencies[key] = readWrap(newwrap.dependencies[key])
342 })
343
344 // fold in devDependencies if not already present, at top level
345 if (opts && opts.dev) {
346 Object.keys(data.devDependencies || {}).forEach(function (k) {
347 rv.dependencies[k] = rv.dependencies[k] || data.devDependencies[k]
348 })
349 }
350
351 log.verbose("readDependencies returned deps", rv.dependencies)
352 return cb(null, rv, newwrap.dependencies)
353 })
354 })
355}
356
357function readWrap (w) {
358 return (w.resolved) ? w.resolved
359 : (w.from && url.parse(w.from).protocol) ? w.from
360 : w.version
361}
362
363// if the -S|--save option is specified, then write installed packages
364// as dependencies to a package.json file.
365// This is experimental.
366function save (where, installed, tree, pretty, hasArguments, cb) {
367 if (!hasArguments ||
368 !npm.config.get("save") &&
369 !npm.config.get("save-dev") &&
370 !npm.config.get("save-optional") ||
371 npm.config.get("global")) {
372 return cb(null, installed, tree, pretty)
373 }
374
375 var saveBundle = npm.config.get("save-bundle")
376 var savePrefix = npm.config.get("save-prefix")
377
378 // each item in the tree is a top-level thing that should be saved
379 // to the package.json file.
380 // The relevant tree shape is { <folder>: {what:<pkg>} }
381 var saveTarget = path.resolve(where, "package.json")
382
383 asyncMap(Object.keys(tree), function (k, cb) {
384 // if "what" was a url, then save that instead.
385 var t = tree[k]
386 , u = url.parse(t.from)
387 , a = npa(t.what)
388 , w = [a.name, a.spec]
389
390
391 fs.stat(t.from, function (er){
392 if (!er) {
393 w[1] = "file:" + t.from
394 } else if (u && u.protocol) {
395 w[1] = t.from
396 }
397 cb(null, [w])
398 })
399 }
400 , function (er, arr) {
401 var things = arr.reduce(function (set, k) {
402 var rangeDescriptor = semver.valid(k[1], true) &&
403 semver.gte(k[1], "0.1.0", true) &&
404 !npm.config.get("save-exact")
405 ? savePrefix : ""
406 set[k[0]] = rangeDescriptor + k[1]
407 return set
408 }, {})
409
410
411 // don't use readJson, because we don't want to do all the other
412 // tricky npm-specific stuff that's in there.
413 fs.readFile(saveTarget, function (er, data) {
414 // ignore errors here, just don't save it.
415 try {
416 data = JSON.parse(data.toString("utf8"))
417 } catch (ex) {
418 er = ex
419 }
420
421 if (er) {
422 return cb(null, installed, tree, pretty)
423 }
424
425 var deps = npm.config.get("save-optional") ? "optionalDependencies"
426 : npm.config.get("save-dev") ? "devDependencies"
427 : "dependencies"
428
429 if (saveBundle) {
430 var bundle = data.bundleDependencies || data.bundledDependencies
431 delete data.bundledDependencies
432 if (!Array.isArray(bundle)) bundle = []
433 data.bundleDependencies = bundle.sort()
434 }
435
436 log.verbose("saving", things)
437 data[deps] = data[deps] || {}
438 Object.keys(things).forEach(function (t) {
439 data[deps][t] = things[t]
440 if (saveBundle) {
441 var i = bundle.indexOf(t)
442 if (i === -1) bundle.push(t)
443 data.bundleDependencies = bundle.sort()
444 }
445 })
446
447 data[deps] = sortedObject(data[deps])
448
449 data = JSON.stringify(data, null, 2) + "\n"
450 writeFileAtomic(saveTarget, data, function (er) {
451 cb(er, installed, tree, pretty)
452 })
453 })
454 })
455}
456
457
458// Outputting *all* the installed modules is a bit confusing,
459// because the length of the path does not make it clear
460// that the submodules are not immediately require()able.
461// TODO: Show the complete tree, ls-style, but only if --long is provided
462function prettify (tree, installed) {
463 function red (set, kv) {
464 set[kv[0]] = kv[1]
465 return set
466 }
467
468 if (npm.config.get("json")) {
469 tree = Object.keys(tree).map(function (p) {
470 if (!tree[p]) return null
471 var what = npa(tree[p].what)
472 , name = what.name
473 , version = what.spec
474 , o = { name: name, version: version, from: tree[p].from }
475 o.dependencies = tree[p].children.map(function P (dep) {
476 var what = npa(dep.what)
477 , name = what.name
478 , version = what.spec
479 , o = { version: version, from: dep.from }
480 o.dependencies = dep.children.map(P).reduce(red, {})
481 return [name, o]
482 }).reduce(red, {})
483 return o
484 })
485
486 return JSON.stringify(tree, null, 2)
487 }
488 if (npm.config.get("parseable")) return parseable(installed)
489
490 return Object.keys(tree).map(function (p) {
491 return archy({ label: tree[p].what + " " + p
492 , nodes: (tree[p].children || []).map(function P (c) {
493 if (npm.config.get("long")) {
494 return { label: c.what, nodes: c.children.map(P) }
495 }
496 var g = c.children.map(function (g) {
497 return g.what
498 }).join(", ")
499 if (g) g = " (" + g + ")"
500 return c.what + g
501 })
502 }, "", { unicode: npm.config.get("unicode") })
503 }).join("\n")
504}
505
506function parseable (installed) {
507 var long = npm.config.get("long")
508 , cwd = process.cwd()
509 return installed.map(function (item) {
510 return path.resolve(cwd, item[1]) +
511 ( long ? ":" + item[0] : "" )
512 }).join("\n")
513}
514
515function treeify (installed) {
516 // each item is [what, where, parent, parentDir]
517 // If no parent, then report it.
518 // otherwise, tack it into the parent's children list.
519 // If the parent isn't a top-level then ignore it.
520 var whatWhere = installed.reduce(function (l, r) {
521 var parentDir = r[3]
522 , parent = r[2]
523 , where = r[1]
524 , what = r[0]
525 , from = r[4]
526 l[where] = { parentDir: parentDir
527 , parent: parent
528 , children: []
529 , where: where
530 , what: what
531 , from: from }
532 return l
533 }, {})
534
535 // log.warn("install", whatWhere, "whatWhere")
536 return Object.keys(whatWhere).reduce(function (l, r) {
537 var ww = whatWhere[r]
538 //log.warn("r, ww", [r, ww])
539 if (!ww.parent) {
540 l[r] = ww
541 } else {
542 var p = whatWhere[ww.parentDir]
543 if (p) p.children.push(ww)
544 else l[r] = ww
545 }
546 return l
547 }, {})
548}
549
550
551// just like installMany, but also add the existing packages in
552// where/node_modules to the family object.
553function installManyTop (what, where, context, cb_) {
554 function cb (er, d) {
555 if (context.explicit || er) return cb_(er, d)
556 // since this wasn't an explicit install, let's build the top
557 // folder, so that `npm install` also runs the lifecycle scripts.
558 npm.commands.build([where], false, true, function (er) {
559 return cb_(er, d)
560 })
561 }
562
563 if (context.explicit) return next()
564
565 readJson(path.join(where, "package.json"), log.warn, function (er, data) {
566 if (er) return next(er)
567 lifecycle(data, "preinstall", where, next)
568 })
569
570 function next (er) {
571 if (er) return cb(er)
572 installManyTop_(what, where, context, cb)
573 }
574}
575
576function installManyTop_ (what, where, context, cb) {
577 var nm = path.resolve(where, "node_modules")
578
579 fs.readdir(nm, function (er, pkgs) {
580 if (er) return installMany(what, where, context, cb)
581
582 var scopes = [], unscoped = []
583 pkgs.filter(function (p) {
584 return !p.match(/^[\._-]/)
585 }).forEach(function (p) {
586 // @names deserve deeper investigation
587 if (p[0] === "@") {
588 scopes.push(p)
589 }
590 else {
591 unscoped.push(p)
592 }
593 })
594
595 maybeScoped(scopes, nm, function (er, scoped) {
596 if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
597 // recombine unscoped with @scope/package packages
598 asyncMap(unscoped.concat(scoped).map(function (p) {
599 return path.resolve(nm, p, "package.json")
600 }), function (jsonfile, cb) {
601 readJson(jsonfile, log.warn, function (er, data) {
602 if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
603 if (er) return cb(null, [])
604 cb(null, [[data.name, data.version]])
605 })
606 }, function (er, packages) {
607 // if there's nothing in node_modules, then don't freak out.
608 if (er) packages = []
609 // add all the existing packages to the family list.
610 // however, do not add to the ancestors list.
611 packages.forEach(function (p) {
612 context.family[p[0]] = p[1]
613 })
614 installMany(what, where, context, cb)
615 })
616 })
617 })
618}
619
620function maybeScoped (scopes, where, cb) {
621 // find packages in scopes
622 asyncMap(scopes, function (scope, cb) {
623 fs.readdir(path.resolve(where, scope), function (er, scoped) {
624 if (er) return cb(er)
625 var paths = scoped.map(function (p) {
626 return path.join(scope, p)
627 })
628 cb(null, paths)
629 })
630 }, cb)
631}
632
633function installMany (what, where, context, cb) {
634 // readDependencies takes care of figuring out whether the list of
635 // dependencies we'll iterate below comes from an existing shrinkwrap from a
636 // parent level, a new shrinkwrap at this level, or package.json at this
637 // level, as well as which shrinkwrap (if any) our dependencies should use.
638 var opt = { dev: npm.config.get("dev") }
639 readDependencies(context, where, opt, function (er, data, wrap) {
640 if (er) data = {}
641
642 var parent = data
643
644 var d = data.dependencies || {}
645
646 // if we're explicitly installing "what" into "where", then the shrinkwrap
647 // for "where" doesn't apply. This would be the case if someone were adding
648 // a new package to a shrinkwrapped package. (data.dependencies will not be
649 // used here except to indicate what packages are already present, so
650 // there's no harm in using that.)
651 if (context.explicit) wrap = null
652
653 // what is a list of things.
654 // resolve each one.
655 asyncMap( what
656 , targetResolver(where, context, d)
657 , function (er, targets) {
658
659 if (er) return cb(er)
660
661 // each target will be a data object corresponding
662 // to a package, folder, or whatever that is in the cache now.
663 var newPrev = Object.create(context.family)
664 , newAnc = Object.create(context.ancestors)
665
666 if (!context.root) {
667 newAnc[data.name] = data.version
668 }
669 targets.forEach(function (t) {
670 newPrev[t.name] = t.version
671 })
672 log.silly("install resolved", targets)
673 targets.filter(function (t) { return t }).forEach(function (t) {
674 log.info("install", "%s into %s", t._id, where)
675 })
676 asyncMap(targets, function (target, cb) {
677 log.info("installOne", target._id)
678 var wrapData = wrap ? wrap[target.name] : null
679 var newWrap = wrapData && wrapData.dependencies
680 ? wrap[target.name].dependencies || {}
681 : null
682 var newContext = { family: newPrev
683 , ancestors: newAnc
684 , parent: parent
685 , explicit: false
686 , wrap: newWrap }
687 installOne(target, where, newContext, cb)
688 }, cb)
689 })
690 })
691}
692
693function targetResolver (where, context, deps) {
694 var alreadyInstalledManually = []
695 , resolveLeft = 0
696 , nm = path.resolve(where, "node_modules")
697 , parent = context.parent
698 , wrap = context.wrap
699
700 if (!context.explicit) readdir(nm)
701
702 function readdir(name) {
703 resolveLeft++
704 fs.readdir(name, function (er, inst) {
705 if (er) return resolveLeft--
706
707 // don't even mess with non-package looking things
708 inst = inst.filter(function (p) {
709 if (!p.match(/^[@\._-]/)) return true
710 // scoped packages
711 readdir(path.join(name, p))
712 })
713
714 asyncMap(inst, function (pkg, cb) {
715 readJson(path.resolve(name, pkg, "package.json"), log.warn, function (er, d) {
716 if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
717 // error means it's not a package, most likely.
718 if (er) return cb(null, [])
719
720 // if it's a bundled dep, then assume that anything there is valid.
721 // otherwise, make sure that it's a semver match with what we want.
722 var bd = parent.bundleDependencies
723 if (bd && bd.indexOf(d.name) !== -1 ||
724 semver.satisfies(d.version, deps[d.name] || "*", true) ||
725 deps[d.name] === d._resolved) {
726 return cb(null, d.name)
727 }
728
729 // see if the package had been previously linked
730 fs.lstat(path.resolve(nm, pkg), function(err, s) {
731 if (err) return cb(null, [])
732 if (s.isSymbolicLink()) {
733 return cb(null, d.name)
734 }
735
736 // something is there, but it's not satisfactory. Clobber it.
737 return cb(null, [])
738 })
739 })
740 }, function (er, inst) {
741 // this is the list of things that are valid and should be ignored.
742 alreadyInstalledManually = alreadyInstalledManually.concat(inst)
743 resolveLeft--
744 })
745 })
746 }
747
748 var to = 0
749 return function resolver (what, cb) {
750 if (resolveLeft) return setTimeout(function () {
751 resolver(what, cb)
752 }, to++)
753
754 // now we know what's been installed here manually,
755 // or tampered with in some way that npm doesn't want to overwrite.
756 if (alreadyInstalledManually.indexOf(npa(what).name) !== -1) {
757 log.verbose("already installed", "skipping %s %s", what, where)
758 return cb(null, [])
759 }
760
761 // check for a version installed higher in the tree.
762 // If installing from a shrinkwrap, it must match exactly.
763 if (context.family[what]) {
764 if (wrap && wrap[what].version === context.family[what]) {
765 log.verbose("shrinkwrap", "use existing", what)
766 return cb(null, [])
767 }
768 }
769
770 // if it's identical to its parent, then it's probably someone
771 // doing `npm install foo` inside of the foo project. Print
772 // a warning, and skip it.
773 if (parent && parent.name === what && !npm.config.get("force")) {
774 log.warn("install", "Refusing to install %s as a dependency of itself"
775 , what)
776 return cb(null, [])
777 }
778
779 if (wrap) {
780 var name = npa(what).name
781 if (wrap[name]) {
782 var wrapTarget = readWrap(wrap[name])
783 what = name + "@" + wrapTarget
784 } else {
785 log.verbose("shrinkwrap", "skipping %s (not in shrinkwrap)", what)
786 }
787 } else if (deps[what]) {
788 what = what + "@" + deps[what]
789 }
790
791 // This is where we actually fetch the package, if it's not already
792 // in the cache.
793 // If it's a git repo, then we want to install it, even if the parent
794 // already has a matching copy.
795 // If it's not a git repo, and the parent already has that pkg, then
796 // we can skip installing it again.
797 var pkgroot = path.resolve(npm.prefix, (parent && parent._from) || "")
798 cache.add(what, null, pkgroot, false, function (er, data) {
799 if (er && parent && parent.optionalDependencies &&
800 parent.optionalDependencies.hasOwnProperty(npa(what).name)) {
801 log.warn("optional dep failed, continuing", what)
802 log.verbose("optional dep failed, continuing", [what, er])
803 return cb(null, [])
804 }
805
806 var isGit = npa(what).type === "git"
807
808 if (!er &&
809 data &&
810 !context.explicit &&
811 context.family[data.name] === data.version &&
812 !npm.config.get("force") &&
813 !isGit) {
814 log.info("already installed", data.name + "@" + data.version)
815 return cb(null, [])
816 }
817
818
819 if (data && !data._from) data._from = what
820 if (er && parent && parent.name) er.parent = parent.name
821 return cb(er, data || [])
822 })
823 }
824}
825
826// we've already decided to install this. if anything's in the way,
827// then uninstall it first.
828function installOne (target, where, context, cb) {
829 // the --link flag makes this a "link" command if it's at the
830 // the top level.
831 if (where === npm.prefix && npm.config.get("link")
832 && !npm.config.get("global")) {
833 return localLink(target, where, context, cb)
834 }
835 installOne_(target, where, context, function (er, installedWhat) {
836
837 // check if this one is optional to its parent.
838 if (er && context.parent && context.parent.optionalDependencies &&
839 context.parent.optionalDependencies.hasOwnProperty(target.name)) {
840 log.warn("optional dep failed, continuing", target._id)
841 log.verbose("optional dep failed, continuing", [target._id, er])
842 er = null
843 }
844
845 cb(er, installedWhat)
846 })
847
848}
849
850function localLink (target, where, context, cb) {
851 log.verbose("localLink", target._id)
852 var jsonFile = path.resolve( npm.globalDir, target.name
853 , "package.json" )
854 , parent = context.parent
855
856 readJson(jsonFile, log.warn, function (er, data) {
857 function thenLink () {
858 npm.commands.link([target.name], function (er, d) {
859 log.silly("localLink", "back from link", [er, d])
860 cb(er, [resultList(target, where, parent && parent._id)])
861 })
862 }
863
864 if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
865 if (er || data._id === target._id) {
866 if (er) {
867 install( path.resolve(npm.globalDir, "..")
868 , target._id
869 , function (er) {
870 if (er) return cb(er, [])
871 thenLink()
872 })
873 } else thenLink()
874 } else {
875 log.verbose("localLink", "install locally (no link)", target._id)
876 installOne_(target, where, context, cb)
877 }
878 })
879}
880
881function resultList (target, where, parentId) {
882 var nm = path.resolve(where, "node_modules")
883 , targetFolder = path.resolve(nm, target.name)
884 , prettyWhere = where
885
886 if (!npm.config.get("global")) {
887 prettyWhere = path.relative(process.cwd(), where)
888 }
889
890 if (prettyWhere === ".") prettyWhere = null
891
892 if (!npm.config.get("global")) {
893 // print out the folder relative to where we are right now.
894 targetFolder = path.relative(process.cwd(), targetFolder)
895 }
896
897 return [ target._id
898 , targetFolder
899 , prettyWhere && parentId
900 , parentId && prettyWhere
901 , target._from ]
902}
903
904var installed = Object.create(null)
905
906function installOne_ (target, where, context, cb_) {
907 var nm = path.resolve(where, "node_modules")
908 , targetFolder = path.resolve(nm, target.name)
909 , prettyWhere = path.relative(process.cwd(), where)
910 , parent = context.parent
911
912 if (prettyWhere === ".") prettyWhere = null
913
914 cb_ = inflight(target.name + ":" + where, cb_)
915 if (!cb_) return log.verbose(
916 "installOne",
917 "of", target.name,
918 "to", where,
919 "already in flight; waiting"
920 )
921 else log.verbose(
922 "installOne",
923 "of", target.name,
924 "to", where,
925 "not in flight; installing"
926 )
927
928 function cb(er, data) {
929 unlock(nm, target.name, function () { cb_(er, data) })
930 }
931
932 lock(nm, target.name, function (er) {
933 if (er) return cb(er)
934
935 if (targetFolder in installed) {
936 log.error("install", "trying to install", target.version, "to", targetFolder)
937 log.error("install", "but already installed versions", installed[targetFolder])
938 installed[targetFolder].push(target.version)
939 }
940 else {
941 installed[targetFolder] = [target.version]
942 }
943
944 var force = npm.config.get("force")
945 , nodeVersion = npm.config.get("node-version")
946 , strict = npm.config.get("engine-strict")
947 , c = npmInstallChecks
948
949 chain(
950 [ [c.checkEngine, target, npm.version, nodeVersion, force, strict]
951 , [c.checkPlatform, target, force]
952 , [c.checkCycle, target, context.ancestors]
953 , [c.checkGit, targetFolder]
954 , [write, target, targetFolder, context] ]
955 , function (er, d) {
956 if (er) return cb(er)
957
958 d.push(resultList(target, where, parent && parent._id))
959 cb(er, d)
960 }
961 )
962 })
963}
964
965function write (target, targetFolder, context, cb_) {
966 var up = npm.config.get("unsafe-perm")
967 , user = up ? null : npm.config.get("user")
968 , group = up ? null : npm.config.get("group")
969 , family = context.family
970
971 function cb (er, data) {
972 // cache.unpack returns the data object, and all we care about
973 // is the list of installed packages from that last thing.
974 if (!er) return cb_(er, data)
975
976 if (npm.config.get("rollback") === false) return cb_(er)
977 npm.rollbacks.push(targetFolder)
978 cb_(er, data)
979 }
980
981 var bundled = []
982
983 log.silly("install write", "writing", target.name, target.version, "to", targetFolder)
984 chain(
985 [ [ cache.unpack, target.name, target.version, targetFolder
986 , null, null, user, group ]
987 , [ fs, "writeFile"
988 , path.resolve(targetFolder, "package.json")
989 , JSON.stringify(target, null, 2) + "\n" ]
990 , [ lifecycle, target, "preinstall", targetFolder ]
991 , function (cb) {
992 if (!target.bundleDependencies) return cb()
993
994 var bd = path.resolve(targetFolder, "node_modules")
995 fs.readdir(bd, function (er, b) {
996 // nothing bundled, maybe
997 if (er) return cb()
998 bundled = b || []
999 cb()
1000 })
1001 } ]
1002
1003 // nest the chain so that we can throw away the results returned
1004 // up until this point, since we really don't care about it.
1005 , function X (er) {
1006 if (er) return cb(er)
1007
1008 // before continuing to installing dependencies, check for a shrinkwrap.
1009 var opt = { dev: npm.config.get("dev") }
1010 readDependencies(context, targetFolder, opt, function (er, data, wrap) {
1011 var deps = prepareForInstallMany(data, "dependencies", bundled, wrap,
1012 family)
1013 var depsTargetFolder = targetFolder
1014 var depsContext = { family: family
1015 , ancestors: context.ancestors
1016 , parent: target
1017 , explicit: false
1018 , wrap: wrap }
1019
1020 var actions =
1021 [ [ installManyAndBuild, deps, depsTargetFolder, depsContext ] ]
1022
1023 // FIXME: This is an accident waiting to happen!
1024 //
1025 // 1. If multiple children at the same level of the tree share a
1026 // peerDependency that's not in the parent's dependencies, because
1027 // the peerDeps don't get added to the family, they will keep
1028 // getting reinstalled (worked around by inflighting installOne).
1029 // 2. The installer can't safely build at the parent level because
1030 // that's already being done by the parent's installAndBuild. This
1031 // runs the risk of the peerDependency never getting built.
1032 //
1033 // The fix: Don't install peerDependencies; require them to be
1034 // included as explicit dependencies / devDependencies, and warn
1035 // or error when they're missing. See #5080 for more arguments in
1036 // favor of killing implicit peerDependency installs with fire.
1037 var peerDeps = prepareForInstallMany(data, "peerDependencies", bundled,
1038 wrap, family)
1039 var pdTargetFolder = path.resolve(targetFolder, "..", "..")
1040 var pdContext = context
1041 if (peerDeps.length > 0) {
1042 actions.push(
1043 [ installMany, peerDeps, pdTargetFolder, pdContext ]
1044 )
1045 }
1046
1047 chain(actions, cb)
1048 })
1049 })
1050}
1051
1052function installManyAndBuild (deps, targetFolder, context, cb) {
1053 installMany(deps, targetFolder, context, function (er, d) {
1054 log.verbose("about to build", targetFolder)
1055 if (er) return cb(er)
1056 npm.commands.build( [targetFolder]
1057 , npm.config.get("global")
1058 , true
1059 , function (er) { return cb(er, d) })
1060 })
1061}
1062
1063function prepareForInstallMany (packageData, depsKey, bundled, wrap, family) {
1064 var deps = Object.keys(packageData[depsKey] || {})
1065
1066 // don't install bundleDependencies, unless they're missing.
1067 if (packageData.bundleDependencies) {
1068 deps = deps.filter(function (d) {
1069 return packageData.bundleDependencies.indexOf(d) === -1 ||
1070 bundled.indexOf(d) === -1
1071 })
1072 }
1073
1074 return deps.filter(function (d) {
1075 // prefer to not install things that are satisfied by
1076 // something in the "family" list, unless we're installing
1077 // from a shrinkwrap.
1078 if (wrap) return wrap
1079 if (semver.validRange(family[d], true))
1080 return !semver.satisfies(family[d], packageData[depsKey][d], true)
1081 return true
1082 }).map(function (d) {
1083 var v = packageData[depsKey][d]
1084 var t = d + "@" + v
1085 log.silly("prepareForInstallMany", "adding", t, "from", packageData.name, depsKey)
1086 return t
1087 })
1088}