1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | module.exports = exports = ls
|
8 |
|
9 | var path = require('path')
|
10 | var url = require('url')
|
11 | var readPackageTree = require('read-package-tree')
|
12 | var archy = require('archy')
|
13 | var semver = require('semver')
|
14 | var color = require('ansicolors')
|
15 | var moduleName = require('./utils/module-name.js')
|
16 | var npa = require('npm-package-arg')
|
17 | var sortedObject = require('sorted-object')
|
18 | var npm = require('./npm.js')
|
19 | var mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js')
|
20 | var computeMetadata = require('./install/deps.js').computeMetadata
|
21 | var readShrinkwrap = require('./install/read-shrinkwrap.js')
|
22 | var packageId = require('./utils/package-id.js')
|
23 | var usage = require('./utils/usage')
|
24 | var output = require('./utils/output.js')
|
25 |
|
26 | ls.usage = usage(
|
27 | 'ls',
|
28 | 'npm ls [[<@scope>/]<pkg> ...]'
|
29 | )
|
30 |
|
31 | ls.completion = require('./utils/completion/installed-deep.js')
|
32 |
|
33 | function ls (args, silent, cb) {
|
34 | if (typeof cb !== 'function') {
|
35 | cb = silent
|
36 | silent = false
|
37 | }
|
38 | var dir = path.resolve(npm.dir, '..')
|
39 | readPackageTree(dir, function (_, physicalTree) {
|
40 | if (!physicalTree) physicalTree = {package: {}, path: dir}
|
41 | physicalTree.isTop = true
|
42 | readShrinkwrap.andInflate(physicalTree, function () {
|
43 | lsFromTree(dir, computeMetadata(physicalTree), args, silent, cb)
|
44 | })
|
45 | })
|
46 | }
|
47 |
|
48 | function inList (list, value) {
|
49 | return list.indexOf(value) !== -1
|
50 | }
|
51 |
|
52 | var lsFromTree = ls.fromTree = function (dir, physicalTree, args, silent, cb) {
|
53 | if (typeof cb !== 'function') {
|
54 | cb = silent
|
55 | silent = false
|
56 | }
|
57 |
|
58 |
|
59 | if (!args) {
|
60 | args = []
|
61 | } else {
|
62 | args = args.map(function (a) {
|
63 | if (typeof a === 'object' && a.package._requested.type === 'alias') {
|
64 | return [moduleName(a), `npm:${a.package.name}@${a.package.version}`, a]
|
65 | } else if (typeof a === 'object') {
|
66 | return [a.package.name, a.package.version, a]
|
67 | } else {
|
68 | var p = npa(a)
|
69 | var name = p.name
|
70 |
|
71 |
|
72 |
|
73 | var ver = (p.rawSpec &&
|
74 | (semver.validRange(p.rawSpec) || ''))
|
75 | return [ name, ver, a ]
|
76 | }
|
77 | })
|
78 | }
|
79 |
|
80 | var data = mutateIntoLogicalTree.asReadInstalled(physicalTree)
|
81 |
|
82 | pruneNestedExtraneous(data)
|
83 | filterByEnv(data)
|
84 | filterByLink(data)
|
85 |
|
86 | var unlooped = filterFound(unloop(data), args)
|
87 | var lite = getLite(unlooped)
|
88 |
|
89 | if (silent) return cb(null, data, lite)
|
90 |
|
91 | var long = npm.config.get('long')
|
92 | var json = npm.config.get('json')
|
93 | var out
|
94 | if (json) {
|
95 | var seen = new Set()
|
96 | var d = long ? unlooped : lite
|
97 |
|
98 | out = JSON.stringify(d, function (k, o) {
|
99 | if (typeof o === 'object') {
|
100 | if (seen.has(o)) return '[Circular]'
|
101 | seen.add(o)
|
102 | }
|
103 | return o
|
104 | }, 2)
|
105 | } else if (npm.config.get('parseable')) {
|
106 | out = makeParseable(unlooped, long, dir)
|
107 | } else if (data) {
|
108 | out = makeArchy(unlooped, long, dir)
|
109 | }
|
110 | output(out)
|
111 |
|
112 | if (args.length && !data._found) process.exitCode = 1
|
113 |
|
114 | var er
|
115 |
|
116 | if (lite.problems && lite.problems.length) {
|
117 | er = lite.problems.join('\n')
|
118 | }
|
119 | cb(er, data, lite)
|
120 | }
|
121 |
|
122 | function pruneNestedExtraneous (data, visited) {
|
123 | visited = visited || []
|
124 | visited.push(data)
|
125 | for (var i in data.dependencies) {
|
126 | if (data.dependencies[i].extraneous) {
|
127 | data.dependencies[i].dependencies = {}
|
128 | } else if (visited.indexOf(data.dependencies[i]) === -1) {
|
129 | pruneNestedExtraneous(data.dependencies[i], visited)
|
130 | }
|
131 | }
|
132 | }
|
133 |
|
134 | function filterByEnv (data) {
|
135 | var dev = npm.config.get('dev') || /^dev(elopment)?$/.test(npm.config.get('only'))
|
136 | var production = npm.config.get('production') || /^prod(uction)?$/.test(npm.config.get('only'))
|
137 | var dependencies = {}
|
138 | var devKeys = Object.keys(data.devDependencies || [])
|
139 | var prodKeys = Object.keys(data._dependencies || [])
|
140 | Object.keys(data.dependencies).forEach(function (name) {
|
141 | if (!dev && inList(devKeys, name) && !inList(prodKeys, name) && data.dependencies[name].missing) {
|
142 | return
|
143 | }
|
144 |
|
145 | if ((dev && inList(devKeys, name)) ||
|
146 | (production && inList(prodKeys, name)) ||
|
147 | (!dev && !production)) {
|
148 | dependencies[name] = data.dependencies[name]
|
149 | }
|
150 | })
|
151 | data.dependencies = dependencies
|
152 | }
|
153 |
|
154 | function filterByLink (data) {
|
155 | if (npm.config.get('link')) {
|
156 | var dependencies = {}
|
157 | Object.keys(data.dependencies).forEach(function (name) {
|
158 | var dependency = data.dependencies[name]
|
159 | if (dependency.link) {
|
160 | dependencies[name] = dependency
|
161 | }
|
162 | })
|
163 | data.dependencies = dependencies
|
164 | }
|
165 | }
|
166 |
|
167 | function alphasort (a, b) {
|
168 | a = a.toLowerCase()
|
169 | b = b.toLowerCase()
|
170 | return a > b ? 1
|
171 | : a < b ? -1 : 0
|
172 | }
|
173 |
|
174 | function isCruft (data) {
|
175 | return data.extraneous && data.error && data.error.code === 'ENOTDIR'
|
176 | }
|
177 |
|
178 | function getLite (data, noname, depth) {
|
179 | var lite = {}
|
180 |
|
181 | if (isCruft(data)) return lite
|
182 |
|
183 | var maxDepth = npm.config.get('depth')
|
184 |
|
185 | if (typeof depth === 'undefined') depth = 0
|
186 | if (!noname && data.name) lite.name = data.name
|
187 | if (data.version) lite.version = data.version
|
188 | if (data.extraneous) {
|
189 | lite.extraneous = true
|
190 | lite.problems = lite.problems || []
|
191 | lite.problems.push('extraneous: ' + packageId(data) + ' ' + (data.path || ''))
|
192 | }
|
193 |
|
194 | if (data.error && data.path !== path.resolve(npm.globalDir, '..') &&
|
195 | (data.error.code !== 'ENOENT' || noname)) {
|
196 | lite.invalid = true
|
197 | lite.problems = lite.problems || []
|
198 | var message = data.error.message
|
199 | lite.problems.push('error in ' + data.path + ': ' + message)
|
200 | }
|
201 |
|
202 | if (data._from) {
|
203 | lite.from = data._from
|
204 | }
|
205 |
|
206 | if (data._resolved) {
|
207 | lite.resolved = data._resolved
|
208 | }
|
209 |
|
210 | if (data.invalid) {
|
211 | lite.invalid = true
|
212 | lite.problems = lite.problems || []
|
213 | lite.problems.push('invalid: ' +
|
214 | packageId(data) +
|
215 | ' ' + (data.path || ''))
|
216 | }
|
217 |
|
218 | if (data.peerInvalid) {
|
219 | lite.peerInvalid = true
|
220 | lite.problems = lite.problems || []
|
221 | lite.problems.push('peer dep not met: ' +
|
222 | packageId(data) +
|
223 | ' ' + (data.path || ''))
|
224 | }
|
225 |
|
226 | var deps = (data.dependencies && Object.keys(data.dependencies)) || []
|
227 | if (deps.length) {
|
228 | lite.dependencies = deps.map(function (d) {
|
229 | var dep = data.dependencies[d]
|
230 | if (dep.missing && !dep.optional) {
|
231 | lite.problems = lite.problems || []
|
232 | var p
|
233 | if (data.depth > maxDepth) {
|
234 | p = 'max depth reached: '
|
235 | } else {
|
236 | p = 'missing: '
|
237 | }
|
238 | p += d + '@' + dep.requiredBy +
|
239 | ', required by ' +
|
240 | packageId(data)
|
241 | lite.problems.push(p)
|
242 | if (dep.dependencies) {
|
243 | return [d, getLite(dep, true)]
|
244 | } else {
|
245 | return [d, { required: dep.requiredBy, missing: true }]
|
246 | }
|
247 | } else if (dep.peerMissing) {
|
248 | lite.problems = lite.problems || []
|
249 | dep.peerMissing.forEach(function (missing) {
|
250 | var pdm = 'peer dep missing: ' +
|
251 | missing.requires +
|
252 | ', required by ' +
|
253 | missing.requiredBy
|
254 | lite.problems.push(pdm)
|
255 | })
|
256 | return [d, { required: dep, peerMissing: true }]
|
257 | } else if (npm.config.get('json')) {
|
258 | if (depth === maxDepth) delete dep.dependencies
|
259 | return [d, getLite(dep, true, depth + 1)]
|
260 | }
|
261 | return [d, getLite(dep, true)]
|
262 | }).reduce(function (deps, d) {
|
263 | if (d[1].problems) {
|
264 | lite.problems = lite.problems || []
|
265 | lite.problems.push.apply(lite.problems, d[1].problems)
|
266 | }
|
267 | deps[d[0]] = d[1]
|
268 | return deps
|
269 | }, {})
|
270 | }
|
271 | return lite
|
272 | }
|
273 |
|
274 | function unloop (root) {
|
275 | var queue = [root]
|
276 | var seen = new Set()
|
277 | seen.add(root)
|
278 |
|
279 | while (queue.length) {
|
280 | var current = queue.shift()
|
281 | var deps = current.dependencies = current.dependencies || {}
|
282 | Object.keys(deps).forEach(function (d) {
|
283 | var dep = deps[d]
|
284 | if (dep.missing && !dep.dependencies) return
|
285 | if (dep.path && seen.has(dep)) {
|
286 | dep = deps[d] = Object.assign({}, dep)
|
287 | dep.dependencies = {}
|
288 | dep._deduped = path.relative(root.path, dep.path).replace(/node_modules\//g, '')
|
289 | return
|
290 | }
|
291 | seen.add(dep)
|
292 | queue.push(dep)
|
293 | })
|
294 | }
|
295 |
|
296 | return root
|
297 | }
|
298 |
|
299 | function filterFound (root, args) {
|
300 | if (!args.length) return root
|
301 | if (!root.dependencies) return root
|
302 |
|
303 |
|
304 | var toMark = [root]
|
305 | while (toMark.length) {
|
306 | var markPkg = toMark.shift()
|
307 | var markDeps = markPkg.dependencies
|
308 | if (!markDeps) continue
|
309 | Object.keys(markDeps).forEach(function (depName) {
|
310 | var dep = markDeps[depName]
|
311 | if (dep.peerMissing && !dep._from) return
|
312 | dep._parent = markPkg
|
313 | for (var ii = 0; ii < args.length; ii++) {
|
314 | var argName = args[ii][0]
|
315 | var argVersion = args[ii][1]
|
316 | var argRaw = args[ii][2]
|
317 | var found
|
318 | if (typeof argRaw === 'object') {
|
319 | if (dep.path === argRaw.path) {
|
320 | found = true
|
321 | }
|
322 | } else if (depName === argName && argVersion) {
|
323 | found = semver.satisfies(dep.version, argVersion, true)
|
324 | } else if (depName === argName) {
|
325 |
|
326 | found = true
|
327 | }
|
328 | if (found) {
|
329 | dep._found = 'explicit'
|
330 | var parent = dep._parent
|
331 | while (parent && !parent._found && !parent._deduped) {
|
332 | parent._found = 'implicit'
|
333 | parent = parent._parent
|
334 | }
|
335 | break
|
336 | }
|
337 | }
|
338 | toMark.push(dep)
|
339 | })
|
340 | }
|
341 | var toTrim = [root]
|
342 | while (toTrim.length) {
|
343 | var trimPkg = toTrim.shift()
|
344 | var trimDeps = trimPkg.dependencies
|
345 | if (!trimDeps) continue
|
346 | trimPkg.dependencies = {}
|
347 | Object.keys(trimDeps).forEach(function (name) {
|
348 | var dep = trimDeps[name]
|
349 | if (!dep._found) return
|
350 | if (dep._found === 'implicit' && dep._deduped) return
|
351 | trimPkg.dependencies[name] = dep
|
352 | toTrim.push(dep)
|
353 | })
|
354 | }
|
355 | return root
|
356 | }
|
357 |
|
358 | function makeArchy (data, long, dir) {
|
359 | var out = makeArchy_(data, long, dir, 0)
|
360 | return archy(out, '', { unicode: npm.config.get('unicode') })
|
361 | }
|
362 |
|
363 | function makeArchy_ (data, long, dir, depth, parent, d) {
|
364 | if (data.missing) {
|
365 | if (depth - 1 <= npm.config.get('depth')) {
|
366 |
|
367 | var unmet = 'UNMET ' + (data.optional ? 'OPTIONAL ' : '') + 'DEPENDENCY'
|
368 | if (npm.color) {
|
369 | if (data.optional) {
|
370 | unmet = color.bgBlack(color.yellow(unmet))
|
371 | } else {
|
372 | unmet = color.bgBlack(color.red(unmet))
|
373 | }
|
374 | }
|
375 | var label = data._id || (d + '@' + data.requiredBy)
|
376 | if (data._found === 'explicit' && data._id) {
|
377 | if (npm.color) {
|
378 | label = color.bgBlack(color.yellow(label.trim())) + ' '
|
379 | } else {
|
380 | label = label.trim() + ' '
|
381 | }
|
382 | }
|
383 | return {
|
384 | label: unmet + ' ' + label,
|
385 | nodes: Object.keys(data.dependencies || {})
|
386 | .sort(alphasort).filter(function (d) {
|
387 | return !isCruft(data.dependencies[d])
|
388 | }).map(function (d) {
|
389 | return makeArchy_(sortedObject(data.dependencies[d]), long, dir, depth + 1, data, d)
|
390 | })
|
391 | }
|
392 | } else {
|
393 | return {label: d + '@' + data.requiredBy}
|
394 | }
|
395 | }
|
396 |
|
397 | var out = {}
|
398 | if (data._requested && data._requested.type === 'alias') {
|
399 | out.label = `${d}@npm:${data._id}`
|
400 | } else {
|
401 | out.label = data._id || ''
|
402 | }
|
403 | if (data._found === 'explicit' && data._id) {
|
404 | if (npm.color) {
|
405 | out.label = color.bgBlack(color.yellow(out.label.trim())) + ' '
|
406 | } else {
|
407 | out.label = out.label.trim() + ' '
|
408 | }
|
409 | }
|
410 | if (data.link) out.label += ' -> ' + data.link
|
411 |
|
412 | if (data._deduped) {
|
413 | if (npm.color) {
|
414 | out.label += ' ' + color.brightBlack('deduped')
|
415 | } else {
|
416 | out.label += ' deduped'
|
417 | }
|
418 | }
|
419 |
|
420 | if (data.invalid) {
|
421 | if (data.realName !== data.name) out.label += ' (' + data.realName + ')'
|
422 | var invalid = 'invalid'
|
423 | if (npm.color) invalid = color.bgBlack(color.red(invalid))
|
424 | out.label += ' ' + invalid
|
425 | }
|
426 |
|
427 | if (data.peerInvalid) {
|
428 | var peerInvalid = 'peer invalid'
|
429 | if (npm.color) peerInvalid = color.bgBlack(color.red(peerInvalid))
|
430 | out.label += ' ' + peerInvalid
|
431 | }
|
432 |
|
433 | if (data.peerMissing) {
|
434 | var peerMissing = 'UNMET PEER DEPENDENCY'
|
435 |
|
436 | if (npm.color) peerMissing = color.bgBlack(color.red(peerMissing))
|
437 | out.label = peerMissing + ' ' + out.label
|
438 | }
|
439 |
|
440 | if (data.extraneous && data.path !== dir) {
|
441 | var extraneous = 'extraneous'
|
442 | if (npm.color) extraneous = color.bgBlack(color.green(extraneous))
|
443 | out.label += ' ' + extraneous
|
444 | }
|
445 |
|
446 | if (data.error && depth) {
|
447 | var message = data.error.message
|
448 | if (message.indexOf('\n')) message = message.slice(0, message.indexOf('\n'))
|
449 | var error = 'error: ' + message
|
450 | if (npm.color) error = color.bgRed(color.brightWhite(error))
|
451 | out.label += ' ' + error
|
452 | }
|
453 |
|
454 |
|
455 | if (data._resolved) {
|
456 | try {
|
457 | var type = npa(data._resolved).type
|
458 | var isGit = type === 'git' || type === 'hosted'
|
459 | if (isGit) {
|
460 | out.label += ' (' + data._resolved + ')'
|
461 | }
|
462 | } catch (ex) {
|
463 |
|
464 | }
|
465 | }
|
466 |
|
467 | if (long) {
|
468 | if (dir === data.path) out.label += '\n' + dir
|
469 | out.label += '\n' + getExtras(data)
|
470 | } else if (dir === data.path) {
|
471 | if (out.label) out.label += ' '
|
472 | out.label += dir
|
473 | }
|
474 |
|
475 |
|
476 | out.nodes = []
|
477 | if (depth <= npm.config.get('depth')) {
|
478 | out.nodes = Object.keys(data.dependencies || {})
|
479 | .sort(alphasort).filter(function (d) {
|
480 | return !isCruft(data.dependencies[d])
|
481 | }).map(function (d) {
|
482 | return makeArchy_(sortedObject(data.dependencies[d]), long, dir, depth + 1, data, d)
|
483 | })
|
484 | }
|
485 |
|
486 | if (out.nodes.length === 0 && data.path === dir) {
|
487 | out.nodes = ['(empty)']
|
488 | }
|
489 |
|
490 | return out
|
491 | }
|
492 |
|
493 | function getExtras (data) {
|
494 | var extras = []
|
495 |
|
496 | if (data.description) extras.push(data.description)
|
497 | if (data.repository) extras.push(data.repository.url)
|
498 | if (data.homepage) extras.push(data.homepage)
|
499 | if (data._from) {
|
500 | var from = data._from
|
501 | if (from.indexOf(data.name + '@') === 0) {
|
502 | from = from.substr(data.name.length + 1)
|
503 | }
|
504 | var u = url.parse(from)
|
505 | if (u.protocol) extras.push(from)
|
506 | }
|
507 | return extras.join('\n')
|
508 | }
|
509 |
|
510 | function makeParseable (data, long, dir, depth, parent, d) {
|
511 | if (data._deduped) return []
|
512 | depth = depth || 0
|
513 | if (depth > npm.config.get('depth')) return [ makeParseable_(data, long, dir, depth, parent, d) ]
|
514 | return [ makeParseable_(data, long, dir, depth, parent, d) ]
|
515 | .concat(Object.keys(data.dependencies || {})
|
516 | .sort(alphasort).map(function (d) {
|
517 | return makeParseable(data.dependencies[d], long, dir, depth + 1, data, d)
|
518 | }))
|
519 | .filter(function (x) { return x && x.length })
|
520 | .join('\n')
|
521 | }
|
522 |
|
523 | function makeParseable_ (data, long, dir, depth, parent, d) {
|
524 | if (data.hasOwnProperty('_found') && data._found !== 'explicit') return ''
|
525 |
|
526 | if (data.missing) {
|
527 | if (depth < npm.config.get('depth')) {
|
528 | data = npm.config.get('long')
|
529 | ? path.resolve(parent.path, 'node_modules', d) +
|
530 | ':' + d + '@' + JSON.stringify(data.requiredBy) + ':INVALID:MISSING'
|
531 | : ''
|
532 | } else {
|
533 | data = path.resolve(dir || '', 'node_modules', d || '') +
|
534 | (npm.config.get('long')
|
535 | ? ':' + d + '@' + JSON.stringify(data.requiredBy) +
|
536 | ':' +
|
537 | ':MAXDEPTH'
|
538 | : '')
|
539 | }
|
540 |
|
541 | return data
|
542 | }
|
543 |
|
544 | if (!npm.config.get('long')) return data.path
|
545 |
|
546 | return data.path +
|
547 | ':' + (data._id || '') +
|
548 | ':' + (data.realPath !== data.path ? data.realPath : '') +
|
549 | (data.extraneous ? ':EXTRANEOUS' : '') +
|
550 | (data.error && data.path !== path.resolve(npm.globalDir, '..') ? ':ERROR' : '') +
|
551 | (data.invalid ? ':INVALID' : '') +
|
552 | (data.peerInvalid ? ':PEERINVALID' : '') +
|
553 | (data.peerMissing ? ':PEERINVALID:MISSING' : '')
|
554 | }
|