import { mkdir, writeFile } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import type { ScanReport } from '../index.js'; import { generateHTML } from './template.js'; /** * Generates and persists the S-Edition HTML report under `.supreme2l/reports`. */ export async function generateReport( projectPath: string, report: ScanReport, ): Promise<{ verifiedPath: string; rawPath: string }> { const normalizedReport: ScanReport = { ...report, meta: { ...report.meta, s_edition_version: '1.0.0', }, }; const allCodeFindings = [...normalizedReport.confirmed_findings, ...normalizedReport.filtered_fp]; const verifiedFindings = allCodeFindings.filter( (finding) => finding.fp_analysis.is_likely_fp === false && finding.fp_analysis.reason !== 'verification_skipped_quota', ); const timestamp = formatFileTimestamp(new Date()); const verifiedFileName = `s-edition-report-${timestamp}.html`; const rawFileName = `s-edition-raw-report-${timestamp}.html`; const verifiedPath = resolve(projectPath, '.supreme2l', 'reports', verifiedFileName); const rawPath = resolve(projectPath, '.supreme2l', 'reports', rawFileName); const verifiedReport: ScanReport = { ...normalizedReport, summary: { ...normalizedReport.summary, confirmed: verifiedFindings.length, false_positives_filtered: normalizedReport.filtered_fp.length, by_severity: countBySeverity(verifiedFindings), }, confirmed_findings: verifiedFindings, }; const rawReport: ScanReport = { ...normalizedReport, summary: { ...normalizedReport.summary, confirmed: allCodeFindings.length, false_positives_filtered: 0, unverified: 0, by_severity: countBySeverity(allCodeFindings), }, confirmed_findings: allCodeFindings, filtered_fp: [], }; const verifiedHtml = generateHTML(verifiedReport); const rawHtml = generateHTML(rawReport, { isRaw: true }); await mkdir(dirname(verifiedPath), { recursive: true }); await writeFile(verifiedPath, verifiedHtml, 'utf8'); await writeFile(rawPath, rawHtml, 'utf8'); return { verifiedPath, rawPath }; } function countBySeverity(findings: Array<{ severity: string }>): Record { const counts: Record = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, UNDEFINED: 0, }; for (const finding of findings) { const severity = finding.severity.toUpperCase(); if (counts[severity] !== undefined) { counts[severity] += 1; } else { counts.UNDEFINED += 1; } } return counts; } function formatFileTimestamp(date: Date): string { const year = String(date.getFullYear()); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}${month}${day}-${hours}${minutes}${seconds}`; }