diff --git a/CLAUDE.md b/CLAUDE.md index c6685f9..cac8dc7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,13 +12,19 @@ This is a **composite GitHub Action** (not a standalone application) that provid The action is defined in `action.yml` and executes as a series of composite steps: -1. **YAML Linting**: Validates YAML files using yamllint -2. **Python Environment Setup**: Installs Python version from `.python-version` -3. **Security Scanning**: Uses Anchore scan-action to detect vulnerabilities -4. **Dependency Installation**: Installs packages from `requirements.txt` if present -5. **Testing**: Runs pytest if tests are detected -6. **Code Linting**: Uses Flake8 with a configurable error threshold -7. **Binary Building**: Conditionally builds PyInstaller binaries on tag releases +1. **Python Environment Setup**: Installs Python version from `.python-version` +2. **Security Scanning**: Uses Anchore scan-action (Grype) to detect vulnerabilities +3. **Resolve**: Picks the package manager (uv when `uv.lock` exists, else pip) and validates inputs +4. **Dependency Installation**: `uv sync --all-packages --frozen`, or `requirements.txt` if present +5. **Testing**: Plain pytest for uv projects or when `test-paths`/`pytest-args` is set; otherwise the legacy `test.py` run +6. **Code Linting**: ruff (default for uv) or Flake8 with an error threshold (default for pip) +7. **Type Checking**: Optional mypy or pyright +8. **Binary Building**: Conditionally builds PyInstaller binaries on tag releases (pip projects only) + +Project tools (ruff, mypy, pyright, pytest) run from the project environment via the +`run` output of the resolve step (`uv run --no-sync` or `python3 -m`), so their versions +and config come from the project. User inputs reach the shell through `env:`, never +through `${{ }}` inside `run:`. ### Key Design Decisions @@ -91,6 +97,7 @@ Pipeline fails if error count > 4 (action.yml:76). This threshold may need adjus Binary building is **conditional** and requires: 1. Push event to a tag (`refs/tags/*`) 2. Non-empty `pyinstaller-binary-name` input +3. A pip project (uv projects fail with an error) The binary is automatically attached to GitHub releases (action.yml:86-111). @@ -101,11 +108,17 @@ Required inputs when using this action: | Input | Required | Purpose | |-------|----------|---------| | `token` | Yes | GitHub token used to attach binaries to releases | +| `package-manager` | No | `auto` (default), `uv` or `pip` | +| `linter` | No | `ruff`, `flake8` or `none`; default depends on package manager | +| `type-checker` / `type-check-args` | No | `mypy`, `pyright` or `none` (default) | +| `test-paths` / `pytest-args` | No | Switch pip projects to plain pytest; `pytest-args` goes through `PYTEST_ADDOPTS` | +| `working-directory` | No | Project directory for monorepos (default `.`) | | `pyinstaller-binary-name` | No | If set, builds and releases a binary | +| `pyinstaller-entrypoint` | No | Script to build (default `main.py`) | ## Important Workflow Notes - Projects using this action must have a `.python-version` file to specify Python version -- `requirements.txt` is optional - only installed if present -- Tests only run if `import pytest` is found in Python files -- Security scanning uses severity cutoff of "high" (action.yml:34) +- `requirements.txt` is optional - only installed if present (pip projects) +- Legacy pip tests only run if `import pytest` is found in root-level Python files +- Security scanning uses severity cutoff of "high" diff --git a/README.md b/README.md index 907ea0c..45484df 100644 --- a/README.md +++ b/README.md @@ -24,25 +24,25 @@ jobs: - uses: actions/checkout@some-hash # v4.2.2 with: persist-credentials: false - - uses: schubergphilis/mcvs-python-action@some-hash # v0.2.1 + - uses: schubergphilis/mcvs-python-action@some-hash # v0.3.0 with: token: ${{ secrets.GITHUB_TOKEN }} ``` -| Option | Default | Required | Description | -| :---------------------- | :------ | -------- | :--------------------------------------------------------------------------------------------------------- | -| linter | | | `ruff`, `flake8` or `none`. Defaults to ruff for uv projects, flake8 for pip. ruff must be a project dependency and any finding fails | -| package-manager | auto | | `auto`, `uv` or `pip`. `auto` uses uv (`uv sync --all-packages --frozen`) when `uv.lock` exists, else pip | -| pyinstaller-binary-name | | | If populated, then a binary will be created using pyinstaller and attached to a release | -| pyinstaller-entrypoint | main.py | | The Python script that pyinstaller turns into a binary | -| type-check-args | | | Arguments for the type checker, e.g. `--strict packages/*/src`. Globs are expanded | -| type-checker | none | | `mypy`, `pyright` or `none`. The type checker must be a project dependency | -| pytest-args | | | Extra pytest arguments, e.g. `-m "not integration"`. Quoting works as in a shell | -| test-paths | | | Paths to test, e.g. `packages/*/tests`. Defaults to pytest's own discovery | -| working-directory | . | | Directory of the Python project, for monorepos. For a uv workspace this is the workspace root | -| token | | x | GitHub token required for Docker registry authentication and uploading release assets (if building binary) | +| Option | Default | Required | Description | +| :---------------------- | :------ | -------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| linter | | | `ruff`, `flake8` or `none`. Defaults to ruff for uv projects and flake8 for pip projects. ruff runs with the project's own configuration and any finding fails | +| package-manager | auto | | `auto`, `uv` or `pip`. `auto` uses uv (`uv sync --all-packages --frozen`) when `uv.lock` exists, else pip with `requirements.txt` | +| pyinstaller-binary-name | | | If populated, then a binary will be created using pyinstaller and attached to a release. pip projects only | +| pyinstaller-entrypoint | main.py | | The Python script that pyinstaller turns into a binary | +| pytest-args | | | Extra pytest arguments, e.g. `-m "not integration"`. Quoting works as in a shell | +| test-paths | | | Paths to test, e.g. `packages/*/tests`. Defaults to pytest's own discovery | +| token | | x | GitHub token used to attach binaries to releases | +| type-check-args | | | Arguments for the type checker, e.g. `--strict packages/*/src`. Globs are expanded | +| type-checker | none | | `mypy`, `pyright` or `none` | +| working-directory | . | | Directory of the Python project, for monorepos. For a uv workspace this is the workspace root | @@ -52,3 +52,36 @@ action receives the token only through the `token` input. Define the Python version of the project by adding it to a `.python-version` file. + +ruff, mypy, pyright and pytest run from the project environment +(`uv run` for uv, `python3 -m` for pip), so add the ones you use to the +project's dependencies, e.g. the `dev` dependency group. + +### uv workspace example + +```yaml + - uses: schubergphilis/mcvs-python-action@some-hash # v0.3.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + type-checker: mypy + type-check-args: --strict packages/*/src + test-paths: packages/*/tests + pytest-args: -m "not integration and not live" --import-mode=importlib +``` + +## Migrating to v0.3.0 + +pip projects need no changes. The action detects them because there is no +`uv.lock`, and it keeps installing `requirements.txt`, running `test.py` +with coverage of `main` and linting with flake8. + +- **uv projects** (a `uv.lock` is present) are now installed with + `uv sync --all-packages --frozen`, linted with ruff and tested with plain + `pytest`. Set `package-manager: pip` to keep the old behaviour. +- **pip projects that set `test-paths` or `pytest-args`** run plain `pytest` + instead of `test.py`. Pass `--cov` options in `pytest-args` if you need + coverage. +- **PyInstaller** binaries are now named after `pyinstaller-binary-name`. + Before, they were always built as `gomod-go-version-updater`, and the + release upload failed for any other name. Set `pyinstaller-entrypoint` if + your script is not `main.py`.