pury
Version:
đĄī¸ AI-powered security scanner with advanced threat detection, dual reporting system (detailed & summary), and comprehensive code analysis
292 lines âĸ 12.4 kB
JavaScript
import chalk from 'chalk';
import { table } from 'table';
import { Severity } from '../types/index.js';
export class ConsoleReporter {
async output(report) {
this.printHeader();
this.printSummary(report);
if (report.findings.length > 0) {
this.printFindings(report.findings);
}
else {
this.printNoFindings();
}
this.printFooter(report);
}
async writeToFile(report, filePath) {
// For console output, we'll just write a plain text version
const fs = await import('fs/promises');
const output = this.generateTextReport(report);
await fs.writeFile(filePath, output, 'utf8');
}
printHeader() {
console.log();
console.log(chalk.cyan.bold('ââââââââââââââââââââââââââââââââââââââââââââââââââââ'));
console.log(chalk.cyan.bold('â') +
chalk.white.bold(' đĄī¸ PuryAI Security Scanner ') +
chalk.cyan.bold('â'));
console.log(chalk.cyan.bold('â') +
chalk.gray(' Advanced AI-Powered Code Analysis ') +
chalk.cyan.bold('â'));
console.log(chalk.cyan.bold('ââââââââââââââââââââââââââââââââââââââââââââââââââââ'));
}
printSummary(report) {
const { summary } = report;
console.log();
console.log(chalk.yellow.bold('đ Scan Summary'));
console.log(chalk.yellow('â'.repeat(50)));
const summaryData = [
['Files Scanned', summary.filesScanned.toString()],
[
'Threats Found',
this.colorBySeverity(summary.threatsFound.toString(), summary.threatsFound > 0 ? 'medium' : 'info')
],
['Scan Duration', `${(summary.scanDuration / 1000).toFixed(2)}s`],
['Timestamp', new Date(report.metadata.timestamp).toLocaleString()]
];
// Add severity breakdown
if (summary.threatsFound > 0) {
summaryData.push(['', '']); // Empty row
summaryData.push([chalk.bold('Severity Breakdown'), '']);
for (const [severity, count] of Object.entries(summary.severityCount)) {
if (count > 0) {
summaryData.push([
` ${this.getSeverityIcon(severity)} ${severity.charAt(0).toUpperCase() + severity.slice(1)}`,
this.colorBySeverity(count.toString(), severity)
]);
}
}
}
console.log(table(summaryData, {
border: {
topBody: '',
topJoin: '',
topLeft: '',
topRight: '',
bottomBody: '',
bottomJoin: '',
bottomLeft: '',
bottomRight: '',
bodyLeft: '',
bodyRight: '',
bodyJoin: '',
joinBody: '',
joinLeft: '',
joinRight: '',
joinJoin: ''
},
columnDefault: {
paddingLeft: 0,
paddingRight: 2
}
}));
}
printFindings(findings) {
console.log();
console.log(chalk.red.bold('đ Security Findings'));
console.log(chalk.red('â'.repeat(50)));
// Group findings by file and severity for better organization
const groupedByFile = this.groupFindingsByFileAndSeverity(findings);
const basePath = process.cwd();
for (const [filePath, severityGroups] of groupedByFile.entries()) {
const relativePath = this.getRelativePath(filePath, basePath);
console.log();
console.log(chalk.cyan.bold(`đ ${relativePath}`));
// Show findings by severity for this file
for (const severity of ['critical', 'high', 'medium', 'low']) {
const severityFindings = severityGroups[severity];
if (severityFindings && severityFindings.length > 0) {
this.printFileSeverityFindings(severity, severityFindings);
}
}
}
}
printFileSeverityFindings(severity, findings) {
const icon = this.getSeverityIcon(severity);
const severityColor = this.colorBySeverity(` ${icon} ${severity.toUpperCase()}`, severity);
// Group by issue type to reduce repetition
const groupedByType = this.groupFindingsByType(findings);
for (const [issueType, typeFindings] of groupedByType.entries()) {
if (typeFindings.length === 1) {
const finding = typeFindings[0];
console.log(severityColor);
console.log(` ââ ${finding.title}${finding.line ? ` (Line ${finding.line})` : ''}`);
if (finding.evidence && finding.evidence.length <= 50) {
console.log(chalk.gray(` â đĄ ${finding.evidence}`));
}
}
else {
console.log(severityColor);
console.log(` ââ ${issueType} (${typeFindings.length} occurrences)`);
typeFindings.forEach((finding, idx) => {
const prefix = idx === typeFindings.length - 1 ? ' ââ' : ' ââ';
console.log(`${prefix} Line ${finding.line || '?'}`);
});
}
}
}
printNoFindings() {
console.log();
console.log(chalk.green.bold('âââââââââââââââââââââââââââââââââââââââââ'));
console.log(chalk.green.bold('â') +
chalk.white.bold(' â
No security issues found! ') +
chalk.green.bold('â'));
console.log(chalk.green.bold('â') +
chalk.gray(' Your code appears clean & secure ') +
chalk.green.bold('â'));
console.log(chalk.green.bold('âââââââââââââââââââââââââââââââââââââââââ'));
}
printFooter(report) {
console.log();
console.log(chalk.cyan('â'.repeat(50)));
console.log(chalk.magenta(`đ Generated by PuryAI v${report.metadata.version}`));
if (report.metadata.aiProvider) {
console.log(chalk.blue(`đ¤ AI Analysis powered by ${report.metadata.aiProvider}`));
}
// Show recommendations
if (report.summary.threatsFound > 0) {
console.log();
console.log(chalk.yellow.bold('đĄ Recommendations:'));
console.log(chalk.yellow(' âĸ Review and address all critical and high severity issues'));
console.log(chalk.yellow(' âĸ Use --format json for detailed analysis'));
console.log(chalk.yellow(' âĸ Consider running additional analyzers with --analyzers'));
}
else {
console.log(chalk.green('đ Great job! Your code follows security best practices.'));
}
console.log();
}
groupFindingsByFileAndSeverity(findings) {
const grouped = new Map();
for (const finding of findings) {
if (!grouped.has(finding.file)) {
grouped.set(finding.file, {
[Severity.CRITICAL]: [],
[Severity.HIGH]: [],
[Severity.MEDIUM]: [],
[Severity.LOW]: []
});
}
grouped.get(finding.file)[finding.severity].push(finding);
}
return grouped;
}
groupFindingsByType(findings) {
const grouped = new Map();
for (const finding of findings) {
const key = finding.title;
if (!grouped.has(key)) {
grouped.set(key, []);
}
grouped.get(key).push(finding);
}
return grouped;
}
getRelativePath(filePath, basePath) {
if (filePath.startsWith(basePath)) {
const relativePath = filePath.substring(basePath.length);
return relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
}
return filePath;
}
groupFindingsBySeverity(findings) {
const grouped = {
[Severity.CRITICAL]: [],
[Severity.HIGH]: [],
[Severity.MEDIUM]: [],
[Severity.LOW]: []
};
for (const finding of findings) {
grouped[finding.severity].push(finding);
}
return grouped;
}
getSeverityIcon(severity) {
switch (severity) {
case Severity.CRITICAL:
return 'đ¨';
case Severity.HIGH:
return 'â ī¸';
case Severity.MEDIUM:
return 'đ';
case Severity.LOW:
return 'âšī¸';
default:
return 'đ';
}
}
colorBySeverity(text, severity) {
switch (severity) {
case Severity.CRITICAL:
return chalk.red.bold(text);
case Severity.HIGH:
return chalk.red(text);
case Severity.MEDIUM:
return chalk.yellow(text);
case Severity.LOW:
return chalk.blue(text);
case 'info':
return chalk.cyan(text);
default:
return text;
}
}
generateTextReport(report) {
const lines = [];
lines.push('PuryAI Security Scanner Report');
lines.push('='.repeat(40));
lines.push('');
// Summary
lines.push('SCAN SUMMARY');
lines.push('-'.repeat(20));
lines.push(`Files Scanned: ${report.summary.filesScanned}`);
lines.push(`Threats Found: ${report.summary.threatsFound}`);
lines.push(`Scan Duration: ${(report.summary.scanDuration / 1000).toFixed(2)}s`);
lines.push(`Timestamp: ${new Date(report.metadata.timestamp).toLocaleString()}`);
lines.push('');
// Severity breakdown
if (report.summary.threatsFound > 0) {
lines.push('SEVERITY BREAKDOWN');
lines.push('-'.repeat(20));
for (const [severity, count] of Object.entries(report.summary.severityCount)) {
if (count > 0) {
lines.push(`${severity.charAt(0).toUpperCase() + severity.slice(1)}: ${count}`);
}
}
lines.push('');
}
// Findings
if (report.findings.length > 0) {
lines.push('SECURITY FINDINGS');
lines.push('-'.repeat(20));
const groupedFindings = this.groupFindingsBySeverity(report.findings);
for (const severity of ['critical', 'high', 'medium', 'low']) {
const severityFindings = groupedFindings[severity];
if (severityFindings && severityFindings.length > 0) {
lines.push(`\\n${severity.toUpperCase()} SEVERITY (${severityFindings.length})`);
lines.push('-'.repeat(30));
severityFindings.forEach((finding, index) => {
const location = finding.line ? `${finding.file}:${finding.line}` : finding.file;
lines.push(`\\n${index + 1}. ${finding.title}`);
lines.push(` Location: ${location}`);
lines.push(` Description: ${finding.description}`);
if (finding.evidence) {
lines.push(` Evidence: ${finding.evidence}`);
}
if (finding.suggestion) {
lines.push(` Suggestion: ${finding.suggestion}`);
}
});
}
}
}
else {
lines.push('No security issues found!');
}
lines.push('');
lines.push('-'.repeat(40));
lines.push(`Generated by PuryAI v${report.metadata.version}`);
return lines.join('\\n');
}
}
//# sourceMappingURL=console.js.map