diff --git a/__tests__/download/download-version.test.ts b/__tests__/download/download-version.test.ts index afc57ab..337c976 100644 --- a/__tests__/download/download-version.test.ts +++ b/__tests__/download/download-version.test.ts @@ -37,10 +37,13 @@ const mockGetLatestVersionFromNdjson = jest.fn(); const mockGetAllVersionsFromNdjson = jest.fn(); // biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests. const mockGetArtifactFromNdjson = jest.fn(); +// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests. +const mockGetHighestSatisfyingVersionFromNdjson = jest.fn(); jest.unstable_mockModule("../../src/download/versions-client", () => ({ getAllVersions: mockGetAllVersionsFromNdjson, getArtifact: mockGetArtifactFromNdjson, + getHighestSatisfyingVersion: mockGetHighestSatisfyingVersionFromNdjson, getLatestVersion: mockGetLatestVersionFromNdjson, })); @@ -81,6 +84,7 @@ describe("download-version", () => { mockGetLatestVersionFromNdjson.mockReset(); mockGetAllVersionsFromNdjson.mockReset(); mockGetArtifactFromNdjson.mockReset(); + mockGetHighestSatisfyingVersionFromNdjson.mockReset(); mockGetAllManifestVersions.mockReset(); mockGetLatestVersionInManifest.mockReset(); mockGetManifestArtifact.mockReset(); @@ -102,13 +106,26 @@ describe("download-version", () => { expect(mockGetLatestVersionFromNdjson).toHaveBeenCalledTimes(1); }); - it("uses astral-sh/versions to resolve available versions", async () => { - mockGetAllVersionsFromNdjson.mockResolvedValue(["0.9.26", "0.9.25"]); + it("streams astral-sh/versions to resolve the highest matching version", async () => { + mockGetHighestSatisfyingVersionFromNdjson.mockResolvedValue("0.9.26"); const version = await resolveVersion("^0.9.0", undefined); expect(version).toBe("0.9.26"); + expect(mockGetHighestSatisfyingVersionFromNdjson).toHaveBeenCalledWith( + "^0.9.0", + ); + expect(mockGetAllVersionsFromNdjson).not.toHaveBeenCalled(); + }); + + it("still loads all versions when resolving the lowest matching version", async () => { + mockGetAllVersionsFromNdjson.mockResolvedValue(["0.9.26", "0.9.25"]); + + const version = await resolveVersion("^0.9.0", undefined, "lowest"); + + expect(version).toBe("0.9.25"); expect(mockGetAllVersionsFromNdjson).toHaveBeenCalledTimes(1); + expect(mockGetHighestSatisfyingVersionFromNdjson).not.toHaveBeenCalled(); }); it("does not fall back when astral-sh/versions fails", async () => { diff --git a/__tests__/download/versions-client.test.ts b/__tests__/download/versions-client.test.ts index f963716..3496948 100644 --- a/__tests__/download/versions-client.test.ts +++ b/__tests__/download/versions-client.test.ts @@ -12,6 +12,7 @@ const { fetchVersionData, getAllVersions, getArtifact, + getHighestSatisfyingVersion, getLatestVersion, parseVersionData, } = await import("../../src/download/versions-client"); @@ -21,13 +22,28 @@ const sampleNdjsonResponse = `{"version":"0.9.26","artifacts":[{"platform":"aarc const multiVariantNdjsonResponse = `{"version":"0.9.26","artifacts":[{"platform":"aarch64-apple-darwin","variant":"python-managed","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin-managed.tar.gz","archive_format":"tar.gz","sha256":"managed-checksum"},{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.zip","archive_format":"zip","sha256":"default-checksum"}]}`; +function createMockStream(chunks: string[]): ReadableStream { + const encoder = new TextEncoder(); + + return new ReadableStream({ + start(controller) { + for (const chunk of chunks) { + controller.enqueue(encoder.encode(chunk)); + } + controller.close(); + }, + }); +} + function createMockResponse( ok: boolean, status: number, statusText: string, data: string, + chunks: string[] = [data], ) { return { + body: createMockStream(chunks), ok, status, statusText, @@ -86,6 +102,22 @@ describe("versions-client", () => { expect(latest).toBe("0.9.26"); }); + + it("should stop after the first record when resolving latest", async () => { + mockFetch.mockResolvedValue( + createMockResponse( + true, + 200, + "OK", + `${sampleNdjsonResponse}\n{"version":`, + [`${sampleNdjsonResponse.split("\n")[0]}\n`, '{"version":'], + ), + ); + + const latest = await getLatestVersion(); + + expect(latest).toBe("0.9.26"); + }); }); describe("getAllVersions", () => { @@ -100,6 +132,24 @@ describe("versions-client", () => { }); }); + describe("getHighestSatisfyingVersion", () => { + it("should return the first matching version from the stream", async () => { + mockFetch.mockResolvedValue( + createMockResponse( + true, + 200, + "OK", + `${sampleNdjsonResponse}\n{"version":`, + [`${sampleNdjsonResponse.split("\n")[0]}\n`, '{"version":'], + ), + ); + + const version = await getHighestSatisfyingVersion("^0.9.0"); + + expect(version).toBe("0.9.26"); + }); + }); + describe("getArtifact", () => { beforeEach(() => { mockFetch.mockResolvedValue( @@ -118,6 +168,27 @@ describe("versions-client", () => { }); }); + it("should stop once the requested version is found", async () => { + mockFetch.mockResolvedValue( + createMockResponse( + true, + 200, + "OK", + `${sampleNdjsonResponse}\n{"version":`, + [`${sampleNdjsonResponse.split("\n")[0]}\n`, '{"version":'], + ), + ); + + const artifact = await getArtifact("0.9.26", "aarch64", "apple-darwin"); + + expect(artifact).toEqual({ + archiveFormat: "tar.gz", + sha256: + "fcf0a9ea6599c6ae28a4c854ac6da76f2c889354d7c36ce136ef071f7ab9721f", + url: "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.tar.gz", + }); + }); + it("should find windows artifact", async () => { const artifact = await getArtifact("0.9.26", "x86_64", "pc-windows-msvc"); diff --git a/dist/setup/index.cjs b/dist/setup/index.cjs index da0b05a..4a67d9f 100644 --- a/dist/setup/index.cjs +++ b/dist/setup/index.cjs @@ -20742,7 +20742,7 @@ var require_satisfies = __commonJS({ "node_modules/@actions/cache/node_modules/semver/functions/satisfies.js"(exports2, module2) { "use strict"; var Range = require_range(); - var satisfies4 = (version4, range2, options) => { + var satisfies6 = (version4, range2, options) => { try { range2 = new Range(range2, options); } catch (er) { @@ -20750,7 +20750,7 @@ var require_satisfies = __commonJS({ } return range2.test(version4); }; - module2.exports = satisfies4; + module2.exports = satisfies6; } }); @@ -20905,7 +20905,7 @@ var require_outside = __commonJS({ var Comparator = require_comparator(); var { ANY } = Comparator; var Range = require_range(); - var satisfies4 = require_satisfies(); + var satisfies6 = require_satisfies(); var gt3 = require_gt(); var lt = require_lt(); var lte = require_lte(); @@ -20932,7 +20932,7 @@ var require_outside = __commonJS({ default: throw new TypeError('Must provide a hilo val of "<" or ">"'); } - if (satisfies4(version4, range2, options)) { + if (satisfies6(version4, range2, options)) { return false; } for (let i = 0; i < range2.set.length; ++i) { @@ -21004,7 +21004,7 @@ var require_intersects = __commonJS({ var require_simplify = __commonJS({ "node_modules/@actions/cache/node_modules/semver/ranges/simplify.js"(exports2, module2) { "use strict"; - var satisfies4 = require_satisfies(); + var satisfies6 = require_satisfies(); var compare = require_compare(); module2.exports = (versions, range2, options) => { const set = []; @@ -21012,7 +21012,7 @@ var require_simplify = __commonJS({ let prev = null; const v = versions.sort((a, b) => compare(a, b, options)); for (const version4 of v) { - const included = satisfies4(version4, range2, options); + const included = satisfies6(version4, range2, options); if (included) { prev = version4; if (!first) { @@ -21057,7 +21057,7 @@ var require_subset = __commonJS({ var Range = require_range(); var Comparator = require_comparator(); var { ANY } = Comparator; - var satisfies4 = require_satisfies(); + var satisfies6 = require_satisfies(); var compare = require_compare(); var subset = (sub, dom, options = {}) => { if (sub === dom) { @@ -21126,14 +21126,14 @@ var require_subset = __commonJS({ } } for (const eq of eqSet) { - if (gt3 && !satisfies4(eq, String(gt3), options)) { + if (gt3 && !satisfies6(eq, String(gt3), options)) { return null; } - if (lt && !satisfies4(eq, String(lt), options)) { + if (lt && !satisfies6(eq, String(lt), options)) { return null; } for (const c of dom) { - if (!satisfies4(eq, String(c), options)) { + if (!satisfies6(eq, String(c), options)) { return false; } } @@ -21160,7 +21160,7 @@ var require_subset = __commonJS({ if (higher === c && higher !== gt3) { return false; } - } else if (gt3.operator === ">=" && !satisfies4(gt3.semver, String(c), options)) { + } else if (gt3.operator === ">=" && !satisfies6(gt3.semver, String(c), options)) { return false; } } @@ -21175,7 +21175,7 @@ var require_subset = __commonJS({ if (lower === c && lower !== lt) { return false; } - } else if (lt.operator === "<=" && !satisfies4(lt.semver, String(c), options)) { + } else if (lt.operator === "<=" && !satisfies6(lt.semver, String(c), options)) { return false; } } @@ -21245,7 +21245,7 @@ var require_semver2 = __commonJS({ var coerce = require_coerce(); var Comparator = require_comparator(); var Range = require_range(); - var satisfies4 = require_satisfies(); + var satisfies6 = require_satisfies(); var toComparators = require_to_comparators(); var maxSatisfying3 = require_max_satisfying(); var minSatisfying4 = require_min_satisfying(); @@ -21283,7 +21283,7 @@ var require_semver2 = __commonJS({ coerce, Comparator, Range, - satisfies: satisfies4, + satisfies: satisfies6, toComparators, maxSatisfying: maxSatisfying3, minSatisfying: minSatisfying4, @@ -28352,7 +28352,7 @@ var require_satisfies2 = __commonJS({ "node_modules/@actions/tool-cache/node_modules/semver/functions/satisfies.js"(exports2, module2) { "use strict"; var Range = require_range2(); - var satisfies4 = (version4, range2, options) => { + var satisfies6 = (version4, range2, options) => { try { range2 = new Range(range2, options); } catch (er) { @@ -28360,7 +28360,7 @@ var require_satisfies2 = __commonJS({ } return range2.test(version4); }; - module2.exports = satisfies4; + module2.exports = satisfies6; } }); @@ -28515,7 +28515,7 @@ var require_outside2 = __commonJS({ var Comparator = require_comparator2(); var { ANY } = Comparator; var Range = require_range2(); - var satisfies4 = require_satisfies2(); + var satisfies6 = require_satisfies2(); var gt3 = require_gt2(); var lt = require_lt2(); var lte = require_lte2(); @@ -28542,7 +28542,7 @@ var require_outside2 = __commonJS({ default: throw new TypeError('Must provide a hilo val of "<" or ">"'); } - if (satisfies4(version4, range2, options)) { + if (satisfies6(version4, range2, options)) { return false; } for (let i = 0; i < range2.set.length; ++i) { @@ -28614,7 +28614,7 @@ var require_intersects2 = __commonJS({ var require_simplify2 = __commonJS({ "node_modules/@actions/tool-cache/node_modules/semver/ranges/simplify.js"(exports2, module2) { "use strict"; - var satisfies4 = require_satisfies2(); + var satisfies6 = require_satisfies2(); var compare = require_compare2(); module2.exports = (versions, range2, options) => { const set = []; @@ -28622,7 +28622,7 @@ var require_simplify2 = __commonJS({ let prev = null; const v = versions.sort((a, b) => compare(a, b, options)); for (const version4 of v) { - const included = satisfies4(version4, range2, options); + const included = satisfies6(version4, range2, options); if (included) { prev = version4; if (!first) { @@ -28667,7 +28667,7 @@ var require_subset2 = __commonJS({ var Range = require_range2(); var Comparator = require_comparator2(); var { ANY } = Comparator; - var satisfies4 = require_satisfies2(); + var satisfies6 = require_satisfies2(); var compare = require_compare2(); var subset = (sub, dom, options = {}) => { if (sub === dom) { @@ -28736,14 +28736,14 @@ var require_subset2 = __commonJS({ } } for (const eq of eqSet) { - if (gt3 && !satisfies4(eq, String(gt3), options)) { + if (gt3 && !satisfies6(eq, String(gt3), options)) { return null; } - if (lt && !satisfies4(eq, String(lt), options)) { + if (lt && !satisfies6(eq, String(lt), options)) { return null; } for (const c of dom) { - if (!satisfies4(eq, String(c), options)) { + if (!satisfies6(eq, String(c), options)) { return false; } } @@ -28770,7 +28770,7 @@ var require_subset2 = __commonJS({ if (higher === c && higher !== gt3) { return false; } - } else if (gt3.operator === ">=" && !satisfies4(gt3.semver, String(c), options)) { + } else if (gt3.operator === ">=" && !satisfies6(gt3.semver, String(c), options)) { return false; } } @@ -28785,7 +28785,7 @@ var require_subset2 = __commonJS({ if (lower === c && lower !== lt) { return false; } - } else if (lt.operator === "<=" && !satisfies4(lt.semver, String(c), options)) { + } else if (lt.operator === "<=" && !satisfies6(lt.semver, String(c), options)) { return false; } } @@ -28855,7 +28855,7 @@ var require_semver4 = __commonJS({ var coerce = require_coerce2(); var Comparator = require_comparator2(); var Range = require_range2(); - var satisfies4 = require_satisfies2(); + var satisfies6 = require_satisfies2(); var toComparators = require_to_comparators2(); var maxSatisfying3 = require_max_satisfying2(); var minSatisfying4 = require_min_satisfying2(); @@ -28893,7 +28893,7 @@ var require_semver4 = __commonJS({ coerce, Comparator, Range, - satisfies: satisfies4, + satisfies: satisfies6, toComparators, maxSatisfying: maxSatisfying3, minSatisfying: minSatisfying4, @@ -29229,7 +29229,7 @@ var require_specifier = __commonJS({ module2.exports = { RANGE_PATTERN, parse: parse3, - satisfies: satisfies4, + satisfies: satisfies6, filter, validRange, maxSatisfying: maxSatisfying3, @@ -29306,7 +29306,7 @@ var require_specifier = __commonJS({ }, true); }); } - function satisfies4(version4, specifier, options = {}) { + function satisfies6(version4, specifier, options = {}) { const filtered = pick([version4], specifier, options); return filtered.length === 1; } @@ -29336,7 +29336,7 @@ var require_specifier = __commonJS({ if (spec.epoch) { compatiblePrefix = spec.epoch + "!" + compatiblePrefix; } - return satisfies4(version4, `>=${spec.version}, ==${compatiblePrefix}`, { + return satisfies6(version4, `>=${spec.version}, ==${compatiblePrefix}`, { prereleases: spec.prereleases }); } @@ -29527,7 +29527,7 @@ var require_pep440 = __commonJS({ maxSatisfying: maxSatisfying3, minSatisfying: minSatisfying4, RANGE_PATTERN, - satisfies: satisfies4, + satisfies: satisfies6, validRange } = require_specifier(); var { major, minor, patch, inc } = require_semantic(); @@ -29554,7 +29554,7 @@ var require_pep440 = __commonJS({ maxSatisfying: maxSatisfying3, minSatisfying: minSatisfying4, RANGE_PATTERN, - satisfies: satisfies4, + satisfies: satisfies6, validRange, // semantic major, @@ -30183,13 +30183,13 @@ var require_semver5 = __commonJS({ return true; } rangeTmp = new Range(comp26.value, options); - return satisfies4(this.value, rangeTmp, options); + return satisfies6(this.value, rangeTmp, options); } else if (comp26.operator === "") { if (comp26.value === "") { return true; } rangeTmp = new Range(this.value, options); - return satisfies4(comp26.semver, rangeTmp, options); + return satisfies6(comp26.semver, rangeTmp, options); } var sameDirectionIncreasing = (this.operator === ">=" || this.operator === ">") && (comp26.operator === ">=" || comp26.operator === ">"); var sameDirectionDecreasing = (this.operator === "<=" || this.operator === "<") && (comp26.operator === "<=" || comp26.operator === "<"); @@ -30516,8 +30516,8 @@ var require_semver5 = __commonJS({ } return true; } - exports2.satisfies = satisfies4; - function satisfies4(version4, range2, options) { + exports2.satisfies = satisfies6; + function satisfies6(version4, range2, options) { try { range2 = new Range(range2, options); } catch (er) { @@ -30647,7 +30647,7 @@ var require_semver5 = __commonJS({ default: throw new TypeError('Must provide a hilo val of "<" or ">"'); } - if (satisfies4(version4, range2, options)) { + if (satisfies6(version4, range2, options)) { return false; } for (var i2 = 0; i2 < range2.set.length; ++i2) { @@ -91879,8 +91879,8 @@ function _getGlobal(key, defaultValue) { } // src/download/download-version.ts -var pep440 = __toESM(require_pep440(), 1); -var semver5 = __toESM(require_semver5(), 1); +var pep4402 = __toESM(require_pep440(), 1); +var semver6 = __toESM(require_semver5(), 1); // src/utils/constants.ts var TOOL_CACHE_NAME = "uv"; @@ -96338,7 +96338,7 @@ async function validateFileCheckSum(filePath, expected) { } // src/download/version-manifest.ts -var semver4 = __toESM(require_semver5(), 1); +var semver5 = __toESM(require_semver5(), 1); // src/utils/fetch.ts var import_undici2 = __toESM(require_undici2(), 1); @@ -96424,7 +96424,11 @@ function formatVariants(entries) { } // src/download/versions-client.ts +var pep440 = __toESM(require_pep440(), 1); +var semver4 = __toESM(require_semver5(), 1); var cachedVersionData = /* @__PURE__ */ new Map(); +var cachedLatestVersionData = /* @__PURE__ */ new Map(); +var cachedVersionLookup = /* @__PURE__ */ new Map(); async function fetchVersionData(url2 = VERSIONS_NDJSON_URL) { const cachedVersions = cachedVersionData.get(url2); if (cachedVersions !== void 0) { @@ -96432,15 +96436,8 @@ async function fetchVersionData(url2 = VERSIONS_NDJSON_URL) { return cachedVersions; } info(`Fetching version data from ${url2} ...`); - const response = await fetch(url2, {}); - if (!response.ok) { - throw new Error( - `Failed to fetch version data: ${response.status} ${response.statusText}` - ); - } - const body2 = await response.text(); - const versions = parseVersionData(body2, url2); - cachedVersionData.set(url2, versions); + const { versions } = await readVersionData(url2); + cacheCompleteVersionData(url2, versions); return versions; } function parseVersionData(data, sourceDescription) { @@ -96450,20 +96447,7 @@ function parseVersionData(data, sourceDescription) { if (trimmed === "") { continue; } - let parsed; - try { - parsed = JSON.parse(trimmed); - } catch (error2) { - throw new Error( - `Failed to parse version data from ${sourceDescription} at line ${index + 1}: ${error2.message}` - ); - } - if (!isNdjsonVersion(parsed)) { - throw new Error( - `Invalid NDJSON record in ${sourceDescription} at line ${index + 1}.` - ); - } - versions.push(parsed); + versions.push(parseVersionLine(trimmed, sourceDescription, index + 1)); } if (versions.length === 0) { throw new Error(`No version data found in ${sourceDescription}.`); @@ -96471,23 +96455,34 @@ function parseVersionData(data, sourceDescription) { return versions; } async function getLatestVersion() { - const versions = await fetchVersionData(); - const latestVersion = versions[0]?.version; + const cachedVersions = cachedVersionData.get(VERSIONS_NDJSON_URL); + const cachedLatestVersion = cachedVersions?.[0] ?? cachedLatestVersionData.get(VERSIONS_NDJSON_URL); + if (cachedLatestVersion !== void 0) { + debug( + `Latest version from NDJSON cache: ${cachedLatestVersion.version}` + ); + return cachedLatestVersion.version; + } + const latestVersion = await findVersionData(() => true); if (!latestVersion) { throw new Error("No versions found in NDJSON data"); } - debug(`Latest version from NDJSON: ${latestVersion}`); - return latestVersion; + debug(`Latest version from NDJSON: ${latestVersion.version}`); + return latestVersion.version; } async function getAllVersions() { const versions = await fetchVersionData(); return versions.map((versionData) => versionData.version); } -async function getArtifact(version4, arch3, platform2) { - const versions = await fetchVersionData(); - const versionData = versions.find( - (candidate) => candidate.version === version4 +async function getHighestSatisfyingVersion(versionSpecifier, url2 = VERSIONS_NDJSON_URL) { + const matchedVersion = await findVersionData( + (candidate) => versionSatisfies(candidate.version, versionSpecifier), + url2 ); + return matchedVersion?.version; +} +async function getArtifact(version4, arch3, platform2) { + const versionData = await getVersionData(version4); if (!versionData) { debug(`Version ${version4} not found in NDJSON data`); return void 0; @@ -96515,6 +96510,135 @@ function selectArtifact(artifacts, version4, targetPlatform) { `Multiple artifacts found for ${targetPlatform} in version ${version4}` ); } +async function getVersionData(version4, url2 = VERSIONS_NDJSON_URL) { + const cachedVersions = cachedVersionData.get(url2); + if (cachedVersions !== void 0) { + return cachedVersions.find((candidate) => candidate.version === version4); + } + const cachedVersion = cachedVersionLookup.get(url2)?.get(version4); + if (cachedVersion !== void 0) { + return cachedVersion; + } + return await findVersionData( + (candidate) => candidate.version === version4, + url2 + ); +} +async function findVersionData(predicate, url2 = VERSIONS_NDJSON_URL) { + const cachedVersions = cachedVersionData.get(url2); + if (cachedVersions !== void 0) { + return cachedVersions.find(predicate); + } + const { matchedVersion, versions, complete } = await readVersionData( + url2, + predicate + ); + if (complete) { + cacheCompleteVersionData(url2, versions); + } + return matchedVersion; +} +async function readVersionData(url2, stopWhen) { + const response = await fetch(url2, {}); + if (!response.ok) { + throw new Error( + `Failed to fetch version data: ${response.status} ${response.statusText}` + ); + } + if (response.body === null) { + const body2 = await response.text(); + const versions2 = parseVersionData(body2, url2); + const matchedVersion2 = stopWhen ? versions2.find((candidate) => stopWhen(candidate)) : void 0; + return { complete: true, matchedVersion: matchedVersion2, versions: versions2 }; + } + const versions = []; + let lineNumber = 0; + let matchedVersion; + let buffer3 = ""; + const decoder = new TextDecoder(); + const reader = response.body.getReader(); + const processLine = (line) => { + const trimmed = line.trim(); + if (trimmed === "") { + return false; + } + lineNumber += 1; + const versionData = parseVersionLine(trimmed, url2, lineNumber); + if (versions.length === 0) { + cachedLatestVersionData.set(url2, versionData); + } + versions.push(versionData); + cacheVersion(url2, versionData); + if (stopWhen?.(versionData) === true) { + matchedVersion = versionData; + return true; + } + return false; + }; + while (true) { + const { done, value } = await reader.read(); + if (done) { + buffer3 += decoder.decode(); + break; + } + buffer3 += decoder.decode(value, { stream: true }); + let newlineIndex = buffer3.indexOf("\n"); + while (newlineIndex !== -1) { + const line = buffer3.slice(0, newlineIndex); + buffer3 = buffer3.slice(newlineIndex + 1); + if (processLine(line)) { + await reader.cancel(); + return { complete: false, matchedVersion, versions }; + } + newlineIndex = buffer3.indexOf("\n"); + } + } + if (buffer3.trim() !== "" && processLine(buffer3)) { + return { complete: true, matchedVersion, versions }; + } + if (versions.length === 0) { + throw new Error(`No version data found in ${url2}.`); + } + return { complete: true, matchedVersion, versions }; +} +function cacheCompleteVersionData(url2, versions) { + cachedVersionData.set(url2, versions); + if (versions[0] !== void 0) { + cachedLatestVersionData.set(url2, versions[0]); + } + const versionLookup = /* @__PURE__ */ new Map(); + for (const versionData of versions) { + versionLookup.set(versionData.version, versionData); + } + cachedVersionLookup.set(url2, versionLookup); +} +function cacheVersion(url2, versionData) { + let versionLookup = cachedVersionLookup.get(url2); + if (versionLookup === void 0) { + versionLookup = /* @__PURE__ */ new Map(); + cachedVersionLookup.set(url2, versionLookup); + } + versionLookup.set(versionData.version, versionData); +} +function parseVersionLine(line, sourceDescription, lineNumber) { + let parsed; + try { + parsed = JSON.parse(line); + } catch (error2) { + throw new Error( + `Failed to parse version data from ${sourceDescription} at line ${lineNumber}: ${error2.message}` + ); + } + if (!isNdjsonVersion(parsed)) { + throw new Error( + `Invalid NDJSON record in ${sourceDescription} at line ${lineNumber}.` + ); + } + return parsed; +} +function versionSatisfies(version4, versionSpecifier) { + return semver4.satisfies(version4, versionSpecifier) || pep440.satisfies(version4, versionSpecifier); +} function isNdjsonVersion(value) { if (!isRecord2(value)) { return false; @@ -96540,7 +96664,7 @@ var cachedManifestEntries = /* @__PURE__ */ new Map(); async function getLatestKnownVersion(manifestUrl) { const versions = await getAllVersions2(manifestUrl); const latestVersion = versions.reduce( - (latest, current) => semver4.gt(current, latest) ? current : latest + (latest, current) => semver5.gt(current, latest) ? current : latest ); return latestVersion; } @@ -96745,12 +96869,20 @@ async function resolveVersion(versionInput, manifestUrl, resolutionStrategy2 = " if (isExplicitVersion(version4)) { debug(`Version ${version4} is an explicit version.`); if (resolveVersionSpecifierToLatest) { - if (!pep440.satisfies(version4, versionInput)) { + if (!pep4402.satisfies(version4, versionInput)) { throw new Error(`No version found for ${versionInput}`); } } return version4; } + if (manifestUrl === void 0 && resolutionStrategy2 === "highest") { + const resolvedVersion2 = await getHighestSatisfyingVersion(version4); + if (resolvedVersion2 !== void 0) { + debug(`Resolved version from NDJSON stream: ${resolvedVersion2}`); + return resolvedVersion2; + } + throw new Error(`No version found for ${version4}`); + } const availableVersions = await getAvailableVersions(manifestUrl); debug(`Available versions: ${availableVersions}`); const resolvedVersion = resolutionStrategy2 === "lowest" ? minSatisfying3(availableVersions, version4) : maxSatisfying2(availableVersions, version4); @@ -96775,7 +96907,7 @@ function maxSatisfying2(versions, version4) { debug(`Found a version that satisfies the semver range: ${maxSemver}`); return maxSemver; } - const maxPep440 = pep440.maxSatisfying(versions, version4); + const maxPep440 = pep4402.maxSatisfying(versions, version4); if (maxPep440 !== null) { debug( `Found a version that satisfies the pep440 specifier: ${maxPep440}` @@ -96785,12 +96917,12 @@ function maxSatisfying2(versions, version4) { return void 0; } function minSatisfying3(versions, version4) { - const minSemver = semver5.minSatisfying(versions, version4); + const minSemver = semver6.minSatisfying(versions, version4); if (minSemver !== null) { debug(`Found a version that satisfies the semver range: ${minSemver}`); return minSemver; } - const minPep440 = pep440.minSatisfying(versions, version4); + const minPep440 = pep4402.minSatisfying(versions, version4); if (minPep440 !== null) { debug( `Found a version that satisfies the pep440 specifier: ${minPep440}` diff --git a/dist/update-known-checksums/index.cjs b/dist/update-known-checksums/index.cjs index 2468493..dd90fb9 100644 --- a/dist/update-known-checksums/index.cjs +++ b/dist/update-known-checksums/index.cjs @@ -19201,13 +19201,13 @@ var require_semver = __commonJS({ return true; } rangeTmp = new Range(comp.value, options); - return satisfies(this.value, rangeTmp, options); + return satisfies3(this.value, rangeTmp, options); } else if (comp.operator === "") { if (comp.value === "") { return true; } rangeTmp = new Range(this.value, options); - return satisfies(comp.semver, rangeTmp, options); + return satisfies3(comp.semver, rangeTmp, options); } var sameDirectionIncreasing = (this.operator === ">=" || this.operator === ">") && (comp.operator === ">=" || comp.operator === ">"); var sameDirectionDecreasing = (this.operator === "<=" || this.operator === "<") && (comp.operator === "<=" || comp.operator === "<"); @@ -19534,8 +19534,8 @@ var require_semver = __commonJS({ } return true; } - exports2.satisfies = satisfies; - function satisfies(version, range, options) { + exports2.satisfies = satisfies3; + function satisfies3(version, range, options) { try { range = new Range(range, options); } catch (er) { @@ -19665,7 +19665,7 @@ var require_semver = __commonJS({ default: throw new TypeError('Must provide a hilo val of "<" or ">"'); } - if (satisfies(version, range, options)) { + if (satisfies3(version, range, options)) { return false; } for (var i2 = 0; i2 < range.set.length; ++i2) { @@ -19739,6 +19739,654 @@ var require_semver = __commonJS({ } }); +// node_modules/@renovatebot/pep440/lib/version.js +var require_version = __commonJS({ + "node_modules/@renovatebot/pep440/lib/version.js"(exports2, module2) { + var VERSION_PATTERN = [ + "v?", + "(?:", + /* */ + "(?:(?[0-9]+)!)?", + // epoch + /* */ + "(?[0-9]+(?:\\.[0-9]+)*)", + // release segment + /* */ + "(?
",
+      // pre-release
+      /*    */
+      "[-_\\.]?",
+      /*    */
+      "(?(a|b|c|rc|alpha|beta|pre|preview))",
+      /*    */
+      "[-_\\.]?",
+      /*    */
+      "(?[0-9]+)?",
+      /* */
+      ")?",
+      /* */
+      "(?",
+      // post release
+      /*    */
+      "(?:-(?[0-9]+))",
+      /*    */
+      "|",
+      /*    */
+      "(?:",
+      /*        */
+      "[-_\\.]?",
+      /*        */
+      "(?post|rev|r)",
+      /*        */
+      "[-_\\.]?",
+      /*        */
+      "(?[0-9]+)?",
+      /*    */
+      ")",
+      /* */
+      ")?",
+      /* */
+      "(?",
+      // dev release
+      /*    */
+      "[-_\\.]?",
+      /*    */
+      "(?dev)",
+      /*    */
+      "[-_\\.]?",
+      /*    */
+      "(?[0-9]+)?",
+      /* */
+      ")?",
+      ")",
+      "(?:\\+(?[a-z0-9]+(?:[-_\\.][a-z0-9]+)*))?"
+      // local version
+    ].join("");
+    module2.exports = {
+      VERSION_PATTERN,
+      valid,
+      clean,
+      explain,
+      parse,
+      stringify
+    };
+    var validRegex = new RegExp("^" + VERSION_PATTERN + "$", "i");
+    function valid(version) {
+      return validRegex.test(version) ? version : null;
+    }
+    var cleanRegex = new RegExp("^\\s*" + VERSION_PATTERN + "\\s*$", "i");
+    function clean(version) {
+      return stringify(parse(version, cleanRegex));
+    }
+    function parse(version, regex) {
+      const { groups } = (regex || validRegex).exec(version) || {};
+      if (!groups) {
+        return null;
+      }
+      const parsed = {
+        epoch: Number(groups.epoch ? groups.epoch : 0),
+        release: groups.release.split(".").map(Number),
+        pre: normalize_letter_version(groups.pre_l, groups.pre_n),
+        post: normalize_letter_version(
+          groups.post_l,
+          groups.post_n1 || groups.post_n2
+        ),
+        dev: normalize_letter_version(groups.dev_l, groups.dev_n),
+        local: parse_local_version(groups.local)
+      };
+      return parsed;
+    }
+    function stringify(parsed) {
+      if (!parsed) {
+        return null;
+      }
+      const { epoch, release, pre, post, dev, local } = parsed;
+      const parts = [];
+      if (epoch !== 0) {
+        parts.push(`${epoch}!`);
+      }
+      parts.push(release.join("."));
+      if (pre) {
+        parts.push(pre.join(""));
+      }
+      if (post) {
+        parts.push("." + post.join(""));
+      }
+      if (dev) {
+        parts.push("." + dev.join(""));
+      }
+      if (local) {
+        parts.push(`+${local}`);
+      }
+      return parts.join("");
+    }
+    function normalize_letter_version(letterIn, numberIn) {
+      let letter = letterIn;
+      let number = numberIn;
+      if (letter) {
+        if (!number) {
+          number = 0;
+        }
+        letter = letter.toLowerCase();
+        if (letter === "alpha") {
+          letter = "a";
+        } else if (letter === "beta") {
+          letter = "b";
+        } else if (["c", "pre", "preview"].includes(letter)) {
+          letter = "rc";
+        } else if (["rev", "r"].includes(letter)) {
+          letter = "post";
+        }
+        return [letter, Number(number)];
+      }
+      if (!letter && number) {
+        letter = "post";
+        return [letter, Number(number)];
+      }
+      return null;
+    }
+    function parse_local_version(local) {
+      if (local) {
+        return local.split(/[._-]/).map(
+          (part) => Number.isNaN(Number(part)) ? part.toLowerCase() : Number(part)
+        );
+      }
+      return null;
+    }
+    function explain(version) {
+      const parsed = parse(version);
+      if (!parsed) {
+        return parsed;
+      }
+      const { epoch, release, pre, post, dev, local } = parsed;
+      let base_version = "";
+      if (epoch !== 0) {
+        base_version += epoch + "!";
+      }
+      base_version += release.join(".");
+      const is_prerelease = Boolean(dev || pre);
+      const is_devrelease = Boolean(dev);
+      const is_postrelease = Boolean(post);
+      return {
+        epoch,
+        release,
+        pre,
+        post: post ? post[1] : post,
+        dev: dev ? dev[1] : dev,
+        local: local ? local.join(".") : local,
+        public: stringify(parsed).split("+", 1)[0],
+        base_version,
+        is_prerelease,
+        is_devrelease,
+        is_postrelease
+      };
+    }
+  }
+});
+
+// node_modules/@renovatebot/pep440/lib/operator.js
+var require_operator = __commonJS({
+  "node_modules/@renovatebot/pep440/lib/operator.js"(exports2, module2) {
+    var { parse } = require_version();
+    module2.exports = {
+      compare,
+      rcompare: rcompare2,
+      lt,
+      le,
+      eq,
+      ne,
+      ge,
+      gt,
+      "<": lt,
+      "<=": le,
+      "==": eq,
+      "!=": ne,
+      ">=": ge,
+      ">": gt,
+      "===": arbitrary
+    };
+    function lt(version, other) {
+      return compare(version, other) < 0;
+    }
+    function le(version, other) {
+      return compare(version, other) <= 0;
+    }
+    function eq(version, other) {
+      return compare(version, other) === 0;
+    }
+    function ne(version, other) {
+      return compare(version, other) !== 0;
+    }
+    function ge(version, other) {
+      return compare(version, other) >= 0;
+    }
+    function gt(version, other) {
+      return compare(version, other) > 0;
+    }
+    function arbitrary(version, other) {
+      return version.toLowerCase() === other.toLowerCase();
+    }
+    function compare(version, other) {
+      const parsedVersion = parse(version);
+      const parsedOther = parse(other);
+      const keyVersion = calculateKey(parsedVersion);
+      const keyOther = calculateKey(parsedOther);
+      return pyCompare(keyVersion, keyOther);
+    }
+    function rcompare2(version, other) {
+      return -compare(version, other);
+    }
+    function pyCompare(elemIn, otherIn) {
+      let elem = elemIn;
+      let other = otherIn;
+      if (elem === other) {
+        return 0;
+      }
+      if (Array.isArray(elem) !== Array.isArray(other)) {
+        elem = Array.isArray(elem) ? elem : [elem];
+        other = Array.isArray(other) ? other : [other];
+      }
+      if (Array.isArray(elem)) {
+        const len = Math.min(elem.length, other.length);
+        for (let i = 0; i < len; i += 1) {
+          const res = pyCompare(elem[i], other[i]);
+          if (res !== 0) {
+            return res;
+          }
+        }
+        return elem.length - other.length;
+      }
+      if (elem === -Infinity || other === Infinity) {
+        return -1;
+      }
+      if (elem === Infinity || other === -Infinity) {
+        return 1;
+      }
+      return elem < other ? -1 : 1;
+    }
+    function calculateKey(input) {
+      const { epoch } = input;
+      let { release, pre, post, local, dev } = input;
+      release = release.concat();
+      release.reverse();
+      while (release.length && release[0] === 0) {
+        release.shift();
+      }
+      release.reverse();
+      if (!pre && !post && dev) pre = -Infinity;
+      else if (!pre) pre = Infinity;
+      if (!post) post = -Infinity;
+      if (!dev) dev = Infinity;
+      if (!local) {
+        local = -Infinity;
+      } else {
+        local = local.map(
+          (i) => Number.isNaN(Number(i)) ? [-Infinity, i] : [Number(i), ""]
+        );
+      }
+      return [epoch, release, pre, post, dev, local];
+    }
+  }
+});
+
+// node_modules/@renovatebot/pep440/lib/specifier.js
+var require_specifier = __commonJS({
+  "node_modules/@renovatebot/pep440/lib/specifier.js"(exports2, module2) {
+    var { VERSION_PATTERN, explain: explainVersion } = require_version();
+    var Operator = require_operator();
+    var RANGE_PATTERN = [
+      "(?(===|~=|==|!=|<=|>=|<|>))",
+      "\\s*",
+      "(",
+      /*  */
+      "(?(?:" + VERSION_PATTERN.replace(/\?<\w+>/g, "?:") + "))",
+      /*  */
+      "(?\\.\\*)?",
+      /*  */
+      "|",
+      /*  */
+      "(?[^,;\\s)]+)",
+      ")"
+    ].join("");
+    module2.exports = {
+      RANGE_PATTERN,
+      parse,
+      satisfies: satisfies3,
+      filter,
+      validRange,
+      maxSatisfying,
+      minSatisfying
+    };
+    var isEqualityOperator = (op) => ["==", "!=", "==="].includes(op);
+    var rangeRegex = new RegExp("^" + RANGE_PATTERN + "$", "i");
+    function parse(ranges) {
+      if (!ranges.trim()) {
+        return [];
+      }
+      const specifiers = ranges.split(",").map((range) => rangeRegex.exec(range.trim()) || {}).map(({ groups }) => {
+        if (!groups) {
+          return null;
+        }
+        let { ...spec } = groups;
+        const { operator, version, prefix, legacy } = groups;
+        if (version) {
+          spec = { ...spec, ...explainVersion(version) };
+          if (operator === "~=") {
+            if (spec.release.length < 2) {
+              return null;
+            }
+          }
+          if (!isEqualityOperator(operator) && spec.local) {
+            return null;
+          }
+          if (prefix) {
+            if (!isEqualityOperator(operator) || spec.dev || spec.local) {
+              return null;
+            }
+          }
+        }
+        if (legacy && operator !== "===") {
+          return null;
+        }
+        return spec;
+      });
+      if (specifiers.filter(Boolean).length !== specifiers.length) {
+        return null;
+      }
+      return specifiers;
+    }
+    function filter(versions, specifier, options = {}) {
+      const filtered = pick(versions, specifier, options);
+      if (filtered.length === 0 && options.prereleases === void 0) {
+        return pick(versions, specifier, { prereleases: true });
+      }
+      return filtered;
+    }
+    function maxSatisfying(versions, range, options) {
+      const found = filter(versions, range, options).sort(Operator.compare);
+      return found.length === 0 ? null : found[found.length - 1];
+    }
+    function minSatisfying(versions, range, options) {
+      const found = filter(versions, range, options).sort(Operator.compare);
+      return found.length === 0 ? null : found[0];
+    }
+    function pick(versions, specifier, options) {
+      const parsed = parse(specifier);
+      if (!parsed) {
+        return [];
+      }
+      return versions.filter((version) => {
+        const explained = explainVersion(version);
+        if (!parsed.length) {
+          return explained && !(explained.is_prerelease && !options.prereleases);
+        }
+        return parsed.reduce((pass, spec) => {
+          if (!pass) {
+            return false;
+          }
+          return contains({ ...spec, ...options }, { version, explained });
+        }, true);
+      });
+    }
+    function satisfies3(version, specifier, options = {}) {
+      const filtered = pick([version], specifier, options);
+      return filtered.length === 1;
+    }
+    function arrayStartsWith(array, prefix) {
+      if (prefix.length > array.length) {
+        return false;
+      }
+      for (let i = 0; i < prefix.length; i += 1) {
+        if (prefix[i] !== array[i]) {
+          return false;
+        }
+      }
+      return true;
+    }
+    function contains(specifier, input) {
+      const { explained } = input;
+      let { version } = input;
+      const { ...spec } = specifier;
+      if (spec.prereleases === void 0) {
+        spec.prereleases = spec.is_prerelease;
+      }
+      if (explained && explained.is_prerelease && !spec.prereleases) {
+        return false;
+      }
+      if (spec.operator === "~=") {
+        let compatiblePrefix = spec.release.slice(0, -1).concat("*").join(".");
+        if (spec.epoch) {
+          compatiblePrefix = spec.epoch + "!" + compatiblePrefix;
+        }
+        return satisfies3(version, `>=${spec.version}, ==${compatiblePrefix}`, {
+          prereleases: spec.prereleases
+        });
+      }
+      if (spec.prefix) {
+        const isMatching = explained.epoch === spec.epoch && arrayStartsWith(explained.release, spec.release);
+        const isEquality = spec.operator !== "!=";
+        return isEquality ? isMatching : !isMatching;
+      }
+      if (explained) {
+        if (explained.local && spec.version) {
+          version = explained.public;
+          spec.version = explainVersion(spec.version).public;
+        }
+      }
+      if (spec.operator === "<" || spec.operator === ">") {
+        if (Operator.eq(spec.release.join("."), explained.release.join("."))) {
+          return false;
+        }
+      }
+      const op = Operator[spec.operator];
+      return op(version, spec.version || spec.legacy);
+    }
+    function validRange(specifier) {
+      return Boolean(parse(specifier));
+    }
+  }
+});
+
+// node_modules/@renovatebot/pep440/lib/semantic.js
+var require_semantic = __commonJS({
+  "node_modules/@renovatebot/pep440/lib/semantic.js"(exports2, module2) {
+    var { explain, parse, stringify } = require_version();
+    module2.exports = {
+      major,
+      minor,
+      patch,
+      inc
+    };
+    function major(input) {
+      const version = explain(input);
+      if (!version) {
+        throw new TypeError("Invalid Version: " + input);
+      }
+      return version.release[0];
+    }
+    function minor(input) {
+      const version = explain(input);
+      if (!version) {
+        throw new TypeError("Invalid Version: " + input);
+      }
+      if (version.release.length < 2) {
+        return 0;
+      }
+      return version.release[1];
+    }
+    function patch(input) {
+      const version = explain(input);
+      if (!version) {
+        throw new TypeError("Invalid Version: " + input);
+      }
+      if (version.release.length < 3) {
+        return 0;
+      }
+      return version.release[2];
+    }
+    function inc(input, release, preReleaseIdentifier) {
+      let identifier = preReleaseIdentifier || `a`;
+      const version = parse(input);
+      if (!version) {
+        return null;
+      }
+      if (!["a", "b", "c", "rc", "alpha", "beta", "pre", "preview"].includes(
+        identifier
+      )) {
+        return null;
+      }
+      switch (release) {
+        case "premajor":
+          {
+            const [majorVersion] = version.release;
+            version.release.fill(0);
+            version.release[0] = majorVersion + 1;
+          }
+          version.pre = [identifier, 0];
+          delete version.post;
+          delete version.dev;
+          delete version.local;
+          break;
+        case "preminor":
+          {
+            const [majorVersion, minorVersion = 0] = version.release;
+            version.release.fill(0);
+            version.release[0] = majorVersion;
+            version.release[1] = minorVersion + 1;
+          }
+          version.pre = [identifier, 0];
+          delete version.post;
+          delete version.dev;
+          delete version.local;
+          break;
+        case "prepatch":
+          {
+            const [majorVersion, minorVersion = 0, patchVersion = 0] = version.release;
+            version.release.fill(0);
+            version.release[0] = majorVersion;
+            version.release[1] = minorVersion;
+            version.release[2] = patchVersion + 1;
+          }
+          version.pre = [identifier, 0];
+          delete version.post;
+          delete version.dev;
+          delete version.local;
+          break;
+        case "prerelease":
+          if (version.pre === null) {
+            const [majorVersion, minorVersion = 0, patchVersion = 0] = version.release;
+            version.release.fill(0);
+            version.release[0] = majorVersion;
+            version.release[1] = minorVersion;
+            version.release[2] = patchVersion + 1;
+            version.pre = [identifier, 0];
+          } else {
+            if (preReleaseIdentifier === void 0 && version.pre !== null) {
+              [identifier] = version.pre;
+            }
+            const [letter, number] = version.pre;
+            if (letter === identifier) {
+              version.pre = [letter, number + 1];
+            } else {
+              version.pre = [identifier, 0];
+            }
+          }
+          delete version.post;
+          delete version.dev;
+          delete version.local;
+          break;
+        case "major":
+          if (version.release.slice(1).some((value) => value !== 0) || version.pre === null) {
+            const [majorVersion] = version.release;
+            version.release.fill(0);
+            version.release[0] = majorVersion + 1;
+          }
+          delete version.pre;
+          delete version.post;
+          delete version.dev;
+          delete version.local;
+          break;
+        case "minor":
+          if (version.release.slice(2).some((value) => value !== 0) || version.pre === null) {
+            const [majorVersion, minorVersion = 0] = version.release;
+            version.release.fill(0);
+            version.release[0] = majorVersion;
+            version.release[1] = minorVersion + 1;
+          }
+          delete version.pre;
+          delete version.post;
+          delete version.dev;
+          delete version.local;
+          break;
+        case "patch":
+          if (version.release.slice(3).some((value) => value !== 0) || version.pre === null) {
+            const [majorVersion, minorVersion = 0, patchVersion = 0] = version.release;
+            version.release.fill(0);
+            version.release[0] = majorVersion;
+            version.release[1] = minorVersion;
+            version.release[2] = patchVersion + 1;
+          }
+          delete version.pre;
+          delete version.post;
+          delete version.dev;
+          delete version.local;
+          break;
+        default:
+          return null;
+      }
+      return stringify(version);
+    }
+  }
+});
+
+// node_modules/@renovatebot/pep440/index.js
+var require_pep440 = __commonJS({
+  "node_modules/@renovatebot/pep440/index.js"(exports2, module2) {
+    var { valid, clean, explain, parse } = require_version();
+    var { lt, le, eq, ne, ge, gt, compare, rcompare: rcompare2 } = require_operator();
+    var {
+      filter,
+      maxSatisfying,
+      minSatisfying,
+      RANGE_PATTERN,
+      satisfies: satisfies3,
+      validRange
+    } = require_specifier();
+    var { major, minor, patch, inc } = require_semantic();
+    module2.exports = {
+      // version
+      valid,
+      clean,
+      explain,
+      parse,
+      // operator
+      lt,
+      le,
+      lte: le,
+      eq,
+      ne,
+      neq: ne,
+      ge,
+      gte: ge,
+      gt,
+      compare,
+      rcompare: rcompare2,
+      // range
+      filter,
+      maxSatisfying,
+      minSatisfying,
+      RANGE_PATTERN,
+      satisfies: satisfies3,
+      validRange,
+      // semantic
+      major,
+      minor,
+      patch,
+      inc
+    };
+  }
+});
+
 // node_modules/undici/lib/core/symbols.js
 var require_symbols6 = __commonJS({
   "node_modules/undici/lib/core/symbols.js"(exports2, module2) {
@@ -44945,7 +45593,7 @@ function info(message) {
 }
 
 // src/update-known-checksums.ts
-var semver = __toESM(require_semver(), 1);
+var semver2 = __toESM(require_semver(), 1);
 
 // src/download/checksum/known-checksums.ts
 var KNOWN_CHECKSUMS = {
@@ -49376,6 +50024,10 @@ async function updateChecksums(filePath, checksumEntries) {
   await import_node_fs.promises.writeFile(filePath, content);
 }
 
+// src/download/versions-client.ts
+var pep440 = __toESM(require_pep440(), 1);
+var semver = __toESM(require_semver(), 1);
+
 // src/utils/constants.ts
 var VERSIONS_NDJSON_URL = "https://raw.githubusercontent.com/astral-sh/versions/main/v1/uv.ndjson";
 
@@ -49399,6 +50051,8 @@ var fetch = async (url, opts) => await (0, import_undici2.fetch)(url, {
 
 // src/download/versions-client.ts
 var cachedVersionData = /* @__PURE__ */ new Map();
+var cachedLatestVersionData = /* @__PURE__ */ new Map();
+var cachedVersionLookup = /* @__PURE__ */ new Map();
 async function fetchVersionData(url = VERSIONS_NDJSON_URL) {
   const cachedVersions = cachedVersionData.get(url);
   if (cachedVersions !== void 0) {
@@ -49406,15 +50060,8 @@ async function fetchVersionData(url = VERSIONS_NDJSON_URL) {
     return cachedVersions;
   }
   info(`Fetching version data from ${url} ...`);
-  const response = await fetch(url, {});
-  if (!response.ok) {
-    throw new Error(
-      `Failed to fetch version data: ${response.status} ${response.statusText}`
-    );
-  }
-  const body = await response.text();
-  const versions = parseVersionData(body, url);
-  cachedVersionData.set(url, versions);
+  const { versions } = await readVersionData(url);
+  cacheCompleteVersionData(url, versions);
   return versions;
 }
 function parseVersionData(data, sourceDescription) {
@@ -49424,20 +50071,7 @@ function parseVersionData(data, sourceDescription) {
     if (trimmed === "") {
       continue;
     }
-    let parsed;
-    try {
-      parsed = JSON.parse(trimmed);
-    } catch (error) {
-      throw new Error(
-        `Failed to parse version data from ${sourceDescription} at line ${index + 1}: ${error.message}`
-      );
-    }
-    if (!isNdjsonVersion(parsed)) {
-      throw new Error(
-        `Invalid NDJSON record in ${sourceDescription} at line ${index + 1}.`
-      );
-    }
-    versions.push(parsed);
+    versions.push(parseVersionLine(trimmed, sourceDescription, index + 1));
   }
   if (versions.length === 0) {
     throw new Error(`No version data found in ${sourceDescription}.`);
@@ -49445,13 +50079,132 @@ function parseVersionData(data, sourceDescription) {
   return versions;
 }
 async function getLatestVersion() {
-  const versions = await fetchVersionData();
-  const latestVersion = versions[0]?.version;
+  const cachedVersions = cachedVersionData.get(VERSIONS_NDJSON_URL);
+  const cachedLatestVersion = cachedVersions?.[0] ?? cachedLatestVersionData.get(VERSIONS_NDJSON_URL);
+  if (cachedLatestVersion !== void 0) {
+    debug(
+      `Latest version from NDJSON cache: ${cachedLatestVersion.version}`
+    );
+    return cachedLatestVersion.version;
+  }
+  const latestVersion = await findVersionData(() => true);
   if (!latestVersion) {
     throw new Error("No versions found in NDJSON data");
   }
-  debug(`Latest version from NDJSON: ${latestVersion}`);
-  return latestVersion;
+  debug(`Latest version from NDJSON: ${latestVersion.version}`);
+  return latestVersion.version;
+}
+async function findVersionData(predicate, url = VERSIONS_NDJSON_URL) {
+  const cachedVersions = cachedVersionData.get(url);
+  if (cachedVersions !== void 0) {
+    return cachedVersions.find(predicate);
+  }
+  const { matchedVersion, versions, complete } = await readVersionData(
+    url,
+    predicate
+  );
+  if (complete) {
+    cacheCompleteVersionData(url, versions);
+  }
+  return matchedVersion;
+}
+async function readVersionData(url, stopWhen) {
+  const response = await fetch(url, {});
+  if (!response.ok) {
+    throw new Error(
+      `Failed to fetch version data: ${response.status} ${response.statusText}`
+    );
+  }
+  if (response.body === null) {
+    const body = await response.text();
+    const versions2 = parseVersionData(body, url);
+    const matchedVersion2 = stopWhen ? versions2.find((candidate) => stopWhen(candidate)) : void 0;
+    return { complete: true, matchedVersion: matchedVersion2, versions: versions2 };
+  }
+  const versions = [];
+  let lineNumber = 0;
+  let matchedVersion;
+  let buffer = "";
+  const decoder = new TextDecoder();
+  const reader = response.body.getReader();
+  const processLine = (line) => {
+    const trimmed = line.trim();
+    if (trimmed === "") {
+      return false;
+    }
+    lineNumber += 1;
+    const versionData = parseVersionLine(trimmed, url, lineNumber);
+    if (versions.length === 0) {
+      cachedLatestVersionData.set(url, versionData);
+    }
+    versions.push(versionData);
+    cacheVersion(url, versionData);
+    if (stopWhen?.(versionData) === true) {
+      matchedVersion = versionData;
+      return true;
+    }
+    return false;
+  };
+  while (true) {
+    const { done, value } = await reader.read();
+    if (done) {
+      buffer += decoder.decode();
+      break;
+    }
+    buffer += decoder.decode(value, { stream: true });
+    let newlineIndex = buffer.indexOf("\n");
+    while (newlineIndex !== -1) {
+      const line = buffer.slice(0, newlineIndex);
+      buffer = buffer.slice(newlineIndex + 1);
+      if (processLine(line)) {
+        await reader.cancel();
+        return { complete: false, matchedVersion, versions };
+      }
+      newlineIndex = buffer.indexOf("\n");
+    }
+  }
+  if (buffer.trim() !== "" && processLine(buffer)) {
+    return { complete: true, matchedVersion, versions };
+  }
+  if (versions.length === 0) {
+    throw new Error(`No version data found in ${url}.`);
+  }
+  return { complete: true, matchedVersion, versions };
+}
+function cacheCompleteVersionData(url, versions) {
+  cachedVersionData.set(url, versions);
+  if (versions[0] !== void 0) {
+    cachedLatestVersionData.set(url, versions[0]);
+  }
+  const versionLookup = /* @__PURE__ */ new Map();
+  for (const versionData of versions) {
+    versionLookup.set(versionData.version, versionData);
+  }
+  cachedVersionLookup.set(url, versionLookup);
+}
+function cacheVersion(url, versionData) {
+  let versionLookup = cachedVersionLookup.get(url);
+  if (versionLookup === void 0) {
+    versionLookup = /* @__PURE__ */ new Map();
+    cachedVersionLookup.set(url, versionLookup);
+  }
+  versionLookup.set(versionData.version, versionData);
+}
+function parseVersionLine(line, sourceDescription, lineNumber) {
+  let parsed;
+  try {
+    parsed = JSON.parse(line);
+  } catch (error) {
+    throw new Error(
+      `Failed to parse version data from ${sourceDescription} at line ${lineNumber}: ${error.message}`
+    );
+  }
+  if (!isNdjsonVersion(parsed)) {
+    throw new Error(
+      `Invalid NDJSON record in ${sourceDescription} at line ${lineNumber}.`
+    );
+  }
+  return parsed;
 }
 function isNdjsonVersion(value) {
   if (!isRecord(value)) {
@@ -49484,7 +50237,7 @@ async function run() {
   }
   const latestVersion = await getLatestVersion();
   const latestKnownVersion = getLatestKnownVersionFromChecksums();
-  if (semver.lte(latestVersion, latestKnownVersion)) {
+  if (semver2.lte(latestVersion, latestKnownVersion)) {
     info(
       `Latest release (${latestVersion}) is not newer than the latest known version (${latestKnownVersion}). Skipping update.`
     );
@@ -49503,7 +50256,7 @@ function getLatestKnownVersionFromChecksums() {
       versions.add(version);
     }
   }
-  const latestVersion = [...versions].sort(semver.rcompare)[0];
+  const latestVersion = [...versions].sort(semver2.rcompare)[0];
   if (!latestVersion) {
     throw new Error("Could not determine latest known version from checksums.");
   }
diff --git a/package.json b/package.json
index a8cde6b..74c46fe 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
     "build": "tsc --noEmit",
     "check": "biome check --write",
     "package": "node scripts/build-dist.mjs",
+    "bench:versions": "node scripts/bench-versions-client.mjs",
     "test:unit": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
     "test": "npm run build && npm run test:unit",
     "act": "act pull_request -W .github/workflows/test.yml --container-architecture linux/amd64 -s GITHUB_TOKEN=\"$(gh auth token)\"",
diff --git a/scripts/bench-versions-client.mjs b/scripts/bench-versions-client.mjs
new file mode 100644
index 0000000..78fdc03
--- /dev/null
+++ b/scripts/bench-versions-client.mjs
@@ -0,0 +1,483 @@
+import { performance } from "node:perf_hooks";
+import * as pep440 from "@renovatebot/pep440";
+import * as semver from "semver";
+import { ProxyAgent, fetch as undiciFetch } from "undici";
+
+const DEFAULT_URL =
+  "https://raw.githubusercontent.com/astral-sh/versions/main/v1/uv.ndjson";
+const DEFAULT_ITERATIONS = 100;
+const DEFAULT_ARCH = "aarch64";
+const DEFAULT_PLATFORM = "apple-darwin";
+
+function getProxyAgent() {
+  const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy;
+  if (httpProxy) {
+    return new ProxyAgent(httpProxy);
+  }
+
+  const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
+  if (httpsProxy) {
+    return new ProxyAgent(httpsProxy);
+  }
+
+  return undefined;
+}
+
+async function fetch(url) {
+  return await undiciFetch(url, {
+    dispatcher: getProxyAgent(),
+  });
+}
+
+function parseArgs(argv) {
+  const options = {
+    arch: DEFAULT_ARCH,
+    iterations: DEFAULT_ITERATIONS,
+    platform: DEFAULT_PLATFORM,
+    url: DEFAULT_URL,
+  };
+
+  for (let index = 0; index < argv.length; index += 1) {
+    const arg = argv[index];
+    const next = argv[index + 1];
+
+    if (arg === "--iterations" && next !== undefined) {
+      options.iterations = Number.parseInt(next, 10);
+      index += 1;
+      continue;
+    }
+
+    if (arg === "--url" && next !== undefined) {
+      options.url = next;
+      index += 1;
+      continue;
+    }
+
+    if (arg === "--arch" && next !== undefined) {
+      options.arch = next;
+      index += 1;
+      continue;
+    }
+
+    if (arg === "--platform" && next !== undefined) {
+      options.platform = next;
+      index += 1;
+    }
+  }
+
+  if (!Number.isInteger(options.iterations) || options.iterations <= 0) {
+    throw new Error("--iterations must be a positive integer");
+  }
+
+  return options;
+}
+
+function parseVersionLine(line, sourceDescription, lineNumber) {
+  let parsed;
+  try {
+    parsed = JSON.parse(line);
+  } catch (error) {
+    throw new Error(
+      `Failed to parse version data from ${sourceDescription} at line ${lineNumber}: ${error.message}`,
+    );
+  }
+
+  if (
+    typeof parsed !== "object" ||
+    parsed === null ||
+    typeof parsed.version !== "string" ||
+    !Array.isArray(parsed.artifacts)
+  ) {
+    throw new Error(
+      `Invalid NDJSON record in ${sourceDescription} at line ${lineNumber}.`,
+    );
+  }
+
+  return parsed;
+}
+
+function parseVersionData(data, sourceDescription) {
+  const versions = [];
+
+  for (const [index, line] of data.split("\n").entries()) {
+    const trimmed = line.trim();
+    if (trimmed === "") {
+      continue;
+    }
+
+    versions.push(parseVersionLine(trimmed, sourceDescription, index + 1));
+  }
+
+  if (versions.length === 0) {
+    throw new Error(`No version data found in ${sourceDescription}.`);
+  }
+
+  return versions;
+}
+
+async function readEntireResponse(response) {
+  if (response.body === null) {
+    const text = await response.text();
+    return {
+      bytesRead: Buffer.byteLength(text, "utf8"),
+      text,
+    };
+  }
+
+  const reader = response.body.getReader();
+  const decoder = new TextDecoder();
+  const chunks = [];
+  let bytesRead = 0;
+
+  while (true) {
+    const { done, value } = await reader.read();
+    if (done) {
+      chunks.push(decoder.decode());
+      break;
+    }
+
+    bytesRead += value.byteLength;
+    chunks.push(decoder.decode(value, { stream: true }));
+  }
+
+  return {
+    bytesRead,
+    text: chunks.join(""),
+  };
+}
+
+async function fetchAllVersions(url) {
+  const response = await fetch(url);
+  if (!response.ok) {
+    throw new Error(
+      `Failed to fetch version data: ${response.status} ${response.statusText}`,
+    );
+  }
+
+  const { bytesRead, text } = await readEntireResponse(response);
+  return {
+    bytesRead,
+    versions: parseVersionData(text, url),
+  };
+}
+
+async function streamUntil(url, predicate) {
+  const response = await fetch(url);
+  if (!response.ok) {
+    throw new Error(
+      `Failed to fetch version data: ${response.status} ${response.statusText}`,
+    );
+  }
+
+  if (response.body === null) {
+    const { bytesRead, versions } = await fetchAllVersions(url);
+    return {
+      bytesRead,
+      matchedVersion: versions.find(predicate),
+    };
+  }
+
+  const reader = response.body.getReader();
+  const decoder = new TextDecoder();
+  let bytesRead = 0;
+  let buffer = "";
+  let lineNumber = 0;
+
+  while (true) {
+    const { done, value } = await reader.read();
+    if (done) {
+      buffer += decoder.decode();
+      break;
+    }
+
+    bytesRead += value.byteLength;
+    buffer += decoder.decode(value, { stream: true });
+
+    let newlineIndex = buffer.indexOf("\n");
+    while (newlineIndex !== -1) {
+      const line = buffer.slice(0, newlineIndex);
+      buffer = buffer.slice(newlineIndex + 1);
+      const trimmed = line.trim();
+
+      if (trimmed !== "") {
+        lineNumber += 1;
+        const versionData = parseVersionLine(trimmed, url, lineNumber);
+        if (predicate(versionData)) {
+          await reader.cancel();
+          return { bytesRead, matchedVersion: versionData };
+        }
+      }
+
+      newlineIndex = buffer.indexOf("\n");
+    }
+  }
+
+  if (buffer.trim() !== "") {
+    lineNumber += 1;
+    const versionData = parseVersionLine(buffer.trim(), url, lineNumber);
+    if (predicate(versionData)) {
+      return { bytesRead, matchedVersion: versionData };
+    }
+  }
+
+  return { bytesRead, matchedVersion: undefined };
+}
+
+function versionSatisfies(version, versionSpecifier) {
+  return (
+    semver.satisfies(version, versionSpecifier) ||
+    pep440.satisfies(version, versionSpecifier)
+  );
+}
+
+function maxSatisfying(versions, versionSpecifier) {
+  const semverMatch = semver.maxSatisfying(versions, versionSpecifier);
+  if (semverMatch !== null) {
+    return semverMatch;
+  }
+
+  return pep440.maxSatisfying(versions, versionSpecifier) ?? undefined;
+}
+
+function selectArtifact(artifacts) {
+  if (artifacts.length === 1) {
+    return artifacts[0];
+  }
+
+  const defaultVariant = artifacts.find(
+    (candidate) => candidate.variant === "default",
+  );
+  if (defaultVariant !== undefined) {
+    return defaultVariant;
+  }
+
+  return artifacts[0];
+}
+
+async function benchmarkCase(name, expected, implementations, iterations) {
+  const results = {
+    name,
+    new: [],
+    old: [],
+  };
+
+  for (let iteration = 0; iteration < iterations; iteration += 1) {
+    const order = iteration % 2 === 0 ? ["old", "new"] : ["new", "old"];
+
+    for (const label of order) {
+      const implementation = implementations[label];
+      const startedAt = performance.now();
+      const outcome = await implementation.run();
+      const durationMs = performance.now() - startedAt;
+
+      if (outcome.value !== expected) {
+        throw new Error(
+          `${name} ${label} produced ${JSON.stringify(outcome.value)}; expected ${JSON.stringify(expected)}`,
+        );
+      }
+
+      results[label].push({
+        bytesRead: outcome.bytesRead,
+        durationMs,
+      });
+    }
+  }
+
+  return results;
+}
+
+function summarize(samples) {
+  const durations = samples
+    .map((sample) => sample.durationMs)
+    .sort((left, right) => left - right);
+  const bytes = samples
+    .map((sample) => sample.bytesRead)
+    .sort((left, right) => left - right);
+
+  const sum = (values) => values.reduce((total, value) => total + value, 0);
+  const percentile = (values, ratio) => {
+    const index = Math.min(
+      values.length - 1,
+      Math.max(0, Math.ceil(values.length * ratio) - 1),
+    );
+    return values[index];
+  };
+
+  return {
+    avgBytes: sum(bytes) / bytes.length,
+    avgMs: sum(durations) / durations.length,
+    maxMs: durations[durations.length - 1],
+    medianMs: percentile(durations, 0.5),
+    minMs: durations[0],
+    p95Ms: percentile(durations, 0.95),
+  };
+}
+
+function formatNumber(value, digits = 2) {
+  return value.toFixed(digits);
+}
+
+function formatSummary(name, oldSummary, newSummary) {
+  const speedup = oldSummary.avgMs / newSummary.avgMs;
+  const timeReduction =
+    ((oldSummary.avgMs - newSummary.avgMs) / oldSummary.avgMs) * 100;
+  const byteReduction =
+    ((oldSummary.avgBytes - newSummary.avgBytes) / oldSummary.avgBytes) * 100;
+
+  return [
+    `Scenario: ${name}`,
+    `  old avg: ${formatNumber(oldSummary.avgMs)} ms | median: ${formatNumber(oldSummary.medianMs)} ms | p95: ${formatNumber(oldSummary.p95Ms)} ms | avg bytes: ${Math.round(oldSummary.avgBytes)}`,
+    `  new avg: ${formatNumber(newSummary.avgMs)} ms | median: ${formatNumber(newSummary.medianMs)} ms | p95: ${formatNumber(newSummary.p95Ms)} ms | avg bytes: ${Math.round(newSummary.avgBytes)}`,
+    `  delta: ${formatNumber(timeReduction)}% faster | ${formatNumber(speedup)}x speedup | ${formatNumber(byteReduction)}% fewer bytes read`,
+  ].join("\n");
+}
+
+async function main() {
+  const options = parseArgs(process.argv.slice(2));
+  console.log(`Preparing benchmark data from ${options.url}`);
+  const baseline = await fetchAllVersions(options.url);
+  const latestVersion = baseline.versions[0]?.version;
+  if (!latestVersion) {
+    throw new Error("No versions found in NDJSON data");
+  }
+
+  const latestArtifact = selectArtifact(
+    baseline.versions[0].artifacts.filter(
+      (candidate) =>
+        candidate.platform === `${options.arch}-${options.platform}`,
+    ),
+  );
+  if (!latestArtifact) {
+    throw new Error(
+      `No artifact found for ${options.arch}-${options.platform} in ${latestVersion}`,
+    );
+  }
+
+  const rangeSpecifier = `^${latestVersion.split(".")[0]}.${latestVersion.split(".")[1]}.0`;
+
+  console.log(
+    `Running ${options.iterations} iterations per scenario against ${options.url}`,
+  );
+  console.log(`Latest version: ${latestVersion}`);
+  console.log(`Range benchmark: ${rangeSpecifier}`);
+  console.log(`Artifact benchmark: ${options.arch}-${options.platform}`);
+  console.log("");
+
+  const scenarios = [
+    await benchmarkCase(
+      "latest version",
+      latestVersion,
+      {
+        new: {
+          run: async () => {
+            const { bytesRead, matchedVersion } = await streamUntil(
+              options.url,
+              () => true,
+            );
+            return {
+              bytesRead,
+              value: matchedVersion?.version,
+            };
+          },
+        },
+        old: {
+          run: async () => {
+            const { bytesRead, versions } = await fetchAllVersions(options.url);
+            return {
+              bytesRead,
+              value: versions[0]?.version,
+            };
+          },
+        },
+      },
+      options.iterations,
+    ),
+    await benchmarkCase(
+      "highest satisfying range",
+      latestVersion,
+      {
+        new: {
+          run: async () => {
+            const { bytesRead, matchedVersion } = await streamUntil(
+              options.url,
+              (candidate) =>
+                versionSatisfies(candidate.version, rangeSpecifier),
+            );
+            return {
+              bytesRead,
+              value: matchedVersion?.version,
+            };
+          },
+        },
+        old: {
+          run: async () => {
+            const { bytesRead, versions } = await fetchAllVersions(options.url);
+            return {
+              bytesRead,
+              value: maxSatisfying(
+                versions.map((versionData) => versionData.version),
+                rangeSpecifier,
+              ),
+            };
+          },
+        },
+      },
+      options.iterations,
+    ),
+    await benchmarkCase(
+      "exact version artifact",
+      latestArtifact.url,
+      {
+        new: {
+          run: async () => {
+            const { bytesRead, matchedVersion } = await streamUntil(
+              options.url,
+              (candidate) => candidate.version === latestVersion,
+            );
+            const artifact = matchedVersion
+              ? selectArtifact(
+                  matchedVersion.artifacts.filter(
+                    (candidate) =>
+                      candidate.platform ===
+                      `${options.arch}-${options.platform}`,
+                  ),
+                )
+              : undefined;
+            return {
+              bytesRead,
+              value: artifact?.url,
+            };
+          },
+        },
+        old: {
+          run: async () => {
+            const { bytesRead, versions } = await fetchAllVersions(options.url);
+            const versionData = versions.find(
+              (candidate) => candidate.version === latestVersion,
+            );
+            const artifact = selectArtifact(
+              versionData.artifacts.filter(
+                (candidate) =>
+                  candidate.platform === `${options.arch}-${options.platform}`,
+              ),
+            );
+            return {
+              bytesRead,
+              value: artifact?.url,
+            };
+          },
+        },
+      },
+      options.iterations,
+    ),
+  ];
+
+  for (const scenario of scenarios) {
+    const oldSummary = summarize(scenario.old);
+    const newSummary = summarize(scenario.new);
+    console.log(formatSummary(scenario.name, oldSummary, newSummary));
+    console.log("");
+  }
+}
+
+await main();
diff --git a/src/download/download-version.ts b/src/download/download-version.ts
index ed9207c..32dbf7e 100644
--- a/src/download/download-version.ts
+++ b/src/download/download-version.ts
@@ -15,6 +15,7 @@ import {
 import {
   getAllVersions as getAllVersionsFromNdjson,
   getArtifact as getArtifactFromNdjson,
+  getHighestSatisfyingVersion as getHighestSatisfyingVersionFromNdjson,
   getLatestVersion as getLatestVersionFromNdjson,
 } from "./versions-client";
 
@@ -187,6 +188,17 @@ export async function resolveVersion(
     return version;
   }
 
+  if (manifestUrl === undefined && resolutionStrategy === "highest") {
+    const resolvedVersion =
+      await getHighestSatisfyingVersionFromNdjson(version);
+    if (resolvedVersion !== undefined) {
+      core.debug(`Resolved version from NDJSON stream: ${resolvedVersion}`);
+      return resolvedVersion;
+    }
+
+    throw new Error(`No version found for ${version}`);
+  }
+
   const availableVersions = await getAvailableVersions(manifestUrl);
   core.debug(`Available versions: ${availableVersions}`);
   const resolvedVersion =
diff --git a/src/download/versions-client.ts b/src/download/versions-client.ts
index f11c091..ae631ea 100644
--- a/src/download/versions-client.ts
+++ b/src/download/versions-client.ts
@@ -1,4 +1,6 @@
 import * as core from "@actions/core";
+import * as pep440 from "@renovatebot/pep440";
+import * as semver from "semver";
 import { VERSIONS_NDJSON_URL } from "../utils/constants";
 import { fetch } from "../utils/fetch";
 import { selectDefaultVariant } from "./variant-selection";
@@ -23,6 +25,8 @@ export interface ArtifactResult {
 }
 
 const cachedVersionData = new Map();
+const cachedLatestVersionData = new Map();
+const cachedVersionLookup = new Map>();
 
 export async function fetchVersionData(
   url: string = VERSIONS_NDJSON_URL,
@@ -34,16 +38,8 @@ export async function fetchVersionData(
   }
 
   core.info(`Fetching version data from ${url} ...`);
-  const response = await fetch(url, {});
-  if (!response.ok) {
-    throw new Error(
-      `Failed to fetch version data: ${response.status} ${response.statusText}`,
-    );
-  }
-
-  const body = await response.text();
-  const versions = parseVersionData(body, url);
-  cachedVersionData.set(url, versions);
+  const { versions } = await readVersionData(url);
+  cacheCompleteVersionData(url, versions);
   return versions;
 }
 
@@ -59,22 +55,7 @@ export function parseVersionData(
       continue;
     }
 
-    let parsed: unknown;
-    try {
-      parsed = JSON.parse(trimmed);
-    } catch (error) {
-      throw new Error(
-        `Failed to parse version data from ${sourceDescription} at line ${index + 1}: ${(error as Error).message}`,
-      );
-    }
-
-    if (!isNdjsonVersion(parsed)) {
-      throw new Error(
-        `Invalid NDJSON record in ${sourceDescription} at line ${index + 1}.`,
-      );
-    }
-
-    versions.push(parsed);
+    versions.push(parseVersionLine(trimmed, sourceDescription, index + 1));
   }
 
   if (versions.length === 0) {
@@ -85,14 +66,23 @@ export function parseVersionData(
 }
 
 export async function getLatestVersion(): Promise {
-  const versions = await fetchVersionData();
-  const latestVersion = versions[0]?.version;
+  const cachedVersions = cachedVersionData.get(VERSIONS_NDJSON_URL);
+  const cachedLatestVersion =
+    cachedVersions?.[0] ?? cachedLatestVersionData.get(VERSIONS_NDJSON_URL);
+  if (cachedLatestVersion !== undefined) {
+    core.debug(
+      `Latest version from NDJSON cache: ${cachedLatestVersion.version}`,
+    );
+    return cachedLatestVersion.version;
+  }
+
+  const latestVersion = await findVersionData(() => true);
   if (!latestVersion) {
     throw new Error("No versions found in NDJSON data");
   }
 
-  core.debug(`Latest version from NDJSON: ${latestVersion}`);
-  return latestVersion;
+  core.debug(`Latest version from NDJSON: ${latestVersion.version}`);
+  return latestVersion.version;
 }
 
 export async function getAllVersions(): Promise {
@@ -100,15 +90,24 @@ export async function getAllVersions(): Promise {
   return versions.map((versionData) => versionData.version);
 }
 
+export async function getHighestSatisfyingVersion(
+  versionSpecifier: string,
+  url: string = VERSIONS_NDJSON_URL,
+): Promise {
+  const matchedVersion = await findVersionData(
+    (candidate) => versionSatisfies(candidate.version, versionSpecifier),
+    url,
+  );
+
+  return matchedVersion?.version;
+}
+
 export async function getArtifact(
   version: string,
   arch: string,
   platform: string,
 ): Promise {
-  const versions = await fetchVersionData();
-  const versionData = versions.find(
-    (candidate) => candidate.version === version,
-  );
+  const versionData = await getVersionData(version);
   if (!versionData) {
     core.debug(`Version ${version} not found in NDJSON data`);
     return undefined;
@@ -140,10 +139,14 @@ export async function getArtifact(
 export function clearCache(url?: string): void {
   if (url === undefined) {
     cachedVersionData.clear();
+    cachedLatestVersionData.clear();
+    cachedVersionLookup.clear();
     return;
   }
 
   cachedVersionData.delete(url);
+  cachedLatestVersionData.delete(url);
+  cachedVersionLookup.delete(url);
 }
 
 function selectArtifact(
@@ -157,6 +160,192 @@ function selectArtifact(
   );
 }
 
+async function getVersionData(
+  version: string,
+  url: string = VERSIONS_NDJSON_URL,
+): Promise {
+  const cachedVersions = cachedVersionData.get(url);
+  if (cachedVersions !== undefined) {
+    return cachedVersions.find((candidate) => candidate.version === version);
+  }
+
+  const cachedVersion = cachedVersionLookup.get(url)?.get(version);
+  if (cachedVersion !== undefined) {
+    return cachedVersion;
+  }
+
+  return await findVersionData(
+    (candidate) => candidate.version === version,
+    url,
+  );
+}
+
+async function findVersionData(
+  predicate: (versionData: NdjsonVersion) => boolean,
+  url: string = VERSIONS_NDJSON_URL,
+): Promise {
+  const cachedVersions = cachedVersionData.get(url);
+  if (cachedVersions !== undefined) {
+    return cachedVersions.find(predicate);
+  }
+
+  const { matchedVersion, versions, complete } = await readVersionData(
+    url,
+    predicate,
+  );
+
+  if (complete) {
+    cacheCompleteVersionData(url, versions);
+  }
+
+  return matchedVersion;
+}
+
+async function readVersionData(
+  url: string,
+  stopWhen?: (versionData: NdjsonVersion) => boolean,
+): Promise<{
+  complete: boolean;
+  matchedVersion: NdjsonVersion | undefined;
+  versions: NdjsonVersion[];
+}> {
+  const response = await fetch(url, {});
+  if (!response.ok) {
+    throw new Error(
+      `Failed to fetch version data: ${response.status} ${response.statusText}`,
+    );
+  }
+
+  if (response.body === null) {
+    const body = await response.text();
+    const versions = parseVersionData(body, url);
+    const matchedVersion = stopWhen
+      ? versions.find((candidate) => stopWhen(candidate))
+      : undefined;
+    return { complete: true, matchedVersion, versions };
+  }
+
+  const versions: NdjsonVersion[] = [];
+  let lineNumber = 0;
+  let matchedVersion: NdjsonVersion | undefined;
+  let buffer = "";
+  const decoder = new TextDecoder();
+  const reader = response.body.getReader();
+
+  const processLine = (line: string): boolean => {
+    const trimmed = line.trim();
+    if (trimmed === "") {
+      return false;
+    }
+
+    lineNumber += 1;
+    const versionData = parseVersionLine(trimmed, url, lineNumber);
+    if (versions.length === 0) {
+      cachedLatestVersionData.set(url, versionData);
+    }
+
+    versions.push(versionData);
+    cacheVersion(url, versionData);
+
+    if (stopWhen?.(versionData) === true) {
+      matchedVersion = versionData;
+      return true;
+    }
+
+    return false;
+  };
+
+  while (true) {
+    const { done, value } = await reader.read();
+    if (done) {
+      buffer += decoder.decode();
+      break;
+    }
+
+    buffer += decoder.decode(value, { stream: true });
+    let newlineIndex = buffer.indexOf("\n");
+    while (newlineIndex !== -1) {
+      const line = buffer.slice(0, newlineIndex);
+      buffer = buffer.slice(newlineIndex + 1);
+
+      if (processLine(line)) {
+        await reader.cancel();
+        return { complete: false, matchedVersion, versions };
+      }
+
+      newlineIndex = buffer.indexOf("\n");
+    }
+  }
+
+  if (buffer.trim() !== "" && processLine(buffer)) {
+    return { complete: true, matchedVersion, versions };
+  }
+
+  if (versions.length === 0) {
+    throw new Error(`No version data found in ${url}.`);
+  }
+
+  return { complete: true, matchedVersion, versions };
+}
+
+function cacheCompleteVersionData(
+  url: string,
+  versions: NdjsonVersion[],
+): void {
+  cachedVersionData.set(url, versions);
+
+  if (versions[0] !== undefined) {
+    cachedLatestVersionData.set(url, versions[0]);
+  }
+
+  const versionLookup = new Map();
+  for (const versionData of versions) {
+    versionLookup.set(versionData.version, versionData);
+  }
+
+  cachedVersionLookup.set(url, versionLookup);
+}
+
+function cacheVersion(url: string, versionData: NdjsonVersion): void {
+  let versionLookup = cachedVersionLookup.get(url);
+  if (versionLookup === undefined) {
+    versionLookup = new Map();
+    cachedVersionLookup.set(url, versionLookup);
+  }
+
+  versionLookup.set(versionData.version, versionData);
+}
+
+function parseVersionLine(
+  line: string,
+  sourceDescription: string,
+  lineNumber: number,
+): NdjsonVersion {
+  let parsed: unknown;
+  try {
+    parsed = JSON.parse(line);
+  } catch (error) {
+    throw new Error(
+      `Failed to parse version data from ${sourceDescription} at line ${lineNumber}: ${(error as Error).message}`,
+    );
+  }
+
+  if (!isNdjsonVersion(parsed)) {
+    throw new Error(
+      `Invalid NDJSON record in ${sourceDescription} at line ${lineNumber}.`,
+    );
+  }
+
+  return parsed;
+}
+
+function versionSatisfies(version: string, versionSpecifier: string): boolean {
+  return (
+    semver.satisfies(version, versionSpecifier) ||
+    pep440.satisfies(version, versionSpecifier)
+  );
+}
+
 function isNdjsonVersion(value: unknown): value is NdjsonVersion {
   if (!isRecord(value)) {
     return false;