supamend
Version:
Pluggable DevSecOps Security Scanner with 10+ scanners and multiple reporting channels
234 lines ⢠10.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsoleReporter = void 0;
const errors_1 = require("../core/errors");
const chalk_1 = __importDefault(require("chalk"));
class ConsoleReporter {
constructor() {
this.name = 'console';
this.description = 'Display security scan results in the console with rich formatting';
this.version = '1.0.0';
}
async init(config) {
this.config = {
colorize: config?.colorize !== false, // Default to true
showDetails: config?.showDetails !== false, // Default to true
maxIssues: config?.maxIssues || 50,
format: config?.format || 'table'
};
}
async report(results, options) {
try {
const config = { ...this.config, ...options };
console.log('\n' + chalk_1.default.bold.blue('š Security Scan Results'));
console.log(chalk_1.default.gray('='.repeat(50)) + '\n');
// Summary
this.printSummary(results, config);
if (results.length > 0) {
console.log('\n' + chalk_1.default.bold('Detailed Results:'));
console.log(chalk_1.default.gray('-'.repeat(50)));
switch (config.format) {
case 'table':
this.printTable(results, config);
break;
case 'list':
this.printList(results, config);
break;
case 'summary':
this.printSummaryOnly(results, config);
break;
default:
this.printTable(results, config);
}
}
else {
console.log(chalk_1.default.green.bold('ā
No security issues found!'));
}
console.log('\n' + chalk_1.default.gray('='.repeat(50)));
console.log(chalk_1.default.gray(`Scan completed at ${new Date().toLocaleString()}`));
}
catch (error) {
const reporterError = error instanceof Error ? error : new Error(String(error));
throw new errors_1.ReporterError(`Failed to display console report: ${reporterError.message}`, this.name, {
recoverable: false,
retryable: false,
cause: reporterError,
context: { operation: 'report' }
});
}
}
async isAvailable() {
return true; // Always available
}
getConfigSchema() {
return {
type: 'object',
properties: {
colorize: {
type: 'boolean',
description: 'Enable colored output'
},
showDetails: {
type: 'boolean',
description: 'Show detailed issue information'
},
maxIssues: {
type: 'number',
description: 'Maximum number of issues to display'
},
format: {
type: 'string',
enum: ['table', 'list', 'summary'],
description: 'Output format'
}
}
};
}
printSummary(results, config) {
const summary = this.generateSummary(results);
const colorize = config.colorize !== false;
console.log(chalk_1.default.bold('š Summary:'));
// Total issues
const totalText = `Total Issues: ${results.length}`;
console.log(colorize ? chalk_1.default.bold(totalText) : totalText);
// Severity breakdown
if (Object.keys(summary.severity).length > 0) {
console.log('\nSeverity Breakdown:');
Object.entries(summary.severity).forEach(([severity, count]) => {
const severityText = `${severity.toUpperCase()}: ${count}`;
if (colorize) {
const color = this.getSeverityColor(severity);
console.log(` ${color(severityText)}`);
}
else {
console.log(` ${severityText}`);
}
});
}
// Scanner breakdown
if (Object.keys(summary.scanners).length > 0) {
console.log('\nScanners Used:');
Object.entries(summary.scanners).forEach(([scanner, count]) => {
const scannerText = `${scanner}: ${count} issues`;
console.log(` ${colorize ? chalk_1.default.cyan(scannerText) : scannerText}`);
});
}
}
printTable(results, config) {
const colorize = config.colorize !== false;
const maxIssues = config.maxIssues || 50;
const displayResults = results.slice(0, maxIssues);
if (displayResults.length === 0)
return;
// Table header with better spacing
const header = ['Severity', 'Scanner', 'Type', 'Title', 'File', 'Rule'];
const headerText = header.map(h => colorize ? chalk_1.default.bold(h) : h).join(' | ');
console.log(headerText);
console.log(chalk_1.default.gray('-'.repeat(Math.max(headerText.length, 120))));
// Table rows
displayResults.forEach(result => {
const severity = colorize ? this.getSeverityColor(result.severity)(result.severity.toUpperCase().padEnd(8)) : result.severity.toUpperCase().padEnd(8);
const scanner = (colorize ? chalk_1.default.cyan(result.scanner) : result.scanner).padEnd(15);
const type = (colorize ? chalk_1.default.yellow(result.type) : result.type).padEnd(12);
const title = result.title.length > 35 ? result.title.substring(0, 32) + '...' : result.title.padEnd(35);
const file = result.file ? (result.line ? `${result.file}:${result.line}` : result.file) : 'N/A';
const fileFormatted = file.length > 25 ? file.substring(0, 22) + '...' : file.padEnd(25);
const rule = result.rule ? (result.rule.length > 15 ? result.rule.substring(0, 12) + '...' : result.rule) : '';
const row = [severity, scanner, type, title, colorize ? chalk_1.default.blue(fileFormatted) : fileFormatted, colorize ? chalk_1.default.gray(rule) : rule].join(' | ');
console.log(row);
});
if (results.length > maxIssues) {
console.log(chalk_1.default.gray(`\n... and ${results.length - maxIssues} more issues (use --format list for full details)`));
}
}
printList(results, config) {
const colorize = config.colorize !== false;
const maxIssues = config.maxIssues || 50;
const displayResults = results.slice(0, maxIssues);
if (displayResults.length === 0)
return;
displayResults.forEach((result, index) => {
const severity = colorize ? this.getSeverityColor(result.severity)(result.severity.toUpperCase()) : result.severity.toUpperCase();
const scanner = colorize ? chalk_1.default.cyan(result.scanner) : result.scanner;
console.log(`\n${index + 1}. ${severity} - ${result.title}`);
console.log(` š Scanner: ${scanner}`);
console.log(` š·ļø Type: ${result.type}`);
if (result.description) {
const desc = result.description.length > 100 ? result.description.substring(0, 97) + '...' : result.description;
console.log(` š Description: ${desc}`);
}
if (result.file) {
const fileInfo = result.line ? `${result.file}:${result.line}` : result.file;
console.log(` š File: ${colorize ? chalk_1.default.blue(fileInfo) : fileInfo}`);
}
if (result.rule) {
console.log(` š Rule: ${colorize ? chalk_1.default.gray(result.rule) : result.rule}`);
}
if (result.column) {
console.log(` š Position: Line ${result.line || 'N/A'}, Column ${result.column}`);
}
});
if (results.length > maxIssues) {
console.log(chalk_1.default.gray(`\n... and ${results.length - maxIssues} more issues`));
}
}
printSummaryOnly(results, config) {
const colorize = config.colorize !== false;
const criticalIssues = results.filter(r => r.severity === 'critical');
const highIssues = results.filter(r => r.severity === 'high');
if (criticalIssues.length > 0) {
console.log(chalk_1.default.bold.red('\nšØ Critical Issues:'));
criticalIssues.slice(0, 5).forEach(issue => {
const title = colorize ? chalk_1.default.red(issue.title) : issue.title;
console.log(` ⢠${title} (${issue.scanner})`);
});
if (criticalIssues.length > 5) {
console.log(chalk_1.default.gray(` ... and ${criticalIssues.length - 5} more`));
}
}
if (highIssues.length > 0) {
console.log(chalk_1.default.bold.yellow('\nā ļø High Priority Issues:'));
highIssues.slice(0, 5).forEach(issue => {
const title = colorize ? chalk_1.default.yellow(issue.title) : issue.title;
console.log(` ⢠${title} (${issue.scanner})`);
});
if (highIssues.length > 5) {
console.log(chalk_1.default.gray(` ... and ${highIssues.length - 5} more`));
}
}
}
generateSummary(results) {
const severityCounts = results.reduce((acc, result) => {
acc[result.severity] = (acc[result.severity] || 0) + 1;
return acc;
}, {});
const scannerCounts = results.reduce((acc, result) => {
acc[result.scanner] = (acc[result.scanner] || 0) + 1;
return acc;
}, {});
return {
severity: severityCounts,
scanners: scannerCounts
};
}
getSeverityColor(severity) {
switch (severity) {
case 'critical':
return chalk_1.default.red;
case 'high':
return chalk_1.default.yellow;
case 'medium':
return chalk_1.default.magenta;
case 'low':
return chalk_1.default.green;
default:
return chalk_1.default.white;
}
}
}
exports.ConsoleReporter = ConsoleReporter;
exports.default = new ConsoleReporter();
//# sourceMappingURL=console.js.map