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 25.3 kB
{"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../src/charts/svg.ts","../../src/charts/renderer.ts","../../src/features/charts.ts"],"sourcesContent":["import { svgEscape } from '../utils/formatting.ts';\n\ninterface BarChartOptions {\n limit?: number;\n width?: number;\n height?: number;\n}\n\nexport function generateBarChartSVG(\n title: string,\n labels: string[],\n values: number[],\n options: BarChartOptions = {}\n): string {\n const maxBars = Math.min(labels.length, options.limit || 20);\n labels = labels.slice(0, maxBars);\n values = values.slice(0, maxBars);\n\n const width = options.width || 900;\n const height = options.height || 360;\n const margin = { top: 40, right: 20, bottom: 120, left: 80 };\n const chartW = width - margin.left - margin.right;\n const chartH = height - margin.top - margin.bottom;\n const maxVal = Math.max(1, ...values);\n const denominator = Math.max(1, maxBars);\n const barW = (chartW / denominator) * 0.7;\n const gap = (chartW / denominator) * 0.3;\n\n const svg: string[] = [];\n svg.push(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">`,\n `<style>text{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial;font-size:12px} .title{font-size:16px;font-weight:700}</style>`,\n `<rect width=\"100%\" height=\"100%\" fill=\"#fff\"/>`,\n `<text class=\"title\" x=\"${margin.left}\" y=\"${margin.top - 12}\">${svgEscape(title)}</text>`,\n // axes\n `<line x1=\"${margin.left}\" y1=\"${margin.top}\" x2=\"${margin.left}\" y2=\"${margin.top + chartH}\" stroke=\"#333\"/>`,\n `<line x1=\"${margin.left}\" y1=\"${margin.top + chartH}\" x2=\"${margin.left + chartW}\" y2=\"${margin.top + chartH}\" stroke=\"#333\"/>`\n );\n\n // bars\n for (let i = 0; i < labels.length; i++) {\n const lab = labels[i];\n const x = margin.left + i * (barW + gap) + gap * 0.5;\n const h = Math.round((values[i] / maxVal) * chartH);\n const y = margin.top + (chartH - h);\n svg.push(\n `<rect x=\"${x}\" y=\"${y}\" width=\"${barW}\" height=\"${h}\" fill=\"#4e79a7\"/>`,\n `<text x=\"${x + barW / 2}\" y=\"${y - 4}\" text-anchor=\"middle\">${values[i]}</text>`\n );\n const labText = lab.length > 16 ? `${lab.slice(0, 16)}…` : lab;\n svg.push(\n `<g transform=\"translate(${x + barW / 2},${margin.top + chartH + 4}) rotate(45)\"><text text-anchor=\"start\">${svgEscape(labText)}</text></g>`\n );\n }\n\n if (maxBars === 0) {\n svg.push(\n `<text x=\"${margin.left + chartW / 2}\" y=\"${margin.top + chartH / 2}\" text-anchor=\"middle\" fill=\"#666\">No data</text>`\n );\n }\n\n // y ticks\n for (let t = 0; t <= 4; t++) {\n const val = Math.round((t / 4) * maxVal);\n const yy = margin.top + chartH - Math.round((t / 4) * chartH);\n svg.push(\n `<line x1=\"${margin.left - 5}\" y1=\"${yy}\" x2=\"${margin.left}\" y2=\"${yy}\" stroke=\"#333\"/>`,\n `<text x=\"${margin.left - 8}\" y=\"${yy + 4}\" text-anchor=\"end\">${val}</text>`\n );\n }\n svg.push(`</svg>`);\n return svg.join('');\n}\n\nexport function generateHeatmapSVG(heatmap: number[][]): string {\n const cellW = 26,\n cellH = 20;\n const margin = { top: 28, right: 10, bottom: 10, left: 28 };\n const width = margin.left + margin.right + 24 * cellW;\n const height = margin.top + margin.bottom + 7 * cellH;\n const max = Math.max(1, ...heatmap.flat());\n const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n\n const svg: string[] = [];\n svg.push(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">`,\n `<style>text{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial;font-size:10px}</style>`\n );\n\n // hour headers\n for (let h = 0; h < 24; h++) {\n svg.push(\n `<text x=\"${margin.left + h * cellW + 6}\" y=\"${margin.top - 10}\" text-anchor=\"middle\">${h}</text>`\n );\n }\n\n for (let d = 0; d < 7; d++) {\n svg.push(\n `<text x=\"${margin.left - 6}\" y=\"${margin.top + d * cellH + 14}\" text-anchor=\"end\">${days[d]}</text>`\n );\n for (let h = 0; h < 24; h++) {\n const val = heatmap[d][h] || 0;\n const intensity = Math.round((val / max) * 200);\n const fill = `rgb(${255 - intensity},255,${255 - intensity})`;\n svg.push(\n `<rect x=\"${margin.left + h * cellW}\" y=\"${margin.top + d * cellH}\" width=\"${cellW - 1}\" height=\"${cellH - 1}\" fill=\"${fill}\"/>`\n );\n if (val > 0)\n svg.push(\n `<text x=\"${margin.left + h * cellW + cellW / 2}\" y=\"${margin.top + d * cellH + 14}\" text-anchor=\"middle\">${val}</text>`\n );\n }\n }\n svg.push(`</svg>`);\n return svg.join('');\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { ensureDir } from '../utils/files.ts';\nimport { generateBarChartSVG, generateHeatmapSVG } from './svg.ts';\n\ninterface ChartOptions {\n width?: number;\n height?: number;\n verbose?: boolean;\n limit?: number;\n}\n\n// ESM-only: Use top-level await to load chart libraries\nlet ChartJSNodeCanvas: unknown = null;\nlet registerables: unknown = null;\n\ntry {\n const [modCanvas, modChart] = await Promise.all([\n import('chartjs-node-canvas'),\n import('chart.js')\n ]);\n ChartJSNodeCanvas = modCanvas.ChartJSNodeCanvas;\n registerables = modChart.registerables;\n} catch (_error) {\n // Chart library not available, will fall back to SVG\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[warn] chartjs-node-canvas not available, using fallback SVG generation.');\n }\n}\n\nfunction createCanvas(format: string, width: number, height: number): unknown {\n if (!ChartJSNodeCanvas) return null;\n\n const type = format === 'svg' ? 'svg' : 'png';\n // biome-ignore lint/suspicious/noExplicitAny: ChartJS types are dynamic and not available at compile time\n return new (ChartJSNodeCanvas as any)({\n width,\n height,\n type,\n chartCallback: (ChartJS: unknown) => {\n try {\n // biome-ignore lint/suspicious/noExplicitAny: ChartJS types are dynamic\n if (ChartJS && registerables) (ChartJS as any).register(...(registerables as any));\n } catch (_error) {\n // Ignore registration errors\n }\n }\n });\n}\n\nfunction validateAndPrepareDirectory(filePath: string): boolean {\n try {\n ensureDir(path.dirname(filePath));\n return true;\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : JSON.stringify(error);\n console.error(`[error] Failed to create directory: ${message}`);\n return false;\n }\n}\n\nfunction sanitizeChartData(\n labels: string[],\n values: number[],\n title: string,\n verbose?: boolean\n): { labels: string[]; values: number[] } {\n if (!labels || !values || labels.length === 0) {\n if (verbose) {\n console.warn(`[warn] No data for bar chart: ${title}`);\n }\n return { labels: ['No data'], values: [0] };\n }\n return { labels, values };\n}\n\nfunction shouldUsePNG(format: string): boolean {\n return format !== 'svg' && ChartJSNodeCanvas !== null;\n}\n\nasync function renderSVGFallback(\n filePath: string,\n generator: () => string,\n errorContext: string\n): Promise<void> {\n try {\n const svg = generator();\n fs.writeFileSync(filePath, svg, 'utf8');\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : JSON.stringify(error);\n console.error(`[error] ${errorContext}: ${message}`);\n }\n}\n\nexport async function renderBarChartImage(\n format: string,\n title: string,\n labels: string[],\n values: number[],\n filePath: string,\n options: ChartOptions = {}\n): Promise<void> {\n if (!validateAndPrepareDirectory(filePath)) return;\n\n const { width = 900, height = 400, verbose = false, limit = 25 } = options;\n const sanitized = sanitizeChartData(labels, values, title, verbose);\n\n // Use SVG if format is svg or PNG library is unavailable\n if (!shouldUsePNG(format)) {\n await renderSVGFallback(\n filePath,\n () => generateBarChartSVG(title, sanitized.labels, sanitized.values, { limit }),\n `Failed to generate SVG chart ${filePath}`\n );\n return;\n }\n\n // Render PNG using ChartJS\n try {\n const canvas = createCanvas(format, width, height);\n if (!canvas) {\n // Fallback to SVG if canvas creation failed\n await renderSVGFallback(\n filePath,\n () => generateBarChartSVG(title, sanitized.labels, sanitized.values, { limit }),\n `Canvas creation failed for ${filePath}`\n );\n return;\n }\n\n const config = {\n type: 'bar',\n data: {\n labels: sanitized.labels,\n datasets: [\n {\n label: title,\n data: sanitized.values,\n backgroundColor: '#4e79a7'\n }\n ]\n },\n options: {\n plugins: {\n title: { display: true, text: title },\n legend: { display: false }\n },\n responsive: false,\n scales: {\n x: { ticks: { maxRotation: 45, minRotation: 45, autoSkip: false } },\n y: { beginAtZero: true }\n }\n }\n };\n\n const mime = format === 'svg' ? 'image/svg+xml' : 'image/png';\n // biome-ignore lint/suspicious/noExplicitAny: Canvas renderToBuffer requires dynamic typing\n const buffer = await (canvas as any).renderToBuffer(config, mime);\n fs.writeFileSync(filePath, buffer);\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : JSON.stringify(error);\n console.error(`[error] Failed to generate PNG chart: ${message}`);\n // Fallback to SVG on error\n await renderSVGFallback(\n filePath,\n () => generateBarChartSVG(title, sanitized.labels, sanitized.values, { limit }),\n `Fallback SVG generation failed for ${filePath}`\n );\n }\n}\n\nexport async function renderHeatmapImage(\n format: string,\n heatmap: number[][],\n filePath: string,\n options: ChartOptions = {}\n): Promise<void> {\n if (!validateAndPrepareDirectory(filePath)) return;\n\n const { width = 900, height = 220, verbose = false } = options;\n\n // Sanitize heatmap data\n const sanitizedHeatmap =\n !heatmap || !Array.isArray(heatmap) || heatmap.length === 0\n ? (() => {\n if (verbose) {\n console.warn('[warn] Invalid heatmap data, creating empty heatmap');\n }\n return Array.from({ length: 7 }, () => new Array(24).fill(0));\n })()\n : heatmap;\n\n // Use SVG if format is svg or PNG library is unavailable\n if (!shouldUsePNG(format)) {\n await renderSVGFallback(\n filePath,\n () => generateHeatmapSVG(sanitizedHeatmap),\n `Failed to generate SVG heatmap ${filePath}`\n );\n return;\n }\n\n // Render PNG using ChartJS\n try {\n const canvas = createCanvas(format, width, height);\n if (!canvas) {\n // Fallback to SVG if canvas creation failed\n await renderSVGFallback(\n filePath,\n () => generateHeatmapSVG(sanitizedHeatmap),\n `Canvas creation failed for ${filePath}`\n );\n return;\n }\n\n const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n const hours = Array.from({ length: 24 }, (_, i) => i);\n\n const datasets = sanitizedHeatmap.map((row, i) => ({\n label: days[i],\n data: row,\n backgroundColor: row.map((val) => {\n const maxVal = Math.max(1, Math.max(...row));\n const alpha = val > 0 ? Math.min(1, 0.15 + 0.85 * (val / maxVal)) : 0.05;\n return `rgba(78,121,167,${alpha})`;\n }),\n borderWidth: 0,\n type: 'bar',\n barPercentage: 1,\n categoryPercentage: 1\n }));\n\n const config = {\n type: 'bar',\n data: { labels: hours, datasets },\n options: {\n indexAxis: 'y',\n plugins: {\n title: { display: true, text: 'Commit Activity Heatmap (weekday x hour)' },\n legend: { display: false }\n },\n responsive: false,\n scales: {\n x: { stacked: true, beginAtZero: true },\n y: { stacked: true }\n }\n }\n };\n\n const mime = format === 'svg' ? 'image/svg+xml' : 'image/png';\n // biome-ignore lint/suspicious/noExplicitAny: Canvas renderToBuffer requires dynamic typing\n const buffer = await (canvas as any).renderToBuffer(config, mime);\n fs.writeFileSync(filePath, buffer);\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : JSON.stringify(error);\n console.error(`[error] Failed to generate PNG heatmap: ${message}`);\n // Fallback to SVG on error\n await renderSVGFallback(\n filePath,\n () => generateHeatmapSVG(sanitizedHeatmap),\n `Fallback SVG generation failed for ${filePath}`\n );\n }\n}\n","// Feature: Chart generation (SVG and PNG)\r\n// Lazy-loadable module for chart rendering\r\n\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { renderBarChartImage, renderHeatmapImage } from '../charts/renderer.ts';\r\nimport { generateBarChartSVG, generateHeatmapSVG } from '../charts/svg.ts';\r\nimport { ensureDir } from '../utils/files.ts';\r\nimport type { ContributorStatsResult } from './stats.ts';\r\n\r\nexport interface ChartOptions {\r\n charts?: boolean;\r\n chartsDir?: string;\r\n chartFormat?: string;\r\n verbose?: boolean;\r\n}\r\n\r\nexport async function generateCharts(\r\n final: ContributorStatsResult,\r\n opts: ChartOptions = {},\r\n outDir?: string\r\n): Promise<void> {\r\n const chartsRequested = opts.charts;\r\n if (!chartsRequested) return;\r\n\r\n const chartsDir = outDir || opts.chartsDir || path.join(process.cwd(), 'charts');\r\n ensureDir(chartsDir);\r\n\r\n const formatOpt = String(opts.chartFormat || 'svg').toLowerCase();\r\n const formats = formatOpt === 'both' ? ['svg', 'png'] : [formatOpt === 'png' ? 'png' : 'svg'];\r\n\r\n const names = final.topContributors.map((c) => String(c.name || ''));\r\n const commitsVals = final.topContributors.map((c) => Number(c.commits || 0));\r\n const netVals = final.topContributors.map((c) => Number(c.added || 0) - Number(c.deleted || 0));\r\n\r\n const tasks: Promise<void>[] = [];\r\n for (const fmt of formats) {\r\n const ext = fmt === 'svg' ? '.svg' : '.png';\r\n tasks.push(\r\n renderBarChartImage(\r\n fmt,\r\n 'Top contributors by commits',\r\n names,\r\n commitsVals,\r\n path.join(chartsDir, `top-commits${ext}`),\r\n { limit: 25, verbose: opts.verbose }\r\n ),\r\n renderBarChartImage(\r\n fmt,\r\n 'Top contributors by net lines',\r\n names,\r\n netVals,\r\n path.join(chartsDir, `top-net${ext}`),\r\n { limit: 25, verbose: opts.verbose }\r\n ),\r\n renderHeatmapImage(fmt, final.heatmap, path.join(chartsDir, `heatmap${ext}`), {\r\n verbose: opts.verbose\r\n })\r\n );\r\n }\r\n await Promise.all(tasks);\r\n\r\n if (formats.includes('svg')) {\r\n await ensureFallbackSVGs(chartsDir, names, commitsVals, netVals, final.heatmap, opts.verbose);\r\n }\r\n console.error(`Wrote ${formats.join('+').toUpperCase()} charts to ${chartsDir}`);\r\n}\r\n\r\nasync function ensureFallbackSVGs(\r\n chartsDir: string,\r\n names: string[],\r\n commitsVals: number[],\r\n netVals: number[],\r\n heatmap: number[][],\r\n verbose?: boolean\r\n): Promise<void> {\r\n const svgFiles = [\r\n {\r\n path: path.join(chartsDir, 'top-commits.svg'),\r\n gen: () =>\r\n generateBarChartSVG('Top contributors by commits', names, commitsVals, { limit: 25 })\r\n },\r\n {\r\n path: path.join(chartsDir, 'top-net.svg'),\r\n gen: () => generateBarChartSVG('Top contributors by net lines', names, netVals, { limit: 25 })\r\n },\r\n { path: path.join(chartsDir, 'heatmap.svg'), gen: () => generateHeatmapSVG(heatmap) }\r\n ];\r\n for (const { path: svgPath, gen } of svgFiles) {\r\n if (!fs.existsSync(svgPath)) {\r\n try {\r\n fs.writeFileSync(svgPath, gen(), 'utf8');\r\n } catch (e) {\r\n if (verbose) console.error('[error] Fallback write failed', svgPath, (e as Error).message);\r\n }\r\n }\r\n }\r\n}\r\n"],"file":"charts.mjs"}