diff --git a/AGENTS.md b/AGENTS.md index 7082a72..e801337 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -87,17 +87,16 @@ The state passed to a render-fn should be thought of as `{persistent db state, e - Enter the dev environment with `nix develop` if you are not already inside it - Sync Python dependencies with `uv sync --all-groups`. - Run the app with `uv run repub`. +- Run `uv run qa` often while working. It runs Tailwind CSS generation, `black`, `flake8`, `pyright`, and the full test suite in that order. +- Run `uv run qa-final` at the end before declaring a task complete and always before staging or committing. It runs `uv run qa`, then `nix fmt`, then `nix flake check`. - Generate CSS with `tailwindcss -i ./repub/static/app.tailwind.css -o ./repub/static/app.css` and add `--watch` when you need live rebuilds. - Validate a generated feed with `./scripts/validate-feed path/to/feed.rss`. This wraps the local checkout at `~/src/github.com/w3c/feedvalidator` and pages the validator output through `less` by default. ```sh uv sync --all-groups -uv run pytest -uv run flake8 repub/ tests/ -uv run pyright +uv run qa ./scripts/validate-feed out/feeds/mn-cuba/feed.rss -nix fmt -nix flake check +uv run qa-final uv run repub uv run repub crawl -c repub.toml ``` diff --git a/pyproject.toml b/pyproject.toml index 668db40..b87027b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,8 @@ dependencies = [ [project.scripts] repub = "repub.entrypoint:entrypoint" +qa = "repub.qa:qa_entrypoint" +qa-final = "repub.qa:qa_final_entrypoint" [dependency-groups] dev = [ diff --git a/repub/qa.py b/repub/qa.py new file mode 100644 index 0000000..955d283 --- /dev/null +++ b/repub/qa.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import subprocess + +QA_COMMANDS: tuple[tuple[str, ...], ...] = ( + ( + "tailwindcss", + "-i", + "./repub/static/app.tailwind.css", + "-o", + "./repub/static/app.css", + ), + ("black", "repub/", "tests/"), + ("flake8", "repub/", "tests/"), + ("pyright",), + ("pytest",), +) + +QA_FINAL_COMMANDS: tuple[tuple[str, ...], ...] = QA_COMMANDS + ( + ("nix", "fmt"), + ("nix", "flake", "check"), +) + + +def _run_commands(commands: tuple[tuple[str, ...], ...]) -> int: + for command in commands: + result = subprocess.run(command, check=False) + if result.returncode != 0: + return result.returncode + return 0 + + +def qa_entrypoint() -> int: + return _run_commands(QA_COMMANDS) + + +def qa_final_entrypoint() -> int: + return _run_commands(QA_FINAL_COMMANDS) diff --git a/tests/test_qa.py b/tests/test_qa.py new file mode 100644 index 0000000..906555d --- /dev/null +++ b/tests/test_qa.py @@ -0,0 +1,67 @@ +import tomllib +from pathlib import Path +from types import SimpleNamespace + +from repub.qa import qa_entrypoint, qa_final_entrypoint + + +def test_pyproject_registers_qa_scripts() -> None: + pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml" + config = tomllib.loads(pyproject_path.read_text(encoding="utf-8")) + + scripts = config["project"]["scripts"] + + assert scripts["qa"] == "repub.qa:qa_entrypoint" + assert scripts["qa-final"] == "repub.qa:qa_final_entrypoint" + + +def test_qa_entrypoint_runs_expected_commands_in_order(monkeypatch) -> None: + recorded: list[tuple[str, ...]] = [] + + def fake_run(command: tuple[str, ...], *, check: bool) -> SimpleNamespace: + recorded.append(command) + return SimpleNamespace(returncode=0) + + monkeypatch.setattr("repub.qa.subprocess.run", fake_run) + + assert qa_entrypoint() == 0 + assert recorded == [ + ( + "tailwindcss", + "-i", + "./repub/static/app.tailwind.css", + "-o", + "./repub/static/app.css", + ), + ("black", "repub/", "tests/"), + ("flake8", "repub/", "tests/"), + ("pyright",), + ("pytest",), + ] + + +def test_qa_final_entrypoint_runs_expected_commands_in_order(monkeypatch) -> None: + recorded: list[tuple[str, ...]] = [] + + def fake_run(command: tuple[str, ...], *, check: bool) -> SimpleNamespace: + recorded.append(command) + return SimpleNamespace(returncode=0) + + monkeypatch.setattr("repub.qa.subprocess.run", fake_run) + + assert qa_final_entrypoint() == 0 + assert recorded == [ + ( + "tailwindcss", + "-i", + "./repub/static/app.tailwind.css", + "-o", + "./repub/static/app.css", + ), + ("black", "repub/", "tests/"), + ("flake8", "repub/", "tests/"), + ("pyright",), + ("pytest",), + ("nix", "fmt"), + ("nix", "flake", "check"), + ]