Compare commits

...
7 Commits
Author SHA1 Message Date
Kevin StillhammerandGitHub a7b1296fb5 Refactor internal function names (#76) 2025-02-06 17:51:03 +01:00
Kevin StillhammerandGitHub 097d5252c8 Run npm install as part of npm run all (#75) 2025-02-06 17:46:44 +01:00
Kevin StillhammerandGitHub 5f8cbe30d7 Make it clearer how to fix lint errors (#74) 2025-02-06 17:42:12 +01:00
Dave JohansenandGitHub d8f577dec4 Support requirements.txt for version-file (#68) 2025-02-06 17:41:44 +01:00
Kevin StillhammerandGitHub f173943ec8 Run update-known-checksums every night (#73) 2025-02-06 08:43:41 +01:00
Kevin StillhammerandGitHub d34edb0565 Fix version-file input (#72)
The input was previously ignored

Fixes: #69
2025-02-06 08:40:41 +01:00
Adam TurnerandGitHub f14634c415 Read the [project.optional-dependencies] and [dependency-groups] tables (#66)
I'd quite like to use `ruff-action`'s auto-detection feature, but it
currently doesn't read the `[project.optional-dependency]` table, and
support for the PEP 735 `[dependency-groups]` table is currently limited
to only the special-cased `dev` key, and does not account for the
possibility of `{include-group="..."}` inline tables, as [described by
the PEP](https://peps.python.org/pep-0735/#dependency-group-include).

I have guessed how to make the tests work, as the only JS development I
do is plain JS (sans frameworks), so TypeScript is still quite new to me
-- feel free to push required changes to this branch.

A
2025-01-31 22:47:17 +01:00
19 changed files with 236 additions and 62 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
- name: Compare the expected and actual dist/ directories - name: Compare the expected and actual dist/ directories
run: | run: |
if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:" echo "::error::Detected uncommitted changes after build. Run 'npm run build' and commit the changes."
git diff --text -v git diff --text -v
exit 1 exit 1
fi fi
+69 -1
View File
@@ -26,7 +26,7 @@ jobs:
npm run all npm run all
- name: Make sure no changes from linters are detected - name: Make sure no changes from linters are detected
run: | run: |
git diff --exit-code git diff --exit-code || (echo "::error::Please run 'npm run all' to fix the issues" && exit 1)
test-latest-version: test-latest-version:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@@ -51,6 +51,23 @@ jobs:
with: with:
version: ${{ matrix.ruff-version }} version: ${{ matrix.ruff-version }}
src: __tests__/fixtures/python-project src: __tests__/fixtures/python-project
test-version-from-version-file-pyproject:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use version from pyproject.toml
id: ruff-action
uses: ./
with:
src: __tests__/fixtures/python-project
version-file: __tests__/fixtures/pyproject.toml
- name: Correct version gets installed
run: |
if [ "$RUFF_VERSION" != "0.9.3" ]; then
exit 1
fi
env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-default-version-from-pyproject: test-default-version-from-pyproject:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -84,6 +101,57 @@ jobs:
fi fi
env: env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }} RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-default-version-from-pyproject-dependency-groups:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use default version from pyproject.toml dependency groups
id: ruff-action
uses: ./
with:
src: __tests__/fixtures/pyproject-dependency-groups-project
version-file: __tests__/fixtures/pyproject-dependency-groups-project/pyproject.toml
- name: Correct version gets installed
run: |
if [ "$RUFF_VERSION" != "0.8.3" ]; then
exit 1
fi
env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-default-version-from-pyproject-optional-dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use default version from pyproject.toml optional dependencies
id: ruff-action
uses: ./
with:
src: __tests__/fixtures/pyproject-optional-dependencies-project
version-file: __tests__/fixtures/pyproject-optional-dependencies-project/pyproject.toml
- name: Correct version gets installed
run: |
if [ "$RUFF_VERSION" != "0.8.3" ]; then
exit 1
fi
env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-default-version-from-requirements:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use default version from requirements.txt
id: ruff-action
uses: ./
with:
src: __tests__/fixtures/python-project
version-file: __tests__/fixtures/requirements.txt
- name: Correct version gets installed
run: |
if [ "$RUFF_VERSION" != "0.9.0" ]; then
exit 1
fi
env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-semver-range: test-semver-range:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -1,6 +1,8 @@
name: "Update known checksums" name: "Update known checksums"
on: on:
workflow_dispatch: workflow_dispatch:
schedule:
- cron: "0 4 * * *" # Run every day at 4am UTC
jobs: jobs:
build: build:
+4 -3
View File
@@ -81,7 +81,8 @@ This action adds ruff to the PATH, so you can use it in subsequent steps.
By default this action looks for a pyproject.toml file in the root of the repository to determine By default this action looks for a pyproject.toml file in the root of the repository to determine
the ruff version to install. If no pyproject.toml file is found, or no ruff version is defined in the ruff version to install. If no pyproject.toml file is found, or no ruff version is defined in
either `dependencies` or `dependency-groups.dev` the latest version is installed. `project.dependencies`, `project.optional-dependencies`, or `dependency-groups`,
the latest version is installed.
#### Install the latest version #### Install the latest version
@@ -123,13 +124,13 @@ to install the latest version that satisfies the range.
#### Install a version from a specified version file #### Install a version from a specified version file
You can specify a file to read the version from. You can specify a file to read the version from.
Currently `pyproject.toml` is supported. Currently `pyproject.toml` and `requirements.txt` are supported.
```yaml ```yaml
- name: Install a version from a specified version file - name: Install a version from a specified version file
uses: astral-sh/ruff-action@v3 uses: astral-sh/ruff-action@v3
with: with:
version-file: "my-path/to/pyproject.toml" version-file: "my-path/to/pyproject.toml-or-requirements.txt"
``` ```
### Validate checksum ### Validate checksum
@@ -0,0 +1,22 @@
[project]
name = "pyproject-dependency-groups-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
[dependency-groups]
dev = [
{ include-group = "docs" },
{ include-group = "lint" },
]
docs = [
"sphinx",
]
lint = [
"ruff==0.8.3",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
@@ -0,0 +1,2 @@
def hello() -> str:
return "Hello from python-project!"
@@ -0,0 +1,15 @@
[project]
name = "pyproject-optional-dependencies-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
[project.optional-dependencies]
lint = [
"ruff==0.8.3",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
@@ -0,0 +1,2 @@
def hello() -> str:
return "Hello from python-project!"
+13
View File
@@ -0,0 +1,13 @@
[project]
name = "pyython-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"ruff==0.9.3",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
+1
View File
@@ -0,0 +1 @@
ruff==0.9.0
Generated Vendored
+40 -24
View File
@@ -30387,6 +30387,7 @@ async function downloadVersion(platform, arch, version, checkSum, githubToken) {
return { version: version, cachedToolDir }; return { version: version, cachedToolDir };
} }
async function resolveVersion(versionInput, githubToken) { async function resolveVersion(versionInput, githubToken) {
core.debug(`Resolving ${versionInput}...`);
const version = versionInput === "latest" const version = versionInput === "latest"
? await getLatestVersion(githubToken) ? await getLatestVersion(githubToken)
: versionInput; : versionInput;
@@ -30399,6 +30400,7 @@ async function resolveVersion(versionInput, githubToken) {
if (resolvedVersion === "") { if (resolvedVersion === "") {
throw new Error(`No version found for ${version}`); throw new Error(`No version found for ${version}`);
} }
core.debug(`Resolved version: ${resolvedVersion}`);
return resolvedVersion; return resolvedVersion;
} }
async function getAvailableVersions(githubToken) { async function getAvailableVersions(githubToken) {
@@ -30552,17 +30554,21 @@ async function determineVersion() {
return await (0, download_version_1.resolveVersion)(inputs_1.version, inputs_1.githubToken); return await (0, download_version_1.resolveVersion)(inputs_1.version, inputs_1.githubToken);
} }
if (inputs_1.versionFile !== "") { if (inputs_1.versionFile !== "") {
const versionFromPyproject = (0, pyproject_1.getRuffVersionFromPyproject)(inputs_1.versionFile); const versionFromPyproject = (0, pyproject_1.getRuffVersionFromRequirementsFile)(inputs_1.versionFile);
if (versionFromPyproject === undefined) { if (versionFromPyproject === undefined) {
core.warning("Could not parse version from supplied pyproject.toml. Using latest version."); core.warning(`Could not parse version from ${inputs_1.versionFile}. Using latest version.`);
return await (0, download_version_1.resolveVersion)("latest", inputs_1.githubToken);
} }
return await (0, download_version_1.resolveVersion)(versionFromPyproject || "latest", inputs_1.githubToken);
} }
const pyProjectPath = path.join(inputs_1.src, "pyproject.toml"); const pyProjectPath = path.join(inputs_1.src, "pyproject.toml");
if (!fs.existsSync(pyProjectPath)) { if (!fs.existsSync(pyProjectPath)) {
core.info(`Could not find ${pyProjectPath}. Using latest version.`);
return await (0, download_version_1.resolveVersion)("latest", inputs_1.githubToken); return await (0, download_version_1.resolveVersion)("latest", inputs_1.githubToken);
} }
const versionFromPyproject = (0, pyproject_1.getRuffVersionFromPyproject)(pyProjectPath); const versionFromPyproject = (0, pyproject_1.getRuffVersionFromRequirementsFile)(pyProjectPath);
if (versionFromPyproject === undefined) {
core.warning(`Could not parse version from ${pyProjectPath}. Using latest version.`);
}
return await (0, download_version_1.resolveVersion)(versionFromPyproject || "latest", inputs_1.githubToken); return await (0, download_version_1.resolveVersion)(versionFromPyproject || "latest", inputs_1.githubToken);
} }
function addRuffToPath(cachedPath) { function addRuffToPath(cachedPath) {
@@ -30725,29 +30731,12 @@ var __importStar = (this && this.__importStar) || (function () {
}; };
})(); })();
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getRuffVersionFromPyproject = getRuffVersionFromPyproject; exports.getRuffVersionFromRequirementsFile = getRuffVersionFromRequirementsFile;
const fs = __importStar(__nccwpck_require__(3024)); const fs = __importStar(__nccwpck_require__(3024));
const core = __importStar(__nccwpck_require__(7484)); const core = __importStar(__nccwpck_require__(7484));
const toml = __importStar(__nccwpck_require__(7106)); const toml = __importStar(__nccwpck_require__(7106));
function getRuffVersionFromPyproject(filePath) { function getRuffVersionFromAllDependencies(allDependencies) {
if (!fs.existsSync(filePath)) { const ruffVersionDefinition = allDependencies.find((dep) => dep.startsWith("ruff"));
core.warning(`Could not find file: ${filePath}`);
return undefined;
}
const pyprojectContent = fs.readFileSync(filePath, "utf-8");
let pyproject;
try {
pyproject = toml.parse(pyprojectContent);
}
catch (err) {
const message = err.message;
core.warning(`Error while parsing ${filePath}: ${message}`);
return undefined;
}
const dependencies = pyproject?.project?.dependencies || [];
const devDependencies = pyproject?.["dependency-groups"]?.dev || [];
const ruffVersionDefinition = dependencies.find((dep) => dep.startsWith("ruff")) ||
devDependencies.find((dep) => dep.startsWith("ruff"));
if (ruffVersionDefinition) { if (ruffVersionDefinition) {
const ruffVersion = ruffVersionDefinition const ruffVersion = ruffVersionDefinition
.match(/^ruff([^A-Z0-9._-]+.*)$/)?.[1] .match(/^ruff([^A-Z0-9._-]+.*)$/)?.[1]
@@ -30760,6 +30749,33 @@ function getRuffVersionFromPyproject(filePath) {
} }
return undefined; return undefined;
} }
function parsePyproject(pyprojectContent) {
const pyproject = toml.parse(pyprojectContent);
const dependencies = pyproject?.project?.dependencies || [];
const optionalDependencies = Object.values(pyproject?.project?.["optional-dependencies"] || {}).flat();
const devDependencies = Object.values(pyproject?.["dependency-groups"] || {})
.flat()
.filter((item) => typeof item === "string");
return getRuffVersionFromAllDependencies(dependencies.concat(optionalDependencies, devDependencies));
}
function getRuffVersionFromRequirementsFile(filePath) {
if (!fs.existsSync(filePath)) {
core.warning(`Could not find file: ${filePath}`);
return undefined;
}
const pyprojectContent = fs.readFileSync(filePath, "utf-8");
if (filePath.endsWith(".txt")) {
return getRuffVersionFromAllDependencies(pyprojectContent.split("\n"));
}
try {
return parsePyproject(pyprojectContent);
}
catch (err) {
const message = err.message;
core.warning(`Error while parsing ${filePath}: ${message}`);
return undefined;
}
}
/***/ }), /***/ }),
+1 -1
View File
@@ -12,7 +12,7 @@
"package": "ncc build -o dist/ruff-action src/ruff-action.ts && ncc build -o dist/update-known-checksums src/update-known-checksums.ts", "package": "ncc build -o dist/ruff-action src/ruff-action.ts && ncc build -o dist/update-known-checksums src/update-known-checksums.ts",
"act": "act pull_request -W .github/workflows/test.yml --container-architecture linux/amd64 -s GITHUB_TOKEN=\"$(gh auth token)\"", "act": "act pull_request -W .github/workflows/test.yml --container-architecture linux/amd64 -s GITHUB_TOKEN=\"$(gh auth token)\"",
"update-known-checksums": "RUNNER_TEMP=known_checksums node dist/update-known-checksums/index.js src/download/checksum/known-checksums.ts \"$(gh auth token)\"", "update-known-checksums": "RUNNER_TEMP=known_checksums node dist/update-known-checksums/index.js src/download/checksum/known-checksums.ts \"$(gh auth token)\"",
"all": "npm run build && npm run format && npm run lint && npm run package" "all": "npm install && npm run build && npm run format && npm run lint && npm run package"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
+2
View File
@@ -71,6 +71,7 @@ export async function resolveVersion(
versionInput: string, versionInput: string,
githubToken: string, githubToken: string,
): Promise<string> { ): Promise<string> {
core.debug(`Resolving ${versionInput}...`);
const version = const version =
versionInput === "latest" versionInput === "latest"
? await getLatestVersion(githubToken) ? await getLatestVersion(githubToken)
@@ -84,6 +85,7 @@ export async function resolveVersion(
if (resolvedVersion === "") { if (resolvedVersion === "") {
throw new Error(`No version found for ${version}`); throw new Error(`No version found for ${version}`);
} }
core.debug(`Resolved version: ${resolvedVersion}`);
return resolvedVersion; return resolvedVersion;
} }
+13 -5
View File
@@ -21,7 +21,7 @@ import {
version, version,
versionFile as versionFileInput, versionFile as versionFileInput,
} from "./utils/inputs"; } from "./utils/inputs";
import { getRuffVersionFromPyproject } from "./utils/pyproject"; import { getRuffVersionFromRequirementsFile } from "./utils/pyproject";
import * as fs from "node:fs"; import * as fs from "node:fs";
async function run(): Promise<void> { async function run(): Promise<void> {
@@ -93,19 +93,27 @@ async function determineVersion(): Promise<string> {
return await resolveVersion(version, githubToken); return await resolveVersion(version, githubToken);
} }
if (versionFileInput !== "") { if (versionFileInput !== "") {
const versionFromPyproject = getRuffVersionFromPyproject(versionFileInput); const versionFromPyproject =
getRuffVersionFromRequirementsFile(versionFileInput);
if (versionFromPyproject === undefined) { if (versionFromPyproject === undefined) {
core.warning( core.warning(
"Could not parse version from supplied pyproject.toml. Using latest version.", `Could not parse version from ${versionFileInput}. Using latest version.`,
); );
return await resolveVersion("latest", githubToken);
} }
return await resolveVersion(versionFromPyproject || "latest", githubToken);
} }
const pyProjectPath = path.join(src, "pyproject.toml"); const pyProjectPath = path.join(src, "pyproject.toml");
if (!fs.existsSync(pyProjectPath)) { if (!fs.existsSync(pyProjectPath)) {
core.info(`Could not find ${pyProjectPath}. Using latest version.`);
return await resolveVersion("latest", githubToken); return await resolveVersion("latest", githubToken);
} }
const versionFromPyproject = getRuffVersionFromPyproject(pyProjectPath); const versionFromPyproject =
getRuffVersionFromRequirementsFile(pyProjectPath);
if (versionFromPyproject === undefined) {
core.warning(
`Could not parse version from ${pyProjectPath}. Using latest version.`,
);
}
return await resolveVersion(versionFromPyproject || "latest", githubToken); return await resolveVersion(versionFromPyproject || "latest", githubToken);
} }
+47 -27
View File
@@ -2,34 +2,12 @@ import * as fs from "node:fs";
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as toml from "smol-toml"; import * as toml from "smol-toml";
export function getRuffVersionFromPyproject( function getRuffVersionFromAllDependencies(
filePath: string, allDependencies: string[],
): string | undefined { ): string | undefined {
if (!fs.existsSync(filePath)) { const ruffVersionDefinition = allDependencies.find((dep: string) =>
core.warning(`Could not find file: ${filePath}`); dep.startsWith("ruff"),
return undefined; );
}
const pyprojectContent = fs.readFileSync(filePath, "utf-8");
let pyproject:
| {
project?: { dependencies?: string[] };
"dependency-groups"?: { dev?: string[] };
}
| undefined;
try {
pyproject = toml.parse(pyprojectContent);
} catch (err) {
const message = (err as Error).message;
core.warning(`Error while parsing ${filePath}: ${message}`);
return undefined;
}
const dependencies: string[] = pyproject?.project?.dependencies || [];
const devDependencies: string[] = pyproject?.["dependency-groups"]?.dev || [];
const ruffVersionDefinition =
dependencies.find((dep: string) => dep.startsWith("ruff")) ||
devDependencies.find((dep: string) => dep.startsWith("ruff"));
if (ruffVersionDefinition) { if (ruffVersionDefinition) {
const ruffVersion = ruffVersionDefinition const ruffVersion = ruffVersionDefinition
@@ -44,3 +22,45 @@ export function getRuffVersionFromPyproject(
return undefined; return undefined;
} }
function parsePyproject(pyprojectContent: string): string | undefined {
const pyproject: {
project?: {
dependencies?: string[];
"optional-dependencies"?: Map<string, string[]>;
};
"dependency-groups"?: Map<string, Array<string | object>>;
} = toml.parse(pyprojectContent);
const dependencies: string[] = pyproject?.project?.dependencies || [];
const optionalDependencies: string[] = Object.values(
pyproject?.project?.["optional-dependencies"] || {},
).flat();
const devDependencies: string[] = Object.values(
pyproject?.["dependency-groups"] || {},
)
.flat()
.filter((item: string | object) => typeof item === "string");
return getRuffVersionFromAllDependencies(
dependencies.concat(optionalDependencies, devDependencies),
);
}
export function getRuffVersionFromRequirementsFile(
filePath: string,
): string | undefined {
if (!fs.existsSync(filePath)) {
core.warning(`Could not find file: ${filePath}`);
return undefined;
}
const pyprojectContent = fs.readFileSync(filePath, "utf-8");
if (filePath.endsWith(".txt")) {
return getRuffVersionFromAllDependencies(pyprojectContent.split("\n"));
}
try {
return parsePyproject(pyprojectContent);
} catch (err) {
const message = (err as Error).message;
core.warning(`Error while parsing ${filePath}: ${message}`);
return undefined;
}
}