explain-dep.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. const { relative } = require('node:path')
  2. const explainNode = (node, depth, chalk, seen = new Set()) =>
  3. printNode(node, chalk) +
  4. explainDependents(node, depth, chalk, seen) +
  5. explainLinksIn(node, depth, chalk, seen)
  6. const colorType = (type, chalk) => {
  7. const style = type === 'extraneous' ? chalk.red
  8. : type === 'dev' ? chalk.blue
  9. : type === 'optional' ? chalk.magenta
  10. : type === 'peer' ? chalk.magentaBright
  11. : type === 'bundled' ? chalk.underline.cyan
  12. : type === 'workspace' ? chalk.blueBright
  13. : type === 'overridden' ? chalk.dim
  14. : /* istanbul ignore next */ s => s
  15. return style(type)
  16. }
  17. const printNode = (node, chalk) => {
  18. const extra = []
  19. for (const meta of ['extraneous', 'dev', 'optional', 'peer', 'bundled', 'overridden']) {
  20. if (node[meta]) {
  21. extra.push(` ${colorType(meta, chalk)}`)
  22. }
  23. }
  24. const pkgid = node.isWorkspace
  25. ? chalk.blueBright(`${node.name}@${node.version}`)
  26. : `${node.name}@${node.version}`
  27. return `${pkgid}${extra.join('')}` +
  28. (node.location ? chalk.dim(`\n${node.location}`) : '')
  29. }
  30. const explainLinksIn = ({ linksIn }, depth, chalk, seen) => {
  31. if (!linksIn || !linksIn.length || depth <= 0) {
  32. return ''
  33. }
  34. const messages = linksIn.map(link => explainNode(link, depth - 1, chalk, seen))
  35. const str = '\n' + messages.join('\n')
  36. return str.split('\n').join('\n ')
  37. }
  38. const explainDependents = ({ dependents }, depth, chalk, seen) => {
  39. if (!dependents || !dependents.length || depth <= 0) {
  40. return ''
  41. }
  42. const max = Math.ceil(depth / 2)
  43. const messages = dependents.slice(0, max)
  44. .map(edge => explainEdge(edge, depth, chalk, seen))
  45. // show just the names of the first 5 deps that overflowed the list
  46. if (dependents.length > max) {
  47. let len = 0
  48. const maxLen = 50
  49. const showNames = []
  50. for (let i = max; i < dependents.length; i++) {
  51. const { from: { name: depName = 'the root project' } } = dependents[i]
  52. len += depName.length
  53. if (len >= maxLen && i < dependents.length - 1) {
  54. showNames.push('...')
  55. break
  56. }
  57. showNames.push(depName)
  58. }
  59. const show = `(${showNames.join(', ')})`
  60. messages.push(`${dependents.length - max} more ${show}`)
  61. }
  62. const str = '\n' + messages.join('\n')
  63. return str.split('\n').join('\n ')
  64. }
  65. const explainEdge = (
  66. { name, type, bundled, from, spec, rawSpec, overridden },
  67. depth, chalk, seen = new Set()
  68. ) => {
  69. let dep = type === 'workspace'
  70. ? chalk.bold(relative(from.location, spec.slice('file:'.length)))
  71. : `${name}@"${spec}"`
  72. if (overridden) {
  73. dep = `${colorType('overridden', chalk)} ${dep} (was "${rawSpec}")`
  74. }
  75. const fromMsg = ` from ${explainFrom(from, depth, chalk, seen)}`
  76. return (type === 'prod' ? '' : `${colorType(type, chalk)} `) +
  77. (bundled ? `${colorType('bundled', chalk)} ` : '') +
  78. `${dep}${fromMsg}`
  79. }
  80. const explainFrom = (from, depth, chalk, seen) => {
  81. if (!from.name && !from.version) {
  82. return 'the root project'
  83. }
  84. // 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.
  85. const nodeId = `${from.name}@${from.version}:${from.location}`
  86. if (seen.has(nodeId)) {
  87. return printNode(from, chalk)
  88. }
  89. seen.add(nodeId)
  90. const result = printNode(from, chalk) +
  91. explainDependents(from, depth - 1, chalk, seen) +
  92. explainLinksIn(from, depth - 1, chalk, seen)
  93. seen.delete(nodeId)
  94. return result
  95. }
  96. module.exports = { explainNode, printNode, explainEdge }