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