| |
|
|
| import { noStoreUrl } from "./constants.js"; |
|
|
| const LOCAL_VERSION_ROUTE = "/koolook/api/version"; |
| const FALLBACK_LATEST_RELEASE_API = |
| "https://api.github.com/repos/malkuthro/ComfyUI-Koolook/releases/latest"; |
| const RELEASES_URL = "https://github.com/malkuthro/ComfyUI-Koolook/releases"; |
|
|
| function releaseNotesUrl(version, rawUrl) { |
| const expected = `${RELEASES_URL}/tag/v${version}`; |
| if (typeof rawUrl !== "string") return expected; |
| try { |
| const parsed = new URL(rawUrl); |
| if ( |
| parsed.protocol === "https:" && |
| parsed.hostname === "github.com" && |
| parsed.pathname === `/malkuthro/ComfyUI-Koolook/releases/tag/v${version}` |
| ) { |
| return parsed.href; |
| } |
| } catch (_err) { |
| |
| } |
| return expected; |
| } |
|
|
| function parseVersion(value) { |
| if (typeof value !== "string") return null; |
| const match = value.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/); |
| if (!match) return null; |
| return { |
| parts: match.slice(1, 4).map((part) => Number(part)), |
| prerelease: match[4] || "", |
| }; |
| } |
|
|
| export function parseSemver(value) { |
| const parsed = parseVersion(value); |
| return parsed ? parsed.parts : null; |
| } |
|
|
| export function compareSemver(a, b) { |
| const av = parseVersion(a); |
| const bv = parseVersion(b); |
| if (!av || !bv) return 0; |
| for (let i = 0; i < 3; i += 1) { |
| if (av.parts[i] > bv.parts[i]) return 1; |
| if (av.parts[i] < bv.parts[i]) return -1; |
| } |
| if (av.prerelease && !bv.prerelease) return -1; |
| if (!av.prerelease && bv.prerelease) return 1; |
| return 0; |
| } |
|
|
| function normalizeLatestRelease(payload) { |
| if (!payload || typeof payload !== "object") return null; |
| const version = typeof payload.tag_name === "string" |
| ? payload.tag_name.replace(/^v/i, "") |
| : ""; |
| if (!parseSemver(version)) return null; |
| return { |
| version, |
| url: releaseNotesUrl(version, payload.html_url), |
| }; |
| } |
|
|
| export async function checkForUpdate(fetchImpl = fetch) { |
| try { |
| const localResponse = await fetchImpl(noStoreUrl(LOCAL_VERSION_ROUTE), { |
| cache: "no-store", |
| }); |
| if (!localResponse || !localResponse.ok) return null; |
| const local = await localResponse.json(); |
| const current = typeof local.version === "string" ? local.version : ""; |
| if (!parseSemver(current)) return null; |
|
|
| const latestUrl = typeof local.latestReleaseApiUrl === "string" && local.latestReleaseApiUrl |
| ? local.latestReleaseApiUrl |
| : FALLBACK_LATEST_RELEASE_API; |
| const latestResponse = await fetchImpl(latestUrl, { cache: "no-store" }); |
| if (!latestResponse || !latestResponse.ok) return null; |
| const latest = normalizeLatestRelease(await latestResponse.json()); |
| if (!latest || compareSemver(latest.version, current) <= 0) return null; |
|
|
| return { |
| current, |
| latest: latest.version, |
| url: latest.url, |
| }; |
| } catch (_err) { |
| return null; |
| } |
| } |
|
|
| export function renderUpdateFooter(footerEl, update) { |
| footerEl.textContent = ""; |
| footerEl.classList.remove("koolook-update-footer-visible"); |
| if (!update) return; |
|
|
| const label = document.createElement("span"); |
| label.textContent = "Update Available"; |
| const link = document.createElement("a"); |
| link.href = update.url; |
| link.target = "_blank"; |
| link.rel = "noopener noreferrer"; |
| link.textContent = "Release notes"; |
| link.title = `Kforge Labs ${update.latest} release notes`; |
| footerEl.append(label, link); |
| footerEl.classList.add("koolook-update-footer-visible"); |
| } |
|
|