123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- // show the installed versions of packages
- //
- // --parseable creates output like this:
- // <fullpath>:<name@ver>:<realpath>:<flags>
- // Flags are a :-separated list of zero or more indicators
- module.exports = exports = ls
- var path = require('path')
- var url = require('url')
- var readPackageTree = require('read-package-tree')
- var archy = require('archy')
- var semver = require('semver')
- var color = require('ansicolors')
- var moduleName = require('./utils/module-name.js')
- var npa = require('npm-package-arg')
- var sortedObject = require('sorted-object')
- var npm = require('./npm.js')
- var mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js')
- var computeMetadata = require('./install/deps.js').computeMetadata
- var readShrinkwrap = require('./install/read-shrinkwrap.js')
- var packageId = require('./utils/package-id.js')
- var usage = require('./utils/usage')
- var output = require('./utils/output.js')
- ls.usage = usage(
- 'ls',
- 'npm ls [[<@scope>/]<pkg> ...]'
- )
- ls.completion = require('./utils/completion/installed-deep.js')
- function ls (args, silent, cb) {
- if (typeof cb !== 'function') {
- cb = silent
- silent = false
- }
- var dir = path.resolve(npm.dir, '..')
- readPackageTree(dir, function (_, physicalTree) {
- if (!physicalTree) physicalTree = {package: {}, path: dir}
- physicalTree.isTop = true
- readShrinkwrap.andInflate(physicalTree, function () {
- lsFromTree(dir, computeMetadata(physicalTree), args, silent, cb)
- })
- })
- }
- function inList (list, value) {
- return list.indexOf(value) !== -1
- }
- var lsFromTree = ls.fromTree = function (dir, physicalTree, args, silent, cb) {
- if (typeof cb !== 'function') {
- cb = silent
- silent = false
- }
- // npm ls 'foo@~1.3' bar 'baz@<2'
- if (!args) {
- args = []
- } else {
- args = args.map(function (a) {
- if (typeof a === 'object' && a.package._requested.type === 'alias') {
- return [moduleName(a), `npm:${a.package.name}@${a.package.version}`, a]
- } else if (typeof a === 'object') {
- return [a.package.name, a.package.version, a]
- } else {
- var p = npa(a)
- var name = p.name
- // When version spec is missing, we'll skip using it when filtering.
- // Otherwise, `semver.validRange` would return '*', which won't
- // match prerelease versions.
- var ver = (p.rawSpec &&
- (semver.validRange(p.rawSpec) || ''))
- return [ name, ver, a ]
- }
- })
- }
- var data = mutateIntoLogicalTree.asReadInstalled(physicalTree)
- pruneNestedExtraneous(data)
- filterByEnv(data)
- filterByLink(data)
- var unlooped = filterFound(unloop(data), args)
- var lite = getLite(unlooped)
- if (silent) return cb(null, data, lite)
- var long = npm.config.get('long')
- var json = npm.config.get('json')
- var out
- if (json) {
- var seen = new Set()
- var d = long ? unlooped : lite
- // the raw data can be circular
- out = JSON.stringify(d, function (k, o) {
- if (typeof o === 'object') {
- if (seen.has(o)) return '[Circular]'
- seen.add(o)
- }
- return o
- }, 2)
- } else if (npm.config.get('parseable')) {
- out = makeParseable(unlooped, long, dir)
- } else if (data) {
- out = makeArchy(unlooped, long, dir)
- }
- output(out)
- if (args.length && !data._found) process.exitCode = 1
- var er
- // if any errors were found, then complain and exit status 1
- if (lite.problems && lite.problems.length) {
- er = lite.problems.join('\n')
- }
- cb(er, data, lite)
- }
- function pruneNestedExtraneous (data, visited) {
- visited = visited || []
- visited.push(data)
- for (var i in data.dependencies) {
- if (data.dependencies[i].extraneous) {
- data.dependencies[i].dependencies = {}
- } else if (visited.indexOf(data.dependencies[i]) === -1) {
- pruneNestedExtraneous(data.dependencies[i], visited)
- }
- }
- }
- function filterByEnv (data) {
- var dev = npm.config.get('dev') || /^dev(elopment)?$/.test(npm.config.get('only'))
- var production = npm.config.get('production') || /^prod(uction)?$/.test(npm.config.get('only'))
- var dependencies = {}
- var devKeys = Object.keys(data.devDependencies || [])
- var prodKeys = Object.keys(data._dependencies || [])
- Object.keys(data.dependencies).forEach(function (name) {
- if (!dev && inList(devKeys, name) && !inList(prodKeys, name) && data.dependencies[name].missing) {
- return
- }
- if ((dev && inList(devKeys, name)) || // only --dev
- (production && inList(prodKeys, name)) || // only --production
- (!dev && !production)) { // no --production|--dev|--only=xxx
- dependencies[name] = data.dependencies[name]
- }
- })
- data.dependencies = dependencies
- }
- function filterByLink (data) {
- if (npm.config.get('link')) {
- var dependencies = {}
- Object.keys(data.dependencies).forEach(function (name) {
- var dependency = data.dependencies[name]
- if (dependency.link) {
- dependencies[name] = dependency
- }
- })
- data.dependencies = dependencies
- }
- }
- function alphasort (a, b) {
- a = a.toLowerCase()
- b = b.toLowerCase()
- return a > b ? 1
- : a < b ? -1 : 0
- }
- function isCruft (data) {
- return data.extraneous && data.error && data.error.code === 'ENOTDIR'
- }
- function getLite (data, noname, depth) {
- var lite = {}
- if (isCruft(data)) return lite
- var maxDepth = npm.config.get('depth')
- if (typeof depth === 'undefined') depth = 0
- if (!noname && data.name) lite.name = data.name
- if (data.version) lite.version = data.version
- if (data.extraneous) {
- lite.extraneous = true
- lite.problems = lite.problems || []
- lite.problems.push('extraneous: ' + packageId(data) + ' ' + (data.path || ''))
- }
- if (data.error && data.path !== path.resolve(npm.globalDir, '..') &&
- (data.error.code !== 'ENOENT' || noname)) {
- lite.invalid = true
- lite.problems = lite.problems || []
- var message = data.error.message
- lite.problems.push('error in ' + data.path + ': ' + message)
- }
- if (data._from) {
- lite.from = data._from
- }
- if (data._resolved) {
- lite.resolved = data._resolved
- }
- if (data.invalid) {
- lite.invalid = true
- lite.problems = lite.problems || []
- lite.problems.push('invalid: ' +
- packageId(data) +
- ' ' + (data.path || ''))
- }
- if (data.peerInvalid) {
- lite.peerInvalid = true
- lite.problems = lite.problems || []
- lite.problems.push('peer dep not met: ' +
- packageId(data) +
- ' ' + (data.path || ''))
- }
- var deps = (data.dependencies && Object.keys(data.dependencies)) || []
- if (deps.length) {
- lite.dependencies = deps.map(function (d) {
- var dep = data.dependencies[d]
- if (dep.missing && !dep.optional) {
- lite.problems = lite.problems || []
- var p
- if (data.depth > maxDepth) {
- p = 'max depth reached: '
- } else {
- p = 'missing: '
- }
- p += d + '@' + dep.requiredBy +
- ', required by ' +
- packageId(data)
- lite.problems.push(p)
- if (dep.dependencies) {
- return [d, getLite(dep, true)]
- } else {
- return [d, { required: dep.requiredBy, missing: true }]
- }
- } else if (dep.peerMissing) {
- lite.problems = lite.problems || []
- dep.peerMissing.forEach(function (missing) {
- var pdm = 'peer dep missing: ' +
- missing.requires +
- ', required by ' +
- missing.requiredBy
- lite.problems.push(pdm)
- })
- return [d, { required: dep, peerMissing: true }]
- } else if (npm.config.get('json')) {
- if (depth === maxDepth) delete dep.dependencies
- return [d, getLite(dep, true, depth + 1)]
- }
- return [d, getLite(dep, true)]
- }).reduce(function (deps, d) {
- if (d[1].problems) {
- lite.problems = lite.problems || []
- lite.problems.push.apply(lite.problems, d[1].problems)
- }
- deps[d[0]] = d[1]
- return deps
- }, {})
- }
- return lite
- }
- function unloop (root) {
- var queue = [root]
- var seen = new Set()
- seen.add(root)
- while (queue.length) {
- var current = queue.shift()
- var deps = current.dependencies = current.dependencies || {}
- Object.keys(deps).forEach(function (d) {
- var dep = deps[d]
- if (dep.missing && !dep.dependencies) return
- if (dep.path && seen.has(dep)) {
- dep = deps[d] = Object.assign({}, dep)
- dep.dependencies = {}
- dep._deduped = path.relative(root.path, dep.path).replace(/node_modules\//g, '')
- return
- }
- seen.add(dep)
- queue.push(dep)
- })
- }
- return root
- }
- function filterFound (root, args) {
- if (!args.length) return root
- if (!root.dependencies) return root
- // Mark all deps
- var toMark = [root]
- while (toMark.length) {
- var markPkg = toMark.shift()
- var markDeps = markPkg.dependencies
- if (!markDeps) continue
- Object.keys(markDeps).forEach(function (depName) {
- var dep = markDeps[depName]
- if (dep.peerMissing && !dep._from) return
- dep._parent = markPkg
- for (var ii = 0; ii < args.length; ii++) {
- var argName = args[ii][0]
- var argVersion = args[ii][1]
- var argRaw = args[ii][2]
- var found
- if (typeof argRaw === 'object') {
- if (dep.path === argRaw.path) {
- found = true
- }
- } else if (depName === argName && argVersion) {
- found = semver.satisfies(dep.version, argVersion, true)
- } else if (depName === argName) {
- // If version is missing from arg, just do a name match.
- found = true
- }
- if (found) {
- dep._found = 'explicit'
- var parent = dep._parent
- while (parent && !parent._found && !parent._deduped) {
- parent._found = 'implicit'
- parent = parent._parent
- }
- break
- }
- }
- toMark.push(dep)
- })
- }
- var toTrim = [root]
- while (toTrim.length) {
- var trimPkg = toTrim.shift()
- var trimDeps = trimPkg.dependencies
- if (!trimDeps) continue
- trimPkg.dependencies = {}
- Object.keys(trimDeps).forEach(function (name) {
- var dep = trimDeps[name]
- if (!dep._found) return
- if (dep._found === 'implicit' && dep._deduped) return
- trimPkg.dependencies[name] = dep
- toTrim.push(dep)
- })
- }
- return root
- }
- function makeArchy (data, long, dir) {
- var out = makeArchy_(data, long, dir, 0)
- return archy(out, '', { unicode: npm.config.get('unicode') })
- }
- function makeArchy_ (data, long, dir, depth, parent, d) {
- if (data.missing) {
- if (depth - 1 <= npm.config.get('depth')) {
- // just missing
- var unmet = 'UNMET ' + (data.optional ? 'OPTIONAL ' : '') + 'DEPENDENCY'
- if (npm.color) {
- if (data.optional) {
- unmet = color.bgBlack(color.yellow(unmet))
- } else {
- unmet = color.bgBlack(color.red(unmet))
- }
- }
- var label = data._id || (d + '@' + data.requiredBy)
- if (data._found === 'explicit' && data._id) {
- if (npm.color) {
- label = color.bgBlack(color.yellow(label.trim())) + ' '
- } else {
- label = label.trim() + ' '
- }
- }
- return {
- label: unmet + ' ' + label,
- nodes: Object.keys(data.dependencies || {})
- .sort(alphasort).filter(function (d) {
- return !isCruft(data.dependencies[d])
- }).map(function (d) {
- return makeArchy_(sortedObject(data.dependencies[d]), long, dir, depth + 1, data, d)
- })
- }
- } else {
- return {label: d + '@' + data.requiredBy}
- }
- }
- var out = {}
- if (data._requested && data._requested.type === 'alias') {
- out.label = `${d}@npm:${data._id}`
- } else {
- out.label = data._id || ''
- }
- if (data._found === 'explicit' && data._id) {
- if (npm.color) {
- out.label = color.bgBlack(color.yellow(out.label.trim())) + ' '
- } else {
- out.label = out.label.trim() + ' '
- }
- }
- if (data.link) out.label += ' -> ' + data.link
- if (data._deduped) {
- if (npm.color) {
- out.label += ' ' + color.brightBlack('deduped')
- } else {
- out.label += ' deduped'
- }
- }
- if (data.invalid) {
- if (data.realName !== data.name) out.label += ' (' + data.realName + ')'
- var invalid = 'invalid'
- if (npm.color) invalid = color.bgBlack(color.red(invalid))
- out.label += ' ' + invalid
- }
- if (data.peerInvalid) {
- var peerInvalid = 'peer invalid'
- if (npm.color) peerInvalid = color.bgBlack(color.red(peerInvalid))
- out.label += ' ' + peerInvalid
- }
- if (data.peerMissing) {
- var peerMissing = 'UNMET PEER DEPENDENCY'
- if (npm.color) peerMissing = color.bgBlack(color.red(peerMissing))
- out.label = peerMissing + ' ' + out.label
- }
- if (data.extraneous && data.path !== dir) {
- var extraneous = 'extraneous'
- if (npm.color) extraneous = color.bgBlack(color.green(extraneous))
- out.label += ' ' + extraneous
- }
- if (data.error && depth) {
- var message = data.error.message
- if (message.indexOf('\n')) message = message.slice(0, message.indexOf('\n'))
- var error = 'error: ' + message
- if (npm.color) error = color.bgRed(color.brightWhite(error))
- out.label += ' ' + error
- }
- // add giturl to name@version
- if (data._resolved) {
- try {
- var type = npa(data._resolved).type
- var isGit = type === 'git' || type === 'hosted'
- if (isGit) {
- out.label += ' (' + data._resolved + ')'
- }
- } catch (ex) {
- // npa threw an exception then it ain't git so whatev
- }
- }
- if (long) {
- if (dir === data.path) out.label += '\n' + dir
- out.label += '\n' + getExtras(data)
- } else if (dir === data.path) {
- if (out.label) out.label += ' '
- out.label += dir
- }
- // now all the children.
- out.nodes = []
- if (depth <= npm.config.get('depth')) {
- out.nodes = Object.keys(data.dependencies || {})
- .sort(alphasort).filter(function (d) {
- return !isCruft(data.dependencies[d])
- }).map(function (d) {
- return makeArchy_(sortedObject(data.dependencies[d]), long, dir, depth + 1, data, d)
- })
- }
- if (out.nodes.length === 0 && data.path === dir) {
- out.nodes = ['(empty)']
- }
- return out
- }
- function getExtras (data) {
- var extras = []
- if (data.description) extras.push(data.description)
- if (data.repository) extras.push(data.repository.url)
- if (data.homepage) extras.push(data.homepage)
- if (data._from) {
- var from = data._from
- if (from.indexOf(data.name + '@') === 0) {
- from = from.substr(data.name.length + 1)
- }
- var u = url.parse(from)
- if (u.protocol) extras.push(from)
- }
- return extras.join('\n')
- }
- function makeParseable (data, long, dir, depth, parent, d) {
- if (data._deduped) return []
- depth = depth || 0
- if (depth > npm.config.get('depth')) return [ makeParseable_(data, long, dir, depth, parent, d) ]
- return [ makeParseable_(data, long, dir, depth, parent, d) ]
- .concat(Object.keys(data.dependencies || {})
- .sort(alphasort).map(function (d) {
- return makeParseable(data.dependencies[d], long, dir, depth + 1, data, d)
- }))
- .filter(function (x) { return x && x.length })
- .join('\n')
- }
- function makeParseable_ (data, long, dir, depth, parent, d) {
- if (data.hasOwnProperty('_found') && data._found !== 'explicit') return ''
- if (data.missing) {
- if (depth < npm.config.get('depth')) {
- data = npm.config.get('long')
- ? path.resolve(parent.path, 'node_modules', d) +
- ':' + d + '@' + JSON.stringify(data.requiredBy) + ':INVALID:MISSING'
- : ''
- } else {
- data = path.resolve(dir || '', 'node_modules', d || '') +
- (npm.config.get('long')
- ? ':' + d + '@' + JSON.stringify(data.requiredBy) +
- ':' + // no realpath resolved
- ':MAXDEPTH'
- : '')
- }
- return data
- }
- if (!npm.config.get('long')) return data.path
- return data.path +
- ':' + (data._id || '') +
- (data.link && data.link !== data.path ? ':' + data.link : '') +
- (data.extraneous ? ':EXTRANEOUS' : '') +
- (data.error && data.path !== path.resolve(npm.globalDir, '..') ? ':ERROR' : '') +
- (data.invalid ? ':INVALID' : '') +
- (data.peerInvalid ? ':PEERINVALID' : '') +
- (data.peerMissing ? ':PEERINVALID:MISSING' : '')
- }
|