fix(website): fetch release version at runtime with asset validation
Move release version resolution from build-time (vite.config.ts) to runtime (server function on Cloudflare Workers). The server function fetches GitHub releases, filters out prereleases/drafts, and only returns a version that has all required assets (Mac DMG, Linux AppImage, Windows exe). Uses Cloudflare's cf.cacheEverything with a 60s TTL to avoid hitting GitHub on every request. This fixes a race condition where the website would deploy and show download links for a new release before the desktop build assets were actually uploaded, resulting in 404s for users.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { createServerFn } from "@tanstack/react-start";
|
||||
import websitePackage from "../package.json";
|
||||
|
||||
interface GitHubAsset {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface GitHubRelease {
|
||||
tag_name: string;
|
||||
assets: GitHubAsset[];
|
||||
prerelease: boolean;
|
||||
draft: boolean;
|
||||
}
|
||||
|
||||
const REQUIRED_ASSET_PATTERNS = [
|
||||
/Paseo-.*-arm64\.dmg$/, // Mac Apple Silicon
|
||||
/Paseo-.*-x86_64\.AppImage$/, // Linux AppImage
|
||||
/Paseo-Setup-.*\.exe$/, // Windows
|
||||
];
|
||||
|
||||
function hasRequiredAssets(release: GitHubRelease): boolean {
|
||||
return REQUIRED_ASSET_PATTERNS.every((pattern) =>
|
||||
release.assets.some((asset) => pattern.test(asset.name)),
|
||||
);
|
||||
}
|
||||
|
||||
function versionFromTag(tag: string): string {
|
||||
return tag.replace(/^v/, "");
|
||||
}
|
||||
|
||||
const GITHUB_RELEASES_URL =
|
||||
"https://api.github.com/repos/getpaseo/paseo/releases?per_page=10";
|
||||
|
||||
async function fetchLatestReadyRelease(): Promise<string> {
|
||||
const fallback = websitePackage.version.replace(/-.*$/, "");
|
||||
|
||||
try {
|
||||
const res = await fetch(GITHUB_RELEASES_URL, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
"User-Agent": "paseo-website",
|
||||
},
|
||||
// Cloudflare Workers: cache the upstream response with stale-while-revalidate.
|
||||
// Fresh for 60s, serve stale for up to 300s while revalidating in background.
|
||||
// In non-Workers environments this is ignored.
|
||||
cf: {
|
||||
cacheEverything: true,
|
||||
cacheTtl: 60,
|
||||
cacheKey: "github-releases-latest",
|
||||
},
|
||||
} as RequestInit);
|
||||
if (!res.ok) return fallback;
|
||||
|
||||
const releases = (await res.json()) as GitHubRelease[];
|
||||
const ready = releases.find(
|
||||
(r) => !r.prerelease && !r.draft && hasRequiredAssets(r),
|
||||
);
|
||||
return ready ? versionFromTag(ready.tag_name) : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
export const getLatestRelease = createServerFn({ method: "GET" }).handler(
|
||||
async () => {
|
||||
const version = await fetchLatestReadyRelease();
|
||||
return { version };
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user