yarn-audit-html
Version:
Generate a HTML report for Yarn Audit
79 lines (78 loc) • 3.08 kB
JavaScript
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);
}