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

4342
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 }));
exports.REMOTE_MANIFEST_URL = void 0;
exports.getLatestKnownVersion = getLatestKnownVersion;
exports.getDownloadUrl = getDownloadUrl;
exports.getAvailableVersionsFromManifest = getAvailableVersionsFromManifest;
exports.updateVersionManifest = updateVersionManifest;
const node_fs_1 = __nccwpck_require__(3024);
const node_path_1 = __nccwpck_require__(6760);
@@ -32491,6 +32493,9 @@ const core = __importStar(__nccwpck_require__(7484));
const semver = __importStar(__nccwpck_require__(9318));
const fetch_1 = __nccwpck_require__(3385);
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) {
const manifestEntries = await getManifestEntries(manifestUrl);
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);
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) {
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;
if (manifestUrl !== undefined) {
core.info(`Fetching manifest-file from: ${manifestUrl}`);
@@ -32517,7 +32533,9 @@ async function getManifestEntries(manifestUrl) {
const fileContent = await node_fs_1.promises.readFile(localManifestFile);
data = fileContent.toString();
}
return JSON.parse(data);
const entries = JSON.parse(data);
manifestCache.set(cacheKey, entries);
return entries;
}
async function updateVersionManifest(manifestUrl, downloadUrls) {
const manifest = [];

View File

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

View File

@@ -5,6 +5,11 @@ import * as semver from "semver";
import { fetch } from "../utils/fetch";
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 {
version: string;
@@ -39,9 +44,25 @@ export async function getDownloadUrl(
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(
manifestUrl: string | undefined,
): 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;
if (manifestUrl !== undefined) {
core.info(`Fetching manifest-file from: ${manifestUrl}`);
@@ -58,7 +79,9 @@ async function getManifestEntries(
data = fileContent.toString();
}
return JSON.parse(data);
const entries: ManifestEntry[] = JSON.parse(data);
manifestCache.set(cacheKey, entries);
return entries;
}
export async function updateVersionManifest(

View File

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