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