mirror of
https://github.com/astral-sh/setup-uv.git
synced 2026-11-05 01:35:32 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6942401f3a | ||
|
|
2f29784424 | ||
|
|
9c7332190c | ||
|
|
80fbe0140c | ||
|
|
36b7b57007 |
@@ -73,4 +73,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
|
||||
Vendored
+87
@@ -0,0 +1,87 @@
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
afterAll,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
jest,
|
||||
} from "@jest/globals";
|
||||
import { CacheLocalSource, type SetupInputs } from "../../src/utils/inputs";
|
||||
import { createSetupInputs } from "../helpers/setup-inputs";
|
||||
|
||||
const mockSaveCache = jest.fn<() => Promise<number>>();
|
||||
const mockInfo = jest.fn();
|
||||
const mockLoadInputs = jest.fn<() => SetupInputs>(() =>
|
||||
createSetupInputs({ enableCache: false }),
|
||||
);
|
||||
const state: Record<string, string> = {};
|
||||
|
||||
jest.unstable_mockModule("@actions/cache", () => ({
|
||||
saveCache: mockSaveCache,
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
getState: jest.fn((name: string) => state[name] ?? ""),
|
||||
setFailed: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule("../../src/utils/inputs", () => ({
|
||||
loadInputs: mockLoadInputs,
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule("../../src/utils/logging", () => ({
|
||||
info: mockInfo,
|
||||
warning: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.spyOn(process, "exit").mockImplementation((() => undefined) as never);
|
||||
|
||||
const { run } = await import("../../src/save-cache");
|
||||
|
||||
const cacheDir = mkdtempSync(join(tmpdir(), "setup-uv-save-cache-"));
|
||||
const cacheKey = "setup-uv-2-x86_64-unknown-linux-gnu-ubuntu-24.04-3.12-hash";
|
||||
|
||||
function infoMessages(): string[] {
|
||||
return mockInfo.mock.calls.map(([msg]) => msg as string);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.UV_CACHE_DIR;
|
||||
state["cache-key"] = cacheKey;
|
||||
state["cache-matched-key"] = "";
|
||||
mockLoadInputs.mockReturnValue(
|
||||
createSetupInputs({
|
||||
cacheLocalPath: { path: cacheDir, source: CacheLocalSource.Input },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
rmSync(cacheDir, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
describe("saveCache", () => {
|
||||
it("reports the saved key when the cache is saved", async () => {
|
||||
mockSaveCache.mockResolvedValue(42);
|
||||
|
||||
await run();
|
||||
|
||||
expect(mockSaveCache).toHaveBeenCalledWith([cacheDir], cacheKey);
|
||||
expect(infoMessages()).toContain(`uv cache saved with key: ${cacheKey}`);
|
||||
});
|
||||
|
||||
it("does not report the cache as saved when saving fails", async () => {
|
||||
// @actions/cache logs the failure itself and returns -1
|
||||
mockSaveCache.mockResolvedValue(-1);
|
||||
|
||||
await run();
|
||||
|
||||
expect(mockSaveCache).toHaveBeenCalledWith([cacheDir], cacheKey);
|
||||
expect(infoMessages()).not.toContain(
|
||||
`uv cache saved with key: ${cacheKey}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -146,6 +146,28 @@ describe("download-version", () => {
|
||||
expect(mockGetAllVersions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves ~= with PEP 440 compatible release semantics", async () => {
|
||||
mockGetFirstMatchingVersion.mockImplementation(
|
||||
(predicate: (version: string) => boolean) =>
|
||||
["1.0.0", "0.10.2", "0.9.26"].find(predicate),
|
||||
);
|
||||
|
||||
const version = await resolveVersion("~=0.9", undefined);
|
||||
|
||||
expect(version).toBe("0.10.2");
|
||||
});
|
||||
|
||||
it("resolves ~= with PEP 440 compatible release semantics from manifest-file", async () => {
|
||||
mockGetAllVersions.mockResolvedValue(["1.0.0", "0.10.2", "0.9.26"]);
|
||||
|
||||
const version = await resolveVersion(
|
||||
"~=0.9",
|
||||
"https://example.com/custom.ndjson",
|
||||
);
|
||||
|
||||
expect(version).toBe("0.10.2");
|
||||
});
|
||||
|
||||
it("treats == exact pins as explicit versions", async () => {
|
||||
const version = await resolveVersion("==0.9.26", undefined);
|
||||
|
||||
|
||||
@@ -35,3 +35,14 @@ test("strips PEP 508 markers from requirements dependencies", () => {
|
||||
|
||||
expect(parsedVersion).toBe("==0.11.20");
|
||||
});
|
||||
|
||||
test.each([
|
||||
["uv >= 0.5.0", ">=0.5.0"],
|
||||
["uv == 0.11.20", "==0.11.20"],
|
||||
["uv>=0.5.0, <0.6.0", ">=0.5.0,<0.6.0"],
|
||||
["uv >= 0.5.0 , < 0.6.0", ">=0.5.0,<0.6.0"],
|
||||
["uv==0.11.20 --hash=sha256:abc", "==0.11.20"],
|
||||
["uv==0.11.20 # pinned for CI", "==0.11.20"],
|
||||
])("allows whitespace in the uv specifier %s", (dependency, expected) => {
|
||||
expect(getUvVersionFromRequirementsText(dependency)).toBe(expected);
|
||||
});
|
||||
|
||||
+4
-2
@@ -66671,8 +66671,10 @@ async function saveCacheToKey(cacheKey, cachePath, stateKey, cacheName) {
|
||||
return;
|
||||
}
|
||||
info2(`Including ${cacheName} path: ${cachePath}`);
|
||||
await saveCache2([cachePath], cacheKey);
|
||||
info2(`${cacheName} saved with key: ${cacheKey}`);
|
||||
const cacheId = await saveCache2([cachePath], cacheKey);
|
||||
if (cacheId !== -1) {
|
||||
info2(`${cacheName} saved with key: ${cacheKey}`);
|
||||
}
|
||||
}
|
||||
__name(saveCacheToKey, "saveCacheToKey");
|
||||
run();
|
||||
|
||||
+7
-4
@@ -101579,7 +101579,7 @@ function getUvVersionFromAllDependencies(allDependencies) {
|
||||
}
|
||||
function getUvVersionFromDependency(dependency) {
|
||||
const dependencyWithoutMarker = dependency.split(";", 1)[0]?.trim();
|
||||
return dependencyWithoutMarker?.match(/^uv([=<>~!]+\S*)/)?.[1].trim();
|
||||
return dependencyWithoutMarker?.match(/^uv\s*((?:[=<>~!]+\s*[^\s,]+\s*,?\s*)+)/)?.[1].replace(/\s/g, "");
|
||||
}
|
||||
|
||||
// src/version/tool-versions-file.ts
|
||||
@@ -101937,7 +101937,7 @@ async function resolveVersion(versionInput, manifestUrl, resolutionStrategy = "h
|
||||
throw new Error(`No version found for ${versionInput}`);
|
||||
}
|
||||
async function findHighestSatisfyingVersion(versionSpecifier) {
|
||||
const semverRange = semver5.validRange(versionSpecifier);
|
||||
const semverRange = isCompatibleReleaseSpecifier(versionSpecifier) ? null : semver5.validRange(versionSpecifier);
|
||||
if (semverRange !== null) {
|
||||
const semverMatch = await getFirstMatchingVersion(
|
||||
(version3) => semver5.satisfies(version3, semverRange)
|
||||
@@ -101959,8 +101959,11 @@ async function findHighestSatisfyingVersion(versionSpecifier) {
|
||||
}
|
||||
return pep440Match;
|
||||
}
|
||||
function isCompatibleReleaseSpecifier(versionSpecifier) {
|
||||
return versionSpecifier.includes("~=");
|
||||
}
|
||||
function maxSatisfying2(versions, version3) {
|
||||
const maxSemver = evaluateVersions(versions, version3);
|
||||
const maxSemver = isCompatibleReleaseSpecifier(version3) ? "" : evaluateVersions(versions, version3);
|
||||
if (maxSemver !== "") {
|
||||
debug(`Found a version that satisfies the semver range: ${maxSemver}`);
|
||||
return maxSemver;
|
||||
@@ -101975,7 +101978,7 @@ function maxSatisfying2(versions, version3) {
|
||||
return void 0;
|
||||
}
|
||||
function minSatisfying3(versions, version3) {
|
||||
const minSemver = semver5.minSatisfying(versions, version3);
|
||||
const minSemver = isCompatibleReleaseSpecifier(version3) ? null : semver5.minSatisfying(versions, version3);
|
||||
if (minSemver !== null) {
|
||||
debug(`Found a version that satisfies the semver range: ${minSemver}`);
|
||||
return minSemver;
|
||||
|
||||
Generated
+7
-7
@@ -17,7 +17,7 @@
|
||||
"@actions/tool-cache": "^4.0.0",
|
||||
"@renovatebot/pep440": "^5.0.0",
|
||||
"smol-toml": "^1.8.0",
|
||||
"undici": "^8.11.0"
|
||||
"undici": "^8.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.5.11",
|
||||
@@ -5800,9 +5800,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.0.tgz",
|
||||
"integrity": "sha512-AdzuGcAkzhbha3GgCSoUBx2P0quwym6qdFNCQz/fDGZc8w9XJ741wHUbt2kTmfPz+ZIOMkySFkiY7RXmiDDC8w==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz",
|
||||
"integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
@@ -9683,9 +9683,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"undici": {
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.0.tgz",
|
||||
"integrity": "sha512-AdzuGcAkzhbha3GgCSoUBx2P0quwym6qdFNCQz/fDGZc8w9XJ741wHUbt2kTmfPz+ZIOMkySFkiY7RXmiDDC8w=="
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz",
|
||||
"integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ=="
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "8.3.0",
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@
|
||||
"@actions/tool-cache": "^4.0.0",
|
||||
"@renovatebot/pep440": "^5.0.0",
|
||||
"smol-toml": "^1.8.0",
|
||||
"undici": "^8.11.0"
|
||||
"undici": "^8.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.5.11",
|
||||
|
||||
+4
-2
@@ -159,8 +159,10 @@ async function saveCacheToKey(
|
||||
}
|
||||
|
||||
log.info(`Including ${cacheName} path: ${cachePath}`);
|
||||
await cache.saveCache([cachePath], cacheKey);
|
||||
log.info(`${cacheName} saved with key: ${cacheKey}`);
|
||||
const cacheId = await cache.saveCache([cachePath], cacheKey);
|
||||
if (cacheId !== -1) {
|
||||
log.info(`${cacheName} saved with key: ${cacheKey}`);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
@@ -69,5 +69,7 @@ function getUvVersionFromAllDependencies(
|
||||
|
||||
function getUvVersionFromDependency(dependency: string): string | undefined {
|
||||
const dependencyWithoutMarker = dependency.split(";", 1)[0]?.trim();
|
||||
return dependencyWithoutMarker?.match(/^uv([=<>~!]+\S*)/)?.[1].trim();
|
||||
return dependencyWithoutMarker
|
||||
?.match(/^uv\s*((?:[=<>~!]+\s*[^\s,]+\s*,?\s*)+)/)?.[1]
|
||||
.replace(/\s/g, "");
|
||||
}
|
||||
|
||||
+18
-3
@@ -169,7 +169,9 @@ async function findHighestSatisfyingVersion(
|
||||
): Promise<string | undefined> {
|
||||
// This fast path assumes uv releases are semver-monotonic: newer manifest
|
||||
// entries always have higher versions, with no lower-version backports.
|
||||
const semverRange = semver.validRange(versionSpecifier);
|
||||
const semverRange = isCompatibleReleaseSpecifier(versionSpecifier)
|
||||
? null
|
||||
: semver.validRange(versionSpecifier);
|
||||
if (semverRange !== null) {
|
||||
const semverMatch = await getFirstMatchingVersion((version) =>
|
||||
semver.satisfies(version, semverRange),
|
||||
@@ -193,11 +195,22 @@ async function findHighestSatisfyingVersion(
|
||||
return pep440Match;
|
||||
}
|
||||
|
||||
/**
|
||||
* `~=` is the PEP 440 compatible release operator. semver accepts it as a
|
||||
* tilde range with a narrower upper bound (`~=0.5` as `<0.6.0` instead of
|
||||
* `<1.0`), so it must only be evaluated as PEP 440.
|
||||
*/
|
||||
function isCompatibleReleaseSpecifier(versionSpecifier: string): boolean {
|
||||
return versionSpecifier.includes("~=");
|
||||
}
|
||||
|
||||
function maxSatisfying(
|
||||
versions: string[],
|
||||
version: string,
|
||||
): string | undefined {
|
||||
const maxSemver = tc.evaluateVersions(versions, version);
|
||||
const maxSemver = isCompatibleReleaseSpecifier(version)
|
||||
? ""
|
||||
: tc.evaluateVersions(versions, version);
|
||||
if (maxSemver !== "") {
|
||||
core.debug(`Found a version that satisfies the semver range: ${maxSemver}`);
|
||||
return maxSemver;
|
||||
@@ -218,7 +231,9 @@ function minSatisfying(
|
||||
versions: string[],
|
||||
version: string,
|
||||
): string | undefined {
|
||||
const minSemver = semver.minSatisfying(versions, version);
|
||||
const minSemver = isCompatibleReleaseSpecifier(version)
|
||||
? null
|
||||
: semver.minSatisfying(versions, version);
|
||||
if (minSemver !== null) {
|
||||
core.debug(`Found a version that satisfies the semver range: ${minSemver}`);
|
||||
return minSemver;
|
||||
|
||||
Reference in New Issue
Block a user