explain.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. const { explainNode } = require('../utils/explain-dep.js')
  2. const npa = require('npm-package-arg')
  3. const semver = require('semver')
  4. const { relative, resolve } = require('node:path')
  5. const validName = require('validate-npm-package-name')
  6. const { output } = require('proc-log')
  7. const ArboristWorkspaceCmd = require('../arborist-cmd.js')
  8. class Explain extends ArboristWorkspaceCmd {
  9. static description = 'Explain installed packages'
  10. static name = 'explain'
  11. static usage = ['<package-spec>']
  12. static params = [
  13. 'json',
  14. 'workspace',
  15. ]
  16. static ignoreImplicitWorkspace = false
  17. static async completion (opts, npm) {
  18. const completion = require('../utils/installed-deep.js')
  19. return completion(npm, opts)
  20. }
  21. async exec (args) {
  22. if (!args.length) {
  23. throw this.usageError()
  24. }
  25. const Arborist = require('@npmcli/arborist')
  26. const arb = new Arborist({ path: this.npm.prefix, ...this.npm.flatOptions })
  27. const tree = await arb.loadActual()
  28. if (this.npm.flatOptions.workspacesEnabled
  29. && this.workspaceNames
  30. && this.workspaceNames.length
  31. ) {
  32. this.filterSet = arb.workspaceDependencySet(tree, this.workspaceNames)
  33. } else if (!this.npm.flatOptions.workspacesEnabled) {
  34. this.filterSet =
  35. arb.excludeWorkspacesDependencySet(tree)
  36. }
  37. const nodes = new Set()
  38. for (const arg of args) {
  39. for (const node of this.getNodes(tree, arg)) {
  40. const filteredOut = this.filterSet
  41. && this.filterSet.size > 0
  42. && !this.filterSet.has(node)
  43. if (!filteredOut) {
  44. nodes.add(node)
  45. }
  46. }
  47. }
  48. if (nodes.size === 0) {
  49. throw new Error(`No dependencies found matching ${args.join(', ')}`)
  50. }
  51. const expls = []
  52. for (const node of nodes) {
  53. const { extraneous, dev, optional, devOptional, peer, inBundle, overridden } = node
  54. const expl = node.explain()
  55. if (extraneous) {
  56. expl.extraneous = true
  57. } else {
  58. expl.dev = dev
  59. expl.optional = optional
  60. expl.devOptional = devOptional
  61. expl.peer = peer
  62. expl.bundled = inBundle
  63. expl.overridden = overridden
  64. }
  65. expls.push(expl)
  66. }
  67. if (this.npm.flatOptions.json) {
  68. output.buffer(expls)
  69. } else {
  70. output.standard(expls.map(expl => {
  71. return explainNode(expl, Infinity, this.npm.chalk)
  72. }).join('\n\n'))
  73. }
  74. }
  75. getNodes (tree, arg) {
  76. // if it's just a name, return packages by that name
  77. const { validForOldPackages: valid } = validName(arg)
  78. if (valid) {
  79. return tree.inventory.query('packageName', arg)
  80. }
  81. // if it's a location, get that node
  82. const maybeLoc = arg.replace(/\\/g, '/').replace(/(?<!\/)\/+$/, '')
  83. const nodeByLoc = tree.inventory.get(maybeLoc)
  84. if (nodeByLoc) {
  85. return [nodeByLoc]
  86. }
  87. // maybe a path to a node_modules folder
  88. const maybePath = relative(this.npm.prefix, resolve(maybeLoc))
  89. .replace(/\\/g, '/').replace(/(?<!\/)\/+$/, '')
  90. const nodeByPath = tree.inventory.get(maybePath)
  91. if (nodeByPath) {
  92. return [nodeByPath]
  93. }
  94. // otherwise, try to select all matching nodes
  95. try {
  96. return this.getNodesByVersion(tree, arg)
  97. } catch {
  98. return []
  99. }
  100. }
  101. getNodesByVersion (tree, arg) {
  102. const spec = npa(arg, this.npm.prefix)
  103. if (spec.type !== 'version' && spec.type !== 'range') {
  104. return []
  105. }
  106. return tree.inventory.filter(node => {
  107. return node.package.name === spec.name &&
  108. semver.satisfies(node.package.version, spec.rawSpec)
  109. })
  110. }
  111. }
  112. module.exports = Explain