Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions nextjs/data/repository_stats.json
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
{
"hiero-consensus-node": {
"stars": 376
"stars": 383
},
"hiero-local-node": {
"stars": 272
"stars": 266
},
"hiero-mirror-node": {
"stars": 175
"stars": 178
},
"hiero-improvement-proposals": {
"stars": 175
"stars": 176
},
"hiero-sdk-js": {
"stars": 306
"stars": 317
},
"hiero-sdk-java": {
"stars": 249
"stars": 250
},
"hiero-json-rpc-relay": {
"stars": 87
},
"hiero-sdk-go": {
"stars": 123
"stars": 122
},
"hiero-sdk-rust": {
"stars": 56
"stars": 58
},
"hiero-mirror-node-explorer": {
"stars": 46
},
"hiero-cli": {
"stars": 36
"stars": 41
},
"solo": {
"stars": 35
"stars": 38
},
"hiero-block-node": {
"stars": 33
"stars": 35
},
"hiero-sdk-tck": {
"stars": 22
},
"hiero-sdk-cpp": {
"stars": 36
"stars": 39
},
"governance": {
"stars": 13
},
"hiero-sdk-python": {
"stars": 47
"stars": 54
},
"hiero-sdk-swift": {
"stars": 33
},
"sdk-collaboration-hub": {
"stars": 5
"stars": 7
},
"tsc": {
"stars": 5
Expand Down
133 changes: 118 additions & 15 deletions nextjs/scripts/sync-repo-stats.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
];
Comment on lines +13 to +20
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded repos list 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.

Copilot uses AI. Check for mistakes.

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
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script runs during pnpm dev and pnpm build (via sync:repo-stats) and always makes 20 GitHub API calls. Without GITHUB_TOKEN, the unauthenticated limit (60 req/hr per IP) can be exhausted quickly in CI or during local development, causing frequent fallbacks/zeros. Consider adding a cache TTL / "only refresh when explicitly requested" behavior (e.g., skip the API fetch if a recent repository_stats.json exists, or require a token/flag to refresh).

Copilot uses AI. Check for mistakes.
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
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run() recomputes totalStars with Object.values(stats).reduce((sum, r) => sum + r.stars, 0), which will produce NaN or throw if any cached entry is missing/null/has non-numeric stars (possible when falling back to a partially-written or malformed cache). Reuse the existing totalStars(stats) helper here (and avoid naming the local variable the same as the helper) to keep the total robust.

Suggested change
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()}`);

Copilot uses AI. Check for mistakes.
}

run().catch((error) => {
console.error("[sync-repo-stats] Unexpected error:", error);
process.exit(1);
});
Loading