110 lines
4.0 KiB
JavaScript
110 lines
4.0 KiB
JavaScript
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.MarkdownExporter = void 0;
|
|
const fs = __importStar(require("fs"));
|
|
const path = __importStar(require("path"));
|
|
class MarkdownExporter {
|
|
async export(finding, outputDir) {
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
const date = finding.createdAt.toISOString().split('T')[0];
|
|
const seed = finding.actionTrace[0]?.seed ?? 'N/A';
|
|
const replayCmd = `npm run replay -- --report ${outputDir}/report.json`;
|
|
const steps = finding.actionTrace
|
|
.map((action, i) => {
|
|
switch (action.type) {
|
|
case 'navigate':
|
|
return `${i + 1}. Navigate to \`${action.url}\``;
|
|
case 'click':
|
|
return `${i + 1}. Click element \`${action.selector}\``;
|
|
case 'fill':
|
|
return `${i + 1}. Fill \`${action.selector}\` with \`${JSON.stringify(action.value ?? '')}\``;
|
|
case 'select':
|
|
return `${i + 1}. Select \`${action.value}\` in \`${action.selector}\``;
|
|
case 'submit':
|
|
return `${i + 1}. Submit form \`${action.selector}\``;
|
|
default:
|
|
return `${i + 1}. ${action.type}`;
|
|
}
|
|
})
|
|
.join('\n');
|
|
const httpTable = finding.evidence.httpLog.length > 0
|
|
? [
|
|
'| Method | URL | Status | Duration |',
|
|
'|--------|-----|--------|----------|',
|
|
...finding.evidence.httpLog.map((r) => `| ${r.method} | ${r.url} | ${r.status} | ${r.durationMs}ms |`),
|
|
].join('\n')
|
|
: '_No HTTP log available._';
|
|
const rawErrors = finding.evidence.rawErrors.length > 0
|
|
? '```\n' + finding.evidence.rawErrors.join('\n') + '\n```'
|
|
: '_No raw errors recorded._';
|
|
const md = `# Bug Report — ${finding.type.value} — ${date}
|
|
|
|
## Summary
|
|
${finding.description}
|
|
|
|
## Severity
|
|
**${finding.severity.value}** — detected by ABE heuristic rule \`${finding.type.value}\`
|
|
|
|
## Status
|
|
**${finding.status.value}**
|
|
|
|
## Reproduction Steps
|
|
|
|
${steps.length > 0 ? steps : '_No steps recorded._'}
|
|
|
|
**Seed used**: \`${seed}\`
|
|
**Replay command**: \`${replayCmd}\`
|
|
|
|
## Observed Behavior
|
|
${finding.description}
|
|
|
|
## Evidence
|
|
- Screenshot: \`${finding.evidence.screenshotPath ?? 'N/A'}\`
|
|
- DOM Snapshot: \`${finding.evidence.domSnapshotPath ?? 'N/A'}\`
|
|
- HTTP Log:
|
|
|
|
${httpTable}
|
|
|
|
## Raw Errors
|
|
${rawErrors}
|
|
`;
|
|
const filePath = path.join(outputDir, 'report.md');
|
|
fs.writeFileSync(filePath, md, 'utf8');
|
|
return filePath;
|
|
}
|
|
}
|
|
exports.MarkdownExporter = MarkdownExporter;
|