Compare commits

...

2 Commits

Author SHA1 Message Date
Zsolt Dollenstein
71191068af Stop fetching version information from github APIs 2026-01-22 11:26:57 +00:00
Zsolt Dollenstein
450788bda3 Try and fetch manifest from astral-sh/setup-uv@main first 2026-01-22 11:13:26 +00:00
6 changed files with 5281 additions and 9502 deletions

5120
dist/save-cache/index.js generated vendored

File diff suppressed because it is too large Load Diff

9462
dist/setup/index.js generated vendored

File diff suppressed because it is too large Load Diff

20
dist/update-known-versions/index.js generated vendored
View File

@@ -32482,8 +32482,10 @@ var __importStar = (this && this.__importStar) || (function () {
}; };
})(); })();
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.REMOTE_MANIFEST_URL = void 0;
exports.getLatestKnownVersion = getLatestKnownVersion; exports.getLatestKnownVersion = getLatestKnownVersion;
exports.getDownloadUrl = getDownloadUrl; exports.getDownloadUrl = getDownloadUrl;
exports.getAvailableVersionsFromManifest = getAvailableVersionsFromManifest;
exports.updateVersionManifest = updateVersionManifest; exports.updateVersionManifest = updateVersionManifest;
const node_fs_1 = __nccwpck_require__(3024); const node_fs_1 = __nccwpck_require__(3024);
const node_path_1 = __nccwpck_require__(6760); const node_path_1 = __nccwpck_require__(6760);
@@ -32491,6 +32493,9 @@ const core = __importStar(__nccwpck_require__(7484));
const semver = __importStar(__nccwpck_require__(9318)); const semver = __importStar(__nccwpck_require__(9318));
const fetch_1 = __nccwpck_require__(3385); const fetch_1 = __nccwpck_require__(3385);
const localManifestFile = (0, node_path_1.join)(__dirname, "..", "..", "version-manifest.json"); const localManifestFile = (0, node_path_1.join)(__dirname, "..", "..", "version-manifest.json");
exports.REMOTE_MANIFEST_URL = "https://raw.githubusercontent.com/astral-sh/setup-uv/main/version-manifest.json";
// Cache for manifest entries to avoid re-fetching
const manifestCache = new Map();
async function getLatestKnownVersion(manifestUrl) { async function getLatestKnownVersion(manifestUrl) {
const manifestEntries = await getManifestEntries(manifestUrl); const manifestEntries = await getManifestEntries(manifestUrl);
return manifestEntries.reduce((a, b) => semver.gt(a.version, b.version) ? a : b).version; return manifestEntries.reduce((a, b) => semver.gt(a.version, b.version) ? a : b).version;
@@ -32502,7 +32507,18 @@ async function getDownloadUrl(manifestUrl, version, arch, platform) {
entry.platform === platform); entry.platform === platform);
return entry ? entry.downloadUrl : undefined; return entry ? entry.downloadUrl : undefined;
} }
async function getAvailableVersionsFromManifest(manifestUrl) {
const manifestEntries = await getManifestEntries(manifestUrl);
return [...new Set(manifestEntries.map((entry) => entry.version))];
}
async function getManifestEntries(manifestUrl) { async function getManifestEntries(manifestUrl) {
const cacheKey = manifestUrl ?? "local";
// Return cached entries if available
const cached = manifestCache.get(cacheKey);
if (cached !== undefined) {
core.debug(`Using cached manifest entries for: ${cacheKey}`);
return cached;
}
let data; let data;
if (manifestUrl !== undefined) { if (manifestUrl !== undefined) {
core.info(`Fetching manifest-file from: ${manifestUrl}`); core.info(`Fetching manifest-file from: ${manifestUrl}`);
@@ -32517,7 +32533,9 @@ async function getManifestEntries(manifestUrl) {
const fileContent = await node_fs_1.promises.readFile(localManifestFile); const fileContent = await node_fs_1.promises.readFile(localManifestFile);
data = fileContent.toString(); data = fileContent.toString();
} }
return JSON.parse(data); const entries = JSON.parse(data);
manifestCache.set(cacheKey, entries);
return entries;
} }
async function updateVersionManifest(manifestUrl, downloadUrls) { async function updateVersionManifest(manifestUrl, downloadUrls) {
const manifest = []; const manifest = [];

View File

@@ -2,21 +2,18 @@ import { promises as fs } from "node:fs";
import * as path from "node:path"; import * as path from "node:path";
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as tc from "@actions/tool-cache"; import * as tc from "@actions/tool-cache";
import type { Endpoints } from "@octokit/types";
import * as pep440 from "@renovatebot/pep440"; import * as pep440 from "@renovatebot/pep440";
import * as semver from "semver"; import * as semver from "semver";
import { OWNER, REPO, TOOL_CACHE_NAME } from "../utils/constants"; import { OWNER, REPO, TOOL_CACHE_NAME } from "../utils/constants";
import { Octokit } from "../utils/octokit";
import type { Architecture, Platform } from "../utils/platforms"; import type { Architecture, Platform } from "../utils/platforms";
import { validateChecksum } from "./checksum/checksum"; import { validateChecksum } from "./checksum/checksum";
import { import {
getAvailableVersionsFromManifest,
getDownloadUrl, getDownloadUrl,
getLatestKnownVersion as getLatestVersionInManifest, getLatestKnownVersion as getLatestVersionInManifest,
REMOTE_MANIFEST_URL,
} from "./version-manifest"; } from "./version-manifest";
type Release =
Endpoints["GET /repos/{owner}/{repo}/releases"]["response"]["data"][number];
export function tryGetFromToolCache( export function tryGetFromToolCache(
arch: Architecture, arch: Architecture,
version: string, version: string,
@@ -61,27 +58,36 @@ export async function downloadVersionFromManifest(
checkSum: string | undefined, checkSum: string | undefined,
githubToken: string, githubToken: string,
): Promise<{ version: string; cachedToolDir: string }> { ): Promise<{ version: string; cachedToolDir: string }> {
const downloadUrl = await getDownloadUrl( // If no user-provided manifest, try remote manifest first (will use cache if already fetched)
manifestUrl, // then fall back to bundled manifest
version, const manifestSources =
arch, manifestUrl !== undefined
platform, ? [manifestUrl]
); : [REMOTE_MANIFEST_URL, undefined];
if (!downloadUrl) {
core.info( for (const source of manifestSources) {
`manifest-file does not contain version ${version}, arch ${arch}, platform ${platform}. Falling back to GitHub releases.`, try {
); const downloadUrl = await getDownloadUrl(source, version, arch, platform);
return await downloadVersionFromGithub( if (downloadUrl) {
platform, return await downloadVersion(
arch, downloadUrl,
version, `uv-${arch}-${platform}`,
checkSum, platform,
githubToken, arch,
); version,
checkSum,
githubToken,
);
}
} catch (err) {
core.debug(`Failed to get download URL from manifest ${source}: ${err}`);
}
} }
return await downloadVersion(
downloadUrl, core.info(
`uv-${arch}-${platform}`, `Manifest does not contain version ${version}, arch ${arch}, platform ${platform}. Falling back to GitHub releases.`,
);
return await downloadVersionFromGithub(
platform, platform,
arch, arch,
version, version,
@@ -143,7 +149,6 @@ function getExtension(platform: Platform): string {
export async function resolveVersion( export async function resolveVersion(
versionInput: string, versionInput: string,
manifestFile: string | undefined, manifestFile: string | undefined,
githubToken: string,
resolutionStrategy: "highest" | "lowest" = "highest", resolutionStrategy: "highest" | "lowest" = "highest",
): Promise<string> { ): Promise<string> {
core.debug(`Resolving version: ${versionInput}`); core.debug(`Resolving version: ${versionInput}`);
@@ -163,7 +168,7 @@ export async function resolveVersion(
} else { } else {
version = version =
versionInput === "latest" || resolveVersionSpecifierToLatest versionInput === "latest" || resolveVersionSpecifierToLatest
? await getLatestVersion(githubToken) ? await getLatestVersion()
: versionInput; : versionInput;
} }
if (tc.isExplicitVersion(version)) { if (tc.isExplicitVersion(version)) {
@@ -175,7 +180,7 @@ export async function resolveVersion(
} }
return version; return version;
} }
const availableVersions = await getAvailableVersions(githubToken); const availableVersions = await getAvailableVersions();
core.debug(`Available versions: ${availableVersions}`); core.debug(`Available versions: ${availableVersions}`);
const resolvedVersion = const resolvedVersion =
resolutionStrategy === "lowest" resolutionStrategy === "lowest"
@@ -187,79 +192,37 @@ export async function resolveVersion(
return resolvedVersion; return resolvedVersion;
} }
async function getAvailableVersions(githubToken: string): Promise<string[]> { async function getAvailableVersions(): Promise<string[]> {
core.info("Getting available versions from GitHub API..."); // 1. Try remote manifest first (no rate limits, always current)
try { try {
const octokit = new Octokit({ core.info("Getting available versions from remote manifest...");
auth: githubToken, const versions =
}); await getAvailableVersionsFromManifest(REMOTE_MANIFEST_URL);
return await getReleaseTagNames(octokit); core.debug(`Found ${versions.length} versions from remote manifest`);
return versions;
} catch (err) { } catch (err) {
if ((err as Error).message.includes("Bad credentials")) { core.debug(`Remote manifest lookup failed: ${err}`);
core.info(
"No (valid) GitHub token provided. Falling back to anonymous. Requests might be rate limited.",
);
const octokit = new Octokit();
return await getReleaseTagNames(octokit);
}
throw err;
} }
// 2. Fall back to bundled manifest (no network, may be stale)
core.info("Getting available versions from bundled manifest...");
return await getAvailableVersionsFromManifest(undefined);
} }
async function getReleaseTagNames(octokit: Octokit): Promise<string[]> { async function getLatestVersion() {
const response: Release[] = await octokit.paginate( // 1. Try remote manifest first (no rate limits, always current)
octokit.rest.repos.listReleases,
{
owner: OWNER,
repo: REPO,
},
);
const releaseTagNames = response.map((release) => release.tag_name);
if (releaseTagNames.length === 0) {
throw Error(
"Github API request failed while getting releases. Check the GitHub status page for outages. Try again later.",
);
}
return releaseTagNames;
}
async function getLatestVersion(githubToken: string) {
core.info("Getting latest version from GitHub API...");
const octokit = new Octokit({
auth: githubToken,
});
let latestRelease: { tag_name: string } | undefined;
try { try {
latestRelease = await getLatestRelease(octokit); core.info("Getting latest version from remote manifest...");
const version = await getLatestVersionInManifest(REMOTE_MANIFEST_URL);
core.debug(`Latest version from remote manifest: ${version}`);
return version;
} catch (err) { } catch (err) {
if ((err as Error).message.includes("Bad credentials")) { core.debug(`Remote manifest lookup failed: ${err}`);
core.info(
"No (valid) GitHub token provided. Falling back to anonymous. Requests might be rate limited.",
);
const octokit = new Octokit();
latestRelease = await getLatestRelease(octokit);
} else {
core.error(
"Github API request failed while getting latest release. Check the GitHub status page for outages. Try again later.",
);
throw err;
}
} }
if (!latestRelease) { // 2. Fall back to bundled manifest (no network, may be stale)
throw new Error("Could not determine latest release."); core.info("Getting latest version from bundled manifest...");
} return await getLatestVersionInManifest(undefined);
core.debug(`Latest version: ${latestRelease.tag_name}`);
return latestRelease.tag_name;
}
async function getLatestRelease(octokit: Octokit) {
const { data: latestRelease } = await octokit.rest.repos.getLatestRelease({
owner: OWNER,
repo: REPO,
});
return latestRelease;
} }
function maxSatisfying( function maxSatisfying(

View File

@@ -5,6 +5,11 @@ import * as semver from "semver";
import { fetch } from "../utils/fetch"; import { fetch } from "../utils/fetch";
const localManifestFile = join(__dirname, "..", "..", "version-manifest.json"); const localManifestFile = join(__dirname, "..", "..", "version-manifest.json");
export const REMOTE_MANIFEST_URL =
"https://raw.githubusercontent.com/astral-sh/setup-uv/main/version-manifest.json";
// Cache for manifest entries to avoid re-fetching
const manifestCache = new Map<string, ManifestEntry[]>();
interface ManifestEntry { interface ManifestEntry {
version: string; version: string;
@@ -39,9 +44,25 @@ export async function getDownloadUrl(
return entry ? entry.downloadUrl : undefined; return entry ? entry.downloadUrl : undefined;
} }
export async function getAvailableVersionsFromManifest(
manifestUrl: string | undefined,
): Promise<string[]> {
const manifestEntries = await getManifestEntries(manifestUrl);
return [...new Set(manifestEntries.map((entry) => entry.version))];
}
async function getManifestEntries( async function getManifestEntries(
manifestUrl: string | undefined, manifestUrl: string | undefined,
): Promise<ManifestEntry[]> { ): Promise<ManifestEntry[]> {
const cacheKey = manifestUrl ?? "local";
// Return cached entries if available
const cached = manifestCache.get(cacheKey);
if (cached !== undefined) {
core.debug(`Using cached manifest entries for: ${cacheKey}`);
return cached;
}
let data: string; let data: string;
if (manifestUrl !== undefined) { if (manifestUrl !== undefined) {
core.info(`Fetching manifest-file from: ${manifestUrl}`); core.info(`Fetching manifest-file from: ${manifestUrl}`);
@@ -58,7 +79,9 @@ async function getManifestEntries(
data = fileContent.toString(); data = fileContent.toString();
} }
return JSON.parse(data); const entries: ManifestEntry[] = JSON.parse(data);
manifestCache.set(cacheKey, entries);
return entries;
} }
export async function updateVersionManifest( export async function updateVersionManifest(

View File

@@ -157,12 +157,7 @@ async function determineVersion(
manifestFile: string | undefined, manifestFile: string | undefined,
): Promise<string> { ): Promise<string> {
if (versionInput !== "") { if (versionInput !== "") {
return await resolveVersion( return await resolveVersion(versionInput, manifestFile, resolutionStrategy);
versionInput,
manifestFile,
githubToken,
resolutionStrategy,
);
} }
if (versionFileInput !== "") { if (versionFileInput !== "") {
const versionFromFile = getUvVersionFromFile(versionFileInput); const versionFromFile = getUvVersionFromFile(versionFileInput);
@@ -174,7 +169,6 @@ async function determineVersion(
return await resolveVersion( return await resolveVersion(
versionFromFile, versionFromFile,
manifestFile, manifestFile,
githubToken,
resolutionStrategy, resolutionStrategy,
); );
} }
@@ -192,7 +186,6 @@ async function determineVersion(
return await resolveVersion( return await resolveVersion(
versionFromUvToml || versionFromPyproject || "latest", versionFromUvToml || versionFromPyproject || "latest",
manifestFile, manifestFile,
githubToken,
resolutionStrategy, resolutionStrategy,
); );
} }