summaryrefslogtreecommitdiff
path: root/deps/npm/lib/outdated.js
diff options
context:
space:
mode:
Diffstat (limited to 'deps/npm/lib/outdated.js')
-rw-r--r--deps/npm/lib/outdated.js628
1 files changed, 226 insertions, 402 deletions
diff --git a/deps/npm/lib/outdated.js b/deps/npm/lib/outdated.js
index 5b84ae3558..c3b15e4118 100644
--- a/deps/npm/lib/outdated.js
+++ b/deps/npm/lib/outdated.js
@@ -1,454 +1,278 @@
-/*
-
-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')
+'use strict'
const os = require('os')
-const url = require('url')
const path = require('path')
-const readPackageTree = require('read-package-tree')
-const asyncMap = require('slide').asyncMap
+const pacote = require('pacote')
+const table = require('text-table')
const color = require('ansicolors')
const styles = require('ansistyles')
-const table = require('text-table')
-const semver = require('semver')
-const npa = require('libnpm/parse-arg')
+const npa = require('npm-package-arg')
const pickManifest = require('npm-pick-manifest')
-const fetchPackageMetadata = require('./fetch-package-metadata.js')
-const mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js')
+
+const Arborist = require('@npmcli/arborist')
+
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
+const usageUtil = require('./utils/usage.js')
+const ansiTrim = require('./utils/ansi-trim.js')
+
+const usage = usageUtil('outdated',
+ 'npm outdated [[<@scope>/]<pkg> ...]'
+)
+const completion = require('./utils/completion/none.js')
+
+function cmd (args, cb) {
+ outdated(args)
+ .then(() => cb())
+ .catch(cb)
}
-function andComputeMetadata (next) {
- return function (er, tree) {
- if (er) return next(er)
- next(null, computeMetadata(tree))
- }
-}
+async function outdated (args) {
+ const opts = npm.flatOptions
+ const global = path.resolve(npm.globalDir, '..')
+ const where = opts.global
+ ? global
+ : npm.prefix
-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)
- })
- }
+ const arb = new Arborist({
+ ...opts,
+ path: where
+ })
- 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)) }))
- })
- }))
-}
+ const tree = await arb.loadActual()
+ const list = await outdated_(tree, args, opts)
-// [[ 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
- }
+ // sorts list alphabetically
+ const outdated = list.sort((a, b) => a.name.localeCompare(b.name))
- 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 if no outdated packages
+ if (outdated.length === 0 && !opts.json) {
+ return
}
- 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
+ // display results
+ if (opts.json) {
+ output(makeJSON(outdated, opts))
+ } else if (opts.parseable) {
+ output(makeParseable(outdated, opts))
+ } else {
+ const outList = outdated.map(x => makePretty(x, opts))
+ const outHead = ['Package',
+ 'Current',
+ 'Wanted',
+ 'Latest',
+ 'Location',
+ 'Depended by'
]
- if (long) out.push(type, dep.package.homepage)
- return out.join(':')
- }).join(os.EOL)
-}
+ if (opts.long) outHead.push('Package Type', 'Homepage')
+ const outTable = [outHead].concat(outList)
-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
+ if (opts.color) {
+ outTable[0] = outTable[0].map(heading => styles.underline(heading))
}
- })
- 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, [])
+ const tableOpts = {
+ align: ['l', 'r', 'r', 'r', 'l'],
+ stringLength: s => ansiTrim(s).length
+ }
+ output(table(outTable, tableOpts))
}
- 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'
- })
+async function outdated_ (tree, deps, opts) {
+ const list = []
- // 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'
+ const edges = new Set()
+ function getEdges (nodes, type) {
+ const getEdgesIn = (node) => {
+ for (const edge of node.edgesIn) {
+ edges.add(edge)
}
- })
- }
+ }
- 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'
+ const getEdgesOut = (node) => {
+ if (opts.global) {
+ for (const child of node.children.values()) {
+ edges.add(child)
+ }
+ } else {
+ for (const edge of node.edgesOut.values()) {
+ edges.add(edge)
+ }
}
- })
- }
-
- 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 (!nodes) return getEdgesOut(tree)
+ for (const node of nodes) {
+ type === 'edgesOut'
+ ? getEdgesOut(node)
+ : getEdgesIn(node)
}
+ }
- 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)
+ async function getPackument (spec) {
+ const packument = await pacote.packument(spec, {
+ fullMetadata: npm.flatOptions.long,
+ preferOnline: true
+ })
+ return packument
}
- if (args.length && args.indexOf(dep) === -1) return skip()
+ async function getOutdatedInfo (edge) {
+ const spec = npa(edge.name)
+ const node = edge.to || edge
+ const { path, location } = node
+ const { version: current } = node.package || {}
+
+ const type = edge.optional ? 'optionalDependencies'
+ : edge.peer ? 'peerDependencies'
+ : edge.dev ? 'devDependencies'
+ : 'dependencies'
- if (tree.isLink && req == null) return skip()
+ for (const omitType of opts.omit || []) {
+ if (node[omitType]) {
+ return
+ }
+ }
- if (req == null || req === '') req = '*'
+ // deps different from prod not currently
+ // on disk are not included in the output
+ if (edge.error === 'MISSING' && type !== 'dependencies') return
- var parsed = npa.resolve(dep, req)
- if (parsed.type === 'directory') {
- if (tree.isLink) {
- return skip()
- } else {
- return doIt('linked', 'linked')
+ try {
+ const packument = await getPackument(spec)
+ const expected = edge.spec
+ // if it's not a range, version, or tag, skip it
+ try {
+ if (!npa(`${edge.name}@${edge.spec}`).registry) {
+ return null
+ }
+ } catch (err) {
+ return null
+ }
+ const wanted = pickManifest(packument, expected, npm.flatOptions)
+ const latest = pickManifest(packument, '*', npm.flatOptions)
+
+ if (
+ !current ||
+ current !== wanted.version ||
+ wanted.version !== latest.version
+ ) {
+ list.push({
+ name: edge.name,
+ path,
+ type,
+ current,
+ location,
+ wanted: wanted.version,
+ latest: latest.version,
+ dependent: edge.from ? edge.from.name : 'global',
+ homepage: packument.homepage
+ })
+ }
+ } catch (err) {
+ // silently catch and ignore ETARGET, E403 &
+ // E404 errors, deps are just skipped
+ if (!(
+ err.code === 'ETARGET' ||
+ err.code === 'E403' ||
+ err.code === 'E404')
+ ) {
+ throw err
+ }
}
- } 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}`
+ const p = []
+ if (deps.length !== 0) {
+ // specific deps
+ for (let i = 0; i < deps.length; i++) {
+ const nodes = tree.inventory.query('name', deps[i])
+ getEdges(nodes, 'edgesIn')
}
- if (!long) {
- return cb(null, [[tree, dep, c, wanted, latest, req, null, pkgpath]])
+ } else {
+ if (opts.all) {
+ // all deps in tree
+ const nodes = tree.inventory.values()
+ getEdges(nodes, 'edgesOut')
}
- cb(null, [[tree, dep, c, wanted, latest, req, type, pkgpath]])
+ // top-level deps
+ getEdges()
}
- function updateLocalDeps (latestRegistryVersion) {
- fetchPackageMetadata('file:' + parsed.fetchSpec, '.', (er, localDependency) => {
- if (er) return cb()
+ for (const edge of edges) {
+ p.push(getOutdatedInfo(edge))
+ }
- var wanted = localDependency.version
- var latest = localDependency.version
+ await Promise.all(p)
+ return list
+}
- if (latestRegistryVersion) {
- latest = latestRegistryVersion
- if (semver.lt(wanted, latestRegistryVersion)) {
- wanted = latestRegistryVersion
- req = dep + '@' + latest
- }
- }
+// formatting functions
+function makePretty (dep, opts) {
+ const {
+ current = 'MISSING',
+ location = '-',
+ homepage = '',
+ name,
+ wanted,
+ latest,
+ type,
+ dependent
+ } = dep
- if (!curr || curr.version !== wanted) {
- doIt(wanted, latest)
- } else {
- skip()
- }
- })
+ const columns = [name, current, wanted, latest, location, dependent]
+
+ if (opts.long) {
+ columns[6] = type
+ columns[7] = homepage
}
- function updateDeps (er, d) {
- if (er) return cb(er)
+ if (opts.color) {
+ columns[0] = color[current === wanted ? 'yellow' : 'red'](columns[0]) // current
+ columns[2] = color.green(columns[2]) // wanted
+ columns[3] = color.magenta(columns[3]) // latest
+ }
- 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()
- }
- }
+ return columns
+}
- // 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()
+// --parseable creates output like this:
+// <fullpath>:<name@wanted>:<name@installed>:<name@latest>:<dependedby>
+function makeParseable (list, opts) {
+ return list.map(dep => {
+ const { name, current, wanted, latest, path, dependent, type, homepage } = dep
+ const out = [
+ path,
+ name + '@' + wanted,
+ current ? (name + '@' + current) : 'MISSING',
+ name + '@' + latest,
+ dependent
+ ]
+ if (opts.long) out.push(type, homepage)
+
+ return out.join(':')
+ }).join(os.EOL)
+}
+
+function makeJSON (list, opts) {
+ const out = {}
+ list.forEach(dep => {
+ const { name, current, wanted, latest, path, type, dependent, homepage } = dep
+ out[name] = {
+ current,
+ wanted,
+ latest,
+ dependent,
+ location: path
}
- }
+ if (opts.long) {
+ out[name].type = type
+ out[name].homepage = homepage
+ }
+ })
+ return JSON.stringify(out, null, 2)
}
+
+module.exports = Object.assign(cmd, { completion, usage })