UNPKG

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