ls.js 16 KB

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