mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 01:35:39 -04:00
- 211 tests across 15 files covering services (tracking, discovery, metadata, backup, credits, webhooks, settings, update-check, colors, person), utilities (config, cron, providers, title-theme), and TMDB image URL helpers - `lib/test-preload.ts` + `bunfig.toml` wire up a global in-memory SQLite DB with migrations for all DB-backed tests - `lib/test-utils.ts` provides `clearAllTables()` and seed helpers (insertUser, insertTitle, insertTvShow, insertMovieWatch, etc.) - Export `getBackupSource`, `isKnownBackup`, `isValidBackupFilename`, `buildBackupCron`, `performUpdateCheck` internals for direct testing - GitHub Actions workflow runs `bun test --coverage` on push/PR to main
105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
import { createLogger } from "@/lib/logger";
|
|
import { getSetting, setSetting } from "@/lib/services/settings";
|
|
|
|
const APP_VERSION = process.env.APP_VERSION || "0.0.0";
|
|
|
|
const log = createLogger("update-check");
|
|
|
|
const GITHUB_RELEASES_URL =
|
|
"https://api.github.com/repos/jakejarvis/sofa/releases/latest";
|
|
const CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
|
|
export interface UpdateCheckResult {
|
|
updateAvailable: boolean;
|
|
currentVersion: string;
|
|
latestVersion: string | null;
|
|
releaseUrl: string | null;
|
|
lastCheckedAt: string | null;
|
|
}
|
|
|
|
export function isUpdateCheckEnabled(): boolean {
|
|
const setting = getSetting("updateCheckEnabled");
|
|
// Default to true when no setting exists
|
|
return setting !== "false";
|
|
}
|
|
|
|
/** @internal Returns true if `latest` is strictly newer than `current` using semver comparison. */
|
|
export function isNewerVersion(latest: string, current: string): boolean {
|
|
const parse = (v: string) => v.replace(/^v/, "").split(".").map(Number);
|
|
const [lMajor = 0, lMinor = 0, lPatch = 0] = parse(latest);
|
|
const [cMajor = 0, cMinor = 0, cPatch = 0] = parse(current);
|
|
if (lMajor !== cMajor) return lMajor > cMajor;
|
|
if (lMinor !== cMinor) return lMinor > cMinor;
|
|
return lPatch > cPatch;
|
|
}
|
|
|
|
export function getCachedUpdateCheck(): UpdateCheckResult {
|
|
const latestVersion = getSetting("updateCheckLatestVersion");
|
|
const releaseUrl = getSetting("updateCheckReleaseUrl");
|
|
const lastCheckedAt = getSetting("updateCheckLastCheckedAt");
|
|
|
|
return {
|
|
updateAvailable: latestVersion
|
|
? isNewerVersion(latestVersion, APP_VERSION)
|
|
: false,
|
|
currentVersion: APP_VERSION,
|
|
latestVersion,
|
|
releaseUrl,
|
|
lastCheckedAt,
|
|
};
|
|
}
|
|
|
|
export async function performUpdateCheck(): Promise<UpdateCheckResult> {
|
|
if (!isUpdateCheckEnabled()) {
|
|
return {
|
|
updateAvailable: false,
|
|
currentVersion: APP_VERSION,
|
|
latestVersion: null,
|
|
releaseUrl: null,
|
|
lastCheckedAt: null,
|
|
};
|
|
}
|
|
|
|
// Respect cache interval
|
|
const lastChecked = getSetting("updateCheckLastCheckedAt");
|
|
if (lastChecked) {
|
|
const elapsed = Date.now() - new Date(lastChecked).getTime();
|
|
if (elapsed < CHECK_INTERVAL_MS) {
|
|
return getCachedUpdateCheck();
|
|
}
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(GITHUB_RELEASES_URL, {
|
|
headers: {
|
|
Accept: "application/vnd.github.v3+json",
|
|
"User-Agent": "sofa-update-check",
|
|
},
|
|
signal: AbortSignal.timeout(10_000),
|
|
});
|
|
if (!res.ok) throw new Error(`GitHub API ${res.status}`);
|
|
|
|
const data = (await res.json()) as { tag_name: string; html_url: string };
|
|
const version = data.tag_name.replace(/^v/, "");
|
|
|
|
setSetting("updateCheckLatestVersion", version);
|
|
setSetting("updateCheckReleaseUrl", data.html_url);
|
|
setSetting("updateCheckLastCheckedAt", new Date().toISOString());
|
|
|
|
log.info(
|
|
`Update check complete: current=${APP_VERSION}, latest=${version}`,
|
|
);
|
|
|
|
return {
|
|
updateAvailable: isNewerVersion(version, APP_VERSION),
|
|
currentVersion: APP_VERSION,
|
|
latestVersion: version,
|
|
releaseUrl: data.html_url,
|
|
lastCheckedAt: new Date().toISOString(),
|
|
};
|
|
} catch (err) {
|
|
log.warn("Update check failed:", err);
|
|
return getCachedUpdateCheck();
|
|
}
|
|
}
|