UNPKG

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