2 // show the installed versions of packages
4 // --parseable creates output like this:
5 // <fullpath>:<name@ver>:<realpath>:<flags>
6 // Flags are a :-separated list of zero or more indicators
8 module.exports = exports = ls
10 var npm = require("./npm.js")
11 , readInstalled = require("read-installed")
12 , log = require("npmlog")
13 , path = require("path")
14 , archy = require("archy")
15 , semver = require("semver")
16 , url = require("url")
17 , color = require("ansicolors")
18 , npa = require("npm-package-arg")
22 ls.completion = require("./utils/completion/installed-deep.js")
24 function ls (args, silent, cb) {
25 if (typeof cb !== "function") cb = silent, silent = false
27 var dir = path.resolve(npm.dir, "..")
29 // npm ls 'foo@~1.3' bar 'baz@<2'
31 else args = args.map(function (a) {
34 , ver = semver.validRange(p.rawSpec) || ""
39 var depth = npm.config.get("depth")
40 var opt = { depth: depth, log: log.warn, dev: true }
41 readInstalled(dir, opt, function (er, data) {
42 pruneNestedExtraneous(data)
44 var bfs = bfsify(data, args)
47 if (er || silent) return cb(er, data, lite)
49 var long = npm.config.get("long")
50 , json = npm.config.get("json")
54 var d = long ? bfs : lite
55 // the raw data can be circular
56 out = JSON.stringify(d, function (k, o) {
57 if (typeof o === "object") {
58 if (-1 !== seen.indexOf(o)) return "[Circular]"
63 } else if (npm.config.get("parseable")) {
64 out = makeParseable(bfs, long, dir)
66 out = makeArchy(bfs, long, dir)
70 if (args.length && !data._found) process.exitCode = 1
72 // if any errors were found, then complain and exit status 1
73 if (lite.problems && lite.problems.length) {
74 er = lite.problems.join("\n")
80 function pruneNestedExtraneous (data, visited) {
81 visited = visited || []
83 for (var i in data.dependencies) {
84 if (data.dependencies[i].extraneous) {
85 data.dependencies[i].dependencies = {}
86 } else if (visited.indexOf(data.dependencies[i]) === -1) {
87 pruneNestedExtraneous(data.dependencies[i], visited)
92 function filterByEnv (data) {
93 var dev = npm.config.get("dev")
94 var production = npm.config.get("production")
95 if (dev === production) return
97 var devDependencies = data.devDependencies || []
98 Object.keys(data.dependencies).forEach(function (name) {
99 var keys = Object.keys(devDependencies)
100 if (production && keys.indexOf(name) !== -1) return
101 if (dev && keys.indexOf(name) === -1) return
102 dependencies[name] = data.dependencies[name]
104 data.dependencies = dependencies
107 function alphasort (a, b) {
114 function isCruft (data) {
115 return data.extraneous && data.error && data.error.code === 'ENOTDIR'
118 function getLite (data, noname, depth) {
120 , maxDepth = npm.config.get("depth")
122 if (typeof depth === 'undefined') depth = 0
123 if (!noname && data.name) lite.name = data.name
124 if (data.version) lite.version = data.version
125 if (data.extraneous) {
126 lite.extraneous = true
127 lite.problems = lite.problems || []
128 lite.problems.push( "extraneous: "
129 + data.name + "@" + data.version
130 + " " + (data.path || "") )
134 lite.from = data._from
137 lite.resolved = data._resolved
141 lite.problems = lite.problems || []
142 lite.problems.push( "invalid: "
143 + data.name + "@" + data.version
144 + " " + (data.path || "") )
147 if (data.peerInvalid) {
148 lite.peerInvalid = true
149 lite.problems = lite.problems || []
150 lite.problems.push( "peer invalid: "
151 + data.name + "@" + data.version
152 + " " + (data.path || "") )
155 if (data.dependencies) {
156 var deps = Object.keys(data.dependencies)
157 if (deps.length) lite.dependencies = deps.map(function (d) {
158 var dep = data.dependencies[d]
159 if (typeof dep === "string") {
160 lite.problems = lite.problems || []
162 if (data.depth > maxDepth) {
163 p = "max depth reached: "
169 + data.name + "@" + data.version
170 lite.problems.push(p)
171 return [d, { required: dep.requiredBy, missing: true }]
172 } else if (dep.peerMissing) {
173 lite.problems = lite.problems || []
174 dep.peerMissing.forEach(function (missing) {
175 var pdm = 'peer dep missing: ' +
179 lite.problems.push(pdm)
181 return [d, { required: dep, peerMissing: true }]
182 } else if (npm.config.get('json')) {
183 if (depth === maxDepth) delete dep.dependencies
184 return [d, getLite(dep, true, depth + 1)]
186 return [d, getLite(dep, true)]
187 }).reduce(function (deps, d) {
189 lite.problems = lite.problems || []
190 lite.problems.push.apply(lite.problems, d[1].problems)
199 function bfsify (root, args, current, queue, seen) {
200 // walk over the data, and turn it from this:
203 // | `-- a (truncated)
210 current = current || root
212 seen = seen || [root]
213 var deps = current.dependencies = current.dependencies || {}
214 Object.keys(deps).forEach(function (d) {
216 if (typeof dep !== "object") return
217 if (seen.indexOf(dep) !== -1) {
218 if (npm.config.get("parseable") || !npm.config.get("long")) {
222 dep = deps[d] = Object.create(dep)
223 dep.dependencies = {}
231 // if there were args, then only show the paths to found nodes.
232 return filterFound(root, args)
234 return bfsify(root, args, queue.shift(), queue, seen)
237 function filterFound (root, args) {
238 if (!args.length) return root
239 var deps = root.dependencies
240 if (deps) Object.keys(deps).forEach(function (d) {
241 var dep = filterFound(deps[d], args)
243 // see if this one itself matches
245 for (var i = 0; !found && i < args.length; i ++) {
246 if (d === args[i][0]) {
247 found = semver.satisfies(dep.version, args[i][1], true)
250 // included explicitly
251 if (found) dep._found = true
252 // included because a child was included
253 if (dep._found && !root._found) root._found = 1
255 if (!dep._found) delete deps[d]
257 if (!root._found) root._found = false
261 function makeArchy (data, long, dir) {
262 var out = makeArchy_(data, long, dir, 0)
263 return archy(out, "", { unicode: npm.config.get("unicode") })
266 function makeArchy_ (data, long, dir, depth, parent, d) {
267 if (typeof data === "string") {
268 if (depth -1 <= npm.config.get("depth")) {
270 var unmet = "UNMET DEPENDENCY"
272 unmet = color.bgBlack(color.red(unmet))
274 data = unmet + " " + d + "@" + data
282 // the top level is a bit special.
283 out.label = data._id || ""
284 if (data._found === true && data._id) {
286 out.label = color.bgBlack(color.yellow(out.label.trim())) + " "
288 out.label = out.label.trim() + " "
291 if (data.link) out.label += " -> " + data.link
294 if (data.realName !== data.name) out.label += " ("+data.realName+")"
295 var invalid = "invalid"
296 if (npm.color) invalid = color.bgBlack(color.red(invalid))
297 out.label += " " + invalid
300 if (data.peerInvalid) {
301 var peerInvalid = "peer invalid"
302 if (npm.color) peerInvalid = color.bgBlack(color.red(peerInvalid))
303 out.label += " " + peerInvalid
306 if (data.extraneous && data.path !== dir) {
307 var extraneous = "extraneous"
308 if (npm.color) extraneous = color.bgBlack(color.green(extraneous))
309 out.label += " " + extraneous
312 // add giturl to name@version
313 if (data._resolved) {
314 var type = npa(data._resolved).type
315 var isGit = type === 'git' || type === 'hosted'
317 out.label += ' (' + data._resolved + ')'
322 if (dir === data.path) out.label += "\n" + dir
323 out.label += "\n" + getExtras(data, dir)
324 } else if (dir === data.path) {
325 if (out.label) out.label += " "
329 // now all the children.
331 if (depth <= npm.config.get("depth")) {
332 out.nodes = Object.keys(data.dependencies || {})
333 .sort(alphasort).map(function (d) {
334 return makeArchy_(data.dependencies[d], long, dir, depth + 1, data, d)
338 if (out.nodes.length === 0 && data.path === dir) {
339 out.nodes = ["(empty)"]
345 function getExtras (data) {
348 if (data.description) extras.push(data.description)
349 if (data.repository) extras.push(data.repository.url)
350 if (data.homepage) extras.push(data.homepage)
352 var from = data._from
353 if (from.indexOf(data.name + "@") === 0) {
354 from = from.substr(data.name.length + 1)
356 var u = url.parse(from)
357 if (u.protocol) extras.push(from)
359 return extras.join("\n")
363 function makeParseable (data, long, dir, depth, parent, d) {
366 return [ makeParseable_(data, long, dir, depth, parent, d) ]
367 .concat(Object.keys(data.dependencies || {})
368 .sort(alphasort).map(function (d) {
369 return makeParseable(data.dependencies[d], long, dir, depth + 1, data, d)
371 .filter(function (x) { return x })
375 function makeParseable_ (data, long, dir, depth, parent, d) {
376 if (data.hasOwnProperty("_found") && data._found !== true) return ""
378 if (typeof data === "string") {
379 if (data.depth < npm.config.get("depth")) {
380 data = npm.config.get("long")
381 ? path.resolve(parent.path, "node_modules", d)
382 + ":"+d+"@"+JSON.stringify(data)+":INVALID:MISSING"
385 data = path.resolve(data.path || "", "node_modules", d || "")
386 + (npm.config.get("long")
387 ? ":" + d + "@" + JSON.stringify(data)
388 + ":" // no realpath resolved
396 if (!npm.config.get("long")) return data.path
399 + ":" + (data._id || "")
400 + ":" + (data.realPath !== data.path ? data.realPath : "")
401 + (data.extraneous ? ":EXTRANEOUS" : "")
402 + (data.invalid ? ":INVALID" : "")
403 + (data.peerInvalid ? ":PEERINVALID" : "")