github.ts•3.19 kB
type GitHubRelease = {
tag_name: string;
prerelease: boolean;
draft: boolean;
assets: { name: string }[];
};
type LinkHeader = {
prev?: string;
next?: string;
first?: string;
last?: string;
};
/**
* Parse the HTTP Link header for pagination
* https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2022-11-28#using-link-headers
*/
export function parseLinkHeader(header: string): LinkHeader {
const links: { [key: string]: string } = {};
const parts = header.split(",");
for (const part of parts) {
const section = part.split(";");
if (section.length !== 2) {
continue;
}
const url = section[0].trim().slice(1, -1);
const rel = section[1].trim().slice(5, -1);
links[rel] = url;
}
return links;
}
/**
* Fetch all GitHub releases for a repository with pagination
*/
export async function fetchAllGitHubReleases(
repoPath: string,
perPage: number = 30,
): Promise<GitHubRelease[]> {
const allReleases: GitHubRelease[] = [];
let nextUrl = `https://api.github.com/repos/${repoPath}/releases?per_page=${perPage}`;
while (nextUrl) {
const response = await fetch(nextUrl);
if (!response.ok) {
const text = await response.text();
throw new Error(`GitHub API returned ${response.status}: ${text}`);
}
const releases = (await response.json()) as GitHubRelease[];
if (releases.length === 0) {
break;
}
allReleases.push(...releases);
// Get the next page URL from the Link header
const linkHeader = response.headers.get("Link");
if (!linkHeader) {
break;
}
const links = parseLinkHeader(linkHeader);
nextUrl = links["next"] || "";
}
return allReleases;
}
/**
* Find the first stable release that contains a specific asset
*/
export function findReleaseWithAsset(
releases: GitHubRelease[],
assetName: string,
options: { verbose?: boolean } = {},
): GitHubRelease | null {
for (const release of releases) {
// Only consider stable releases
if (!release.prerelease && !release.draft) {
// Check if this release has the asset
if (release.assets.find((asset) => asset.name === assetName)) {
if (options.verbose) {
console.log(
`Latest stable version with ${assetName} is ${release.tag_name}`,
);
}
return release;
} else {
if (options.verbose) {
console.log(
`Version ${release.tag_name} does not contain a ${assetName}, checking previous version`,
);
console.log(
`ASSETS: ${release.assets.map((asset) => asset.name).join(", ")}`,
);
}
}
}
}
return null;
}
/**
* Download an asset from a GitHub release
*/
export async function downloadAssetFromRelease(
repoPath: string,
version: string,
assetName: string,
): Promise<string> {
const downloadUrl = `https://github.com/${repoPath}/releases/download/${version}/${assetName}`;
const response = await fetch(downloadUrl);
if (!response.ok) {
throw new Error(`Failed to download ${assetName} from ${downloadUrl}`);
}
return await response.text();
}