UNPKG

yarn-audit-html

Version:

Generate a HTML report for Yarn Audit

104 lines (83 loc) 3.1 kB
import { readFile, writeFile } from "node:fs/promises"; import process from "node:process"; import ejs from "ejs"; import { marked } from "marked"; import { AuditAdvisor, AuditMetadata, Options, RawAuditAdvisor, Severity, Vulnerabilities } from "./types.js"; const bootstrapClassSeverityMap: Record<Severity, string> = { critical: "danger", high: "warning", moderate: "info", low: "primary", info: "secondary", }; const severitySortPriority = Object.keys(bootstrapClassSeverityMap); export function parseAdvisory(advisory: RawAuditAdvisor) { const vulnerabilities: 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: AuditAdvisor[], summary: AuditMetadata, options: 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: ejs.Data, template: string) { const htmlTemplate = await readFile(template, "utf8"); const locale = Intl.DateTimeFormat().resolvedOptions().locale; return ejs.render(htmlTemplate, { data, formatNumber: (number: number) => new Intl.NumberFormat(locale).format(number), formatDate: (dateStr: string) => new Intl.DateTimeFormat(locale, { dateStyle: "long", timeStyle: "long" }).format(new Date(dateStr)), severityClass: (severity: Severity) => bootstrapClassSeverityMap[severity], markdown: (code: string) => marked(code, { mangle: false, headerIds: false }), }); } export async function writeReport(outputPath: string, report: string) { await writeFile(outputPath, report, { encoding: "utf8" }); } export function bailWithError(message: string, error: Error, isFatalExitCode: boolean) { console.error(`${message}\n`, error); if (isFatalExitCode) { return process.exit(1); } return process.exit(0); }