depdrift
Version:
A tool to analyze dependency drift in JavaScript projects
187 lines (169 loc) • 6.67 kB
JavaScript
/**
* DepDrift HTML Formatter
* Formats dependency analysis results as HTML
*
* @module formatters/htmlFormatter
*/
import fs from 'fs';
import path from 'path';
import fsExtra from 'fs-extra';
/**
* Format analysis results as HTML
* @param {Object} results - Analysis results
* @returns {string} HTML report
*/
function generateHtmlReport(results) {
// Ensure dependencies exist
const dependencies = results.dependencies || [];
const template = `
<html>
<head>
<title>DepDrift Analysis Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 2rem; line-height: 1.6; color: #333; }
h1 { color: #2c3e50; margin-bottom: 1rem; }
h2 { color: #3498db; margin-top: 2rem; margin-bottom: 1rem; }
table { border-collapse: collapse; width: 100%; margin-bottom: 2rem; }
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f2f2f2; font-weight: bold; }
tr:hover { background-color: #f5f5f5; }
.critical { color: #fff; background-color: #e74c3c; padding: 3px 7px; border-radius: 3px; }
.high { color: #fff; background-color: #e67e22; padding: 3px 7px; border-radius: 3px; }
.medium { color: #fff; background-color: #f39c12; padding: 3px 7px; border-radius: 3px; }
.low { color: #fff; background-color: #3498db; padding: 3px 7px; border-radius: 3px; }
.none { color: #fff; background-color: #2ecc71; padding: 3px 7px; border-radius: 3px; }
.summary-box { background-color: #f9f9f9; border: 1px solid #ddd; padding: 20px; border-radius: 5px; margin-bottom: 2rem; }
.footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid #eee; color: #7f8c8d; font-size: 0.9rem; }
.stat { font-weight: bold; color: #2980b9; }
</style>
</head>
<body>
<h1>DepDrift Analysis Report</h1>
<div class="summary-box">
<h2>Project Summary</h2>
<p><strong>Project:</strong> ${results.projectName || 'Unknown'} ${results.projectVersion ? `v${results.projectVersion}` : ''}</p>
<p><strong>Dependencies Analyzed:</strong> <span class="stat">${dependencies.length}</span></p>
<p><strong>Up-to-date:</strong> <span class="stat">${dependencies.filter(dep => dep.driftLevel === 'none').length}</span> | <strong>Needs Update:</strong> <span class="stat">${dependencies.filter(dep => dep.driftLevel !== 'none').length}</span></p>
<p><strong>Vulnerable Dependencies:</strong> <span class="stat">${dependencies.filter(dep => dep.security && dep.security.vulnerable).length}</span></p>
</div>
<h2>Dependency Analysis</h2>
<table>
<thead>
<tr>
<th>Package</th>
<th>Current</th>
<th>Latest</th>
<th>Update Status</th>
<th>Last Published</th>
<th>Drift</th>
<th>Security</th>
</tr>
</thead>
<tbody>
${dependencies.map(dep => `
<tr>
<td>${dep.name}${dep.isDevDependency ? ' <small>(dev)</small>' : ''}</td>
<td class="version current">${dep.currentVersion || 'Unknown'}</td>
<td class="version latest">${dep.latestVersion || 'Unknown'}</td>
<td class="status ${dep.driftLevel === 'none' ? 'none' : dep.driftLevel === 'high' ? 'critical' : dep.driftLevel === 'medium' ? 'warning' : 'error'}">${dep.driftLevel === 'none' ?
'<span class="none">Up to date</span>' :
`<span class="${dep.driftLevel}">Needs update (${dep.daysBehind || 0} days behind)</span>`}
</td>
<td class="last-published ${dep.driftLevel === 'none' ? 'none' : dep.driftLevel === 'high' ? 'critical' : dep.driftLevel === 'medium' ? 'warning' : 'error'}">${new Date(dep.lastPublished).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) || 'Unknown'}</td>
<td class="drift-level ${dep.driftLevel}">${capitalizeDriftLevel(dep.driftLevel || 'none')}</td>
<td>${renderSecurityIssues(dep)}</td>
</tr>
`).join('')}
</tbody>
</table>
${renderRecommendations(results)}
<div class="footer">
<p>Generated by DepDrift on ${new Date().toLocaleString()}</p>
</div>
</body>
</html>`;
return template;
}
/**
* Render security issues
* @param {Object} dependency - Dependency object
* @returns {string} HTML for security issues
*/
function renderSecurityIssues(dependency) {
if (!dependency.security || !dependency.security.vulnerable) {
return '<span class="none">None</span>';
}
const vulnerabilities = dependency.security.vulnerabilities || [];
const severityClass = dependency.security.highestSeverity || 'high';
const capitalizedSeverity = capitalizeDriftLevel(severityClass);
return `<span class="${severityClass}">${capitalizedSeverity}: ${vulnerabilities.length} ${vulnerabilities.length === 1 ? 'Issue' : 'Issues'}</span>`;
}
/**
* Render recommendations section
* @param {Object} results - Analysis results
* @returns {string} HTML for recommendations
*/
function renderRecommendations(results) {
if (!results.recommendations || results.recommendations.length === 0) {
return '';
}
return `
<h2>Recommendations</h2>
<table>
<thead>
<tr>
<th>Priority</th>
<th>Package</th>
<th>Current Version</th>
<th>Recommendation</th>
<th>Details</th>
</tr>
</thead>
<tbody>
${results.recommendations.map((rec, index) => `
<tr>
<td>${index + 1}</td>
<td>${rec.dependencyName}</td>
<td>${rec.currentVersion || 'N/A'}</td>
<td>${capitalizeFirstLetter(rec.recommendation)}</td>
<td>${capitalizeFirstLetter(rec.details)}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
/**
* Capitalize the first letter of a string
* @param {string} text - Input text
* @returns {string} Text with first letter capitalized
*/
function capitalizeFirstLetter(text) {
if (!text) return '';
return text.charAt(0).toUpperCase() + text.slice(1);
}
/**
* Save HTML report to file
* @param {string} html - HTML content
* @param {string} filePath - Path to save the HTML report
* @returns {Promise<void>}
*/
async function saveHtmlReport(html, filePath) {
await fsExtra.ensureDir(path.dirname(filePath));
await fsExtra.writeFile(filePath, html, 'utf8');
}
/**
* Capitalize the first letter of drift level
* @param {string} level - Drift level
* @returns {string} Capitalized drift level
*/
function capitalizeDriftLevel(level) {
if (!level) return 'None';
return level.charAt(0).toUpperCase() + level.slice(1).toLowerCase();
}
// Export functions
export {
generateHtmlReport,
saveHtmlReport
};