File Explorer

/var/lang/lib/node_modules/npm/lib/commands

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

1 dir
67 files
outdated.js7.9 KB · 290 lines
const { resolve } = require('node:path')const { stripVTControlCharacters } = require('node:util')const pacote = require('pacote')const table = require('text-table')const npa = require('npm-package-arg')const pickManifest = require('npm-pick-manifest')const { output } = require('proc-log')const localeCompare = require('@isaacs/string-locale-compare')('en')const ArboristWorkspaceCmd = require('../arborist-cmd.js') const safeNpa = (spec) => {  try {    return npa(spec)  } catch {    return null  }} // This string is load bearing and is shared with Arboristconst MISSING = 'MISSING' class Outdated extends ArboristWorkspaceCmd {  static description = 'Check for outdated packages'  static name = 'outdated'  static usage = ['[<package-spec> ...]']  static params = [    'all',    'json',    'long',    'parseable',    'global',    'workspace',    'before',  ]   #tree  #list = []  #edges = new Set()  #filterSet   async exec (args) {    const Arborist = require('@npmcli/arborist')    const arb = new Arborist({      ...this.npm.flatOptions,      path: this.npm.global ? resolve(this.npm.globalDir, '..') : this.npm.prefix,    })    this.#tree = await arb.loadActual()     if (this.workspaceNames?.length) {      this.#filterSet = arb.workspaceDependencySet(        this.#tree,        this.workspaceNames,        this.npm.flatOptions.includeWorkspaceRoot      )    } else if (!this.npm.flatOptions.workspacesEnabled) {      this.#filterSet = arb.excludeWorkspacesDependencySet(this.#tree)    }     if (args.length) {      for (const arg of args) {        // specific deps        this.#getEdges(this.#tree.inventory.query('name', arg), 'edgesIn')      }    } else {      if (this.npm.config.get('all')) {        // all deps in tree        this.#getEdges(this.#tree.inventory.values(), 'edgesOut')      }      // top-level deps      this.#getEdges()    }     await Promise.all([...this.#edges].map((e) => this.#getOutdatedInfo(e)))     // sorts list alphabetically by name and then dependent    const outdated = this.#list      .sort((a, b) => localeCompare(a.name, b.name) || localeCompare(a.dependent, b.dependent))     if (outdated.length) {      process.exitCode = 1    }     if (this.npm.config.get('json')) {      output.buffer(this.#json(outdated))      return    }     const res = this.npm.config.get('parseable')      ? this.#parseable(outdated)      : this.#pretty(outdated)     if (res) {      output.standard(res)    }  }   #getEdges (nodes, type) {    // when no nodes are provided then it should only read direct deps    // from the root node and its workspaces direct dependencies    if (!nodes) {      this.#getEdgesOut(this.#tree)      this.#getWorkspacesEdges()      return    }     for (const node of nodes) {      if (type === 'edgesOut') {        this.#getEdgesOut(node)      } else {        this.#getEdgesIn(node)      }    }  }   #getEdgesIn (node) {    for (const edge of node.edgesIn) {      this.#trackEdge(edge)    }  }   #getEdgesOut (node) {    // TODO: normalize usage of edges and avoid looping through nodes here    const edges = this.npm.global ? node.children.values() : node.edgesOut.values()    for (const edge of edges) {      this.#trackEdge(edge)    }  }   #trackEdge (edge) {    if (edge.from && this.#filterSet?.size > 0 && !this.#filterSet.has(edge.from.target)) {      return    }    this.#edges.add(edge)  }   #getWorkspacesEdges () {    if (this.npm.global) {      return    }     for (const edge of this.#tree.edgesOut.values()) {      if (edge?.to?.target?.isWorkspace) {        this.#getEdgesOut(edge.to.target)      }    }  }   async #getPackument (spec) {    return pacote.packument(spec, {      ...this.npm.flatOptions,      fullMetadata: this.npm.config.get('long'),      preferOnline: true,    })  }   async #getOutdatedInfo (edge) {    const alias = safeNpa(edge.spec)?.subSpec    const spec = npa(alias ? alias.name : edge.name)    const node = edge.to || edge    const { path, location, package: { version: current } = {} } = node     const type = edge.optional ? 'optionalDependencies'      : edge.peer ? 'peerDependencies'      : edge.dev ? 'devDependencies'      : 'dependencies'     for (const omitType of this.npm.flatOptions.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    }     // if it's not a range, version, or tag, skip it    if (!safeNpa(`${edge.name}@${edge.spec}`)?.registry) {      return null    }     try {      const packument = await this.#getPackument(spec)      const expected = alias ? alias.fetchSpec : edge.spec      const wanted = pickManifest(packument, expected, this.npm.flatOptions)      const latest = pickManifest(packument, '*', this.npm.flatOptions)      if (!current || current !== wanted.version || wanted.version !== latest.version) {        this.#list.push({          name: alias ? edge.spec.replace('npm', edge.name) : edge.name,          path,          type,          current,          location,          wanted: wanted.version,          latest: latest.version,          workspaceDependent: edge.from?.isWorkspace ? edge.from.pkgid : null,          dependedByLocation: edge.from?.name            ? edge.from?.location            : 'global',          dependent: edge.from?.name ?? 'global',          homepage: packument.homepage,        })      }    } catch (err) {      // silently catch and ignore ETARGET, E403 &      // E404 errors, deps are just skipped      if (!['ETARGET', 'E404', 'E404'].includes(err.code)) {        throw err      }    }  }   // formatting functions   #pretty (list) {    if (!list.length) {      return    }     const long = this.npm.config.get('long')    const { bold, yellow, red, cyan, blue } = this.npm.chalk     return table([      [        'Package',        'Current',        'Wanted',        'Latest',        'Location',        'Depended by',        ...long ? ['Package Type', 'Homepage', 'Depended By Location'] : [],      ].map(h => bold.underline(h)),      ...list.map((d) => [        d.current === d.wanted ? yellow(d.name) : red(d.name),        d.current ?? 'MISSING',        cyan(d.wanted),        blue(d.latest),        d.location ?? '-',        d.workspaceDependent ? blue(d.workspaceDependent) : d.dependent,        ...long ? [d.type, blue(d.homepage ?? ''), d.dependedByLocation] : [],      ]),    ], {      align: ['l', 'r', 'r', 'r', 'l'],      stringLength: s => stripVTControlCharacters(s).length,    })  }   // --parseable creates output like this:  // <fullpath>:<name@wanted>:<name@installed>:<name@latest>:<dependedby>  #parseable (list) {    return list.map(d => [      d.path,      `${d.name}@${d.wanted}`,      d.current ? `${d.name}@${d.current}` : 'MISSING',      `${d.name}@${d.latest}`,      d.dependent,      ...this.npm.config.get('long') ? [d.type, d.homepage, d.dependedByLocation] : [],    ].join(':')).join('\n')  }   #json (list) {    // TODO(BREAKING_CHANGE): this should just return an array. It's a list and    // turing it into an object with keys is lossy since multiple items in the    // list could have the same key. For now we hack that by only changing    // top level values into arrays if they have multiple outdated items    return list.reduce((acc, d) => {      const dep = {        current: d.current,        wanted: d.wanted,        latest: d.latest,        dependent: d.dependent,        location: d.path,        ...this.npm.config.get('long') ? {          type: d.type,          homepage: d.homepage,          dependedByLocation: d.dependedByLocation } : {},      }      acc[d.name] = acc[d.name]        // If this item already has an outdated dep then we turn it into an array        ? (Array.isArray(acc[d.name]) ? acc[d.name] : [acc[d.name]]).concat(dep)        : dep      return acc    }, {})  }} module.exports = Outdated