aboutsummaryrefslogtreecommitdiff
path: root/deps/node/deps/npm/lib/install/mutate-into-logical-tree.js
blob: 885149450f1f1ae37ee985bfea7efb6d0c43583b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
'use strict'
var union = require('lodash.union')
var without = require('lodash.without')
var validate = require('aproba')
var flattenTree = require('./flatten-tree.js')
var isExtraneous = require('./is-extraneous.js')
var validateAllPeerDeps = require('./deps.js').validateAllPeerDeps
var packageId = require('../utils/package-id.js')
var moduleName = require('../utils/module-name.js')
var npm = require('../npm.js')

// Return true if tree is a part of a cycle that:
//   A) Never connects to the top of the tree
//   B) Has not not had a point in the cycle arbitrarily declared its top
//      yet.
function isDisconnectedCycle (tree, seen) {
  if (!seen) seen = {}
  if (tree.isTop || tree.cycleTop || tree.requiredBy.length === 0) {
    return false
  } else if (seen[tree.path]) {
    return true
  } else {
    seen[tree.path] = true
    return tree.requiredBy.every(function (node) {
      return isDisconnectedCycle(node, Object.create(seen))
    })
  }
}

var mutateIntoLogicalTree = module.exports = function (tree) {
  validate('O', arguments)

  validateAllPeerDeps(tree, function (tree, pkgname, version) {
    if (!tree.missingPeers) tree.missingPeers = {}
    tree.missingPeers[pkgname] = version
  })

  var flat = flattenTree(tree)

  Object.keys(flat).sort().forEach(function (flatname) {
    var node = flat[flatname]
    if (!(node.requiredBy && node.requiredBy.length)) return

    if (node.parent) {
      // If a node is a cycle that never reaches the root of the logical
      // tree then we'll leave it attached to the root, or else it
      // would go missing. Further we'll note that this is the node in the
      // cycle that we picked arbitrarily to be the one attached to the root.
      // others will fall
      if (isDisconnectedCycle(node)) {
        node.cycleTop = true
      // Nor do we want to disconnect non-cyclical extraneous modules from the tree.
      } else if (node.requiredBy.length) {
        // regular deps though, we do, as we're moving them into the capable
        // hands of the modules that require them.
        node.parent.children = without(node.parent.children, node)
      }
    }

    node.requiredBy.forEach(function (parentNode) {
      parentNode.children = union(parentNode.children, [node])
    })
  })
  return tree
}

module.exports.asReadInstalled = function (tree) {
  mutateIntoLogicalTree(tree)
  return translateTree(tree)
}

function translateTree (tree) {
  return translateTree_(tree, new Set())
}

function translateTree_ (tree, seen) {
  var pkg = tree.package
  if (seen.has(tree)) return pkg
  seen.add(tree)
  if (pkg._dependencies) return pkg
  pkg._dependencies = pkg.dependencies
  pkg.dependencies = {}
  tree.children.forEach(function (child) {
    const dep = pkg.dependencies[moduleName(child)] = translateTree_(child, seen)
    if (child.fakeChild) {
      dep.missing = true
      dep.optional = child.package._optional
      dep.requiredBy = child.package._spec
    }
  })

  function markMissing (name, requiredBy) {
    if (pkg.dependencies[name]) {
      if (pkg.dependencies[name].missing) return
      pkg.dependencies[name].invalid = true
      pkg.dependencies[name].realName = name
      pkg.dependencies[name].extraneous = false
    } else {
      pkg.dependencies[name] = {
        requiredBy: requiredBy,
        missing: true,
        optional: !!pkg.optionalDependencies[name]
      }
    }
  }

  Object.keys(tree.missingDeps).forEach(function (name) {
    markMissing(name, tree.missingDeps[name])
  })
  Object.keys(tree.missingDevDeps).forEach(function (name) {
    markMissing(name, tree.missingDevDeps[name])
  })
  var checkForMissingPeers = (tree.parent ? [] : [tree]).concat(tree.children)
  checkForMissingPeers.filter(function (child) {
    return child.missingPeers
  }).forEach(function (child) {
    Object.keys(child.missingPeers).forEach(function (pkgname) {
      var version = child.missingPeers[pkgname]
      var peerPkg = pkg.dependencies[pkgname]
      if (!peerPkg) {
        peerPkg = pkg.dependencies[pkgname] = {
          _id: pkgname + '@' + version,
          name: pkgname,
          version: version
        }
      }
      if (!peerPkg.peerMissing) peerPkg.peerMissing = []
      peerPkg.peerMissing.push({
        requiredBy: packageId(child),
        requires: pkgname + '@' + version
      })
    })
  })
  pkg.path = tree.path

  pkg.error = tree.error
  pkg.extraneous = !tree.isTop && (!tree.parent.isTop || !tree.parent.error) && !npm.config.get('global') && isExtraneous(tree)
  if (tree.target && tree.parent && !tree.parent.target) pkg.link = tree.realpath
  return pkg
}