818a8eade8
The skills source pipeline was hardcoded to GitHub conventions, so even
though the UI now accepts non-GitHub URLs, the server couldn't actually
fetch from anywhere else.
- github-fetch.ts: dispatch by host family (github.com → GitHub API +
raw.githubusercontent.com; everything else → Gitea/Forgejo API v1 +
/api/v1/repos/.../media for raw content).
- parseGitHubSourceUrl: also accept Gitea/Forgejo web URLs
(/{owner}/{repo}/src/{branch|commit|tag}/{ref}/{path}).
- routes/company-skills.ts: drop the hostname='github.com' gate in
deriveTrackedSkillRef so non-GitHub skills are still tracked.
- Generalize user-facing strings ('GitHub PAT' → 'PAT', 'GitHub source URL'
→ 'Source URL', etc.).
GitHub Enterprise (was assumed by '/api/v3') is no longer a special case —
non-github.com hosts are treated as Gitea/Forgejo. If GHE support is needed
later, add a per-source host-family override.
39 lines
1.3 KiB
TypeScript
39 lines
1.3 KiB
TypeScript
import { unprocessable } from "../errors.js";
|
|
|
|
export type GitHostFamily = "github" | "gitea";
|
|
|
|
export function inferGitHostFamily(hostname: string): GitHostFamily {
|
|
const h = hostname.toLowerCase();
|
|
if (h === "github.com" || h === "www.github.com") return "github";
|
|
return "gitea";
|
|
}
|
|
|
|
export function gitHubApiBase(hostname: string) {
|
|
return inferGitHostFamily(hostname) === "github"
|
|
? "https://api.github.com"
|
|
: `https://${hostname}/api/v1`;
|
|
}
|
|
|
|
export function resolveRawGitHubUrl(hostname: string, owner: string, repo: string, ref: string, filePath: string) {
|
|
const p = filePath.replace(/^\/+/, "");
|
|
if (inferGitHostFamily(hostname) === "github") {
|
|
return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${p}`;
|
|
}
|
|
return `https://${hostname}/api/v1/repos/${owner}/${repo}/media/${p}?ref=${encodeURIComponent(ref)}`;
|
|
}
|
|
|
|
export async function ghFetch(url: string, init?: RequestInit, authToken?: string): Promise<Response> {
|
|
const headers = new Headers(init?.headers);
|
|
if (authToken) {
|
|
headers.set("Authorization", `Bearer ${authToken}`);
|
|
}
|
|
try {
|
|
return await fetch(url, { ...init, headers, redirect: authToken ? "manual" : "follow" });
|
|
} catch {
|
|
const hostname = (() => {
|
|
try { return new URL(url).hostname; } catch { return url; }
|
|
})();
|
|
throw unprocessable(`Could not connect to ${hostname}`);
|
|
}
|
|
}
|