1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
|
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')
const ArboristWorkspaceCmd = require('../arborist-cmd.js')
class Explain extends ArboristWorkspaceCmd {
static description = 'Explain installed packages'
static name = 'explain'
static usage = ['<package-spec>']
static params = [
'json',
'workspace',
]
static ignoreImplicitWorkspace = false
// TODO
/* istanbul ignore next */
async completion (opts) {
return completion(this.npm, opts)
}
async exec (args) {
if (!args.length) {
throw this.usageError()
}
const arb = new Arborist({ path: this.npm.prefix, ...this.npm.flatOptions })
const tree = await arb.loadActual()
if (this.npm.flatOptions.workspacesEnabled
&& this.workspaceNames
&& this.workspaceNames.length
) {
this.filterSet = arb.workspaceDependencySet(tree, this.workspaceNames)
} else if (!this.npm.flatOptions.workspacesEnabled) {
this.filterSet =
arb.excludeWorkspacesDependencySet(tree)
}
const nodes = new Set()
for (const arg of args) {
for (const node of this.getNodes(tree, arg)) {
const filteredOut = this.filterSet
&& this.filterSet.size > 0
&& !this.filterSet.has(node)
if (!filteredOut) {
nodes.add(node)
}
}
}
if (nodes.size === 0) {
throw new Error(`No dependencies found matching ${args.join(', ')}`)
}
const expls = []
for (const node of nodes) {
const { extraneous, dev, optional, devOptional, peer, inBundle, overridden } = 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
expl.overridden = overridden
}
expls.push(expl)
}
if (this.npm.flatOptions.json) {
this.npm.output(JSON.stringify(expls, null, 2))
} else {
this.npm.output(expls.map(expl => {
return explainNode(expl, Infinity, this.npm.color)
}).join('\n\n'))
}
}
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('packageName', arg)
}
// if it's a location, get that node
const maybeLoc = arg.replace(/\\/g, '/').replace(/\/+$/, '')
const nodeByLoc = tree.inventory.get(maybeLoc)
if (nodeByLoc) {
return [nodeByLoc]
}
// 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]
}
// otherwise, try to select all matching nodes
try {
return this.getNodesByVersion(tree, arg)
} catch (er) {
return []
}
}
getNodesByVersion (tree, arg) {
const spec = npa(arg, this.npm.prefix)
if (spec.type !== 'version' && spec.type !== 'range') {
return []
}
return tree.inventory.filter(node => {
return node.package.name === spec.name &&
semver.satisfies(node.package.version, spec.rawSpec)
})
}
}
module.exports = Explain
|