UNPKG

yarn-audit-html

Version:

Generate a HTML report for Yarn Audit

79 lines (78 loc) 3.08 kB
import { readFile, writeFile } from 'node:fs/promises'; import process from 'node:process'; import ejs from 'ejs'; import { marked } from 'marked'; const bootstrapClassSeverityMap = { critical: 'danger', high: 'warning', moderate: 'info', low: 'primary', info: 'secondary', }; const severitySortPriority = Object.keys(bootstrapClassSeverityMap); export function parseAdvisory(advisory) { const vulnerabilities = {}; advisory.findings.forEach((finding) => { const { version } = finding; const key = `${advisory.module_name}@${version}-${advisory.vulnerable_versions}-${advisory.created}.${advisory.cwe}`; if (!(key in vulnerabilities)) { vulnerabilities[key] = { ...advisory, key, version, paths: finding.paths, }; } else { vulnerabilities[key].paths = [...vulnerabilities[key].paths, ...finding.paths]; } }); Object.entries(vulnerabilities).forEach(([key, vulnerability]) => { vulnerabilities[key].paths = Array.from(new Set(vulnerability.paths)); }); return Object.values(vulnerabilities); } export async function generateReport(vulnerabilities, summary, options) { vulnerabilities.sort((left, right) => severitySortPriority.indexOf(left.severity) - severitySortPriority.indexOf(right.severity)); const report = await renderReport({ reportDate: new Date(), vulnerabilities, theme: options.theme, summary: { vulnerabilities: Object.values(summary.vulnerabilities).reduce((sum, value) => sum + value, 0), totalDependencies: summary.totalDependencies, }, }, options.template); await writeReport(options.output, report); if (vulnerabilities.length > 0) { console.info(`Found ${vulnerabilities.length} vulnerabilities. Report is saved in "${options.output}"`); if (options.fatalExitCode) { return process.exit(1); } } else { console.info('Congrats!!! No vulnerabilities found.'); } return process.exit(0); } export async function renderReport(data, template) { const htmlTemplate = await readFile(template, 'utf8'); const locale = Intl.DateTimeFormat().resolvedOptions().locale; return ejs.render(htmlTemplate, { data, formatNumber: (number) => new Intl.NumberFormat(locale).format(number), formatDate: (dateStr) => new Intl.DateTimeFormat(locale, { dateStyle: 'long', timeStyle: 'long' }).format(new Date(dateStr)), severityClass: (severity) => bootstrapClassSeverityMap[severity], markdown: (code) => marked(code, { mangle: false, headerIds: false }), }); } export async function writeReport(outputPath, report) { await writeFile(outputPath, report, { encoding: 'utf8' }); } export function bailWithError(message, error, isFatalExitCode) { console.error(`${message}\n`, error); if (isFatalExitCode) { return process.exit(1); } return process.exit(0); }