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