• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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