Skip to content

Commit 37802ad

Browse files
authored
Fetch uv from Astral's mirror by default (#809)
This PR tries fetching the uv artifact from `releases.astral.sh` by default, only in cases where the artifact would otherwise have come from `https://github.com/astral-sh/uv/releases/download/`. The checksums are supposed to be the same for the mirror, and can still come from `raw.githubusercontent.com/astral-sh/versions`. If the download fails, we fall back to the original URL. This avoids hitting GitHub's Releases API which is prone to rate limiting. As far as I can tell, together with #802 this PR makes a github token entirely unnecessary for this action. Towards astral-sh/uv#18503.
1 parent 9f00d18 commit 37802ad

File tree

4 files changed

+228
-20
lines changed

4 files changed

+228
-20
lines changed

__tests__/download/download-version.test.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const {
6868
downloadVersionFromManifest,
6969
downloadVersionFromNdjson,
7070
resolveVersion,
71+
rewriteToMirror,
7172
} = await import("../../src/download/download-version");
7273

7374
describe("download-version", () => {
@@ -198,6 +199,135 @@ describe("download-version", () => {
198199
"0.9.26",
199200
);
200201
});
202+
203+
it("rewrites GitHub Releases URLs to the Astral mirror", async () => {
204+
mockGetArtifactFromNdjson.mockResolvedValue({
205+
archiveFormat: "tar.gz",
206+
sha256: "abc123",
207+
url: "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
208+
});
209+
210+
await downloadVersionFromNdjson(
211+
"unknown-linux-gnu",
212+
"x86_64",
213+
"0.9.26",
214+
undefined,
215+
"token",
216+
);
217+
218+
expect(mockDownloadTool).toHaveBeenCalledWith(
219+
"https://releases.astral.sh/github/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
220+
undefined,
221+
undefined,
222+
);
223+
});
224+
225+
it("does not rewrite non-GitHub URLs", async () => {
226+
mockGetArtifactFromNdjson.mockResolvedValue({
227+
archiveFormat: "tar.gz",
228+
sha256: "abc123",
229+
url: "https://example.com/uv.tar.gz",
230+
});
231+
232+
await downloadVersionFromNdjson(
233+
"unknown-linux-gnu",
234+
"x86_64",
235+
"0.9.26",
236+
undefined,
237+
"token",
238+
);
239+
240+
expect(mockDownloadTool).toHaveBeenCalledWith(
241+
"https://example.com/uv.tar.gz",
242+
undefined,
243+
"token",
244+
);
245+
});
246+
247+
it("falls back to GitHub Releases when the mirror fails", async () => {
248+
mockGetArtifactFromNdjson.mockResolvedValue({
249+
archiveFormat: "tar.gz",
250+
sha256: "abc123",
251+
url: "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
252+
});
253+
254+
mockDownloadTool
255+
.mockRejectedValueOnce(new Error("mirror unavailable"))
256+
.mockResolvedValueOnce("/tmp/downloaded");
257+
258+
await downloadVersionFromNdjson(
259+
"unknown-linux-gnu",
260+
"x86_64",
261+
"0.9.26",
262+
undefined,
263+
"token",
264+
);
265+
266+
expect(mockDownloadTool).toHaveBeenCalledTimes(2);
267+
// Mirror request: no token
268+
expect(mockDownloadTool).toHaveBeenNthCalledWith(
269+
1,
270+
"https://releases.astral.sh/github/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
271+
undefined,
272+
undefined,
273+
);
274+
// GitHub fallback: token restored
275+
expect(mockDownloadTool).toHaveBeenNthCalledWith(
276+
2,
277+
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
278+
undefined,
279+
"token",
280+
);
281+
expect(mockWarning).toHaveBeenCalledWith(
282+
"Failed to download from mirror, falling back to GitHub Releases: mirror unavailable",
283+
);
284+
});
285+
286+
it("does not fall back for non-GitHub URLs", async () => {
287+
mockGetArtifactFromNdjson.mockResolvedValue({
288+
archiveFormat: "tar.gz",
289+
sha256: "abc123",
290+
url: "https://example.com/uv.tar.gz",
291+
});
292+
293+
mockDownloadTool.mockRejectedValue(new Error("download failed"));
294+
295+
await expect(
296+
downloadVersionFromNdjson(
297+
"unknown-linux-gnu",
298+
"x86_64",
299+
"0.9.26",
300+
undefined,
301+
"token",
302+
),
303+
).rejects.toThrow("download failed");
304+
305+
expect(mockDownloadTool).toHaveBeenCalledTimes(1);
306+
});
307+
});
308+
309+
describe("rewriteToMirror", () => {
310+
it("rewrites a GitHub Releases URL to the Astral mirror", () => {
311+
expect(
312+
rewriteToMirror(
313+
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
314+
),
315+
).toBe(
316+
"https://releases.astral.sh/github/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
317+
);
318+
});
319+
320+
it("returns undefined for non-GitHub URLs", () => {
321+
expect(rewriteToMirror("https://example.com/uv.tar.gz")).toBeUndefined();
322+
});
323+
324+
it("returns undefined for a different GitHub repo", () => {
325+
expect(
326+
rewriteToMirror(
327+
"https://github.com/other/repo/releases/download/v1.0/file.tar.gz",
328+
),
329+
).toBeUndefined();
330+
});
201331
});
202332

