UNPKG

depdrift

Version:

A tool to analyze dependency drift in JavaScript projects

187 lines (169 loc) 6.67 kB
/** * 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 = ` <!DOCTYPE html> <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 };