forked from farhoodlabs/paperclip
e559218f98
Reintroduce Gitea/Forgejo as a skill import source on dev only, since the fork deploys against git.farh.net. Pasting a Gitea/Forgejo repo URL into the skills sidebar mirrors the existing GitHub experience: pin to a commit SHA, check for updates, read repo files. Server: new gitea-fetch.ts (URL builders, probe-cache helpers) and gitea-skills.ts (parse, probe, pin, tree, text, branch). Dispatch in readUrlSkillImports probes /api/v1/version and routes non-github.com hosts into the new readGiteaUrlSkillImports branch. updateStatus and readFile get a gitea arm alongside the github/skills_sh arm. Audit falls through to "remote not supported" the same way github does. UI: Server icon, Gitea source label, gitea in the "external" source class, Pin/Update UI gate widened to sourceType === "gitea". CLI help text updated. Existing github code is left byte-for-byte unchanged (wrapped in isGitHubDotCom) so dev <-> master syncs stay clean. PAT support, gitea portability descriptors, and gitea audit are deliberate follow-ups. Detection requires /api/v1/version to return Gitea-shaped JSON; the per-host result is cached for process lifetime with FIFO eviction at 1024 entries. Non-Gitea hosts fall through to the existing raw-markdown url branch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
import { unprocessable } from "../errors.js";
|
|
|
|
const PROBE_CACHE_MAX_ENTRIES = 1024;
|
|
|
|
/**
|
|
* Process-lifetime cache of Gitea/Forgejo probe results.
|
|
* Keyed by lowercased hostname. Positive and negative results are both cached
|
|
* to avoid re-probing the same host on every import. FIFO-evicted at
|
|
* PROBE_CACHE_MAX_ENTRIES to bound memory.
|
|
*/
|
|
export const giteaHostProbeCache = new Map<string, boolean>();
|
|
|
|
function evictProbeCacheIfFull() {
|
|
if (giteaHostProbeCache.size > PROBE_CACHE_MAX_ENTRIES) {
|
|
const oldestKey = giteaHostProbeCache.keys().next().value;
|
|
if (oldestKey !== undefined) giteaHostProbeCache.delete(oldestKey);
|
|
}
|
|
}
|
|
|
|
export function setGiteaHostProbe(hostname: string, isGitea: boolean) {
|
|
giteaHostProbeCache.set(hostname.toLowerCase(), isGitea);
|
|
evictProbeCacheIfFull();
|
|
}
|
|
|
|
export function getGiteaHostProbe(hostname: string): boolean | undefined {
|
|
return giteaHostProbeCache.get(hostname.toLowerCase());
|
|
}
|
|
|
|
/**
|
|
* Gitea/Forgejo API base. There is no dotcom short-circuit — every host uses
|
|
* the same /api/v1 path, including the public gitea.com instance.
|
|
*/
|
|
export function giteaApiBase(hostname: string) {
|
|
return `https://${hostname}/api/v1`;
|
|
}
|
|
|
|
/**
|
|
* Canonical raw-content URL for Gitea/Forgejo ≥ 1.18.
|
|
* Modern format: `https://{host}/{owner}/{repo}/raw/branch/{ref}/{path}`.
|
|
*/
|
|
export function resolveRawGiteaUrl(
|
|
hostname: string,
|
|
owner: string,
|
|
repo: string,
|
|
ref: string,
|
|
filePath: string,
|
|
) {
|
|
const p = filePath.replace(/^\/+/, "");
|
|
return `https://${hostname}/${owner}/${repo}/raw/branch/${ref}/${p}`;
|
|
}
|
|
|
|
/**
|
|
* Legacy raw-content URL for Gitea < 1.18 and some Forgejo setups.
|
|
* Format: `https://{host}/{owner}/{repo}/raw/{ref}/{path}`.
|
|
*/
|
|
export function resolveRawGiteaUrlLegacy(
|
|
hostname: string,
|
|
owner: string,
|
|
repo: string,
|
|
ref: string,
|
|
filePath: string,
|
|
) {
|
|
const p = filePath.replace(/^\/+/, "");
|
|
return `https://${hostname}/${owner}/${repo}/raw/${ref}/${p}`;
|
|
}
|
|
|
|
export async function giteaFetch(url: string, init?: RequestInit): Promise<Response> {
|
|
try {
|
|
return await fetch(url, init);
|
|
} catch {
|
|
throw unprocessable(
|
|
`Could not connect to ${new URL(url).hostname} — ensure the URL points to a Gitea/Forgejo instance`,
|
|
);
|
|
}
|
|
}
|