Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 23 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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).

Expand All @@ -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"
59 changes: 46 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
```

<!-- markdownlint-disable MD013 -->

| 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 |

<!-- markdownlint-enable MD013 -->

Expand All @@ -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`.
Loading