Skip to content

Commit 4f037fe

Browse files
committed
add starchart
1 parent e839245 commit 4f037fe

2 files changed

Lines changed: 73 additions & 0 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ on:
44
push:
55
branches: ['main']
66
workflow_dispatch:
7+
schedule:
8+
- cron: '0 19 * * *'
79

810
permissions:
911
contents: read
@@ -29,6 +31,11 @@ jobs:
2931
- name: 📦 Install dependencies
3032
run: bun install
3133

34+
- name: 📊 Generate star history SVG
35+
run: node scripts/gen-star-chart.mjs
36+
env:
37+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
3239
- name: 🛠️ Build project
3340
run: bun run build
3441

scripts/gen-star-chart.mjs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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

Comments
 (0)