UNPKG

10 kBJavaScriptView Raw
1
2// show the installed versions of packages
3//
4// --parseable creates output like this:
5// <fullpath>:<name@ver>:<realpath>:<flags>
6// Flags are a :-separated list of zero or more indicators
7
8module.exports = exports = ls
9
10var 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")
19
20ls.usage = "npm ls"
21
22ls.completion = require("./utils/completion/installed-deep.js")
23
24function ls (args, silent, cb) {
25 if (typeof cb !== "function") cb = silent, silent = false
26
27 var dir = path.resolve(npm.dir, "..")
28
29 // npm ls 'foo@~1.3' bar 'baz@<2'
30 if (!args) args = []
31 else args = args.map(function (a) {
32 var p = npa(a)
33 , name = p.name
34 , ver = semver.validRange(p.rawSpec) || ""
35
36 return [ name, ver ]
37 })
38
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)
43 var bfs = bfsify(data, args)
44 , lite = getLite(bfs)
45
46 if (er || silent) return cb(er, data, lite)
47
48 var long = npm.config.get("long")
49 , json = npm.config.get("json")
50 , out
51 if (json) {
52 var seen = []
53 var d = long ? bfs : lite
54 // the raw data can be circular
55 out = JSON.stringify(d, function (k, o) {
56 if (typeof o === "object") {
57 if (-1 !== seen.indexOf(o)) return "[Circular]"
58 seen.push(o)
59 }
60 return o
61 }, 2)
62 } else if (npm.config.get("parseable")) {
63 out = makeParseable(bfs, long, dir)
64 } else if (data) {
65 out = makeArchy(bfs, long, dir)
66 }
67 console.log(out)
68
69 if (args.length && !data._found) process.exitCode = 1
70
71 // if any errors were found, then complain and exit status 1
72 if (lite.problems && lite.problems.length) {
73 er = lite.problems.join("\n")
74 }
75 cb(er, data, lite)
76 })
77}
78
79function pruneNestedExtraneous (data, visited) {
80 visited = visited || []
81 visited.push(data)
82 for (var i in data.dependencies) {
83 if (data.dependencies[i].extraneous) {
84 data.dependencies[i].dependencies = {}
85 } else if (visited.indexOf(data.dependencies[i]) === -1) {
86 pruneNestedExtraneous(data.dependencies[i], visited)
87 }
88 }
89}
90
91function alphasort (a, b) {
92 a = a.toLowerCase()
93 b = b.toLowerCase()
94 return a > b ? 1
95 : a < b ? -1 : 0
96}
97
98function getLite (data, noname) {
99 var lite = {}
100 , maxDepth = npm.config.get("depth")
101
102 if (!noname && data.name) lite.name = data.name
103 if (data.version) lite.version = data.version
104 if (data.extraneous) {
105 lite.extraneous = true
106 lite.problems = lite.problems || []
107 lite.problems.push( "extraneous: "
108 + data.name + "@" + data.version
109 + " " + (data.path || "") )
110 }
111
112 if (data._from)
113 lite.from = data._from
114
115 if (data._resolved)
116 lite.resolved = data._resolved
117
118 if (data.invalid) {
119 lite.invalid = true
120 lite.problems = lite.problems || []
121 lite.problems.push( "invalid: "
122 + data.name + "@" + data.version
123 + " " + (data.path || "") )
124 }
125
126 if (data.peerInvalid) {
127 lite.peerInvalid = true
128 lite.problems = lite.problems || []
129 lite.problems.push( "peer invalid: "
130 + data.name + "@" + data.version
131 + " " + (data.path || "") )
132 }
133
134 if (data.dependencies) {
135 var deps = Object.keys(data.dependencies)
136 if (deps.length) lite.dependencies = deps.map(function (d) {
137 var dep = data.dependencies[d]
138 if (typeof dep === "string") {
139 lite.problems = lite.problems || []
140 var p
141 if (data.depth > maxDepth) {
142 p = "max depth reached: "
143 } else {
144 p = "missing: "
145 }
146 p += d + "@" + dep
147 + ", required by "
148 + data.name + "@" + data.version
149 lite.problems.push(p)
150 return [d, { required: dep, missing: true }]
151 }
152 return [d, getLite(dep, true)]
153 }).reduce(function (deps, d) {
154 if (d[1].problems) {
155 lite.problems = lite.problems || []
156 lite.problems.push.apply(lite.problems, d[1].problems)
157 }
158 deps[d[0]] = d[1]
159 return deps
160 }, {})
161 }
162 return lite
163}
164
165function bfsify (root, args, current, queue, seen) {
166 // walk over the data, and turn it from this:
167 // +-- a
168 // | `-- b
169 // | `-- a (truncated)
170 // `--b (truncated)
171 // into this:
172 // +-- a
173 // `-- b
174 // which looks nicer
175 args = args || []
176 current = current || root
177 queue = queue || []
178 seen = seen || [root]
179 var deps = current.dependencies = current.dependencies || {}
180 Object.keys(deps).forEach(function (d) {
181 var dep = deps[d]
182 if (typeof dep !== "object") return
183 if (seen.indexOf(dep) !== -1) {
184 if (npm.config.get("parseable") || !npm.config.get("long")) {
185 delete deps[d]
186 return
187 } else {
188 dep = deps[d] = Object.create(dep)
189 dep.dependencies = {}
190 }
191 }
192 queue.push(dep)
193 seen.push(dep)
194 })
195
196 if (!queue.length) {
197 // if there were args, then only show the paths to found nodes.
198 return filterFound(root, args)
199 }
200 return bfsify(root, args, queue.shift(), queue, seen)
201}
202
203function filterFound (root, args) {
204 if (!args.length) return root
205 var deps = root.dependencies
206 if (deps) Object.keys(deps).forEach(function (d) {
207 var dep = filterFound(deps[d], args)
208
209 // see if this one itself matches
210 var found = false
211 for (var i = 0; !found && i < args.length; i ++) {
212 if (d === args[i][0]) {
213 found = semver.satisfies(dep.version, args[i][1], true)
214 }
215 }
216 // included explicitly
217 if (found) dep._found = true
218 // included because a child was included
219 if (dep._found && !root._found) root._found = 1
220 // not included
221 if (!dep._found) delete deps[d]
222 })
223 if (!root._found) root._found = false
224 return root
225}
226
227function makeArchy (data, long, dir) {
228 var out = makeArchy_(data, long, dir, 0)
229 return archy(out, "", { unicode: npm.config.get("unicode") })
230}
231
232function makeArchy_ (data, long, dir, depth, parent, d) {
233 if (typeof data === "string") {
234 if (depth -1 <= npm.config.get("depth")) {
235 // just missing
236 var unmet = "UNMET DEPENDENCY"
237 if (npm.color) {
238 unmet = color.bgBlack(color.red(unmet))
239 }
240 data = unmet + " " + d + "@" + data
241 } else {
242 data = d+"@"+ data
243 }
244 return data
245 }
246
247 var out = {}
248 // the top level is a bit special.
249 out.label = data._id || ""
250 if (data._found === true && data._id) {
251 if (npm.color) {
252 out.label = color.bgBlack(color.yellow(out.label.trim())) + " "
253 }
254 else {
255 out.label = out.label.trim() + " "
256 }
257 }
258 if (data.link) out.label += " -> " + data.link
259
260 if (data.invalid) {
261 if (data.realName !== data.name) out.label += " ("+data.realName+")"
262 var invalid = "invalid"
263 if (npm.color) invalid = color.bgBlack(color.red(invalid))
264 out.label += " " + invalid
265 }
266
267 if (data.peerInvalid) {
268 var peerInvalid = "peer invalid"
269 if (npm.color) peerInvalid = color.bgBlack(color.red(peerInvalid))
270 out.label += " " + peerInvalid
271 }
272
273 if (data.extraneous && data.path !== dir) {
274 var extraneous = "extraneous"
275 if (npm.color) extraneous = color.bgBlack(color.green(extraneous))
276 out.label += " " + extraneous
277 }
278
279 // add giturl to name@version
280 if (data._resolved) {
281 if (npa(data._resolved).type === "git")
282 out.label += " (" + data._resolved + ")"
283 }
284
285 if (long) {
286 if (dir === data.path) out.label += "\n" + dir
287 out.label += "\n" + getExtras(data, dir)
288 } else if (dir === data.path) {
289 if (out.label) out.label += " "
290 out.label += dir
291 }
292
293 // now all the children.
294 out.nodes = Object.keys(data.dependencies || {})
295 .sort(alphasort).map(function (d) {
296 return makeArchy_(data.dependencies[d], long, dir, depth + 1, data, d)
297 })
298
299 if (out.nodes.length === 0 && data.path === dir) {
300 out.nodes = ["(empty)"]
301 }
302
303 return out
304}
305
306function getExtras (data) {
307 var extras = []
308
309 if (data.description) extras.push(data.description)
310 if (data.repository) extras.push(data.repository.url)
311 if (data.homepage) extras.push(data.homepage)
312 if (data._from) {
313 var from = data._from
314 if (from.indexOf(data.name + "@") === 0) {
315 from = from.substr(data.name.length + 1)
316 }
317 var u = url.parse(from)
318 if (u.protocol) extras.push(from)
319 }
320 return extras.join("\n")
321}
322
323
324function makeParseable (data, long, dir, depth, parent, d) {
325 depth = depth || 0
326
327 return [ makeParseable_(data, long, dir, depth, parent, d) ]
328 .concat(Object.keys(data.dependencies || {})
329 .sort(alphasort).map(function (d) {
330 return makeParseable(data.dependencies[d], long, dir, depth + 1, data, d)
331 }))
332 .filter(function (x) { return x })
333 .join("\n")
334}
335
336function makeParseable_ (data, long, dir, depth, parent, d) {
337 if (data.hasOwnProperty("_found") && data._found !== true) return ""
338
339 if (typeof data === "string") {
340 if (data.depth < npm.config.get("depth")) {
341 data = npm.config.get("long")
342 ? path.resolve(parent.path, "node_modules", d)
343 + ":"+d+"@"+JSON.stringify(data)+":INVALID:MISSING"
344 : ""
345 } else {
346 data = path.resolve(data.path || "", "node_modules", d || "")
347 + (npm.config.get("long")
348 ? ":" + d + "@" + JSON.stringify(data)
349 + ":" // no realpath resolved
350 + ":MAXDEPTH"
351 : "")
352 }
353
354 return data
355 }
356
357 if (!npm.config.get("long")) return data.path
358
359 return data.path
360 + ":" + (data._id || "")
361 + ":" + (data.realPath !== data.path ? data.realPath : "")
362 + (data.extraneous ? ":EXTRANEOUS" : "")
363 + (data.invalid ? ":INVALID" : "")
364 + (data.peerInvalid ? ":PEERINVALID" : "")
365}