Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
reify-output.js221 linesDownload Raw Back to utils
1// pass in an arborist object, and it'll output the data about what
2// was done, what was audited, etc.
3//
4// added ## packages, removed ## packages, and audited ## packages in 19.157s
5//
6// 1 package is looking for funding
7//   run `npm fund` for details
8//
9// found 37 vulnerabilities (5 low, 7 moderate, 25 high)
10//   run `npm audit fix` to fix them, or `npm audit` for details
11
12const { log, output } = require('proc-log')
13const { depth } = require('treeverse')
14const ms = require('ms')
15const npmAuditReport = require('npm-audit-report')
16const { readTree: getFundingInfo } = require('libnpmfund')
17const auditError = require('./audit-error.js')
18
19// TODO: output JSON if flatOptions.json is true
20const reifyOutput = (npm, arb) => {
21  const { diff, actualTree } = arb
22
23  // note: fails and crashes if we're running audit fix and there was an error which is a good thing, because there's no point printing all this other stuff in that case!
24  const auditReport = auditError(npm, arb.auditReport) ? null : arb.auditReport
25
26  // don't print any info in --silent mode, but we still need to set the exitCode properly from the audit report, if we have one.
27  if (npm.silent) {
28    getAuditReport(npm, auditReport)
29    return
30  }
31
32  const summary = {
33    add: [],
34    added: 0,
35    // audit gets added later
36    audited: auditReport && !auditReport.error ? actualTree.inventory.size : 0,
37    change: [],
38    changed: 0,
39    funding: 0,
40    remove: [],
41    removed: 0,
42  }
43
44  if (diff) {
45    const showDiff = npm.config.get('dry-run') || npm.config.get('long')
46    const chalk = npm.chalk
47
48    depth({
49      tree: diff,
50      visit: d => {
51        switch (d.action) {
52          case 'REMOVE':
53            if (showDiff) {
54              output.standard(`${chalk.blue('remove')} ${d.actual.name} ${d.actual.package.version}`)
55            }
56            summary.removed++
57            summary.remove.push({
58              name: d.actual.name,
59              version: d.actual.package.version,
60              path: d.actual.path,
61            })
62            break
63          case 'ADD':
64            if (showDiff) {
65              output.standard(`${chalk.green('add')} ${d.ideal.name} ${d.ideal.package.version}`)
66            }
67            if (actualTree.inventory.has(d.ideal)) {
68              summary.added++
69              summary.add.push({
70                name: d.ideal.name,
71                version: d.ideal.package.version,
72                path: d.ideal.path,
73              })
74            }
75            break
76          case 'CHANGE':
77            if (showDiff) {
78              output.standard(`${chalk.cyan('change')} ${d.actual.name} ${d.actual.package.version} => ${d.ideal.package.version}`)
79            }
80            summary.changed++
81            summary.change.push({
82              from: {
83                name: d.actual.name,
84                version: d.actual.package.version,
85                path: d.actual.path,
86              },
87              to: {
88                name: d.ideal.name,
89                version: d.ideal.package.version,
90                path: d.ideal.path,
91              },
92            })
93            break
94          default:
95            return
96        }
97        const node = d.actual || d.ideal
98        log.silly(d.action, node.location)
99      },
100      getChildren: d => d.children,
101    })
102  }
103
104  if (npm.flatOptions.fund) {
105    const fundingInfo = getFundingInfo(actualTree, { countOnly: true })
106    summary.funding = fundingInfo.length
107  }
108
109  if (npm.flatOptions.json) {
110    if (auditReport) {
111      // call this to set the exit code properly
112      getAuditReport(npm, auditReport)
113      summary.audit = npm.command === 'audit' ? auditReport
114        : auditReport.toJSON().metadata
115    }
116    output.buffer(summary)
117  } else {
118    packagesChangedMessage(npm, summary)
119    packagesFundingMessage(npm, summary)
120    printAuditReport(npm, auditReport)
121  }
122}
123
124// if we're running `npm audit fix`, then we print the full audit report at the end if there's still stuff, because it's silly for `npm audit` to tell you to run `npm audit` for details.
125// otherwise, use the summary report.
126// if we get here, we know it's not quiet or json.
127// If the loglevel is silent, then we just run the report to get the exitCode set appropriately.
128const printAuditReport = (npm, report) => {
129  const res = getAuditReport(npm, report)
130  if (!res || !res.report) {
131    return
132  }
133  output.standard(`\n${res.report}`)
134}
135
136const getAuditReport = (npm, report) => {
137  if (!report) {
138    return
139  }
140
141  // when in silent mode, we print nothing.
142  // the JSON output is going to just JSON.stringify() the report object.
143  const reporter = npm.silent ? 'quiet'
144    : npm.flatOptions.json ? 'quiet'
145    : npm.command !== 'audit' ? 'install'
146    : 'detail'
147  const defaultAuditLevel = npm.command !== 'audit' ? 'none' : 'low'
148  const auditLevel = npm.flatOptions.auditLevel || defaultAuditLevel
149
150  const res = npmAuditReport(report, {
151    reporter,
152    ...npm.flatOptions,
153    auditLevel,
154    chalk: npm.chalk,
155  })
156  if (npm.command === 'audit') {
157    process.exitCode = process.exitCode || res.exitCode
158  }
159  return res
160}
161
162const packagesChangedMessage = (npm, { added, removed, changed, audited }) => {
163  const msg = ['\n']
164  if (added === 0 && removed === 0 && changed === 0) {
165    msg.push('up to date')
166    if (audited) {
167      msg.push(', ')
168    }
169  } else {
170    if (added) {
171      msg.push(`added ${added} package${added === 1 ? '' : 's'}`)
172    }
173
174    if (removed) {
175      if (added) {
176        msg.push(', ')
177      }
178
179      if (added && !audited && !changed) {
180        msg.push('and ')
181      }
182
183      msg.push(`removed ${removed} package${removed === 1 ? '' : 's'}`)
184    }
185    if (changed) {
186      if (added || removed) {
187        msg.push(', ')
188      }
189
190      if (!audited && (added || removed)) {
191        msg.push('and ')
192      }
193
194      msg.push(`changed ${changed} package${changed === 1 ? '' : 's'}`)
195    }
196    if (audited) {
197      msg.push(', and ')
198    }
199  }
200  if (audited) {
201    msg.push(`audited ${audited} package${audited === 1 ? '' : 's'}`)
202  }
203
204  msg.push(` in ${ms(Date.now() - npm.started)}`)
205  output.standard(msg.join(''))
206}
207
208const packagesFundingMessage = (npm, { funding }) => {
209  if (!funding) {
210    return
211  }
212
213  output.standard()
214  const pkg = funding === 1 ? 'package' : 'packages'
215  const is = funding === 1 ? 'is' : 'are'
216  output.standard(`${funding} ${pkg} ${is} looking for funding`)
217  output.standard('  run `npm fund` for details')
218}
219
220module.exports = reifyOutput
221 
codekingpro/portable-devtools · Team Ai