Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
af67608
chore: update known checksums for 0.11.31/0.11.32 (#972)
github-actions[bot] Aug 14, 2026
a082309
chore: update known checksums for 0.12.0 (#981)
github-actions[bot] Aug 14, 2026
a102de1
chore: update known checksums for 0.12.1 (#982)
github-actions[bot] Aug 14, 2026
b5a8916
chore: update known checksums for 0.12.2 (#985)
github-actions[bot] Aug 14, 2026
c2c86e0
chore: update known checksums for 0.12.3 (#991)
github-actions[bot] Aug 14, 2026
8961796
Disable automatic caching for sensitive events (#992)
github-actions[bot] Aug 14, 2026
ee3bcea
Disable automatic caching for sensitive events (#992)
github-actions[bot] Aug 14, 2026
c7e76ca
Disable automatic caching for sensitive events (#992)
github-actions[bot] Aug 14, 2026
ba56a8b
Disable automatic caching for sensitive events (#992)
github-actions[bot] Aug 14, 2026
dba81cd
Disable automatic caching for sensitive events (#992)
github-actions[bot] Aug 14, 2026
1c144d7
Add latest-known version selector (#993)
github-actions[bot] Aug 14, 2026
84a1c14
Add latest-known version selector (#993)
github-actions[bot] Aug 14, 2026
eb7af31
Add latest-known version selector (#993)
github-actions[bot] Aug 14, 2026
acda593
Add latest-known version selector (#993)
github-actions[bot] Aug 14, 2026
be218fd
Add latest-known version selector (#993)
github-actions[bot] Aug 14, 2026
dbac766
Add latest-known version selector (#993)
github-actions[bot] Aug 14, 2026
d4da00d
chore(deps): roll up Dependabot updates (#994)
github-actions[bot] Aug 14, 2026
758d007
chore(deps): roll up Dependabot updates (#994)
github-actions[bot] Aug 14, 2026
1a520a5
chore(deps): roll up Dependabot updates (#994)
github-actions[bot] Aug 14, 2026
3d479ed
chore(deps): roll up Dependabot updates (#994)
github-actions[bot] Aug 14, 2026
8472798
Read Python version from .tool-versions (#996)
github-actions[bot] Aug 14, 2026
e37effd
Read Python version from .tool-versions (#996)
github-actions[bot] Aug 14, 2026
41a1641
Read Python version from .tool-versions (#996)
github-actions[bot] Aug 14, 2026
85a13aa
Read Python version from .tool-versions (#996)
github-actions[bot] Aug 14, 2026
7a15537
Read Python version from .tool-versions (#996)
github-actions[bot] Aug 14, 2026
3fdd791
Read Python version from .tool-versions (#996)
github-actions[bot] Aug 14, 2026
d0189dc
Read Python version from .tool-versions (#996)
github-actions[bot] Aug 14, 2026
248c864
Read Python version from .tool-versions (#996)
github-actions[bot] Aug 14, 2026
c74ba70
Read Python version from .tool-versions (#996)
github-actions[bot] Aug 14, 2026
600d0e9
chore(deps): roll up Dependabot updates (#1004)
github-actions[bot] Aug 14, 2026
7e2c43e
Reject paths in .tool-versions (#1007)
github-actions[bot] Aug 14, 2026
f09825d
Reject paths in .tool-versions (#1007)
github-actions[bot] Aug 14, 2026
5b86eb9
Reject paths in .tool-versions (#1007)
github-actions[bot] Aug 14, 2026
e99aac5
chore(deps): roll up Dependabot updates (#1013)
github-actions[bot] Aug 14, 2026
646014f
conflicted commits cherry-picked
Raj-StepSecurity Aug 14, 2026
cf67c4f
comments addressed
Raj-StepSecurity Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 102 additions & 46 deletions .github/workflows/test.yml

Large diffs are not rendered by default.

45 changes: 32 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Set up your GitHub Actions workflow with a specific version of [uv](https://docs

```yaml
- name: Install the latest version of uv
uses: step-security/setup-uv@v8
uses: step-security/setup-uv@v10
```

If you do not specify a version, this action will look for a [required-version](https://docs.astral.sh/uv/reference/settings/#required-version)
Expand All @@ -44,18 +44,18 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed

```yaml
- name: Install uv with all available options
uses: step-security/setup-uv@v8
uses: step-security/setup-uv@v10
with:
# The version of uv to install (default: searches for version in config files, then latest)
# The version of uv to install, e.g., "0.5.0", "latest", or "latest-known" (default: searches for version in config files, then latest)
version: ""

# Path to a file containing the version of uv to install, e.g., uv.toml, pyproject.toml, .tool-versions, requirements.txt or uv.lock (default: searches uv.toml then pyproject.toml)
# Path to a file containing the version of uv to install, e.g., uv.toml, pyproject.toml, .tool-versions, requirements.txt or uv.lock. A selected .tool-versions file can also provide the Python version (default: searches uv.toml then pyproject.toml)
version-file: ""

# Resolution strategy when resolving version ranges: 'highest' or 'lowest'
resolution-strategy: "highest"

# The version of Python to set UV_PYTHON to
# The version of Python to set UV_PYTHON to (overrides the Python version from .tool-versions)
python-version: ""

# Use uv venv to activate a venv ready to be used by later steps
Expand All @@ -76,7 +76,7 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed
# Used when downloading uv from GitHub releases
github-token: ${{ github.token }}

# Enable uploading of the uv cache: true, false, or auto (enabled on GitHub-hosted runners, disabled on self-hosted runners)
# Enable the GitHub Actions cache for uv: true, false, or auto (enabled on GitHub-hosted runners except for release, tag push, pull_request_target, and workflow_run events; disabled on self-hosted runners)
enable-cache: "auto"

# Glob pattern to match files relative to the repository root to control the cache
Expand Down Expand Up @@ -146,11 +146,30 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed

You can use the input `python-version` to set the environment variable `UV_PYTHON` for the rest of your workflow

This will override any python version specifications in `pyproject.toml` and `.python-version`
This will override any python version specifications in `pyproject.toml`, `.python-version`, and
an explicitly selected `.tool-versions` file.

When `version-file` points to `.tool-versions`, its `python` entry is used if neither
`python-version` nor `UV_PYTHON` is set:

```text
uv 0.12.3
python 3.13
```

```yaml
- uses: step-security/setup-uv@v9
with:
version-file: ".tool-versions"
```

Only a single Python version is supported. Filesystem paths are not supported for uv or Python.
Multiple Python fallback versions and the asdf `ref:`, `path:`, and `system` forms are ignored with
a warning.

```yaml
- name: Install the latest version of uv and set the python version to 3.13t
uses: step-security/setup-uv@v8
uses: step-security/setup-uv@v10
with:
python-version: 3.13t
- run: uv pip install --python=3.13t pip
Expand All @@ -168,7 +187,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install the latest version of uv and set the python version
uses: step-security/setup-uv@v8
uses: step-security/setup-uv@v10
with:
python-version: ${{ matrix.python-version }}
- name: Test with python ${{ matrix.python-version }}
Expand All @@ -185,7 +204,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: step-security/setup-uv@v8
uses: step-security/setup-uv@v10
with:
working-directory: my/subproject/dir
```
Expand Down Expand Up @@ -227,7 +246,7 @@ For example:
- name: Checkout the repository
uses: actions/checkout@v6
- name: Install the latest version of uv
uses: step-security/setup-uv@v8
uses: step-security/setup-uv@v10
with:
enable-cache: true
- name: Test
Expand All @@ -239,7 +258,7 @@ To install a specific version of Python, use

```yaml
- name: Install the latest version of uv
uses: step-security/setup-uv@v8
uses: step-security/setup-uv@v10
with:
enable-cache: true
- name: Install Python 3.12
Expand All @@ -258,7 +277,7 @@ output:
uses: actions/checkout@v6
- name: Install the default version of uv
id: setup-uv
uses: step-security/setup-uv@v8
uses: step-security/setup-uv@v10
- name: Print the installed version
run: echo "Installed uv version is ${{ steps.setup-uv.outputs.uv-version }}"
```
Expand Down
12 changes: 6 additions & 6 deletions __tests__/download/checksum/checksum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ it.each<KnownVersionFixture>([
known: false,
version: "0.0.15",
},
])("isknownVersion should return $known for version $version", ({
version,
known,
}) => {
expect(isknownVersion(version)).toBe(known);
});
])(
"isknownVersion should return $known for version $version",
({ version, known }) => {
expect(isknownVersion(version)).toBe(known);
},
);
20 changes: 20 additions & 0 deletions __tests__/download/checksum/known-version.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, it, jest } from "@jest/globals";

jest.unstable_mockModule(
"../../../src/download/checksum/known-checksums",
() => ({
KNOWN_CHECKSUMS: {
"aarch64-apple-darwin-1.9.0": "checksum",
"x86_64-unknown-linux-gnu-1.8.0": "checksum",
"x86_64-unknown-linux-gnu-1.10.0": "checksum",
},
}),
);

const { getLatestKnownVersion } = await import(
"../../../src/download/checksum/known-version"
);

it("returns the highest version with a built-in checksum", () => {
expect(getLatestKnownVersion()).toBe("1.10.0");
});
17 changes: 17 additions & 0 deletions __tests__/download/download-version.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ jest.unstable_mockModule("../../src/download/checksum/checksum", () => ({
validateChecksum: mockValidateChecksum,
}));

const mockGetLatestKnownVersion = jest.fn(() => "0.9.25");

jest.unstable_mockModule("../../src/download/checksum/known-version", () => ({
getLatestKnownVersion: mockGetLatestKnownVersion,
}));

const { downloadVersion, resolveVersion, rewriteToMirror } = await import(
"../../src/download/download-version"
);
Expand All @@ -72,6 +78,7 @@ describe("download-version", () => {
mockGetFirstMatchingVersion.mockReset();
mockGetArtifact.mockReset();
mockValidateChecksum.mockReset();
mockGetLatestKnownVersion.mockClear();

mockDownloadTool.mockResolvedValue("/tmp/downloaded");
mockExtractTar.mockResolvedValue("/tmp/extracted");
Expand All @@ -90,6 +97,16 @@ describe("download-version", () => {
expect(mockGetLatestVersion).toHaveBeenCalledWith(undefined);
});

it("resolves latest-known without reading the manifest", async () => {
const version = await resolveVersion("latest-known", undefined);

expect(version).toBe("0.9.25");
expect(mockGetLatestKnownVersion).toHaveBeenCalledTimes(1);
expect(mockGetLatestVersion).not.toHaveBeenCalled();
expect(mockGetAllVersions).not.toHaveBeenCalled();
expect(mockGetFirstMatchingVersion).not.toHaveBeenCalled();
});

it("stops at the first matching version in the default manifest", async () => {
mockGetFirstMatchingVersion.mockImplementation(
(predicate: (version: string) => boolean) =>
Expand Down
1 change: 1 addition & 0 deletions __tests__/fixtures/.tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
uv 0.5.15
python 3.13.1t
112 changes: 112 additions & 0 deletions __tests__/utils/inputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ import {

let mockInputs: Record<string, string> = {};
const tempDirs: string[] = [];
const ORIGINAL_GITHUB_EVENT_NAME = process.env.GITHUB_EVENT_NAME;
const ORIGINAL_GITHUB_REF = process.env.GITHUB_REF;
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 = process.env.UV_PYTHON;
const ORIGINAL_UV_PYTHON_INSTALL_DIR = process.env.UV_PYTHON_INSTALL_DIR;

const mockDebug = jest.fn();
Expand Down Expand Up @@ -52,10 +55,13 @@ function createTempProject(files: Record<string, string> = {}): string {
function resetEnvironment(): void {
jest.clearAllMocks();
mockInputs = {};
delete process.env.GITHUB_EVENT_NAME;
delete process.env.GITHUB_REF;
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;
delete process.env.UV_PYTHON_INSTALL_DIR;
}

Expand All @@ -64,10 +70,13 @@ function restoreEnvironment(): void {
fs.rmSync(dir, { force: true, recursive: true });
}

process.env.GITHUB_EVENT_NAME = ORIGINAL_GITHUB_EVENT_NAME;
process.env.GITHUB_REF = ORIGINAL_GITHUB_REF;
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 = ORIGINAL_UV_PYTHON;
process.env.UV_PYTHON_INSTALL_DIR = ORIGINAL_UV_PYTHON_INSTALL_DIR;
}

Expand All @@ -94,6 +103,109 @@ describe("loadInputs", () => {
expect(inputs.resolutionStrategy).toBe("highest");
});

it("uses the Python version from an explicitly selected .tool-versions file", () => {
mockInputs["working-directory"] = createTempProject({
".tool-versions": "uv 0.12.3\npython 3.13.1t\n",
});
mockInputs["version-file"] = ".tool-versions";

const inputs = loadInputs();

expect(inputs.pythonVersion).toBe("3.13.1t");
});

it("prefers the python-version input over .tool-versions", () => {
mockInputs["working-directory"] = createTempProject({
".tool-versions": "uv 0.12.3\npython 3.13\n",
});
mockInputs["version-file"] = ".tool-versions";
mockInputs["python-version"] = "3.12";

const inputs = loadInputs();

expect(inputs.pythonVersion).toBe("3.12");
});

it("preserves UV_PYTHON instead of overriding it from .tool-versions", () => {
mockInputs["working-directory"] = createTempProject({
".tool-versions": "uv 0.12.3\npython 3.13\n",
});
mockInputs["version-file"] = ".tool-versions";
process.env.UV_PYTHON = "3.11";

const inputs = loadInputs();

expect(inputs.pythonVersion).toBe("");
expect(process.env.UV_PYTHON).toBe("3.11");
});

it("does not discover .tool-versions from the working directory", () => {
mockInputs["working-directory"] = createTempProject({
".tool-versions": "uv 0.12.3\npython 3.13\n",
});

const inputs = loadInputs();

expect(inputs.pythonVersion).toBe("");
});

it.each(["pull_request_target", "workflow_run", "release"])(
"disables automatic caching for the %s event",
(eventName) => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "auto";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
process.env.GITHUB_EVENT_NAME = eventName;

const inputs = loadInputs();

expect(inputs.enableCache).toBe(false);
expect(mockInfo).toHaveBeenCalledWith(
`Caching is disabled for the ${eventName} event`,
);
},
);

it("disables automatic caching for tag pushes", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "auto";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
process.env.GITHUB_EVENT_NAME = "push";
process.env.GITHUB_REF = "refs/tags/v1.0.0";

const inputs = loadInputs();

expect(inputs.enableCache).toBe(false);
expect(mockInfo).toHaveBeenCalledWith("Caching is disabled for tag pushes");
});

it("enables automatic caching for branch pushes", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "auto";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
process.env.GITHUB_EVENT_NAME = "push";
process.env.GITHUB_REF = "refs/heads/main";

const inputs = loadInputs();

expect(inputs.enableCache).toBe(true);
});

it("honors explicitly enabled caching for sensitive events", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "true";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
process.env.GITHUB_EVENT_NAME = "release";

const inputs = loadInputs();

expect(inputs.enableCache).toBe(true);
});

it("uses cache-dir from pyproject.toml when present", () => {
mockInputs["working-directory"] = createTempProject({
"pyproject.toml": `[project]
Expand Down
11 changes: 7 additions & 4 deletions __tests__/version/requirements-file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@ test("ignores dependencies starting with uv", async () => {
test.each([
["without space before marker", "uv==0.11.20; sys_platform != 'emscripten'"],
["with space before marker", "uv==0.11.20 ; sys_platform != 'emscripten'"],
])("strips PEP 508 markers from pyproject dependency groups %s", (_, dependency) => {
const parsedVersion = getUvVersionFromPyprojectContent(`[dependency-groups]
])(
"strips PEP 508 markers from pyproject dependency groups %s",
(_, dependency) => {
const parsedVersion = getUvVersionFromPyprojectContent(`[dependency-groups]
test = [
"${dependency}",
]
`);

expect(parsedVersion).toBe("==0.11.20");
});
expect(parsedVersion).toBe("==0.11.20");
},
);

test("strips PEP 508 markers from requirements dependencies", () => {
const parsedVersion = getUvVersionFromRequirementsText(
Expand Down
Loading
Loading