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.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.extraneous = node.target[flag] = false
}
},
getChildren: node => [...node.target.edgesOut.values()]
.filter(edge => edge.to && edge.to[flag] &&
(flag !== 'peer' && edge.type === 'peer' || edge.type === 'prod'))
.map(edge => edge.to),
})
}
}
module.exports = calcDepFlags
|