import dayjs from "dayjs"; import { Octokit } from "octokit"; import { compareSemVer, isValidSemVer } from "semver-parser"; import { fetchWithTimeout } from "@homarr/common"; import { logger } from "@homarr/log"; import { createChannelWithLatestAndEvents } from "@homarr/redis"; import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler"; import packageJson from "../../../package.json"; export const updateCheckerRequestHandler = createCachedRequestHandler({ queryKey: "homarr-update-checker", cacheDuration: dayjs.duration(1, "hour"), async requestAsync(_) { const octokit = new Octokit({ request: { fetch: fetchWithTimeout, }, }); const releases = await octokit.rest.repos.listReleases({ owner: "homarr-labs", repo: "homarr", }); const currentVersion = (packageJson as { version: string }).version; const availableReleases = []; for (const release of releases.data) { if (!isValidSemVer(release.tag_name)) { logger.warn(`Unable to parse semantic tag '${release.tag_name}'. Update check might not work.`); continue; } availableReleases.push(release); } const availableNewerReleases = availableReleases .filter((release) => compareSemVer(release.tag_name, currentVersion) > 0) .sort((releaseA, releaseB) => compareSemVer(releaseB.tag_name, releaseA.tag_name)); if (availableNewerReleases.length > 0) { logger.info( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion `Update checker found a new available version: ${availableReleases[0]!.tag_name}. Current version is ${currentVersion}`, ); } else { logger.debug(`Update checker did not find any available updates. Current version is ${currentVersion}`); } return { availableUpdates: availableNewerReleases.map((release) => ({ name: release.name, contentHtml: release.body_html, url: release.html_url, tagName: release.tag_name, })), }; }, createRedisChannel() { return createChannelWithLatestAndEvents<{ availableUpdates: { name: string | null; contentHtml?: string; url: string; tagName: string }[]; }>("homarr:update"); }, });