-
Notifications
You must be signed in to change notification settings - Fork 0
chore: script to update the repo stars in real time #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,27 +7,130 @@ import { fileURLToPath } from "node:url"; | |||||||||
| const __filename = fileURLToPath(import.meta.url); | ||||||||||
| const __dirname = path.dirname(__filename); | ||||||||||
|
|
||||||||||
| const sourceFile = path.join(__dirname, "..", "..", "data", "repository_stats.json"); | ||||||||||
| const targetFile = path.join(__dirname, "..", "data", "repository_stats.json"); | ||||||||||
| const fallbackFile = path.join(__dirname, "..", "..", "data", "repository_stats.json"); | ||||||||||
|
|
||||||||||
| function syncRepoStats() { | ||||||||||
| if (!fs.existsSync(sourceFile)) { | ||||||||||
| console.warn("[sync-repo-stats] Source file not found, skipping sync:", sourceFile); | ||||||||||
| return; | ||||||||||
| const repos = [ | ||||||||||
| "hiero-consensus-node", "hiero-local-node", "hiero-mirror-node", | ||||||||||
| "hiero-improvement-proposals", "hiero-sdk-js", "hiero-sdk-java", | ||||||||||
| "hiero-json-rpc-relay", "hiero-sdk-go", "hiero-sdk-rust", | ||||||||||
| "hiero-mirror-node-explorer", "hiero-cli", "solo", "hiero-block-node", | ||||||||||
| "hiero-sdk-tck", "hiero-sdk-cpp", "governance", "hiero-sdk-python", | ||||||||||
| "hiero-sdk-swift", "sdk-collaboration-hub", "tsc", | ||||||||||
| ]; | ||||||||||
|
|
||||||||||
| async function fetchFromGitHub() { | ||||||||||
| const stats = new Map(); | ||||||||||
| const cachedStats = loadFallback(false); | ||||||||||
| const headers = { | ||||||||||
| "User-Agent": "hiero-website-build", | ||||||||||
| "Accept": "application/vnd.github+json", | ||||||||||
| "X-GitHub-Api-Version": "2022-11-28", | ||||||||||
| }; | ||||||||||
| if (process.env.GITHUB_TOKEN) { | ||||||||||
| headers["Authorization"] = `Bearer ${process.env.GITHUB_TOKEN}`; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| console.log("[sync-repo-stats] Fetching repository statistics from GitHub..."); | ||||||||||
|
|
||||||||||
| let successCount = 0; | ||||||||||
|
|
||||||||||
| for (const repo of repos) { | ||||||||||
| const response = await fetch(`https://api.github.com/repos/hiero-ledger/${repo}`, { headers }); | ||||||||||
|
Comment on lines
+34
to
+39
|
||||||||||
| if (response.ok) { | ||||||||||
| const data = await response.json(); | ||||||||||
| stats.set(repo, { stars: data.stargazers_count }); | ||||||||||
| successCount += 1; | ||||||||||
| console.log(` ✓ ${repo}: ${data.stargazers_count} stars`); | ||||||||||
| } else { | ||||||||||
| const cachedStars = cachedStats?.[repo]?.stars; | ||||||||||
| if (typeof cachedStars === "number") { | ||||||||||
| stats.set(repo, { stars: cachedStars }); | ||||||||||
| console.warn(` ⚠ ${repo}: API ${response.status}, using cached value ${cachedStars}`); | ||||||||||
| } else { | ||||||||||
| stats.set(repo, { stars: 0 }); | ||||||||||
| console.warn(` ✗ ${repo}: API responded with ${response.status}`); | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // If access is blocked/rate-limited and nothing has succeeded, stop early. | ||||||||||
| if ((response.status === 401 || response.status === 403) && successCount === 0) { | ||||||||||
| const resetAt = response.headers.get("x-ratelimit-reset"); | ||||||||||
| if (resetAt) { | ||||||||||
| const resetTime = new Date(Number(resetAt) * 1000).toISOString(); | ||||||||||
| console.warn(`[sync-repo-stats] GitHub access currently limited; rate limit resets at ${resetTime}.`); | ||||||||||
| } | ||||||||||
| break; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Small delay to stay well within GitHub's rate limits. | ||||||||||
| await new Promise((resolve) => setTimeout(resolve, 50)); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const sourceJson = fs.readFileSync(sourceFile, "utf8"); | ||||||||||
| // Ensure all repos exist in output, preferring cache over zeroes when available. | ||||||||||
| for (const repo of repos) { | ||||||||||
| if (stats.has(repo)) continue; | ||||||||||
| const cachedStars = cachedStats?.[repo]?.stars; | ||||||||||
| stats.set(repo, { stars: typeof cachedStars === "number" ? cachedStars : 0 }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Validate JSON before writing, so bad source data does not break the app silently. | ||||||||||
| JSON.parse(sourceJson); | ||||||||||
| return Object.fromEntries(stats); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| fs.writeFileSync(targetFile, sourceJson); | ||||||||||
| console.log("[sync-repo-stats] Synced repository_stats.json to nextjs/data"); | ||||||||||
| function totalStars(stats) { | ||||||||||
| return Object.values(stats).reduce((sum, repo) => sum + (repo?.stars ?? 0), 0); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| try { | ||||||||||
| syncRepoStats(); | ||||||||||
| } catch (error) { | ||||||||||
| console.error("[sync-repo-stats] Failed:", error); | ||||||||||
| process.exit(1); | ||||||||||
| function loadFallback(log = true) { | ||||||||||
| let bestStats = null; | ||||||||||
| let bestSource = null; | ||||||||||
|
|
||||||||||
| for (const file of [targetFile, fallbackFile]) { | ||||||||||
| if (fs.existsSync(file)) { | ||||||||||
| try { | ||||||||||
| const parsed = JSON.parse(fs.readFileSync(file, "utf8")); | ||||||||||
| if (!bestStats || totalStars(parsed) > totalStars(bestStats)) { | ||||||||||
| bestStats = parsed; | ||||||||||
| bestSource = file; | ||||||||||
| } | ||||||||||
| } catch { | ||||||||||
| // Ignore malformed cache files and keep searching. | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (bestStats) { | ||||||||||
| if (log) { | ||||||||||
| console.warn(`[sync-repo-stats] Using cached data from ${bestSource}`); | ||||||||||
| } | ||||||||||
| return bestStats; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (log) { | ||||||||||
| console.warn("[sync-repo-stats] No cached data available — writing empty stats."); | ||||||||||
| } | ||||||||||
| return Object.fromEntries(repos.map((r) => [r, { stars: 0 }])); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| async function run() { | ||||||||||
| let stats; | ||||||||||
| try { | ||||||||||
| stats = await fetchFromGitHub(); | ||||||||||
| } catch (error) { | ||||||||||
| console.warn(`[sync-repo-stats] GitHub fetch failed (${error.message}), falling back to cached data.`); | ||||||||||
| stats = loadFallback(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const dataDir = path.dirname(targetFile); | ||||||||||
| if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true }); | ||||||||||
|
|
||||||||||
| fs.writeFileSync(targetFile, JSON.stringify(stats, null, 2)); | ||||||||||
|
|
||||||||||
| const totalStars = Object.values(stats).reduce((sum, r) => sum + r.stars, 0); | ||||||||||
| console.log(`[sync-repo-stats] Done. Total stars: ${totalStars.toLocaleString()}`); | ||||||||||
|
Comment on lines
+129
to
+130
|
||||||||||
| const totalStars = Object.values(stats).reduce((sum, r) => sum + r.stars, 0); | |
| console.log(`[sync-repo-stats] Done. Total stars: ${totalStars.toLocaleString()}`); | |
| const totalStarsCount = totalStars(stats); | |
| console.log(`[sync-repo-stats] Done. Total stars: ${totalStarsCount.toLocaleString()}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded
reposlist is duplicated elsewhere in the repo (e.g.,scripts/fetch-repo-stats.mjs). Maintaining multiple sources of truth increases the chance the Hugo and Next.js builds drift (different repo sets / missing new repos). Consider centralizing the repo list (single JSON/config module) and importing it from both scripts.