11cb0ef41Sopenharmony_ci// pass in an arborist object, and it'll output the data about what
21cb0ef41Sopenharmony_ci// was done, what was audited, etc.
31cb0ef41Sopenharmony_ci//
41cb0ef41Sopenharmony_ci// added ## packages, removed ## packages, and audited ## packages in 19.157s
51cb0ef41Sopenharmony_ci//
61cb0ef41Sopenharmony_ci// 1 package is looking for funding
71cb0ef41Sopenharmony_ci//   run `npm fund` for details
81cb0ef41Sopenharmony_ci//
91cb0ef41Sopenharmony_ci// found 37 vulnerabilities (5 low, 7 moderate, 25 high)
101cb0ef41Sopenharmony_ci//   run `npm audit fix` to fix them, or `npm audit` for details
111cb0ef41Sopenharmony_ci
121cb0ef41Sopenharmony_ciconst log = require('./log-shim.js')
131cb0ef41Sopenharmony_ciconst { depth } = require('treeverse')
141cb0ef41Sopenharmony_ciconst ms = require('ms')
151cb0ef41Sopenharmony_ciconst npmAuditReport = require('npm-audit-report')
161cb0ef41Sopenharmony_ciconst { readTree: getFundingInfo } = require('libnpmfund')
171cb0ef41Sopenharmony_ciconst auditError = require('./audit-error.js')
181cb0ef41Sopenharmony_ciconst Table = require('cli-table3')
191cb0ef41Sopenharmony_ci
201cb0ef41Sopenharmony_ci// TODO: output JSON if flatOptions.json is true
211cb0ef41Sopenharmony_ciconst reifyOutput = (npm, arb) => {
221cb0ef41Sopenharmony_ci  const { diff, actualTree } = arb
231cb0ef41Sopenharmony_ci
241cb0ef41Sopenharmony_ci  // note: fails and crashes if we're running audit fix and there was an error
251cb0ef41Sopenharmony_ci  // which is a good thing, because there's no point printing all this other
261cb0ef41Sopenharmony_ci  // stuff in that case!
271cb0ef41Sopenharmony_ci  const auditReport = auditError(npm, arb.auditReport) ? null : arb.auditReport
281cb0ef41Sopenharmony_ci
291cb0ef41Sopenharmony_ci  // don't print any info in --silent mode, but we still need to
301cb0ef41Sopenharmony_ci  // set the exitCode properly from the audit report, if we have one.
311cb0ef41Sopenharmony_ci  if (npm.silent) {
321cb0ef41Sopenharmony_ci    getAuditReport(npm, auditReport)
331cb0ef41Sopenharmony_ci    return
341cb0ef41Sopenharmony_ci  }
351cb0ef41Sopenharmony_ci
361cb0ef41Sopenharmony_ci  const summary = {
371cb0ef41Sopenharmony_ci    added: 0,
381cb0ef41Sopenharmony_ci    removed: 0,
391cb0ef41Sopenharmony_ci    changed: 0,
401cb0ef41Sopenharmony_ci    audited: auditReport && !auditReport.error ? actualTree.inventory.size : 0,
411cb0ef41Sopenharmony_ci    funding: 0,
421cb0ef41Sopenharmony_ci  }
431cb0ef41Sopenharmony_ci
441cb0ef41Sopenharmony_ci  if (diff) {
451cb0ef41Sopenharmony_ci    let diffTable
461cb0ef41Sopenharmony_ci    if (npm.config.get('dry-run') || npm.config.get('long')) {
471cb0ef41Sopenharmony_ci      diffTable = new Table({
481cb0ef41Sopenharmony_ci        chars: {
491cb0ef41Sopenharmony_ci          top: '',
501cb0ef41Sopenharmony_ci          'top-mid': '',
511cb0ef41Sopenharmony_ci          'top-left': '',
521cb0ef41Sopenharmony_ci          'top-right': '',
531cb0ef41Sopenharmony_ci          bottom: '',
541cb0ef41Sopenharmony_ci          'bottom-mid': '',
551cb0ef41Sopenharmony_ci          'bottom-left': '',
561cb0ef41Sopenharmony_ci          'bottom-right': '',
571cb0ef41Sopenharmony_ci          left: '',
581cb0ef41Sopenharmony_ci          'left-mid': '',
591cb0ef41Sopenharmony_ci          mid: '',
601cb0ef41Sopenharmony_ci          'mid-mid': '',
611cb0ef41Sopenharmony_ci          right: '',
621cb0ef41Sopenharmony_ci          'right-mid': '',
631cb0ef41Sopenharmony_ci          middle: '  ',
641cb0ef41Sopenharmony_ci        },
651cb0ef41Sopenharmony_ci        style: {
661cb0ef41Sopenharmony_ci          'padding-left': 0,
671cb0ef41Sopenharmony_ci          'padding-right': 0,
681cb0ef41Sopenharmony_ci          border: 0,
691cb0ef41Sopenharmony_ci        },
701cb0ef41Sopenharmony_ci      })
711cb0ef41Sopenharmony_ci    }
721cb0ef41Sopenharmony_ci
731cb0ef41Sopenharmony_ci    depth({
741cb0ef41Sopenharmony_ci      tree: diff,
751cb0ef41Sopenharmony_ci      visit: d => {
761cb0ef41Sopenharmony_ci        switch (d.action) {
771cb0ef41Sopenharmony_ci          case 'REMOVE':
781cb0ef41Sopenharmony_ci            diffTable?.push(['remove', d.actual.name, d.actual.package.version])
791cb0ef41Sopenharmony_ci            summary.removed++
801cb0ef41Sopenharmony_ci            break
811cb0ef41Sopenharmony_ci          case 'ADD':
821cb0ef41Sopenharmony_ci            diffTable?.push(['add', d.ideal.name, d.ideal.package.version])
831cb0ef41Sopenharmony_ci            actualTree.inventory.has(d.ideal) && summary.added++
841cb0ef41Sopenharmony_ci            break
851cb0ef41Sopenharmony_ci          case 'CHANGE':
861cb0ef41Sopenharmony_ci            diffTable?.push(['change',
871cb0ef41Sopenharmony_ci              d.actual.name,
881cb0ef41Sopenharmony_ci              d.actual.package.version + ' -> ' + d.ideal.package.version,
891cb0ef41Sopenharmony_ci            ])
901cb0ef41Sopenharmony_ci            summary.changed++
911cb0ef41Sopenharmony_ci            break
921cb0ef41Sopenharmony_ci          default:
931cb0ef41Sopenharmony_ci            return
941cb0ef41Sopenharmony_ci        }
951cb0ef41Sopenharmony_ci        const node = d.actual || d.ideal
961cb0ef41Sopenharmony_ci        log.silly(d.action, node.location)
971cb0ef41Sopenharmony_ci      },
981cb0ef41Sopenharmony_ci      getChildren: d => d.children,
991cb0ef41Sopenharmony_ci    })
1001cb0ef41Sopenharmony_ci
1011cb0ef41Sopenharmony_ci    if (diffTable) {
1021cb0ef41Sopenharmony_ci      npm.output('\n' + diffTable.toString())
1031cb0ef41Sopenharmony_ci    }
1041cb0ef41Sopenharmony_ci  }
1051cb0ef41Sopenharmony_ci
1061cb0ef41Sopenharmony_ci  if (npm.flatOptions.fund) {
1071cb0ef41Sopenharmony_ci    const fundingInfo = getFundingInfo(actualTree, { countOnly: true })
1081cb0ef41Sopenharmony_ci    summary.funding = fundingInfo.length
1091cb0ef41Sopenharmony_ci  }
1101cb0ef41Sopenharmony_ci
1111cb0ef41Sopenharmony_ci  if (npm.flatOptions.json) {
1121cb0ef41Sopenharmony_ci    if (auditReport) {
1131cb0ef41Sopenharmony_ci      // call this to set the exit code properly
1141cb0ef41Sopenharmony_ci      getAuditReport(npm, auditReport)
1151cb0ef41Sopenharmony_ci      summary.audit = npm.command === 'audit' ? auditReport
1161cb0ef41Sopenharmony_ci        : auditReport.toJSON().metadata
1171cb0ef41Sopenharmony_ci    }
1181cb0ef41Sopenharmony_ci    npm.output(JSON.stringify(summary, null, 2))
1191cb0ef41Sopenharmony_ci  } else {
1201cb0ef41Sopenharmony_ci    packagesChangedMessage(npm, summary)
1211cb0ef41Sopenharmony_ci    packagesFundingMessage(npm, summary)
1221cb0ef41Sopenharmony_ci    printAuditReport(npm, auditReport)
1231cb0ef41Sopenharmony_ci  }
1241cb0ef41Sopenharmony_ci}
1251cb0ef41Sopenharmony_ci
1261cb0ef41Sopenharmony_ci// if we're running `npm audit fix`, then we print the full audit report
1271cb0ef41Sopenharmony_ci// at the end if there's still stuff, because it's silly for `npm audit`
1281cb0ef41Sopenharmony_ci// to tell you to run `npm audit` for details.  otherwise, use the summary
1291cb0ef41Sopenharmony_ci// report.  if we get here, we know it's not quiet or json.
1301cb0ef41Sopenharmony_ci// If the loglevel is silent, then we just run the report
1311cb0ef41Sopenharmony_ci// to get the exitCode set appropriately.
1321cb0ef41Sopenharmony_ciconst printAuditReport = (npm, report) => {
1331cb0ef41Sopenharmony_ci  const res = getAuditReport(npm, report)
1341cb0ef41Sopenharmony_ci  if (!res || !res.report) {
1351cb0ef41Sopenharmony_ci    return
1361cb0ef41Sopenharmony_ci  }
1371cb0ef41Sopenharmony_ci  npm.output(`\n${res.report}`)
1381cb0ef41Sopenharmony_ci}
1391cb0ef41Sopenharmony_ci
1401cb0ef41Sopenharmony_ciconst getAuditReport = (npm, report) => {
1411cb0ef41Sopenharmony_ci  if (!report) {
1421cb0ef41Sopenharmony_ci    return
1431cb0ef41Sopenharmony_ci  }
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci  // when in silent mode, we print nothing.  the JSON output is
1461cb0ef41Sopenharmony_ci  // going to just JSON.stringify() the report object.
1471cb0ef41Sopenharmony_ci  const reporter = npm.silent ? 'quiet'
1481cb0ef41Sopenharmony_ci    : npm.flatOptions.json ? 'quiet'
1491cb0ef41Sopenharmony_ci    : npm.command !== 'audit' ? 'install'
1501cb0ef41Sopenharmony_ci    : 'detail'
1511cb0ef41Sopenharmony_ci  const defaultAuditLevel = npm.command !== 'audit' ? 'none' : 'low'
1521cb0ef41Sopenharmony_ci  const auditLevel = npm.flatOptions.auditLevel || defaultAuditLevel
1531cb0ef41Sopenharmony_ci
1541cb0ef41Sopenharmony_ci  const res = npmAuditReport(report, {
1551cb0ef41Sopenharmony_ci    reporter,
1561cb0ef41Sopenharmony_ci    ...npm.flatOptions,
1571cb0ef41Sopenharmony_ci    auditLevel,
1581cb0ef41Sopenharmony_ci    chalk: npm.chalk,
1591cb0ef41Sopenharmony_ci  })
1601cb0ef41Sopenharmony_ci  if (npm.command === 'audit') {
1611cb0ef41Sopenharmony_ci    process.exitCode = process.exitCode || res.exitCode
1621cb0ef41Sopenharmony_ci  }
1631cb0ef41Sopenharmony_ci  return res
1641cb0ef41Sopenharmony_ci}
1651cb0ef41Sopenharmony_ci
1661cb0ef41Sopenharmony_ciconst packagesChangedMessage = (npm, { added, removed, changed, audited }) => {
1671cb0ef41Sopenharmony_ci  const msg = ['\n']
1681cb0ef41Sopenharmony_ci  if (added === 0 && removed === 0 && changed === 0) {
1691cb0ef41Sopenharmony_ci    msg.push('up to date')
1701cb0ef41Sopenharmony_ci    if (audited) {
1711cb0ef41Sopenharmony_ci      msg.push(', ')
1721cb0ef41Sopenharmony_ci    }
1731cb0ef41Sopenharmony_ci  } else {
1741cb0ef41Sopenharmony_ci    if (added) {
1751cb0ef41Sopenharmony_ci      msg.push(`added ${added} package${added === 1 ? '' : 's'}`)
1761cb0ef41Sopenharmony_ci    }
1771cb0ef41Sopenharmony_ci
1781cb0ef41Sopenharmony_ci    if (removed) {
1791cb0ef41Sopenharmony_ci      if (added) {
1801cb0ef41Sopenharmony_ci        msg.push(', ')
1811cb0ef41Sopenharmony_ci      }
1821cb0ef41Sopenharmony_ci
1831cb0ef41Sopenharmony_ci      if (added && !audited && !changed) {
1841cb0ef41Sopenharmony_ci        msg.push('and ')
1851cb0ef41Sopenharmony_ci      }
1861cb0ef41Sopenharmony_ci
1871cb0ef41Sopenharmony_ci      msg.push(`removed ${removed} package${removed === 1 ? '' : 's'}`)
1881cb0ef41Sopenharmony_ci    }
1891cb0ef41Sopenharmony_ci    if (changed) {
1901cb0ef41Sopenharmony_ci      if (added || removed) {
1911cb0ef41Sopenharmony_ci        msg.push(', ')
1921cb0ef41Sopenharmony_ci      }
1931cb0ef41Sopenharmony_ci
1941cb0ef41Sopenharmony_ci      if (!audited && (added || removed)) {
1951cb0ef41Sopenharmony_ci        msg.push('and ')
1961cb0ef41Sopenharmony_ci      }
1971cb0ef41Sopenharmony_ci
1981cb0ef41Sopenharmony_ci      msg.push(`changed ${changed} package${changed === 1 ? '' : 's'}`)
1991cb0ef41Sopenharmony_ci    }
2001cb0ef41Sopenharmony_ci    if (audited) {
2011cb0ef41Sopenharmony_ci      msg.push(', and ')
2021cb0ef41Sopenharmony_ci    }
2031cb0ef41Sopenharmony_ci  }
2041cb0ef41Sopenharmony_ci  if (audited) {
2051cb0ef41Sopenharmony_ci    msg.push(`audited ${audited} package${audited === 1 ? '' : 's'}`)
2061cb0ef41Sopenharmony_ci  }
2071cb0ef41Sopenharmony_ci
2081cb0ef41Sopenharmony_ci  msg.push(` in ${ms(Date.now() - npm.started)}`)
2091cb0ef41Sopenharmony_ci  npm.output(msg.join(''))
2101cb0ef41Sopenharmony_ci}
2111cb0ef41Sopenharmony_ci
2121cb0ef41Sopenharmony_ciconst packagesFundingMessage = (npm, { funding }) => {
2131cb0ef41Sopenharmony_ci  if (!funding) {
2141cb0ef41Sopenharmony_ci    return
2151cb0ef41Sopenharmony_ci  }
2161cb0ef41Sopenharmony_ci
2171cb0ef41Sopenharmony_ci  npm.output('')
2181cb0ef41Sopenharmony_ci  const pkg = funding === 1 ? 'package' : 'packages'
2191cb0ef41Sopenharmony_ci  const is = funding === 1 ? 'is' : 'are'
2201cb0ef41Sopenharmony_ci  npm.output(`${funding} ${pkg} ${is} looking for funding`)
2211cb0ef41Sopenharmony_ci  npm.output('  run `npm fund` for details')
2221cb0ef41Sopenharmony_ci}
2231cb0ef41Sopenharmony_ci
2241cb0ef41Sopenharmony_cimodule.exports = reifyOutput
225