mirror of
https://github.com/altstackHQ/altstack-data.git
synced 2026-04-17 19:53:12 +02:00
feat: add auto-fetch script for GitHub stars and last_commit metadata
This commit is contained in:
144
scripts/fetch-github-metadata.js
Normal file
144
scripts/fetch-github-metadata.js
Normal file
@@ -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);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user