codekingpro/portable-devtools
114k
1const { relative } = require('node:path')
2
3const explainNode = (node, depth, chalk, seen = new Set()) =>
4 printNode(node, chalk) +
5 explainDependents(node, depth, chalk, seen) +
6 explainLinksIn(node, depth, chalk, seen)
7
8const colorType = (type, chalk) => {
9 const style = type === 'extraneous' ? chalk.red
10 : type === 'dev' ? chalk.blue
11 : type === 'optional' ? chalk.magenta
12 : type === 'peer' ? chalk.magentaBright
13 : type === 'bundled' ? chalk.underline.cyan
14 : type === 'workspace' ? chalk.blueBright
15 : type === 'overridden' ? chalk.dim
16 : /* istanbul ignore next */ s => s
17 return style(type)
18}
19
20const printNode = (node, chalk) => {
21 const extra = []
22
23 for (const meta of ['extraneous', 'dev', 'optional', 'peer', 'bundled', 'overridden']) {
24 if (node[meta]) {
25 extra.push(` ${colorType(meta, chalk)}`)
26 }
27 }
28
29 const pkgid = node.isWorkspace
30 ? chalk.blueBright(`${node.name}@${node.version}`)
31 : `${node.name}@${node.version}`
32
33 return `${pkgid}${extra.join('')}` +
34 (node.location ? chalk.dim(`\n${node.location}`) : '')
35}
36
37const explainLinksIn = ({ linksIn }, depth, chalk, seen) => {
38 if (!linksIn || !linksIn.length || depth <= 0) {
39 return ''
40 }
41
42 const messages = linksIn.map(link => explainNode(link, depth - 1, chalk, seen))
43 const str = '\n' + messages.join('\n')
44 return str.split('\n').join('\n ')
45}
46
47const explainDependents = ({ dependents }, depth, chalk, seen) => {
48 if (!dependents || !dependents.length || depth <= 0) {
49 return ''
50 }
51
52 const max = Math.ceil(depth / 2)
53 const messages = dependents.slice(0, max)
54 .map(edge => explainEdge(edge, depth, chalk, seen))
55
56 // show just the names of the first 5 deps that overflowed the list
57 if (dependents.length > max) {
58 let len = 0
59 const maxLen = 50
60 const showNames = []
61 for (let i = max; i < dependents.length; i++) {
62 const { from: { name: depName = 'the root project' } } = dependents[i]
63 len += depName.length
64 if (len >= maxLen && i < dependents.length - 1) {
65 showNames.push('...')
66 break
67 }
68 showNames.push(depName)
69 }
70 const show = `(${showNames.join(', ')})`
71 messages.push(`${dependents.length - max} more ${show}`)
72 }
73
74 const str = '\n' + messages.join('\n')
75 return str.split('\n').join('\n ')
76}
77
78const explainEdge = (
79 { name, type, bundled, from, spec, rawSpec, overridden },
80 depth, chalk, seen = new Set()
81) => {
82 let dep = type === 'workspace'
83 ? chalk.bold(relative(from.location, spec.slice('file:'.length)))
84 : `${name}@"${spec}"`
85 if (overridden) {
86 dep = `${colorType('overridden', chalk)} ${dep} (was "${rawSpec}")`
87 }
88
89 const fromMsg = ` from ${explainFrom(from, depth, chalk, seen)}`
90
91 return (type === 'prod' ? '' : `${colorType(type, chalk)} `) +
92 (bundled ? `${colorType('bundled', chalk)} ` : '') +
93 `${dep}${fromMsg}`
94}
95
96const explainFrom = (from, depth, chalk, seen) => {
97 if (!from.name && !from.version) {
98 return 'the root project'
99 }
100
101 // Prevent infinite recursion from cycles in the dependency graph (e.g. linked strategy store nodes). Use stack-based tracking so diamond dependencies (same node reached via different paths) are still explained, but recursive cycles are broken.
102 const nodeId = `${from.name}@${from.version}:${from.location}`
103 if (seen.has(nodeId)) {
104 return printNode(from, chalk)
105 }
106 seen.add(nodeId)
107
108 const result = printNode(from, chalk) +
109 explainDependents(from, depth - 1, chalk, seen) +
110 explainLinksIn(from, depth - 1, chalk, seen)
111
112 seen.delete(nodeId)
113 return result
114}
115
116module.exports = { explainNode, printNode, explainEdge }
117 