// show the installed versions of packages // // --parseable creates output like this: // ::: // Flags are a :-separated list of zero or more indicators module.exports = exports = ls var npm = require("./npm.js") , readInstalled = require("read-installed") , log = require("npmlog") , path = require("path") , archy = require("archy") , semver = require("semver") , url = require("url") , color = require("ansicolors") , npa = require("npm-package-arg") ls.usage = "npm ls" 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, "..") // npm ls 'foo@~1.3' bar 'baz@<2' if (!args) args = [] else args = args.map(function (a) { var p = npa(a) , name = p.name , ver = semver.validRange(p.rawSpec) || "" return [ name, ver ] }) var depth = npm.config.get("depth") var opt = { depth: depth, log: log.warn, dev: true } readInstalled(dir, opt, function (er, data) { pruneNestedExtraneous(data) filterByEnv(data) var bfs = bfsify(data, args) , lite = getLite(bfs) if (er || silent) return cb(er, data, lite) var long = npm.config.get("long") , json = npm.config.get("json") , out if (json) { var seen = [] var d = long ? bfs : lite // the raw data can be circular out = JSON.stringify(d, function (k, o) { if (typeof o === "object") { if (-1 !== seen.indexOf(o)) return "[Circular]" seen.push(o) } return o }, 2) } else if (npm.config.get("parseable")) { out = makeParseable(bfs, long, dir) } else if (data) { out = makeArchy(bfs, long, dir) } console.log(out) 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") } 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") var production = npm.config.get("production") if (dev === production) return var dependencies = {} var devDependencies = data.devDependencies || [] Object.keys(data.dependencies).forEach(function (name) { var keys = Object.keys(devDependencies) if (production && keys.indexOf(name) !== -1) return if (dev && keys.indexOf(name) === -1) return dependencies[name] = data.dependencies[name] }) data.dependencies = dependencies } function alphasort (a, b) { a = a.toLowerCase() b = b.toLowerCase() return a > b ? 1 : a < b ? -1 : 0 } function getLite (data, noname) { var lite = {} , maxDepth = npm.config.get("depth") 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: " + data.name + "@" + data.version + " " + (data.path || "") ) } 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: " + data.name + "@" + data.version + " " + (data.path || "") ) } if (data.peerInvalid) { lite.peerInvalid = true lite.problems = lite.problems || [] lite.problems.push( "peer invalid: " + data.name + "@" + data.version + " " + (data.path || "") ) } if (data.dependencies) { var deps = Object.keys(data.dependencies) if (deps.length) lite.dependencies = deps.map(function (d) { var dep = data.dependencies[d] if (typeof dep === "string") { lite.problems = lite.problems || [] var p if (data.depth > maxDepth) { p = "max depth reached: " } else { p = "missing: " } p += d + "@" + dep + ", required by " + data.name + "@" + data.version lite.problems.push(p) return [d, { required: dep, missing: true }] } 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 bfsify (root, args, current, queue, seen) { // walk over the data, and turn it from this: // +-- a // | `-- b // | `-- a (truncated) // `--b (truncated) // into this: // +-- a // `-- b // which looks nicer args = args || [] current = current || root queue = queue || [] seen = seen || [root] var deps = current.dependencies = current.dependencies || {} Object.keys(deps).forEach(function (d) { var dep = deps[d] if (typeof dep !== "object") return if (seen.indexOf(dep) !== -1) { if (npm.config.get("parseable") || !npm.config.get("long")) { delete deps[d] return } else { dep = deps[d] = Object.create(dep) dep.dependencies = {} } } queue.push(dep) seen.push(dep) }) if (!queue.length) { // if there were args, then only show the paths to found nodes. return filterFound(root, args) } return bfsify(root, args, queue.shift(), queue, seen) } function filterFound (root, args) { if (!args.length) return root var deps = root.dependencies if (deps) Object.keys(deps).forEach(function (d) { var dep = filterFound(deps[d], args) // see if this one itself matches var found = false for (var i = 0; !found && i < args.length; i ++) { if (d === args[i][0]) { found = semver.satisfies(dep.version, args[i][1], true) } } // included explicitly if (found) dep._found = true // included because a child was included if (dep._found && !root._found) root._found = 1 // not included if (!dep._found) delete deps[d] }) if (!root._found) root._found = false 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 (typeof data === "string") { if (depth -1 <= npm.config.get("depth")) { // just missing var unmet = "UNMET DEPENDENCY" if (npm.color) { unmet = color.bgBlack(color.red(unmet)) } data = unmet + " " + d + "@" + data } else { data = d+"@"+ data } return data } var out = {} // the top level is a bit special. out.label = data._id || "" if (data._found === true && 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.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.extraneous && data.path !== dir) { var extraneous = "extraneous" if (npm.color) extraneous = color.bgBlack(color.green(extraneous)) out.label += " " + extraneous } // add giturl to name@version if (data._resolved) { var type = npa(data._resolved).type var isGit = type === 'git' || type === 'hosted' if (isGit) { out.label += ' (' + data._resolved + ')' } } if (long) { if (dir === data.path) out.label += "\n" + dir out.label += "\n" + getExtras(data, dir) } 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).map(function (d) { return makeArchy_(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) { depth = depth || 0 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 }) .join("\n") } function makeParseable_ (data, long, dir, depth, parent, d) { if (data.hasOwnProperty("_found") && data._found !== true) return "" if (typeof data === "string") { if (data.depth < npm.config.get("depth")) { data = npm.config.get("long") ? path.resolve(parent.path, "node_modules", d) + ":"+d+"@"+JSON.stringify(data)+":INVALID:MISSING" : "" } else { data = path.resolve(data.path || "", "node_modules", d || "") + (npm.config.get("long") ? ":" + d + "@" + JSON.stringify(data) + ":" // no realpath resolved + ":MAXDEPTH" : "") } return data } if (!npm.config.get("long")) return data.path return data.path + ":" + (data._id || "") + ":" + (data.realPath !== data.path ? data.realPath : "") + (data.extraneous ? ":EXTRANEOUS" : "") + (data.invalid ? ":INVALID" : "") + (data.peerInvalid ? ":PEERINVALID" : "") }