nodejs/deps/npm/lib/outdated.js

272 lines
6.4 KiB
JavaScript
Raw Normal View History

const os = require('os')
const path = require('path')
const pacote = require('pacote')
const table = require('text-table')
const color = require('ansicolors')
const styles = require('ansistyles')
const npa = require('npm-package-arg')
const pickManifest = require('npm-pick-manifest')
const Arborist = require('@npmcli/arborist')
const npm = require('./npm.js')
const output = require('./utils/output.js')
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)
}
async function outdated (args) {
const opts = npm.flatOptions
const global = path.resolve(npm.globalDir, '..')
const where = opts.global
? global
: npm.prefix
const arb = new Arborist({
...opts,
path: where,
})
2014-02-16 20:43:16 -08:00
const tree = await arb.loadActual()
const list = await outdated_(tree, args, opts)
// sorts list alphabetically
const outdated = list.sort((a, b) => a.name.localeCompare(b.name))
2014-02-16 20:43:16 -08:00
// return if no outdated packages
if (outdated.length === 0 && !opts.json)
return
2014-02-16 20:43:16 -08:00
// 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 (opts.long)
outHead.push('Package Type', 'Homepage')
const outTable = [outHead].concat(outList)
2014-02-16 20:43:16 -08:00
if (opts.color)
outTable[0] = outTable[0].map(heading => styles.underline(heading))
2013-12-11 10:20:26 -08:00
const tableOpts = {
align: ['l', 'r', 'r', 'r', 'l'],
stringLength: s => ansiTrim(s).length,
}
output(table(outTable, tableOpts))
2013-12-11 10:20:26 -08:00
}
}
async function outdated_ (tree, deps, opts) {
const list = []
const edges = new Set()
function getEdges (nodes, type) {
const getEdgesIn = (node) => {
for (const edge of node.edgesIn)
edges.add(edge)
}
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)
}
}
if (!nodes)
return getEdgesOut(tree)
for (const node of nodes) {
type === 'edgesOut'
? getEdgesOut(node)
: getEdgesIn(node)
}
}
async function getPackument (spec) {
const packument = await pacote.packument(spec, {
fullMetadata: npm.flatOptions.long,
preferOnline: true,
})
return packument
}
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'
for (const omitType of opts.omit || []) {
if (node[omitType])
return
}
// deps different from prod not currently
// on disk are not included in the output
if (edge.error === 'MISSING' && type !== 'dependencies')
return
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
}
}
2014-09-24 14:41:07 -07: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')
}
} else {
if (opts.all) {
// all deps in tree
const nodes = tree.inventory.values()
getEdges(nodes, 'edgesOut')
}
// top-level deps
getEdges()
}
for (const edge of edges)
p.push(getOutdatedInfo(edge))
await Promise.all(p)
return list
}
// formatting functions
function makePretty (dep, opts) {
const {
current = 'MISSING',
location = '-',
homepage = '',
name,
wanted,
latest,
type,
dependent,
} = dep
const columns = [name, current, wanted, latest, location, dependent]
if (opts.long) {
columns[6] = type
columns[7] = homepage
}
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
return columns
}
2013-10-24 09:21:59 -07: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,
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,
2014-02-16 20:43:16 -08:00
}
if (opts.long) {
out[name].type = type
out[name].homepage = homepage
}
})
return JSON.stringify(out, null, 2)
}
module.exports = Object.assign(cmd, { completion, usage })