UNPKG

git-contributor-stats

Version:

CLI to compute contributor and repository statistics from a Git repository (commits, lines added/deleted, frequency, heatmap, bus-factor), with filters and multiple output formats.

1 lines 31.9 kB
{"version":3,"file":"reports.mjs","sources":["../../src/reports/csv.ts","../../src/reports/html.ts","../../src/reports/markdown.ts","../../src/features/reports.ts"],"sourcesContent":["function escapeCSV(v: string | number): string {\n if (v === null || v === undefined) return '';\n const s = String(v);\n if (s.includes(',') || s.includes('\"') || s.includes('\\n')) {\n return `\"${s.replaceAll('\"', '\"\"')}\"`;\n }\n return s;\n}\n\nexport function toCSV(rows: Record<string, string | number>[], headers: string[]): string {\n const lines: string[] = [];\n if (headers)\n lines.push(headers.map((header) => header.charAt(0).toUpperCase() + header.slice(1)).join(','));\n\n for (const r of rows) {\n lines.push(headers.map((h) => escapeCSV(r[h])).join(','));\n }\n\n return lines.join('\\n');\n}\n\ninterface TopFile {\n filename: string;\n changes: number;\n}\n\ninterface Contributor {\n name?: string;\n email?: string;\n commits: number;\n added: number;\n deleted: number;\n topFiles?: TopFile[];\n}\n\ninterface AnalysisData {\n topContributors: Contributor[];\n}\n\nexport function generateCSVReport(analysis: AnalysisData): string {\n const contribRows = analysis.topContributors.map((c) => ({\n contributor: `${c.name || 'Unknown'} <${c.email || ''}>`,\n commits: c.commits,\n added: c.added,\n deleted: c.deleted,\n net: c.added - c.deleted,\n topFiles: c.topFiles\n ? c.topFiles\n .slice(0, 5)\n .map((f) => `${f.filename} (${f.changes})`)\n .join('; ')\n : ''\n }));\n return toCSV(contribRows, ['contributor', 'commits', 'added', 'deleted', 'net', 'topFiles']);\n}\n","interface TopFile {\n filename: string;\n changes: number;\n}\n\ninterface Contributor {\n name: string;\n email: string;\n commits: number;\n added: number;\n deleted: number;\n topFiles: TopFile[];\n}\n\ninterface BusFactorFile {\n file: string;\n owner: string;\n changes: number;\n}\n\ninterface BusFactor {\n filesSingleOwner: BusFactorFile[];\n}\n\ninterface TopStatsEntry {\n name?: string;\n email?: string;\n commits?: number;\n added?: number;\n deleted?: number;\n net?: number;\n}\n\ninterface TopStats {\n byCommits?: TopStatsEntry | null;\n byAdditions?: TopStatsEntry | null;\n byDeletions?: TopStatsEntry | null;\n byNet?: TopStatsEntry | null;\n byChanges?: TopStatsEntry | null;\n}\n\ninterface AnalysisData {\n contributors: Record<string, unknown>;\n topContributors: Contributor[];\n totalCommits: number;\n totalLines: number;\n busFactor: BusFactor;\n topStats?: TopStats;\n heatmap: number[][];\n heatmapContributors?: Record<string, Record<string, number>>;\n}\n\ninterface ReportOptions {\n includeTopStats?: boolean;\n topStatsMetrics?: string[];\n}\n\nexport function generateHTMLReport(\n data: AnalysisData,\n repoRoot: string,\n opts: ReportOptions = {}\n): string {\n const includeTopStats = opts.includeTopStats !== false;\n const topMetrics = Array.isArray(opts.topStatsMetrics)\n ? opts.topStatsMetrics\n : ['commits', 'additions', 'deletions', 'net', 'changes'];\n\n const topStatsHTML = includeTopStats ? generateTopStatsHTML(data.topStats, topMetrics) : '';\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>Git Contributor Stats - ${repoRoot}</title>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <style>\n ${getCSS()}\n </style>\n</head>\n<body>\n <header class=\"header\">\n <h1>📊 Git Contributor Stats</h1>\n <p class=\"repo-path\"><strong>Repository:</strong> <code>${repoRoot}</code></p>\n <p class=\"generated-time\">Generated: ${new Date().toLocaleString()}</p>\n </header>\n\n <main class=\"main\">\n ${generateSummaryHTML(data)}\n ${topStatsHTML}\n ${generateContributorsHTML(data)}\n ${generateChartsHTML()}\n ${generateBusFactorHTML(data)}\n ${generateActivityHTML()}\n </main>\n\n <script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n <script>\n ${getJavaScript(data)}\n </script>\n</body>\n</html>`;\n}\n\nfunction getCSS(): string {\n return `\n * { box-sizing: border-box; }\n body { \n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n line-height: 1.6; margin: 0; background: #f5f7fa; color: #2d3748;\n }\n .header { \n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white; padding: 2rem; text-align: center; box-shadow: 0 4px 6px rgba(0,0,0,0.1);\n }\n .header h1 { margin: 0; font-size: 2.5rem; font-weight: 300; }\n .repo-path, .generated-time { opacity: 0.9; margin: 0.5rem 0; }\n .main { max-width: 1200px; margin: 2rem auto; padding: 0 1rem; }\n .section { \n background: white; margin: 2rem 0; padding: 2rem; border-radius: 12px;\n box-shadow: 0 2px 10px rgba(0,0,0,0.1); border: 1px solid #e2e8f0;\n }\n .section h2 { margin-top: 0; color: #2d3748; border-bottom: 2px solid #e2e8f0; padding-bottom: 0.5rem; }\n .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; }\n .stat-card { background: #f7fafc; padding: 1.5rem; border-radius: 8px; border-left: 4px solid #4299e1; }\n .stat-value { font-size: 2rem; font-weight: bold; color: #2d3748; }\n .stat-label { color: #718096; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.5px; }\n .contributors-table { width: 100%; border-collapse: collapse; margin-top: 1rem; }\n .contributors-table th, .contributors-table td { \n padding: 0.75rem; text-align: left; border-bottom: 1px solid #e2e8f0; \n }\n .contributors-table th { background: #f7fafc; font-weight: 600; color: #4a5568; }\n .contributors-table tr:hover { background: #f7fafc; }\n .chart-container { max-width: 100%; margin: 2rem 0; }\n .chart-container canvas { max-height: 400px; }\n .heatmap-table { border-collapse: collapse; margin: 1rem auto; }\n .heatmap-table th, .heatmap-table td { \n width: 30px; height: 25px; text-align: center; font-size: 10px; \n border: 1px solid #e2e8f0; padding: 2px; \n }\n .heatmap-table th { background: #f7fafc; font-weight: 600; }\n .heatmap-table td { position: relative; transition: transform 0.2s; }\n .heatmap-table td:hover { transform: scale(1.2); z-index: 10; box-shadow: 0 2px 8px rgba(0,0,0,0.3); }\n .heatmap-table td[data-tooltip]:not([data-tooltip=\"\"]):hover { cursor: pointer; }\n .heatmap-table td[data-tooltip]:not([data-tooltip=\"\"]):hover::after {\n content: attr(data-tooltip);\n position: absolute;\n cursor: default;\n bottom: 100%;\n left: 50%;\n transform: translateX(-50%);\n background: #2d3748;\n color: white;\n padding: 0.5rem 0.75rem;\n border-radius: 6px;\n font-size: 12px;\n white-space: pre;\n z-index: 1000;\n box-shadow: 0 4px 12px rgba(0,0,0,0.3);\n margin-bottom: 5px;\n }\n .heatmap-table td[data-tooltip]:not([data-tooltip=\"\"]):hover::before {\n content: '';\n position: absolute;\n cursor: pointer;\n bottom: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 5px solid transparent;\n border-top-color: #2d3748;\n margin-bottom: -5px;\n }\n .top-stats { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; }\n .top-stats h2 { color: white; border-color: rgba(255,255,255,0.3); }\n .top-stat-item { margin: 0.5rem 0; font-size: 1.1rem; }\n code { background: rgba(0,0,0,0.1); padding: 0.2rem 0.4rem; border-radius: 4px; }\n `;\n}\n\nfunction generateSummaryHTML(data: AnalysisData): string {\n return `\n <section class=\"section\">\n <h2>📈 Repository Summary</h2>\n <div class=\"stats-grid\">\n <div class=\"stat-card\">\n <div class=\"stat-value\">${Object.keys(data.contributors).length.toLocaleString()}</div>\n <div class=\"stat-label\">Contributors</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-value\">${data.totalCommits.toLocaleString()}</div>\n <div class=\"stat-label\">Total Commits</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-value\">${data.totalLines.toLocaleString()}</div>\n <div class=\"stat-label\">Lines of Code</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-value\">${data.busFactor.filesSingleOwner.length}</div>\n <div class=\"stat-label\">Single-Owner Files</div>\n </div>\n </div>\n </section>\n `;\n}\n\nfunction generateTopStatsHTML(topStats: TopStats | undefined, metrics: string[]): string {\n if (!topStats) return '';\n\n const want = new Set(metrics);\n const items: string[] = [];\n\n if (want.has('commits') && topStats.byCommits) {\n items.push(\n `<div class=\"top-stat-item\">🏆 <strong>Most Commits:</strong> ${topStats.byCommits.name} (${topStats.byCommits.commits})</div>`\n );\n }\n if (want.has('additions') && topStats.byAdditions) {\n items.push(\n `<div class=\"top-stat-item\">➕ <strong>Most Additions:</strong> ${topStats.byAdditions.name} (${topStats.byAdditions.added?.toLocaleString()})</div>`\n );\n }\n if (want.has('deletions') && topStats.byDeletions) {\n items.push(\n `<div class=\"top-stat-item\">➖ <strong>Most Deletions:</strong> ${topStats.byDeletions.name} (${topStats.byDeletions.deleted?.toLocaleString()})</div>`\n );\n }\n if (want.has('net') && topStats.byNet) {\n items.push(\n `<div class=\"top-stat-item\">📊 <strong>Best Net Contribution:</strong> ${topStats.byNet.name} (${topStats.byNet.net?.toLocaleString()})</div>`\n );\n }\n\n return `\n <section class=\"section top-stats\">\n <h2>🎯 Top Statistics</h2>\n ${items.join('')}\n </section>\n `;\n}\n\nfunction generateContributorsHTML(data: AnalysisData): string {\n const rows = data.topContributors\n .slice(0, 25)\n .map((c, idx) => {\n const net = (c.added || 0) - (c.deleted || 0);\n return `\n <tr>\n <td>${idx + 1}</td>\n <td><strong>${c.name}</strong><br><code>${c.email}</code></td>\n <td>${(c.commits || 0).toLocaleString()}</td>\n <td style=\"color: #38a169\">${(c.added || 0).toLocaleString()}</td>\n <td style=\"color: #e53e3e\">${(c.deleted || 0).toLocaleString()}</td>\n <td style=\"color: ${net >= 0 ? '#38a169' : '#e53e3e'}\">${net.toLocaleString()}</td>\n </tr>\n `;\n })\n .join('');\n\n return `\n <section class=\"section\">\n <h2>👥 Top Contributors</h2>\n <table class=\"contributors-table\">\n <thead>\n <tr>\n <th>Rank</th>\n <th>Contributor</th>\n <th>Commits</th>\n <th>Added</th>\n <th>Deleted</th>\n <th>Net</th>\n </tr>\n </thead>\n <tbody>${rows}</tbody>\n </table>\n </section>\n `;\n}\n\nfunction generateChartsHTML(): string {\n return `\n <section class=\"section\">\n <h2>📊 Contribution Charts</h2>\n <div class=\"chart-container\">\n <h3>Commits by Contributor</h3>\n <canvas id=\"commitsChart\"></canvas>\n </div>\n <div class=\"chart-container\">\n <h3>Net Lines by Contributor</h3>\n <canvas id=\"netChart\"></canvas>\n </div>\n </section>\n `;\n}\n\nfunction generateBusFactorHTML(data: AnalysisData): string {\n const files = data.busFactor.filesSingleOwner\n .slice(0, 10)\n .map(\n (f) =>\n `<tr><td><code>${f.file}</code></td><td>${f.owner}</td><td>${f.changes.toLocaleString()}</td></tr>`\n )\n .join('');\n\n return `\n <section class=\"section\">\n <h2>⚠️ Bus Factor Analysis</h2>\n <p>Files with only one contributor (high risk):</p>\n <table class=\"contributors-table\">\n <thead><tr><th>File</th><th>Owner</th><th>Changes</th></tr></thead>\n <tbody>${files}</tbody>\n </table>\n </section>\n `;\n}\n\nfunction generateActivityHTML(): string {\n return `\n <section class=\"section\">\n <h2>🕒 Activity Heatmap</h2>\n <p>Commit activity by day of week and hour:</p>\n <table class=\"heatmap-table\" id=\"heatmap\"></table>\n </section>\n `;\n}\n\nfunction getJavaScript(data: AnalysisData): string {\n return `\n const topContributors = ${JSON.stringify(data.topContributors.slice(0, 15))};\n const heatmap = ${JSON.stringify(data.heatmap)};\n const heatmapContributors = ${JSON.stringify(data.heatmapContributors || {})};\n \n // Commits chart\n new Chart(document.getElementById('commitsChart'), {\n type: 'bar',\n data: {\n labels: topContributors.map(c => c.name),\n datasets: [{\n label: 'Commits',\n data: topContributors.map(c => c.commits),\n backgroundColor: '#4299e1'\n }]\n },\n options: { responsive: true, plugins: { legend: { display: false } } }\n });\n \n // Net lines chart\n new Chart(document.getElementById('netChart'), {\n type: 'bar',\n data: {\n labels: topContributors.map(c => c.name),\n datasets: [{\n label: 'Net Lines',\n data: topContributors.map(c => (c.added || 0) - (c.deleted || 0)),\n backgroundColor: '#48bb78'\n }]\n },\n options: { responsive: true, plugins: { legend: { display: false } } }\n });\n \n // Heatmap\n const dayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n const max = Math.max(...heatmap.flat());\n let html = '<tr><th></th>' + Array.from({length:24}, (_, i) => '<th>' + i + '</th>').join('') + '</tr>';\n \n for (let day = 0; day < 7; day++) {\n html += '<tr><th>' + dayAbbr[day] + '</th>';\n for (let hour = 0; hour < 24; hour++) {\n const val = heatmap[day][hour] || 0;\n const intensity = max ? (val / max) * 0.8 : 0;\n const color = 'rgba(66, 153, 225, ' + intensity + ')';\n \n // Get top contributors for this time slot\n const key = day + '-' + hour;\n const contributors = heatmapContributors[key] || {};\n const topContribs = Object.entries(contributors)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3)\n .map(([name, count]) => name + ' (' + count + ')')\n .join('&#10;');\n\n html += '<td style=\"background:' + color + '\" data-tooltip=\"' + topContribs + '\">' + (val || '') + '</td>';\n\n }\n html += '</tr>';\n }\n document.getElementById('heatmap').innerHTML = html;\n `;\n}\n","import { getMetricValue, type TopStatsEntry } from '../utils/formatting.ts';\n\ninterface TopFile {\n filename: string;\n changes: number;\n}\n\ninterface Contributor {\n name: string;\n email: string;\n commits: number;\n added: number;\n deleted: number;\n topFiles: TopFile[];\n}\n\ninterface BusFactorFile {\n file: string;\n owner: string;\n changes: number;\n}\n\ninterface BusFactor {\n filesSingleOwner: BusFactorFile[];\n}\n\ninterface TopStats {\n byCommits?: TopStatsEntry | null;\n byAdditions?: TopStatsEntry | null;\n byDeletions?: TopStatsEntry | null;\n byNet?: TopStatsEntry | null;\n byChanges?: TopStatsEntry | null;\n}\n\ninterface CommitFrequency {\n monthly: Record<string, number>;\n}\n\ninterface AnalysisData {\n contributors: Record<string, unknown>;\n topContributors: Contributor[];\n totalCommits: number;\n totalLines: number;\n busFactor: BusFactor;\n topStats?: TopStats;\n heatmap: number[][];\n commitFrequency: CommitFrequency;\n}\n\ninterface ReportOptions {\n includeTopStats?: boolean;\n topStatsMetrics?: string[];\n}\n\nfunction formatStatLine(\n label: string,\n entry: TopStatsEntry | null | undefined,\n metricKey: string\n): string {\n if (!entry) return `- **${label}:** —`;\n\n const metricVal = getMetricValue(entry, metricKey);\n const suffix = typeof metricVal === 'number' ? ` (${metricVal.toLocaleString()})` : '';\n const emailPart = entry.email ? ` <${entry.email}>` : '';\n const who = `${entry.name || '—'}${emailPart}`;\n return `- **${label}:** ${who}${suffix}`;\n}\n\nfunction addTopStatsSection(lines: string[], topStats: TopStats, topMetrics: string[]): void {\n const want = new Set(topMetrics);\n lines.push('## Top stats', '');\n\n if (want.has('commits'))\n lines.push(formatStatLine('Most commits', topStats.byCommits, 'commits'));\n if (want.has('additions'))\n lines.push(formatStatLine('Most additions', topStats.byAdditions, 'added'));\n if (want.has('deletions'))\n lines.push(formatStatLine('Most deletions', topStats.byDeletions, 'deleted'));\n if (want.has('net')) lines.push(formatStatLine('Best net contribution', topStats.byNet, 'net'));\n if (want.has('changes'))\n lines.push(formatStatLine('Most changes', topStats.byChanges, 'changes'));\n\n lines.push('');\n}\n\nfunction addTopContributorsSection(lines: string[], contributors: Contributor[]): void {\n lines.push(\n '## Top contributors',\n '',\n '| Rank | Contributor | Commits | Added | Deleted | Net | Top Files |',\n '|---:|---|---:|---:|---:|---:|---|'\n );\n\n const topContributorsList = contributors.slice(0, 50);\n for (let idx = 0; idx < topContributorsList.length; idx++) {\n const c = topContributorsList[idx];\n const net = (c.added || 0) - (c.deleted || 0);\n const topFiles = (c.topFiles || [])\n .slice(0, 3)\n .map((f) => `\\`${f.filename}\\`(${f.changes})`)\n .join(', ');\n lines.push(\n `| ${idx + 1} | **${c.name}** \\`<${c.email}>\\` | ${(c.commits || 0).toLocaleString()} | ${(c.added || 0).toLocaleString()} | ${(c.deleted || 0).toLocaleString()} | ${net.toLocaleString()} | ${topFiles} |`\n );\n }\n lines.push('');\n}\n\nfunction addBusFactorSection(lines: string[], busFactor: BusFactor): void {\n lines.push(\n '## Bus Factor Analysis',\n '',\n `**Files with single contributor:** ${busFactor.filesSingleOwner.length}`,\n ''\n );\n\n if (busFactor.filesSingleOwner.length > 0) {\n lines.push(\n '### High-Risk Files (Single Owner)',\n '',\n '| File | Owner | Changes |',\n '|---|---|---:|'\n );\n\n for (const f of busFactor.filesSingleOwner.slice(0, 20)) {\n lines.push(`| \\`${f.file}\\` | ${f.owner} | ${f.changes.toLocaleString()} |`);\n }\n lines.push('');\n }\n}\n\nfunction addActivityPatternsSection(\n lines: string[],\n commitFrequency: CommitFrequency,\n heatmap: number[][]\n): void {\n lines.push('## Activity Patterns', '');\n\n const monthlyEntries = Object.entries(commitFrequency.monthly)\n .sort(([a], [b]) => a.localeCompare(b))\n .slice(-12);\n\n if (monthlyEntries.length > 0) {\n lines.push('### Recent Monthly Activity', '', '| Month | Commits |', '|---|---:|');\n\n for (const [month, commits] of monthlyEntries) {\n lines.push(`| ${month} | ${commits.toLocaleString()} |`);\n }\n lines.push('');\n }\n\n lines.push(\n '### Commit Heatmap Data',\n '',\n '> Commit activity by day of week (0=Sunday) and hour (0-23)',\n '',\n '```json',\n JSON.stringify(heatmap, null, 2),\n '```'\n );\n}\n\nexport function generateMarkdownReport(\n data: AnalysisData,\n repoRoot: string,\n opts: ReportOptions = {}\n): string {\n const includeTopStats = opts.includeTopStats !== false;\n const topMetrics = Array.isArray(opts.topStatsMetrics)\n ? opts.topStatsMetrics\n : ['commits', 'additions', 'deletions', 'net', 'changes'];\n const lines: string[] = [];\n\n lines.push(\n '# Git Contributor Stats',\n '',\n `**Repository:** ${repoRoot}`,\n `**Generated:** ${new Date().toISOString()}`,\n '',\n '## Summary',\n '',\n `- **Total contributors:** ${Object.keys(data.contributors).length}`,\n `- **Total commits:** ${data.totalCommits.toLocaleString()}`,\n `- **Total lines:** ${data.totalLines.toLocaleString()}`,\n ''\n );\n\n if (includeTopStats && data.topStats) {\n addTopStatsSection(lines, data.topStats, topMetrics);\n }\n\n addTopContributorsSection(lines, data.topContributors);\n addBusFactorSection(lines, data.busFactor);\n addActivityPatternsSection(lines, data.commitFrequency, data.heatmap);\n\n return lines.join('\\n');\n}\n","// Feature: Report generation (CSV, Markdown, HTML)\n// Separated from core stats to enable tree-shaking when reports aren't needed\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { TopContributor, TopFileEntry } from '../analytics/analyzer.ts';\nimport { generateCSVReport } from '../reports/csv.ts';\nimport { generateHTMLReport } from '../reports/html.ts';\nimport { generateMarkdownReport } from '../reports/markdown.ts';\nimport { ensureDir } from '../utils/files.ts';\nimport { parseTopStatsMetrics } from '../utils/formatting.ts';\nimport type { ContributorStatsResult } from './stats.ts';\n\nexport interface ReportOptions {\n outDir?: string;\n csv?: string;\n md?: string;\n html?: string;\n topStats?: string;\n verbose?: boolean;\n}\n\nfunction toReportContributor(tc: TopContributor): {\n name: string;\n email: string;\n commits: number;\n added: number;\n deleted: number;\n topFiles: TopFileEntry[];\n} {\n return {\n name: tc.name ?? '',\n email: tc.email ? tc.email : '',\n commits: tc.commits,\n added: tc.added,\n deleted: tc.deleted,\n topFiles: tc.topFiles ?? []\n };\n}\n\nexport async function generateReports(\n final: ContributorStatsResult,\n opts: ReportOptions = {}\n): Promise<void> {\n const outDir = opts.outDir;\n const writeCSVPath = opts.csv || (outDir ? path.join(outDir, 'contributors.csv') : undefined);\n const writeMDPath = opts.md || (outDir ? path.join(outDir, 'report.md') : undefined);\n const writeHTMLPath = opts.html || (outDir ? path.join(outDir, 'report.html') : undefined);\n\n if (writeCSVPath) {\n ensureDir(path.dirname(writeCSVPath));\n const csv = generateCSVReport({ topContributors: final.topContributors });\n fs.writeFileSync(writeCSVPath, csv, 'utf8');\n console.error(`Wrote CSV to ${writeCSVPath}`);\n }\n\n const topStatsMetrics = parseTopStatsMetrics(opts.topStats);\n\n const analysisData =\n writeMDPath || writeHTMLPath\n ? {\n ...final,\n topContributors: final.topContributors.map(toReportContributor),\n busFactor: {\n ...final.busFactor,\n filesSingleOwner: final.busFactor.filesSingleOwner ?? []\n }\n }\n : null;\n\n if (writeMDPath && analysisData) {\n ensureDir(path.dirname(writeMDPath));\n const md = generateMarkdownReport(analysisData, final.meta.repo, {\n includeTopStats: !!opts.topStats,\n topStatsMetrics\n });\n fs.writeFileSync(writeMDPath, md, 'utf8');\n console.error(`Wrote Markdown report to ${writeMDPath}`);\n }\n\n if (writeHTMLPath && analysisData) {\n ensureDir(path.dirname(writeHTMLPath));\n const html = generateHTMLReport(analysisData, final.meta.repo, {\n includeTopStats: !!opts.topStats,\n topStatsMetrics\n });\n fs.writeFileSync(writeHTMLPath, html, 'utf8');\n console.error(`Wrote HTML report to ${writeHTMLPath}`);\n }\n}\n"],"names":[],"mappings":"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"}