import { readJsonFile } from "./storage.js"; import { getCached, setCached } from "./cache.js"; export function parseRepoEntry(entry) { if (typeof entry === "string") { return { provider: "github", ownerRepo: entry, baseUrl: "https://github.com" }; } const provider = (entry?.provider || "github").toLowerCase(); const ownerRepo = entry?.repo || entry?.ownerRepo || ""; const baseUrl = entry?.baseUrl || (provider === "github" ? "https://github.com" : ""); return { provider, ownerRepo, baseUrl }; } export function normalizeRepoInput(input, extras = {}) { if (typeof input === "string") { return normalizeRepoInput({ repo: input }, extras); } const source = input && typeof input === "object" ? input : {}; const repo = source.repo || source.ownerRepo || source.fullName || extras.repo; if (!repo) { return null; } const provider = (source.provider || extras.provider || "github").toLowerCase(); const normalized = { repo, provider }; const baseUrl = source.baseUrl || extras.baseUrl || (provider === "github" ? "https://github.com" : undefined); if (baseUrl) { normalized.baseUrl = baseUrl; } return normalized; } export async function readRepos(reposFile) { const parsed = await readJsonFile(reposFile, []); return Array.isArray(parsed) ? parsed : []; } async function fetchJson(url, cacheKey, opts = {}) { const cached = getCached(cacheKey); if (cached) return cached; const headers = { "User-Agent": "siti-plugin-repo", Accept: "application/json" }; if (!opts.provider || opts.provider === "github") { headers.Accept = "application/vnd.github+json"; } const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`${opts.provider || "git"} request failed for ${url}`); } const data = await response.json().catch(async () => { const text = await response.text(); try { return JSON.parse(text); } catch { return null; } }); setCached(cacheKey, data); return data; } export async function fetchRepo(entry) { const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry); const cacheKey = `repo:${provider}:${ownerRepo}`; const cached = getCached(cacheKey); if (cached) return cached; let data; if (provider === "gitea") { const url = `${baseUrl.replace(/\/$/, "")}/api/v1/repos/${ownerRepo}`; data = await fetchJson(url, `repo-raw:${provider}:${ownerRepo}`, { provider }); const mapped = { fullName: data.full_name || `${ownerRepo}`, name: data.name || ownerRepo.split("/")[1] || ownerRepo, description: data.description || null, repoUrl: `${baseUrl.replace(/\/$/, "")}/${ownerRepo}`, defaultBranch: data.default_branch || "main", stars: data.stargazers_count || data.watchers || 0, forks: data.forks_count || data.forks || 0, issues: data.open_issues_count || 0, updatedAt: data.updated_at || data.updated || null, topics: data.topics || [], provider, ownerRepo, baseUrl }; setCached(cacheKey, mapped); return mapped; } data = await fetchJson(`https://api.github.com/repos/${ownerRepo}`, `repo-raw:github:${ownerRepo}`, { provider: "github" }); const mapped = { fullName: data.full_name, name: data.name, description: data.description, repoUrl: data.html_url, defaultBranch: data.default_branch, stars: data.stargazers_count, forks: data.forks_count, issues: data.open_issues_count, updatedAt: data.updated_at, topics: data.topics || [], provider, ownerRepo, baseUrl: "https://github.com" }; setCached(cacheKey, mapped); return mapped; } export async function fetchManifest(entry, defaultBranch) { const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry); const cacheKey = `manifest:${provider}:${ownerRepo}`; const cached = getCached(cacheKey); if (cached) return cached; const branches = [defaultBranch, "main", "master"].filter(Boolean); const [owner, repo] = ownerRepo.split("/"); for (const branch of branches) { let url; if (provider === "gitea") { url = `${baseUrl.replace(/\/$/, "")}/repos/${owner}/${repo}/raw/${branch}/manifest.json`; } else { url = `https://raw.githubusercontent.com/${ownerRepo}/${branch}/manifest.json`; } const response = await fetch(url, { headers: { "User-Agent": "siti-plugin-repo" } }); if (response.ok) { const manifest = await response.json().catch(() => null); setCached(cacheKey, manifest); return manifest; } } return null; } export async function fetchReleases(entry) { const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry); if (provider === "gitea") { const url = `${baseUrl.replace(/\/$/, "")}/api/v1/repos/${ownerRepo}/releases?limit=5`; const data = await fetchJson(url, `releases:${provider}:${ownerRepo}`, { provider }); return Array.isArray(data) ? data.map((release) => ({ tag: release.tag_name || release.name, name: release.name || release.tag_name, url: release.html_url || `${baseUrl.replace(/\/$/, "")}/${ownerRepo}/releases`, publishedAt: release.published_at || release.created_at })) : []; } const data = await fetchJson( `https://api.github.com/repos/${ownerRepo}/releases?per_page=5`, `releases:github:${ownerRepo}`, { provider: "github" } ); return Array.isArray(data) ? data.map((release) => ({ tag: release.tag_name, name: release.name || release.tag_name, url: release.html_url, publishedAt: release.published_at })) : []; } export async function fetchCommits(entry) { const { provider, ownerRepo, baseUrl } = parseRepoEntry(entry); if (provider === "gitea") { const url = `${baseUrl.replace(/\/$/, "")}/api/v1/repos/${ownerRepo}/commits?limit=5`; const data = await fetchJson(url, `commits:${provider}:${ownerRepo}`, { provider }); return Array.isArray(data) ? data.map((commit) => ({ sha: commit.sha, message: commit.commit?.message || commit.message, author: commit.commit?.author?.name || commit.author?.name, date: commit.commit?.author?.date || commit.author?.date, url: commit.html_url || `${baseUrl.replace(/\/$/, "")}/${ownerRepo}/commit/${commit.sha}` })) : []; } const data = await fetchJson( `https://api.github.com/repos/${ownerRepo}/commits?per_page=5`, `commits:github:${ownerRepo}`, { provider: "github" } ); return Array.isArray(data) ? data.map((commit) => ({ sha: commit.sha, message: commit.commit?.message, author: commit.commit?.author?.name, date: commit.commit?.author?.date, url: commit.html_url })) : []; }