use github release artifacts instead of cloning repos when available (#9147)

This commit is contained in:
Jacob MacDonald
2025-09-22 12:12:41 -07:00
committed by GitHub
parent 4ef46e441c
commit 710e00e02d
+40 -23
View File
@@ -103,11 +103,11 @@ export function parseGitHubRepoForReleases(source: string): {
return { owner, repo }; return { owner, repo };
} }
async function fetchFromGithub( async function fetchReleaseFromGithub(
owner: string, owner: string,
repo: string, repo: string,
ref?: string, ref?: string,
): Promise<{ assets: Asset[]; tag_name: string }> { ): Promise<GithubReleaseData> {
const endpoint = ref ? `releases/tags/${ref}` : 'releases/latest'; const endpoint = ref ? `releases/tags/${ref}` : 'releases/latest';
const url = `https://api.github.com/repos/${owner}/${repo}/${endpoint}`; const url = `https://api.github.com/repos/${owner}/${repo}/${endpoint}`;
return await fetchJson(url); return await fetchJson(url);
@@ -199,7 +199,7 @@ export async function checkForExtensionUpdate(
} }
const { owner, repo } = parseGitHubRepoForReleases(source); const { owner, repo } = parseGitHubRepoForReleases(source);
const releaseData = await fetchFromGithub( const releaseData = await fetchReleaseFromGithub(
owner, owner,
repo, repo,
installMetadata.ref, installMetadata.ref,
@@ -228,7 +228,7 @@ export async function downloadFromGitHubRelease(
const { owner, repo } = parseGitHubRepoForReleases(source); const { owner, repo } = parseGitHubRepoForReleases(source);
try { try {
const releaseData = await fetchFromGithub(owner, repo, ref); const releaseData = await fetchReleaseFromGithub(owner, repo, ref);
if (!releaseData) { if (!releaseData) {
throw new Error( throw new Error(
`No release data found for ${owner}/${repo} at tag ${ref}`, `No release data found for ${owner}/${repo} at tag ${ref}`,
@@ -236,24 +236,36 @@ export async function downloadFromGitHubRelease(
} }
const asset = findReleaseAsset(releaseData.assets); const asset = findReleaseAsset(releaseData.assets);
if (!asset) { let archiveUrl: string | undefined;
// If there are no release assets, then we just clone the repo using the let isTar = false;
// ref the release points to. let isZip = false;
await cloneFromGit( if (asset) {
{ archiveUrl = asset.browser_download_url;
...installMetadata, } else {
ref: releaseData.tag_name, if (releaseData.tarball_url) {
}, archiveUrl = releaseData.tarball_url;
destination, isTar = true;
} else if (releaseData.zipball_url) {
archiveUrl = releaseData.zipball_url;
isZip = true;
}
}
if (!archiveUrl) {
throw new Error(
`No assets found for release with tag ${releaseData.tag_name}`,
); );
return releaseData.tag_name; }
let downloadedAssetPath = path.join(
destination,
path.basename(new URL(archiveUrl).pathname),
);
if (isTar && !downloadedAssetPath.endsWith('.tar.gz')) {
downloadedAssetPath += '.tar.gz';
} else if (isZip && !downloadedAssetPath.endsWith('.zip')) {
downloadedAssetPath += '.zip';
} }
const downloadedAssetPath = path.join( await downloadFile(archiveUrl, downloadedAssetPath);
destination,
path.basename(asset.browser_download_url),
);
await downloadFile(asset.browser_download_url, downloadedAssetPath);
extractFile(downloadedAssetPath, destination); extractFile(downloadedAssetPath, destination);
@@ -284,6 +296,13 @@ export async function downloadFromGitHubRelease(
} }
} }
interface GithubReleaseData {
assets: Asset[];
tag_name: string;
tarball_url?: string;
zipball_url?: string;
}
interface Asset { interface Asset {
name: string; name: string;
browser_download_url: string; browser_download_url: string;
@@ -326,9 +345,7 @@ export function findReleaseAsset(assets: Asset[]): Asset | undefined {
return undefined; return undefined;
} }
async function fetchJson( async function fetchJson<T>(url: string): Promise<T> {
url: string,
): Promise<{ assets: Asset[]; tag_name: string }> {
const headers: { 'User-Agent': string; Authorization?: string } = { const headers: { 'User-Agent': string; Authorization?: string } = {
'User-Agent': 'gemini-cli', 'User-Agent': 'gemini-cli',
}; };
@@ -348,7 +365,7 @@ async function fetchJson(
res.on('data', (chunk) => chunks.push(chunk)); res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => { res.on('end', () => {
const data = Buffer.concat(chunks).toString(); const data = Buffer.concat(chunks).toString();
resolve(JSON.parse(data) as { assets: Asset[]; tag_name: string }); resolve(JSON.parse(data) as T);
}); });
}) })
.on('error', reject); .on('error', reject);