mirror of
https://github.com/luxfi/lux-apps.git
synced 2026-10-30 16:20:46 +00:00
218 lines
6.0 KiB
TypeScript
218 lines
6.0 KiB
TypeScript
import { config } from './config';
|
|
|
|
const ORG = process.env.GITHUB_ORG || config.org.github;
|
|
|
|
export interface GitHubRepo {
|
|
id: number;
|
|
name: string;
|
|
full_name: string;
|
|
description: string | null;
|
|
html_url: string;
|
|
homepage: string | null;
|
|
language: string | null;
|
|
stargazers_count: number;
|
|
forks_count: number;
|
|
watchers_count: number;
|
|
open_issues_count: number;
|
|
topics: string[];
|
|
fork: boolean;
|
|
archived: boolean;
|
|
disabled: boolean;
|
|
visibility: string;
|
|
default_branch: string;
|
|
has_pages: boolean;
|
|
has_wiki: boolean;
|
|
has_issues: boolean;
|
|
has_projects: boolean;
|
|
has_downloads: boolean;
|
|
license: { key: string; name: string; spdx_id: string } | null;
|
|
size: number;
|
|
updated_at: string;
|
|
created_at: string;
|
|
pushed_at: string;
|
|
}
|
|
|
|
export async function fetchAllRepos(): Promise<GitHubRepo[]> {
|
|
const allRepos: GitHubRepo[] = [];
|
|
let page = 1;
|
|
const perPage = 100;
|
|
|
|
console.log(`Fetching all repos from ${ORG}...`);
|
|
|
|
while (true) {
|
|
const url = `https://api.github.com/orgs/${ORG}/repos?per_page=${perPage}&page=${page}&sort=updated&direction=desc`;
|
|
|
|
const headers: HeadersInit = {
|
|
Accept: 'application/vnd.github.v3+json',
|
|
'User-Agent': `${ORG}-landing`,
|
|
};
|
|
|
|
if (process.env.GITHUB_TOKEN) {
|
|
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
}
|
|
|
|
const response = await fetch(url, { headers, next: { revalidate: 3600 } });
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 403) {
|
|
console.warn('GitHub API rate limit reached. Using cached data if available.');
|
|
break;
|
|
}
|
|
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
const repos: GitHubRepo[] = await response.json();
|
|
console.log(`Page ${page}: fetched ${repos.length} repos`);
|
|
|
|
if (repos.length === 0) break;
|
|
|
|
allRepos.push(...repos);
|
|
page++;
|
|
}
|
|
|
|
console.log(`Total repos fetched: ${allRepos.length}`);
|
|
return allRepos;
|
|
}
|
|
|
|
export function filterAndSortRepos(repos: GitHubRepo[]): GitHubRepo[] {
|
|
return repos
|
|
.filter((repo) => !repo.archived && !repo.disabled && repo.visibility === 'public')
|
|
.sort((a, b) => b.stargazers_count - a.stargazers_count);
|
|
}
|
|
|
|
export function calculateStats(repos: GitHubRepo[]) {
|
|
return {
|
|
totalRepos: repos.length,
|
|
totalStars: repos.reduce((sum, repo) => sum + repo.stargazers_count, 0),
|
|
totalForks: repos.reduce((sum, repo) => sum + repo.forks_count, 0),
|
|
languages: [...new Set(repos.map((r) => r.language).filter(Boolean))].length,
|
|
};
|
|
}
|
|
|
|
export function categorizeRepos(repos: GitHubRepo[]) {
|
|
const categories: Record<string, GitHubRepo[]> = {
|
|
'Blockchain Core': [],
|
|
'SDKs & Libraries': [],
|
|
'Web & Frontend': [],
|
|
'Infrastructure': [],
|
|
'Documentation': [],
|
|
'Other': [],
|
|
};
|
|
|
|
for (const repo of repos) {
|
|
const name = repo.name.toLowerCase();
|
|
const topics = repo.topics.map((t) => t.toLowerCase());
|
|
const lang = repo.language?.toLowerCase() || '';
|
|
|
|
if (
|
|
name.includes('node') ||
|
|
name.includes('chain') ||
|
|
name.includes('consensus') ||
|
|
name.includes('vm') ||
|
|
topics.includes('blockchain')
|
|
) {
|
|
categories['Blockchain Core'].push(repo);
|
|
} else if (
|
|
name.includes('sdk') ||
|
|
name.includes('lib') ||
|
|
name.includes('client') ||
|
|
topics.includes('sdk') ||
|
|
topics.includes('library')
|
|
) {
|
|
categories['SDKs & Libraries'].push(repo);
|
|
} else if (
|
|
lang === 'typescript' ||
|
|
lang === 'javascript' ||
|
|
name.includes('web') ||
|
|
name.includes('ui') ||
|
|
name.includes('app') ||
|
|
name.includes('frontend') ||
|
|
topics.includes('react') ||
|
|
topics.includes('nextjs')
|
|
) {
|
|
categories['Web & Frontend'].push(repo);
|
|
} else if (
|
|
name.includes('infra') ||
|
|
name.includes('deploy') ||
|
|
name.includes('docker') ||
|
|
name.includes('ops') ||
|
|
name.includes('ci') ||
|
|
name.includes('runner') ||
|
|
topics.includes('devops') ||
|
|
topics.includes('infrastructure')
|
|
) {
|
|
categories['Infrastructure'].push(repo);
|
|
} else if (
|
|
name.includes('doc') ||
|
|
name.includes('guide') ||
|
|
name.includes('tutorial') ||
|
|
name.includes('example') ||
|
|
topics.includes('documentation')
|
|
) {
|
|
categories['Documentation'].push(repo);
|
|
} else {
|
|
categories['Other'].push(repo);
|
|
}
|
|
}
|
|
|
|
return Object.fromEntries(Object.entries(categories).filter(([, repos]) => repos.length > 0));
|
|
}
|
|
|
|
export function formatCount(num: number): string {
|
|
if (num >= 1000) {
|
|
return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
|
}
|
|
return num.toString();
|
|
}
|
|
|
|
export function formatRelativeTime(dateString: string): string {
|
|
const date = new Date(dateString);
|
|
const now = new Date();
|
|
const diffMs = now.getTime() - date.getTime();
|
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays === 0) return 'today';
|
|
if (diffDays === 1) return 'yesterday';
|
|
if (diffDays < 7) return `${diffDays} days ago`;
|
|
if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
|
|
if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`;
|
|
return `${Math.floor(diffDays / 365)} years ago`;
|
|
}
|
|
|
|
export function getPagesUrl(repo: GitHubRepo): string | null {
|
|
if (!repo.has_pages) return null;
|
|
const [org, name] = repo.full_name.split('/');
|
|
if (name === `${org}.github.io`) {
|
|
return `https://${org}.github.io`;
|
|
}
|
|
return `https://${org}.github.io/${name}`;
|
|
}
|
|
|
|
export function getLanguageColor(language: string | null): string {
|
|
const colors: Record<string, string> = {
|
|
TypeScript: '#3178c6',
|
|
JavaScript: '#f1e05a',
|
|
Python: '#3572A5',
|
|
Go: '#00ADD8',
|
|
Rust: '#dea584',
|
|
Java: '#b07219',
|
|
'C++': '#f34b7d',
|
|
C: '#555555',
|
|
Ruby: '#701516',
|
|
Swift: '#F05138',
|
|
Kotlin: '#A97BFF',
|
|
Scala: '#c22d40',
|
|
Shell: '#89e051',
|
|
Dockerfile: '#384d54',
|
|
HTML: '#e34c26',
|
|
CSS: '#563d7c',
|
|
SCSS: '#c6538c',
|
|
Vue: '#41b883',
|
|
Svelte: '#ff3e00',
|
|
Solidity: '#AA6746',
|
|
Move: '#4a137f',
|
|
Cairo: '#ff4f00',
|
|
};
|
|
return colors[language || ''] || '#8b8b8b';
|
|
}
|