Compare commits

...

16 Commits

Author SHA1 Message Date
dependabot[bot]
10ec13649e chore(deps-dev): bump @biomejs/biome from 2.4.7 to 2.4.9
Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.4.7 to 2.4.9.
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.4.9/packages/@biomejs/biome)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.4.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-02 10:53:57 +00:00
github-actions[bot]
1c15d185f0 chore: update known checksums for 0.11.3 (#836)
chore: update known checksums for 0.11.3

Co-authored-by: eifinger <eifinger@users.noreply.github.com>
2026-04-02 10:42:50 +02:00
Kevin Stillhammer
d7fe1a5a18 Update ignore-nothing-to-cache documentation (#833)
Add the error message so it can be found when searching for it

Helps issues like #831
2026-03-31 09:27:10 +02:00
Kevin Stillhammer
16592cddee Pin setup-uv docs to v8 (#829)
Update all README and docs examples to use the pinned v8 release SHA for
astral-sh/setup-uv, with a comment showing the release version for
clarity and best practices.
2026-03-29 19:36:34 +02:00
Kevin Stillhammer
cec208311d Shortcircuit latest version from manifest (#828)
The first version is guaranteed to be the latest
2026-03-28 17:43:22 +01:00
Kevin Stillhammer
4dd8ab4520 Simplify inputs.ts (#827)
Do not pass around inputs.
Its okay to freely work with core.getInput in this file
2026-03-28 17:38:30 +01:00
Kevin Stillhammer
7fdbe7cf0c Remove update-major-minor-tags workflow (#826) 2026-03-28 16:29:52 +01:00
Kevin Stillhammer
485abd05e5 Bump release-drafter to v7.1.1 (#825) 2026-03-28 16:25:54 +01:00
Kevin Stillhammer
f82eb19c06 Refactor inputs (#823)
Don't load at import time and make it easier to test
2026-03-28 16:23:26 +01:00
Kevin Stillhammer
868d1f74d9 Replace inline compile args with tsconfig (#824) 2026-03-28 16:19:09 +01:00
github-actions[bot]
447e6d02b1 chore: update known checksums for 0.11.2 (#821)
chore: update known checksums for 0.11.2

Co-authored-by: eifinger <eifinger@users.noreply.github.com>
2026-03-27 07:12:16 +01:00
github-actions[bot]
5c62c59261 chore: update known checksums for 0.11.1 (#817)
chore: update known checksums for 0.11.1

Co-authored-by: eifinger <eifinger@users.noreply.github.com>
2026-03-25 08:14:06 +01:00
github-actions[bot]
e1a7373adb chore: update known checksums for 0.11.0 (#815)
chore: update known checksums for 0.11.0

Co-authored-by: eifinger <eifinger@users.noreply.github.com>
2026-03-24 07:42:01 +01:00
Kevin Stillhammer
89709315bb Remove deprecrated custom manifest (#813) 2026-03-23 09:15:51 +01:00
Kevin Stillhammer
8cc8d1cbfc Fix latest-version workflow check (#812)
## Summary
- make the latest-version workflow test use the action output for the
exact installed version
- allow `uv --version` to include additional platform/build metadata
- keep validating that the reported version matches the latest GitHub
release

## Testing
- npm ci --ignore-scripts
- npm run all
- actionlint .github/workflows/test.yml

Fixes the failure in
https://github.com/astral-sh/setup-uv/actions/runs/23332230171/job/67866051063
2026-03-20 10:51:55 +01:00
github-actions[bot]
c20049fc26 chore: update known checksums for 0.10.11/0.10.12 (#811) 2026-03-20 07:48:09 +01:00
34 changed files with 3597 additions and 3618 deletions

9
.github/scripts/tsconfig.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"target": "es2022",
"types": ["node"]
},
"include": ["check-all-tests-passed-needs.ts"]
}

View File

@@ -19,6 +19,6 @@ jobs:
pull-requests: read
steps:
- name: 🚀 Run Release Drafter
uses: release-drafter/release-drafter@6db134d15f3909ccc9eefd369f02bd1e9cffdf97 # v6.2.0
uses: release-drafter/release-drafter@139054aeaa9adc52ab36ddf67437541f039b88e2 # v7.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -38,7 +38,7 @@ jobs:
npm run all
- name: Check all jobs are in all-tests-passed.needs
run: |
tsc --module nodenext --moduleResolution nodenext --target es2022 check-all-tests-passed-needs.ts
tsc -p tsconfig.json
node check-all-tests-passed-needs.js
working-directory: .github/scripts
- name: Make sure no changes from linters are detected
@@ -164,10 +164,22 @@ jobs:
- name: Latest version gets installed
run: |
LATEST_VERSION=$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/astral-sh/uv/releases/latest | jq -r '.tag_name')
UV_VERSION_OUTPUT=$(uv --version)
if [[ ! "$UV_VERSION_OUTPUT" =~ ^uv[[:space:]]+([^[:space:]]+) ]]; then
echo "Could not parse uv version from: $UV_VERSION_OUTPUT"
exit 1
fi
UV_VERSION="${BASH_REMATCH[1]}"
echo "Latest version is $LATEST_VERSION"
if [ "$(uv --version)" != "uv $LATEST_VERSION" ]; then
echo "Wrong uv version: $(uv --version)"
exit 1
echo "uv --version output is $UV_VERSION_OUTPUT"
echo "Parsed uv version is $UV_VERSION"
if [ "$UV_VERSION" != "$LATEST_VERSION" ]; then
echo "Wrong uv version: $UV_VERSION_OUTPUT"
exit 1
fi
env:
GH_TOKEN: ${{ github.token }}
@@ -796,12 +808,12 @@ jobs:
- name: Install from custom manifest file
uses: ./
with:
manifest-file: "https://raw.githubusercontent.com/astral-sh/setup-uv/${{ github.ref }}/__tests__/download/custom-manifest.json"
manifest-file: "https://raw.githubusercontent.com/astral-sh/setup-uv/${{ github.ref }}/__tests__/download/custom-manifest.ndjson"
- run: uv sync
working-directory: __tests__/fixtures/uv-project
- name: Correct version gets installed
run: |
if [ "$(uv --version)" != "uv 0.7.12-alpha.1" ]; then
if [ "$(uv --version)" != "uv 0.9.26" ]; then
echo "Wrong uv version: $(uv --version)"
exit 1
fi

View File

@@ -1,51 +0,0 @@
---
name: Update Major Minor Tags
on:
push:
branches-ignore:
- "**"
tags:
- "v*.*.*"
permissions: {}
jobs:
update_major_minor_tags:
name: Make sure major and minor tags are up to date on a patch release
runs-on: ubuntu-24.04-arm
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: true # needed for git push below
- name: Update Major Minor Tags
run: |
set -x
cd "${GITHUB_WORKSPACE}" || exit
# Set up variables.
TAG="${GITHUB_REF#refs/tags/}" # v1.2.3
MINOR="${TAG%.*}" # v1.2
MAJOR="${MINOR%.*}" # v1
if [ "${GITHUB_REF}" = "${TAG}" ]; then
echo "This workflow is not triggered by tag push: GITHUB_REF=${GITHUB_REF}"
exit 1
fi
MESSAGE="Release ${TAG}"
# Set up Git.
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
# Update MAJOR/MINOR tag
git tag -fa "${MAJOR}" -m "${MESSAGE}"
git tag -fa "${MINOR}" -m "${MESSAGE}"
# Push
git push --force origin "${MINOR}"
git push --force origin "${MAJOR}"

View File

@@ -26,7 +26,7 @@ Set up your GitHub Actions workflow with a specific version of [uv](https://docs
```yaml
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
```
If you do not specify a version, this action will look for a [required-version](https://docs.astral.sh/uv/reference/settings/#required-version)
@@ -42,7 +42,7 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed
```yaml
- name: Install uv with all available options
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
# The version of uv to install (default: searches for version in config files, then latest)
version: ""
@@ -114,7 +114,7 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed
# Custom path to set UV_TOOL_BIN_DIR to
tool-bin-dir: ""
# URL to a custom manifest file (NDJSON preferred, legacy JSON array is deprecated)
# URL to a custom manifest file in the astral-sh/versions format
manifest-file: ""
# Add problem matchers
@@ -139,7 +139,7 @@ This will override any python version specifications in `pyproject.toml` and `.p
```yaml
- name: Install the latest version of uv and set the python version to 3.13t
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
python-version: 3.13t
- run: uv pip install --python=3.13t pip
@@ -157,7 +157,7 @@ jobs:
steps:
- uses: actions/checkout@v5
- name: Install the latest version of uv and set the python version
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Test with python ${{ matrix.python-version }}
@@ -174,7 +174,7 @@ It also controls where [the venv gets created](#activate-environment), unless `v
```yaml
- name: Install uv based on the config files in the working-directory
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
working-directory: my/subproject/dir
```
@@ -190,8 +190,8 @@ For more advanced configuration options, see our detailed documentation:
## How it works
By default, this action resolves uv versions from
[`astral-sh/versions`](https://github.com/astral-sh/versions) (NDJSON) and downloads uv from the
By default, this action resolves uv versions from the
[`astral-sh/versions`](https://github.com/astral-sh/versions) manifest and downloads uv from the
official [GitHub Releases](https://github.com/astral-sh/uv).
It then uses the [GitHub Actions Toolkit](https://github.com/actions/toolkit) to cache uv as a
@@ -216,7 +216,7 @@ For example:
- name: Checkout the repository
uses: actions/checkout@main
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
- name: Test
@@ -228,7 +228,7 @@ To install a specific version of Python, use
```yaml
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
- name: Install Python 3.12
@@ -247,7 +247,7 @@ output:
uses: actions/checkout@main
- name: Install the default version of uv
id: setup-uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- name: Print the installed version
run: echo "Installed uv version is ${{ steps.setup-uv.outputs.uv-version }}"
```

View File

@@ -1,9 +0,0 @@
[
{
"arch": "x86_64",
"artifactName": "uv-x86_64-unknown-linux-gnu.tar.gz",
"downloadUrl": "https://release.pyx.dev/0.7.12-alpha.1/uv-x86_64-unknown-linux-gnu.tar.gz",
"platform": "unknown-linux-gnu",
"version": "0.7.12-alpha.1"
}
]

View File

@@ -0,0 +1 @@
{"version":"0.9.26","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"30ccbf0a66dc8727a02b0e245c583ee970bdafecf3a443c1686e1b30ec4939e8"}]}

View File

@@ -32,29 +32,16 @@ jest.unstable_mockModule("@actions/tool-cache", () => ({
}));
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
const mockGetLatestVersionFromNdjson = jest.fn<any>();
const mockGetLatestVersion = jest.fn<any>();
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
const mockGetAllVersionsFromNdjson = jest.fn<any>();
const mockGetAllVersions = jest.fn<any>();
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
const mockGetArtifactFromNdjson = jest.fn<any>();
const mockGetArtifact = jest.fn<any>();
jest.unstable_mockModule("../../src/download/versions-client", () => ({
getAllVersions: mockGetAllVersionsFromNdjson,
getArtifact: mockGetArtifactFromNdjson,
getLatestVersion: mockGetLatestVersionFromNdjson,
}));
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
const mockGetAllManifestVersions = jest.fn<any>();
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
const mockGetLatestVersionInManifest = jest.fn<any>();
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
const mockGetManifestArtifact = jest.fn<any>();
jest.unstable_mockModule("../../src/download/version-manifest", () => ({
getAllVersions: mockGetAllManifestVersions,
getLatestKnownVersion: mockGetLatestVersionInManifest,
getManifestArtifact: mockGetManifestArtifact,
jest.unstable_mockModule("../../src/download/manifest", () => ({
getAllVersions: mockGetAllVersions,
getArtifact: mockGetArtifact,
getLatestVersion: mockGetLatestVersion,
}));
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
@@ -64,12 +51,9 @@ jest.unstable_mockModule("../../src/download/checksum/checksum", () => ({
validateChecksum: mockValidateChecksum,
}));
const {
downloadVersionFromManifest,
downloadVersionFromNdjson,
resolveVersion,
rewriteToMirror,
} = await import("../../src/download/download-version");
const { downloadVersion, resolveVersion, rewriteToMirror } = await import(
"../../src/download/download-version"
);
describe("download-version", () => {
beforeEach(() => {
@@ -79,12 +63,9 @@ describe("download-version", () => {
mockExtractTar.mockReset();
mockExtractZip.mockReset();
mockCacheDir.mockReset();
mockGetLatestVersionFromNdjson.mockReset();
mockGetAllVersionsFromNdjson.mockReset();
mockGetArtifactFromNdjson.mockReset();
mockGetAllManifestVersions.mockReset();
mockGetLatestVersionInManifest.mockReset();
mockGetManifestArtifact.mockReset();
mockGetLatestVersion.mockReset();
mockGetAllVersions.mockReset();
mockGetArtifact.mockReset();
mockValidateChecksum.mockReset();
mockDownloadTool.mockResolvedValue("/tmp/downloaded");
@@ -94,36 +75,28 @@ describe("download-version", () => {
});
describe("resolveVersion", () => {
it("uses astral-sh/versions to resolve latest", async () => {
mockGetLatestVersionFromNdjson.mockResolvedValue("0.9.26");
it("uses the default manifest to resolve latest", async () => {
mockGetLatestVersion.mockResolvedValue("0.9.26");
const version = await resolveVersion("latest", undefined);
expect(version).toBe("0.9.26");
expect(mockGetLatestVersionFromNdjson).toHaveBeenCalledTimes(1);
expect(mockGetLatestVersion).toHaveBeenCalledTimes(1);
expect(mockGetLatestVersion).toHaveBeenCalledWith(undefined);
});
it("uses astral-sh/versions to resolve available versions", async () => {
mockGetAllVersionsFromNdjson.mockResolvedValue(["0.9.26", "0.9.25"]);
it("uses the default manifest to resolve available versions", async () => {
mockGetAllVersions.mockResolvedValue(["0.9.26", "0.9.25"]);
const version = await resolveVersion("^0.9.0", undefined);
expect(version).toBe("0.9.26");
expect(mockGetAllVersionsFromNdjson).toHaveBeenCalledTimes(1);
});
it("does not fall back when astral-sh/versions fails", async () => {
mockGetLatestVersionFromNdjson.mockRejectedValue(
new Error("NDJSON unavailable"),
);
await expect(resolveVersion("latest", undefined)).rejects.toThrow(
"NDJSON unavailable",
);
expect(mockGetAllVersions).toHaveBeenCalledTimes(1);
expect(mockGetAllVersions).toHaveBeenCalledWith(undefined);
});
it("uses manifest-file when provided", async () => {
mockGetAllManifestVersions.mockResolvedValue(["0.9.26", "0.9.25"]);
mockGetAllVersions.mockResolvedValue(["0.9.26", "0.9.25"]);
const version = await resolveVersion(
"^0.9.0",
@@ -131,37 +104,35 @@ describe("download-version", () => {
);
expect(version).toBe("0.9.26");
expect(mockGetAllManifestVersions).toHaveBeenCalledWith(
expect(mockGetAllVersions).toHaveBeenCalledWith(
"https://example.com/custom.ndjson",
);
});
});
describe("downloadVersionFromNdjson", () => {
it("fails when NDJSON metadata lookup fails", async () => {
mockGetArtifactFromNdjson.mockRejectedValue(
new Error("NDJSON unavailable"),
);
describe("downloadVersion", () => {
it("fails when manifest lookup fails", async () => {
mockGetArtifact.mockRejectedValue(new Error("manifest unavailable"));
await expect(
downloadVersionFromNdjson(
downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.9.26",
undefined,
"token",
),
).rejects.toThrow("NDJSON unavailable");
).rejects.toThrow("manifest unavailable");
expect(mockDownloadTool).not.toHaveBeenCalled();
expect(mockValidateChecksum).not.toHaveBeenCalled();
});
it("fails when no matching artifact exists in NDJSON metadata", async () => {
mockGetArtifactFromNdjson.mockResolvedValue(undefined);
it("fails when no matching artifact exists in the default manifest", async () => {
mockGetArtifact.mockResolvedValue(undefined);
await expect(
downloadVersionFromNdjson(
downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.9.26",
@@ -176,14 +147,14 @@ describe("download-version", () => {
expect(mockValidateChecksum).not.toHaveBeenCalled();
});
it("uses built-in checksums for default NDJSON downloads", async () => {
mockGetArtifactFromNdjson.mockResolvedValue({
it("uses built-in checksums for default manifest downloads", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
sha256: "ndjson-checksum-that-should-be-ignored",
url: "https://example.com/uv.tar.gz",
checksum: "manifest-checksum-that-should-be-ignored",
downloadUrl: "https://example.com/uv.tar.gz",
});
await downloadVersionFromNdjson(
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.9.26",
@@ -201,13 +172,14 @@ describe("download-version", () => {
});
it("rewrites GitHub Releases URLs to the Astral mirror", async () => {
mockGetArtifactFromNdjson.mockResolvedValue({
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
sha256: "abc123",
url: "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
checksum: "abc123",
downloadUrl:
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
});
await downloadVersionFromNdjson(
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.9.26",
@@ -223,13 +195,13 @@ describe("download-version", () => {
});
it("does not rewrite non-GitHub URLs", async () => {
mockGetArtifactFromNdjson.mockResolvedValue({
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
sha256: "abc123",
url: "https://example.com/uv.tar.gz",
checksum: "abc123",
downloadUrl: "https://example.com/uv.tar.gz",
});
await downloadVersionFromNdjson(
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.9.26",
@@ -245,17 +217,18 @@ describe("download-version", () => {
});
it("falls back to GitHub Releases when the mirror fails", async () => {
mockGetArtifactFromNdjson.mockResolvedValue({
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
sha256: "abc123",
url: "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
checksum: "abc123",
downloadUrl:
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
});
mockDownloadTool
.mockRejectedValueOnce(new Error("mirror unavailable"))
.mockResolvedValueOnce("/tmp/downloaded");
await downloadVersionFromNdjson(
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.9.26",
@@ -264,14 +237,12 @@ describe("download-version", () => {
);
expect(mockDownloadTool).toHaveBeenCalledTimes(2);
// Mirror request: no token
expect(mockDownloadTool).toHaveBeenNthCalledWith(
1,
"https://releases.astral.sh/github/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
undefined,
undefined,
);
// GitHub fallback: token restored
expect(mockDownloadTool).toHaveBeenNthCalledWith(
2,
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz",
@@ -284,16 +255,16 @@ describe("download-version", () => {
});
it("does not fall back for non-GitHub URLs", async () => {
mockGetArtifactFromNdjson.mockResolvedValue({
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
sha256: "abc123",
url: "https://example.com/uv.tar.gz",
checksum: "abc123",
downloadUrl: "https://example.com/uv.tar.gz",
});
mockDownloadTool.mockRejectedValue(new Error("download failed"));
await expect(
downloadVersionFromNdjson(
downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.9.26",
@@ -304,6 +275,56 @@ describe("download-version", () => {
expect(mockDownloadTool).toHaveBeenCalledTimes(1);
});
it("uses manifest-file checksum metadata when checksum input is unset", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "manifest-checksum",
downloadUrl: "https://example.com/custom-uv.tar.gz",
});
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.9.26",
"",
"token",
"https://example.com/custom.ndjson",
);
expect(mockValidateChecksum).toHaveBeenCalledWith(
"manifest-checksum",
"/tmp/downloaded",
"x86_64",
"unknown-linux-gnu",
"0.9.26",
);
});
it("prefers checksum input over manifest-file checksum metadata", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "manifest-checksum",
downloadUrl: "https://example.com/custom-uv.tar.gz",
});
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.9.26",
"user-checksum",
"token",
"https://example.com/custom.ndjson",
);
expect(mockValidateChecksum).toHaveBeenCalledWith(
"user-checksum",
"/tmp/downloaded",
"x86_64",
"unknown-linux-gnu",
"0.9.26",
);
});
});
describe("rewriteToMirror", () => {
@@ -329,56 +350,4 @@ describe("download-version", () => {
).toBeUndefined();
});
});
describe("downloadVersionFromManifest", () => {
it("uses manifest-file checksum metadata when checksum input is unset", async () => {
mockGetManifestArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "manifest-checksum",
downloadUrl: "https://example.com/custom-uv.tar.gz",
});
await downloadVersionFromManifest(
"https://example.com/custom.ndjson",
"unknown-linux-gnu",
"x86_64",
"0.9.26",
"",
"token",
);
expect(mockValidateChecksum).toHaveBeenCalledWith(
"manifest-checksum",
"/tmp/downloaded",
"x86_64",
"unknown-linux-gnu",
"0.9.26",
);
});
it("prefers checksum input over manifest-file checksum metadata", async () => {
mockGetManifestArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "manifest-checksum",
downloadUrl: "https://example.com/custom-uv.tar.gz",
});
await downloadVersionFromManifest(
"https://example.com/custom.ndjson",
"unknown-linux-gnu",
"x86_64",
"0.9.26",
"user-checksum",
"token",
);
expect(mockValidateChecksum).toHaveBeenCalledWith(
"user-checksum",
"/tmp/downloaded",
"x86_64",
"unknown-linux-gnu",
"0.9.26",
);
});
});
});

View File

@@ -0,0 +1,180 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
const mockFetch = jest.fn<any>();
jest.unstable_mockModule("@actions/core", () => ({
debug: jest.fn(),
info: jest.fn(),
}));
jest.unstable_mockModule("../../src/utils/fetch", () => ({
fetch: mockFetch,
}));
const {
clearManifestCache,
fetchManifest,
getAllVersions,
getArtifact,
getLatestVersion,
parseManifest,
} = await import("../../src/download/manifest");
const sampleManifestResponse = `{"version":"0.9.26","artifacts":[{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.tar.gz","archive_format":"tar.gz","sha256":"fcf0a9ea6599c6ae28a4c854ac6da76f2c889354d7c36ce136ef071f7ab9721f"},{"platform":"x86_64-pc-windows-msvc","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-pc-windows-msvc.zip","archive_format":"zip","sha256":"eb02fd95d8e0eed462b4a67ecdd320d865b38c560bffcda9a0b87ec944bdf036"}]}
{"version":"0.9.25","artifacts":[{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.25/uv-aarch64-apple-darwin.tar.gz","archive_format":"tar.gz","sha256":"606b3c6949d971709f2526fa0d9f0fd23ccf60e09f117999b406b424af18a6a6"}]}`;
const multiVariantManifestResponse = `{"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 createMockResponse(
ok: boolean,
status: number,
statusText: string,
data: string,
) {
return {
ok,
status,
statusText,
text: async () => data,
};
}
describe("manifest", () => {
beforeEach(() => {
clearManifestCache();
mockFetch.mockReset();
});
describe("fetchManifest", () => {
it("fetches and parses manifest data", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
const versions = await fetchManifest();
expect(versions).toHaveLength(2);
expect(versions[0]?.version).toBe("0.9.26");
expect(versions[1]?.version).toBe("0.9.25");
});
it("throws on a failed fetch", async () => {
mockFetch.mockResolvedValue(
createMockResponse(false, 500, "Internal Server Error", ""),
);
await expect(fetchManifest()).rejects.toThrow(
"Failed to fetch manifest data: 500 Internal Server Error",
);
});
it("caches results per URL", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
await fetchManifest("https://example.com/custom.ndjson");
await fetchManifest("https://example.com/custom.ndjson");
expect(mockFetch).toHaveBeenCalledTimes(1);
});
});
describe("getAllVersions", () => {
it("returns all version strings", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
const versions = await getAllVersions(
"https://example.com/custom.ndjson",
);
expect(versions).toEqual(["0.9.26", "0.9.25"]);
});
});
describe("getLatestVersion", () => {
it("returns the first version string", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
await expect(
getLatestVersion("https://example.com/custom.ndjson"),
).resolves.toBe("0.9.26");
});
});
describe("getArtifact", () => {
beforeEach(() => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
});
it("finds an artifact by version and platform", async () => {
const artifact = await getArtifact("0.9.26", "aarch64", "apple-darwin");
expect(artifact).toEqual({
archiveFormat: "tar.gz",
checksum:
"fcf0a9ea6599c6ae28a4c854ac6da76f2c889354d7c36ce136ef071f7ab9721f",
downloadUrl:
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.tar.gz",
});
});
it("finds a windows artifact", async () => {
const artifact = await getArtifact("0.9.26", "x86_64", "pc-windows-msvc");
expect(artifact).toEqual({
archiveFormat: "zip",
checksum:
"eb02fd95d8e0eed462b4a67ecdd320d865b38c560bffcda9a0b87ec944bdf036",
downloadUrl:
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-pc-windows-msvc.zip",
});
});
it("prefers the default variant when multiple artifacts share a platform", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", multiVariantManifestResponse),
);
const artifact = await getArtifact("0.9.26", "aarch64", "apple-darwin");
expect(artifact).toEqual({
archiveFormat: "zip",
checksum: "default-checksum",
downloadUrl:
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.zip",
});
});
it("returns undefined for an unknown version", async () => {
const artifact = await getArtifact("0.0.1", "aarch64", "apple-darwin");
expect(artifact).toBeUndefined();
});
it("returns undefined for an unknown platform", async () => {
const artifact = await getArtifact(
"0.9.26",
"aarch64",
"unknown-linux-musl",
);
expect(artifact).toBeUndefined();
});
});
describe("parseManifest", () => {
it("throws for malformed manifest data", () => {
expect(() => parseManifest('{"version":"0.1.0"', "test-source")).toThrow(
"Failed to parse manifest data from test-source",
);
});
});
});

View File

@@ -1,136 +0,0 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
const mockWarning = jest.fn();
jest.unstable_mockModule("@actions/core", () => ({
debug: jest.fn(),
info: jest.fn(),
warning: mockWarning,
}));
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
const mockFetch = jest.fn<any>();
jest.unstable_mockModule("../../src/utils/fetch", () => ({
fetch: mockFetch,
}));
const {
clearManifestCache,
getAllVersions,
getLatestKnownVersion,
getManifestArtifact,
} = await import("../../src/download/version-manifest");
const legacyManifestResponse = JSON.stringify([
{
arch: "x86_64",
artifactName: "uv-x86_64-unknown-linux-gnu.tar.gz",
downloadUrl:
"https://example.com/releases/download/0.7.12-alpha.1/uv-x86_64-unknown-linux-gnu.tar.gz",
platform: "unknown-linux-gnu",
version: "0.7.12-alpha.1",
},
{
arch: "x86_64",
artifactName: "uv-x86_64-unknown-linux-gnu.tar.gz",
downloadUrl:
"https://example.com/releases/download/0.7.13/uv-x86_64-unknown-linux-gnu.tar.gz",
platform: "unknown-linux-gnu",
version: "0.7.13",
},
]);
const ndjsonManifestResponse = `{"version":"0.10.0","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://example.com/releases/download/0.10.0/uv-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"checksum-100"}]}
{"version":"0.9.30","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://example.com/releases/download/0.9.30/uv-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"checksum-0930"}]}`;
const multiVariantManifestResponse = `{"version":"0.10.0","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"managed-python","url":"https://example.com/releases/download/0.10.0/uv-x86_64-unknown-linux-gnu-managed-python.tar.gz","archive_format":"tar.gz","sha256":"checksum-managed"},{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://example.com/releases/download/0.10.0/uv-x86_64-unknown-linux-gnu-default.zip","archive_format":"zip","sha256":"checksum-default"}]}`;
function createMockResponse(
ok: boolean,
status: number,
statusText: string,
data: string,
) {
return {
ok,
status,
statusText,
text: async () => data,
};
}
describe("version-manifest", () => {
beforeEach(() => {
clearManifestCache();
mockFetch.mockReset();
mockWarning.mockReset();
});
it("supports the legacy JSON manifest format", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", legacyManifestResponse),
);
const latest = await getLatestKnownVersion(
"https://example.com/legacy.json",
);
const artifact = await getManifestArtifact(
"https://example.com/legacy.json",
"0.7.13",
"x86_64",
"unknown-linux-gnu",
);
expect(latest).toBe("0.7.13");
expect(artifact).toEqual({
archiveFormat: undefined,
checksum: undefined,
downloadUrl:
"https://example.com/releases/download/0.7.13/uv-x86_64-unknown-linux-gnu.tar.gz",
});
expect(mockWarning).toHaveBeenCalledTimes(1);
});
it("supports NDJSON manifests", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", ndjsonManifestResponse),
);
const versions = await getAllVersions("https://example.com/custom.ndjson");
const artifact = await getManifestArtifact(
"https://example.com/custom.ndjson",
"0.10.0",
"x86_64",
"unknown-linux-gnu",
);
expect(versions).toEqual(["0.10.0", "0.9.30"]);
expect(artifact).toEqual({
archiveFormat: "tar.gz",
checksum: "checksum-100",
downloadUrl:
"https://example.com/releases/download/0.10.0/uv-x86_64-unknown-linux-gnu.tar.gz",
});
expect(mockWarning).not.toHaveBeenCalled();
});
it("prefers the default variant when a manifest contains multiple variants", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", multiVariantManifestResponse),
);
const artifact = await getManifestArtifact(
"https://example.com/multi-variant.ndjson",
"0.10.0",
"x86_64",
"unknown-linux-gnu",
);
expect(artifact).toEqual({
archiveFormat: "zip",
checksum: "checksum-default",
downloadUrl:
"https://example.com/releases/download/0.10.0/uv-x86_64-unknown-linux-gnu-default.zip",
});
});
});

View File

@@ -1,170 +0,0 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
// biome-ignore lint/suspicious/noExplicitAny: Mock requires flexible typing in tests.
const mockFetch = jest.fn<any>();
jest.unstable_mockModule("../../src/utils/fetch", () => ({
fetch: mockFetch,
}));
const {
clearCache,
fetchVersionData,
getAllVersions,
getArtifact,
getLatestVersion,
parseVersionData,
} = await import("../../src/download/versions-client");
const sampleNdjsonResponse = `{"version":"0.9.26","artifacts":[{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.tar.gz","archive_format":"tar.gz","sha256":"fcf0a9ea6599c6ae28a4c854ac6da76f2c889354d7c36ce136ef071f7ab9721f"},{"platform":"x86_64-pc-windows-msvc","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-pc-windows-msvc.zip","archive_format":"zip","sha256":"eb02fd95d8e0eed462b4a67ecdd320d865b38c560bffcda9a0b87ec944bdf036"}]}
{"version":"0.9.25","artifacts":[{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/uv/releases/download/0.9.25/uv-aarch64-apple-darwin.tar.gz","archive_format":"tar.gz","sha256":"606b3c6949d971709f2526fa0d9f0fd23ccf60e09f117999b406b424af18a6a6"}]}`;
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 createMockResponse(
ok: boolean,
status: number,
statusText: string,
data: string,
) {
return {
ok,
status,
statusText,
text: async () => data,
};
}
describe("versions-client", () => {
beforeEach(() => {
clearCache();
mockFetch.mockReset();
});
describe("fetchVersionData", () => {
it("should fetch and parse NDJSON data", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleNdjsonResponse),
);
const versions = await fetchVersionData();
expect(versions).toHaveLength(2);
expect(versions[0].version).toBe("0.9.26");
expect(versions[1].version).toBe("0.9.25");
});
it("should throw error on failed fetch", async () => {
mockFetch.mockResolvedValue(
createMockResponse(false, 500, "Internal Server Error", ""),
);
await expect(fetchVersionData()).rejects.toThrow(
"Failed to fetch version data: 500 Internal Server Error",
);
});
it("should cache results", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleNdjsonResponse),
);
await fetchVersionData();
await fetchVersionData();
expect(mockFetch).toHaveBeenCalledTimes(1);
});
});
describe("getLatestVersion", () => {
it("should return the first version (newest)", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleNdjsonResponse),
);
const latest = await getLatestVersion();
expect(latest).toBe("0.9.26");
});
});
describe("getAllVersions", () => {
it("should return all version strings", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleNdjsonResponse),
);
const versions = await getAllVersions();
expect(versions).toEqual(["0.9.26", "0.9.25"]);
});
});
describe("getArtifact", () => {
beforeEach(() => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleNdjsonResponse),
);
});
it("should find artifact by version and platform", async () => {
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");
expect(artifact).toEqual({
archiveFormat: "zip",
sha256:
"eb02fd95d8e0eed462b4a67ecdd320d865b38c560bffcda9a0b87ec944bdf036",
url: "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-pc-windows-msvc.zip",
});
});
it("should prefer the default variant when multiple artifacts share a platform", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", multiVariantNdjsonResponse),
);
const artifact = await getArtifact("0.9.26", "aarch64", "apple-darwin");
expect(artifact).toEqual({
archiveFormat: "zip",
sha256: "default-checksum",
url: "https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.zip",
});
});
it("should return undefined for unknown version", async () => {
const artifact = await getArtifact("0.0.1", "aarch64", "apple-darwin");
expect(artifact).toBeUndefined();
});
it("should return undefined for unknown platform", async () => {
const artifact = await getArtifact(
"0.9.26",
"aarch64",
"unknown-linux-musl",
);
expect(artifact).toBeUndefined();
});
});
describe("parseVersionData", () => {
it("should throw for malformed NDJSON", () => {
expect(() =>
parseVersionData('{"version":"0.1.0"', "test-source"),
).toThrow("Failed to parse version data from test-source");
});
});
});

View File

@@ -1,3 +1,6 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import {
afterEach,
beforeEach,
@@ -7,9 +10,13 @@ import {
jest,
} from "@jest/globals";
// Will be mutated per test before (re-)importing the module under test
let mockInputs: Record<string, string> = {};
const tempDirs: string[] = [];
const ORIGINAL_HOME = process.env.HOME;
const ORIGINAL_RUNNER_ENVIRONMENT = process.env.RUNNER_ENVIRONMENT;
const ORIGINAL_RUNNER_TEMP = process.env.RUNNER_TEMP;
const ORIGINAL_UV_CACHE_DIR = process.env.UV_CACHE_DIR;
const ORIGINAL_UV_PYTHON_INSTALL_DIR = process.env.UV_PYTHON_INSTALL_DIR;
const mockDebug = jest.fn();
const mockGetBooleanInput = jest.fn(
@@ -27,118 +34,220 @@ jest.unstable_mockModule("@actions/core", () => ({
warning: mockWarning,
}));
async function importInputsModule() {
return await import("../../src/utils/inputs");
const { CacheLocalSource, loadInputs } = await import("../../src/utils/inputs");
function createTempProject(files: Record<string, string> = {}): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "setup-uv-inputs-test-"));
tempDirs.push(dir);
for (const [relativePath, content] of Object.entries(files)) {
const filePath = path.join(dir, relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, content);
}
return dir;
}
function resetEnvironment(): void {
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
delete process.env.RUNNER_ENVIRONMENT;
delete process.env.RUNNER_TEMP;
delete process.env.UV_CACHE_DIR;
delete process.env.UV_PYTHON_INSTALL_DIR;
}
function restoreEnvironment(): void {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { force: true, recursive: true });
}
process.env.HOME = ORIGINAL_HOME;
process.env.RUNNER_ENVIRONMENT = ORIGINAL_RUNNER_ENVIRONMENT;
process.env.RUNNER_TEMP = ORIGINAL_RUNNER_TEMP;
process.env.UV_CACHE_DIR = ORIGINAL_UV_CACHE_DIR;
process.env.UV_PYTHON_INSTALL_DIR = ORIGINAL_UV_PYTHON_INSTALL_DIR;
}
beforeEach(resetEnvironment);
afterEach(restoreEnvironment);
describe("loadInputs", () => {
it("loads defaults for a github-hosted runner", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "auto";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
const inputs = loadInputs();
expect(inputs.enableCache).toBe(true);
expect(inputs.cacheLocalPath).toEqual({
path: "/runner-temp/setup-uv-cache",
source: CacheLocalSource.Default,
});
expect(inputs.pythonDir).toBe("/runner-temp/uv-python-dir");
expect(inputs.venvPath).toBe("/workspace/.venv");
expect(inputs.manifestFile).toBeUndefined();
expect(inputs.resolutionStrategy).toBe("highest");
});
it("uses cache-dir from pyproject.toml when present", () => {
mockInputs["working-directory"] = createTempProject({
"pyproject.toml": `[project]
name = "uv-project"
version = "0.1.0"
[tool.uv]
cache-dir = "/tmp/pyproject-toml-defined-cache-path"
`,
});
const inputs = loadInputs();
expect(inputs.cacheLocalPath).toEqual({
path: "/tmp/pyproject-toml-defined-cache-path",
source: CacheLocalSource.Config,
});
expect(mockInfo).toHaveBeenCalledWith(
expect.stringContaining("Found cache-dir in"),
);
});
it("uses UV_CACHE_DIR from the environment", () => {
mockInputs["working-directory"] = createTempProject();
process.env.UV_CACHE_DIR = "/env/cache-dir";
const inputs = loadInputs();
expect(inputs.cacheLocalPath).toEqual({
path: "/env/cache-dir",
source: CacheLocalSource.Env,
});
expect(mockInfo).toHaveBeenCalledWith(
"UV_CACHE_DIR is already set to /env/cache-dir",
);
});
it("uses UV_PYTHON_INSTALL_DIR from the environment", () => {
mockInputs["working-directory"] = "/workspace";
process.env.UV_PYTHON_INSTALL_DIR = "/env/python-dir";
const inputs = loadInputs();
expect(inputs.pythonDir).toBe("/env/python-dir");
expect(mockInfo).toHaveBeenCalledWith(
"UV_PYTHON_INSTALL_DIR is already set to /env/python-dir",
);
});
it("warns when parsing a malformed pyproject.toml for cache-dir", () => {
mockInputs["working-directory"] = createTempProject({
"pyproject.toml": `[project]
name = "malformed-pyproject-toml-project"
version = "0.1.0"
[malformed-toml
`,
});
const inputs = loadInputs();
expect(inputs.cacheLocalPath).toBeUndefined();
expect(mockWarning).toHaveBeenCalledWith(
expect.stringContaining("Error while parsing pyproject.toml:"),
);
});
it("throws for an invalid resolution strategy", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["resolution-strategy"] = "middle";
expect(() => loadInputs()).toThrow(
"Invalid resolution-strategy: middle. Must be 'highest' or 'lowest'.",
);
});
});
describe("cacheDependencyGlob", () => {
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
});
afterEach(() => {
process.env.HOME = ORIGINAL_HOME;
});
it("returns empty string when input not provided", async () => {
it("returns empty string when input not provided", () => {
mockInputs["working-directory"] = "/workspace";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe("");
const inputs = loadInputs();
expect(inputs.cacheDependencyGlob).toBe("");
});
it("resolves a single relative path", async () => {
it.each([
["requirements.txt", "/workspace/requirements.txt"],
["./uv.lock", "/workspace/uv.lock"],
])("resolves %s to %s", (globInput, expected) => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = "requirements.txt";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe("/workspace/requirements.txt");
mockInputs["cache-dependency-glob"] = globInput;
const inputs = loadInputs();
expect(inputs.cacheDependencyGlob).toBe(expected);
});
it("strips leading ./ from relative path", async () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = "./uv.lock";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe("/workspace/uv.lock");
});
it("handles multiple lines, trimming whitespace, tilde expansion and absolute paths", async () => {
it("handles multiple lines, trimming whitespace, tilde expansion and absolute paths", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] =
" ~/.cache/file1\n ./rel/file2 \nfile3.txt";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe(
const inputs = loadInputs();
expect(inputs.cacheDependencyGlob).toBe(
[
"/home/testuser/.cache/file1", // expanded tilde, absolute path unchanged
"/workspace/rel/file2", // ./ stripped and resolved
"/workspace/file3.txt", // relative path resolved
"/home/testuser/.cache/file1",
"/workspace/rel/file2",
"/workspace/file3.txt",
].join("\n"),
);
});
it("keeps absolute path unchanged in multiline input", async () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = "/abs/path.lock\nrelative.lock";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe(
it.each([
[
"/abs/path.lock\nrelative.lock",
["/abs/path.lock", "/workspace/relative.lock"].join("\n"),
);
});
it("handles exclusions in relative paths correct", async () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = "!/abs/path.lock\n!relative.lock";
const { cacheDependencyGlob } = await importInputsModule();
expect(cacheDependencyGlob).toBe(
],
[
"!/abs/path.lock\n!relative.lock",
["!/abs/path.lock", "!/workspace/relative.lock"].join("\n"),
);
],
])("normalizes multiline glob %s", (globInput, expected) => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-dependency-glob"] = globInput;
const inputs = loadInputs();
expect(inputs.cacheDependencyGlob).toBe(expected);
});
});
describe("tool directories", () => {
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
});
afterEach(() => {
process.env.HOME = ORIGINAL_HOME;
});
it("expands tilde for tool-bin-dir and tool-dir", async () => {
it("expands tilde for tool-bin-dir and tool-dir", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["tool-bin-dir"] = "~/tool-bin-dir";
mockInputs["tool-dir"] = "~/tool-dir";
const { toolBinDir, toolDir } = await importInputsModule();
const inputs = loadInputs();
expect(toolBinDir).toBe("/home/testuser/tool-bin-dir");
expect(toolDir).toBe("/home/testuser/tool-dir");
expect(inputs.toolBinDir).toBe("/home/testuser/tool-bin-dir");
expect(inputs.toolDir).toBe("/home/testuser/tool-dir");
});
});
describe("cacheLocalPath", () => {
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
});
afterEach(() => {
process.env.HOME = ORIGINAL_HOME;
});
it("expands tilde in cache-local-path", async () => {
it("expands tilde in cache-local-path", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["cache-local-path"] = "~/uv-cache/cache-local-path";
const { CacheLocalSource, cacheLocalPath } = await importInputsModule();
const inputs = loadInputs();
expect(cacheLocalPath).toEqual({
expect(inputs.cacheLocalPath).toEqual({
path: "/home/testuser/uv-cache/cache-local-path",
source: CacheLocalSource.Input,
});
@@ -146,63 +255,37 @@ describe("cacheLocalPath", () => {
});
describe("venvPath", () => {
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
mockInputs = {};
process.env.HOME = "/home/testuser";
});
afterEach(() => {
process.env.HOME = ORIGINAL_HOME;
});
it("defaults to .venv in the working directory", async () => {
it("defaults to .venv in the working directory", () => {
mockInputs["working-directory"] = "/workspace";
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/workspace/.venv");
const inputs = loadInputs();
expect(inputs.venvPath).toBe("/workspace/.venv");
});
it("resolves a relative venv-path", async () => {
it.each([
["custom-venv", "/workspace/custom-venv"],
["custom-venv/", "/workspace/custom-venv"],
["/tmp/custom-venv", "/tmp/custom-venv"],
["~/.venv", "/home/testuser/.venv"],
])("resolves venv-path %s to %s", (venvPathInput, expected) => {
mockInputs["working-directory"] = "/workspace";
mockInputs["activate-environment"] = "true";
mockInputs["venv-path"] = "custom-venv";
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/workspace/custom-venv");
mockInputs["venv-path"] = venvPathInput;
const inputs = loadInputs();
expect(inputs.venvPath).toBe(expected);
});
it("normalizes venv-path with trailing slash", async () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["activate-environment"] = "true";
mockInputs["venv-path"] = "custom-venv/";
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/workspace/custom-venv");
});
it("keeps an absolute venv-path unchanged", async () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["activate-environment"] = "true";
mockInputs["venv-path"] = "/tmp/custom-venv";
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/tmp/custom-venv");
});
it("expands tilde in venv-path", async () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["activate-environment"] = "true";
mockInputs["venv-path"] = "~/.venv";
const { venvPath } = await importInputsModule();
expect(venvPath).toBe("/home/testuser/.venv");
});
it("warns when venv-path is set but activate-environment is false", async () => {
it("warns when venv-path is set but activate-environment is false", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["venv-path"] = "custom-venv";
const { activateEnvironment, venvPath } = await importInputsModule();
const inputs = loadInputs();
expect(activateEnvironment).toBe(false);
expect(venvPath).toBe("/workspace/custom-venv");
expect(inputs.activateEnvironment).toBe(false);
expect(inputs.venvPath).toBe("/workspace/custom-venv");
expect(mockWarning).toHaveBeenCalledWith(
"venv-path is only used when activate-environment is true",
);

View File

@@ -75,7 +75,7 @@ inputs:
description: "Custom path to set UV_TOOL_BIN_DIR to."
required: false
manifest-file:
description: "URL to a custom manifest file. Supports the astral-sh/versions NDJSON format and the legacy JSON array format (deprecated)."
description: "URL to a custom manifest file in the astral-sh/versions format."
required: false
add-problem-matchers:
description: "Add problem matchers."

627
dist/save-cache/index.cjs generated vendored

File diff suppressed because it is too large Load Diff

3501
dist/setup/index.cjs generated vendored

File diff suppressed because it is too large Load Diff

175
dist/update-known-checksums/index.cjs generated vendored
View File

@@ -44949,6 +44949,111 @@ var semver = __toESM(require_semver(), 1);
// src/download/checksum/known-checksums.ts
var KNOWN_CHECKSUMS = {
"aarch64-apple-darwin-0.11.3": "2bc3d0c7bf2bd08325b1e170abac6f7e5b3346e1d4eab3370d17cefec934996f",
"aarch64-pc-windows-msvc-0.11.3": "e99c56f9ab5e1e1ddcaea3e2389990c94baf38e0d7cb2148de08baf2d3261d49",
"aarch64-unknown-linux-gnu-0.11.3": "711382e3158433f06b11d99afb440f4416359fc3c84558886d8ed8826a921bff",
"aarch64-unknown-linux-musl-0.11.3": "8ecec82cb9a744d5fabff6d16d7777218a7730f699d2aa0d2f751c17858e2efa",
"arm-unknown-linux-musleabihf-0.11.3": "3d021046a94ad11f12b9d83f36442a1a28e92e7149c3f79ba2951c96653dafac",
"armv7-unknown-linux-gnueabihf-0.11.3": "13c9a0f5f624275ccd36db2896607f4fee3585f420734b16f6c66d70e32aa458",
"armv7-unknown-linux-musleabihf-0.11.3": "260a88e2f00daab0363a745fde036a7881002d7a81094388f31925acb284110b",
"i686-pc-windows-msvc-0.11.3": "036fa39fa5ea3cb86c127324924b913b5858e8d91c4cb413edacfc3123001696",
"i686-unknown-linux-gnu-0.11.3": "b9410c8dae2fa0d4939af5b0ee7272d5591bd55890e8274dcf7f1aea84bfe043",
"i686-unknown-linux-musl-0.11.3": "afe533fd409105e753d844490c65a4375e75bfb3812e49122684f996bed9e90a",
"powerpc64le-unknown-linux-gnu-0.11.3": "5cdcadf4d50a5354312bc8ef37c2a6cfab4e2f13ccdf8380d3012b927b4ded95",
"riscv64gc-unknown-linux-gnu-0.11.3": "8271e07ed9695870f4b0ae5ec722e3ae08fff280068f08bc6a8ca76c67d7fefa",
"riscv64gc-unknown-linux-musl-0.11.3": "b750fc8393ced9939448849b05e94de6bf1e998bb7030c4ebe744b47b372bce9",
"s390x-unknown-linux-gnu-0.11.3": "6dc4f555a5f6515f7fddb281422d2a8a3943853dae5de837bbb5d996d7576c71",
"x86_64-apple-darwin-0.11.3": "b0e05e0b43a000fdc2132ee3f3400ba5dee427bc2337d3ec4eb8cf4f3d5722af",
"x86_64-pc-windows-msvc-0.11.3": "ae681c0aaec7cc96af184648cb88d73f8393ed60fa5880abdd6bdb910f9b227c",
"x86_64-unknown-linux-gnu-0.11.3": "c0f3236f146e55472663cfbcc9be3042a9f1092275bbe3fe2a56a6cbfd3da5ce",
"x86_64-unknown-linux-musl-0.11.3": "8b40cf16b849634b81a530a3d0a0bcae5f24996ef9ae782976fd69b6266d3b8e",
"aarch64-apple-darwin-0.11.2": "4beaa9550f93ef7f0fc02f7c28c9c48cd61fe30db00f5ac8947e0a425c3fb282",
"aarch64-pc-windows-msvc-0.11.2": "ffdded8338205f53727b51d404563a5ac8eaa9aea53279a7b7c42177e11d478c",
"aarch64-unknown-linux-gnu-0.11.2": "04792cac761c4a6ba78267f36f2af541b7f92196d42ac55d21d3ff6b0f5ab6a5",
"aarch64-unknown-linux-musl-0.11.2": "275d91dd1f1955136591e7ec5e1fa21e84d0d37ead7da7c35c3683df748d9855",
"arm-unknown-linux-musleabihf-0.11.2": "ce572dac1a8f9a92960f89e99351352fae068d34b24bed86fb88e75fd5dd67d9",
"armv7-unknown-linux-gnueabihf-0.11.2": "3e90d7de9e3a4e2d8d1bd9ce164362fce22248474986e712039479fb6fd73136",
"armv7-unknown-linux-musleabihf-0.11.2": "5222cdd7c7dd3263f8c243831606a9f01a1a07a40ffc3c26c03afb34491075c2",
"i686-pc-windows-msvc-0.11.2": "506f8274b253b2386881a121f3b7d915b637019bda15876bbd1357235305cf12",
"i686-unknown-linux-gnu-0.11.2": "c7ec378bab887443a70786382e58d76489da14a7e33b155915d648cca4bdb46c",
"i686-unknown-linux-musl-0.11.2": "ade8714be45457899568c5b03ef885a0cc94476c07a0bdbe34531ba84231bab2",
"powerpc64le-unknown-linux-gnu-0.11.2": "3f3a50e99364efc8ff7add10e79757a2b8458700a38180ec5f313524481b9fbc",
"riscv64gc-unknown-linux-gnu-0.11.2": "e56a93f0ff21d6908461a6ecbf465beae19ae22719f900284abb7680bd07ec41",
"riscv64gc-unknown-linux-musl-0.11.2": "4f263571bb457a16a31cb38fba4fcc9cf1059d1d32c5b2e54c43175fcd59205d",
"s390x-unknown-linux-gnu-0.11.2": "42ebe40775f2a77a514fa47399fde86473bf35bd33b6896c6410a0309fc4d205",
"x86_64-apple-darwin-0.11.2": "a9c3653245031304c50dd60ac0301bf6c112e12c38c32302a71d4fa6a63ba2cb",
"x86_64-pc-windows-msvc-0.11.2": "171b7ccda1bbd562da6babeffcf533a1c6cc7862cf998da826e1db534fc43e48",
"x86_64-unknown-linux-gnu-0.11.2": "7ac2ca0449c8d68dae9b99e635cd3bc9b22a4cb1de64b7c43716398447d42981",
"x86_64-unknown-linux-musl-0.11.2": "4700d9fc75734247587deb3e25dd2c6c24f4ac69e8fe91d6acad4a6013115c06",
"aarch64-apple-darwin-0.11.1": "f7815f739ed5d0e4202e6292acedb8659b9ae7de663d07188d8c6cbd7f96303f",
"aarch64-pc-windows-msvc-0.11.1": "b789db0c1504dd3b02c090bd5783487497cc46cc2eb71754874cdd1ef59eb52a",
"aarch64-unknown-linux-gnu-0.11.1": "1340e62da1ee3c1109764340e1247e8a1a232c30dde4a0f0548976dcaa90f06d",
"aarch64-unknown-linux-musl-0.11.1": "bd04ffce77ee8d77f39823c13606183581847c2f5dcd704f2ea0f15e376b1a27",
"arm-unknown-linux-musleabihf-0.11.1": "625c0e756e2374fce864ceaa6beedd5821e276e2b6307f2b719f2d62b449b89c",
"armv7-unknown-linux-gnueabihf-0.11.1": "baf8daaab20b0502d1853dbfd916afb0762c024ae7f0df1c2deb2a1a1c1c3467",
"armv7-unknown-linux-musleabihf-0.11.1": "684c25b74e83bcb1b177152379cfe2c974ba731aa5af278e1d161e41709f8bcf",
"i686-pc-windows-msvc-0.11.1": "3c07858a08c54e4e5753239354c7b07ae69071b2b6f5aa2cc970e612adcb4740",
"i686-unknown-linux-gnu-0.11.1": "6e83167c05708570563b10b6cc7e8c289daef5f51fde0b152e41af2a7ef70813",
"i686-unknown-linux-musl-0.11.1": "b0d5152635c257fec76f95cb9268112b47ff70bd33a23866295a4f2ed9f46b7f",
"powerpc64le-unknown-linux-gnu-0.11.1": "e42d2abfac46f57564789e2bfa6dbea4ae3135892e36ae066ba0ae77b69bb676",
"riscv64gc-unknown-linux-gnu-0.11.1": "5e2c757b35dab015ad37f74ee3e060208390b5f4defb6684876f1be0664f3f6e",
"riscv64gc-unknown-linux-musl-0.11.1": "6f590a824aed363cbec4079f7ddab87b5685119e0f5f0e71cd114c7b7c326199",
"s390x-unknown-linux-gnu-0.11.1": "4208173c74e29572b799178709b5ed5828b24888659f944a4b47c0aaf78b42d2",
"x86_64-apple-darwin-0.11.1": "2103670e8e949605e51926c7b953923ff6f6befbfb55aee928f5e760c9c910f8",
"x86_64-pc-windows-msvc-0.11.1": "6659250cebbd3bb6ee48bcb21a3f0c6656450d63fb97f0f069bcb532bdb688ed",
"x86_64-unknown-linux-gnu-0.11.1": "7c0c8069053e6e99e5911ff32b916be571f3419cd8e11bd28fb7da2c7dcaa553",
"x86_64-unknown-linux-musl-0.11.1": "4e949471a95b37088a1ff1a585f69abed4d3cd3f921f50709a46b6ba62986d38",
"aarch64-apple-darwin-0.11.0": "0c0f32c6a3473c5928aff96c3233715edfc79290e892f255cac93710cde7b91a",
"aarch64-pc-windows-msvc-0.11.0": "95419e04a3ef5f13fb2a06bd6d787ba80a9d8981d6f097780e5a979817a2879d",
"aarch64-unknown-linux-gnu-0.11.0": "8e179ca110343a17f801444ff9ef117dba56ef5fc9f6a4c9bb77b318ddba5f24",
"aarch64-unknown-linux-musl-0.11.0": "658be4b8ec905635f1295468d4d5120d9e1ab1722eec9a104473ce993590babe",
"arm-unknown-linux-musleabihf-0.11.0": "bfdcbd5fa41c8a9877a72c2b55a95da2bc79933885ef56c699b65bb2ed9cea91",
"armv7-unknown-linux-gnueabihf-0.11.0": "0cad4e1b6769e48aa1e80cf639ddcc7c1bfe9ed017e95868fed185a8d818c949",
"armv7-unknown-linux-musleabihf-0.11.0": "2aa9da83c6c0cf8a06bc9df14d51056284fa067ef5390b4db79998ff12f3bee7",
"i686-pc-windows-msvc-0.11.0": "3b09d70e686087e096dbd8a2af21b922a2cac7d613dc053c3281c3ddbb961961",
"i686-unknown-linux-gnu-0.11.0": "59928a0267501c20d9f9942f5f1d81a991ec55e29a19e002ae3d5c178c674c89",
"i686-unknown-linux-musl-0.11.0": "1f438d6f6f851f0dabad3307ce7fd46541ecc5c42ebb664f382eb6c9a424a67d",
"powerpc64le-unknown-linux-gnu-0.11.0": "29f17fb43595492b1a36cda57df7adad74183132df32799d32897268ff4e26dd",
"riscv64gc-unknown-linux-gnu-0.11.0": "84ef37dda1003c5b65fa6c8f84242d35a7fcc84cc5ea9490d702edc36cad1f67",
"s390x-unknown-linux-gnu-0.11.0": "b25be62f3b642348a2fece5c658624586661b8d1103891ab6903768b0529edc4",
"x86_64-apple-darwin-0.11.0": "31aaec764166af8885cf99321fd6ed24fef80225a6f26ed1ae8ce04111688a7e",
"x86_64-pc-windows-msvc-0.11.0": "e21d00b172df83531564a95e75a2bdc0c59b471dbb3515f0c1b4d6ef657dc451",
"x86_64-unknown-linux-gnu-0.11.0": "cc0fbb42b3642125f600a55b0b095bea65cddaadb94c6ea2b6ba5d79c5825089",
"x86_64-unknown-linux-musl-0.11.0": "bf6b0757c73d1726faa2a819b155d4d864919a95766720215d78fdcd09d42d26",
"aarch64-apple-darwin-0.10.12": "ae738b5661a900579ec621d3918c0ef17bdec0da2a8a6d8b161137cd15f25414",
"aarch64-pc-windows-msvc-0.10.12": "e79881e2c4f98a0f3a37b8770bf224e8fee70f6dcf8fc17055d8291bb1b0b867",
"aarch64-unknown-linux-gnu-0.10.12": "0ed7d20f49f6b9b60d45fdfcac28f3ac01a671a6ef08672401ed2833423fea2a",
"aarch64-unknown-linux-musl-0.10.12": "55bd1c1c10ec8b95a8c184f5e18b566703c6ab105f0fc118aaa4d748aabf28e4",
"arm-unknown-linux-musleabihf-0.10.12": "9714e5059b05110a1c7ddbc18c971c13e0260e10551b7b77d82cbf907a4ebd9b",
"armv7-unknown-linux-gnueabihf-0.10.12": "eaa02f36d5112029601b18ac3d1a0c03a83bb20cb4154c2f5345f777fa6c4101",
"armv7-unknown-linux-musleabihf-0.10.12": "bd735652298c6e62cdd2ac939babe176a3356613e6803baa33d0bc10e8d9e4ed",
"i686-pc-windows-msvc-0.10.12": "2312e75b9c77befdc1bff30da18f16df03083452852952553bee91da362c1a1d",
"i686-unknown-linux-gnu-0.10.12": "8501844b34e3a28cfbba5a4b857eebd696d952e0bb4160357451ad80f3f49db8",
"i686-unknown-linux-musl-0.10.12": "56cad78abcf5b710d2f7b9f774fcfd6bbed340d2aa9d9fc9e3b515542ec5e953",
"powerpc64le-unknown-linux-gnu-0.10.12": "3c8017d9112221c83f43e8a15a58099663c0b2bdeabc8b43bb800413dfa21218",
"riscv64gc-unknown-linux-gnu-0.10.12": "b1ca482b6b5dd7bf6ab733a3695cb0ab5b8e992ca96527efae93aa78fcc52a9b",
"s390x-unknown-linux-gnu-0.10.12": "e1a0345eefe6fd3300948cd6f18aab092f9b88a243782113e645ce96530a6693",
"x86_64-apple-darwin-0.10.12": "17443e293f2ae407bb2d8d34b875ebfe0ae01cf1296de5647e69e7b2e2b428f0",
"x86_64-pc-windows-msvc-0.10.12": "4c1d55501869b3330d4aabf45ad6024ce2367e0f3af83344395702d272c22e88",
"x86_64-unknown-linux-gnu-0.10.12": "ec72570c9d1f33021aa80b176d7baba390de2cfeb1abcbefca346d563bf17484",
"x86_64-unknown-linux-musl-0.10.12": "adccf40b5d1939a5e0093081ec2307ea24235adf7c2d96b122c561fa37711c46",
"aarch64-apple-darwin-0.10.11": "437a7d498dd6564d5bf986074249ba1fc600e73da55ae04d7bd4c24d5f149b95",
"aarch64-pc-windows-msvc-0.10.11": "6a3eec4105c775dd87c11ef8ec41564648273751ff807c8955c24ddbcc636d03",
"aarch64-unknown-linux-gnu-0.10.11": "23003df007937dd607409c8ddf010baa82bad2673e60e254632ca5b04edcce13",
"aarch64-unknown-linux-musl-0.10.11": "5d80a7f6343d2676dfde1e5126582070a2bbc62df6f60d5527a169be3788532a",
"arm-unknown-linux-musleabihf-0.10.11": "d3c248497c450d22a39c1d43a4a358c0c852e6056f5f49be96495eea41afb96c",
"armv7-unknown-linux-gnueabihf-0.10.11": "7895a6470dfba051af4e74253599482fc0b37141b5d229956b383365e1a22902",
"armv7-unknown-linux-musleabihf-0.10.11": "d2880c08acfdaef0985488972c8b14969f7139c27545046e2f6202f0e0f4d9d8",
"i686-pc-windows-msvc-0.10.11": "c17f3dc3b2c47490057f17a1f0c37270f11a7b7cedf9bf2c0f841ce02bc7001b",
"i686-unknown-linux-gnu-0.10.11": "1ab69ff7dd104a902731758ee05b782dfd9bdb263384e61650de638f33f586df",
"i686-unknown-linux-musl-0.10.11": "cffb80d303fc1655e259d0b769c489f452e97425a6b6d3393d766413783a1d8c",
"powerpc64le-unknown-linux-gnu-0.10.11": "ddc6a20670e60219e947b1b04813be80d7e9f4c4a0234231c8ed9298eec04aa6",
"riscv64gc-unknown-linux-gnu-0.10.11": "c0719473cf5f8b475e917b8dfef6ae5d876b86a00a82ef91e47a02f561399f4f",
"s390x-unknown-linux-gnu-0.10.11": "305ee734c585918515a22fe43b7cf253c38d468771373a0c02364d67498e07b2",
"x86_64-apple-darwin-0.10.11": "ff90020b554cf02ef8008535c9aab6ef27bb7be6b075359300dec79c361df897",
"x86_64-pc-windows-msvc-0.10.11": "9ee74df98582f37fdd6069e1caac80d2616f9a489f5dbb2b1c152f30be69c58e",
"x86_64-unknown-linux-gnu-0.10.11": "5a360b0de092ddf4131f5313d0411b48c4e95e8107e40c3f8f2e9fcb636b3583",
"x86_64-unknown-linux-musl-0.10.11": "d78246139dc6cf3ed6d03c84da762686bced7ad1de67977ee372a45b95a1f6d0",
"aarch64-apple-darwin-0.10.10": "8a09f0ef51ee7f7170731b4cb8bde5bf9ba6da5304f49a7df6cdab42a1f37b5d",
"aarch64-pc-windows-msvc-0.10.10": "2c6fe113f14574bc27f085751c68d3485589fcc3c3c64ed85dd1eecc2f87cffc",
"aarch64-unknown-linux-gnu-0.10.10": "2b80457b950deda12e8d5dc3b9b7494ac143eae47f1fb11b1c6e5a8495a6421e",
@@ -49377,7 +49482,7 @@ async function updateChecksums(filePath, checksumEntries) {
}
// src/utils/constants.ts
var VERSIONS_NDJSON_URL = "https://raw.githubusercontent.com/astral-sh/versions/main/v1/uv.ndjson";
var VERSIONS_MANIFEST_URL = "https://raw.githubusercontent.com/astral-sh/versions/main/v1/uv.ndjson";
// src/utils/fetch.ts
var import_undici2 = __toESM(require_undici2(), 1);
@@ -49397,72 +49502,80 @@ var fetch = async (url, opts) => await (0, import_undici2.fetch)(url, {
...opts
});
// src/download/versions-client.ts
var cachedVersionData = /* @__PURE__ */ new Map();
async function fetchVersionData(url = VERSIONS_NDJSON_URL) {
const cachedVersions = cachedVersionData.get(url);
// src/download/manifest.ts
var cachedManifestData = /* @__PURE__ */ new Map();
async function fetchManifest(manifestUrl = VERSIONS_MANIFEST_URL) {
const cachedVersions = cachedManifestData.get(manifestUrl);
if (cachedVersions !== void 0) {
debug(`Using cached NDJSON version data from ${url}`);
debug(`Using cached manifest data from ${manifestUrl}`);
return cachedVersions;
}
info(`Fetching version data from ${url} ...`);
const response = await fetch(url, {});
info(`Fetching manifest data from ${manifestUrl} ...`);
const response = await fetch(manifestUrl, {});
if (!response.ok) {
throw new Error(
`Failed to fetch version data: ${response.status} ${response.statusText}`
`Failed to fetch manifest data: ${response.status} ${response.statusText}`
);
}
const body = await response.text();
const versions = parseVersionData(body, url);
cachedVersionData.set(url, versions);
const versions = parseManifest(body, manifestUrl);
cachedManifestData.set(manifestUrl, versions);
return versions;
}
function parseVersionData(data, sourceDescription) {
function parseManifest(data, sourceDescription) {
const trimmed = data.trim();
if (trimmed === "") {
throw new Error(`Manifest at ${sourceDescription} is empty.`);
}
if (trimmed.startsWith("[")) {
throw new Error(
`Legacy JSON array manifests are no longer supported in ${sourceDescription}. Use the astral-sh/versions manifest format instead.`
);
}
const versions = [];
for (const [index, line] of data.split("\n").entries()) {
const trimmed = line.trim();
if (trimmed === "") {
const record = line.trim();
if (record === "") {
continue;
}
let parsed;
try {
parsed = JSON.parse(trimmed);
parsed = JSON.parse(record);
} catch (error) {
throw new Error(
`Failed to parse version data from ${sourceDescription} at line ${index + 1}: ${error.message}`
`Failed to parse manifest data from ${sourceDescription} at line ${index + 1}: ${error.message}`
);
}
if (!isNdjsonVersion(parsed)) {
if (!isManifestVersion(parsed)) {
throw new Error(
`Invalid NDJSON record in ${sourceDescription} at line ${index + 1}.`
`Invalid manifest record in ${sourceDescription} at line ${index + 1}.`
);
}
versions.push(parsed);
}
if (versions.length === 0) {
throw new Error(`No version data found in ${sourceDescription}.`);
throw new Error(`No manifest data found in ${sourceDescription}.`);
}
return versions;
}
async function getLatestVersion() {
const versions = await fetchVersionData();
const latestVersion = versions[0]?.version;
if (!latestVersion) {
throw new Error("No versions found in NDJSON data");
async function getLatestVersion(manifestUrl = VERSIONS_MANIFEST_URL) {
const latestVersion = (await fetchManifest(manifestUrl))[0]?.version;
if (latestVersion === void 0) {
throw new Error("No versions found in manifest data");
}
debug(`Latest version from NDJSON: ${latestVersion}`);
debug(`Latest version from manifest: ${latestVersion}`);
return latestVersion;
}
function isNdjsonVersion(value) {
function isManifestVersion(value) {
if (!isRecord(value)) {
return false;
}
if (typeof value.version !== "string" || !Array.isArray(value.artifacts)) {
return false;
}
return value.artifacts.every(isNdjsonArtifact);
return value.artifacts.every(isManifestArtifact);
}
function isNdjsonArtifact(value) {
function isManifestArtifact(value) {
if (!isRecord(value)) {
return false;
}
@@ -49490,8 +49603,8 @@ async function run() {
);
return;
}
const versions = await fetchVersionData();
const checksumEntries = extractChecksumsFromNdjson(versions);
const versions = await fetchManifest();
const checksumEntries = extractChecksumsFromManifest(versions);
await updateChecksums(checksumFilePath, checksumEntries);
setOutput("latest-version", latestVersion);
}
@@ -49512,7 +49625,7 @@ function getLatestKnownVersionFromChecksums() {
function extractVersionFromChecksumKey(key) {
return key.match(VERSION_IN_CHECKSUM_KEY_PATTERN)?.[1];
}
function extractChecksumsFromNdjson(versions) {
function extractChecksumsFromManifest(versions) {
const checksums = [];
for (const version of versions) {
for (const artifact of version.artifacts) {

View File

@@ -6,7 +6,7 @@ This document covers advanced options for configuring which version of uv to ins
```yaml
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version: "latest"
```
@@ -15,7 +15,7 @@ This document covers advanced options for configuring which version of uv to ins
```yaml
- name: Install a specific version of uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version: "0.4.4"
```
@@ -28,21 +28,21 @@ to install the latest version that satisfies the range.
```yaml
- name: Install a semver range of uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version: ">=0.4.0"
```
```yaml
- name: Pinning a minor version of uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version: "0.4.x"
```
```yaml
- name: Install a pep440-specifier-satisfying version of uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version: ">=0.4.25,<0.5"
```
@@ -54,7 +54,7 @@ You can change this behavior using the `resolution-strategy` input:
```yaml
- name: Install the lowest compatible version of uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version: ">=0.4.0"
resolution-strategy: "lowest"
@@ -76,7 +76,7 @@ uv defined as a dependency in `pyproject.toml` or `requirements.txt`.
```yaml
- name: Install uv based on the version defined in pyproject.toml
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version-file: "pyproject.toml"
```

View File

@@ -23,7 +23,7 @@ The computed cache key is available as the `cache-key` output:
```yaml
- name: Setup uv
id: setup-uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
- name: Print cache key
@@ -50,7 +50,7 @@ You can optionally define a custom cache key suffix.
```yaml
- name: Enable caching and define a custom cache key suffix
id: setup-uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-suffix: "optional-suffix"
@@ -89,7 +89,7 @@ changes. If you use relative paths, they are relative to the working directory.
```yaml
- name: Define a cache dependency glob
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: "**/pyproject.toml"
@@ -97,7 +97,7 @@ changes. If you use relative paths, they are relative to the working directory.
```yaml
- name: Define a list of cache dependency globs
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: |
@@ -107,7 +107,7 @@ changes. If you use relative paths, they are relative to the working directory.
```yaml
- name: Define an absolute cache dependency glob
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: "/tmp/my-folder/requirements*.txt"
@@ -115,7 +115,7 @@ changes. If you use relative paths, they are relative to the working directory.
```yaml
- name: Never invalidate the cache
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: ""
@@ -128,7 +128,7 @@ By default, the cache will be restored.
```yaml
- name: Don't restore an existing cache
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
restore-cache: false
@@ -142,7 +142,7 @@ By default, the cache will be saved.
```yaml
- name: Don't save the cache after the run
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
save-cache: false
@@ -168,7 +168,7 @@ It defaults to `setup-uv-cache` in the `TMP` dir, `D:\a\_temp\setup-uv-cache` on
```yaml
- name: Define a custom uv cache path
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
cache-local-path: "/path/to/cache"
```
@@ -187,7 +187,7 @@ input.
```yaml
- name: Don't prune the cache before saving it
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
prune-cache: false
@@ -205,7 +205,7 @@ To force managed Python installs, set `UV_PYTHON_PREFERENCE=only-managed`.
```yaml
- name: Cache Python installs
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-python: true
@@ -213,12 +213,17 @@ To force managed Python installs, set `UV_PYTHON_PREFERENCE=only-managed`.
## Ignore nothing to cache
By default, the action will fail if caching is enabled but there is nothing to upload (the uv cache directory does not exist).
By default, the action will fail if caching is enabled but there is nothing to upload (the uv cache directory does not exist) with an error like
```console
Error: Cache path /home/runner/.cache/uv does not exist on disk. This likely indicates that there are no dependencies to cache. Consider disabling the cache input if it is not needed.
```
If you want to ignore this, set the `ignore-nothing-to-cache` input to `true`.
```yaml
- name: Ignore nothing to cache
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
ignore-nothing-to-cache: true

View File

@@ -10,7 +10,7 @@ are automatically verified by this action. The sha256 hashes can be found on the
```yaml
- name: Install a specific version and validate the checksum
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version: "0.3.1"
checksum: "e11b01402ab645392c7ad6044db63d37e4fd1e745e015306993b07695ea5f9f8"
@@ -19,14 +19,14 @@ are automatically verified by this action. The sha256 hashes can be found on the
## Manifest file
By default, setup-uv reads version metadata from
[`astral-sh/versions`](https://github.com/astral-sh/versions) (NDJSON format).
[`astral-sh/versions`](https://github.com/astral-sh/versions).
The `manifest-file` input lets you override that source with your own URL, for example to test
custom uv builds or alternate download locations.
### Format
The manifest file must be in NDJSON format, where each line is a JSON object representing a version and its artifacts. For example:
The manifest file must use the same format as `astral-sh/versions`: one JSON object per line, where each object represents a version and its artifacts. The versions must be sorted in descending order. For example:
```json
{"version":"0.10.7","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://example.com/uv-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"..."}]}
@@ -37,26 +37,9 @@ setup-uv currently only supports `default` as the `variant`.
The `archive_format` field is currently ignored.
### Legacy format: JSON array (deprecated)
The previous JSON array format is still supported for compatibility, but deprecated and will be
removed in a future major release.
```json
[
{
"version": "0.7.13",
"artifactName": "uv-aarch64-apple-darwin.tar.gz",
"arch": "aarch64",
"platform": "apple-darwin",
"downloadUrl": "https://github.com/astral-sh/uv/releases/download/0.7.13/uv-aarch64-apple-darwin.tar.gz"
}
]
```
```yaml
- name: Use a custom manifest file
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
manifest-file: "https://example.com/my-custom-manifest.ndjson"
```
@@ -75,7 +58,7 @@ You can disable this by setting the `add-problem-matchers` input to `false`.
```yaml
- name: Install the latest version of uv without problem matchers
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
add-problem-matchers: false
```

View File

@@ -9,7 +9,7 @@ This allows directly using it in later steps:
```yaml
- name: Install the latest version of uv and activate the environment
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
activate-environment: true
- run: uv pip install pip
@@ -20,7 +20,7 @@ By default, the venv is created at `.venv` inside the `working-directory`.
You can customize the venv location with `venv-path`, for example to place it in the runner temp directory:
```yaml
- uses: astral-sh/setup-uv@v7
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
activate-environment: true
venv-path: ${{ runner.temp }}/custom-venv
@@ -51,7 +51,7 @@ are not sufficient, you can provide a custom GitHub token with the necessary per
```yaml
- name: Install the latest version of uv with a custom GitHub token
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
github-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
```
@@ -69,7 +69,7 @@ input:
```yaml
- name: Install the latest version of uv with a custom tool dir
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
tool-dir: "/path/to/tool/dir"
```
@@ -88,7 +88,7 @@ If you want to change this behaviour (especially on self-hosted runners) you can
```yaml
- name: Install the latest version of uv with a custom tool bin dir
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
tool-bin-dir: "/path/to/tool-bin/dir"
```
@@ -105,7 +105,7 @@ This action supports expanding the `~` character to the user's home directory fo
```yaml
- name: Expand the tilde character
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
cache-local-path: "~/path/to/cache"
tool-dir: "~/path/to/tool/dir"
@@ -122,7 +122,7 @@ If you want to ignore this, set the `ignore-empty-workdir` input to `true`.
```yaml
- name: Ignore empty workdir
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
ignore-empty-workdir: true
```
@@ -145,7 +145,7 @@ This action sets several environment variables that influence uv's behavior and
```yaml
- name: Example using environment variables
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
python-version: "3.12"
tool-dir: "/custom/tool/dir"

151
package-lock.json generated
View File

@@ -20,7 +20,7 @@
"undici": "^7.24.2"
},
"devDependencies": {
"@biomejs/biome": "^2.4.7",
"@biomejs/biome": "^2.4.9",
"@types/js-yaml": "^4.0.9",
"@types/node": "^25.5.0",
"@types/semver": "^7.7.1",
@@ -863,11 +863,10 @@
"license": "MIT"
},
"node_modules/@biomejs/biome": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.7.tgz",
"integrity": "sha512-vXrgcmNGZ4lpdwZSpMf1hWw1aWS6B+SyeSYKTLrNsiUsAdSRN0J4d/7mF3ogJFbIwFFSOL3wT92Zzxia/d5/ng==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.9.tgz",
"integrity": "sha512-wvZW92FrwitTcacvCBT8xdAbfbxWfDLwjYMmU3djjqQTh7Ni4ZdiWIT/x5VcZ+RQuxiKzIOzi5D+dcyJDFZMsA==",
"dev": true,
"license": "MIT OR Apache-2.0",
"bin": {
"biome": "bin/biome"
},
@@ -879,25 +878,24 @@
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
"@biomejs/cli-darwin-arm64": "2.4.7",
"@biomejs/cli-darwin-x64": "2.4.7",
"@biomejs/cli-linux-arm64": "2.4.7",
"@biomejs/cli-linux-arm64-musl": "2.4.7",
"@biomejs/cli-linux-x64": "2.4.7",
"@biomejs/cli-linux-x64-musl": "2.4.7",
"@biomejs/cli-win32-arm64": "2.4.7",
"@biomejs/cli-win32-x64": "2.4.7"
"@biomejs/cli-darwin-arm64": "2.4.9",
"@biomejs/cli-darwin-x64": "2.4.9",
"@biomejs/cli-linux-arm64": "2.4.9",
"@biomejs/cli-linux-arm64-musl": "2.4.9",
"@biomejs/cli-linux-x64": "2.4.9",
"@biomejs/cli-linux-x64-musl": "2.4.9",
"@biomejs/cli-win32-arm64": "2.4.9",
"@biomejs/cli-win32-x64": "2.4.9"
}
},
"node_modules/@biomejs/cli-darwin-arm64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.7.tgz",
"integrity": "sha512-Oo0cF5mHzmvDmTXw8XSjhCia8K6YrZnk7aCS54+/HxyMdZMruMO3nfpDsrlar/EQWe41r1qrwKiCa2QDYHDzWA==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.9.tgz",
"integrity": "sha512-d5G8Gf2RpH5pYwiHLPA+UpG3G9TLQu4WM+VK6sfL7K68AmhcEQ9r+nkj/DvR/GYhYox6twsHUtmWWWIKfcfQQA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
@@ -907,14 +905,13 @@
}
},
"node_modules/@biomejs/cli-darwin-x64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.7.tgz",
"integrity": "sha512-I+cOG3sd/7HdFtvDSnF9QQPrWguUH7zrkIMMykM3PtfWU9soTcS2yRb9Myq6MHmzbeCT08D1UmY+BaiMl5CcoQ==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.9.tgz",
"integrity": "sha512-LNCLNgqDMG7BLdc3a8aY/dwKPK7+R8/JXJoXjCvZh2gx8KseqBdFDKbhrr7HCWF8SzNhbTaALhTBoh/I6rf9lA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
@@ -924,14 +921,13 @@
}
},
"node_modules/@biomejs/cli-linux-arm64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.7.tgz",
"integrity": "sha512-om6FugwmibzfP/6ALj5WRDVSND4H2G9X0nkI1HZpp2ySf9lW2j0X68oQSaHEnls6666oy4KDsc5RFjT4m0kV0w==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.9.tgz",
"integrity": "sha512-4adnkAUi6K4C/emPRgYznMOcLlUqZdXWM6aIui4VP4LraE764g6Q4YguygnAUoxKjKIXIWPteKMgRbN0wsgwcg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -941,14 +937,13 @@
}
},
"node_modules/@biomejs/cli-linux-arm64-musl": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.7.tgz",
"integrity": "sha512-I2NvM9KPb09jWml93O2/5WMfNR7Lee5Latag1JThDRMURVhPX74p9UDnyTw3Ae6cE1DgXfw7sqQgX7rkvpc0vw==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.9.tgz",
"integrity": "sha512-8RCww5xnPn2wpK4L/QDGDOW0dq80uVWfppPxHIUg6mOs9B6gRmqPp32h1Ls3T8GnW8Wo5A8u7vpTwz4fExN+sw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -958,14 +953,13 @@
}
},
"node_modules/@biomejs/cli-linux-x64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.7.tgz",
"integrity": "sha512-bV8/uo2Tj+gumnk4sUdkerWyCPRabaZdv88IpbmDWARQQoA/Q0YaqPz1a+LSEDIL7OfrnPi9Hq1Llz4ZIGyIQQ==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.9.tgz",
"integrity": "sha512-L10na7POF0Ks/cgLFNF1ZvIe+X4onLkTi5oP9hY+Rh60Q+7fWzKDDCeGyiHUFf1nGIa9dQOOUPGe2MyYg8nMSQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -975,14 +969,13 @@
}
},
"node_modules/@biomejs/cli-linux-x64-musl": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.7.tgz",
"integrity": "sha512-00kx4YrBMU8374zd2wHuRV5wseh0rom5HqRND+vDldJPrWwQw+mzd/d8byI9hPx926CG+vWzq6AeiT7Yi5y59g==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.9.tgz",
"integrity": "sha512-5TD+WS9v5vzXKzjetF0hgoaNFHMcpQeBUwKKVi3JbG1e9UCrFuUK3Gt185fyTzvRdwYkJJEMqglRPjmesmVv4A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -992,14 +985,13 @@
}
},
"node_modules/@biomejs/cli-win32-arm64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.7.tgz",
"integrity": "sha512-hOUHBMlFCvDhu3WCq6vaBoG0dp0LkWxSEnEEsxxXvOa9TfT6ZBnbh72A/xBM7CBYB7WgwqboetzFEVDnMxelyw==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.9.tgz",
"integrity": "sha512-aDZr0RBC3sMGJOU10BvG7eZIlWLK/i51HRIfScE2lVhfts2dQTreowLiJJd+UYg/tHKxS470IbzpuKmd0MiD6g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
@@ -1009,14 +1001,13 @@
}
},
"node_modules/@biomejs/cli-win32-x64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.7.tgz",
"integrity": "sha512-qEpGjSkPC3qX4ycbMUthXvi9CkRq7kZpkqMY1OyhmYlYLnANnooDQ7hDerM8+0NJ+DZKVnsIc07h30XOpt7LtQ==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.9.tgz",
"integrity": "sha512-NS4g/2G9SoQ4ktKtz31pvyc/rmgzlcIDCGU/zWbmHJAqx6gcRj2gj5Q/guXhoWTzCUaQZDIqiCQXHS7BcGYc0w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
@@ -6302,74 +6293,74 @@
"dev": true
},
"@biomejs/biome": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.7.tgz",
"integrity": "sha512-vXrgcmNGZ4lpdwZSpMf1hWw1aWS6B+SyeSYKTLrNsiUsAdSRN0J4d/7mF3ogJFbIwFFSOL3wT92Zzxia/d5/ng==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.9.tgz",
"integrity": "sha512-wvZW92FrwitTcacvCBT8xdAbfbxWfDLwjYMmU3djjqQTh7Ni4ZdiWIT/x5VcZ+RQuxiKzIOzi5D+dcyJDFZMsA==",
"dev": true,
"requires": {
"@biomejs/cli-darwin-arm64": "2.4.7",
"@biomejs/cli-darwin-x64": "2.4.7",
"@biomejs/cli-linux-arm64": "2.4.7",
"@biomejs/cli-linux-arm64-musl": "2.4.7",
"@biomejs/cli-linux-x64": "2.4.7",
"@biomejs/cli-linux-x64-musl": "2.4.7",
"@biomejs/cli-win32-arm64": "2.4.7",
"@biomejs/cli-win32-x64": "2.4.7"
"@biomejs/cli-darwin-arm64": "2.4.9",
"@biomejs/cli-darwin-x64": "2.4.9",
"@biomejs/cli-linux-arm64": "2.4.9",
"@biomejs/cli-linux-arm64-musl": "2.4.9",
"@biomejs/cli-linux-x64": "2.4.9",
"@biomejs/cli-linux-x64-musl": "2.4.9",
"@biomejs/cli-win32-arm64": "2.4.9",
"@biomejs/cli-win32-x64": "2.4.9"
}
},
"@biomejs/cli-darwin-arm64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.7.tgz",
"integrity": "sha512-Oo0cF5mHzmvDmTXw8XSjhCia8K6YrZnk7aCS54+/HxyMdZMruMO3nfpDsrlar/EQWe41r1qrwKiCa2QDYHDzWA==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.9.tgz",
"integrity": "sha512-d5G8Gf2RpH5pYwiHLPA+UpG3G9TLQu4WM+VK6sfL7K68AmhcEQ9r+nkj/DvR/GYhYox6twsHUtmWWWIKfcfQQA==",
"dev": true,
"optional": true
},
"@biomejs/cli-darwin-x64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.7.tgz",
"integrity": "sha512-I+cOG3sd/7HdFtvDSnF9QQPrWguUH7zrkIMMykM3PtfWU9soTcS2yRb9Myq6MHmzbeCT08D1UmY+BaiMl5CcoQ==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.9.tgz",
"integrity": "sha512-LNCLNgqDMG7BLdc3a8aY/dwKPK7+R8/JXJoXjCvZh2gx8KseqBdFDKbhrr7HCWF8SzNhbTaALhTBoh/I6rf9lA==",
"dev": true,
"optional": true
},
"@biomejs/cli-linux-arm64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.7.tgz",
"integrity": "sha512-om6FugwmibzfP/6ALj5WRDVSND4H2G9X0nkI1HZpp2ySf9lW2j0X68oQSaHEnls6666oy4KDsc5RFjT4m0kV0w==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.9.tgz",
"integrity": "sha512-4adnkAUi6K4C/emPRgYznMOcLlUqZdXWM6aIui4VP4LraE764g6Q4YguygnAUoxKjKIXIWPteKMgRbN0wsgwcg==",
"dev": true,
"optional": true
},
"@biomejs/cli-linux-arm64-musl": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.7.tgz",
"integrity": "sha512-I2NvM9KPb09jWml93O2/5WMfNR7Lee5Latag1JThDRMURVhPX74p9UDnyTw3Ae6cE1DgXfw7sqQgX7rkvpc0vw==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.9.tgz",
"integrity": "sha512-8RCww5xnPn2wpK4L/QDGDOW0dq80uVWfppPxHIUg6mOs9B6gRmqPp32h1Ls3T8GnW8Wo5A8u7vpTwz4fExN+sw==",
"dev": true,
"optional": true
},
"@biomejs/cli-linux-x64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.7.tgz",
"integrity": "sha512-bV8/uo2Tj+gumnk4sUdkerWyCPRabaZdv88IpbmDWARQQoA/Q0YaqPz1a+LSEDIL7OfrnPi9Hq1Llz4ZIGyIQQ==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.9.tgz",
"integrity": "sha512-L10na7POF0Ks/cgLFNF1ZvIe+X4onLkTi5oP9hY+Rh60Q+7fWzKDDCeGyiHUFf1nGIa9dQOOUPGe2MyYg8nMSQ==",
"dev": true,
"optional": true
},
"@biomejs/cli-linux-x64-musl": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.7.tgz",
"integrity": "sha512-00kx4YrBMU8374zd2wHuRV5wseh0rom5HqRND+vDldJPrWwQw+mzd/d8byI9hPx926CG+vWzq6AeiT7Yi5y59g==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.9.tgz",
"integrity": "sha512-5TD+WS9v5vzXKzjetF0hgoaNFHMcpQeBUwKKVi3JbG1e9UCrFuUK3Gt185fyTzvRdwYkJJEMqglRPjmesmVv4A==",
"dev": true,
"optional": true
},
"@biomejs/cli-win32-arm64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.7.tgz",
"integrity": "sha512-hOUHBMlFCvDhu3WCq6vaBoG0dp0LkWxSEnEEsxxXvOa9TfT6ZBnbh72A/xBM7CBYB7WgwqboetzFEVDnMxelyw==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.9.tgz",
"integrity": "sha512-aDZr0RBC3sMGJOU10BvG7eZIlWLK/i51HRIfScE2lVhfts2dQTreowLiJJd+UYg/tHKxS470IbzpuKmd0MiD6g==",
"dev": true,
"optional": true
},
"@biomejs/cli-win32-x64": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.7.tgz",
"integrity": "sha512-qEpGjSkPC3qX4ycbMUthXvi9CkRq7kZpkqMY1OyhmYlYLnANnooDQ7hDerM8+0NJ+DZKVnsIc07h30XOpt7LtQ==",
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.9.tgz",
"integrity": "sha512-NS4g/2G9SoQ4ktKtz31pvyc/rmgzlcIDCGU/zWbmHJAqx6gcRj2gj5Q/guXhoWTzCUaQZDIqiCQXHS7BcGYc0w==",
"dev": true,
"optional": true
},

View File

@@ -39,7 +39,7 @@
"undici": "^7.24.2"
},
"devDependencies": {
"@biomejs/biome": "^2.4.7",
"@biomejs/biome": "^2.4.9",
"@types/js-yaml": "^4.0.9",
"@types/node": "^25.5.0",
"@types/semver": "^7.7.1",

View File

@@ -1,15 +1,7 @@
import * as cache from "@actions/cache";
import * as core from "@actions/core";
import { hashFiles } from "../hash/hash-files";
import {
cacheDependencyGlob,
cacheLocalPath,
cachePython,
cacheSuffix,
pruneCache,
pythonDir,
restoreCache as shouldRestoreCache,
} from "../utils/inputs";
import type { SetupInputs } from "../utils/inputs";
import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms";
export const STATE_CACHE_KEY = "cache-key";
@@ -18,18 +10,21 @@ export const STATE_PYTHON_CACHE_MATCHED_KEY = "python-cache-matched-key";
const CACHE_VERSION = "2";
export async function restoreCache(pythonVersion?: string): Promise<void> {
const cacheKey = await computeKeys(pythonVersion);
export async function restoreCache(
inputs: SetupInputs,
pythonVersion?: string,
): Promise<void> {
const cacheKey = await computeKeys(inputs, pythonVersion);
core.saveState(STATE_CACHE_KEY, cacheKey);
core.setOutput("cache-key", cacheKey);
if (!shouldRestoreCache) {
if (!inputs.restoreCache) {
core.info("restore-cache is false. Skipping restore cache step.");
core.setOutput("python-cache-hit", false);
return;
}
if (cacheLocalPath === undefined) {
if (inputs.cacheLocalPath === undefined) {
throw new Error(
"cache-local-path is not set. Cannot restore cache without a valid cache path.",
);
@@ -37,15 +32,15 @@ export async function restoreCache(pythonVersion?: string): Promise<void> {
await restoreCacheFromKey(
cacheKey,
cacheLocalPath.path,
inputs.cacheLocalPath.path,
STATE_CACHE_MATCHED_KEY,
"cache-hit",
);
if (cachePython) {
if (inputs.cachePython) {
await restoreCacheFromKey(
`${cacheKey}-python`,
pythonDir,
inputs.pythonDir,
STATE_PYTHON_CACHE_MATCHED_KEY,
"python-cache-hit",
);
@@ -76,28 +71,34 @@ async function restoreCacheFromKey(
handleMatchResult(matchedKey, cacheKey, stateKey, outputKey);
}
async function computeKeys(pythonVersion?: string): Promise<string> {
async function computeKeys(
inputs: SetupInputs,
pythonVersion?: string,
): Promise<string> {
let cacheDependencyPathHash = "-";
if (cacheDependencyGlob !== "") {
if (inputs.cacheDependencyGlob !== "") {
core.info(
`Searching files using cache dependency glob: ${cacheDependencyGlob.split("\n").join(",")}`,
`Searching files using cache dependency glob: ${inputs.cacheDependencyGlob.split("\n").join(",")}`,
);
cacheDependencyPathHash += await hashFiles(
inputs.cacheDependencyGlob,
true,
);
cacheDependencyPathHash += await hashFiles(cacheDependencyGlob, true);
if (cacheDependencyPathHash === "-") {
core.warning(
`No file matched to [${cacheDependencyGlob.split("\n").join(",")}]. The cache will never get invalidated. Make sure you have checked out the target repository and configured the cache-dependency-glob input correctly.`,
`No file matched to [${inputs.cacheDependencyGlob.split("\n").join(",")}]. The cache will never get invalidated. Make sure you have checked out the target repository and configured the cache-dependency-glob input correctly.`,
);
}
}
if (cacheDependencyPathHash === "-") {
cacheDependencyPathHash = "-no-dependency-glob";
}
const suffix = cacheSuffix ? `-${cacheSuffix}` : "";
const suffix = inputs.cacheSuffix ? `-${inputs.cacheSuffix}` : "";
const version = pythonVersion ?? "unknown";
const platform = await getPlatform();
const osNameVersion = getOSNameVersion();
const pruned = pruneCache ? "-pruned" : "";
const python = cachePython ? "-py" : "";
const pruned = inputs.pruneCache ? "-pruned" : "";
const python = inputs.cachePython ? "-py" : "";
return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${osNameVersion}-${version}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
}

View File

@@ -1,5 +1,215 @@
// AUTOGENERATED_DO_NOT_EDIT
export const KNOWN_CHECKSUMS: { [key: string]: string } = {
"aarch64-apple-darwin-0.11.3":
"2bc3d0c7bf2bd08325b1e170abac6f7e5b3346e1d4eab3370d17cefec934996f",
"aarch64-pc-windows-msvc-0.11.3":
"e99c56f9ab5e1e1ddcaea3e2389990c94baf38e0d7cb2148de08baf2d3261d49",
"aarch64-unknown-linux-gnu-0.11.3":
"711382e3158433f06b11d99afb440f4416359fc3c84558886d8ed8826a921bff",
"aarch64-unknown-linux-musl-0.11.3":
"8ecec82cb9a744d5fabff6d16d7777218a7730f699d2aa0d2f751c17858e2efa",
"arm-unknown-linux-musleabihf-0.11.3":
"3d021046a94ad11f12b9d83f36442a1a28e92e7149c3f79ba2951c96653dafac",
"armv7-unknown-linux-gnueabihf-0.11.3":
"13c9a0f5f624275ccd36db2896607f4fee3585f420734b16f6c66d70e32aa458",
"armv7-unknown-linux-musleabihf-0.11.3":
"260a88e2f00daab0363a745fde036a7881002d7a81094388f31925acb284110b",
"i686-pc-windows-msvc-0.11.3":
"036fa39fa5ea3cb86c127324924b913b5858e8d91c4cb413edacfc3123001696",
"i686-unknown-linux-gnu-0.11.3":
"b9410c8dae2fa0d4939af5b0ee7272d5591bd55890e8274dcf7f1aea84bfe043",
"i686-unknown-linux-musl-0.11.3":
"afe533fd409105e753d844490c65a4375e75bfb3812e49122684f996bed9e90a",
"powerpc64le-unknown-linux-gnu-0.11.3":
"5cdcadf4d50a5354312bc8ef37c2a6cfab4e2f13ccdf8380d3012b927b4ded95",
"riscv64gc-unknown-linux-gnu-0.11.3":
"8271e07ed9695870f4b0ae5ec722e3ae08fff280068f08bc6a8ca76c67d7fefa",
"riscv64gc-unknown-linux-musl-0.11.3":
"b750fc8393ced9939448849b05e94de6bf1e998bb7030c4ebe744b47b372bce9",
"s390x-unknown-linux-gnu-0.11.3":
"6dc4f555a5f6515f7fddb281422d2a8a3943853dae5de837bbb5d996d7576c71",
"x86_64-apple-darwin-0.11.3":
"b0e05e0b43a000fdc2132ee3f3400ba5dee427bc2337d3ec4eb8cf4f3d5722af",
"x86_64-pc-windows-msvc-0.11.3":
"ae681c0aaec7cc96af184648cb88d73f8393ed60fa5880abdd6bdb910f9b227c",
"x86_64-unknown-linux-gnu-0.11.3":
"c0f3236f146e55472663cfbcc9be3042a9f1092275bbe3fe2a56a6cbfd3da5ce",
"x86_64-unknown-linux-musl-0.11.3":
"8b40cf16b849634b81a530a3d0a0bcae5f24996ef9ae782976fd69b6266d3b8e",
"aarch64-apple-darwin-0.11.2":
"4beaa9550f93ef7f0fc02f7c28c9c48cd61fe30db00f5ac8947e0a425c3fb282",
"aarch64-pc-windows-msvc-0.11.2":
"ffdded8338205f53727b51d404563a5ac8eaa9aea53279a7b7c42177e11d478c",
"aarch64-unknown-linux-gnu-0.11.2":
"04792cac761c4a6ba78267f36f2af541b7f92196d42ac55d21d3ff6b0f5ab6a5",
"aarch64-unknown-linux-musl-0.11.2":
"275d91dd1f1955136591e7ec5e1fa21e84d0d37ead7da7c35c3683df748d9855",
"arm-unknown-linux-musleabihf-0.11.2":
"ce572dac1a8f9a92960f89e99351352fae068d34b24bed86fb88e75fd5dd67d9",
"armv7-unknown-linux-gnueabihf-0.11.2":
"3e90d7de9e3a4e2d8d1bd9ce164362fce22248474986e712039479fb6fd73136",
"armv7-unknown-linux-musleabihf-0.11.2":
"5222cdd7c7dd3263f8c243831606a9f01a1a07a40ffc3c26c03afb34491075c2",
"i686-pc-windows-msvc-0.11.2":
"506f8274b253b2386881a121f3b7d915b637019bda15876bbd1357235305cf12",
"i686-unknown-linux-gnu-0.11.2":
"c7ec378bab887443a70786382e58d76489da14a7e33b155915d648cca4bdb46c",
"i686-unknown-linux-musl-0.11.2":
"ade8714be45457899568c5b03ef885a0cc94476c07a0bdbe34531ba84231bab2",
"powerpc64le-unknown-linux-gnu-0.11.2":
"3f3a50e99364efc8ff7add10e79757a2b8458700a38180ec5f313524481b9fbc",
"riscv64gc-unknown-linux-gnu-0.11.2":
"e56a93f0ff21d6908461a6ecbf465beae19ae22719f900284abb7680bd07ec41",
"riscv64gc-unknown-linux-musl-0.11.2":
"4f263571bb457a16a31cb38fba4fcc9cf1059d1d32c5b2e54c43175fcd59205d",
"s390x-unknown-linux-gnu-0.11.2":
"42ebe40775f2a77a514fa47399fde86473bf35bd33b6896c6410a0309fc4d205",
"x86_64-apple-darwin-0.11.2":
"a9c3653245031304c50dd60ac0301bf6c112e12c38c32302a71d4fa6a63ba2cb",
"x86_64-pc-windows-msvc-0.11.2":
"171b7ccda1bbd562da6babeffcf533a1c6cc7862cf998da826e1db534fc43e48",
"x86_64-unknown-linux-gnu-0.11.2":
"7ac2ca0449c8d68dae9b99e635cd3bc9b22a4cb1de64b7c43716398447d42981",
"x86_64-unknown-linux-musl-0.11.2":
"4700d9fc75734247587deb3e25dd2c6c24f4ac69e8fe91d6acad4a6013115c06",
"aarch64-apple-darwin-0.11.1":
"f7815f739ed5d0e4202e6292acedb8659b9ae7de663d07188d8c6cbd7f96303f",
"aarch64-pc-windows-msvc-0.11.1":
"b789db0c1504dd3b02c090bd5783487497cc46cc2eb71754874cdd1ef59eb52a",
"aarch64-unknown-linux-gnu-0.11.1":
"1340e62da1ee3c1109764340e1247e8a1a232c30dde4a0f0548976dcaa90f06d",
"aarch64-unknown-linux-musl-0.11.1":
"bd04ffce77ee8d77f39823c13606183581847c2f5dcd704f2ea0f15e376b1a27",
"arm-unknown-linux-musleabihf-0.11.1":
"625c0e756e2374fce864ceaa6beedd5821e276e2b6307f2b719f2d62b449b89c",
"armv7-unknown-linux-gnueabihf-0.11.1":
"baf8daaab20b0502d1853dbfd916afb0762c024ae7f0df1c2deb2a1a1c1c3467",
"armv7-unknown-linux-musleabihf-0.11.1":
"684c25b74e83bcb1b177152379cfe2c974ba731aa5af278e1d161e41709f8bcf",
"i686-pc-windows-msvc-0.11.1":
"3c07858a08c54e4e5753239354c7b07ae69071b2b6f5aa2cc970e612adcb4740",
"i686-unknown-linux-gnu-0.11.1":
"6e83167c05708570563b10b6cc7e8c289daef5f51fde0b152e41af2a7ef70813",
"i686-unknown-linux-musl-0.11.1":
"b0d5152635c257fec76f95cb9268112b47ff70bd33a23866295a4f2ed9f46b7f",
"powerpc64le-unknown-linux-gnu-0.11.1":
"e42d2abfac46f57564789e2bfa6dbea4ae3135892e36ae066ba0ae77b69bb676",
"riscv64gc-unknown-linux-gnu-0.11.1":
"5e2c757b35dab015ad37f74ee3e060208390b5f4defb6684876f1be0664f3f6e",
"riscv64gc-unknown-linux-musl-0.11.1":
"6f590a824aed363cbec4079f7ddab87b5685119e0f5f0e71cd114c7b7c326199",
"s390x-unknown-linux-gnu-0.11.1":
"4208173c74e29572b799178709b5ed5828b24888659f944a4b47c0aaf78b42d2",
"x86_64-apple-darwin-0.11.1":
"2103670e8e949605e51926c7b953923ff6f6befbfb55aee928f5e760c9c910f8",
"x86_64-pc-windows-msvc-0.11.1":
"6659250cebbd3bb6ee48bcb21a3f0c6656450d63fb97f0f069bcb532bdb688ed",
"x86_64-unknown-linux-gnu-0.11.1":
"7c0c8069053e6e99e5911ff32b916be571f3419cd8e11bd28fb7da2c7dcaa553",
"x86_64-unknown-linux-musl-0.11.1":
"4e949471a95b37088a1ff1a585f69abed4d3cd3f921f50709a46b6ba62986d38",
"aarch64-apple-darwin-0.11.0":
"0c0f32c6a3473c5928aff96c3233715edfc79290e892f255cac93710cde7b91a",
"aarch64-pc-windows-msvc-0.11.0":
"95419e04a3ef5f13fb2a06bd6d787ba80a9d8981d6f097780e5a979817a2879d",
"aarch64-unknown-linux-gnu-0.11.0":
"8e179ca110343a17f801444ff9ef117dba56ef5fc9f6a4c9bb77b318ddba5f24",
"aarch64-unknown-linux-musl-0.11.0":
"658be4b8ec905635f1295468d4d5120d9e1ab1722eec9a104473ce993590babe",
"arm-unknown-linux-musleabihf-0.11.0":
"bfdcbd5fa41c8a9877a72c2b55a95da2bc79933885ef56c699b65bb2ed9cea91",
"armv7-unknown-linux-gnueabihf-0.11.0":
"0cad4e1b6769e48aa1e80cf639ddcc7c1bfe9ed017e95868fed185a8d818c949",
"armv7-unknown-linux-musleabihf-0.11.0":
"2aa9da83c6c0cf8a06bc9df14d51056284fa067ef5390b4db79998ff12f3bee7",
"i686-pc-windows-msvc-0.11.0":
"3b09d70e686087e096dbd8a2af21b922a2cac7d613dc053c3281c3ddbb961961",
"i686-unknown-linux-gnu-0.11.0":
"59928a0267501c20d9f9942f5f1d81a991ec55e29a19e002ae3d5c178c674c89",
"i686-unknown-linux-musl-0.11.0":
"1f438d6f6f851f0dabad3307ce7fd46541ecc5c42ebb664f382eb6c9a424a67d",
"powerpc64le-unknown-linux-gnu-0.11.0":
"29f17fb43595492b1a36cda57df7adad74183132df32799d32897268ff4e26dd",
"riscv64gc-unknown-linux-gnu-0.11.0":
"84ef37dda1003c5b65fa6c8f84242d35a7fcc84cc5ea9490d702edc36cad1f67",
"s390x-unknown-linux-gnu-0.11.0":
"b25be62f3b642348a2fece5c658624586661b8d1103891ab6903768b0529edc4",
"x86_64-apple-darwin-0.11.0":
"31aaec764166af8885cf99321fd6ed24fef80225a6f26ed1ae8ce04111688a7e",
"x86_64-pc-windows-msvc-0.11.0":
"e21d00b172df83531564a95e75a2bdc0c59b471dbb3515f0c1b4d6ef657dc451",
"x86_64-unknown-linux-gnu-0.11.0":
"cc0fbb42b3642125f600a55b0b095bea65cddaadb94c6ea2b6ba5d79c5825089",
"x86_64-unknown-linux-musl-0.11.0":
"bf6b0757c73d1726faa2a819b155d4d864919a95766720215d78fdcd09d42d26",
"aarch64-apple-darwin-0.10.12":
"ae738b5661a900579ec621d3918c0ef17bdec0da2a8a6d8b161137cd15f25414",
"aarch64-pc-windows-msvc-0.10.12":
"e79881e2c4f98a0f3a37b8770bf224e8fee70f6dcf8fc17055d8291bb1b0b867",
"aarch64-unknown-linux-gnu-0.10.12":
"0ed7d20f49f6b9b60d45fdfcac28f3ac01a671a6ef08672401ed2833423fea2a",
"aarch64-unknown-linux-musl-0.10.12":
"55bd1c1c10ec8b95a8c184f5e18b566703c6ab105f0fc118aaa4d748aabf28e4",
"arm-unknown-linux-musleabihf-0.10.12":
"9714e5059b05110a1c7ddbc18c971c13e0260e10551b7b77d82cbf907a4ebd9b",
"armv7-unknown-linux-gnueabihf-0.10.12":
"eaa02f36d5112029601b18ac3d1a0c03a83bb20cb4154c2f5345f777fa6c4101",
"armv7-unknown-linux-musleabihf-0.10.12":
"bd735652298c6e62cdd2ac939babe176a3356613e6803baa33d0bc10e8d9e4ed",
"i686-pc-windows-msvc-0.10.12":
"2312e75b9c77befdc1bff30da18f16df03083452852952553bee91da362c1a1d",
"i686-unknown-linux-gnu-0.10.12":
"8501844b34e3a28cfbba5a4b857eebd696d952e0bb4160357451ad80f3f49db8",
"i686-unknown-linux-musl-0.10.12":
"56cad78abcf5b710d2f7b9f774fcfd6bbed340d2aa9d9fc9e3b515542ec5e953",
"powerpc64le-unknown-linux-gnu-0.10.12":
"3c8017d9112221c83f43e8a15a58099663c0b2bdeabc8b43bb800413dfa21218",
"riscv64gc-unknown-linux-gnu-0.10.12":
"b1ca482b6b5dd7bf6ab733a3695cb0ab5b8e992ca96527efae93aa78fcc52a9b",
"s390x-unknown-linux-gnu-0.10.12":
"e1a0345eefe6fd3300948cd6f18aab092f9b88a243782113e645ce96530a6693",
"x86_64-apple-darwin-0.10.12":
"17443e293f2ae407bb2d8d34b875ebfe0ae01cf1296de5647e69e7b2e2b428f0",
"x86_64-pc-windows-msvc-0.10.12":
"4c1d55501869b3330d4aabf45ad6024ce2367e0f3af83344395702d272c22e88",
"x86_64-unknown-linux-gnu-0.10.12":
"ec72570c9d1f33021aa80b176d7baba390de2cfeb1abcbefca346d563bf17484",
"x86_64-unknown-linux-musl-0.10.12":
"adccf40b5d1939a5e0093081ec2307ea24235adf7c2d96b122c561fa37711c46",
"aarch64-apple-darwin-0.10.11":
"437a7d498dd6564d5bf986074249ba1fc600e73da55ae04d7bd4c24d5f149b95",
"aarch64-pc-windows-msvc-0.10.11":
"6a3eec4105c775dd87c11ef8ec41564648273751ff807c8955c24ddbcc636d03",
"aarch64-unknown-linux-gnu-0.10.11":
"23003df007937dd607409c8ddf010baa82bad2673e60e254632ca5b04edcce13",
"aarch64-unknown-linux-musl-0.10.11":
"5d80a7f6343d2676dfde1e5126582070a2bbc62df6f60d5527a169be3788532a",
"arm-unknown-linux-musleabihf-0.10.11":
"d3c248497c450d22a39c1d43a4a358c0c852e6056f5f49be96495eea41afb96c",
"armv7-unknown-linux-gnueabihf-0.10.11":
"7895a6470dfba051af4e74253599482fc0b37141b5d229956b383365e1a22902",
"armv7-unknown-linux-musleabihf-0.10.11":
"d2880c08acfdaef0985488972c8b14969f7139c27545046e2f6202f0e0f4d9d8",
"i686-pc-windows-msvc-0.10.11":
"c17f3dc3b2c47490057f17a1f0c37270f11a7b7cedf9bf2c0f841ce02bc7001b",
"i686-unknown-linux-gnu-0.10.11":
"1ab69ff7dd104a902731758ee05b782dfd9bdb263384e61650de638f33f586df",
"i686-unknown-linux-musl-0.10.11":
"cffb80d303fc1655e259d0b769c489f452e97425a6b6d3393d766413783a1d8c",
"powerpc64le-unknown-linux-gnu-0.10.11":
"ddc6a20670e60219e947b1b04813be80d7e9f4c4a0234231c8ed9298eec04aa6",
"riscv64gc-unknown-linux-gnu-0.10.11":
"c0719473cf5f8b475e917b8dfef6ae5d876b86a00a82ef91e47a02f561399f4f",
"s390x-unknown-linux-gnu-0.10.11":
"305ee734c585918515a22fe43b7cf253c38d468771373a0c02364d67498e07b2",
"x86_64-apple-darwin-0.10.11":
"ff90020b554cf02ef8008535c9aab6ef27bb7be6b075359300dec79c361df897",
"x86_64-pc-windows-msvc-0.10.11":
"9ee74df98582f37fdd6069e1caac80d2616f9a489f5dbb2b1c152f30be69c58e",
"x86_64-unknown-linux-gnu-0.10.11":
"5a360b0de092ddf4131f5313d0411b48c4e95e8107e40c3f8f2e9fcb636b3583",
"x86_64-unknown-linux-musl-0.10.11":
"d78246139dc6cf3ed6d03c84da762686bced7ad1de67977ee372a45b95a1f6d0",
"aarch64-apple-darwin-0.10.10":
"8a09f0ef51ee7f7170731b4cb8bde5bf9ba6da5304f49a7df6cdab42a1f37b5d",
"aarch64-pc-windows-msvc-0.10.10":

View File

@@ -8,20 +8,11 @@ import {
ASTRAL_MIRROR_PREFIX,
GITHUB_RELEASES_PREFIX,
TOOL_CACHE_NAME,
VERSIONS_NDJSON_URL,
VERSIONS_MANIFEST_URL,
} from "../utils/constants";
import type { Architecture, Platform } from "../utils/platforms";
import { validateChecksum } from "./checksum/checksum";
import {
getAllVersions as getAllManifestVersions,
getLatestKnownVersion as getLatestVersionInManifest,
getManifestArtifact,
} from "./version-manifest";
import {
getAllVersions as getAllVersionsFromNdjson,
getArtifact as getArtifactFromNdjson,
getLatestVersion as getLatestVersionFromNdjson,
} from "./versions-client";
import { getAllVersions, getArtifact, getLatestVersion } from "./manifest";
export function tryGetFromToolCache(
arch: Architecture,
@@ -38,36 +29,42 @@ export function tryGetFromToolCache(
return { installedPath, version: resolvedVersion };
}
export async function downloadVersionFromNdjson(
export async function downloadVersion(
platform: Platform,
arch: Architecture,
version: string,
checkSum: string | undefined,
checksum: string | undefined,
githubToken: string,
manifestUrl?: string,
): Promise<{ version: string; cachedToolDir: string }> {
const artifact = await getArtifactFromNdjson(version, arch, platform);
const artifact = await getArtifact(version, arch, platform, manifestUrl);
if (!artifact) {
throw new Error(
`Could not find artifact for version ${version}, arch ${arch}, platform ${platform} in ${VERSIONS_NDJSON_URL} .`,
getMissingArtifactMessage(version, arch, platform, manifestUrl),
);
}
const mirrorUrl = rewriteToMirror(artifact.url);
const downloadUrl = mirrorUrl ?? artifact.url;
// For the default astral-sh/versions source, checksum validation relies on
// user input or the built-in KNOWN_CHECKSUMS table, not manifest sha256 values.
const resolvedChecksum =
manifestUrl === undefined
? checksum
: resolveChecksum(checksum, artifact.checksum);
const mirrorUrl = rewriteToMirror(artifact.downloadUrl);
const downloadUrl = mirrorUrl ?? artifact.downloadUrl;
// Don't send the GitHub token to the Astral mirror.
const downloadToken = mirrorUrl !== undefined ? undefined : githubToken;
// For the default astral-sh/versions source, checksum validation relies on
// user input or the built-in KNOWN_CHECKSUMS table, not NDJSON sha256 values.
try {
return await downloadVersion(
return await downloadArtifact(
downloadUrl,
`uv-${arch}-${platform}`,
platform,
arch,
version,
checkSum,
resolvedChecksum,
downloadToken,
);
} catch (err) {
@@ -79,13 +76,13 @@ export async function downloadVersionFromNdjson(
`Failed to download from mirror, falling back to GitHub Releases: ${(err as Error).message}`,
);
return await downloadVersion(
artifact.url,
return await downloadArtifact(
artifact.downloadUrl,
`uv-${arch}-${platform}`,
platform,
arch,
version,
checkSum,
resolvedChecksum,
githubToken,
);
}
@@ -99,41 +96,11 @@ export function rewriteToMirror(url: string): string | undefined {
if (!url.startsWith(GITHUB_RELEASES_PREFIX)) {
return undefined;
}
return ASTRAL_MIRROR_PREFIX + url.slice(GITHUB_RELEASES_PREFIX.length);
}
export async function downloadVersionFromManifest(
manifestUrl: string,
platform: Platform,
arch: Architecture,
version: string,
checkSum: string | undefined,
githubToken: string,
): Promise<{ version: string; cachedToolDir: string }> {
const artifact = await getManifestArtifact(
manifestUrl,
version,
arch,
platform,
);
if (!artifact) {
throw new Error(
`manifest-file does not contain version ${version}, arch ${arch}, platform ${platform}.`,
);
}
return await downloadVersion(
artifact.downloadUrl,
`uv-${arch}-${platform}`,
platform,
arch,
version,
resolveChecksum(checkSum, artifact.checksum),
githubToken,
);
}
async function downloadVersion(
async function downloadArtifact(
downloadUrl: string,
artifactName: string,
platform: Platform,
@@ -177,15 +144,28 @@ async function downloadVersion(
version,
arch,
);
return { cachedToolDir, version: version };
return { cachedToolDir, version };
}
function getMissingArtifactMessage(
version: string,
arch: Architecture,
platform: Platform,
manifestUrl?: string,
): string {
if (manifestUrl === undefined) {
return `Could not find artifact for version ${version}, arch ${arch}, platform ${platform} in ${VERSIONS_MANIFEST_URL} .`;
}
return `manifest-file does not contain version ${version}, arch ${arch}, platform ${platform}.`;
}
function resolveChecksum(
checkSum: string | undefined,
manifestChecksum?: string,
): string | undefined {
return checkSum !== undefined && checkSum !== ""
? checkSum
checksum: string | undefined,
manifestChecksum: string,
): string {
return checksum !== undefined && checksum !== ""
? checksum
: manifestChecksum;
}
@@ -199,31 +179,27 @@ export async function resolveVersion(
resolutionStrategy: "highest" | "lowest" = "highest",
): Promise<string> {
core.debug(`Resolving version: ${versionInput}`);
let version: string;
const isSimpleMinimumVersionSpecifier =
versionInput.includes(">") && !versionInput.includes(",");
const resolveVersionSpecifierToLatest =
isSimpleMinimumVersionSpecifier && resolutionStrategy === "highest";
if (resolveVersionSpecifierToLatest) {
core.info("Found minimum version specifier, using latest version");
}
if (manifestUrl !== undefined) {
version =
versionInput === "latest" || resolveVersionSpecifierToLatest
? await getLatestVersionInManifest(manifestUrl)
: versionInput;
} else {
version =
versionInput === "latest" || resolveVersionSpecifierToLatest
? await getLatestVersionFromNdjson()
: versionInput;
}
const version =
versionInput === "latest" || resolveVersionSpecifierToLatest
? await getLatestVersion(manifestUrl)
: versionInput;
if (tc.isExplicitVersion(version)) {
core.debug(`Version ${version} is an explicit version.`);
if (resolveVersionSpecifierToLatest) {
if (!pep440.satisfies(version, versionInput)) {
throw new Error(`No version found for ${versionInput}`);
}
if (
resolveVersionSpecifierToLatest &&
!pep440.satisfies(version, versionInput)
) {
throw new Error(`No version found for ${versionInput}`);
}
return version;
}
@@ -249,11 +225,11 @@ async function getAvailableVersions(
core.info(
`Getting available versions from manifest-file ${manifestUrl} ...`,
);
return await getAllManifestVersions(manifestUrl);
} else {
core.info(`Getting available versions from ${VERSIONS_MANIFEST_URL} ...`);
}
core.info(`Getting available versions from ${VERSIONS_NDJSON_URL} ...`);
return await getAllVersionsFromNdjson();
return await getAllVersions(manifestUrl);
}
function maxSatisfying(

View File

@@ -1,80 +0,0 @@
import * as core from "@actions/core";
export interface ManifestEntry {
arch: string;
platform: string;
version: string;
downloadUrl: string;
checksum?: string;
variant?: string;
archiveFormat?: string;
}
interface LegacyManifestEntry {
arch: string;
platform: string;
version: string;
downloadUrl: string;
checksum?: string;
}
const warnedLegacyManifestUrls = new Set<string>();
export function parseLegacyManifestEntries(
parsedEntries: unknown[],
manifestUrl: string,
): ManifestEntry[] {
warnAboutLegacyManifestFormat(manifestUrl);
return parsedEntries.map((entry, index) => {
if (!isLegacyManifestEntry(entry)) {
throw new Error(
`Invalid legacy manifest-file entry at index ${index} in ${manifestUrl}.`,
);
}
return {
arch: entry.arch,
checksum: entry.checksum,
downloadUrl: entry.downloadUrl,
platform: entry.platform,
version: entry.version,
};
});
}
export function clearLegacyManifestWarnings(): void {
warnedLegacyManifestUrls.clear();
}
function warnAboutLegacyManifestFormat(manifestUrl: string): void {
if (warnedLegacyManifestUrls.has(manifestUrl)) {
return;
}
warnedLegacyManifestUrls.add(manifestUrl);
core.warning(
`manifest-file ${manifestUrl} uses the legacy JSON array format, which is deprecated. Please migrate to the astral-sh/versions NDJSON format before the next major release.`,
);
}
function isLegacyManifestEntry(value: unknown): value is LegacyManifestEntry {
if (!isRecord(value)) {
return false;
}
const checksumIsValid =
typeof value.checksum === "string" || value.checksum === undefined;
return (
typeof value.arch === "string" &&
checksumIsValid &&
typeof value.downloadUrl === "string" &&
typeof value.platform === "string" &&
typeof value.version === "string"
);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}

199
src/download/manifest.ts Normal file
View File

@@ -0,0 +1,199 @@
import * as core from "@actions/core";
import { VERSIONS_MANIFEST_URL } from "../utils/constants";
import { fetch } from "../utils/fetch";
import { selectDefaultVariant } from "./variant-selection";
export interface ManifestArtifact {
platform: string;
variant?: string;
url: string;
archive_format: string;
sha256: string;
}
export interface ManifestVersion {
version: string;
artifacts: ManifestArtifact[];
}
export interface ArtifactResult {
archiveFormat: string;
checksum: string;
downloadUrl: string;
}
const cachedManifestData = new Map<string, ManifestVersion[]>();
export async function fetchManifest(
manifestUrl: string = VERSIONS_MANIFEST_URL,
): Promise<ManifestVersion[]> {
const cachedVersions = cachedManifestData.get(manifestUrl);
if (cachedVersions !== undefined) {
core.debug(`Using cached manifest data from ${manifestUrl}`);
return cachedVersions;
}
core.info(`Fetching manifest data from ${manifestUrl} ...`);
const response = await fetch(manifestUrl, {});
if (!response.ok) {
throw new Error(
`Failed to fetch manifest data: ${response.status} ${response.statusText}`,
);
}
const body = await response.text();
const versions = parseManifest(body, manifestUrl);
cachedManifestData.set(manifestUrl, versions);
return versions;
}
export function parseManifest(
data: string,
sourceDescription: string,
): ManifestVersion[] {
const trimmed = data.trim();
if (trimmed === "") {
throw new Error(`Manifest at ${sourceDescription} is empty.`);
}
if (trimmed.startsWith("[")) {
throw new Error(
`Legacy JSON array manifests are no longer supported in ${sourceDescription}. Use the astral-sh/versions manifest format instead.`,
);
}
const versions: ManifestVersion[] = [];
for (const [index, line] of data.split("\n").entries()) {
const record = line.trim();
if (record === "") {
continue;
}
let parsed: unknown;
try {
parsed = JSON.parse(record);
} catch (error) {
throw new Error(
`Failed to parse manifest data from ${sourceDescription} at line ${index + 1}: ${(error as Error).message}`,
);
}
if (!isManifestVersion(parsed)) {
throw new Error(
`Invalid manifest record in ${sourceDescription} at line ${index + 1}.`,
);
}
versions.push(parsed);
}
if (versions.length === 0) {
throw new Error(`No manifest data found in ${sourceDescription}.`);
}
return versions;
}
export async function getLatestVersion(
manifestUrl: string = VERSIONS_MANIFEST_URL,
): Promise<string> {
const latestVersion = (await fetchManifest(manifestUrl))[0]?.version;
if (latestVersion === undefined) {
throw new Error("No versions found in manifest data");
}
core.debug(`Latest version from manifest: ${latestVersion}`);
return latestVersion;
}
export async function getAllVersions(
manifestUrl: string = VERSIONS_MANIFEST_URL,
): Promise<string[]> {
const versions = await fetchManifest(manifestUrl);
return versions.map((versionData) => versionData.version);
}
export async function getArtifact(
version: string,
arch: string,
platform: string,
manifestUrl: string = VERSIONS_MANIFEST_URL,
): Promise<ArtifactResult | undefined> {
const versions = await fetchManifest(manifestUrl);
const versionData = versions.find(
(candidate) => candidate.version === version,
);
if (!versionData) {
core.debug(`Version ${version} not found in manifest ${manifestUrl}`);
return undefined;
}
const targetPlatform = `${arch}-${platform}`;
const matchingArtifacts = versionData.artifacts.filter(
(candidate) => candidate.platform === targetPlatform,
);
if (matchingArtifacts.length === 0) {
core.debug(
`Artifact for ${targetPlatform} not found in version ${version}. Available platforms: ${versionData.artifacts
.map((candidate) => candidate.platform)
.join(", ")}`,
);
return undefined;
}
const artifact = selectDefaultVariant(
matchingArtifacts,
`Multiple artifacts found for ${targetPlatform} in version ${version}`,
);
return {
archiveFormat: artifact.archive_format,
checksum: artifact.sha256,
downloadUrl: artifact.url,
};
}
export function clearManifestCache(manifestUrl?: string): void {
if (manifestUrl === undefined) {
cachedManifestData.clear();
return;
}
cachedManifestData.delete(manifestUrl);
}
function isManifestVersion(value: unknown): value is ManifestVersion {
if (!isRecord(value)) {
return false;
}
if (typeof value.version !== "string" || !Array.isArray(value.artifacts)) {
return false;
}
return value.artifacts.every(isManifestArtifact);
}
function isManifestArtifact(value: unknown): value is ManifestArtifact {
if (!isRecord(value)) {
return false;
}
const variantIsValid =
typeof value.variant === "string" || value.variant === undefined;
return (
typeof value.archive_format === "string" &&
typeof value.platform === "string" &&
typeof value.sha256 === "string" &&
typeof value.url === "string" &&
variantIsValid
);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}

View File

@@ -1,169 +0,0 @@
import * as core from "@actions/core";
import * as semver from "semver";
import { fetch } from "../utils/fetch";
import {
clearLegacyManifestWarnings,
type ManifestEntry,
parseLegacyManifestEntries,
} from "./legacy-version-manifest";
import { selectDefaultVariant } from "./variant-selection";
import { type NdjsonVersion, parseVersionData } from "./versions-client";
export interface ManifestArtifact {
downloadUrl: string;
checksum?: string;
archiveFormat?: string;
}
const cachedManifestEntries = new Map<string, ManifestEntry[]>();
export async function getLatestKnownVersion(
manifestUrl: string,
): Promise<string> {
const versions = await getAllVersions(manifestUrl);
const latestVersion = versions.reduce((latest, current) =>
semver.gt(current, latest) ? current : latest,
);
return latestVersion;
}
export async function getAllVersions(manifestUrl: string): Promise<string[]> {
const manifestEntries = await getManifestEntries(manifestUrl);
return [...new Set(manifestEntries.map((entry) => entry.version))];
}
export async function getManifestArtifact(
manifestUrl: string,
version: string,
arch: string,
platform: string,
): Promise<ManifestArtifact | undefined> {
const manifestEntries = await getManifestEntries(manifestUrl);
const entry = selectManifestEntry(
manifestEntries,
manifestUrl,
version,
arch,
platform,
);
if (!entry) {
return undefined;
}
return {
archiveFormat: entry.archiveFormat,
checksum: entry.checksum,
downloadUrl: entry.downloadUrl,
};
}
export function clearManifestCache(): void {
cachedManifestEntries.clear();
clearLegacyManifestWarnings();
}
async function getManifestEntries(
manifestUrl: string,
): Promise<ManifestEntry[]> {
const cachedEntries = cachedManifestEntries.get(manifestUrl);
if (cachedEntries !== undefined) {
core.debug(`Using cached manifest-file from: ${manifestUrl}`);
return cachedEntries;
}
core.info(`Fetching manifest-file from: ${manifestUrl}`);
const response = await fetch(manifestUrl, {});
if (!response.ok) {
throw new Error(
`Failed to fetch manifest-file: ${response.status} ${response.statusText}`,
);
}
const data = await response.text();
const parsedEntries = parseManifestEntries(data, manifestUrl);
cachedManifestEntries.set(manifestUrl, parsedEntries);
return parsedEntries;
}
function parseManifestEntries(
data: string,
manifestUrl: string,
): ManifestEntry[] {
const trimmed = data.trim();
if (trimmed === "") {
throw new Error(`manifest-file at ${manifestUrl} is empty.`);
}
const parsedAsJson = tryParseJson(trimmed);
if (Array.isArray(parsedAsJson)) {
return parseLegacyManifestEntries(parsedAsJson, manifestUrl);
}
const versions = parseVersionData(trimmed, manifestUrl);
return mapNdjsonVersionsToManifestEntries(versions, manifestUrl);
}
function mapNdjsonVersionsToManifestEntries(
versions: NdjsonVersion[],
manifestUrl: string,
): ManifestEntry[] {
const manifestEntries: ManifestEntry[] = [];
for (const versionData of versions) {
for (const artifact of versionData.artifacts) {
const [arch, ...platformParts] = artifact.platform.split("-");
if (arch === undefined || platformParts.length === 0) {
throw new Error(
`Invalid artifact platform '${artifact.platform}' in manifest-file ${manifestUrl}.`,
);
}
manifestEntries.push({
arch,
archiveFormat: artifact.archive_format,
checksum: artifact.sha256,
downloadUrl: artifact.url,
platform: platformParts.join("-"),
variant: artifact.variant,
version: versionData.version,
});
}
}
return manifestEntries;
}
function selectManifestEntry(
manifestEntries: ManifestEntry[],
manifestUrl: string,
version: string,
arch: string,
platform: string,
): ManifestEntry | undefined {
const matches = manifestEntries.filter(
(candidate) =>
candidate.version === version &&
candidate.arch === arch &&
candidate.platform === platform,
);
if (matches.length === 0) {
return undefined;
}
return selectDefaultVariant(
matches,
`manifest-file ${manifestUrl} contains multiple artifacts for version ${version}, arch ${arch}, platform ${platform}`,
);
}
function tryParseJson(value: string): unknown {
try {
return JSON.parse(value);
} catch {
return undefined;
}
}

View File

@@ -1,191 +0,0 @@
import * as core from "@actions/core";
import { VERSIONS_NDJSON_URL } from "../utils/constants";
import { fetch } from "../utils/fetch";
import { selectDefaultVariant } from "./variant-selection";
export interface NdjsonArtifact {
platform: string;
variant?: string;
url: string;
archive_format: string;
sha256: string;
}
export interface NdjsonVersion {
version: string;
artifacts: NdjsonArtifact[];
}
export interface ArtifactResult {
url: string;
sha256: string;
archiveFormat: string;
}
const cachedVersionData = new Map<string, NdjsonVersion[]>();
export async function fetchVersionData(
url: string = VERSIONS_NDJSON_URL,
): Promise<NdjsonVersion[]> {
const cachedVersions = cachedVersionData.get(url);
if (cachedVersions !== undefined) {
core.debug(`Using cached NDJSON version data from ${url}`);
return cachedVersions;
}
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);
return versions;
}
export function parseVersionData(
data: string,
sourceDescription: string,
): NdjsonVersion[] {
const versions: NdjsonVersion[] = [];
for (const [index, line] of data.split("\n").entries()) {
const trimmed = line.trim();
if (trimmed === "") {
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);
}
if (versions.length === 0) {
throw new Error(`No version data found in ${sourceDescription}.`);
}
return versions;
}
export async function getLatestVersion(): Promise<string> {
const versions = await fetchVersionData();
const latestVersion = versions[0]?.version;
if (!latestVersion) {
throw new Error("No versions found in NDJSON data");
}
core.debug(`Latest version from NDJSON: ${latestVersion}`);
return latestVersion;
}
export async function getAllVersions(): Promise<string[]> {
const versions = await fetchVersionData();
return versions.map((versionData) => versionData.version);
}
export async function getArtifact(
version: string,
arch: string,
platform: string,
): Promise<ArtifactResult | undefined> {
const versions = await fetchVersionData();
const versionData = versions.find(
(candidate) => candidate.version === version,
);
if (!versionData) {
core.debug(`Version ${version} not found in NDJSON data`);
return undefined;
}
const targetPlatform = `${arch}-${platform}`;
const matchingArtifacts = versionData.artifacts.filter(
(candidate) => candidate.platform === targetPlatform,
);
if (matchingArtifacts.length === 0) {
core.debug(
`Artifact for ${targetPlatform} not found in version ${version}. Available platforms: ${versionData.artifacts
.map((candidate) => candidate.platform)
.join(", ")}`,
);
return undefined;
}
const artifact = selectArtifact(matchingArtifacts, version, targetPlatform);
return {
archiveFormat: artifact.archive_format,
sha256: artifact.sha256,
url: artifact.url,
};
}
export function clearCache(url?: string): void {
if (url === undefined) {
cachedVersionData.clear();
return;
}
cachedVersionData.delete(url);
}
function selectArtifact(
artifacts: NdjsonArtifact[],
version: string,
targetPlatform: string,
): NdjsonArtifact {
return selectDefaultVariant(
artifacts,
`Multiple artifacts found for ${targetPlatform} in version ${version}`,
);
}
function isNdjsonVersion(value: unknown): value is NdjsonVersion {
if (!isRecord(value)) {
return false;
}
if (typeof value.version !== "string" || !Array.isArray(value.artifacts)) {
return false;
}
return value.artifacts.every(isNdjsonArtifact);
}
function isNdjsonArtifact(value: unknown): value is NdjsonArtifact {
if (!isRecord(value)) {
return false;
}
const variantIsValid =
typeof value.variant === "string" || value.variant === undefined;
return (
typeof value.archive_format === "string" &&
typeof value.platform === "string" &&
typeof value.sha256 === "string" &&
typeof value.url === "string" &&
variantIsValid
);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}

View File

@@ -9,21 +9,14 @@ import {
STATE_PYTHON_CACHE_MATCHED_KEY,
} from "./cache/restore-cache";
import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants";
import {
cacheLocalPath,
cachePython,
enableCache,
ignoreNothingToCache,
pythonDir,
pruneCache as shouldPruneCache,
saveCache as shouldSaveCache,
} from "./utils/inputs";
import { loadInputs, type SetupInputs } from "./utils/inputs";
export async function run(): Promise<void> {
try {
if (enableCache) {
if (shouldSaveCache) {
await saveCache();
const inputs = loadInputs();
if (inputs.enableCache) {
if (inputs.saveCache) {
await saveCache(inputs);
} else {
core.info("save-cache is false. Skipping save cache step.");
}
@@ -43,7 +36,7 @@ export async function run(): Promise<void> {
}
}
async function saveCache(): Promise<void> {
async function saveCache(inputs: SetupInputs): Promise<void> {
const cacheKey = core.getState(STATE_CACHE_KEY);
const matchedKey = core.getState(STATE_CACHE_MATCHED_KEY);
@@ -54,13 +47,13 @@ async function saveCache(): Promise<void> {
if (matchedKey === cacheKey) {
core.info(`Cache hit occurred on key ${cacheKey}, not saving cache.`);
} else {
if (shouldPruneCache) {
if (inputs.pruneCache) {
await pruneCache();
}
const actualCachePath = getUvCachePath();
const actualCachePath = getUvCachePath(inputs);
if (!fs.existsSync(actualCachePath)) {
if (ignoreNothingToCache) {
if (inputs.ignoreNothingToCache) {
core.info(
"No cacheable uv cache paths were found. Ignoring because ignore-nothing-to-cache is enabled.",
);
@@ -79,10 +72,10 @@ async function saveCache(): Promise<void> {
}
}
if (cachePython) {
if (!fs.existsSync(pythonDir)) {
if (inputs.cachePython) {
if (!fs.existsSync(inputs.pythonDir)) {
core.warning(
`Python cache path ${pythonDir} does not exist on disk. Skipping Python cache save because no managed Python installation was found. If you want uv to install managed Python instead of using a system interpreter, set UV_PYTHON_PREFERENCE=only-managed.`,
`Python cache path ${inputs.pythonDir} does not exist on disk. Skipping Python cache save because no managed Python installation was found. If you want uv to install managed Python instead of using a system interpreter, set UV_PYTHON_PREFERENCE=only-managed.`,
);
return;
}
@@ -90,7 +83,7 @@ async function saveCache(): Promise<void> {
const pythonCacheKey = `${cacheKey}-python`;
await saveCacheToKey(
pythonCacheKey,
pythonDir,
inputs.pythonDir,
STATE_PYTHON_CACHE_MATCHED_KEY,
"Python cache",
);
@@ -113,22 +106,22 @@ async function pruneCache(): Promise<void> {
await exec.exec(uvPath, execArgs, options);
}
function getUvCachePath(): string {
if (cacheLocalPath === undefined) {
function getUvCachePath(inputs: SetupInputs): string {
if (inputs.cacheLocalPath === undefined) {
throw new Error(
"cache-local-path is not set. Cannot save cache without a valid cache path.",
);
}
if (
process.env.UV_CACHE_DIR &&
process.env.UV_CACHE_DIR !== cacheLocalPath.path
process.env.UV_CACHE_DIR !== inputs.cacheLocalPath.path
) {
core.warning(
`The environment variable UV_CACHE_DIR has been changed to "${process.env.UV_CACHE_DIR}", by an action or step running after astral-sh/setup-uv. This can lead to unexpected behavior. If you expected this to happen set the cache-local-path input to "${process.env.UV_CACHE_DIR}" instead of "${cacheLocalPath.path}".`,
`The environment variable UV_CACHE_DIR has been changed to "${process.env.UV_CACHE_DIR}", by an action or step running after astral-sh/setup-uv. This can lead to unexpected behavior. If you expected this to happen set the cache-local-path input to "${process.env.UV_CACHE_DIR}" instead of "${inputs.cacheLocalPath.path}".`,
);
return process.env.UV_CACHE_DIR;
}
return cacheLocalPath.path;
return inputs.cacheLocalPath.path;
}
async function saveCacheToKey(

View File

@@ -4,32 +4,12 @@ import * as core from "@actions/core";
import * as exec from "@actions/exec";
import { restoreCache } from "./cache/restore-cache";
import {
downloadVersionFromManifest,
downloadVersionFromNdjson,
downloadVersion,
resolveVersion,
tryGetFromToolCache,
} from "./download/download-version";
import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants";
import {
activateEnvironment as activateEnvironmentInput,
addProblemMatchers,
CacheLocalSource,
cacheLocalPath,
checkSum,
enableCache,
githubToken,
ignoreEmptyWorkdir,
manifestFile,
pythonDir,
pythonVersion,
resolutionStrategy,
toolBinDir,
toolDir,
venvPath,
versionFile as versionFileInput,
version as versionInput,
workingDirectory,
} from "./utils/inputs";
import { CacheLocalSource, loadInputs, type SetupInputs } from "./utils/inputs";
import {
type Architecture,
getArch,
@@ -40,9 +20,9 @@ import { getUvVersionFromFile } from "./version/resolve";
const sourceDir = __dirname;
async function getPythonVersion(): Promise<string> {
if (pythonVersion !== "") {
return pythonVersion;
async function getPythonVersion(inputs: SetupInputs): Promise<string> {
if (inputs.pythonVersion !== "") {
return inputs.pythonVersion;
}
let output = "";
@@ -56,7 +36,7 @@ async function getPythonVersion(): Promise<string> {
};
try {
const execArgs = ["python", "find", "--directory", workingDirectory];
const execArgs = ["python", "find", "--directory", inputs.workingDirectory];
await exec.exec("uv", execArgs, options);
const pythonPath = output.trim();
@@ -72,37 +52,38 @@ async function getPythonVersion(): Promise<string> {
}
async function run(): Promise<void> {
detectEmptyWorkdir();
const platform = await getPlatform();
const arch = getArch();
try {
const inputs = loadInputs();
detectEmptyWorkdir(inputs);
const platform = await getPlatform();
const arch = getArch();
if (platform === undefined) {
throw new Error(`Unsupported platform: ${process.platform}`);
}
if (arch === undefined) {
throw new Error(`Unsupported architecture: ${process.arch}`);
}
const setupResult = await setupUv(platform, arch, checkSum, githubToken);
const setupResult = await setupUv(inputs, platform, arch);
addToolBinToPath();
addToolBinToPath(inputs);
addUvToPathAndOutput(setupResult.uvDir);
setToolDir();
addPythonDirToPath();
setupPython();
await activateEnvironment();
addMatchers();
setCacheDir();
setToolDir(inputs);
addPythonDirToPath(inputs);
setupPython(inputs);
await activateEnvironment(inputs);
addMatchers(inputs);
setCacheDir(inputs);
core.setOutput("uv-version", setupResult.version);
core.saveState(STATE_UV_VERSION, setupResult.version);
core.info(`Successfully installed uv version ${setupResult.version}`);
const pythonVersion = await getPythonVersion();
core.setOutput("python-version", pythonVersion);
const detectedPythonVersion = await getPythonVersion(inputs);
core.setOutput("python-version", detectedPythonVersion);
if (enableCache) {
await restoreCache(pythonVersion);
if (inputs.enableCache) {
await restoreCache(inputs, detectedPythonVersion);
}
// https://github.com/nodejs/node/issues/56645#issuecomment-3077594952
await new Promise((resolve) => setTimeout(resolve, 50));
@@ -112,9 +93,9 @@ async function run(): Promise<void> {
}
}
function detectEmptyWorkdir(): void {
if (fs.readdirSync(workingDirectory).length === 0) {
if (ignoreEmptyWorkdir) {
function detectEmptyWorkdir(inputs: SetupInputs): void {
if (fs.readdirSync(inputs.workingDirectory).length === 0) {
if (inputs.ignoreEmptyWorkdir) {
core.info(
"Empty workdir detected. Ignoring because ignore-empty-workdir is enabled",
);
@@ -127,12 +108,11 @@ function detectEmptyWorkdir(): void {
}
async function setupUv(
inputs: SetupInputs,
platform: Platform,
arch: Architecture,
checkSum: string | undefined,
githubToken: string,
): Promise<{ uvDir: string; version: string }> {
const resolvedVersion = await determineVersion(manifestFile);
const resolvedVersion = await determineVersion(inputs);
const toolCacheResult = tryGetFromToolCache(arch, resolvedVersion);
if (toolCacheResult.installedPath) {
core.info(`Found uv in tool-cache for ${toolCacheResult.version}`);
@@ -142,65 +122,58 @@ async function setupUv(
};
}
const downloadVersionResult =
manifestFile !== undefined
? await downloadVersionFromManifest(
manifestFile,
platform,
arch,
resolvedVersion,
checkSum,
githubToken,
)
: await downloadVersionFromNdjson(
platform,
arch,
resolvedVersion,
checkSum,
githubToken,
);
const downloadResult = await downloadVersion(
platform,
arch,
resolvedVersion,
inputs.checksum,
inputs.githubToken,
inputs.manifestFile,
);
return {
uvDir: downloadVersionResult.cachedToolDir,
version: downloadVersionResult.version,
uvDir: downloadResult.cachedToolDir,
version: downloadResult.version,
};
}
async function determineVersion(
manifestFile: string | undefined,
): Promise<string> {
if (versionInput !== "") {
return await resolveVersion(versionInput, manifestFile, resolutionStrategy);
async function determineVersion(inputs: SetupInputs): Promise<string> {
return await resolveVersion(
getRequestedVersion(inputs),
inputs.manifestFile,
inputs.resolutionStrategy,
);
}
function getRequestedVersion(inputs: SetupInputs): string {
if (inputs.version !== "") {
return inputs.version;
}
if (versionFileInput !== "") {
const versionFromFile = getUvVersionFromFile(versionFileInput);
if (inputs.versionFile !== "") {
const versionFromFile = getUvVersionFromFile(inputs.versionFile);
if (versionFromFile === undefined) {
throw new Error(
`Could not determine uv version from file: ${versionFileInput}`,
`Could not determine uv version from file: ${inputs.versionFile}`,
);
}
return await resolveVersion(
versionFromFile,
manifestFile,
resolutionStrategy,
);
return versionFromFile;
}
const versionFromUvToml = getUvVersionFromFile(
`${workingDirectory}${path.sep}uv.toml`,
`${inputs.workingDirectory}${path.sep}uv.toml`,
);
const versionFromPyproject = getUvVersionFromFile(
`${workingDirectory}${path.sep}pyproject.toml`,
`${inputs.workingDirectory}${path.sep}pyproject.toml`,
);
if (versionFromUvToml === undefined && versionFromPyproject === undefined) {
core.info(
"Could not determine uv version from uv.toml or pyproject.toml. Falling back to latest.",
);
}
return await resolveVersion(
versionFromUvToml || versionFromPyproject || "latest",
manifestFile,
resolutionStrategy,
);
return versionFromUvToml || versionFromPyproject || "latest";
}
function addUvToPathAndOutput(cachedPath: string): void {
@@ -215,15 +188,17 @@ function addUvToPathAndOutput(cachedPath: string): void {
}
}
function addToolBinToPath(): void {
if (toolBinDir !== undefined) {
core.exportVariable("UV_TOOL_BIN_DIR", toolBinDir);
core.info(`Set UV_TOOL_BIN_DIR to ${toolBinDir}`);
function addToolBinToPath(inputs: SetupInputs): void {
if (inputs.toolBinDir !== undefined) {
core.exportVariable("UV_TOOL_BIN_DIR", inputs.toolBinDir);
core.info(`Set UV_TOOL_BIN_DIR to ${inputs.toolBinDir}`);
if (process.env.UV_NO_MODIFY_PATH !== undefined) {
core.info(`UV_NO_MODIFY_PATH is set, not adding ${toolBinDir} to path`);
core.info(
`UV_NO_MODIFY_PATH is set, not adding ${inputs.toolBinDir} to path`,
);
} else {
core.addPath(toolBinDir);
core.info(`Added ${toolBinDir} to the path`);
core.addPath(inputs.toolBinDir);
core.info(`Added ${inputs.toolBinDir} to the path`);
}
} else {
if (process.env.UV_NO_MODIFY_PATH !== undefined) {
@@ -243,73 +218,73 @@ function addToolBinToPath(): void {
}
}
function setToolDir(): void {
if (toolDir !== undefined) {
core.exportVariable("UV_TOOL_DIR", toolDir);
core.info(`Set UV_TOOL_DIR to ${toolDir}`);
function setToolDir(inputs: SetupInputs): void {
if (inputs.toolDir !== undefined) {
core.exportVariable("UV_TOOL_DIR", inputs.toolDir);
core.info(`Set UV_TOOL_DIR to ${inputs.toolDir}`);
}
}
function addPythonDirToPath(): void {
core.exportVariable("UV_PYTHON_INSTALL_DIR", pythonDir);
core.info(`Set UV_PYTHON_INSTALL_DIR to ${pythonDir}`);
function addPythonDirToPath(inputs: SetupInputs): void {
core.exportVariable("UV_PYTHON_INSTALL_DIR", inputs.pythonDir);
core.info(`Set UV_PYTHON_INSTALL_DIR to ${inputs.pythonDir}`);
if (process.env.UV_NO_MODIFY_PATH !== undefined) {
core.info("UV_NO_MODIFY_PATH is set, not adding python dir to path");
} else {
core.addPath(pythonDir);
core.info(`Added ${pythonDir} to the path`);
core.addPath(inputs.pythonDir);
core.info(`Added ${inputs.pythonDir} to the path`);
}
}
function setupPython(): void {
if (pythonVersion !== "") {
core.exportVariable("UV_PYTHON", pythonVersion);
core.info(`Set UV_PYTHON to ${pythonVersion}`);
function setupPython(inputs: SetupInputs): void {
if (inputs.pythonVersion !== "") {
core.exportVariable("UV_PYTHON", inputs.pythonVersion);
core.info(`Set UV_PYTHON to ${inputs.pythonVersion}`);
}
}
async function activateEnvironment(): Promise<void> {
if (activateEnvironmentInput) {
async function activateEnvironment(inputs: SetupInputs): Promise<void> {
if (inputs.activateEnvironment) {
if (process.env.UV_NO_MODIFY_PATH !== undefined) {
throw new Error(
"UV_NO_MODIFY_PATH and activate-environment cannot be used together.",
);
}
core.info(`Creating and activating python venv at ${venvPath}...`);
core.info(`Creating and activating python venv at ${inputs.venvPath}...`);
await exec.exec("uv", [
"venv",
venvPath,
inputs.venvPath,
"--directory",
workingDirectory,
inputs.workingDirectory,
"--clear",
]);
let venvBinPath = `${venvPath}${path.sep}bin`;
let venvBinPath = `${inputs.venvPath}${path.sep}bin`;
if (process.platform === "win32") {
venvBinPath = `${venvPath}${path.sep}Scripts`;
venvBinPath = `${inputs.venvPath}${path.sep}Scripts`;
}
core.addPath(path.resolve(venvBinPath));
core.exportVariable("VIRTUAL_ENV", venvPath);
core.setOutput("venv", venvPath);
core.exportVariable("VIRTUAL_ENV", inputs.venvPath);
core.setOutput("venv", inputs.venvPath);
}
}
function setCacheDir(): void {
if (cacheLocalPath !== undefined) {
if (cacheLocalPath.source === CacheLocalSource.Config) {
function setCacheDir(inputs: SetupInputs): void {
if (inputs.cacheLocalPath !== undefined) {
if (inputs.cacheLocalPath.source === CacheLocalSource.Config) {
core.info(
"Using cache-dir from uv config file, not modifying UV_CACHE_DIR",
);
return;
}
core.exportVariable("UV_CACHE_DIR", cacheLocalPath.path);
core.info(`Set UV_CACHE_DIR to ${cacheLocalPath.path}`);
core.exportVariable("UV_CACHE_DIR", inputs.cacheLocalPath.path);
core.info(`Set UV_CACHE_DIR to ${inputs.cacheLocalPath.path}`);
}
}
function addMatchers(): void {
if (addProblemMatchers) {
function addMatchers(inputs: SetupInputs): void {
if (inputs.addProblemMatchers) {
const matchersPath = path.join(sourceDir, "..", "..", ".github");
core.info(`##[add-matcher]${path.join(matchersPath, "python.json")}`);
}

View File

@@ -6,10 +6,10 @@ import {
updateChecksums,
} from "./download/checksum/update-known-checksums";
import {
fetchVersionData,
fetchManifest,
getLatestVersion,
type NdjsonVersion,
} from "./download/versions-client";
type ManifestVersion,
} from "./download/manifest";
const VERSION_IN_CHECKSUM_KEY_PATTERN =
/-(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$/;
@@ -32,8 +32,8 @@ async function run(): Promise<void> {
return;
}
const versions = await fetchVersionData();
const checksumEntries = extractChecksumsFromNdjson(versions);
const versions = await fetchManifest();
const checksumEntries = extractChecksumsFromManifest(versions);
await updateChecksums(checksumFilePath, checksumEntries);
core.setOutput("latest-version", latestVersion);
@@ -61,8 +61,8 @@ function extractVersionFromChecksumKey(key: string): string | undefined {
return key.match(VERSION_IN_CHECKSUM_KEY_PATTERN)?.[1];
}
function extractChecksumsFromNdjson(
versions: NdjsonVersion[],
function extractChecksumsFromManifest(
versions: ManifestVersion[],
): ChecksumEntry[] {
const checksums: ChecksumEntry[] = [];

View File

@@ -1,7 +1,7 @@
export const TOOL_CACHE_NAME = "uv";
export const STATE_UV_PATH = "uv-path";
export const STATE_UV_VERSION = "uv-version";
export const VERSIONS_NDJSON_URL =
export const VERSIONS_MANIFEST_URL =
"https://raw.githubusercontent.com/astral-sh/versions/main/v1/uv.ndjson";
/** GitHub Releases URL prefix for uv artifacts. */

View File

@@ -9,53 +9,121 @@ export enum CacheLocalSource {
Default,
}
export const workingDirectory = core.getInput("working-directory");
export const version = core.getInput("version");
export const versionFile = getVersionFile();
export const pythonVersion = core.getInput("python-version");
export const activateEnvironment = core.getBooleanInput("activate-environment");
export const venvPath = getVenvPath();
export const checkSum = core.getInput("checksum");
export const enableCache = getEnableCache();
export const restoreCache = core.getInput("restore-cache") === "true";
export const saveCache = core.getInput("save-cache") === "true";
export const cacheSuffix = core.getInput("cache-suffix") || "";
export const cacheLocalPath = getCacheLocalPath();
export const cacheDependencyGlob = getCacheDependencyGlob();
export const pruneCache = core.getInput("prune-cache") === "true";
export const cachePython = core.getInput("cache-python") === "true";
export const ignoreNothingToCache =
core.getInput("ignore-nothing-to-cache") === "true";
export const ignoreEmptyWorkdir =
core.getInput("ignore-empty-workdir") === "true";
export const toolBinDir = getToolBinDir();
export const toolDir = getToolDir();
export const pythonDir = getUvPythonDir();
export const githubToken = core.getInput("github-token");
export const manifestFile = getManifestFile();
export const addProblemMatchers =
core.getInput("add-problem-matchers") === "true";
export const resolutionStrategy = getResolutionStrategy();
export interface CacheLocalPath {
path: string;
source: CacheLocalSource;
}
function getVersionFile(): string {
export type ResolutionStrategy = "highest" | "lowest";
export interface SetupInputs {
workingDirectory: string;
version: string;
versionFile: string;
pythonVersion: string;
activateEnvironment: boolean;
venvPath: string;
checksum: string;
enableCache: boolean;
restoreCache: boolean;
saveCache: boolean;
cacheSuffix: string;
cacheLocalPath?: CacheLocalPath;
cacheDependencyGlob: string;
pruneCache: boolean;
cachePython: boolean;
ignoreNothingToCache: boolean;
ignoreEmptyWorkdir: boolean;
toolBinDir?: string;
toolDir?: string;
pythonDir: string;
githubToken: string;
manifestFile?: string;
addProblemMatchers: boolean;
resolutionStrategy: ResolutionStrategy;
}
export function loadInputs(): SetupInputs {
const workingDirectory = core.getInput("working-directory");
const version = core.getInput("version");
const versionFile = getVersionFile(workingDirectory);
const pythonVersion = core.getInput("python-version");
const activateEnvironment = core.getBooleanInput("activate-environment");
const venvPath = getVenvPath(workingDirectory, activateEnvironment);
const checksum = core.getInput("checksum");
const enableCache = getEnableCache();
const restoreCache = core.getInput("restore-cache") === "true";
const saveCache = core.getInput("save-cache") === "true";
const cacheSuffix = core.getInput("cache-suffix") || "";
const cacheLocalPath = getCacheLocalPath(
workingDirectory,
versionFile,
enableCache,
);
const cacheDependencyGlob = getCacheDependencyGlob(workingDirectory);
const pruneCache = core.getInput("prune-cache") === "true";
const cachePython = core.getInput("cache-python") === "true";
const ignoreNothingToCache =
core.getInput("ignore-nothing-to-cache") === "true";
const ignoreEmptyWorkdir = core.getInput("ignore-empty-workdir") === "true";
const toolBinDir = getToolBinDir(workingDirectory);
const toolDir = getToolDir(workingDirectory);
const pythonDir = getUvPythonDir();
const githubToken = core.getInput("github-token");
const manifestFile = getManifestFile();
const addProblemMatchers = core.getInput("add-problem-matchers") === "true";
const resolutionStrategy = getResolutionStrategy();
return {
activateEnvironment,
addProblemMatchers,
cacheDependencyGlob,
cacheLocalPath,
cachePython,
cacheSuffix,
checksum,
enableCache,
githubToken,
ignoreEmptyWorkdir,
ignoreNothingToCache,
manifestFile,
pruneCache,
pythonDir,
pythonVersion,
resolutionStrategy,
restoreCache,
saveCache,
toolBinDir,
toolDir,
venvPath,
version,
versionFile,
workingDirectory,
};
}
function getVersionFile(workingDirectory: string): string {
const versionFileInput = core.getInput("version-file");
if (versionFileInput !== "") {
const tildeExpanded = expandTilde(versionFileInput);
return resolveRelativePath(tildeExpanded);
return resolveRelativePath(workingDirectory, tildeExpanded);
}
return versionFileInput;
}
function getVenvPath(): string {
function getVenvPath(
workingDirectory: string,
activateEnvironment: boolean,
): string {
const venvPathInput = core.getInput("venv-path");
if (venvPathInput !== "") {
if (!activateEnvironment) {
core.warning("venv-path is only used when activate-environment is true");
}
const tildeExpanded = expandTilde(venvPathInput);
return normalizePath(resolveRelativePath(tildeExpanded));
return normalizePath(resolveRelativePath(workingDirectory, tildeExpanded));
}
return normalizePath(resolveRelativePath(".venv"));
return normalizePath(resolveRelativePath(workingDirectory, ".venv"));
}
function getEnableCache(): boolean {
@@ -66,11 +134,11 @@ function getEnableCache(): boolean {
return enableCacheInput === "true";
}
function getToolBinDir(): string | undefined {
function getToolBinDir(workingDirectory: string): string | undefined {
const toolBinDirInput = core.getInput("tool-bin-dir");
if (toolBinDirInput !== "") {
const tildeExpanded = expandTilde(toolBinDirInput);
return resolveRelativePath(tildeExpanded);
return resolveRelativePath(workingDirectory, tildeExpanded);
}
if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) {
@@ -83,11 +151,11 @@ function getToolBinDir(): string | undefined {
return undefined;
}
function getToolDir(): string | undefined {
function getToolDir(workingDirectory: string): string | undefined {
const toolDirInput = core.getInput("tool-dir");
if (toolDirInput !== "") {
const tildeExpanded = expandTilde(toolDirInput);
return resolveRelativePath(tildeExpanded);
return resolveRelativePath(workingDirectory, tildeExpanded);
}
if (process.platform === "win32") {
if (process.env.RUNNER_TEMP !== undefined) {
@@ -100,21 +168,23 @@ function getToolDir(): string | undefined {
return undefined;
}
function getCacheLocalPath():
| {
path: string;
source: CacheLocalSource;
}
| undefined {
function getCacheLocalPath(
workingDirectory: string,
versionFile: string,
enableCache: boolean,
): CacheLocalPath | undefined {
const cacheLocalPathInput = core.getInput("cache-local-path");
if (cacheLocalPathInput !== "") {
const tildeExpanded = expandTilde(cacheLocalPathInput);
return {
path: resolveRelativePath(tildeExpanded),
path: resolveRelativePath(workingDirectory, tildeExpanded),
source: CacheLocalSource.Input,
};
}
const cacheDirFromConfig = getCacheDirFromConfig();
const cacheDirFromConfig = getCacheDirFromConfig(
workingDirectory,
versionFile,
);
if (cacheDirFromConfig !== undefined) {
return { path: cacheDirFromConfig, source: CacheLocalSource.Config };
}
@@ -122,7 +192,7 @@ function getCacheLocalPath():
core.info(`UV_CACHE_DIR is already set to ${process.env.UV_CACHE_DIR}`);
return { path: process.env.UV_CACHE_DIR, source: CacheLocalSource.Env };
}
if (getEnableCache()) {
if (enableCache) {
if (process.env.RUNNER_ENVIRONMENT === "github-hosted") {
if (process.env.RUNNER_TEMP !== undefined) {
return {
@@ -147,9 +217,12 @@ function getCacheLocalPath():
}
}
function getCacheDirFromConfig(): string | undefined {
function getCacheDirFromConfig(
workingDirectory: string,
versionFile: string,
): string | undefined {
for (const filePath of [versionFile, "uv.toml", "pyproject.toml"]) {
const resolvedPath = resolveRelativePath(filePath);
const resolvedPath = resolveRelativePath(workingDirectory, filePath);
try {
const cacheDir = getConfigValueFromTomlFile(resolvedPath, "cache-dir");
if (cacheDir !== undefined) {
@@ -175,9 +248,8 @@ export function getUvPythonDir(): string {
if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") {
if (process.platform === "win32") {
return `${process.env.APPDATA}${path.sep}uv${path.sep}python`;
} else {
return `${process.env.HOME}${path.sep}.local${path.sep}share${path.sep}uv${path.sep}python`;
}
return `${process.env.HOME}${path.sep}.local${path.sep}share${path.sep}uv${path.sep}python`;
}
if (process.env.RUNNER_TEMP !== undefined) {
return `${process.env.RUNNER_TEMP}${path.sep}uv-python-dir`;
@@ -187,14 +259,14 @@ export function getUvPythonDir(): string {
);
}
function getCacheDependencyGlob(): string {
function getCacheDependencyGlob(workingDirectory: string): string {
const cacheDependencyGlobInput = core.getInput("cache-dependency-glob");
if (cacheDependencyGlobInput !== "") {
return cacheDependencyGlobInput
.split("\n")
.map((part) => part.trim())
.map((part) => expandTilde(part))
.map((part) => resolveRelativePath(part))
.map((part) => resolveRelativePath(workingDirectory, part))
.join("\n");
}
return cacheDependencyGlobInput;
@@ -220,7 +292,10 @@ function normalizePath(inputPath: string): string {
return trimmed;
}
function resolveRelativePath(inputPath: string): string {
function resolveRelativePath(
workingDirectory: string,
inputPath: string,
): string {
const hasNegation = inputPath.startsWith("!");
const pathWithoutNegation = hasNegation ? inputPath.substring(1) : inputPath;
@@ -240,7 +315,7 @@ function getManifestFile(): string | undefined {
return undefined;
}
function getResolutionStrategy(): "highest" | "lowest" {
function getResolutionStrategy(): ResolutionStrategy {
const resolutionStrategyInput = core.getInput("resolution-strategy");
if (resolutionStrategyInput === "lowest") {
return "lowest";