123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- /*
- npm outdated [pkg]
- Does the following:
- 1. check for a new version of pkg
- If no packages are specified, then run for all installed
- packages.
- --parseable creates output like this:
- <fullpath>:<name@wanted>:<name@installed>:<name@latest>
- */
- module.exports = outdated
- outdated.usage = 'npm outdated [[<@scope>/]<pkg> ...]'
- outdated.completion = require('./utils/completion/installed-deep.js')
- const os = require('os')
- const url = require('url')
- const path = require('path')
- const readPackageTree = require('read-package-tree')
- const asyncMap = require('slide').asyncMap
- const color = require('ansicolors')
- const styles = require('ansistyles')
- const table = require('text-table')
- const semver = require('semver')
- const npa = require('libnpm/parse-arg')
- const pickManifest = require('npm-pick-manifest')
- const fetchPackageMetadata = require('./fetch-package-metadata.js')
- const mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js')
- const npm = require('./npm.js')
- const npmConfig = require('./config/figgy-config.js')
- const figgyPudding = require('figgy-pudding')
- const packument = require('libnpm/packument')
- const long = npm.config.get('long')
- const isExtraneous = require('./install/is-extraneous.js')
- const computeMetadata = require('./install/deps.js').computeMetadata
- const computeVersionSpec = require('./install/deps.js').computeVersionSpec
- const moduleName = require('./utils/module-name.js')
- const output = require('./utils/output.js')
- const ansiTrim = require('./utils/ansi-trim')
- const OutdatedConfig = figgyPudding({
- also: {},
- color: {},
- depth: {},
- dev: 'development',
- development: {},
- global: {},
- json: {},
- only: {},
- parseable: {},
- prod: 'production',
- production: {},
- save: {},
- 'save-dev': {},
- 'save-optional': {}
- })
- function uniq (list) {
- // we maintain the array because we need an array, not iterator, return
- // value.
- var uniqed = []
- var seen = new Set()
- list.forEach(function (item) {
- if (seen.has(item)) return
- seen.add(item)
- uniqed.push(item)
- })
- return uniqed
- }
- function andComputeMetadata (next) {
- return function (er, tree) {
- if (er) return next(er)
- next(null, computeMetadata(tree))
- }
- }
- function outdated (args, silent, cb) {
- if (typeof cb !== 'function') {
- cb = silent
- silent = false
- }
- let opts = OutdatedConfig(npmConfig())
- var dir = path.resolve(npm.dir, '..')
- // default depth for `outdated` is 0 (cf. `ls`)
- if (opts.depth === Infinity) opts = opts.concat({depth: 0})
- readPackageTree(dir, andComputeMetadata(function (er, tree) {
- if (!tree) return cb(er)
- mutateIntoLogicalTree(tree)
- outdated_(args, '', tree, {}, 0, opts, function (er, list) {
- list = uniq(list || []).sort(function (aa, bb) {
- return aa[0].path.localeCompare(bb[0].path) ||
- aa[1].localeCompare(bb[1])
- })
- if (er || silent ||
- (list.length === 0 && !opts.json)) {
- return cb(er, list)
- }
- if (opts.json) {
- output(makeJSON(list, opts))
- } else if (opts.parseable) {
- output(makeParseable(list, opts))
- } else {
- var outList = list.map(x => makePretty(x, opts))
- var outHead = [ 'Package',
- 'Current',
- 'Wanted',
- 'Latest',
- 'Location'
- ]
- if (long) outHead.push('Package Type', 'Homepage')
- var outTable = [outHead].concat(outList)
- if (opts.color) {
- outTable[0] = outTable[0].map(function (heading) {
- return styles.underline(heading)
- })
- }
- var tableOpts = {
- align: ['l', 'r', 'r', 'r', 'l'],
- stringLength: function (s) { return ansiTrim(s).length }
- }
- output(table(outTable, tableOpts))
- }
- process.exitCode = list.length ? 1 : 0
- cb(null, list.map(function (item) { return [item[0].parent.path].concat(item.slice(1, 7)) }))
- })
- }))
- }
- // [[ dir, dep, has, want, latest, type ]]
- function makePretty (p, opts) {
- var depname = p[1]
- var has = p[2]
- var want = p[3]
- var latest = p[4]
- var type = p[6]
- var deppath = p[7]
- var homepage = p[0].package.homepage || ''
- var columns = [ depname,
- has || 'MISSING',
- want,
- latest,
- deppath || 'global'
- ]
- if (long) {
- columns[5] = type
- columns[6] = homepage
- }
- if (opts.color) {
- columns[0] = color[has === want ? 'yellow' : 'red'](columns[0]) // dep
- columns[2] = color.green(columns[2]) // want
- columns[3] = color.magenta(columns[3]) // latest
- }
- return columns
- }
- function makeParseable (list) {
- return list.map(function (p) {
- var dep = p[0]
- var depname = p[1]
- var dir = dep.path
- var has = p[2]
- var want = p[3]
- var latest = p[4]
- var type = p[6]
- var out = [
- dir,
- depname + '@' + want,
- (has ? (depname + '@' + has) : 'MISSING'),
- depname + '@' + latest
- ]
- if (long) out.push(type, dep.package.homepage)
- return out.join(':')
- }).join(os.EOL)
- }
- function makeJSON (list, opts) {
- var out = {}
- list.forEach(function (p) {
- var dep = p[0]
- var depname = p[1]
- var dir = dep.path
- var has = p[2]
- var want = p[3]
- var latest = p[4]
- var type = p[6]
- if (!opts.global) {
- dir = path.relative(process.cwd(), dir)
- }
- out[depname] = { current: has,
- wanted: want,
- latest: latest,
- location: dir
- }
- if (long) {
- out[depname].type = type
- out[depname].homepage = dep.package.homepage
- }
- })
- return JSON.stringify(out, null, 2)
- }
- function outdated_ (args, path, tree, parentHas, depth, opts, cb) {
- if (!tree.package) tree.package = {}
- if (path && moduleName(tree)) path += ' > ' + tree.package.name
- if (!path && moduleName(tree)) path = tree.package.name
- if (depth > opts.depth) {
- return cb(null, [])
- }
- var types = {}
- var pkg = tree.package
- if (!tree.children) tree.children = []
- var deps = tree.error ? tree.children : tree.children.filter((child) => !isExtraneous(child))
- deps.forEach(function (dep) {
- types[moduleName(dep)] = 'dependencies'
- })
- Object.keys(tree.missingDeps || {}).forEach(function (name) {
- deps.push({
- package: { name: name },
- path: tree.path,
- parent: tree,
- isMissing: true
- })
- types[name] = 'dependencies'
- })
- // If we explicitly asked for dev deps OR we didn't ask for production deps
- // AND we asked to save dev-deps OR we didn't ask to save anything that's NOT
- // dev deps then…
- // (All the save checking here is because this gets called from npm-update currently
- // and that requires this logic around dev deps.)
- // FIXME: Refactor npm update to not be in terms of outdated.
- var dev = opts.dev || /^dev(elopment)?$/.test(opts.also)
- var prod = opts.production || /^prod(uction)?$/.test(opts.only)
- if (
- (dev || !prod) &&
- (
- opts['save-dev'] || (!opts.save && !opts['save-optional'])
- )
- ) {
- Object.keys(tree.missingDevDeps).forEach(function (name) {
- deps.push({
- package: { name: name },
- path: tree.path,
- parent: tree,
- isMissing: true
- })
- if (!types[name]) {
- types[name] = 'devDependencies'
- }
- })
- }
- if (opts['save-dev']) {
- deps = deps.filter(function (dep) { return pkg.devDependencies[moduleName(dep)] })
- deps.forEach(function (dep) {
- types[moduleName(dep)] = 'devDependencies'
- })
- } else if (opts.save) {
- // remove optional dependencies from dependencies during --save.
- deps = deps.filter(function (dep) { return !pkg.optionalDependencies[moduleName(dep)] })
- } else if (opts['save-optional']) {
- deps = deps.filter(function (dep) { return pkg.optionalDependencies[moduleName(dep)] })
- deps.forEach(function (dep) {
- types[moduleName(dep)] = 'optionalDependencies'
- })
- }
- var doUpdate = dev || (
- !prod &&
- !Object.keys(parentHas).length &&
- !opts.global
- )
- if (doUpdate) {
- Object.keys(pkg.devDependencies || {}).forEach(function (k) {
- if (!(k in parentHas)) {
- deps[k] = pkg.devDependencies[k]
- types[k] = 'devDependencies'
- }
- })
- }
- var has = Object.create(parentHas)
- tree.children.forEach(function (child) {
- if (moduleName(child) && child.package.private) {
- deps = deps.filter(function (dep) { return dep !== child })
- }
- has[moduleName(child)] = {
- version: child.isLink ? 'linked' : child.package.version,
- from: child.isLink ? 'file:' + child.path : child.package._from
- }
- })
- // now get what we should have, based on the dep.
- // if has[dep] !== shouldHave[dep], then cb with the data
- // otherwise dive into the folder
- asyncMap(deps, function (dep, cb) {
- var name = moduleName(dep)
- var required
- if (tree.package.dependencies && name in tree.package.dependencies) {
- required = tree.package.dependencies[name]
- } else if (tree.package.optionalDependencies && name in tree.package.optionalDependencies) {
- required = tree.package.optionalDependencies[name]
- } else if (tree.package.devDependencies && name in tree.package.devDependencies) {
- required = tree.package.devDependencies[name]
- } else if (has[name]) {
- required = computeVersionSpec(tree, dep)
- }
- if (!long) return shouldUpdate(args, dep, name, has, required, depth, path, opts, cb)
- shouldUpdate(args, dep, name, has, required, depth, path, opts, cb, types[name])
- }, cb)
- }
- function shouldUpdate (args, tree, dep, has, req, depth, pkgpath, opts, cb, type) {
- // look up the most recent version.
- // if that's what we already have, or if it's not on the args list,
- // then dive into it. Otherwise, cb() with the data.
- // { version: , from: }
- var curr = has[dep]
- function skip (er) {
- // show user that no viable version can be found
- if (er) return cb(er)
- outdated_(args,
- pkgpath,
- tree,
- has,
- depth + 1,
- opts,
- cb)
- }
- if (args.length && args.indexOf(dep) === -1) return skip()
- if (tree.isLink && req == null) return skip()
- if (req == null || req === '') req = '*'
- var parsed = npa.resolve(dep, req)
- if (parsed.type === 'directory') {
- if (tree.isLink) {
- return skip()
- } else {
- return doIt('linked', 'linked')
- }
- } else if (parsed.type === 'git') {
- return doIt('git', 'git')
- } else if (parsed.type === 'file') {
- return updateLocalDeps()
- } else if (parsed.type === 'remote') {
- return doIt('remote', 'remote')
- } else {
- return packument(parsed, opts.concat({
- 'prefer-online': true
- })).nodeify(updateDeps)
- }
- function doIt (wanted, latest) {
- let c = curr && curr.version
- if (parsed.type === 'alias') {
- c = `npm:${parsed.subSpec.name}@${c}`
- }
- if (!long) {
- return cb(null, [[tree, dep, c, wanted, latest, req, null, pkgpath]])
- }
- cb(null, [[tree, dep, c, wanted, latest, req, type, pkgpath]])
- }
- function updateLocalDeps (latestRegistryVersion) {
- fetchPackageMetadata('file:' + parsed.fetchSpec, '.', (er, localDependency) => {
- if (er) return cb()
- var wanted = localDependency.version
- var latest = localDependency.version
- if (latestRegistryVersion) {
- latest = latestRegistryVersion
- if (semver.lt(wanted, latestRegistryVersion)) {
- wanted = latestRegistryVersion
- req = dep + '@' + latest
- }
- }
- if (!curr || curr.version !== wanted) {
- doIt(wanted, latest)
- } else {
- skip()
- }
- })
- }
- function updateDeps (er, d) {
- if (er) return cb(er)
- if (parsed.type === 'alias') {
- req = parsed.subSpec.rawSpec
- }
- try {
- var l = pickManifest(d, 'latest')
- var m = pickManifest(d, req)
- } catch (er) {
- if (er.code === 'ETARGET' || er.code === 'E403') {
- return skip(er)
- } else {
- return skip()
- }
- }
- // check that the url origin hasn't changed (#1727) and that
- // there is no newer version available
- var dFromUrl = m._from && url.parse(m._from).protocol
- var cFromUrl = curr && curr.from && url.parse(curr.from).protocol
- if (
- !curr ||
- (dFromUrl && cFromUrl && m._from !== curr.from) ||
- m.version !== curr.version ||
- m.version !== l.version
- ) {
- if (parsed.type === 'alias') {
- doIt(
- `npm:${parsed.subSpec.name}@${m.version}`,
- `npm:${parsed.subSpec.name}@${l.version}`
- )
- } else {
- doIt(m.version, l.version)
- }
- } else {
- skip()
- }
- }
- }
|