|
| 1 | +import { writeFileSync, mkdirSync } from 'fs'; |
| 2 | + |
| 3 | +const repo = process.env.GITHUB_REPOSITORY; |
| 4 | +const token = process.env.GITHUB_TOKEN; |
| 5 | + |
| 6 | +const stars = []; |
| 7 | +let page = 1; |
| 8 | +while (true) { |
| 9 | + const res = await fetch( |
| 10 | + `https://api.github.com/repos/${repo}/stargazers?per_page=100&page=${page}`, |
| 11 | + { |
| 12 | + headers: { |
| 13 | + Authorization: `Bearer ${token}`, |
| 14 | + Accept: 'application/vnd.github.star+json', |
| 15 | + }, |
| 16 | + }, |
| 17 | + ); |
| 18 | + if (!res.ok) throw new Error(`GitHub API ${res.status}: ${await res.text()}`); |
| 19 | + const data = await res.json(); |
| 20 | + if (data.length === 0) break; |
| 21 | + for (const s of data) stars.push(new Date(s.starred_at).getTime()); |
| 22 | + if (data.length < 100) break; |
| 23 | + page++; |
| 24 | +} |
| 25 | + |
| 26 | +stars.sort((a, b) => a - b); |
| 27 | + |
| 28 | +const byDay = new Map(); |
| 29 | +stars.forEach((t, i) => { |
| 30 | + byDay.set(new Date(t).toISOString().slice(0, 10), i + 1); |
| 31 | +}); |
| 32 | +const points = [...byDay.entries()].map(([day, count]) => ({ |
| 33 | + day, |
| 34 | + count, |
| 35 | +})); |
| 36 | + |
| 37 | +const W = 800, |
| 38 | + H = 400, |
| 39 | + PAD = 50; |
| 40 | +const maxCount = Math.max(...points.map((p) => p.count), 1); |
| 41 | +const n = points.length; |
| 42 | +const xy = points.map((p, i) => { |
| 43 | + const x = PAD + (i / Math.max(n - 1, 1)) * (W - 2 * PAD); |
| 44 | + const y = H - PAD - (p.count / maxCount) * (H - 2 * PAD); |
| 45 | + return `${x.toFixed(1)},${y.toFixed(1)}`; |
| 46 | +}); |
| 47 | + |
| 48 | +const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" font-family="sans-serif"> |
| 49 | + <rect width="${W}" height="${H}" fill="#0d1117" rx="8"/> |
| 50 | + <text x="${W / 2}" y="32" fill="#e6edf3" font-size="18" text-anchor="middle" font-weight="bold"> |
| 51 | + ${repo} — Star History (${maxCount} ⭐) |
| 52 | + </text> |
| 53 | + <line x1="${PAD}" y1="${H - PAD}" x2="${W - PAD}" y2="${H - PAD}" stroke="#30363d"/> |
| 54 | + <line x1="${PAD}" y1="${PAD}" x2="${PAD}" y2="${H - PAD}" stroke="#30363d"/> |
| 55 | + <text x="${PAD - 8}" y="${PAD + 5}" fill="#8b949e" font-size="12" text-anchor="end">${maxCount}</text> |
| 56 | + <text x="${PAD - 8}" y="${H - PAD + 5}" fill="#8b949e" font-size="12" text-anchor="end">0</text> |
| 57 | + <text x="${PAD}" y="${H - PAD + 22}" fill="#8b949e" font-size="12">${points[0]?.day ?? ''}</text> |
| 58 | + <text x="${W - PAD}" y="${H - PAD + 22}" fill="#8b949e" font-size="12" text-anchor="end">${points[n - 1]?.day ?? ''}</text> |
| 59 | + <polyline points="${xy.join(' ')}" fill="none" stroke="#f0b429" stroke-width="2.5" stroke-linejoin="round"/> |
| 60 | +</svg>`; |
| 61 | + |
| 62 | +mkdirSync('public', { recursive: true }); |
| 63 | +writeFileSync('public/starcharts.svg', svg); |
| 64 | +console.log( |
| 65 | + `Generated public/starcharts.svg with ${maxCount} stars, ${n} points`, |
| 66 | +); |
0 commit comments