From de17993402bb3d6564c4db640c46a985f0e290d7 Mon Sep 17 00:00:00 2001 From: aa_humaaan Date: Tue, 3 Mar 2026 20:25:18 +0530 Subject: [PATCH] feat: add auto-fetch script for GitHub stars and last_commit metadata --- scripts/fetch-github-metadata.js | 144 +++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 scripts/fetch-github-metadata.js diff --git a/scripts/fetch-github-metadata.js b/scripts/fetch-github-metadata.js new file mode 100644 index 0000000..fcefeef --- /dev/null +++ b/scripts/fetch-github-metadata.js @@ -0,0 +1,144 @@ +#!/usr/bin/env node +/** + * fetch-github-metadata.js + * + * Automatically fetches stars and last_commit from the GitHub API + * for every tool in tools.json that has a `github_repo` field. + * + * Usage: + * node scripts/fetch-github-metadata.js # uses unauthenticated API (60 req/hr) + * GITHUB_TOKEN=ghp_xxx node scripts/fetch-github-metadata.js # authenticated (5000 req/hr) + * + * What it updates in tools.json: + * - stars → repo.stargazers_count + * - last_commit → default branch's latest commit date (ISO 8601) + */ + +const fs = require('fs'); +const path = require('path'); + +const TOOLS_PATH = path.join(__dirname, '../data/tools.json'); +const GITHUB_TOKEN = process.env.GITHUB_TOKEN || ''; + +// Rate-limit: small delay between requests to stay within limits +const DELAY_MS = GITHUB_TOKEN ? 100 : 1200; // faster when authenticated + +async function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function githubFetch(url) { + const headers = { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'altstack-metadata-fetcher', + }; + if (GITHUB_TOKEN) { + headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`; + } + + const response = await fetch(url, { headers }); + + if (response.status === 403 || response.status === 429) { + const resetHeader = response.headers.get('x-ratelimit-reset'); + const resetTime = resetHeader ? new Date(parseInt(resetHeader) * 1000).toLocaleTimeString() : 'unknown'; + console.error(`⚠️ Rate limited. Resets at ${resetTime}. Use GITHUB_TOKEN for higher limits.`); + return null; + } + + if (!response.ok) { + return null; + } + + return response.json(); +} + +async function fetchRepoMetadata(githubRepo) { + // Fetch repo info (stars, default branch) + const repoData = await githubFetch(`https://api.github.com/repos/${githubRepo}`); + if (!repoData) return null; + + const stars = repoData.stargazers_count; + const defaultBranch = repoData.default_branch || 'main'; + + // Fetch latest commit on default branch + const commitsData = await githubFetch( + `https://api.github.com/repos/${githubRepo}/commits?sha=${defaultBranch}&per_page=1` + ); + + let lastCommit = null; + if (commitsData && commitsData.length > 0) { + lastCommit = commitsData[0].commit?.committer?.date || commitsData[0].commit?.author?.date || null; + } + + return { stars, lastCommit }; +} + +async function main() { + if (!fs.existsSync(TOOLS_PATH)) { + console.error('❌ tools.json not found at:', TOOLS_PATH); + process.exit(1); + } + + const tools = JSON.parse(fs.readFileSync(TOOLS_PATH, 'utf8')); + + const reposToFetch = tools.filter(t => t.github_repo); + console.log(`📦 Found ${reposToFetch.length} tools with GitHub repos out of ${tools.length} total.`); + console.log(`🔑 Auth: ${GITHUB_TOKEN ? 'Token provided (5000 req/hr)' : 'No token (60 req/hr — set GITHUB_TOKEN for more)'}\n`); + + let updated = 0; + let failed = 0; + let unchanged = 0; + + for (const tool of tools) { + if (!tool.github_repo) continue; + + process.stdout.write(` ${tool.name} (${tool.github_repo}) ... `); + + const metadata = await fetchRepoMetadata(tool.github_repo); + if (!metadata) { + console.log('❌ failed'); + failed++; + await sleep(DELAY_MS); + continue; + } + + let changed = false; + + // Update stars + if (metadata.stars !== undefined && metadata.stars !== tool.stars) { + const oldStars = tool.stars || 0; + tool.stars = metadata.stars; + process.stdout.write(`★ ${oldStars}→${metadata.stars} `); + changed = true; + } + + // Update last_commit + if (metadata.lastCommit && metadata.lastCommit !== tool.last_commit) { + const oldDate = tool.last_commit ? tool.last_commit.slice(0, 10) : 'none'; + const newDate = metadata.lastCommit.slice(0, 10); + tool.last_commit = metadata.lastCommit; + process.stdout.write(`📅 ${oldDate}→${newDate} `); + changed = true; + } + + if (changed) { + console.log('✅ updated'); + updated++; + } else { + console.log('— no change'); + unchanged++; + } + + await sleep(DELAY_MS); + } + + // Write updated tools back + fs.writeFileSync(TOOLS_PATH, JSON.stringify(tools, null, 2) + '\n'); + + console.log(`\n✨ Done! ${updated} updated, ${unchanged} unchanged, ${failed} failed.`); +} + +main().catch(err => { + console.error('Fatal error:', err); + process.exit(1); +});