@saadjs/gh-stats 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # gh-stats
2
+
3
+ Generate GitHub language stats as JSON or SVG charts with multiple themes.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js 18+ (for built-in `fetch`)
8
+ - pnpm 10 (see `packageManager` in `package.json`)
9
+ - A GitHub token with access to private repositories if needed
10
+
11
+ ## Setup
12
+
13
+ ```bash
14
+ pnpm install
15
+ pnpm run build
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ## Run with npx (no install)
21
+
22
+ ```bash
23
+ GITHUB_TOKEN=your_token npx @saadjs/gh-stats --svg --out stats.svg
24
+ ```
25
+
26
+ ```bash
27
+ npx @saadjs/gh-stats --svg --theme phosphor --in data.json --out stats.svg
28
+ ```
29
+
30
+ ## Install CLI command
31
+
32
+ Build first, then link globally or install from the local path.
33
+
34
+ ```bash
35
+ pnpm run build
36
+ pnpm link --global
37
+ ```
38
+
39
+ Or:
40
+
41
+ ```bash
42
+ pnpm run build
43
+ pnpm add -g .
44
+ ```
45
+
46
+ ### JSON (default)
47
+
48
+ ```bash
49
+ GITHUB_TOKEN=your_token gh-stats
50
+ ```
51
+
52
+ <details>
53
+ <summary>JSON schema (for custom inputs)</summary>
54
+
55
+ ```json
56
+ {
57
+ "type": "object",
58
+ "required": [
59
+ "totalBytes",
60
+ "languages",
61
+ "generatedAt",
62
+ "repositoryCount",
63
+ "includedForks",
64
+ "includedArchived",
65
+ "includedMarkdown"
66
+ ],
67
+ "properties": {
68
+ "totalBytes": { "type": "number" },
69
+ "languages": {
70
+ "type": "array",
71
+ "items": {
72
+ "type": "object",
73
+ "required": ["language", "bytes", "percent"],
74
+ "properties": {
75
+ "language": { "type": "string" },
76
+ "bytes": { "type": "number" },
77
+ "percent": { "type": "number" }
78
+ },
79
+ "additionalProperties": false
80
+ }
81
+ },
82
+ "generatedAt": { "type": "string", "format": "date-time" },
83
+ "repositoryCount": { "type": "number" },
84
+ "includedForks": { "type": "boolean" },
85
+ "includedArchived": { "type": "boolean" },
86
+ "includedMarkdown": { "type": "boolean" }
87
+ },
88
+ "additionalProperties": false
89
+ }
90
+ ```
91
+
92
+ </details>
93
+
94
+ ### SVG
95
+
96
+ ```bash
97
+ GITHUB_TOKEN=your_token gh-stats --svg --out stats.svg
98
+ ```
99
+
100
+ ### Cache JSON once, render SVG offline
101
+
102
+ Generate the JSON once, then re-render SVGs with different themes without hitting the GitHub API.
103
+
104
+ ```bash
105
+ GITHUB_TOKEN=your_token gh-stats --json --out data.json
106
+ ```
107
+
108
+ ```bash
109
+ gh-stats --svg --theme phosphor --in data.json --out stats.svg
110
+ gh-stats --svg --theme infrared --in data.json --out stats.svg
111
+ gh-stats --svg --theme pie --in data.json --out stats.svg
112
+ ```
113
+
114
+ ## Keeping stats updated (profile README)
115
+
116
+ Use a scheduled GitHub Actions workflow to regenerate `stats.svg` and commit it back to the
117
+ profile README repo (`<username>/<username>`).
118
+
119
+ <details>
120
+
121
+ <summary>Example workflow setup</summary>
122
+
123
+ Create `.github/workflows/update-stats.yml` in the profile repo:
124
+
125
+ ```yaml
126
+ name: Update GH Stats
127
+
128
+ on:
129
+ schedule:
130
+ - cron: "0 6 * * *" # daily at 06:00 UTC
131
+ workflow_dispatch:
132
+
133
+ permissions:
134
+ contents: write
135
+
136
+ jobs:
137
+ build:
138
+ runs-on: ubuntu-latest
139
+ steps:
140
+ - uses: actions/checkout@v4
141
+ - uses: pnpm/action-setup@v3
142
+ with:
143
+ version: 10
144
+ - uses: actions/setup-node@v4
145
+ with:
146
+ node-version: 20
147
+ cache: pnpm
148
+
149
+ - run: pnpm install
150
+ - run: pnpm run build
151
+ - run: GITHUB_TOKEN=${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} gh-stats --svg --out stats.svg
152
+
153
+ - run: |
154
+ if [[ -n "$(git status --porcelain)" ]]; then
155
+ git config user.name "github-actions[bot]"
156
+ git config user.email "github-actions[bot]@users.noreply.github.com"
157
+ git add stats.svg
158
+ git commit -m "chore: update language stats"
159
+ git push
160
+ fi
161
+ ```
162
+
163
+ </details>
164
+
165
+ Notes:
166
+
167
+ - For public-only stats, `GITHUB_TOKEN` is enough.
168
+ - For private repos, add a PAT as `GH_TOKEN` in repo secrets (with `repo` scope).
169
+ - Embed the SVG in your profile `README.md` with `![GitHub language stats](./stats.svg)`.
170
+
171
+ ### Options
172
+
173
+ - `--token <token>` GitHub access token (or use `GITHUB_TOKEN`)
174
+ - `--format <json|svg>` choose output format
175
+ - `--json` output JSON
176
+ - `--svg` output SVG
177
+ - `--theme <name>` choose SVG theme: default, phosphor, infrared, outline, pie
178
+ - `--in <path>` read precomputed stats JSON (skips GitHub API)
179
+ - `--include-forks` include forked repositories (default: excluded)
180
+ - `--exclude-archived` exclude archived repositories (default: included)
181
+ - `--include-markdown` include Markdown/MDX in language stats (default: excluded)
182
+ - `--top <n>` limit to top N languages (default: 10)
183
+ - `--all` include all languages (overrides `--top`)
184
+ - `--out <path>` write output to a file
185
+ - `--help` / `-h` show help
186
+
187
+ ## Token scopes
188
+
189
+ For private repos, use a token with `repo` scope. For public-only, `public_repo` is enough.
190
+
191
+ ## Testing
192
+
193
+ ```bash
194
+ pnpm test
195
+ ```
196
+
197
+ ## Notes
198
+
199
+ GitHub’s API reports language byte totals per repository, not per-user LOC. Per-user attribution requires cloning and analyzing repositories locally.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,676 @@
1
+ #!/usr/bin/env node
2
+ import V from "fs";
3
+ import Z from "path";
4
+ import { getLanguageStats as tt } from "./index.js";
5
+ function et(e) {
6
+ return JSON.stringify(e, null, 2);
7
+ }
8
+ const q = {
9
+ default: {
10
+ name: "default",
11
+ palette: [
12
+ "#6366F1",
13
+ "#14B8A6",
14
+ "#F97316",
15
+ "#0EA5E9",
16
+ "#F43F5E",
17
+ "#22C55E",
18
+ "#A855F7",
19
+ "#EAB308",
20
+ "#64748B",
21
+ "#EC4899"
22
+ ],
23
+ background: "#ffffff",
24
+ backgroundSecondary: "#F8FAFC",
25
+ headerColor: "#6B7280",
26
+ labelColor: "#111827",
27
+ footerColor: "#6B7280",
28
+ borderColor: "#E5E7EB",
29
+ barTrackColor: "#EEF2FF",
30
+ fontFamily: "-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif",
31
+ borderRadius: 14,
32
+ effects: {},
33
+ footerSeparator: " • "
34
+ },
35
+ phosphor: {
36
+ name: "phosphor",
37
+ palette: [
38
+ "#33FF33",
39
+ // bright green
40
+ "#FF6B35",
41
+ // orange
42
+ "#FFDD33",
43
+ // yellow
44
+ "#35A7FF",
45
+ // blue
46
+ "#FF35A7",
47
+ // magenta/pink
48
+ "#35FFDD",
49
+ // cyan
50
+ "#DD35FF",
51
+ // purple
52
+ "#A7FF35",
53
+ // lime
54
+ "#FF3535",
55
+ // red
56
+ "#35DDFF"
57
+ // light blue
58
+ ],
59
+ background: "#0A0E0A",
60
+ backgroundSecondary: "#0D120D",
61
+ headerColor: "#33FF33",
62
+ labelColor: "#22DD22",
63
+ footerColor: "#118811",
64
+ borderColor: "#1A3D1A",
65
+ barTrackColor: "#0F1A0F",
66
+ fontFamily: "'IBM Plex Mono', monospace",
67
+ fontImport: "@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&amp;display=swap');",
68
+ borderRadius: 4,
69
+ effects: {
70
+ glow: !0,
71
+ scanlines: !0,
72
+ cursorBlink: !0
73
+ },
74
+ headerPrefix: ">_ ",
75
+ footerSeparator: " | "
76
+ },
77
+ infrared: {
78
+ name: "infrared",
79
+ palette: [
80
+ "#FF1744",
81
+ "#FF6D00",
82
+ "#FFEA00",
83
+ "#FF4081",
84
+ "#E040FB",
85
+ "#7C4DFF",
86
+ "#448AFF",
87
+ "#00E5FF",
88
+ "#FFFFFF",
89
+ "#FF3D00"
90
+ ],
91
+ background: "#0D0D12",
92
+ backgroundSecondary: "#12101A",
93
+ headerColor: "#FFFFFF",
94
+ labelColor: "#E8E8F0",
95
+ footerColor: "#8888AA",
96
+ borderColor: "#7C4DFF",
97
+ barTrackColor: "#1A1525",
98
+ fontFamily: "'Space Grotesk', sans-serif",
99
+ fontImport: "@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600&amp;display=swap');",
100
+ borderRadius: 8,
101
+ effects: {
102
+ heatBloom: !0,
103
+ thermalGradient: !0,
104
+ heatPulse: !0,
105
+ heatLegend: !0
106
+ },
107
+ footerSeparator: " • ",
108
+ headerUppercase: !0,
109
+ headerLetterSpacing: 2
110
+ },
111
+ outline: {
112
+ name: "outline",
113
+ palette: [
114
+ "#18181B",
115
+ "#18181B",
116
+ "#18181B",
117
+ "#18181B",
118
+ "#18181B",
119
+ "#18181B",
120
+ "#18181B",
121
+ "#18181B",
122
+ "#18181B",
123
+ "#18181B"
124
+ ],
125
+ background: "#FAFAFA",
126
+ backgroundSecondary: "#FFFFFF",
127
+ headerColor: "#71717A",
128
+ labelColor: "#18181B",
129
+ footerColor: "#A1A1AA",
130
+ borderColor: "#E4E4E7",
131
+ barTrackColor: "none",
132
+ fontFamily: "'Inter', -apple-system, sans-serif",
133
+ fontImport: "@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500&amp;display=swap');",
134
+ borderRadius: 0,
135
+ effects: {},
136
+ footerSeparator: " · "
137
+ },
138
+ pie: {
139
+ name: "pie",
140
+ palette: [
141
+ "#2F4B7C",
142
+ "#665191",
143
+ "#A05195",
144
+ "#D45087",
145
+ "#F95D6A",
146
+ "#FF7C43",
147
+ "#FFA600",
148
+ "#2E86AB",
149
+ "#1B998B",
150
+ "#E84855"
151
+ ],
152
+ background: "#FFF7F2",
153
+ backgroundSecondary: "#FDEDE4",
154
+ headerColor: "#6B4C3B",
155
+ labelColor: "#2B1B12",
156
+ footerColor: "#8A6A5A",
157
+ borderColor: "#E7C9B8",
158
+ barTrackColor: "#FDEDE4",
159
+ fontFamily: "'Manrope', 'Segoe UI', sans-serif",
160
+ fontImport: "@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;600&amp;display=swap');",
161
+ borderRadius: 16,
162
+ effects: {},
163
+ footerSeparator: " • "
164
+ }
165
+ };
166
+ function ot() {
167
+ return `
168
+ <filter id="phosphorGlow" x="-50%" y="-50%" width="200%" height="200%">
169
+ <feGaussianBlur stdDeviation="2" result="coloredBlur"/>
170
+ <feMerge>
171
+ <feMergeNode in="coloredBlur"/>
172
+ <feMergeNode in="SourceGraphic"/>
173
+ </feMerge>
174
+ </filter>
175
+ <filter id="crtFlicker">
176
+ <feFlood flood-color="#33FF33" flood-opacity="0.03" result="flood"/>
177
+ <feComposite in="flood" in2="SourceGraphic" operator="over"/>
178
+ </filter>
179
+ <pattern id="scanlines" patternUnits="userSpaceOnUse" width="4" height="4">
180
+ <rect width="4" height="2" fill="rgba(0,0,0,0.2)"/>
181
+ </pattern>
182
+ <pattern id="gridPattern" patternUnits="userSpaceOnUse" width="8" height="8">
183
+ <rect width="8" height="8" fill="none" stroke="#1A3D1A" stroke-width="0.5"/>
184
+ </pattern>`;
185
+ }
186
+ function rt(e, o, t) {
187
+ const { palette: n, background: s, backgroundSecondary: f, headerColor: k, labelColor: x, footerColor: y, borderColor: E, fontFamily: D, fontImport: $, borderRadius: l } = o, u = e.languages, c = 20, B = 16, A = 12, b = Math.floor((t - c * 2) / (B + 2)), m = b * 6, r = u.reduce((v, d) => v + d.percent, 0), i = u.map((v, d) => ({
188
+ ...v,
189
+ blocks: Math.max(1, Math.round(v.percent / r * m)),
190
+ color: n[d % n.length]
191
+ }));
192
+ let h = "", g = 0;
193
+ const w = 70, C = c;
194
+ for (const v of i)
195
+ for (let d = 0; d < v.blocks && g < m; d++) {
196
+ const F = Math.floor(g / b), a = g % b, p = C + a * (B + 2), M = w + F * (A + 2);
197
+ h += `<rect x="${p}" y="${M}" width="${B}" height="${A}" fill="${v.color}" filter="url(#phosphorGlow)">
198
+ <animate attributeName="opacity" values="0.9;1;0.9" dur="${2 + Math.random()}s" repeatCount="indefinite"/>
199
+ </rect>`, g++;
200
+ }
201
+ const S = w + 6 * (A + 2) + 25, G = S + 20, z = c, H = u.map((v, d) => {
202
+ const F = G + d * 20, a = n[d % n.length], p = "█";
203
+ return `
204
+ <text x="${z}" y="${F}" font-size="12" fill="${a}" filter="url(#phosphorGlow)">${p}${p}</text>
205
+ <text x="${z + 30}" y="${F}" font-size="12" fill="${x}" filter="url(#phosphorGlow)">${v.language}</text>
206
+ <text x="${t - c}" y="${F}" font-size="12" fill="${a}" filter="url(#phosphorGlow)" text-anchor="end">${v.percent.toFixed(1)}%</text>`;
207
+ }).join(""), T = G + u.length * 20 + 20, N = T + 25;
208
+ return `<?xml version="1.0" encoding="UTF-8"?>
209
+ <svg width="${t}" height="${N}" viewBox="0 0 ${t} ${N}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="GitHub language stats">
210
+ <style>
211
+ ${$ ?? ""}
212
+ text { font-family: ${D}; }
213
+ </style>
214
+ <defs>
215
+ <linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
216
+ <stop offset="0%" stop-color="${s}"/>
217
+ <stop offset="100%" stop-color="${f}"/>
218
+ </linearGradient>
219
+ ${ot()}
220
+ </defs>
221
+
222
+ <!-- Background -->
223
+ <rect width="100%" height="100%" fill="url(#bg)" rx="${l}"/>
224
+ <rect x="1" y="1" width="${t - 2}" height="${N - 2}" fill="none" stroke="${E}" stroke-width="2" rx="${l}"/>
225
+ <rect width="100%" height="100%" fill="url(#gridPattern)" rx="${l}" opacity="0.3"/>
226
+
227
+ <!-- Header -->
228
+ <text x="${c}" y="28" font-size="14" fill="${k}" font-weight="600" filter="url(#phosphorGlow)">┌─ LANG_STATS.EXE ───────────────────────────────────────────────┐</text>
229
+ <text x="${c}" y="50" font-size="11" fill="${x}" filter="url(#phosphorGlow)">│ ${e.repositoryCount} repositories scanned</text>
230
+
231
+ <!-- Blinking cursor -->
232
+ <rect x="${t - c - 20}" y="38" width="8" height="12" fill="${k}">
233
+ <animate attributeName="opacity" values="1;0;1" dur="1s" repeatCount="indefinite"/>
234
+ </rect>
235
+
236
+ <!-- Memory blocks -->
237
+ ${h}
238
+
239
+ <!-- Legend divider -->
240
+ <text x="${c}" y="${S}" font-size="11" fill="${y}" filter="url(#phosphorGlow)">├─ DISTRIBUTION ─────────────────────────────────────────────────┤</text>
241
+
242
+ <!-- Language listing -->
243
+ ${H}
244
+
245
+ <!-- Footer -->
246
+ <text x="${c}" y="${T}" font-size="10" fill="${y}" filter="url(#phosphorGlow)">└─ ${e.generatedAt} ─────────────────────────────────────────┘</text>
247
+
248
+ <!-- Scanlines overlay -->
249
+ <rect width="100%" height="100%" fill="url(#scanlines)" rx="${l}" pointer-events="none"/>
250
+ </svg>`;
251
+ }
252
+ function nt() {
253
+ return `
254
+ <filter id="heatBloom" x="-50%" y="-50%" width="200%" height="200%">
255
+ <feGaussianBlur stdDeviation="4" result="blur"/>
256
+ <feMerge>
257
+ <feMergeNode in="blur"/>
258
+ <feMergeNode in="SourceGraphic"/>
259
+ </feMerge>
260
+ </filter>
261
+ <filter id="thermalGlow" x="-100%" y="-100%" width="300%" height="300%">
262
+ <feGaussianBlur stdDeviation="8" result="glow"/>
263
+ <feMerge>
264
+ <feMergeNode in="glow"/>
265
+ <feMergeNode in="glow"/>
266
+ <feMergeNode in="SourceGraphic"/>
267
+ </feMerge>
268
+ </filter>
269
+ <linearGradient id="infraredBorder" x1="0" y1="0" x2="1" y2="1">
270
+ <stop offset="0%" stop-color="#7C4DFF"/>
271
+ <stop offset="100%" stop-color="#FF4081"/>
272
+ </linearGradient>
273
+ <radialGradient id="scopeGradient" cx="50%" cy="50%" r="50%">
274
+ <stop offset="0%" stop-color="#1A1525"/>
275
+ <stop offset="70%" stop-color="#0D0D12"/>
276
+ <stop offset="100%" stop-color="#08080C"/>
277
+ </radialGradient>
278
+ <linearGradient id="heatScale" x1="0" y1="0" x2="1" y2="0">
279
+ <stop offset="0%" stop-color="#000033"/>
280
+ <stop offset="20%" stop-color="#0066FF"/>
281
+ <stop offset="40%" stop-color="#00FFFF"/>
282
+ <stop offset="60%" stop-color="#00FF00"/>
283
+ <stop offset="80%" stop-color="#FFFF00"/>
284
+ <stop offset="100%" stop-color="#FF0000"/>
285
+ </linearGradient>`;
286
+ }
287
+ function st(e, o, t) {
288
+ const { palette: n, background: s, backgroundSecondary: f, headerColor: k, labelColor: x, footerColor: y, fontFamily: E, fontImport: D, borderRadius: $ } = o, l = e.languages, u = 22, c = 70, B = 30, A = 80, b = 420, m = c + B + Math.max(0, l.length - 1) * u + A, r = Math.max(b, m), i = 180, h = Math.round(r / 2), g = 130, w = 40;
289
+ let C = -90;
290
+ const G = l.map((a, p) => {
291
+ const M = a.percent / 100 * 360, I = C, R = C + M;
292
+ C = R;
293
+ const P = I * Math.PI / 180, O = R * Math.PI / 180, j = i + g * Math.cos(P), L = h + g * Math.sin(P), W = i + g * Math.cos(O), X = h + g * Math.sin(O), Y = i + w * Math.cos(O), J = h + w * Math.sin(O), U = i + w * Math.cos(P), _ = h + w * Math.sin(P), K = M > 180 ? 1 : 0, Q = `M ${j} ${L} A ${g} ${g} 0 ${K} 1 ${W} ${X} L ${Y} ${J} A ${w} ${w} 0 ${K} 0 ${U} ${_} Z`;
294
+ return {
295
+ ...a,
296
+ path: Q,
297
+ color: n[p % n.length]
298
+ };
299
+ }).map((a, p) => `
300
+ <path d="${a.path}" fill="${a.color}" filter="url(#heatBloom)" opacity="0.9">
301
+ ${p === 0 ? '<animate attributeName="opacity" values="0.9;1;0.9" dur="2s" repeatCount="indefinite"/>' : ""}
302
+ </path>`).join(""), z = [60, 95, 130].map(
303
+ (a) => `<circle cx="${i}" cy="${h}" r="${a}" fill="none" stroke="#2A2040" stroke-width="1" stroke-dasharray="4 4"/>`
304
+ ).join(""), H = `
305
+ <line x1="${i - 150}" y1="${h}" x2="${i + 150}" y2="${h}" stroke="#3A3050" stroke-width="1"/>
306
+ <line x1="${i}" y1="${h - 150}" x2="${i}" y2="${h + 150}" stroke="#3A3050" stroke-width="1"/>
307
+ <circle cx="${i}" cy="${h}" r="5" fill="none" stroke="#FF4081" stroke-width="2">
308
+ <animate attributeName="r" values="5;8;5" dur="2s" repeatCount="indefinite"/>
309
+ <animate attributeName="opacity" values="1;0.5;1" dur="2s" repeatCount="indefinite"/>
310
+ </circle>`, T = 340, N = c, v = l.map((a, p) => {
311
+ const M = N + B + p * u, I = n[p % n.length];
312
+ return `
313
+ <rect x="${T}" y="${M - 14}" width="4" height="18" fill="${I}" filter="url(#heatBloom)"/>
314
+ <text x="${T + 14}" y="${M}" font-size="11" fill="${x}">${a.language}</text>
315
+ <text x="${t - 25}" y="${M}" font-size="11" fill="${I}" text-anchor="end" filter="url(#heatBloom)">${a.percent.toFixed(1)}%</text>`;
316
+ }).join(""), d = r - 45, F = t - 60;
317
+ return `<?xml version="1.0" encoding="UTF-8"?>
318
+ <svg width="${t}" height="${r}" viewBox="0 0 ${t} ${r}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="GitHub language stats">
319
+ <style>
320
+ ${D ?? ""}
321
+ text { font-family: ${E}; }
322
+ </style>
323
+ <defs>
324
+ <linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
325
+ <stop offset="0%" stop-color="${s}"/>
326
+ <stop offset="100%" stop-color="${f}"/>
327
+ </linearGradient>
328
+ ${nt()}
329
+ </defs>
330
+
331
+ <!-- Background -->
332
+ <rect width="100%" height="100%" fill="url(#bg)" rx="${$}"/>
333
+ <rect x="2" y="2" width="${t - 4}" height="${r - 4}" fill="none" stroke="url(#infraredBorder)" stroke-width="2" rx="${$ - 2}"/>
334
+
335
+ <!-- Header -->
336
+ <text x="20" y="30" font-size="14" fill="${k}" font-weight="600" letter-spacing="2">THERMAL ANALYSIS</text>
337
+ <text x="20" y="48" font-size="10" fill="${y}" letter-spacing="1">${e.repositoryCount} repositories scanned</text>
338
+
339
+ <!-- Crosshair and rings -->
340
+ ${H}
341
+ ${z}
342
+
343
+ <!-- Pie segments -->
344
+ ${G}
345
+
346
+ <!-- Center readout -->
347
+ <circle cx="${i}" cy="${h}" r="${w - 5}" fill="#0D0D12"/>
348
+ <text x="${i}" y="${h - 5}" font-size="20" fill="${k}" text-anchor="middle" font-weight="600" filter="url(#thermalGlow)">${l[0]?.percent.toFixed(0) ?? 0}%</text>
349
+ <text x="${i}" y="${h + 14}" font-size="10" fill="${y}" text-anchor="middle">${l[0]?.language ?? "N/A"}</text>
350
+
351
+ <!-- Data panel on right -->
352
+ <text x="${T}" y="${N + 10}" font-size="10" fill="${y}" letter-spacing="1">DISTRIBUTION</text>
353
+ ${v}
354
+
355
+ <!-- Heat scale -->
356
+ <rect x="30" y="${d}" width="${F}" height="10" rx="2" fill="url(#heatScale)"/>
357
+ <text x="30" y="${d + 22}" font-size="8" fill="${y}">0%</text>
358
+ <text x="${30 + F}" y="${d + 22}" font-size="8" fill="${y}" text-anchor="end">100%</text>
359
+
360
+ <!-- Timestamp -->
361
+ <text x="20" y="${r - 12}" font-size="9" fill="${y}">${e.generatedAt}</text>
362
+ </svg>`;
363
+ }
364
+ function at(e, o, t) {
365
+ const { background: n, headerColor: s, labelColor: f, footerColor: k, borderColor: x, fontFamily: y, fontImport: E } = o, D = e.languages, $ = 32, l = 32, u = 28, c = 16, B = 100, b = t - $ * 2 - B - 50 - 20, m = l + 50, r = m + D.length * (u + c) + l + 20, i = Math.max(...D.map((g) => g.percent), 0), h = D.map((g, w) => {
366
+ const C = m + w * (u + c), S = i === 0 ? 0 : g.percent / i * b, G = $ + B + 10;
367
+ return `
368
+ <text x="${$}" y="${C + u / 2 + 4}" font-size="13" fill="${f}" font-weight="400">${g.language}</text>
369
+ <rect x="${G}" y="${C}" width="${b}" height="${u}" fill="none" stroke="${x}" stroke-width="1"/>
370
+ <rect x="${G}" y="${C}" width="${S.toFixed(2)}" height="${u}" fill="none" stroke="${f}" stroke-width="2"/>
371
+ <text x="${t - $}" y="${C + u / 2 + 4}" font-size="12" fill="${s}" text-anchor="end">${g.percent.toFixed(1)}%</text>`;
372
+ }).join("");
373
+ return `<?xml version="1.0" encoding="UTF-8"?>
374
+ <svg width="${t}" height="${r}" viewBox="0 0 ${t} ${r}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="GitHub language stats">
375
+ <style>
376
+ ${E ?? ""}
377
+ text { font-family: ${y}; }
378
+ </style>
379
+
380
+ <!-- Background -->
381
+ <rect width="100%" height="100%" fill="${n}"/>
382
+ <rect x="0.5" y="0.5" width="${t - 1}" height="${r - 1}" fill="none" stroke="${x}" stroke-width="1"/>
383
+
384
+ <!-- Header -->
385
+ <text x="${$}" y="${l + 8}" font-size="11" fill="${s}" font-weight="500" letter-spacing="1">LANGUAGE DISTRIBUTION</text>
386
+ <line x1="${$}" y1="${l + 20}" x2="${t - $}" y2="${l + 20}" stroke="${x}" stroke-width="1"/>
387
+ <text x="${t - $}" y="${l + 8}" font-size="11" fill="${s}" text-anchor="end">${e.repositoryCount} repos</text>
388
+
389
+ <!-- Bars -->
390
+ ${h}
391
+
392
+ <!-- Footer -->
393
+ <line x1="${$}" y1="${r - l - 8}" x2="${t - $}" y2="${r - l - 8}" stroke="${x}" stroke-width="1"/>
394
+ <text x="${$}" y="${r - l + 8}" font-size="10" fill="${k}">${e.generatedAt}</text>
395
+ </svg>`;
396
+ }
397
+ function lt(e) {
398
+ const t = e.languages.slice(0, 5), n = t.reduce((x, y) => x + y.bytes, 0), s = Math.max(e.totalBytes - n, 0);
399
+ if (!(s > 0))
400
+ return t;
401
+ const k = e.totalBytes === 0 ? 0 : s / e.totalBytes * 100;
402
+ return [
403
+ ...t,
404
+ {
405
+ language: "Other",
406
+ bytes: s,
407
+ percent: k
408
+ }
409
+ ];
410
+ }
411
+ function it(e, o, t) {
412
+ const {
413
+ palette: n,
414
+ background: s,
415
+ backgroundSecondary: f,
416
+ headerColor: k,
417
+ labelColor: x,
418
+ footerColor: y,
419
+ borderColor: E,
420
+ fontFamily: D,
421
+ fontImport: $,
422
+ borderRadius: l
423
+ } = o, u = 360, c = lt(e), B = e.totalBytes, A = 185, b = 195, m = 110, r = 55, i = 360, h = 90, g = 24;
424
+ let w = -90;
425
+ const C = c.map((d, F) => {
426
+ const a = B === 0 ? 0 : d.bytes / B * 360, p = w, M = w + a;
427
+ w = M;
428
+ const I = p * Math.PI / 180, R = M * Math.PI / 180, P = A + m * Math.cos(I), O = b + m * Math.sin(I), j = A + m * Math.cos(R), L = b + m * Math.sin(R), W = A + r * Math.cos(R), X = b + r * Math.sin(R), Y = A + r * Math.cos(I), J = b + r * Math.sin(I), U = a > 180 ? 1 : 0, _ = `M ${P} ${O} A ${m} ${m} 0 ${U} 1 ${j} ${L} L ${W} ${X} A ${r} ${r} 0 ${U} 0 ${Y} ${J} Z`;
429
+ return {
430
+ ...d,
431
+ path: _,
432
+ color: n[F % n.length]
433
+ };
434
+ }), S = C.length > 0 ? C.map(
435
+ (d) => `<path d="${d.path}" fill="${d.color}" stroke="${f}" stroke-width="1" />`
436
+ ).join("") : `<circle cx="${A}" cy="${b}" r="${m}" fill="${f}" stroke="${E}" stroke-width="1" />`, G = c.length > 0 ? c.map((d, F) => {
437
+ const a = h + F * g, p = n[F % n.length];
438
+ return `
439
+ <circle cx="${i}" cy="${a - 5}" r="5" fill="${p}" />
440
+ <text x="${i + 14}" y="${a}" font-size="12" fill="${x}">${d.language}</text>
441
+ <text x="${t - 24}" y="${a}" font-size="12" fill="${k}" text-anchor="end">${d.percent.toFixed(1)}%</text>`;
442
+ }).join("") : `
443
+ <text x="${i}" y="${h}" font-size="12" fill="${x}">No language data</text>`, z = "Top Languages", H = `${e.repositoryCount} repos`, T = `${c.length} slices`, v = `Generated ${new Date(e.generatedAt).toISOString().split("T")[0]}`;
444
+ return `<?xml version="1.0" encoding="UTF-8"?>
445
+ <svg width="${t}" height="${u}" viewBox="0 0 ${t} ${u}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="GitHub language stats">
446
+ <style>
447
+ ${$ ?? ""}
448
+ text { font-family: ${D}; }
449
+ </style>
450
+ <defs>
451
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
452
+ <stop offset="0%" stop-color="${s}" />
453
+ <stop offset="100%" stop-color="${f}" />
454
+ </linearGradient>
455
+ </defs>
456
+ <rect x="0" y="0" width="100%" height="100%" fill="url(#bg)" rx="${l}" />
457
+ <rect x="10" y="10" width="${t - 20}" height="${u - 20}" fill="none" stroke="${E}" stroke-width="1" rx="${l - 4}" />
458
+
459
+ <text x="24" y="34" font-size="16" fill="${x}" font-weight="600">${z}</text>
460
+ <text x="${t - 24}" y="34" font-size="11" fill="${k}" text-anchor="end">${H} • ${T}</text>
461
+
462
+ ${S}
463
+ <circle cx="${A}" cy="${b}" r="${r - 6}" fill="${s}" />
464
+ <text x="${A}" y="${b - 4}" font-size="14" fill="${x}" text-anchor="middle" font-weight="600">
465
+ ${c[0]?.language ?? "No data"}
466
+ </text>
467
+ <text x="${A}" y="${b + 14}" font-size="11" fill="${y}" text-anchor="middle">
468
+ ${c[0]?.percent?.toFixed(1) ?? "0.0"}%
469
+ </text>
470
+
471
+ <text x="${i}" y="${h - 26}" font-size="11" fill="${y}" letter-spacing="1">DISTRIBUTION</text>
472
+ ${G}
473
+
474
+ <text x="24" y="${u - 16}" font-size="10" fill="${y}">${v}</text>
475
+ </svg>`;
476
+ }
477
+ function ct(e, o = {}) {
478
+ const t = o.width ?? 600, n = o.theme ?? "default", s = q[n] ?? q.default;
479
+ if (n === "phosphor")
480
+ return rt(e, s, t);
481
+ if (n === "infrared")
482
+ return st(e, s, t);
483
+ if (n === "outline")
484
+ return at(e, s, t);
485
+ if (n === "pie")
486
+ return it(e, s, t);
487
+ const f = o.barHeight ?? 18, k = o.gap ?? 10, x = o.labelWidth ?? 140, {
488
+ palette: y,
489
+ background: E,
490
+ backgroundSecondary: D,
491
+ headerColor: $,
492
+ labelColor: l,
493
+ footerColor: u,
494
+ borderColor: c,
495
+ barTrackColor: B,
496
+ fontFamily: A,
497
+ borderRadius: b
498
+ } = s, m = e.languages, r = 16, i = 16, g = r + 28 + 8, w = 26, C = t - x - r - i, S = g + m.length * (f + k) + w, G = Math.max(...m.map((F) => F.percent), 0), z = "GitHub Language Stats", H = `Generated ${e.generatedAt.slice(0, 10)}`, T = `${e.repositoryCount} repos`, v = [
499
+ e.includedForks ? "Forks included" : "Forks excluded",
500
+ e.includedArchived ? "Archived included" : "Archived excluded",
501
+ e.includedMarkdown ? "Markdown included" : "Markdown excluded"
502
+ ].join(" • "), d = m.map((F, a) => {
503
+ const p = g + a * (f + k), M = G === 0 ? 0 : F.percent / G * C, I = `${F.language} (${F.percent.toFixed(1)}%)`;
504
+ return `
505
+ <text x="${r}" y="${p + f - 4}" font-size="12" fill="${l}">${I}</text>
506
+ <rect x="${r + x}" y="${p}" width="${C}" height="${f}" rx="6" fill="${B}" />
507
+ <rect x="${r + x}" y="${p}" width="${M.toFixed(2)}" height="${f}" rx="6" fill="url(#bar-${a})" />
508
+ `;
509
+ }).join("");
510
+ return `<?xml version="1.0" encoding="UTF-8"?>
511
+ <svg width="${t}" height="${S}" viewBox="0 0 ${t} ${S}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="GitHub language stats">
512
+ <style>
513
+ text { font-family: ${A}; }
514
+ .subtitle { letter-spacing: 0.2px; }
515
+ </style>
516
+ <defs>
517
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
518
+ <stop offset="0%" stop-color="${E}" />
519
+ <stop offset="100%" stop-color="${D}" />
520
+ </linearGradient>
521
+ <filter id="softShadow" x="-20%" y="-20%" width="140%" height="160%">
522
+ <feDropShadow dx="0" dy="6" stdDeviation="8" flood-color="#111827" flood-opacity="0.08" />
523
+ </filter>
524
+ ${m.map((F, a) => {
525
+ const p = y[a % y.length];
526
+ return `<linearGradient id="bar-${a}" x1="0" y1="0" x2="1" y2="0">
527
+ <stop offset="0%" stop-color="${p}" />
528
+ <stop offset="100%" stop-color="${p}CC" />
529
+ </linearGradient>`;
530
+ }).join("")}
531
+ </defs>
532
+ <rect x="0" y="0" width="100%" height="100%" fill="url(#bg)" rx="${b}" />
533
+ <rect x="8" y="8" width="${t - 16}" height="${S - 16}" fill="#ffffff" rx="12" filter="url(#softShadow)" />
534
+ <rect x="8.5" y="8.5" width="${t - 17}" height="${S - 17}" fill="none" stroke="${c}" rx="12" />
535
+ <text x="${r}" y="${r + 6}" font-size="16" fill="${l}" font-weight="600">${z}</text>
536
+ <text x="${r}" y="${r + 24}" font-size="11" fill="${$}" class="subtitle">${H}</text>
537
+ <text x="${t - i}" y="${r + 8}" font-size="11" fill="${$}" text-anchor="end">${T}</text>
538
+ ${d}
539
+ <text x="${r}" y="${S - 12}" font-size="10" fill="${u}">${v}</text>
540
+ </svg>`;
541
+ }
542
+ function dt() {
543
+ console.log(`
544
+ Usage: gh-stats [options]
545
+
546
+ Options:
547
+ --token <token> GitHub access token (or use GITHUB_TOKEN)
548
+ --in <path> Read precomputed stats JSON (skips GitHub API)
549
+ --format <json|svg> Output format (default: json)
550
+ --json Output JSON format
551
+ --svg Output SVG format
552
+ --theme <name> SVG theme: default, phosphor, infrared, outline, pie (default: default)
553
+ --include-forks Include forked repositories
554
+ --exclude-archived Exclude archived repositories
555
+ --include-markdown Include Markdown/MDX in language stats
556
+ --top <number> Limit to top N languages (default: 10)
557
+ --all Include all languages (overrides --top)
558
+ --out <path> Write output to a file
559
+ --help Show this help message
560
+
561
+ Themes:
562
+ default Clean, modern light theme
563
+ phosphor Retro CRT terminal with green phosphor glow
564
+ infrared Thermal heat map visualization
565
+ outline Minimalist stroke-only design
566
+ pie Warm donut chart with top 5 + other grouping
567
+
568
+ Examples:
569
+ npx @saadjs/gh-stats --svg --out stats.svg
570
+ gh-stats --svg --out stats.svg
571
+ gh-stats --svg --theme phosphor --out stats.svg
572
+ gh-stats --svg --theme infrared --out stats.svg
573
+ gh-stats --svg --theme pie --out stats.svg
574
+ gh-stats --format json --top 8
575
+ gh-stats --json --out data.json
576
+ gh-stats --svg --theme phosphor --in data.json --out stats.svg
577
+ `.trim());
578
+ }
579
+ function ft(e) {
580
+ const o = {
581
+ format: "json",
582
+ includeForks: !1,
583
+ includeArchived: !0,
584
+ includeMarkdown: !1,
585
+ top: 10
586
+ };
587
+ for (let t = 0; t < e.length; t += 1) {
588
+ const n = e[t];
589
+ switch (n) {
590
+ case "--help":
591
+ case "-h":
592
+ dt(), process.exit(0);
593
+ break;
594
+ case "--token":
595
+ o.token = e[t + 1], t += 1;
596
+ break;
597
+ case "--in":
598
+ o.input = e[t + 1], t += 1;
599
+ break;
600
+ case "--format":
601
+ o.format = e[t + 1] ?? "json", t += 1;
602
+ break;
603
+ case "--json":
604
+ o.format = "json";
605
+ break;
606
+ case "--svg":
607
+ o.format = "svg";
608
+ break;
609
+ case "--include-forks":
610
+ o.includeForks = !0;
611
+ break;
612
+ case "--exclude-archived":
613
+ o.includeArchived = !1;
614
+ break;
615
+ case "--include-markdown":
616
+ o.includeMarkdown = !0;
617
+ break;
618
+ case "--top":
619
+ o.top = Number(e[t + 1]), o.all = !1, t += 1;
620
+ break;
621
+ case "--all":
622
+ o.all = !0, o.top = void 0;
623
+ break;
624
+ case "--out":
625
+ o.out = e[t + 1], t += 1;
626
+ break;
627
+ case "--theme":
628
+ o.theme = e[t + 1], t += 1;
629
+ break;
630
+ default:
631
+ n.startsWith("--") && (console.error(`Unknown option: ${n}`), process.exit(1));
632
+ break;
633
+ }
634
+ }
635
+ return o;
636
+ }
637
+ function ht(e) {
638
+ if (!e || typeof e != "object")
639
+ return !1;
640
+ const o = e;
641
+ return typeof o.totalBytes == "number" && Array.isArray(o.languages) && typeof o.generatedAt == "string" && typeof o.repositoryCount == "number" && typeof o.includedForks == "boolean" && typeof o.includedArchived == "boolean" && typeof o.includedMarkdown == "boolean";
642
+ }
643
+ async function gt() {
644
+ const e = process.argv.slice(2), o = ft(e);
645
+ let t;
646
+ if (o.input) {
647
+ const s = Z.resolve(process.cwd(), o.input);
648
+ try {
649
+ const f = V.readFileSync(s, "utf8"), k = JSON.parse(f);
650
+ ht(k) || (console.error(`Invalid stats JSON in ${s}`), process.exit(1)), t = k;
651
+ } catch (f) {
652
+ console.error(`Failed to read stats JSON from ${s}`), console.error(f instanceof Error ? f.message : String(f)), process.exit(1);
653
+ }
654
+ } else {
655
+ const s = o.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
656
+ s || (console.error("Missing GitHub token. Use --token or set GITHUB_TOKEN."), process.exit(1)), t = await tt({
657
+ token: s,
658
+ includeForks: o.includeForks,
659
+ includeArchived: o.includeArchived,
660
+ includeMarkdown: o.includeMarkdown,
661
+ top: o.top,
662
+ all: o.all
663
+ });
664
+ }
665
+ const n = o.format === "svg" ? ct(t, { theme: o.theme }) : et(t);
666
+ if (o.out) {
667
+ const s = Z.resolve(process.cwd(), o.out);
668
+ V.writeFileSync(s, n, "utf8"), console.log(`Wrote ${o.format.toUpperCase()} to ${s}`);
669
+ } else
670
+ process.stdout.write(n + `
671
+ `);
672
+ }
673
+ gt().catch((e) => {
674
+ console.error(e instanceof Error ? e.message : String(e)), process.exit(1);
675
+ });
676
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sources":["../src/renderers/json.ts","../src/renderers/themes.ts","../src/renderers/layouts/phosphor.ts","../src/renderers/layouts/infrared.ts","../src/renderers/layouts/outline.ts","../src/renderers/layouts/pie.ts","../src/renderers/svg.ts","../src/cli.ts"],"sourcesContent":["import type { LanguageStatsResult } from \"../types.js\";\n\nexport function renderJson(stats: LanguageStatsResult): string {\n return JSON.stringify(stats, null, 2);\n}\n","import type { Theme } from \"./types.js\";\n\nexport const themes: Record<string, Theme> = {\n default: {\n name: \"default\",\n palette: [\n \"#6366F1\",\n \"#14B8A6\",\n \"#F97316\",\n \"#0EA5E9\",\n \"#F43F5E\",\n \"#22C55E\",\n \"#A855F7\",\n \"#EAB308\",\n \"#64748B\",\n \"#EC4899\",\n ],\n background: \"#ffffff\",\n backgroundSecondary: \"#F8FAFC\",\n headerColor: \"#6B7280\",\n labelColor: \"#111827\",\n footerColor: \"#6B7280\",\n borderColor: \"#E5E7EB\",\n barTrackColor: \"#EEF2FF\",\n fontFamily: \"-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif\",\n borderRadius: 14,\n effects: {},\n footerSeparator: \" • \",\n },\n phosphor: {\n name: \"phosphor\",\n palette: [\n \"#33FF33\", // bright green\n \"#FF6B35\", // orange\n \"#FFDD33\", // yellow\n \"#35A7FF\", // blue\n \"#FF35A7\", // magenta/pink\n \"#35FFDD\", // cyan\n \"#DD35FF\", // purple\n \"#A7FF35\", // lime\n \"#FF3535\", // red\n \"#35DDFF\", // light blue\n ],\n background: \"#0A0E0A\",\n backgroundSecondary: \"#0D120D\",\n headerColor: \"#33FF33\",\n labelColor: \"#22DD22\",\n footerColor: \"#118811\",\n borderColor: \"#1A3D1A\",\n barTrackColor: \"#0F1A0F\",\n fontFamily: \"'IBM Plex Mono', monospace\",\n fontImport:\n \"@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&amp;display=swap');\",\n borderRadius: 4,\n effects: {\n glow: true,\n scanlines: true,\n cursorBlink: true,\n },\n headerPrefix: \">_ \",\n footerSeparator: \" | \",\n },\n infrared: {\n name: \"infrared\",\n palette: [\n \"#FF1744\",\n \"#FF6D00\",\n \"#FFEA00\",\n \"#FF4081\",\n \"#E040FB\",\n \"#7C4DFF\",\n \"#448AFF\",\n \"#00E5FF\",\n \"#FFFFFF\",\n \"#FF3D00\",\n ],\n background: \"#0D0D12\",\n backgroundSecondary: \"#12101A\",\n headerColor: \"#FFFFFF\",\n labelColor: \"#E8E8F0\",\n footerColor: \"#8888AA\",\n borderColor: \"#7C4DFF\",\n barTrackColor: \"#1A1525\",\n fontFamily: \"'Space Grotesk', sans-serif\",\n fontImport:\n \"@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600&amp;display=swap');\",\n borderRadius: 8,\n effects: {\n heatBloom: true,\n thermalGradient: true,\n heatPulse: true,\n heatLegend: true,\n },\n footerSeparator: \" • \",\n headerUppercase: true,\n headerLetterSpacing: 2,\n },\n outline: {\n name: \"outline\",\n palette: [\n \"#18181B\",\n \"#18181B\",\n \"#18181B\",\n \"#18181B\",\n \"#18181B\",\n \"#18181B\",\n \"#18181B\",\n \"#18181B\",\n \"#18181B\",\n \"#18181B\",\n ],\n background: \"#FAFAFA\",\n backgroundSecondary: \"#FFFFFF\",\n headerColor: \"#71717A\",\n labelColor: \"#18181B\",\n footerColor: \"#A1A1AA\",\n borderColor: \"#E4E4E7\",\n barTrackColor: \"none\",\n fontFamily: \"'Inter', -apple-system, sans-serif\",\n fontImport:\n \"@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500&amp;display=swap');\",\n borderRadius: 0,\n effects: {},\n footerSeparator: \" · \",\n },\n pie: {\n name: \"pie\",\n palette: [\n \"#2F4B7C\",\n \"#665191\",\n \"#A05195\",\n \"#D45087\",\n \"#F95D6A\",\n \"#FF7C43\",\n \"#FFA600\",\n \"#2E86AB\",\n \"#1B998B\",\n \"#E84855\",\n ],\n background: \"#FFF7F2\",\n backgroundSecondary: \"#FDEDE4\",\n headerColor: \"#6B4C3B\",\n labelColor: \"#2B1B12\",\n footerColor: \"#8A6A5A\",\n borderColor: \"#E7C9B8\",\n barTrackColor: \"#FDEDE4\",\n fontFamily: \"'Manrope', 'Segoe UI', sans-serif\",\n fontImport:\n \"@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;600&amp;display=swap');\",\n borderRadius: 16,\n effects: {},\n footerSeparator: \" • \",\n },\n};\n\nexport type ThemeName = keyof typeof themes;\n","import type { LanguageStatsResult } from \"../../types.js\";\nimport type { Theme } from \"../types.js\";\n\nexport function renderPhosphorDefs(): string {\n return `\n <filter id=\"phosphorGlow\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n <feGaussianBlur stdDeviation=\"2\" result=\"coloredBlur\"/>\n <feMerge>\n <feMergeNode in=\"coloredBlur\"/>\n <feMergeNode in=\"SourceGraphic\"/>\n </feMerge>\n </filter>\n <filter id=\"crtFlicker\">\n <feFlood flood-color=\"#33FF33\" flood-opacity=\"0.03\" result=\"flood\"/>\n <feComposite in=\"flood\" in2=\"SourceGraphic\" operator=\"over\"/>\n </filter>\n <pattern id=\"scanlines\" patternUnits=\"userSpaceOnUse\" width=\"4\" height=\"4\">\n <rect width=\"4\" height=\"2\" fill=\"rgba(0,0,0,0.2)\"/>\n </pattern>\n <pattern id=\"gridPattern\" patternUnits=\"userSpaceOnUse\" width=\"8\" height=\"8\">\n <rect width=\"8\" height=\"8\" fill=\"none\" stroke=\"#1A3D1A\" stroke-width=\"0.5\"/>\n </pattern>`;\n}\n\nexport function renderPhosphorLayout(\n stats: LanguageStatsResult,\n theme: Theme,\n width: number\n): string {\n const { palette, background, backgroundSecondary, headerColor, labelColor, footerColor, borderColor, fontFamily, fontImport, borderRadius } = theme;\n const rows = stats.languages;\n const paddingX = 20;\n\n // Create terminal-style block visualization\n const blockWidth = 16;\n const blockHeight = 12;\n const blocksPerRow = Math.floor((width - paddingX * 2) / (blockWidth + 2));\n const totalBlocks = blocksPerRow * 6; // 6 rows of blocks\n\n // Calculate blocks per language\n const totalPercent = rows.reduce((sum, r) => sum + r.percent, 0);\n const languageBlocks = rows.map((row, i) => ({\n ...row,\n blocks: Math.max(1, Math.round((row.percent / totalPercent) * totalBlocks)),\n color: palette[i % palette.length],\n }));\n\n // Build block grid\n let blocksSvg = \"\";\n let currentBlock = 0;\n const blockStartY = 70;\n const blockStartX = paddingX;\n\n for (const lang of languageBlocks) {\n for (let b = 0; b < lang.blocks && currentBlock < totalBlocks; b++) {\n const row = Math.floor(currentBlock / blocksPerRow);\n const col = currentBlock % blocksPerRow;\n const x = blockStartX + col * (blockWidth + 2);\n const y = blockStartY + row * (blockHeight + 2);\n blocksSvg += `<rect x=\"${x}\" y=\"${y}\" width=\"${blockWidth}\" height=\"${blockHeight}\" fill=\"${lang.color}\" filter=\"url(#phosphorGlow)\">\n <animate attributeName=\"opacity\" values=\"0.9;1;0.9\" dur=\"${2 + Math.random()}s\" repeatCount=\"indefinite\"/>\n </rect>`;\n currentBlock++;\n }\n }\n\n // Calculate where blocks end\n const blockEndY = blockStartY + 6 * (blockHeight + 2) + 25;\n\n // Terminal text output style listing - just language and percentage\n const listStartY = blockEndY + 20;\n const listX = paddingX;\n\n const listingSvg = rows.map((row, i) => {\n const y = listStartY + i * 20;\n const color = palette[i % palette.length];\n const blockChar = \"█\";\n return `\n <text x=\"${listX}\" y=\"${y}\" font-size=\"12\" fill=\"${color}\" filter=\"url(#phosphorGlow)\">${blockChar}${blockChar}</text>\n <text x=\"${listX + 30}\" y=\"${y}\" font-size=\"12\" fill=\"${labelColor}\" filter=\"url(#phosphorGlow)\">${row.language}</text>\n <text x=\"${width - paddingX}\" y=\"${y}\" font-size=\"12\" fill=\"${color}\" filter=\"url(#phosphorGlow)\" text-anchor=\"end\">${row.percent.toFixed(1)}%</text>`;\n }).join(\"\");\n\n // Footer stats\n const footerY = listStartY + rows.length * 20 + 20;\n const finalHeight = footerY + 25;\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg width=\"${width}\" height=\"${finalHeight}\" viewBox=\"0 0 ${width} ${finalHeight}\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"GitHub language stats\">\n <style>\n ${fontImport ?? \"\"}\n text { font-family: ${fontFamily}; }\n </style>\n <defs>\n <linearGradient id=\"bg\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"${background}\"/>\n <stop offset=\"100%\" stop-color=\"${backgroundSecondary}\"/>\n </linearGradient>\n ${renderPhosphorDefs()}\n </defs>\n\n <!-- Background -->\n <rect width=\"100%\" height=\"100%\" fill=\"url(#bg)\" rx=\"${borderRadius}\"/>\n <rect x=\"1\" y=\"1\" width=\"${width - 2}\" height=\"${finalHeight - 2}\" fill=\"none\" stroke=\"${borderColor}\" stroke-width=\"2\" rx=\"${borderRadius}\"/>\n <rect width=\"100%\" height=\"100%\" fill=\"url(#gridPattern)\" rx=\"${borderRadius}\" opacity=\"0.3\"/>\n\n <!-- Header -->\n <text x=\"${paddingX}\" y=\"28\" font-size=\"14\" fill=\"${headerColor}\" font-weight=\"600\" filter=\"url(#phosphorGlow)\">┌─ LANG_STATS.EXE ───────────────────────────────────────────────┐</text>\n <text x=\"${paddingX}\" y=\"50\" font-size=\"11\" fill=\"${labelColor}\" filter=\"url(#phosphorGlow)\">│ ${stats.repositoryCount} repositories scanned</text>\n\n <!-- Blinking cursor -->\n <rect x=\"${width - paddingX - 20}\" y=\"38\" width=\"8\" height=\"12\" fill=\"${headerColor}\">\n <animate attributeName=\"opacity\" values=\"1;0;1\" dur=\"1s\" repeatCount=\"indefinite\"/>\n </rect>\n\n <!-- Memory blocks -->\n ${blocksSvg}\n\n <!-- Legend divider -->\n <text x=\"${paddingX}\" y=\"${blockEndY}\" font-size=\"11\" fill=\"${footerColor}\" filter=\"url(#phosphorGlow)\">├─ DISTRIBUTION ─────────────────────────────────────────────────┤</text>\n\n <!-- Language listing -->\n ${listingSvg}\n\n <!-- Footer -->\n <text x=\"${paddingX}\" y=\"${footerY}\" font-size=\"10\" fill=\"${footerColor}\" filter=\"url(#phosphorGlow)\">└─ ${stats.generatedAt} ─────────────────────────────────────────┘</text>\n\n <!-- Scanlines overlay -->\n <rect width=\"100%\" height=\"100%\" fill=\"url(#scanlines)\" rx=\"${borderRadius}\" pointer-events=\"none\"/>\n</svg>`;\n}\n","import type { LanguageStatsResult } from \"../../types.js\";\nimport type { Theme } from \"../types.js\";\n\nexport function renderInfraredDefs(): string {\n return `\n <filter id=\"heatBloom\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n <feGaussianBlur stdDeviation=\"4\" result=\"blur\"/>\n <feMerge>\n <feMergeNode in=\"blur\"/>\n <feMergeNode in=\"SourceGraphic\"/>\n </feMerge>\n </filter>\n <filter id=\"thermalGlow\" x=\"-100%\" y=\"-100%\" width=\"300%\" height=\"300%\">\n <feGaussianBlur stdDeviation=\"8\" result=\"glow\"/>\n <feMerge>\n <feMergeNode in=\"glow\"/>\n <feMergeNode in=\"glow\"/>\n <feMergeNode in=\"SourceGraphic\"/>\n </feMerge>\n </filter>\n <linearGradient id=\"infraredBorder\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#7C4DFF\"/>\n <stop offset=\"100%\" stop-color=\"#FF4081\"/>\n </linearGradient>\n <radialGradient id=\"scopeGradient\" cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stop-color=\"#1A1525\"/>\n <stop offset=\"70%\" stop-color=\"#0D0D12\"/>\n <stop offset=\"100%\" stop-color=\"#08080C\"/>\n </radialGradient>\n <linearGradient id=\"heatScale\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n <stop offset=\"0%\" stop-color=\"#000033\"/>\n <stop offset=\"20%\" stop-color=\"#0066FF\"/>\n <stop offset=\"40%\" stop-color=\"#00FFFF\"/>\n <stop offset=\"60%\" stop-color=\"#00FF00\"/>\n <stop offset=\"80%\" stop-color=\"#FFFF00\"/>\n <stop offset=\"100%\" stop-color=\"#FF0000\"/>\n </linearGradient>`;\n}\n\nexport function renderInfraredLayout(\n stats: LanguageStatsResult,\n theme: Theme,\n width: number\n): string {\n const { palette, background, backgroundSecondary, headerColor, labelColor, footerColor, fontFamily, fontImport, borderRadius } = theme;\n const rows = stats.languages;\n const rowGap = 22;\n const panelTop = 70;\n const panelHeaderOffset = 30;\n const panelBottomPadding = 80;\n const minHeight = 420;\n const panelContentHeight =\n panelTop +\n panelHeaderOffset +\n Math.max(0, rows.length - 1) * rowGap +\n panelBottomPadding;\n const height = Math.max(minHeight, panelContentHeight);\n\n // Position pie chart on the left side\n const centerX = 180;\n const centerY = Math.round(height / 2);\n const maxRadius = 130;\n const minRadius = 40;\n\n // Create pie/donut segments with thermal coloring\n let currentAngle = -90; // Start from top\n const segments = rows.map((row, i) => {\n const angle = (row.percent / 100) * 360;\n const startAngle = currentAngle;\n const endAngle = currentAngle + angle;\n currentAngle = endAngle;\n\n const startRad = (startAngle * Math.PI) / 180;\n const endRad = (endAngle * Math.PI) / 180;\n\n // Outer arc\n const x1 = centerX + maxRadius * Math.cos(startRad);\n const y1 = centerY + maxRadius * Math.sin(startRad);\n const x2 = centerX + maxRadius * Math.cos(endRad);\n const y2 = centerY + maxRadius * Math.sin(endRad);\n\n // Inner arc\n const x3 = centerX + minRadius * Math.cos(endRad);\n const y3 = centerY + minRadius * Math.sin(endRad);\n const x4 = centerX + minRadius * Math.cos(startRad);\n const y4 = centerY + minRadius * Math.sin(startRad);\n\n const largeArc = angle > 180 ? 1 : 0;\n\n const path = `M ${x1} ${y1} A ${maxRadius} ${maxRadius} 0 ${largeArc} 1 ${x2} ${y2} L ${x3} ${y3} A ${minRadius} ${minRadius} 0 ${largeArc} 0 ${x4} ${y4} Z`;\n\n return {\n ...row,\n path,\n color: palette[i % palette.length],\n };\n });\n\n const segmentsSvg = segments.map((seg, i) => `\n <path d=\"${seg.path}\" fill=\"${seg.color}\" filter=\"url(#heatBloom)\" opacity=\"0.9\">\n ${i === 0 ? '<animate attributeName=\"opacity\" values=\"0.9;1;0.9\" dur=\"2s\" repeatCount=\"indefinite\"/>' : \"\"}\n </path>`).join(\"\");\n\n // Concentric guide rings\n const rings = [60, 95, 130].map(r =>\n `<circle cx=\"${centerX}\" cy=\"${centerY}\" r=\"${r}\" fill=\"none\" stroke=\"#2A2040\" stroke-width=\"1\" stroke-dasharray=\"4 4\"/>`\n ).join(\"\");\n\n // Crosshair\n const crosshair = `\n <line x1=\"${centerX - 150}\" y1=\"${centerY}\" x2=\"${centerX + 150}\" y2=\"${centerY}\" stroke=\"#3A3050\" stroke-width=\"1\"/>\n <line x1=\"${centerX}\" y1=\"${centerY - 150}\" x2=\"${centerX}\" y2=\"${centerY + 150}\" stroke=\"#3A3050\" stroke-width=\"1\"/>\n <circle cx=\"${centerX}\" cy=\"${centerY}\" r=\"5\" fill=\"none\" stroke=\"#FF4081\" stroke-width=\"2\">\n <animate attributeName=\"r\" values=\"5;8;5\" dur=\"2s\" repeatCount=\"indefinite\"/>\n <animate attributeName=\"opacity\" values=\"1;0.5;1\" dur=\"2s\" repeatCount=\"indefinite\"/>\n </circle>`;\n\n // Data readout panel (right side) - positioned clearly to the right\n const panelX = 340;\n const panelY = panelTop;\n const readoutSvg = rows.map((row, i) => {\n const y = panelY + panelHeaderOffset + i * rowGap;\n const color = palette[i % palette.length];\n return `\n <rect x=\"${panelX}\" y=\"${y - 14}\" width=\"4\" height=\"18\" fill=\"${color}\" filter=\"url(#heatBloom)\"/>\n <text x=\"${panelX + 14}\" y=\"${y}\" font-size=\"11\" fill=\"${labelColor}\">${row.language}</text>\n <text x=\"${width - 25}\" y=\"${y}\" font-size=\"11\" fill=\"${color}\" text-anchor=\"end\" filter=\"url(#heatBloom)\">${row.percent.toFixed(1)}%</text>`;\n }).join(\"\");\n\n // Heat scale bar\n const scaleY = height - 45;\n const scaleWidth = width - 60;\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"GitHub language stats\">\n <style>\n ${fontImport ?? \"\"}\n text { font-family: ${fontFamily}; }\n </style>\n <defs>\n <linearGradient id=\"bg\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"${background}\"/>\n <stop offset=\"100%\" stop-color=\"${backgroundSecondary}\"/>\n </linearGradient>\n ${renderInfraredDefs()}\n </defs>\n\n <!-- Background -->\n <rect width=\"100%\" height=\"100%\" fill=\"url(#bg)\" rx=\"${borderRadius}\"/>\n <rect x=\"2\" y=\"2\" width=\"${width - 4}\" height=\"${height - 4}\" fill=\"none\" stroke=\"url(#infraredBorder)\" stroke-width=\"2\" rx=\"${borderRadius - 2}\"/>\n\n <!-- Header -->\n <text x=\"20\" y=\"30\" font-size=\"14\" fill=\"${headerColor}\" font-weight=\"600\" letter-spacing=\"2\">THERMAL ANALYSIS</text>\n <text x=\"20\" y=\"48\" font-size=\"10\" fill=\"${footerColor}\" letter-spacing=\"1\">${stats.repositoryCount} repositories scanned</text>\n\n <!-- Crosshair and rings -->\n ${crosshair}\n ${rings}\n\n <!-- Pie segments -->\n ${segmentsSvg}\n\n <!-- Center readout -->\n <circle cx=\"${centerX}\" cy=\"${centerY}\" r=\"${minRadius - 5}\" fill=\"#0D0D12\"/>\n <text x=\"${centerX}\" y=\"${centerY - 5}\" font-size=\"20\" fill=\"${headerColor}\" text-anchor=\"middle\" font-weight=\"600\" filter=\"url(#thermalGlow)\">${rows[0]?.percent.toFixed(0) ?? 0}%</text>\n <text x=\"${centerX}\" y=\"${centerY + 14}\" font-size=\"10\" fill=\"${footerColor}\" text-anchor=\"middle\">${rows[0]?.language ?? \"N/A\"}</text>\n\n <!-- Data panel on right -->\n <text x=\"${panelX}\" y=\"${panelY + 10}\" font-size=\"10\" fill=\"${footerColor}\" letter-spacing=\"1\">DISTRIBUTION</text>\n ${readoutSvg}\n\n <!-- Heat scale -->\n <rect x=\"30\" y=\"${scaleY}\" width=\"${scaleWidth}\" height=\"10\" rx=\"2\" fill=\"url(#heatScale)\"/>\n <text x=\"30\" y=\"${scaleY + 22}\" font-size=\"8\" fill=\"${footerColor}\">0%</text>\n <text x=\"${30 + scaleWidth}\" y=\"${scaleY + 22}\" font-size=\"8\" fill=\"${footerColor}\" text-anchor=\"end\">100%</text>\n\n <!-- Timestamp -->\n <text x=\"20\" y=\"${height - 12}\" font-size=\"9\" fill=\"${footerColor}\">${stats.generatedAt}</text>\n</svg>`;\n}\n","import type { LanguageStatsResult } from \"../../types.js\";\nimport type { Theme } from \"../types.js\";\n\nexport function renderOutlineLayout(\n stats: LanguageStatsResult,\n theme: Theme,\n width: number\n): string {\n const { background, headerColor, labelColor, footerColor, borderColor, fontFamily, fontImport } = theme;\n const rows = stats.languages;\n const paddingX = 32;\n const paddingY = 32;\n const barHeight = 28;\n const gap = 16;\n const labelWidth = 100;\n const percentWidth = 50;\n const barAreaWidth = width - paddingX * 2 - labelWidth - percentWidth - 20;\n\n const chartTop = paddingY + 50;\n const height = chartTop + rows.length * (barHeight + gap) + paddingY + 20;\n const maxPercent = Math.max(...rows.map((row) => row.percent), 0);\n\n const bars = rows.map((row, index) => {\n const y = chartTop + index * (barHeight + gap);\n const barWidth = maxPercent === 0 ? 0 : (row.percent / maxPercent) * barAreaWidth;\n const barX = paddingX + labelWidth + 10;\n\n return `\n <text x=\"${paddingX}\" y=\"${y + barHeight / 2 + 4}\" font-size=\"13\" fill=\"${labelColor}\" font-weight=\"400\">${row.language}</text>\n <rect x=\"${barX}\" y=\"${y}\" width=\"${barAreaWidth}\" height=\"${barHeight}\" fill=\"none\" stroke=\"${borderColor}\" stroke-width=\"1\"/>\n <rect x=\"${barX}\" y=\"${y}\" width=\"${barWidth.toFixed(2)}\" height=\"${barHeight}\" fill=\"none\" stroke=\"${labelColor}\" stroke-width=\"2\"/>\n <text x=\"${width - paddingX}\" y=\"${y + barHeight / 2 + 4}\" font-size=\"12\" fill=\"${headerColor}\" text-anchor=\"end\">${row.percent.toFixed(1)}%</text>`;\n }).join(\"\");\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"GitHub language stats\">\n <style>\n ${fontImport ?? \"\"}\n text { font-family: ${fontFamily}; }\n </style>\n\n <!-- Background -->\n <rect width=\"100%\" height=\"100%\" fill=\"${background}\"/>\n <rect x=\"0.5\" y=\"0.5\" width=\"${width - 1}\" height=\"${height - 1}\" fill=\"none\" stroke=\"${borderColor}\" stroke-width=\"1\"/>\n\n <!-- Header -->\n <text x=\"${paddingX}\" y=\"${paddingY + 8}\" font-size=\"11\" fill=\"${headerColor}\" font-weight=\"500\" letter-spacing=\"1\">LANGUAGE DISTRIBUTION</text>\n <line x1=\"${paddingX}\" y1=\"${paddingY + 20}\" x2=\"${width - paddingX}\" y2=\"${paddingY + 20}\" stroke=\"${borderColor}\" stroke-width=\"1\"/>\n <text x=\"${width - paddingX}\" y=\"${paddingY + 8}\" font-size=\"11\" fill=\"${headerColor}\" text-anchor=\"end\">${stats.repositoryCount} repos</text>\n\n <!-- Bars -->\n ${bars}\n\n <!-- Footer -->\n <line x1=\"${paddingX}\" y1=\"${height - paddingY - 8}\" x2=\"${width - paddingX}\" y2=\"${height - paddingY - 8}\" stroke=\"${borderColor}\" stroke-width=\"1\"/>\n <text x=\"${paddingX}\" y=\"${height - paddingY + 8}\" font-size=\"10\" fill=\"${footerColor}\">${stats.generatedAt}</text>\n</svg>`;\n}\n","import type { LanguageStat, LanguageStatsResult } from \"../../types.js\";\nimport type { Theme } from \"../types.js\";\n\nfunction buildPieRows(stats: LanguageStatsResult): LanguageStat[] {\n const rows = stats.languages;\n const topRows = rows.slice(0, 5);\n const topBytes = topRows.reduce((sum, row) => sum + row.bytes, 0);\n const remainingBytes = Math.max(stats.totalBytes - topBytes, 0);\n const hasOther = remainingBytes > 0;\n\n if (!hasOther) {\n return topRows;\n }\n\n const otherPercent = stats.totalBytes === 0 ? 0 : (remainingBytes / stats.totalBytes) * 100;\n\n return [\n ...topRows,\n {\n language: \"Other\",\n bytes: remainingBytes,\n percent: otherPercent,\n },\n ];\n}\n\nexport function renderPieLayout(\n stats: LanguageStatsResult,\n theme: Theme,\n width: number\n): string {\n const {\n palette,\n background,\n backgroundSecondary,\n headerColor,\n labelColor,\n footerColor,\n borderColor,\n fontFamily,\n fontImport,\n borderRadius,\n } = theme;\n\n const height = 360;\n const rows = buildPieRows(stats);\n const totalBytes = stats.totalBytes;\n\n const centerX = 185;\n const centerY = 195;\n const outerRadius = 110;\n const innerRadius = 55;\n\n const legendX = 360;\n const legendY = 90;\n const legendGap = 24;\n\n let currentAngle = -90;\n const segments = rows.map((row, index) => {\n const angle = totalBytes === 0 ? 0 : (row.bytes / totalBytes) * 360;\n const startAngle = currentAngle;\n const endAngle = currentAngle + angle;\n currentAngle = endAngle;\n\n const startRad = (startAngle * Math.PI) / 180;\n const endRad = (endAngle * Math.PI) / 180;\n\n const x1 = centerX + outerRadius * Math.cos(startRad);\n const y1 = centerY + outerRadius * Math.sin(startRad);\n const x2 = centerX + outerRadius * Math.cos(endRad);\n const y2 = centerY + outerRadius * Math.sin(endRad);\n\n const x3 = centerX + innerRadius * Math.cos(endRad);\n const y3 = centerY + innerRadius * Math.sin(endRad);\n const x4 = centerX + innerRadius * Math.cos(startRad);\n const y4 = centerY + innerRadius * Math.sin(startRad);\n\n const largeArc = angle > 180 ? 1 : 0;\n const path =\n `M ${x1} ${y1} ` +\n `A ${outerRadius} ${outerRadius} 0 ${largeArc} 1 ${x2} ${y2} ` +\n `L ${x3} ${y3} ` +\n `A ${innerRadius} ${innerRadius} 0 ${largeArc} 0 ${x4} ${y4} Z`;\n\n return {\n ...row,\n path,\n color: palette[index % palette.length],\n };\n });\n\n const segmentsSvg =\n segments.length > 0\n ? segments\n .map(\n (seg) =>\n `<path d=\"${seg.path}\" fill=\"${seg.color}\" stroke=\"${backgroundSecondary}\" stroke-width=\"1\" />`\n )\n .join(\"\")\n : `<circle cx=\"${centerX}\" cy=\"${centerY}\" r=\"${outerRadius}\" fill=\"${backgroundSecondary}\" stroke=\"${borderColor}\" stroke-width=\"1\" />`;\n\n const legendSvg =\n rows.length > 0\n ? rows\n .map((row, index) => {\n const y = legendY + index * legendGap;\n const color = palette[index % palette.length];\n return `\n <circle cx=\"${legendX}\" cy=\"${y - 5}\" r=\"5\" fill=\"${color}\" />\n <text x=\"${legendX + 14}\" y=\"${y}\" font-size=\"12\" fill=\"${labelColor}\">${row.language}</text>\n <text x=\"${width - 24}\" y=\"${y}\" font-size=\"12\" fill=\"${headerColor}\" text-anchor=\"end\">${row.percent.toFixed(1)}%</text>`;\n })\n .join(\"\")\n : `\n <text x=\"${legendX}\" y=\"${legendY}\" font-size=\"12\" fill=\"${labelColor}\">No language data</text>`;\n\n const header = \"Top Languages\";\n const repoSummary = `${stats.repositoryCount} repos`;\n const statSummary = `${rows.length} slices`;\n const generatedDate = new Date(stats.generatedAt).toISOString().split(\"T\")[0];\n const footer = `Generated ${generatedDate}`;\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"GitHub language stats\">\n <style>\n ${fontImport ?? \"\"}\n text { font-family: ${fontFamily}; }\n </style>\n <defs>\n <linearGradient id=\"bg\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"${background}\" />\n <stop offset=\"100%\" stop-color=\"${backgroundSecondary}\" />\n </linearGradient>\n </defs>\n <rect x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" fill=\"url(#bg)\" rx=\"${borderRadius}\" />\n <rect x=\"10\" y=\"10\" width=\"${width - 20}\" height=\"${height - 20}\" fill=\"none\" stroke=\"${borderColor}\" stroke-width=\"1\" rx=\"${borderRadius - 4}\" />\n\n <text x=\"24\" y=\"34\" font-size=\"16\" fill=\"${labelColor}\" font-weight=\"600\">${header}</text>\n <text x=\"${width - 24}\" y=\"34\" font-size=\"11\" fill=\"${headerColor}\" text-anchor=\"end\">${repoSummary} • ${statSummary}</text>\n\n ${segmentsSvg}\n <circle cx=\"${centerX}\" cy=\"${centerY}\" r=\"${innerRadius - 6}\" fill=\"${background}\" />\n <text x=\"${centerX}\" y=\"${centerY - 4}\" font-size=\"14\" fill=\"${labelColor}\" text-anchor=\"middle\" font-weight=\"600\">\n ${rows[0]?.language ?? \"No data\"}\n </text>\n <text x=\"${centerX}\" y=\"${centerY + 14}\" font-size=\"11\" fill=\"${footerColor}\" text-anchor=\"middle\">\n ${rows[0]?.percent?.toFixed(1) ?? \"0.0\"}%\n </text>\n\n <text x=\"${legendX}\" y=\"${legendY - 26}\" font-size=\"11\" fill=\"${footerColor}\" letter-spacing=\"1\">DISTRIBUTION</text>\n ${legendSvg}\n\n <text x=\"24\" y=\"${height - 16}\" font-size=\"10\" fill=\"${footerColor}\">${footer}</text>\n</svg>`;\n}\n","import type { LanguageStatsResult } from \"../types.js\";\nimport type { SvgOptions } from \"./types.js\";\nimport { themes } from \"./themes.js\";\nimport { renderPhosphorLayout } from \"./layouts/phosphor.js\";\nimport { renderInfraredLayout } from \"./layouts/infrared.js\";\nimport { renderOutlineLayout } from \"./layouts/outline.js\";\nimport { renderPieLayout } from \"./layouts/pie.js\";\n\nexport function renderSvg(stats: LanguageStatsResult, options: SvgOptions = {}): string {\n const width = options.width ?? 600;\n const themeName = options.theme ?? \"default\";\n const theme = themes[themeName] ?? themes.default;\n\n // Use specialized layouts for each theme\n if (themeName === \"phosphor\") {\n return renderPhosphorLayout(stats, theme, width);\n }\n if (themeName === \"infrared\") {\n return renderInfraredLayout(stats, theme, width);\n }\n if (themeName === \"outline\") {\n return renderOutlineLayout(stats, theme, width);\n }\n if (themeName === \"pie\") {\n return renderPieLayout(stats, theme, width);\n }\n\n // Default theme: clean bar chart layout\n const barHeight = options.barHeight ?? 18;\n const gap = options.gap ?? 10;\n const labelWidth = options.labelWidth ?? 140;\n\n const {\n palette,\n background,\n backgroundSecondary,\n headerColor,\n labelColor,\n footerColor,\n borderColor,\n barTrackColor,\n fontFamily,\n borderRadius,\n } = theme;\n\n const rows = stats.languages;\n const paddingX = 16;\n const rightPadding = 16;\n const headerHeight = 28;\n const chartTop = paddingX + headerHeight + 8;\n const footerHeight = 26;\n const chartWidth = width - labelWidth - paddingX - rightPadding;\n const height = chartTop + rows.length * (barHeight + gap) + footerHeight;\n const maxPercent = Math.max(...rows.map((row) => row.percent), 0);\n\n const header = \"GitHub Language Stats\";\n const subheader = `Generated ${stats.generatedAt.slice(0, 10)}`;\n const repoSummary = `${stats.repositoryCount} repos`;\n const footerParts = [\n stats.includedForks ? \"Forks included\" : \"Forks excluded\",\n stats.includedArchived ? \"Archived included\" : \"Archived excluded\",\n stats.includedMarkdown ? \"Markdown included\" : \"Markdown excluded\",\n ];\n const footer = footerParts.join(\" • \");\n\n const bars = rows\n .map((row, index) => {\n const y = chartTop + index * (barHeight + gap);\n const barWidth = maxPercent === 0 ? 0 : (row.percent / maxPercent) * chartWidth;\n const label = `${row.language} (${row.percent.toFixed(1)}%)`;\n\n return `\n <text x=\"${paddingX}\" y=\"${y + barHeight - 4}\" font-size=\"12\" fill=\"${labelColor}\">${label}</text>\n <rect x=\"${paddingX + labelWidth}\" y=\"${y}\" width=\"${chartWidth}\" height=\"${barHeight}\" rx=\"6\" fill=\"${barTrackColor}\" />\n <rect x=\"${paddingX + labelWidth}\" y=\"${y}\" width=\"${barWidth.toFixed(2)}\" height=\"${barHeight}\" rx=\"6\" fill=\"url(#bar-${index})\" />\n`;\n })\n .join(\"\");\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"GitHub language stats\">\n <style>\n text { font-family: ${fontFamily}; }\n .subtitle { letter-spacing: 0.2px; }\n </style>\n <defs>\n <linearGradient id=\"bg\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"${background}\" />\n <stop offset=\"100%\" stop-color=\"${backgroundSecondary}\" />\n </linearGradient>\n <filter id=\"softShadow\" x=\"-20%\" y=\"-20%\" width=\"140%\" height=\"160%\">\n <feDropShadow dx=\"0\" dy=\"6\" stdDeviation=\"8\" flood-color=\"#111827\" flood-opacity=\"0.08\" />\n </filter>\n ${rows\n .map((_, index) => {\n const base = palette[index % palette.length];\n return `<linearGradient id=\"bar-${index}\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n <stop offset=\"0%\" stop-color=\"${base}\" />\n <stop offset=\"100%\" stop-color=\"${base}CC\" />\n </linearGradient>`;\n })\n .join(\"\")}\n </defs>\n <rect x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" fill=\"url(#bg)\" rx=\"${borderRadius}\" />\n <rect x=\"8\" y=\"8\" width=\"${width - 16}\" height=\"${height - 16}\" fill=\"#ffffff\" rx=\"12\" filter=\"url(#softShadow)\" />\n <rect x=\"8.5\" y=\"8.5\" width=\"${width - 17}\" height=\"${height - 17}\" fill=\"none\" stroke=\"${borderColor}\" rx=\"12\" />\n <text x=\"${paddingX}\" y=\"${paddingX + 6}\" font-size=\"16\" fill=\"${labelColor}\" font-weight=\"600\">${header}</text>\n <text x=\"${paddingX}\" y=\"${paddingX + 24}\" font-size=\"11\" fill=\"${headerColor}\" class=\"subtitle\">${subheader}</text>\n <text x=\"${width - rightPadding}\" y=\"${paddingX + 8}\" font-size=\"11\" fill=\"${headerColor}\" text-anchor=\"end\">${repoSummary}</text>\n ${bars}\n <text x=\"${paddingX}\" y=\"${height - 12}\" font-size=\"10\" fill=\"${footerColor}\">${footer}</text>\n</svg>`;\n}\n","/// <reference types=\"node\" />\nimport fs from \"fs\";\nimport path from \"path\";\nimport { getLanguageStats } from \"./index.js\";\nimport { renderJson, renderSvg, type ThemeName } from \"./renderers/index.js\";\nimport type { LanguageStatsResult } from \"./types.js\";\n\ninterface CliOptions {\n token?: string;\n input?: string;\n format: \"json\" | \"svg\";\n includeForks: boolean;\n includeArchived: boolean;\n includeMarkdown: boolean;\n top?: number;\n all?: boolean;\n out?: string;\n theme?: ThemeName;\n}\n\nfunction printHelp(): void {\n const help = `\nUsage: gh-stats [options]\n\nOptions:\n --token <token> GitHub access token (or use GITHUB_TOKEN)\n --in <path> Read precomputed stats JSON (skips GitHub API)\n --format <json|svg> Output format (default: json)\n --json Output JSON format\n --svg Output SVG format\n --theme <name> SVG theme: default, phosphor, infrared, outline, pie (default: default)\n --include-forks Include forked repositories\n --exclude-archived Exclude archived repositories\n --include-markdown Include Markdown/MDX in language stats\n --top <number> Limit to top N languages (default: 10)\n --all Include all languages (overrides --top)\n --out <path> Write output to a file\n --help Show this help message\n\nThemes:\n default Clean, modern light theme\n phosphor Retro CRT terminal with green phosphor glow\n infrared Thermal heat map visualization\n outline Minimalist stroke-only design\n pie Warm donut chart with top 5 + other grouping\n\nExamples:\n npx @saadjs/gh-stats --svg --out stats.svg\n gh-stats --svg --out stats.svg\n gh-stats --svg --theme phosphor --out stats.svg\n gh-stats --svg --theme infrared --out stats.svg\n gh-stats --svg --theme pie --out stats.svg\n gh-stats --format json --top 8\n gh-stats --json --out data.json\n gh-stats --svg --theme phosphor --in data.json --out stats.svg\n`;\n\n console.log(help.trim());\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n const options: CliOptions = {\n format: \"json\",\n includeForks: false,\n includeArchived: true,\n includeMarkdown: false,\n top: 10,\n };\n\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n\n switch (arg) {\n case \"--help\":\n case \"-h\":\n printHelp();\n process.exit(0);\n break;\n case \"--token\":\n options.token = argv[i + 1];\n i += 1;\n break;\n case \"--in\":\n options.input = argv[i + 1];\n i += 1;\n break;\n case \"--format\":\n options.format = (argv[i + 1] as CliOptions[\"format\"]) ?? \"json\";\n i += 1;\n break;\n case \"--json\":\n options.format = \"json\";\n break;\n case \"--svg\":\n options.format = \"svg\";\n break;\n case \"--include-forks\":\n options.includeForks = true;\n break;\n case \"--exclude-archived\":\n options.includeArchived = false;\n break;\n case \"--include-markdown\":\n options.includeMarkdown = true;\n break;\n case \"--top\":\n options.top = Number(argv[i + 1]);\n options.all = false;\n i += 1;\n break;\n case \"--all\":\n options.all = true;\n options.top = undefined;\n break;\n case \"--out\":\n options.out = argv[i + 1];\n i += 1;\n break;\n case \"--theme\":\n options.theme = argv[i + 1] as ThemeName;\n i += 1;\n break;\n default:\n if (arg.startsWith(\"--\")) {\n console.error(`Unknown option: ${arg}`);\n process.exit(1);\n }\n break;\n }\n }\n\n return options;\n}\n\nfunction isLanguageStatsResult(value: unknown): value is LanguageStatsResult {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const record = value as Record<string, unknown>;\n\n return (\n typeof record.totalBytes === \"number\" &&\n Array.isArray(record.languages) &&\n typeof record.generatedAt === \"string\" &&\n typeof record.repositoryCount === \"number\" &&\n typeof record.includedForks === \"boolean\" &&\n typeof record.includedArchived === \"boolean\" &&\n typeof record.includedMarkdown === \"boolean\"\n );\n}\n\nasync function run(): Promise<void> {\n const argv = process.argv.slice(2);\n const options = parseArgs(argv);\n let stats: LanguageStatsResult;\n\n if (options.input) {\n const inPath = path.resolve(process.cwd(), options.input);\n try {\n const raw = fs.readFileSync(inPath, \"utf8\");\n const parsed = JSON.parse(raw);\n\n if (!isLanguageStatsResult(parsed)) {\n console.error(`Invalid stats JSON in ${inPath}`);\n process.exit(1);\n }\n\n stats = parsed;\n } catch (error) {\n console.error(`Failed to read stats JSON from ${inPath}`);\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n } else {\n const token = options.token ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;\n\n if (!token) {\n console.error(\"Missing GitHub token. Use --token or set GITHUB_TOKEN.\");\n process.exit(1);\n }\n\n stats = await getLanguageStats({\n token,\n includeForks: options.includeForks,\n includeArchived: options.includeArchived,\n includeMarkdown: options.includeMarkdown,\n top: options.top,\n all: options.all,\n });\n }\n\n const output =\n options.format === \"svg\" ? renderSvg(stats, { theme: options.theme }) : renderJson(stats);\n\n if (options.out) {\n const outPath = path.resolve(process.cwd(), options.out);\n fs.writeFileSync(outPath, output, \"utf8\");\n console.log(`Wrote ${options.format.toUpperCase()} to ${outPath}`);\n } else {\n process.stdout.write(output + \"\\n\");\n }\n}\n\nrun().catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"],"names":["renderJson","stats","themes","renderPhosphorDefs","renderPhosphorLayout","theme","width","palette","background","backgroundSecondary","headerColor","labelColor","footerColor","borderColor","fontFamily","fontImport","borderRadius","rows","paddingX","blockWidth","blockHeight","blocksPerRow","totalBlocks","totalPercent","sum","r","languageBlocks","row","i","blocksSvg","currentBlock","blockStartY","blockStartX","lang","b","col","x","y","blockEndY","listStartY","listX","listingSvg","color","blockChar","footerY","finalHeight","renderInfraredDefs","renderInfraredLayout","rowGap","panelTop","panelHeaderOffset","panelBottomPadding","minHeight","panelContentHeight","height","centerX","centerY","maxRadius","minRadius","currentAngle","segmentsSvg","angle","startAngle","endAngle","startRad","endRad","x1","y1","x2","y2","x3","y3","x4","y4","largeArc","path","seg","rings","crosshair","panelX","panelY","readoutSvg","scaleY","scaleWidth","renderOutlineLayout","paddingY","barHeight","gap","labelWidth","barAreaWidth","chartTop","maxPercent","bars","index","barWidth","barX","buildPieRows","topRows","topBytes","remainingBytes","otherPercent","renderPieLayout","totalBytes","outerRadius","innerRadius","legendX","legendY","legendGap","segments","legendSvg","header","repoSummary","statSummary","footer","renderSvg","options","themeName","barTrackColor","rightPadding","footerHeight","chartWidth","subheader","label","_","base","printHelp","parseArgs","argv","arg","isLanguageStatsResult","value","record","run","inPath","raw","fs","parsed","error","token","getLanguageStats","output","outPath"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ declare interface AggregateOptions extends FetchOptions {
2
+ top?: number;
3
+ all?: boolean;
4
+ }
5
+
6
+ declare interface FetchOptions {
7
+ token: string;
8
+ includeForks: boolean;
9
+ includeArchived: boolean;
10
+ includeMarkdown: boolean;
11
+ }
12
+
13
+ export declare function getLanguageStats(options: AggregateOptions): Promise<LanguageStatsResult>;
14
+
15
+ declare interface LanguageStat {
16
+ language: string;
17
+ bytes: number;
18
+ percent: number;
19
+ }
20
+
21
+ declare interface LanguageStatsResult {
22
+ totalBytes: number;
23
+ languages: LanguageStat[];
24
+ generatedAt: string;
25
+ repositoryCount: number;
26
+ includedForks: boolean;
27
+ includedArchived: boolean;
28
+ includedMarkdown: boolean;
29
+ }
30
+
31
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ const f = "https://api.github.com", g = {
2
+ Accept: "application/vnd.github+json",
3
+ "User-Agent": "gh-stats",
4
+ "X-GitHub-Api-Version": "2022-11-28"
5
+ };
6
+ function h(t) {
7
+ return {
8
+ ...g,
9
+ Authorization: `Bearer ${t}`
10
+ };
11
+ }
12
+ function w(t) {
13
+ if (!t) return;
14
+ const r = t.split(",");
15
+ for (const e of r) {
16
+ const a = e.match(/<([^>]+)>;\s*rel="([^"]+)"/);
17
+ if (a && a[2] === "next")
18
+ return a[1];
19
+ }
20
+ }
21
+ async function y(t, r) {
22
+ const { data: e, headers: a } = await l(t, r), n = w(a.get("link"));
23
+ return { items: e, nextUrl: n };
24
+ }
25
+ async function l(t, r) {
26
+ const e = await fetch(t, {
27
+ headers: h(r)
28
+ });
29
+ if (!e.ok) {
30
+ const n = await e.text();
31
+ throw new Error(`GitHub API error ${e.status}: ${n}`);
32
+ }
33
+ return { data: await e.json(), headers: e.headers };
34
+ }
35
+ async function p(t) {
36
+ const r = new URLSearchParams({
37
+ per_page: "100",
38
+ visibility: "all",
39
+ affiliation: "owner,collaborator,organization_member"
40
+ });
41
+ let e = `${f}/user/repos?${r.toString()}`;
42
+ const a = [];
43
+ for (; e; ) {
44
+ const { items: n, nextUrl: s } = await y(e, t.token);
45
+ a.push(...n), e = s ?? "";
46
+ }
47
+ return a.filter((n) => !(!t.includeForks && n.fork || !t.includeArchived && n.archived));
48
+ }
49
+ async function A(t, r) {
50
+ const { data: e } = await l(t, r);
51
+ return e;
52
+ }
53
+ const b = /* @__PURE__ */ new Set(["Markdown", "MDX"]);
54
+ function m(t, r) {
55
+ for (const [e, a] of Object.entries(r))
56
+ t[e] = (t[e] ?? 0) + a;
57
+ }
58
+ function v(t, r) {
59
+ const e = Object.values(t).reduce((n, s) => n + s, 0), a = Object.entries(t).map(([n, s]) => ({
60
+ language: n,
61
+ bytes: s,
62
+ percent: e === 0 ? 0 : s / e * 100
63
+ })).sort((n, s) => s.bytes - n.bytes);
64
+ return typeof r == "number" ? a.slice(0, r) : a;
65
+ }
66
+ async function k(t, r, e) {
67
+ const a = [], n = [];
68
+ for (const [s, o] of r.entries()) {
69
+ const c = Promise.resolve().then(() => e(o, s));
70
+ if (a.push(c), t <= r.length) {
71
+ const i = c.then(() => {
72
+ });
73
+ if (n.push(i), n.length >= t) {
74
+ await Promise.race(n);
75
+ const u = n.findIndex((d) => d === i);
76
+ u >= 0 && n.splice(u, 1);
77
+ }
78
+ }
79
+ }
80
+ return Promise.all(a);
81
+ }
82
+ async function S(t, r) {
83
+ const e = {}, n = await k(5, t, async (s) => await A(s.languages_url, r));
84
+ for (const s of n)
85
+ m(e, s);
86
+ return e;
87
+ }
88
+ async function x(t) {
89
+ const r = await p(t), e = await S(r, t.token);
90
+ if (!t.includeMarkdown)
91
+ for (const o of b)
92
+ delete e[o];
93
+ const a = t.all ? void 0 : t.top ?? 10, n = v(e, a);
94
+ return {
95
+ totalBytes: Object.values(e).reduce((o, c) => o + c, 0),
96
+ languages: n,
97
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
98
+ repositoryCount: r.length,
99
+ includedForks: t.includeForks,
100
+ includedArchived: t.includeArchived,
101
+ includedMarkdown: t.includeMarkdown
102
+ };
103
+ }
104
+ export {
105
+ x as getLanguageStats
106
+ };
107
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/github.ts","../src/index.ts"],"sourcesContent":["import type { FetchOptions, LanguageBytes, RepoSummary } from \"./types.js\";\n\nconst API_BASE = \"https://api.github.com\";\n\nconst DEFAULT_HEADERS = {\n Accept: \"application/vnd.github+json\",\n \"User-Agent\": \"gh-stats\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n};\n\ninterface PageResult<T> {\n items: T[];\n nextUrl?: string;\n}\n\nfunction getAuthHeaders(token: string): Record<string, string> {\n return {\n ...DEFAULT_HEADERS,\n Authorization: `Bearer ${token}`,\n };\n}\n\nfunction parseNextLink(linkHeader: string | null): string | undefined {\n if (!linkHeader) return undefined;\n const parts = linkHeader.split(\",\");\n for (const part of parts) {\n const match = part.match(/<([^>]+)>;\\s*rel=\"([^\"]+)\"/);\n if (match && match[2] === \"next\") {\n return match[1];\n }\n }\n return undefined;\n}\n\nasync function fetchPage<T>(url: string, token: string): Promise<PageResult<T>> {\n const { data: items, headers } = await fetchJson<T[]>(url, token);\n const nextUrl = parseNextLink(headers.get(\"link\"));\n return { items, nextUrl };\n}\n\nasync function fetchJson<T>(url: string, token: string): Promise<{ data: T; headers: Headers }> {\n const response = await fetch(url, {\n headers: getAuthHeaders(token),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`GitHub API error ${response.status}: ${body}`);\n }\n\n const data = (await response.json()) as T;\n return { data, headers: response.headers };\n}\n\nexport async function listAllRepos(options: FetchOptions): Promise<RepoSummary[]> {\n const params = new URLSearchParams({\n per_page: \"100\",\n visibility: \"all\",\n affiliation: \"owner,collaborator,organization_member\",\n });\n let url = `${API_BASE}/user/repos?${params.toString()}`;\n const repos: RepoSummary[] = [];\n\n while (url) {\n const { items, nextUrl } = await fetchPage<RepoSummary>(url, options.token);\n repos.push(...items);\n url = nextUrl ?? \"\";\n }\n\n return repos.filter((repo) => {\n if (!options.includeForks && repo.fork) return false;\n if (!options.includeArchived && repo.archived) return false;\n return true;\n });\n}\n\nexport async function fetchRepoLanguages(\n languagesUrl: string,\n token: string\n): Promise<LanguageBytes> {\n const { data } = await fetchJson<LanguageBytes>(languagesUrl, token);\n return data;\n}\n","import type {\n FetchOptions,\n LanguageBytes,\n LanguageStatsResult,\n LanguageStat,\n RepoSummary,\n} from \"./types.js\";\nimport { fetchRepoLanguages, listAllRepos } from \"./github.js\";\n\ninterface AggregateOptions extends FetchOptions {\n top?: number;\n all?: boolean;\n}\n\nconst MARKDOWN_LANGUAGES = new Set([\"Markdown\", \"MDX\"]);\n\nfunction mergeLanguageBytes(target: LanguageBytes, source: LanguageBytes): void {\n for (const [language, bytes] of Object.entries(source)) {\n target[language] = (target[language] ?? 0) + bytes;\n }\n}\n\nfunction toSortedStats(languageBytes: LanguageBytes, top?: number): LanguageStat[] {\n const totalBytes = Object.values(languageBytes).reduce((sum, value) => sum + value, 0);\n\n const stats = Object.entries(languageBytes)\n .map(([language, bytes]) => ({\n language,\n bytes,\n percent: totalBytes === 0 ? 0 : (bytes / totalBytes) * 100,\n }))\n .sort((a, b) => b.bytes - a.bytes);\n\n return typeof top === \"number\" ? stats.slice(0, top) : stats;\n}\n\nasync function asyncPool<T, R>(\n poolLimit: number,\n items: T[],\n iteratorFn: (item: T, index: number) => Promise<R>\n): Promise<R[]> {\n const ret: Promise<R>[] = [];\n const executing: Promise<void>[] = [];\n\n for (const [index, item] of items.entries()) {\n const p = Promise.resolve().then(() => iteratorFn(item, index));\n ret.push(p);\n\n if (poolLimit <= items.length) {\n const e = p.then(() => undefined);\n executing.push(e);\n if (executing.length >= poolLimit) {\n await Promise.race(executing);\n const indexToRemove = executing.findIndex((exec) => exec === e);\n if (indexToRemove >= 0) executing.splice(indexToRemove, 1);\n }\n }\n }\n\n return Promise.all(ret);\n}\n\nasync function fetchAllLanguages(repos: RepoSummary[], token: string): Promise<LanguageBytes> {\n const totals: LanguageBytes = {};\n const concurrency = 5;\n\n const results = await asyncPool(concurrency, repos, async (repo) => {\n const languages = await fetchRepoLanguages(repo.languages_url, token);\n return languages;\n });\n\n for (const languages of results) {\n mergeLanguageBytes(totals, languages);\n }\n\n return totals;\n}\n\nexport async function getLanguageStats(options: AggregateOptions): Promise<LanguageStatsResult> {\n const repos = await listAllRepos(options);\n const totals = await fetchAllLanguages(repos, options.token);\n if (!options.includeMarkdown) {\n for (const language of MARKDOWN_LANGUAGES) {\n delete totals[language];\n }\n }\n const top = options.all ? undefined : (options.top ?? 10);\n const languages = toSortedStats(totals, top);\n const totalBytes = Object.values(totals).reduce((sum, value) => sum + value, 0);\n\n return {\n totalBytes,\n languages,\n generatedAt: new Date().toISOString(),\n repositoryCount: repos.length,\n includedForks: options.includeForks,\n includedArchived: options.includeArchived,\n includedMarkdown: options.includeMarkdown,\n };\n}\n"],"names":["API_BASE","DEFAULT_HEADERS","getAuthHeaders","token","parseNextLink","linkHeader","parts","part","match","fetchPage","url","items","headers","fetchJson","nextUrl","response","body","listAllRepos","options","params","repos","repo","fetchRepoLanguages","languagesUrl","data","MARKDOWN_LANGUAGES","mergeLanguageBytes","target","source","language","bytes","toSortedStats","languageBytes","top","totalBytes","sum","value","stats","a","b","asyncPool","poolLimit","iteratorFn","ret","executing","index","item","p","e","indexToRemove","exec","fetchAllLanguages","totals","results","languages","getLanguageStats"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@saadjs/gh-stats",
3
+ "author": "Saad Bash",
4
+ "repository": {
5
+ "url": "github.com/saadjs/gh-stats"
6
+ },
7
+ "version": "1.0.0",
8
+ "description": "Generate GitHub language stats as JSON or SVG",
9
+ "type": "module",
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "bin": {
13
+ "gh-stats": "dist/cli.js"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "keywords": [],
23
+ "license": "ISC",
24
+ "devDependencies": {
25
+ "@types/node": "^25.2.0",
26
+ "@typescript-eslint/eslint-plugin": "^8.54.0",
27
+ "@typescript-eslint/parser": "^8.54.0",
28
+ "@vitest/ui": "^4.0.18",
29
+ "eslint": "^9.39.2",
30
+ "eslint-config-prettier": "^10.1.8",
31
+ "husky": "^9.1.7",
32
+ "lint-staged": "^16.2.7",
33
+ "prettier": "^3.8.1",
34
+ "typescript": "^5.9.3",
35
+ "vite": "^7.3.1",
36
+ "vite-plugin-dts": "^4.5.4",
37
+ "vitest": "^4.0.18"
38
+ },
39
+ "lint-staged": {
40
+ "**/*.{ts,tsx,js,jsx,mjs,cjs}": [
41
+ "eslint --fix",
42
+ "prettier --write"
43
+ ],
44
+ "**/*.{json,md,yml,yaml}": [
45
+ "prettier --write"
46
+ ]
47
+ },
48
+ "scripts": {
49
+ "build": "vite build",
50
+ "lint": "eslint . --max-warnings=0",
51
+ "lint:fix": "eslint . --fix",
52
+ "format": "prettier . --write",
53
+ "format:check": "prettier . --check",
54
+ "postbuild": "node scripts/chmod-cli.js",
55
+ "start": "node dist/cli.js",
56
+ "test": "vitest run",
57
+ "test:ui": "vitest --ui"
58
+ }
59
+ }