2019-04-05 15:17:30 -04:00
|
|
|
const os = require('os')
|
|
|
|
const path = require('path')
|
2020-10-02 17:52:19 -04:00
|
|
|
const pacote = require('pacote')
|
|
|
|
const table = require('text-table')
|
2019-04-05 15:17:30 -04:00
|
|
|
const color = require('ansicolors')
|
|
|
|
const styles = require('ansistyles')
|
2020-10-02 17:52:19 -04:00
|
|
|
const npa = require('npm-package-arg')
|
2019-04-05 15:17:30 -04:00
|
|
|
const pickManifest = require('npm-pick-manifest')
|
2020-10-02 17:52:19 -04:00
|
|
|
|
|
|
|
const Arborist = require('@npmcli/arborist')
|
|
|
|
|
2019-04-05 15:17:30 -04:00
|
|
|
const npm = require('./npm.js')
|
|
|
|
const output = require('./utils/output.js')
|
2020-10-02 17:52:19 -04:00
|
|
|
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)
|
2015-10-09 23:13:57 -07:00
|
|
|
}
|
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
async function outdated (args) {
|
|
|
|
const opts = npm.flatOptions
|
|
|
|
const global = path.resolve(npm.globalDir, '..')
|
|
|
|
const where = opts.global
|
|
|
|
? global
|
|
|
|
: npm.prefix
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
const arb = new Arborist({
|
|
|
|
...opts,
|
2020-11-01 07:54:36 +01:00
|
|
|
path: where,
|
2020-10-02 17:52:19 -04:00
|
|
|
})
|
2014-02-16 20:43:16 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
const tree = await arb.loadActual()
|
|
|
|
const list = await outdated_(tree, args, opts)
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
// sorts list alphabetically
|
|
|
|
const outdated = list.sort((a, b) => a.name.localeCompare(b.name))
|
2014-02-16 20:43:16 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
// return if no outdated packages
|
2020-11-01 07:54:36 +01:00
|
|
|
if (outdated.length === 0 && !opts.json)
|
2020-10-02 17:52:19 -04:00
|
|
|
return
|
2014-02-16 20:43:16 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
// display results
|
2020-11-01 07:54:36 +01:00
|
|
|
if (opts.json)
|
2020-10-02 17:52:19 -04:00
|
|
|
output(makeJSON(outdated, opts))
|
2020-11-01 07:54:36 +01:00
|
|
|
else if (opts.parseable)
|
2020-10-02 17:52:19 -04:00
|
|
|
output(makeParseable(outdated, opts))
|
2020-11-01 07:54:36 +01:00
|
|
|
else {
|
2020-10-02 17:52:19 -04:00
|
|
|
const outList = outdated.map(x => makePretty(x, opts))
|
|
|
|
const outHead = ['Package',
|
|
|
|
'Current',
|
|
|
|
'Wanted',
|
|
|
|
'Latest',
|
|
|
|
'Location',
|
2020-11-01 07:54:36 +01:00
|
|
|
'Depended by',
|
2015-10-09 23:13:57 -07:00
|
|
|
]
|
|
|
|
|
2020-11-01 07:54:36 +01:00
|
|
|
if (opts.long)
|
|
|
|
outHead.push('Package Type', 'Homepage')
|
2020-10-02 17:52:19 -04:00
|
|
|
const outTable = [outHead].concat(outList)
|
2014-02-16 20:43:16 -08:00
|
|
|
|
2020-11-01 07:54:36 +01:00
|
|
|
if (opts.color)
|
2020-10-02 17:52:19 -04:00
|
|
|
outTable[0] = outTable[0].map(heading => styles.underline(heading))
|
2013-12-11 10:20:26 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
const tableOpts = {
|
|
|
|
align: ['l', 'r', 'r', 'r', 'l'],
|
2020-11-01 07:54:36 +01:00
|
|
|
stringLength: s => ansiTrim(s).length,
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
|
|
|
output(table(outTable, tableOpts))
|
2013-12-11 10:20:26 -08:00
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
2011-11-21 09:48:45 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
async function outdated_ (tree, deps, opts) {
|
|
|
|
const list = []
|
2015-10-09 23:13:57 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
const edges = new Set()
|
|
|
|
function getEdges (nodes, type) {
|
|
|
|
const getEdgesIn = (node) => {
|
2020-11-01 07:54:36 +01:00
|
|
|
for (const edge of node.edgesIn)
|
2020-10-02 17:52:19 -04:00
|
|
|
edges.add(edge)
|
|
|
|
}
|
2012-06-17 12:04:35 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
const getEdgesOut = (node) => {
|
|
|
|
if (opts.global) {
|
2020-11-01 07:54:36 +01:00
|
|
|
for (const child of node.children.values())
|
2020-10-02 17:52:19 -04:00
|
|
|
edges.add(child)
|
|
|
|
} else {
|
2020-11-01 07:54:36 +01:00
|
|
|
for (const edge of node.edgesOut.values())
|
2020-10-02 17:52:19 -04:00
|
|
|
edges.add(edge)
|
2015-10-09 23:13:57 -07:00
|
|
|
}
|
|
|
|
}
|
2012-06-17 12:04:35 -07:00
|
|
|
|
2020-11-01 07:54:36 +01:00
|
|
|
if (!nodes)
|
|
|
|
return getEdgesOut(tree)
|
2020-10-02 17:52:19 -04:00
|
|
|
for (const node of nodes) {
|
|
|
|
type === 'edgesOut'
|
|
|
|
? getEdgesOut(node)
|
|
|
|
: getEdgesIn(node)
|
2018-04-20 18:26:37 -07:00
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
}
|
2018-04-20 18:26:37 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
async function getPackument (spec) {
|
|
|
|
const packument = await pacote.packument(spec, {
|
|
|
|
fullMetadata: npm.flatOptions.long,
|
2020-11-01 07:54:36 +01:00
|
|
|
preferOnline: true,
|
2020-10-02 17:52:19 -04:00
|
|
|
})
|
|
|
|
return packument
|
2011-11-21 09:48:45 -08:00
|
|
|
}
|
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
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'
|
2018-04-20 18:26:37 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
for (const omitType of opts.omit || []) {
|
2020-11-01 07:54:36 +01:00
|
|
|
if (node[omitType])
|
2020-10-02 17:52:19 -04:00
|
|
|
return
|
|
|
|
}
|
2018-04-20 18:26:37 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
// deps different from prod not currently
|
|
|
|
// on disk are not included in the output
|
2020-11-01 07:54:36 +01:00
|
|
|
if (edge.error === 'MISSING' && type !== 'dependencies')
|
|
|
|
return
|
2018-04-20 18:26:37 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
try {
|
|
|
|
const packument = await getPackument(spec)
|
|
|
|
const expected = edge.spec
|
|
|
|
// if it's not a range, version, or tag, skip it
|
|
|
|
try {
|
2020-11-01 07:54:36 +01:00
|
|
|
if (!npa(`${edge.name}@${edge.spec}`).registry)
|
2020-10-02 17:52:19 -04:00
|
|
|
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',
|
2020-11-01 07:54:36 +01:00
|
|
|
homepage: packument.homepage,
|
2020-10-02 17:52:19 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
// silently catch and ignore ETARGET, E403 &
|
|
|
|
// E404 errors, deps are just skipped
|
|
|
|
if (!(
|
|
|
|
err.code === 'ETARGET' ||
|
|
|
|
err.code === 'E403' ||
|
|
|
|
err.code === 'E404')
|
2020-11-01 07:54:36 +01:00
|
|
|
)
|
2020-10-02 17:52:19 -04:00
|
|
|
throw err
|
2018-04-20 18:26:37 -07:00
|
|
|
}
|
|
|
|
}
|
2014-09-24 14:41:07 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
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')
|
2019-04-05 15:17:30 -04:00
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
} else {
|
|
|
|
if (opts.all) {
|
|
|
|
// all deps in tree
|
|
|
|
const nodes = tree.inventory.values()
|
|
|
|
getEdges(nodes, 'edgesOut')
|
2019-04-05 15:17:30 -04:00
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
// top-level deps
|
|
|
|
getEdges()
|
2019-04-05 15:17:30 -04:00
|
|
|
}
|
|
|
|
|
2020-11-01 07:54:36 +01:00
|
|
|
for (const edge of edges)
|
2020-10-02 17:52:19 -04:00
|
|
|
p.push(getOutdatedInfo(edge))
|
2015-05-01 01:27:33 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
await Promise.all(p)
|
|
|
|
return list
|
|
|
|
}
|
2015-05-01 01:27:33 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
// formatting functions
|
|
|
|
function makePretty (dep, opts) {
|
|
|
|
const {
|
|
|
|
current = 'MISSING',
|
|
|
|
location = '-',
|
|
|
|
homepage = '',
|
|
|
|
name,
|
|
|
|
wanted,
|
|
|
|
latest,
|
|
|
|
type,
|
2020-11-01 07:54:36 +01:00
|
|
|
dependent,
|
2020-10-02 17:52:19 -04:00
|
|
|
} = dep
|
2015-05-01 01:27:33 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
const columns = [name, current, wanted, latest, location, dependent]
|
|
|
|
|
|
|
|
if (opts.long) {
|
|
|
|
columns[6] = type
|
|
|
|
columns[7] = homepage
|
2015-05-01 01:27:33 -07:00
|
|
|
}
|
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
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
|
|
|
|
}
|
2014-02-16 20:43:16 -08:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
return columns
|
|
|
|
}
|
2013-10-24 09:21:59 -07:00
|
|
|
|
2020-10-02 17:52:19 -04:00
|
|
|
// --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,
|
2020-11-01 07:54:36 +01:00
|
|
|
dependent,
|
2020-10-02 17:52:19 -04:00
|
|
|
]
|
2020-11-01 07:54:36 +01:00
|
|
|
if (opts.long)
|
|
|
|
out.push(type, homepage)
|
2020-10-02 17:52:19 -04:00
|
|
|
|
|
|
|
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,
|
2020-11-01 07:54:36 +01:00
|
|
|
location: path,
|
2014-02-16 20:43:16 -08:00
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
if (opts.long) {
|
|
|
|
out[name].type = type
|
|
|
|
out[name].homepage = homepage
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return JSON.stringify(out, null, 2)
|
2011-11-21 09:48:45 -08:00
|
|
|
}
|
2020-10-02 17:52:19 -04:00
|
|
|
|
|
|
|
module.exports = Object.assign(cmd, { completion, usage })
|