Skip to content

Commit f0c9069

Browse files
authored
Merge pull request #4 from OpenElements/daniel-dev-rebuild
chore: script to update the repo stars in real time
2 parents 91d900e + 0da9ad8 commit f0c9069

File tree

2 files changed

+132
-29
lines changed

2 files changed

+132
-29
lines changed

nextjs/data/repository_stats.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,60 @@
11
{
22
"hiero-consensus-node": {
3-
"stars": 376
3+
"stars": 383
44
},
55
"hiero-local-node": {
6-
"stars": 272
6+
"stars": 266
77
},
88
"hiero-mirror-node": {
9-
"stars": 175
9+
"stars": 178
1010
},
1111
"hiero-improvement-proposals": {
12-
"stars": 175
12+
"stars": 176
1313
},
1414
"hiero-sdk-js": {
15-
"stars": 306
15+
"stars": 317
1616
},
1717
"hiero-sdk-java": {
18-
"stars": 249
18+
"stars": 250
1919
},
2020
"hiero-json-rpc-relay": {
2121
"stars": 87
2222
},
2323
"hiero-sdk-go": {
24-
"stars": 123
24+
"stars": 122
2525
},
2626
"hiero-sdk-rust": {
27-
"stars": 56
27+
"stars": 58
2828
},
2929
"hiero-mirror-node-explorer": {
3030
"stars": 46
3131
},
3232
"hiero-cli": {
33-
"stars": 36
33+
"stars": 41
3434
},
3535
"solo": {
36-
"stars": 35
36+
"stars": 38
3737
},
3838
"hiero-block-node": {
39-
"stars": 33
39+
"stars": 35
4040
},
4141
"hiero-sdk-tck": {
4242
"stars": 22
4343
},
4444
"hiero-sdk-cpp": {
45-
"stars": 36
45+
"stars": 39
4646
},
4747
"governance": {
4848
"stars": 13
4949
},
5050
"hiero-sdk-python": {
51-
"stars": 47
51+
"stars": 54
5252
},
5353
"hiero-sdk-swift": {
5454
"stars": 33
5555
},
5656
"sdk-collaboration-hub": {
57-
"stars": 5
57+
"stars": 7
5858
},
5959
"tsc": {
6060
"stars": 5

nextjs/scripts/sync-repo-stats.mjs

Lines changed: 118 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,130 @@ import { fileURLToPath } from "node:url";
77
const __filename = fileURLToPath(import.meta.url);
88
const __dirname = path.dirname(__filename);
99

10-
const sourceFile = path.join(__dirname, "..", "..", "data", "repository_stats.json");
1110
const targetFile = path.join(__dirname, "..", "data", "repository_stats.json");
11+
const fallbackFile = path.join(__dirname, "..", "..", "data", "repository_stats.json");
1212

13-
function syncRepoStats() {
14-
if (!fs.existsSync(sourceFile)) {
15-
console.warn("[sync-repo-stats] Source file not found, skipping sync:", sourceFile);
16-
return;
13+
const repos = [
14+
"hiero-consensus-node", "hiero-local-node", "hiero-mirror-node",
15+
"hiero-improvement-proposals", "hiero-sdk-js", "hiero-sdk-java",
16+
"hiero-json-rpc-relay", "hiero-sdk-go", "hiero-sdk-rust",
17+
"hiero-mirror-node-explorer", "hiero-cli", "solo", "hiero-block-node",
18+
"hiero-sdk-tck", "hiero-sdk-cpp", "governance", "hiero-sdk-python",
19+
"hiero-sdk-swift", "sdk-collaboration-hub", "tsc",
20+
];
21+
22+
async function fetchFromGitHub() {
23+
const stats = new Map();
24+
const cachedStats = loadFallback(false);
25+
const headers = {
26+
"User-Agent": "hiero-website-build",
27+
"Accept": "application/vnd.github+json",
28+
"X-GitHub-Api-Version": "2022-11-28",
29+
};
30+
if (process.env.GITHUB_TOKEN) {
31+
headers["Authorization"] = `Bearer ${process.env.GITHUB_TOKEN}`;
32+
}
33+
34+
console.log("[sync-repo-stats] Fetching repository statistics from GitHub...");
35+
36+
let successCount = 0;
37+
38+
for (const repo of repos) {
39+
const response = await fetch(`https://api.github.com/repos/hiero-ledger/${repo}`, { headers });
40+
if (response.ok) {
41+
const data = await response.json();
42+
stats.set(repo, { stars: data.stargazers_count });
43+
successCount += 1;
44+
console.log(` ✓ ${repo}: ${data.stargazers_count} stars`);
45+
} else {
46+
const cachedStars = cachedStats?.[repo]?.stars;
47+
if (typeof cachedStars === "number") {
48+
stats.set(repo, { stars: cachedStars });
49+
console.warn(` ⚠ ${repo}: API ${response.status}, using cached value ${cachedStars}`);
50+
} else {
51+
stats.set(repo, { stars: 0 });
52+
console.warn(` ✗ ${repo}: API responded with ${response.status}`);
53+
}
54+
}
55+
56+
// If access is blocked/rate-limited and nothing has succeeded, stop early.
57+
if ((response.status === 401 || response.status === 403) && successCount === 0) {
58+
const resetAt = response.headers.get("x-ratelimit-reset");
59+
if (resetAt) {
60+
const resetTime = new Date(Number(resetAt) * 1000).toISOString();
61+
console.warn(`[sync-repo-stats] GitHub access currently limited; rate limit resets at ${resetTime}.`);
62+
}
63+
break;
64+
}
65+
66+
// Small delay to stay well within GitHub's rate limits.
67+
await new Promise((resolve) => setTimeout(resolve, 50));
1768
}
1869

19-
const sourceJson = fs.readFileSync(sourceFile, "utf8");
70+
// Ensure all repos exist in output, preferring cache over zeroes when available.
71+
for (const repo of repos) {
72+
if (stats.has(repo)) continue;
73+
const cachedStars = cachedStats?.[repo]?.stars;
74+
stats.set(repo, { stars: typeof cachedStars === "number" ? cachedStars : 0 });
75+
}
2076

21-
// Validate JSON before writing, so bad source data does not break the app silently.
22-
JSON.parse(sourceJson);
77+
return Object.fromEntries(stats);
78+
}
2379

24-
fs.writeFileSync(targetFile, sourceJson);
25-
console.log("[sync-repo-stats] Synced repository_stats.json to nextjs/data");
80+
function totalStars(stats) {
81+
return Object.values(stats).reduce((sum, repo) => sum + (repo?.stars ?? 0), 0);
2682
}
2783

28-
try {
29-
syncRepoStats();
30-
} catch (error) {
31-
console.error("[sync-repo-stats] Failed:", error);
32-
process.exit(1);
84+
function loadFallback(log = true) {
85+
let bestStats = null;
86+
let bestSource = null;
87+
88+
for (const file of [targetFile, fallbackFile]) {
89+
if (fs.existsSync(file)) {
90+
try {
91+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
92+
if (!bestStats || totalStars(parsed) > totalStars(bestStats)) {
93+
bestStats = parsed;
94+
bestSource = file;
95+
}
96+
} catch {
97+
// Ignore malformed cache files and keep searching.
98+
}
99+
}
100+
}
101+
102+
if (bestStats) {
103+
if (log) {
104+
console.warn(`[sync-repo-stats] Using cached data from ${bestSource}`);
105+
}
106+
return bestStats;
107+
}
108+
109+
if (log) {
110+
console.warn("[sync-repo-stats] No cached data available — writing empty stats.");
111+
}
112+
return Object.fromEntries(repos.map((r) => [r, { stars: 0 }]));
113+
}
114+
115+
async function run() {
116+
let stats;
117+
try {
118+
stats = await fetchFromGitHub();
119+
} catch (error) {
120+
console.warn(`[sync-repo-stats] GitHub fetch failed (${error.message}), falling back to cached data.`);
121+
stats = loadFallback();
122+
}
123+
124+
const dataDir = path.dirname(targetFile);
125+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
126+
127+
fs.writeFileSync(targetFile, JSON.stringify(stats, null, 2));
128+
129+
const totalStars = Object.values(stats).reduce((sum, r) => sum + r.stars, 0);
130+
console.log(`[sync-repo-stats] Done. Total stars: ${totalStars.toLocaleString()}`);
33131
}
132+
133+
run().catch((error) => {
134+
console.error("[sync-repo-stats] Unexpected error:", error);
135+
process.exit(1);
136+
});

0 commit comments

Comments
 (0)