import type {
CostAnalysis,
SecurityFinding,
ComplianceCheck,
CloudReport,
} from '../types/index.js';
export class Formatters {
/**
* Format cost analysis as markdown
*/
static formatCostAnalysis(analysis: CostAnalysis): string {
let output = `# Cost Analysis - ${analysis.provider.toUpperCase()}\n\n`;
output += `**Period:** ${analysis.period.start.toISOString().split('T')[0]} to ${analysis.period.end.toISOString().split('T')[0]}\n`;
output += `**Total Cost:** ${analysis.currency} ${analysis.totalCost.toFixed(2)}\n\n`;
if (analysis.breakdown.length > 0) {
output += '## Cost Breakdown by Service\n\n';
output += '| Service | Cost | Percentage | Resources |\n';
output += '|---------|------|------------|----------|\n';
for (const item of analysis.breakdown) {
output += `| ${item.service} | ${analysis.currency} ${item.cost.toFixed(2)} | ${item.percentage.toFixed(1)}% | ${item.resourceCount || 'N/A'} |\n`;
}
output += '\n';
}
if (analysis.trends && analysis.trends.length > 0) {
output += '## Cost Trends\n\n';
for (const trend of analysis.trends.slice(0, 10)) {
output += `- ${trend.date.toISOString().split('T')[0]}: ${analysis.currency} ${trend.cost.toFixed(2)}${trend.service ? ` (${trend.service})` : ''}\n`;
}
if (analysis.trends.length > 10) {
output += `\n... and ${analysis.trends.length - 10} more entries\n`;
}
}
return output;
}
/**
* Format security findings as markdown
*/
static formatSecurityFindings(findings: SecurityFinding[]): string {
let output = `# Security Findings\n\n`;
output += `**Total Findings:** ${findings.length}\n\n`;
const bySeverity = {
critical: findings.filter((f) => f.severity === 'critical'),
high: findings.filter((f) => f.severity === 'high'),
medium: findings.filter((f) => f.severity === 'medium'),
low: findings.filter((f) => f.severity === 'low'),
};
output += `- Critical: ${bySeverity.critical.length}\n`;
output += `- High: ${bySeverity.high.length}\n`;
output += `- Medium: ${bySeverity.medium.length}\n`;
output += `- Low: ${bySeverity.low.length}\n\n`;
if (findings.length > 0) {
output += '## Findings\n\n';
for (const finding of findings.slice(0, 20)) {
const severityIcon = {
critical: '🔴',
high: '🟠',
medium: '🟡',
low: '🟢',
}[finding.severity];
output += `### ${severityIcon} ${finding.title}\n\n`;
output += `- **Severity:** ${finding.severity}\n`;
output += `- **Provider:** ${finding.provider.toUpperCase()}\n`;
output += `- **Resource:** ${finding.resourceId}\n`;
output += `- **Type:** ${finding.resourceType}\n`;
output += `- **Description:** ${finding.description}\n`;
output += `- **Recommendation:** ${finding.recommendation}\n`;
output += `- **Detected:** ${finding.detectedAt.toISOString()}\n\n`;
}
if (findings.length > 20) {
output += `\n... and ${findings.length - 20} more findings\n`;
}
}
return output;
}
/**
* Format compliance check as markdown
*/
static formatComplianceCheck(check: ComplianceCheck): string {
let output = `# Compliance Check - ${check.standard}\n\n`;
output += `**Provider:** ${check.provider.toUpperCase()}\n`;
output += `**Compliant:** ${check.compliant ? '✅ Yes' : '❌ No'}\n`;
if (check.score !== undefined) {
output += `**Score:** ${check.score}/100\n`;
}
output += '\n';
if (check.findings.length > 0) {
output += '## Findings\n\n';
for (const finding of check.findings) {
const statusIcon = {
pass: '✅',
fail: '❌',
warning: '⚠️',
}[finding.status];
output += `### ${statusIcon} ${finding.rule}\n\n`;
output += `- **Status:** ${finding.status}\n`;
output += `- **Description:** ${finding.description}\n`;
if (finding.resourceId) {
output += `- **Resource:** ${finding.resourceId}\n`;
}
output += '\n';
}
}
return output;
}
/**
* Format cloud report
*/
static formatCloudReport(report: CloudReport): string {
let output = `# ${report.type.charAt(0).toUpperCase() + report.type.slice(1)} Report\n\n`;
output += `**Provider:** ${report.provider.toUpperCase()}\n`;
output += `**Generated At:** ${report.generatedAt.toISOString()}\n\n`;
output += `## Summary\n\n`;
output += `- Total Resources: ${report.summary.totalResources}\n`;
if (report.summary.totalCost !== undefined) {
output += `- Total Cost: $${report.summary.totalCost.toFixed(2)}\n`;
}
if (report.summary.securityIssues !== undefined) {
output += `- Security Issues: ${report.summary.securityIssues}\n`;
}
if (report.summary.complianceScore !== undefined) {
output += `- Compliance Score: ${report.summary.complianceScore}/100\n`;
}
return output;
}
/**
* Format bytes to human readable format
*/
static formatBytes(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
/**
* Format currency
*/
static formatCurrency(amount: number, currency: string = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount);
}
/**
* Format JSON with indentation
*/
static formatJSON(data: unknown): string {
return JSON.stringify(data, null, 2);
}
}