// 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 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')
'npm ls [[<@scope>/]<pkg> ...]'
ls.completion = require('./utils/completion/installed-deep.js')
function ls (args, silent, cb) {
if (typeof cb !== 'function') {
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') {
// npm ls 'foo@~1.3' bar 'baz@<2'
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]
// When version spec is missing, we'll skip using it when filtering.
// Otherwise, `semver.validRange` would return '*', which won't
// match prerelease versions.
(semver.validRange(p.rawSpec) || ''))
var data = mutateIntoLogicalTree.asReadInstalled(physicalTree)
pruneNestedExtraneous(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 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]'
} else if (npm.config.get('parseable')) {
out = makeParseable(unlooped, long, dir)
out = makeArchy(unlooped, long, dir)
if (args.length && !data._found) process.exitCode = 1
// if any errors were found, then complain and exit status 1
if (lite.problems && lite.problems.length) {
er = lite.problems.join('\n')
function pruneNestedExtraneous (data, visited) {
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 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) {
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')) {
Object.keys(data.dependencies).forEach(function (name) {
var dependency = data.dependencies[name]
dependencies[name] = dependency
data.dependencies = dependencies
function alphasort (a, b) {
function isCruft (data) {
return data.extraneous && data.error && data.error.code === 'ENOTDIR'
function getLite (data, noname, depth) {
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
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.problems = lite.problems || []
var message = data.error.message
lite.problems.push('error in ' + data.path + ': ' + message)
lite.resolved = data._resolved
lite.problems = lite.problems || []
lite.problems.push('invalid: ' +
lite.problems = lite.problems || []
lite.problems.push('peer dep not met: ' +
var deps = (data.dependencies && Object.keys(data.dependencies)) || []
lite.dependencies = deps.map(function (d) {
var dep = data.dependencies[d]
if (dep.missing && !dep.optional) {
lite.problems = lite.problems || []
if (data.depth > maxDepth) {
p = 'max depth reached: '
p += d + '@' + dep.requiredBy +
return [d, getLite(dep, true)]
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: ' +
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) {
lite.problems = lite.problems || []
lite.problems.push.apply(lite.problems, d[1].problems)
var current = queue.shift()
var deps = current.dependencies = current.dependencies || {}
Object.keys(deps).forEach(function (d) {
if (dep.missing && !dep.dependencies) return
if (dep.path && seen.has(dep)) {
dep = deps[d] = Object.assign({}, dep)
dep._deduped = path.relative(root.path, dep.path).replace(/node_modules\//g, '')
function filterFound (root, args) {
if (!args.length) return root
if (!root.dependencies) return root
var markPkg = toMark.shift()
var markDeps = markPkg.dependencies
Object.keys(markDeps).forEach(function (depName) {
var dep = markDeps[depName]
if (dep.peerMissing && !dep._from) return
for (var ii = 0; ii < args.length; ii++) {
var argName = args[ii][0]
var argVersion = args[ii][1]
if (typeof argRaw === 'object') {
if (dep.path === argRaw.path) {
} 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.
while (parent && !parent._found && !parent._deduped) {
parent._found = 'implicit'
var trimPkg = toTrim.shift()
var trimDeps = trimPkg.dependencies
trimPkg.dependencies = {}
Object.keys(trimDeps).forEach(function (name) {
if (dep._found === 'implicit' && dep._deduped) return
trimPkg.dependencies[name] = dep
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 (depth - 1 <= npm.config.get('depth')) {
var unmet = 'UNMET ' + (data.optional ? 'OPTIONAL ' : '') + 'DEPENDENCY'
unmet = color.bgBlack(color.yellow(unmet))
unmet = color.bgBlack(color.red(unmet))
var label = data._id || (d + '@' + data.requiredBy)
if (data._found === 'explicit' && data._id) {
label = color.bgBlack(color.yellow(label.trim())) + ' '
label = label.trim() + ' '
label: unmet + ' ' + label,
nodes: Object.keys(data.dependencies || {})
.sort(alphasort).filter(function (d) {
return !isCruft(data.dependencies[d])
return makeArchy_(sortedObject(data.dependencies[d]), long, dir, depth + 1, data, d)
return {label: d + '@' + data.requiredBy}
if (data._requested && data._requested.type === 'alias') {
out.label = `${d}@npm:${data._id}`
out.label = data._id || ''
if (data._found === 'explicit' && data._id) {
out.label = color.bgBlack(color.yellow(out.label.trim())) + ' '
out.label = out.label.trim() + ' '
if (data.link) out.label += ' -> ' + data.link
out.label += ' ' + color.brightBlack('deduped')
if (data.realName !== data.name) out.label += ' (' + data.realName + ')'
if (npm.color) invalid = color.bgBlack(color.red(invalid))
out.label += ' ' + invalid
var peerInvalid = 'peer invalid'
if (npm.color) peerInvalid = color.bgBlack(color.red(peerInvalid))
out.label += ' ' + peerInvalid
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))
// add giturl to name@version
var type = npa(data._resolved).type
var isGit = type === 'git' || type === 'hosted'
out.label += ' (' + data._resolved + ')'
// npa threw an exception then it ain't git so whatev
if (dir === data.path) out.label += '\n' + dir
out.label += '\n' + getExtras(data)
} else if (dir === data.path) {
if (out.label) out.label += ' '
if (depth <= npm.config.get('depth')) {
out.nodes = Object.keys(data.dependencies || {})
.sort(alphasort).filter(function (d) {
return !isCruft(data.dependencies[d])
return makeArchy_(sortedObject(data.dependencies[d]), long, dir, depth + 1, data, d)
if (out.nodes.length === 0 && data.path === dir) {
function getExtras (data) {
if (data.description) extras.push(data.description)
if (data.repository) extras.push(data.repository.url)
if (data.homepage) extras.push(data.homepage)