diff --git a/src/output/html-reporter.ts b/src/output/html-reporter.ts
index c49f32b9..1b1ea6f1 100644
--- a/src/output/html-reporter.ts
+++ b/src/output/html-reporter.ts
@@ -279,7 +279,16 @@ button.header-link:hover{color:var(--link);border-color:var(--link)}
.priority-legend-item{display:flex;align-items:flex-start;gap:10px;font-size:12px;color:var(--text-2);line-height:1.5}
.sev-dev-note{text-align:center;font-size:12px;color:var(--text-2);margin:-12px 0 16px;padding:0 32px}`;
-export function renderHtmlReport(data: ReportData): string {
+function embedJson(value: unknown): string {
+ return JSON.stringify(value)
+ .replace(//g, "\\u003e")
+ .replace(/&/g, "\\u0026")
+ .replace(/\u2028/g, "\\u2028")
+ .replace(/\u2029/g, "\\u2029");
+}
+
+export function renderHtmlReport(data: ReportData, jsonPayload?: object): string {
const projectName = path.basename(data.projectPath);
const scanDate = new Date(data.scannedAt).toLocaleString();
@@ -307,12 +316,7 @@ export function renderHtmlReport(data: ReportData): string {
const skippedDepsHtml = renderSkippedDependenciesSection(data.skippedDependencies);
const duplicatePackagesHtml = renderDuplicatePackagesSection(data.findings);
const findingRowsHtml = data.findings.map((f, i) => renderFindingRow(f, i, skippedKeys)).join("\n");
- const dataJson = JSON.stringify(data)
- .replace(//g, "\\u003e")
- .replace(/&/g, "\\u0026")
- .replace(/\u2028/g, "\\u2028")
- .replace(/\u2029/g, "\\u2029");
+ const dataJson = embedJson(jsonPayload ?? data);
return `
@@ -1031,6 +1035,8 @@ export function renderFixPlan(plan: SuggestedFixCommandPlan | null): string {
export async function writeHtmlReport(params: {
outputDir: string;
data: ReportData;
+ /** Same object `--json` writes. Stored as report.json and offered as the in-page download. */
+ jsonPayload: object;
autoOpen: boolean;
}): Promise<{ reportPath: string }> {
const dirExisted = fs.existsSync(params.outputDir);
@@ -1044,8 +1050,8 @@ export async function writeHtmlReport(params: {
const jsonPath = path.join(params.outputDir, "report.json");
try {
- fs.writeFileSync(jsonPath, JSON.stringify(params.data, null, 2), "utf8");
- fs.writeFileSync(indexPath, renderHtmlReport(params.data), "utf8");
+ fs.writeFileSync(jsonPath, JSON.stringify(params.jsonPayload, null, 2), "utf8");
+ fs.writeFileSync(indexPath, renderHtmlReport(params.data, params.jsonPayload), "utf8");
} catch (err) {
if (!dirExisted) {
try { fs.rmSync(params.outputDir, { recursive: true, force: true }); } catch { /* best-effort cleanup */ }
diff --git a/src/output/multi-folder-html-reporter.ts b/src/output/multi-folder-html-reporter.ts
index b2e60564..75d88eee 100644
--- a/src/output/multi-folder-html-reporter.ts
+++ b/src/output/multi-folder-html-reporter.ts
@@ -17,6 +17,7 @@ import { renderOverrideFindingsHtml } from "./override-findings-html.js";
import { renderMaintenanceFindingsHtml } from "./maintenance-findings-html.js";
import { renderLicenseFindingsHtml } from "./license-findings-html.js";
import { loadLogoDataUri } from "./logos.js";
+import { buildMultiFolderScanJson } from "./scan-json.js";
import { pluralize } from "../utils/string.js";
import type { SeverityLabel } from "../types.js";
import type { OverrideFinding } from "../overrides/types.js";
@@ -483,9 +484,24 @@ document.addEventListener('click', function(e) {