194 lines
4.9 KiB
TypeScript
194 lines
4.9 KiB
TypeScript
|
import * as plugins from './codefeed.plugins.js';
|
||
|
|
||
|
interface RepositoryOwner {
|
||
|
login: string;
|
||
|
}
|
||
|
|
||
|
interface Repository {
|
||
|
owner: RepositoryOwner;
|
||
|
name: string;
|
||
|
}
|
||
|
|
||
|
interface CommitAuthor {
|
||
|
date: string;
|
||
|
}
|
||
|
|
||
|
interface CommitDetail {
|
||
|
message: string;
|
||
|
author: CommitAuthor;
|
||
|
}
|
||
|
|
||
|
interface Commit {
|
||
|
sha: string;
|
||
|
commit: CommitDetail;
|
||
|
}
|
||
|
|
||
|
interface Tag {
|
||
|
commit?: {
|
||
|
sha?: string;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
interface RepoSearchResponse {
|
||
|
data: Repository[];
|
||
|
}
|
||
|
|
||
|
interface CommitResult {
|
||
|
baseUrl: string;
|
||
|
org: string;
|
||
|
repo: string;
|
||
|
timestamp: string;
|
||
|
hash: string;
|
||
|
commitMessage: string;
|
||
|
tagged: boolean;
|
||
|
}
|
||
|
|
||
|
export class CodeFeed {
|
||
|
private baseUrl: string;
|
||
|
private token?: string;
|
||
|
|
||
|
constructor(baseUrl: string, token?: string) {
|
||
|
this.baseUrl = baseUrl;
|
||
|
this.token = token;
|
||
|
console.log('CodeFeed initialized');
|
||
|
}
|
||
|
|
||
|
private async fetchAllRepositories(): Promise<Repository[]> {
|
||
|
let page = 1;
|
||
|
const allRepos: Repository[] = [];
|
||
|
|
||
|
while (true) {
|
||
|
const url = new URL(`${this.baseUrl}/api/v1/repos/search`);
|
||
|
url.searchParams.set('limit', '50');
|
||
|
url.searchParams.set('page', page.toString());
|
||
|
|
||
|
const resp = await fetch(url.href, {
|
||
|
headers: this.token ? { 'Authorization': `token ${this.token}` } : {}
|
||
|
});
|
||
|
|
||
|
if (!resp.ok) {
|
||
|
throw new Error(`Failed to fetch repositories: ${resp.statusText}`);
|
||
|
}
|
||
|
|
||
|
const data: RepoSearchResponse = await resp.json();
|
||
|
allRepos.push(...data.data);
|
||
|
|
||
|
if (data.data.length < 50) {
|
||
|
break;
|
||
|
}
|
||
|
page++;
|
||
|
}
|
||
|
|
||
|
return allRepos;
|
||
|
}
|
||
|
|
||
|
private async fetchTags(owner: string, repo: string): Promise<Set<string>> {
|
||
|
let page = 1;
|
||
|
const tags: Tag[] = [];
|
||
|
|
||
|
while (true) {
|
||
|
const url = new URL(`${this.baseUrl}/api/v1/repos/${owner}/${repo}/tags`);
|
||
|
url.searchParams.set('limit', '50');
|
||
|
url.searchParams.set('page', page.toString());
|
||
|
|
||
|
const resp = await fetch(url.href, {
|
||
|
headers: this.token ? { 'Authorization': `token ${this.token}` } : {}
|
||
|
});
|
||
|
|
||
|
if (!resp.ok) {
|
||
|
console.error(`Failed to fetch tags for ${owner}/${repo}: ${resp.status} ${resp.statusText} at ${url.href}`);
|
||
|
throw new Error(`Failed to fetch tags for ${owner}/${repo}: ${resp.statusText}`);
|
||
|
}
|
||
|
|
||
|
const data: Tag[] = await resp.json();
|
||
|
tags.push(...data);
|
||
|
|
||
|
if (data.length < 50) {
|
||
|
break;
|
||
|
}
|
||
|
page++;
|
||
|
}
|
||
|
|
||
|
const taggedCommitShas = new Set<string>();
|
||
|
for (const t of tags) {
|
||
|
if (t.commit?.sha) {
|
||
|
taggedCommitShas.add(t.commit.sha);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return taggedCommitShas;
|
||
|
}
|
||
|
|
||
|
private async fetchRecentCommitsForRepo(owner: string, repo: string): Promise<Commit[]> {
|
||
|
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||
|
let page = 1;
|
||
|
const recentCommits: Commit[] = [];
|
||
|
|
||
|
while (true) {
|
||
|
const url = new URL(`${this.baseUrl}/api/v1/repos/${owner}/${repo}/commits`);
|
||
|
url.searchParams.set('limit', '1');
|
||
|
url.searchParams.set('page', page.toString());
|
||
|
|
||
|
const resp = await fetch(url.href, {
|
||
|
headers: this.token ? { 'Authorization': `token ${this.token}` } : {}
|
||
|
});
|
||
|
if (!resp.ok) {
|
||
|
console.error(`Failed to fetch commits for ${owner}/${repo}: ${resp.status} ${resp.statusText} at ${url.href}`);
|
||
|
throw new Error(`Failed to fetch commits for ${owner}/${repo}: ${resp.statusText}`);
|
||
|
}
|
||
|
|
||
|
const data: Commit[] = await resp.json();
|
||
|
if (data.length === 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
for (const commit of data) {
|
||
|
const commitDate = new Date(commit.commit.author.date);
|
||
|
if (commitDate > twentyFourHoursAgo) {
|
||
|
recentCommits.push(commit);
|
||
|
} else {
|
||
|
// If we encounter a commit older than 24 hours, we can stop fetching more pages
|
||
|
return recentCommits;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
page++;
|
||
|
}
|
||
|
|
||
|
return recentCommits;
|
||
|
}
|
||
|
|
||
|
public async fetchAllCommitsFromInstance(): Promise<CommitResult[]> {
|
||
|
const repos = await this.fetchAllRepositories();
|
||
|
let allCommits: CommitResult[] = [];
|
||
|
|
||
|
for (const r of repos) {
|
||
|
const org = r.owner.login;
|
||
|
const repo = r.name;
|
||
|
console.log(`Processing repository ${org}/${repo}`);
|
||
|
|
||
|
try {
|
||
|
const taggedCommitShas = await this.fetchTags(org, repo);
|
||
|
const commits = await this.fetchRecentCommitsForRepo(org, repo);
|
||
|
console.log(`${org}/${repo} -> Found ${commits.length} commits`);
|
||
|
|
||
|
const formatted = commits.map((c): CommitResult => ({
|
||
|
baseUrl: this.baseUrl,
|
||
|
org,
|
||
|
repo,
|
||
|
timestamp: c.commit.author.date,
|
||
|
hash: c.sha,
|
||
|
commitMessage: c.commit.message,
|
||
|
tagged: taggedCommitShas.has(c.sha)
|
||
|
}));
|
||
|
|
||
|
allCommits.push(...formatted);
|
||
|
} catch (error: any) {
|
||
|
console.error(`Skipping repository ${org}/${repo} due to error:`, error.message);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return allCommits;
|
||
|
}
|
||
|
}
|