outdated.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. /*
  2. npm outdated [pkg]
  3. Does the following:
  4. 1. check for a new version of pkg
  5. If no packages are specified, then run for all installed
  6. packages.
  7. --parseable creates output like this:
  8. <fullpath>:<name@wanted>:<name@installed>:<name@latest>
  9. */
  10. module.exports = outdated
  11. outdated.usage = 'npm outdated [[<@scope>/]<pkg> ...]'
  12. outdated.completion = require('./utils/completion/installed-deep.js')
  13. const os = require('os')
  14. const url = require('url')
  15. const path = require('path')
  16. const readPackageTree = require('read-package-tree')
  17. const asyncMap = require('slide').asyncMap
  18. const color = require('ansicolors')
  19. const styles = require('ansistyles')
  20. const table = require('text-table')
  21. const semver = require('semver')
  22. const npa = require('libnpm/parse-arg')
  23. const pickManifest = require('npm-pick-manifest')
  24. const fetchPackageMetadata = require('./fetch-package-metadata.js')
  25. const mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js')
  26. const npm = require('./npm.js')
  27. const npmConfig = require('./config/figgy-config.js')
  28. const figgyPudding = require('figgy-pudding')
  29. const packument = require('libnpm/packument')
  30. const long = npm.config.get('long')
  31. const isExtraneous = require('./install/is-extraneous.js')
  32. const computeMetadata = require('./install/deps.js').computeMetadata
  33. const computeVersionSpec = require('./install/deps.js').computeVersionSpec
  34. const moduleName = require('./utils/module-name.js')
  35. const output = require('./utils/output.js')
  36. const ansiTrim = require('./utils/ansi-trim')
  37. const OutdatedConfig = figgyPudding({
  38. also: {},
  39. color: {},
  40. depth: {},
  41. dev: 'development',
  42. development: {},
  43. global: {},
  44. json: {},
  45. only: {},
  46. parseable: {},
  47. prod: 'production',
  48. production: {},
  49. save: {},
  50. 'save-dev': {},
  51. 'save-optional': {}
  52. })
  53. function uniq (list) {
  54. // we maintain the array because we need an array, not iterator, return
  55. // value.
  56. var uniqed = []
  57. var seen = new Set()
  58. list.forEach(function (item) {
  59. if (seen.has(item)) return
  60. seen.add(item)
  61. uniqed.push(item)
  62. })
  63. return uniqed
  64. }
  65. function andComputeMetadata (next) {
  66. return function (er, tree) {
  67. if (er) return next(er)
  68. next(null, computeMetadata(tree))
  69. }
  70. }
  71. function outdated (args, silent, cb) {
  72. if (typeof cb !== 'function') {
  73. cb = silent
  74. silent = false
  75. }
  76. let opts = OutdatedConfig(npmConfig())
  77. var dir = path.resolve(npm.dir, '..')
  78. // default depth for `outdated` is 0 (cf. `ls`)
  79. if (opts.depth === Infinity) opts = opts.concat({depth: 0})
  80. readPackageTree(dir, andComputeMetadata(function (er, tree) {
  81. if (!tree) return cb(er)
  82. mutateIntoLogicalTree(tree)
  83. outdated_(args, '', tree, {}, 0, opts, function (er, list) {
  84. list = uniq(list || []).sort(function (aa, bb) {
  85. return aa[0].path.localeCompare(bb[0].path) ||
  86. aa[1].localeCompare(bb[1])
  87. })
  88. if (er || silent ||
  89. (list.length === 0 && !opts.json)) {
  90. return cb(er, list)
  91. }
  92. if (opts.json) {
  93. output(makeJSON(list, opts))
  94. } else if (opts.parseable) {
  95. output(makeParseable(list, opts))
  96. } else {
  97. var outList = list.map(x => makePretty(x, opts))
  98. var outHead = [ 'Package',
  99. 'Current',
  100. 'Wanted',
  101. 'Latest',
  102. 'Location'
  103. ]
  104. if (long) outHead.push('Package Type', 'Homepage')
  105. var outTable = [outHead].concat(outList)
  106. if (opts.color) {
  107. outTable[0] = outTable[0].map(function (heading) {
  108. return styles.underline(heading)
  109. })
  110. }
  111. var tableOpts = {
  112. align: ['l', 'r', 'r', 'r', 'l'],
  113. stringLength: function (s) { return ansiTrim(s).length }
  114. }
  115. output(table(outTable, tableOpts))
  116. }
  117. process.exitCode = list.length ? 1 : 0
  118. cb(null, list.map(function (item) { return [item[0].parent.path].concat(item.slice(1, 7)) }))
  119. })
  120. }))
  121. }
  122. // [[ dir, dep, has, want, latest, type ]]
  123. function makePretty (p, opts) {
  124. var depname = p[1]
  125. var has = p[2]
  126. var want = p[3]
  127. var latest = p[4]
  128. var type = p[6]
  129. var deppath = p[7]
  130. var homepage = p[0].package.homepage || ''
  131. var columns = [ depname,
  132. has || 'MISSING',
  133. want,
  134. latest,
  135. deppath || 'global'
  136. ]
  137. if (long) {
  138. columns[5] = type
  139. columns[6] = homepage
  140. }
  141. if (opts.color) {
  142. columns[0] = color[has === want ? 'yellow' : 'red'](columns[0]) // dep
  143. columns[2] = color.green(columns[2]) // want
  144. columns[3] = color.magenta(columns[3]) // latest
  145. }
  146. return columns
  147. }
  148. function makeParseable (list) {
  149. return list.map(function (p) {
  150. var dep = p[0]
  151. var depname = p[1]
  152. var dir = dep.path
  153. var has = p[2]
  154. var want = p[3]
  155. var latest = p[4]
  156. var type = p[6]
  157. var out = [
  158. dir,
  159. depname + '@' + want,
  160. (has ? (depname + '@' + has) : 'MISSING'),
  161. depname + '@' + latest
  162. ]
  163. if (long) out.push(type, dep.package.homepage)
  164. return out.join(':')
  165. }).join(os.EOL)
  166. }
  167. function makeJSON (list, opts) {
  168. var out = {}
  169. list.forEach(function (p) {
  170. var dep = p[0]
  171. var depname = p[1]
  172. var dir = dep.path
  173. var has = p[2]
  174. var want = p[3]
  175. var latest = p[4]
  176. var type = p[6]
  177. if (!opts.global) {
  178. dir = path.relative(process.cwd(), dir)
  179. }
  180. out[depname] = { current: has,
  181. wanted: want,
  182. latest: latest,
  183. location: dir
  184. }
  185. if (long) {
  186. out[depname].type = type
  187. out[depname].homepage = dep.package.homepage
  188. }
  189. })
  190. return JSON.stringify(out, null, 2)
  191. }
  192. function outdated_ (args, path, tree, parentHas, depth, opts, cb) {
  193. if (!tree.package) tree.package = {}
  194. if (path && moduleName(tree)) path += ' > ' + tree.package.name
  195. if (!path && moduleName(tree)) path = tree.package.name
  196. if (depth > opts.depth) {
  197. return cb(null, [])
  198. }
  199. var types = {}
  200. var pkg = tree.package
  201. if (!tree.children) tree.children = []
  202. var deps = tree.error ? tree.children : tree.children.filter((child) => !isExtraneous(child))
  203. deps.forEach(function (dep) {
  204. types[moduleName(dep)] = 'dependencies'
  205. })
  206. Object.keys(tree.missingDeps || {}).forEach(function (name) {
  207. deps.push({
  208. package: { name: name },
  209. path: tree.path,
  210. parent: tree,
  211. isMissing: true
  212. })
  213. types[name] = 'dependencies'
  214. })
  215. // If we explicitly asked for dev deps OR we didn't ask for production deps
  216. // AND we asked to save dev-deps OR we didn't ask to save anything that's NOT
  217. // dev deps then…
  218. // (All the save checking here is because this gets called from npm-update currently
  219. // and that requires this logic around dev deps.)
  220. // FIXME: Refactor npm update to not be in terms of outdated.
  221. var dev = opts.dev || /^dev(elopment)?$/.test(opts.also)
  222. var prod = opts.production || /^prod(uction)?$/.test(opts.only)
  223. if (
  224. (dev || !prod) &&
  225. (
  226. opts['save-dev'] || (!opts.save && !opts['save-optional'])
  227. )
  228. ) {
  229. Object.keys(tree.missingDevDeps).forEach(function (name) {
  230. deps.push({
  231. package: { name: name },
  232. path: tree.path,
  233. parent: tree,
  234. isMissing: true
  235. })
  236. if (!types[name]) {
  237. types[name] = 'devDependencies'
  238. }
  239. })
  240. }
  241. if (opts['save-dev']) {
  242. deps = deps.filter(function (dep) { return pkg.devDependencies[moduleName(dep)] })
  243. deps.forEach(function (dep) {
  244. types[moduleName(dep)] = 'devDependencies'
  245. })
  246. } else if (opts.save) {
  247. // remove optional dependencies from dependencies during --save.
  248. deps = deps.filter(function (dep) { return !pkg.optionalDependencies[moduleName(dep)] })
  249. } else if (opts['save-optional']) {
  250. deps = deps.filter(function (dep) { return pkg.optionalDependencies[moduleName(dep)] })
  251. deps.forEach(function (dep) {
  252. types[moduleName(dep)] = 'optionalDependencies'
  253. })
  254. }
  255. var doUpdate = dev || (
  256. !prod &&
  257. !Object.keys(parentHas).length &&
  258. !opts.global
  259. )
  260. if (doUpdate) {
  261. Object.keys(pkg.devDependencies || {}).forEach(function (k) {
  262. if (!(k in parentHas)) {
  263. deps[k] = pkg.devDependencies[k]
  264. types[k] = 'devDependencies'
  265. }
  266. })
  267. }
  268. var has = Object.create(parentHas)
  269. tree.children.forEach(function (child) {
  270. if (moduleName(child) && child.package.private) {
  271. deps = deps.filter(function (dep) { return dep !== child })
  272. }
  273. has[moduleName(child)] = {
  274. version: child.isLink ? 'linked' : child.package.version,
  275. from: child.isLink ? 'file:' + child.path : child.package._from
  276. }
  277. })
  278. // now get what we should have, based on the dep.
  279. // if has[dep] !== shouldHave[dep], then cb with the data
  280. // otherwise dive into the folder
  281. asyncMap(deps, function (dep, cb) {
  282. var name = moduleName(dep)
  283. var required
  284. if (tree.package.dependencies && name in tree.package.dependencies) {
  285. required = tree.package.dependencies[name]
  286. } else if (tree.package.optionalDependencies && name in tree.package.optionalDependencies) {
  287. required = tree.package.optionalDependencies[name]
  288. } else if (tree.package.devDependencies && name in tree.package.devDependencies) {
  289. required = tree.package.devDependencies[name]
  290. } else if (has[name]) {
  291. required = computeVersionSpec(tree, dep)
  292. }
  293. if (!long) return shouldUpdate(args, dep, name, has, required, depth, path, opts, cb)
  294. shouldUpdate(args, dep, name, has, required, depth, path, opts, cb, types[name])
  295. }, cb)
  296. }
  297. function shouldUpdate (args, tree, dep, has, req, depth, pkgpath, opts, cb, type) {
  298. // look up the most recent version.
  299. // if that's what we already have, or if it's not on the args list,
  300. // then dive into it. Otherwise, cb() with the data.
  301. // { version: , from: }
  302. var curr = has[dep]
  303. function skip (er) {
  304. // show user that no viable version can be found
  305. if (er) return cb(er)
  306. outdated_(args,
  307. pkgpath,
  308. tree,
  309. has,
  310. depth + 1,
  311. opts,
  312. cb)
  313. }
  314. if (args.length && args.indexOf(dep) === -1) return skip()
  315. if (tree.isLink && req == null) return skip()
  316. if (req == null || req === '') req = '*'
  317. var parsed = npa.resolve(dep, req)
  318. if (parsed.type === 'directory') {
  319. if (tree.isLink) {
  320. return skip()
  321. } else {
  322. return doIt('linked', 'linked')
  323. }
  324. } else if (parsed.type === 'git') {
  325. return doIt('git', 'git')
  326. } else if (parsed.type === 'file') {
  327. return updateLocalDeps()
  328. } else if (parsed.type === 'remote') {
  329. return doIt('remote', 'remote')
  330. } else {
  331. return packument(parsed, opts.concat({
  332. 'prefer-online': true
  333. })).nodeify(updateDeps)
  334. }
  335. function doIt (wanted, latest) {
  336. let c = curr && curr.version
  337. if (parsed.type === 'alias') {
  338. c = `npm:${parsed.subSpec.name}@${c}`
  339. }
  340. if (!long) {
  341. return cb(null, [[tree, dep, c, wanted, latest, req, null, pkgpath]])
  342. }
  343. cb(null, [[tree, dep, c, wanted, latest, req, type, pkgpath]])
  344. }
  345. function updateLocalDeps (latestRegistryVersion) {
  346. fetchPackageMetadata('file:' + parsed.fetchSpec, '.', (er, localDependency) => {
  347. if (er) return cb()
  348. var wanted = localDependency.version
  349. var latest = localDependency.version
  350. if (latestRegistryVersion) {
  351. latest = latestRegistryVersion
  352. if (semver.lt(wanted, latestRegistryVersion)) {
  353. wanted = latestRegistryVersion
  354. req = dep + '@' + latest
  355. }
  356. }
  357. if (!curr || curr.version !== wanted) {
  358. doIt(wanted, latest)
  359. } else {
  360. skip()
  361. }
  362. })
  363. }
  364. function updateDeps (er, d) {
  365. if (er) return cb(er)
  366. if (parsed.type === 'alias') {
  367. req = parsed.subSpec.rawSpec
  368. }
  369. try {
  370. var l = pickManifest(d, 'latest')
  371. var m = pickManifest(d, req)
  372. } catch (er) {
  373. if (er.code === 'ETARGET' || er.code === 'E403') {
  374. return skip(er)
  375. } else {
  376. return skip()
  377. }
  378. }
  379. // check that the url origin hasn't changed (#1727) and that
  380. // there is no newer version available
  381. var dFromUrl = m._from && url.parse(m._from).protocol
  382. var cFromUrl = curr && curr.from && url.parse(curr.from).protocol
  383. if (
  384. !curr ||
  385. (dFromUrl && cFromUrl && m._from !== curr.from) ||
  386. m.version !== curr.version ||
  387. m.version !== l.version
  388. ) {
  389. if (parsed.type === 'alias') {
  390. doIt(
  391. `npm:${parsed.subSpec.name}@${m.version}`,
  392. `npm:${parsed.subSpec.name}@${l.version}`
  393. )
  394. } else {
  395. doIt(m.version, l.version)
  396. }
  397. } else {
  398. skip()
  399. }
  400. }
  401. }