1const REPO = "pingdotgg/t3code";
2
3export const RELEASES_URL = `https://github.com/${REPO}/releases`;
4export const NIGHTLY_RELEASES_URL = `${RELEASES_URL}?q=nightly&expanded=true`;
5
6const LATEST_API_URL = `https://api.github.com/repos/${REPO}/releases/latest`;
7// The `latest` endpoint skips prereleases, so nightly needs the list. GitHub
8// returns it newest first and nightlies land several times a day, so the first
9// nightly tag in a small page is the current build.
10const LIST_API_URL = `https://api.github.com/repos/${REPO}/releases?per_page=10`;
11
12export type ReleaseChannel = "stable" | "nightly";
13
14export interface ReleaseAsset {
15 name: string;
16 browser_download_url: string;
17}
18
19export interface Release {
20 tag_name: string;
21 html_url: string;
22 published_at: string;
23 assets: ReleaseAsset[];
24}
25
26function cacheKey(channel: ReleaseChannel) {
27 return `t3code-${channel}-release`;
28}
29
30async function fetchStable(): Promise<Release> {
31 return fetch(LATEST_API_URL).then((r) => r.json());
32}
33
34async function fetchNightly(): Promise<Release> {
35 const list: Release[] = await fetch(LIST_API_URL).then((r) => r.json());
36 const nightly = Array.isArray(list)
37 ? list.find((release) => release.tag_name?.includes("-nightly."))
38 : undefined;
39 if (!nightly) throw new Error("No nightly release in the latest page");
40 return nightly;
41}
42
43export async function fetchLatestRelease(channel: ReleaseChannel = "stable"): Promise<Release> {
44 const key = cacheKey(channel);
45 const cached = sessionStorage.getItem(key);
46 if (cached) return JSON.parse(cached);
47
48 const data = channel === "nightly" ? await fetchNightly() : await fetchStable();
49
50 if (data?.assets) {
51 sessionStorage.setItem(key, JSON.stringify(data));
52 }
53
54 return data;
55}