1'use strict' 2var union = require('lodash.union') 3var without = require('lodash.without') 4var validate = require('aproba') 5var flattenTree = require('./flatten-tree.js') 6var isExtraneous = require('./is-extraneous.js') 7var validateAllPeerDeps = require('./deps.js').validateAllPeerDeps 8var packageId = require('../utils/package-id.js') 9var moduleName = require('../utils/module-name.js') 10var npm = require('../npm.js') 11 12// Return true if tree is a part of a cycle that: 13// A) Never connects to the top of the tree 14// B) Has not not had a point in the cycle arbitrarily declared its top 15// yet. 16function isDisconnectedCycle (tree, seen) { 17 if (!seen) seen = {} 18 if (tree.isTop || tree.cycleTop || tree.requiredBy.length === 0) { 19 return false 20 } else if (seen[tree.path]) { 21 return true 22 } else { 23 seen[tree.path] = true 24 return tree.requiredBy.every(function (node) { 25 return isDisconnectedCycle(node, Object.create(seen)) 26 }) 27 } 28} 29 30var mutateIntoLogicalTree = module.exports = function (tree) { 31 validate('O', arguments) 32 33 validateAllPeerDeps(tree, function (tree, pkgname, version) { 34 if (!tree.missingPeers) tree.missingPeers = {} 35 tree.missingPeers[pkgname] = version 36 }) 37 38 var flat = flattenTree(tree) 39 40 Object.keys(flat).sort().forEach(function (flatname) { 41 var node = flat[flatname] 42 if (!(node.requiredBy && node.requiredBy.length)) return 43 44 if (node.parent) { 45 // If a node is a cycle that never reaches the root of the logical 46 // tree then we'll leave it attached to the root, or else it 47 // would go missing. Further we'll note that this is the node in the 48 // cycle that we picked arbitrarily to be the one attached to the root. 49 // others will fall 50 if (isDisconnectedCycle(node)) { 51 node.cycleTop = true 52 // Nor do we want to disconnect non-cyclical extraneous modules from the tree. 53 } else if (node.requiredBy.length) { 54 // regular deps though, we do, as we're moving them into the capable 55 // hands of the modules that require them. 56 node.parent.children = without(node.parent.children, node) 57 } 58 } 59 60 node.requiredBy.forEach(function (parentNode) { 61 parentNode.children = union(parentNode.children, [node]) 62 }) 63 }) 64 return tree 65} 66 67module.exports.asReadInstalled = function (tree) { 68 mutateIntoLogicalTree(tree) 69 return translateTree(tree) 70} 71 72function translateTree (tree) { 73 return translateTree_(tree, new Set()) 74} 75 76function translateTree_ (tree, seen) { 77 var pkg = tree.package 78 if (seen.has(tree)) return pkg 79 seen.add(tree) 80 if (pkg._dependencies) return pkg 81 pkg._dependencies = pkg.dependencies 82 pkg.dependencies = {} 83 tree.children.forEach(function (child) { 84 const dep = pkg.dependencies[moduleName(child)] = translateTree_(child, seen) 85 if (child.fakeChild) { 86 dep.missing = true 87 dep.optional = child.package._optional 88 dep.requiredBy = child.package._spec 89 } 90 }) 91 92 function markMissing (name, requiredBy) { 93 if (pkg.dependencies[name]) { 94 if (pkg.dependencies[name].missing) return 95 pkg.dependencies[name].invalid = true 96 pkg.dependencies[name].realName = name 97 pkg.dependencies[name].extraneous = false 98 } else { 99 pkg.dependencies[name] = { 100 requiredBy: requiredBy, 101 missing: true, 102 optional: !!pkg.optionalDependencies[name] 103 } 104 } 105 } 106 107 Object.keys(tree.missingDeps).forEach(function (name) { 108 markMissing(name, tree.missingDeps[name]) 109 }) 110 Object.keys(tree.missingDevDeps).forEach(function (name) { 111 markMissing(name, tree.missingDevDeps[name]) 112 }) 113 var checkForMissingPeers = (tree.parent ? [] : [tree]).concat(tree.children) 114 checkForMissingPeers.filter(function (child) { 115 return child.missingPeers 116 }).forEach(function (child) { 117 Object.keys(child.missingPeers).forEach(function (pkgname) { 118 var version = child.missingPeers[pkgname] 119 var peerPkg = pkg.dependencies[pkgname] 120 if (!peerPkg) { 121 peerPkg = pkg.dependencies[pkgname] = { 122 _id: pkgname + '@' + version, 123 name: pkgname, 124 version: version 125 } 126 } 127 if (!peerPkg.peerMissing) peerPkg.peerMissing = [] 128 peerPkg.peerMissing.push({ 129 requiredBy: packageId(child), 130 requires: pkgname + '@' + version 131 }) 132 }) 133 }) 134 pkg.path = tree.path 135 136 pkg.error = tree.error 137 pkg.extraneous = !tree.isTop && (!tree.parent.isTop || !tree.parent.error) && !npm.config.get('global') && isExtraneous(tree) 138 if (tree.target && tree.parent && !tree.parent.target) pkg.link = tree.realpath 139 return pkg 140} 141