query.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. const { resolve } = require('node:path')
  2. const BaseCommand = require('../base-cmd.js')
  3. const { log, output } = require('proc-log')
  4. class QuerySelectorItem {
  5. constructor (node) {
  6. // all enumerable properties from the target
  7. Object.assign(this, node.target.package)
  8. // append extra info
  9. this.pkgid = node.target.pkgid
  10. this.location = node.target.location
  11. this.path = node.target.path
  12. this.realpath = node.target.realpath
  13. this.resolved = node.target.resolved
  14. this.from = []
  15. this.to = []
  16. this.dev = node.target.dev
  17. this.inBundle = node.target.inBundle
  18. this.deduped = this.from.length > 1
  19. this.overridden = node.overridden
  20. this.queryContext = node.queryContext
  21. for (const edge of node.target.edgesIn) {
  22. this.from.push(edge.from.location)
  23. }
  24. for (const [, edge] of node.target.edgesOut) {
  25. if (edge.to) {
  26. this.to.push(edge.to.location)
  27. }
  28. }
  29. }
  30. }
  31. class Query extends BaseCommand {
  32. #response = [] // response is the query response
  33. #seen = new Set() // paths we've seen so we can keep response deduped
  34. static description = 'Retrieve a filtered list of packages'
  35. static name = 'query'
  36. static usage = ['<selector>']
  37. static workspaces = true
  38. static ignoreImplicitWorkspace = false
  39. static params = [
  40. 'global',
  41. 'workspace',
  42. 'workspaces',
  43. 'include-workspace-root',
  44. 'package-lock-only',
  45. 'expect-results',
  46. ]
  47. constructor (...args) {
  48. super(...args)
  49. this.npm.config.set('json', true)
  50. }
  51. async exec (args) {
  52. const packageLockOnly = this.npm.config.get('package-lock-only')
  53. const Arborist = require('@npmcli/arborist')
  54. const arb = new Arborist({
  55. ...this.npm.flatOptions,
  56. // one dir up from wherever node_modules lives
  57. path: resolve(this.npm.dir, '..'),
  58. forceActual: !packageLockOnly,
  59. })
  60. let tree
  61. if (packageLockOnly) {
  62. try {
  63. tree = await arb.loadVirtual()
  64. } catch (err) {
  65. log.verbose('loadVirtual', err.stack)
  66. throw this.usageError(
  67. 'A package lock or shrinkwrap file is required in package-lock-only mode'
  68. )
  69. }
  70. } else {
  71. tree = await arb.loadActual()
  72. }
  73. await this.#queryTree(tree, args[0])
  74. this.#output()
  75. }
  76. async execWorkspaces (args) {
  77. await this.setWorkspaces()
  78. const packageLockOnly = this.npm.config.get('package-lock-only')
  79. const Arborist = require('@npmcli/arborist')
  80. const arb = new Arborist({
  81. ...this.npm.flatOptions,
  82. path: this.npm.prefix,
  83. forceActual: !packageLockOnly,
  84. })
  85. let tree
  86. if (packageLockOnly) {
  87. try {
  88. tree = await arb.loadVirtual()
  89. } catch (err) {
  90. log.verbose('loadVirtual', err.stack)
  91. throw this.usageError(
  92. 'A package lock or shrinkwrap file is required in package-lock-only mode'
  93. )
  94. }
  95. } else {
  96. tree = await arb.loadActual()
  97. }
  98. for (const path of this.workspacePaths) {
  99. const wsTree = path === tree.root.path
  100. ? tree // --includes-workspace-root
  101. : await tree.querySelectorAll(`.workspace:path(${path})`).then(r => r[0]?.target)
  102. if (wsTree) {
  103. await this.#queryTree(wsTree, args[0])
  104. }
  105. }
  106. this.#output()
  107. }
  108. #output () {
  109. this.checkExpected(this.#response.length)
  110. output.buffer(this.#response)
  111. }
  112. // builds a normalized inventory
  113. async #queryTree (tree, arg) {
  114. const items = await tree.querySelectorAll(arg, this.npm.flatOptions)
  115. for (const node of items) {
  116. const { location } = node.target
  117. if (!location || !this.#seen.has(location)) {
  118. const item = new QuerySelectorItem(node)
  119. this.#response.push(item)
  120. if (location) {
  121. this.#seen.add(item.location)
  122. }
  123. }
  124. }
  125. }
  126. }
  127. module.exports = Query