import { APP_VERSION } from "./version"; // Detects whether a newer version has been pushed upstream by reading // src/lib/version.ts from the Gitea repo's default branch and comparing its // APP_VERSION to the running one. Self-contained — no OTM control plane needed. // Reuses the same GITEA_* env as the suggestions module. const GITEA_URL = process.env.GITEA_URL; const GITEA_TOKEN = process.env.GITEA_TOKEN; const GITEA_REPO = process.env.GITEA_REPO; const GITEA_BRANCH = process.env.GITEA_BRANCH || "master"; export type UpdateStatus = { version: string; latest: string | null; updateAvailable: boolean; }; /** Read APP_VERSION from src/lib/version.ts on the upstream branch. null on any failure. */ export async function fetchUpstreamVersion(): Promise { if (!GITEA_URL || !GITEA_TOKEN || !GITEA_REPO) return null; try { const url = `${GITEA_URL}/api/v1/repos/${GITEA_REPO}/contents/src/lib/version.ts?ref=${GITEA_BRANCH}`; const res = await fetch(url, { headers: { Authorization: `token ${GITEA_TOKEN}` }, cache: "no-store", }); if (!res.ok) return null; const json = await res.json(); if (!json?.content) return null; const decoded = Buffer.from( json.content, json.encoding === "base64" ? "base64" : "utf8" ).toString("utf8"); const m = decoded.match(/APP_VERSION\s*=\s*["']([^"']+)["']/); return m ? m[1] : null; } catch { return null; } } /** Numeric dotted compare. -1 if ab. */ export function compareVersions(a: string, b: string): number { const pa = a.split(".").map((n) => parseInt(n, 10) || 0); const pb = b.split(".").map((n) => parseInt(n, 10) || 0); const len = Math.max(pa.length, pb.length); for (let i = 0; i < len; i++) { const d = (pa[i] || 0) - (pb[i] || 0); if (d !== 0) return d > 0 ? 1 : -1; } return 0; } export async function getUpdateStatus(): Promise { const latest = await fetchUpstreamVersion(); return { version: APP_VERSION, latest, updateAvailable: latest != null && compareVersions(latest, APP_VERSION) > 0, }; }