type ReleaseAsset = { name?: string; browser_download_url?: string; }; type Release = { draft?: boolean; prerelease?: boolean; html_url?: string; tag_name?: string; assets?: ReleaseAsset[]; }; type Repo = { stargazers_count?: number; }; const FALLBACK_RELEASE = "https://github.com/different-ai/openwork/releases"; const formatCompact = (value: number) => { try { return new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 }).format(value); } catch { return String(value); } }; const selectAsset = ( assets: ReleaseAsset[], extensions: string[], keywords: string[] = [] ) => { const matches = assets.filter((asset) => { if (!asset?.name || !asset?.browser_download_url) return false; const name = asset.name.toLowerCase(); const extensionMatch = extensions.some((ext) => name.endsWith(ext)); const keywordMatch = keywords.length === 0 || keywords.some((key) => name.includes(key)); return extensionMatch && keywordMatch; }); if (matches.length === 0) return null; return ( matches.find((asset) => asset.name?.toLowerCase().includes("adhoc")) || matches.find((asset) => asset.name?.toLowerCase().includes("universal")) || matches.find((asset) => asset.name?.toLowerCase().includes("aarch64")) || matches[0] ); }; const fetchJson = async (url: string): Promise => { try { const response = await fetch(url, { headers: { Accept: "application/vnd.github+json" }, next: { revalidate: 60 * 60 } }); if (!response.ok) return null; return (await response.json()) as T; } catch { return null; } }; export const getGithubData = async () => { const [repo, releases] = await Promise.all([ fetchJson("https://api.github.com/repos/different-ai/openwork"), fetchJson( "https://api.github.com/repos/different-ai/openwork/releases?per_page=50" ) ]); const stars = typeof repo?.stargazers_count === "number" ? formatCompact(repo.stargazers_count) : "—"; const releaseList = Array.isArray(releases) ? releases : []; const hasDownloadAsset = (release: Release) => { const assets = Array.isArray(release?.assets) ? release.assets : []; return assets.some((asset) => asset?.browser_download_url); }; const hasWindowsDesktopAsset = (release: Release) => { const assets = Array.isArray(release?.assets) ? release.assets : []; return assets.some((asset) => { const name = String(asset?.name || "").toLowerCase(); return name.startsWith("openwork-desktop-windows-") && (name.endsWith(".msi") || name.endsWith(".exe")); }); }; const isStableDesktopRelease = (release: Release) => { if (!release || release.draft || release.prerelease) return false; const tag = String(release.tag_name || "").trim(); if (!/^v\d+\.\d+\.\d+([.-][0-9A-Za-z.-]+)?$/.test(tag)) return false; const assets = Array.isArray(release.assets) ? release.assets : []; return assets.some((asset) => { const name = String(asset?.name || "").toLowerCase(); return name.startsWith("openwork-desktop-"); }); }; const pick = releaseList.find((release) => isStableDesktopRelease(release)) || releaseList.find((release) => !release?.draft && hasDownloadAsset(release)); const windowsPick = releaseList.find((release) => isStableDesktopRelease(release) && hasWindowsDesktopAsset(release)) || releaseList.find((release) => !release?.draft && hasWindowsDesktopAsset(release)); const assets = Array.isArray(pick?.assets) ? pick.assets : []; const releaseUrl = pick?.html_url || FALLBACK_RELEASE; const windowsAssets = Array.isArray(windowsPick?.assets) ? windowsPick.assets : assets; const windowsReleaseUrl = windowsPick?.html_url || releaseUrl; const dmg = selectAsset(assets, [".dmg"]); const exe = selectAsset(windowsAssets, [".exe", ".msi"], ["win", "windows"]); const appImage = selectAsset(assets, [".appimage"], ["linux"]); const macosApple = selectAsset(assets, [".dmg"], ["darwin-aarch64"]); const macosIntel = selectAsset(assets, [".dmg"], ["darwin-x64"]); const windowsX64 = selectAsset(windowsAssets, [".msi", ".exe"], ["windows-x64"]) || exe; const linuxDebX64 = selectAsset(assets, [".deb"], ["linux-amd64", "linux-x64"]); const linuxDebArm64 = selectAsset(assets, [".deb"], ["linux-arm64", "linux-aarch64"]); const linuxRpmX64 = selectAsset(assets, [".rpm"], ["linux-x86_64", "linux-amd64"]); const linuxRpmArm64 = selectAsset(assets, [".rpm"], ["linux-aarch64", "linux-arm64"]); return { stars, releaseUrl, releaseTag: pick?.tag_name || "", downloads: { macos: dmg?.browser_download_url || FALLBACK_RELEASE, windows: exe?.browser_download_url || FALLBACK_RELEASE, linux: appImage?.browser_download_url || linuxDebX64?.browser_download_url || linuxRpmX64?.browser_download_url || FALLBACK_RELEASE }, installers: { macos: { appleSilicon: macosApple?.browser_download_url || dmg?.browser_download_url || releaseUrl, intel: macosIntel?.browser_download_url || dmg?.browser_download_url || releaseUrl }, windows: { x64: windowsX64?.browser_download_url || windowsReleaseUrl }, linux: { aur: "https://aur.archlinux.org/packages/openwork", debX64: linuxDebX64?.browser_download_url || releaseUrl, debArm64: linuxDebArm64?.browser_download_url || releaseUrl, rpmX64: linuxRpmX64?.browser_download_url || releaseUrl, rpmArm64: linuxRpmArm64?.browser_download_url || releaseUrl, appImage: appImage?.browser_download_url || releaseUrl } } }; };