203333
describe("downloadVersionFromManifest", () => {

dist/setup/index.cjs

Lines changed: 38 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/download/download-version.ts

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import * as core from "@actions/core";
44
import * as tc from "@actions/tool-cache";
55
import * as pep440 from "@renovatebot/pep440";
66
import * as semver from "semver";
7-
import { TOOL_CACHE_NAME, VERSIONS_NDJSON_URL } from "../utils/constants";
7+
import {
8+
ASTRAL_MIRROR_PREFIX,
9+
GITHUB_RELEASES_PREFIX,
10+
TOOL_CACHE_NAME,
11+
VERSIONS_NDJSON_URL,
12+
} from "../utils/constants";
813
import type { Architecture, Platform } from "../utils/platforms";
914
import { validateChecksum } from "./checksum/checksum";
1015
import {
@@ -48,17 +53,53 @@ export async function downloadVersionFromNdjson(
4853
);
4954
}
5055

56+
const mirrorUrl = rewriteToMirror(artifact.url);
57+
const downloadUrl = mirrorUrl ?? artifact.url;
58+
// Don't send the GitHub token to the Astral mirror.
59+
const downloadToken = mirrorUrl !== undefined ? undefined : githubToken;
60+
5161
// For the default astral-sh/versions source, checksum validation relies on
5262
// user input or the built-in KNOWN_CHECKSUMS table, not NDJSON sha256 values.
53-
return await downloadVersion(
54-
artifact.url,
55-
`uv-${arch}-${platform}`,
56-
platform,
57-
arch,
58-
version,
59-
checkSum,
60-
githubToken,
61-
);
63+
try {
64+
return await downloadVersion(
65+
downloadUrl,
66+
`uv-${arch}-${platform}`,
67+
platform,
68+
arch,
69+
version,
70+
checkSum,
71+
downloadToken,
72+
);
73+
} catch (err) {
74+
if (mirrorUrl === undefined) {
75+
throw err;
76+
}
77+
78+
core.warning(
79+
`Failed to download from mirror, falling back to GitHub Releases: ${(err as Error).message}`,
80+
);
81+
82+
return await downloadVersion(
83+
artifact.url,
84+
`uv-${arch}-${platform}`,
85+
platform,
86+
arch,
87+
version,
88+
checkSum,
89+
githubToken,
90+
);
91+
}
92+
}
93+
94+
/**
95+
* Rewrite a GitHub Releases URL to the Astral mirror.
96+
* Returns `undefined` if the URL does not match the expected GitHub prefix.
97+
*/
98+
export function rewriteToMirror(url: string): string | undefined {
99+
if (!url.startsWith(GITHUB_RELEASES_PREFIX)) {
100+
return undefined;
101+
}
102+
return ASTRAL_MIRROR_PREFIX + url.slice(GITHUB_RELEASES_PREFIX.length);
62103
}
63104

64105
export async function downloadVersionFromManifest(
@@ -99,7 +140,7 @@ async function downloadVersion(
99140
arch: Architecture,
100141
version: string,
101142
checksum: string | undefined,
102-
githubToken: string,
143+
githubToken: string | undefined,
103144
): Promise<{ version: string; cachedToolDir: string }> {
104145
core.info(`Downloading uv from "${downloadUrl}" ...`);
105146
const downloadPath = await tc.downloadTool(

src/utils/constants.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@ export const STATE_UV_PATH = "uv-path";
33
export const STATE_UV_VERSION = "uv-version";
44
export const VERSIONS_NDJSON_URL =
55
"https://raw.githubusercontent.com/astral-sh/versions/main/v1/uv.ndjson";
6+
7+
/** GitHub Releases URL prefix for uv artifacts. */
8+
export const GITHUB_RELEASES_PREFIX =
9+
"https://github.com/astral-sh/uv/releases/download/";
10+
11+
/** Astral mirror URL prefix that fronts GitHub Releases for uv artifacts. */
12+
export const ASTRAL_MIRROR_PREFIX =
13+
"https://releases.astral.sh/github/uv/releases/download/";

0 commit comments

Comments
 (0)