Skip to content

Commit 0ce1b0b

Browse files
authored
refactor version resolving (#353)
1 parent 9b8caf6 commit 0ce1b0b

15 files changed

Lines changed: 1202 additions & 543 deletions

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ anything `ruff` can (ex, fix).
2929

3030
| Input | Description | Default |
3131
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------------|
32-
| `version` | The version of Ruff to install. See [Install specific versions](#install-specific-versions) | `latest` |
32+
| `version` | The version of Ruff to install. See [Install specific versions](#install-specific-versions) | discovered from `pyproject.toml`, else `latest` |
3333
| `version-file` | The file to read the version from. See [Install a version from a specified version file](#install-a-version-from-a-specified-version-file) | None |
3434
| `manifest-file` | URL to a custom Ruff manifest in the `astral-sh/versions` format. | None |
3535
| `args` | The arguments to pass to the `ruff` command. See [Configuring Ruff] | `check` |
@@ -95,10 +95,10 @@ you can use the `args` input to overwrite the default value (`check`):
9595

9696
### Install specific versions
9797

98-
By default this action looks for a pyproject.toml file in the root of the repository to determine
99-
the ruff version to install. If no pyproject.toml file is found, or no ruff version is defined in
100-
`project.dependencies`, `project.optional-dependencies`, or `dependency-groups`,
101-
the latest version is installed.
98+
By default this action searches upward from `src` until the workspace root to find the nearest
99+
`pyproject.toml` and determine the Ruff version to install. If no `pyproject.toml` file is found,
100+
or no Ruff version is defined in `project.dependencies`, `project.optional-dependencies`,
101+
`dependency-groups`, or supported Poetry dependency tables, the latest version is installed.
102102

103103
> [!NOTE]
104104
> This action does only support ruff versions v0.0.247 and above.
@@ -151,7 +151,8 @@ to install the latest version that satisfies the range.
151151
#### Install a version from a specified version file
152152

153153
You can specify a file to read the version from.
154-
Currently `pyproject.toml` and `requirements.txt` are supported.
154+
Currently `pyproject.toml` and `requirements.txt` are supported. If the file cannot be parsed
155+
or does not contain a Ruff version, the action warns and falls back to `latest`.
155156

156157
```yaml
157158
- name: Install a version from a specified version file
@@ -160,6 +161,13 @@ Currently `pyproject.toml` and `requirements.txt` are supported.
160161
version-file: "my-path/to/pyproject.toml-or-requirements.txt"
161162
```
162163

164+
Version resolution precedence is:
165+
166+
1. `version`
167+
2. `version-file`
168+
3. nearest discoverable `pyproject.toml` found by searching upward from `src`
169+
4. `latest`
170+
163171
#### Install using a custom manifest URL
164172

165173
You can override the default `astral-sh/versions` manifest with `manifest-file`.

__tests__/download/download-version.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,17 @@ const mockCopyFile = jest.fn();
4747
const mockReaddir = jest.fn();
4848

4949
jest.unstable_mockModule("node:fs", () => ({
50+
default: {},
5051
promises: {
5152
copyFile: mockCopyFile,
5253
readdir: mockReaddir,
5354
},
5455
}));
5556

56-
const { downloadVersion, resolveVersion, rewriteToMirror } = await import(
57+
const { downloadVersion, rewriteToMirror } = await import(
5758
"../../src/download/download-version"
5859
);
60+
const { resolveVersion } = await import("../../src/version/resolve");
5961

6062
describe("download-version", () => {
6163
beforeEach(() => {

__tests__/utils/pyproject.test.ts

Lines changed: 0 additions & 193 deletions
This file was deleted.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
2+
3+
const info = jest.fn();
4+
const warning = jest.fn();
5+
6+
jest.unstable_mockModule("@actions/core", () => ({
7+
debug: jest.fn(),
8+
info,
9+
warning,
10+
}));
11+
12+
const { findRuffVersionInSpec, getRuffVersionFromFile } = await import(
13+
"../../src/version/file-parser"
14+
);
15+
16+
describe("file-parser", () => {
17+
beforeEach(() => {
18+
info.mockReset();
19+
warning.mockReset();
20+
});
21+
22+
describe("findRuffVersionInSpec", () => {
23+
it("extracts version from 'ruff==0.9.3'", () => {
24+
const result = findRuffVersionInSpec("ruff==0.9.3");
25+
expect(result).toBe("0.9.3");
26+
expect(info).toHaveBeenCalledWith(
27+
"Found ruff version in requirements file: 0.9.3",
28+
);
29+
expect(warning).not.toHaveBeenCalled();
30+
});
31+
32+
it("extracts version from 'ruff>=0.14'", () => {
33+
const result = findRuffVersionInSpec("ruff>=0.14");
34+
expect(result).toBe(">=0.14");
35+
expect(warning).not.toHaveBeenCalled();
36+
});
37+
38+
it("extracts version from 'ruff ~=1.0.0'", () => {
39+
const result = findRuffVersionInSpec("ruff ~=1.0.0");
40+
expect(result).toBe("~=1.0.0");
41+
expect(warning).not.toHaveBeenCalled();
42+
});
43+
44+
it("extracts version from 'ruff>=0.14,<1.0'", () => {
45+
const result = findRuffVersionInSpec("ruff>=0.14,<1.0");
46+
expect(result).toBe(">=0.14,<1.0");
47+
expect(warning).not.toHaveBeenCalled();
48+
});
49+
50+
it("extracts version from 'ruff>=0.14,<2.0,!=1.5.0'", () => {
51+
const result = findRuffVersionInSpec("ruff>=0.14,<2.0,!=1.5.0");
52+
expect(result).toBe(">=0.14,<2.0,!=1.5.0");
53+
expect(warning).not.toHaveBeenCalled();
54+
});
55+
56+
it("returns undefined for non-ruff dependencies", () => {
57+
const result = findRuffVersionInSpec("another-dep==0.1.6");
58+
expect(result).toBeUndefined();
59+
expect(info).not.toHaveBeenCalled();
60+
expect(warning).not.toHaveBeenCalled();
61+
});
62+
63+
it("strips trailing backslash", () => {
64+
const result = findRuffVersionInSpec("ruff==0.9.3 \\");
65+
expect(result).toBe("0.9.3");
66+
expect(info).toHaveBeenCalledWith(
67+
"Found ruff version in requirements file: 0.9.3",
68+
);
69+
expect(warning).not.toHaveBeenCalled();
70+
});
71+
72+
it("strips environment markers and warns", () => {
73+
const result = findRuffVersionInSpec(
74+
'ruff>=0.14 ; python_version >= "3.11"',
75+
);
76+
expect(result).toBe(">=0.14");
77+
expect(info).toHaveBeenCalledWith(
78+
"Found ruff version in requirements file: >=0.14",
79+
);
80+
expect(warning).toHaveBeenCalledWith(
81+
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
82+
);
83+
});
84+
85+
it("handles whitespace", () => {
86+
const result = findRuffVersionInSpec(" ruff >=0.14 ");
87+
expect(result).toBe(">=0.14");
88+
expect(warning).not.toHaveBeenCalled();
89+
});
90+
91+
it("returns undefined for empty strings", () => {
92+
const result = findRuffVersionInSpec("");
93+
expect(result).toBeUndefined();
94+
expect(info).not.toHaveBeenCalled();
95+
expect(warning).not.toHaveBeenCalled();
96+
});
97+
});
98+
99+
describe("getRuffVersionFromFile", () => {
100+
it("reads the version from requirements.txt", () => {
101+
const result = getRuffVersionFromFile(
102+
"__tests__/fixtures/requirements.txt",
103+
);
104+
expect(result).toBe("0.9.0");
105+
});
106+
107+
it("reads the version from requirements files with hashes", () => {
108+
const result = getRuffVersionFromFile(
109+
"__tests__/fixtures/requirements-with-hash.txt",
110+
);
111+
expect(result).toBe("0.9.0");
112+
});
113+
114+
it("reads the version from pyproject.toml dependencies", () => {
115+
const result = getRuffVersionFromFile(
116+
"__tests__/fixtures/pyproject.toml",
117+
);
118+
expect(result).toBe("0.9.3");
119+
});
120+
121+
it("reads the version from Poetry dependencies", () => {
122+
const result = getRuffVersionFromFile(
123+
"__tests__/fixtures/pyproject-dependency-poetry-project/pyproject.toml",
124+
);
125+
expect(result).toBe("~0.8.2");
126+
});
127+
});
128+
});

0 commit comments

Comments
 (0)