马宇豪
2024-07-16 f591c27b57e2418c9495bc02ae8cfff84d35bc18
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
const { depth } = require('treeverse')
 
const calcDepFlags = (tree, resetRoot = true) => {
  if (resetRoot) {
    tree.dev = false
    tree.optional = false
    tree.devOptional = false
    tree.peer = false
  }
  const ret = depth({
    tree,
    visit: node => calcDepFlagsStep(node),
    filter: node => node,
    getChildren: (node, tree) =>
      [...tree.edgesOut.values()].map(edge => edge.to),
  })
  return ret
}
 
const calcDepFlagsStep = (node) => {
  // This rewalk is necessary to handle cases where devDep and optional
  // or normal dependency graphs overlap deep in the dep graph.
  // Since we're only walking through deps that are not already flagged
  // as non-dev/non-optional, it's typically a very shallow traversal
  node.extraneous = false
  resetParents(node, 'extraneous')
  resetParents(node, 'dev')
  resetParents(node, 'peer')
  resetParents(node, 'devOptional')
  resetParents(node, 'optional')
 
  // for links, map their hierarchy appropriately
  if (node.isLink) {
    // node.target can be null, we check to ensure it's not null before proceeding
    if (node.target == null) {
      return node
    }
    node.target.dev = node.dev
    node.target.optional = node.optional
    node.target.devOptional = node.devOptional
    node.target.peer = node.peer
    return calcDepFlagsStep(node.target)
  }
 
  node.edgesOut.forEach(({ peer, optional, dev, to }) => {
    // if the dep is missing, then its flags are already maximally unset
    if (!to) {
      return
    }
 
    // everything with any kind of edge into it is not extraneous
    to.extraneous = false
 
    // devOptional is the *overlap* of the dev and optional tree.
    // however, for convenience and to save an extra rewalk, we leave
    // it set when we are in *either* tree, and then omit it from the
    // package-lock if either dev or optional are set.
    const unsetDevOpt = !node.devOptional && !node.dev && !node.optional && !dev && !optional
 
    // if we are not in the devOpt tree, then we're also not in
    // either the dev or opt trees
    const unsetDev = unsetDevOpt || !node.dev && !dev
    const unsetOpt = unsetDevOpt || !node.optional && !optional
    const unsetPeer = !node.peer && !peer
 
    if (unsetPeer) {
      unsetFlag(to, 'peer')
    }
 
    if (unsetDevOpt) {
      unsetFlag(to, 'devOptional')
    }
 
    if (unsetDev) {
      unsetFlag(to, 'dev')
    }
 
    if (unsetOpt) {
      unsetFlag(to, 'optional')
    }
  })
 
  return node
}
 
const resetParents = (node, flag) => {
  if (node[flag]) {
    return
  }
 
  for (let p = node; p && (p === node || p[flag]); p = p.resolveParent) {
    p[flag] = false
  }
}
 
// typically a short walk, since it only traverses deps that have the flag set.
const unsetFlag = (node, flag) => {
  if (node[flag]) {
    node[flag] = false
    depth({
      tree: node,
      visit: node => {
        node.extraneous = node[flag] = false
        if (node.isLink && node.target) {
          node.target.extraneous = node.target[flag] = false
        }
      },
      getChildren: node => {
        const children = []
        const targetNode = node.isLink && node.target ? node.target : node
        for (const edge of targetNode.edgesOut.values()) {
          if (
            edge.to &&
            edge.to[flag] &&
            ((flag !== 'peer' && edge.type === 'peer') || edge.type === 'prod')
          ) {
            children.push(edge.to)
          }
        }
        return children
      },
    })
  }
}
 
module.exports = calcDepFlags