2020-10-02 17:52:19 -04:00
|
|
|
const { explainNode } = require('./utils/explain-dep.js')
|
|
|
|
const completion = require('./utils/completion/installed-deep.js')
|
|
|
|
const Arborist = require('@npmcli/arborist')
|
|
|
|
const npa = require('npm-package-arg')
|
|
|
|
const semver = require('semver')
|
|
|
|
const { relative, resolve } = require('path')
|
|
|
|
const validName = require('validate-npm-package-name')
|
2021-03-11 17:54:23 -05:00
|
|
|
const BaseCommand = require('./base-command.js')
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-11 17:54:23 -05:00
|
|
|
class Explain extends BaseCommand {
|
2021-03-23 14:58:11 -04:00
|
|
|
static get description () {
|
|
|
|
return 'Explain installed packages'
|
|
|
|
}
|
|
|
|
|
2021-03-11 17:54:23 -05:00
|
|
|
/* istanbul ignore next - see test/lib/load-all-commands.js */
|
|
|
|
static get name () {
|
|
|
|
return 'explain'
|
2021-03-04 17:40:28 -05:00
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
/* istanbul ignore next - see test/lib/load-all-commands.js */
|
2021-03-11 17:54:23 -05:00
|
|
|
static get usage () {
|
|
|
|
return ['<folder | specifier>']
|
2021-03-04 17:40:28 -05:00
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
/* istanbul ignore next - see test/lib/load-all-commands.js */
|
|
|
|
async completion (opts) {
|
|
|
|
return completion(this.npm, opts)
|
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
exec (args, cb) {
|
|
|
|
this.explain(args).then(() => cb()).catch(cb)
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
async explain (args) {
|
|
|
|
if (!args.length)
|
|
|
|
throw this.usage
|
|
|
|
|
|
|
|
const arb = new Arborist({ path: this.npm.prefix, ...this.npm.flatOptions })
|
|
|
|
const tree = await arb.loadActual()
|
|
|
|
|
|
|
|
const nodes = new Set()
|
|
|
|
for (const arg of args) {
|
|
|
|
for (const node of this.getNodes(tree, arg))
|
|
|
|
nodes.add(node)
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
2021-03-04 17:40:28 -05:00
|
|
|
if (nodes.size === 0)
|
|
|
|
throw `No dependencies found matching ${args.join(', ')}`
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
const expls = []
|
|
|
|
for (const node of nodes) {
|
|
|
|
const { extraneous, dev, optional, devOptional, peer, inBundle } = node
|
|
|
|
const expl = node.explain()
|
|
|
|
if (extraneous)
|
|
|
|
expl.extraneous = true
|
|
|
|
else {
|
|
|
|
expl.dev = dev
|
|
|
|
expl.optional = optional
|
|
|
|
expl.devOptional = devOptional
|
|
|
|
expl.peer = peer
|
|
|
|
expl.bundled = inBundle
|
|
|
|
}
|
|
|
|
expls.push(expl)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.npm.flatOptions.json)
|
2021-03-11 17:54:23 -05:00
|
|
|
this.npm.output(JSON.stringify(expls, null, 2))
|
2021-03-04 17:40:28 -05:00
|
|
|
else {
|
2021-03-11 17:54:23 -05:00
|
|
|
this.npm.output(expls.map(expl => {
|
2021-03-04 17:40:28 -05:00
|
|
|
return explainNode(expl, Infinity, this.npm.color)
|
|
|
|
}).join('\n\n'))
|
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
getNodes (tree, arg) {
|
|
|
|
// if it's just a name, return packages by that name
|
|
|
|
const { validForOldPackages: valid } = validName(arg)
|
|
|
|
if (valid)
|
|
|
|
return tree.inventory.query('name', arg)
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
// if it's a location, get that node
|
|
|
|
const maybeLoc = arg.replace(/\\/g, '/').replace(/\/+$/, '')
|
|
|
|
const nodeByLoc = tree.inventory.get(maybeLoc)
|
|
|
|
if (nodeByLoc)
|
|
|
|
return [nodeByLoc]
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
// maybe a path to a node_modules folder
|
|
|
|
const maybePath = relative(this.npm.prefix, resolve(maybeLoc))
|
|
|
|
.replace(/\\/g, '/').replace(/\/+$/, '')
|
|
|
|
const nodeByPath = tree.inventory.get(maybePath)
|
|
|
|
if (nodeByPath)
|
|
|
|
return [nodeByPath]
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
// otherwise, try to select all matching nodes
|
|
|
|
try {
|
|
|
|
return this.getNodesByVersion(tree, arg)
|
|
|
|
} catch (er) {
|
|
|
|
return []
|
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
getNodesByVersion (tree, arg) {
|
|
|
|
const spec = npa(arg, this.npm.prefix)
|
|
|
|
if (spec.type !== 'version' && spec.type !== 'range')
|
|
|
|
return []
|
2020-10-02 17:52:19 -04:00
|
|
|
|
2021-03-04 17:40:28 -05:00
|
|
|
return tree.inventory.filter(node => {
|
|
|
|
return node.package.name === spec.name &&
|
|
|
|
semver.satisfies(node.package.version, spec.rawSpec)
|
|
|
|
})
|
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
2021-03-04 17:40:28 -05:00
|
|
|
module.exports = Explain
|