Ctrl K
ComunidadGuías…

scripts/check_no_generated_artifacts.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""Fail if generated build/test artifacts are committed (US-035).

Enumerates tracked files via ``git ls-files`` and exits non-zero when any
match a generated-artifact pattern (coverage output, build/dist trees,
Python bytecode caches). CI runs this as a blocking gate so accidental
``git add -A`` commits of local build output are rejected.

Usage:
    python scripts/check_no_generated_artifacts.py
"""

from __future__ import annotations

import re
import subprocess
import sys

# Each entry is (compiled pattern, human-readable description). Patterns are
# matched against git-tracked paths (always forward-slash separated).
# Deliberately tracked paths that would otherwise match a generated pattern.
# Every entry must state why it is committed on purpose.
ALLOWLIST: dict[str, str] = {
    # Served by rex-gui at /ui/ (rex/gui_app.py) so the developer-only
    # dashboard works from a plain `pip install .` without a Node build.
    "rex/ui/dist/index.html": "developer dashboard served by rex-gui",
}

GENERATED_PATTERNS: list[tuple[re.Pattern[str], str]] = [
    (re.compile(r"(^|/)\.coverage($|\.)"), "coverage data file (.coverage)"),
    (re.compile(r"(^|/)coverage\.(xml|txt|html|json)$"), "coverage report"),
    (re.compile(r"(^|/)htmlcov/"), "HTML coverage report directory"),
    (re.compile(r"(^|/)dist/"), "build distribution directory (dist/)"),
    (re.compile(r"(^|/)build/"), "build output directory (build/)"),
    (re.compile(r"(^|/)__pycache__/"), "Python bytecode cache (__pycache__/)"),
    (re.compile(r"\.py[co]$"), "compiled Python bytecode (*.pyc / *.pyo)"),
    (re.compile(r"(^|/)[^/]+\.egg-info/"), "setuptools metadata (*.egg-info/)"),
]


def classify_paths(paths: list[str]) -> list[tuple[str, str]]:
    """Return (path, description) for every generated artifact in *paths*."""
    offenders: list[tuple[str, str]] = []
    for path in paths:
        if not path or path in ALLOWLIST:
            continue
        for pattern, description in GENERATED_PATTERNS:
            if pattern.search(path):
                offenders.append((path, description))
                break
    return offenders


def find_tracked_generated_artifacts() -> list[tuple[str, str]]:
    """Return (path, description) for every tracked generated artifact."""
    result = subprocess.run(
        ["git", "ls-files", "-z"],
        capture_output=True,
        check=True,
        text=True,
    )
    return classify_paths(result.stdout.split("\0"))


def main() -> int:
    offenders = find_tracked_generated_artifacts()
    if offenders:
        print("ERROR: generated artifacts are committed to the repository:")
        for path, description in offenders:
            print(f"  {path}  ({description})")
        print(
            "Remove them with 'git rm --cached <path>' and ensure .gitignore " "covers the pattern."
        )
        return 1
    print("OK: no generated artifacts are tracked.")
    return 0


if __name__ == "__main__":
    sys.exit(main())