import { config } from 'dotenv'; import yargs from 'yargs'; import path from 'path'; import fs from 'fs'; import assert from 'assert'; import { hideBin } from 'yargs/helpers'; import axios from 'axios'; import { Octokit } from '@octokit/rest'; // Load environment variables from .env file config(); const GITEA_BASE_URL = 'https://git.vdb.to'; interface RepoRecord { type: string version: string schema: string name: string url: string clone_url: string owner: { name: string type: 'Organization' | 'User' url: string } is_private: boolean is_archived: boolean default_branch: string latest_tag: { tag_name: string url: string } | null is_fork: boolean fork_source: { name: string url: string } | null license: { name: string spdx_id: string } | null } async function main () { const argv = getArgs(); let repoRecord: RepoRecord; if (argv.repoUrl.includes('github.com')) { repoRecord = await createRepoRecord(argv.repoUrl); } else { repoRecord = await createGiteaRepoRecord(argv.repoUrl); } const jsonData = JSON.stringify(repoRecord, null, 2); const recordsDir = path.resolve(argv.recordsDir); const recordFile = `${recordsDir}/${repoRecord.name}.json`; fs.writeFileSync(recordFile, jsonData, 'utf8'); console.log(`Repo record written to ${recordFile}`); } async function createRepoRecord (repoUrl: string): Promise { const githubPAT = process.env.GITHUB_TOKEN; assert(githubPAT, 'GITHUB_TOKEN not provided'); const octokit = new Octokit({ auth: githubPAT }); const { owner, repo } = extractOwnerAndRepo(repoUrl); // fetch repo details const data = await getRepoDetails(octokit, owner, repo); const repoRecord: RepoRecord = { type: 'RepositoryRecord', version: '0.1.0', // fetch from registry and increment if exists schema: '', name: data.name, url: data.html_url, clone_url: data.clone_url, owner: { name: data.owner.login, type: data.owner.type, url: data.owner.html_url }, is_private: data.private, is_archived: data.archived, default_branch: data.default_branch, latest_tag: null, is_fork: data.fork, fork_source: null, license: null }; // populate fork source if (data.fork) { repoRecord.fork_source = { name: data.source.name, url: data.source.html_url }; } // populate license if (data.license) { repoRecord.license = { name: data.license.name, spdx_id: data.license.spdx_id }; } // fetch and populate latest_tag const latestTag = await getRepoLatestTag(octokit, owner, repo); if (latestTag) { repoRecord.latest_tag = { tag_name: latestTag.tag_name, url: latestTag.html_url }; } return repoRecord; } async function createGiteaRepoRecord (repoUrl: string): Promise { const giteaPAT = process.env.GITEA_TOKEN; assert(giteaPAT, 'GITEA_TOKEN not provided'); const { owner, repo } = extractOwnerAndRepo(repoUrl); // fetch repo details const data = await getGiteaRepoDetails(giteaPAT, owner, repo); const repoRecord: RepoRecord = { type: 'RepositoryRecord', version: '0.1.0', // fetch from registry and increment if exists schema: '', name: data.name, url: data.html_url, clone_url: data.clone_url, owner: { name: data.owner.login, // type: data.owner.type, type: 'Organization', url: data.owner.html_url }, is_private: data.private, is_archived: data.archived, default_branch: data.default_branch, latest_tag: null, is_fork: data.fork, fork_source: null, license: null }; // populate fork source if (data.fork) { repoRecord.fork_source = { name: data.parent.name, url: data.parent.html_url }; } // TODO: populate license // fetch and populate latest_tag const latestTag = await getGiteaRepoLatestTag(giteaPAT, owner, repo); if (latestTag) { repoRecord.latest_tag = { tag_name: latestTag.tag_name, url: latestTag.html_url }; } return repoRecord; } // async function getRepoDetails (octokit: Octokit, repoUrl: string): Promise { async function getRepoDetails (octokit: Octokit, owner: string, repo: string): Promise { try { const response = await octokit.repos.get({ owner: owner, repo: repo }); return response.data; } catch (error) { console.error('Error fetching repository:', error); } } async function getGiteaRepoDetails (token: string, owner: string, repo: string): Promise { try { const response = await axios.get(`${GITEA_BASE_URL}/api/v1/repos/${owner}/${repo}`, { headers: { Authorization: `token ${token}` } }); return response.data; } catch (error) { console.error('Error fetching repository:', error); } } async function getRepoLatestTag (octokit: Octokit, owner: string, repo: string): Promise { try { const response = await octokit.repos.getLatestRelease({ owner: owner, repo: repo }); return response.data; } catch (error: any) { if ((error as Error).message.includes('Not Found')) { return null; } console.error('Error fetching latest release:', error); } } async function getGiteaRepoLatestTag (token: string, owner: string, repo: string): Promise { try { const response = await axios.get(`${GITEA_BASE_URL}/api/v1/repos/${owner}/${repo}/releases/latest`, { headers: { Authorization: `token ${token}` } }); return response.data; } catch (error: any) { if ((error as Error).message.includes('code 404')) { return null; } console.error('Error fetching latest release:', error); } } function extractOwnerAndRepo (url: string): { owner: string; repo: string } { // eslint-disable-next-line no-useless-escape const match = url.match(/(?:github\.com|git\.vdb\.to)\/([^\/]+)\/([^\/]+)(\/|$)/); if (!match) { throw new Error(`Unable to extract repo owner and name from the given URL: ${url}`); } return { owner: match[1], repo: match[2] }; } function getArgs (): any { return yargs(hideBin(process.argv)).parserConfiguration({ 'parse-numbers': false }).usage('Usage: $0 [options]') .option('config', { alias: 'c', describe: 'Config', type: 'string' }) .option('repoUrl', { alias: 'r', describe: 'Repository URL', type: 'string', demandOption: true }) .option('recordsDir', { alias: 'd', describe: 'Directory to export the repo record to', type: 'string', demandOption: true }) .help().argv; } main() .catch(err => { console.error(err); });