diff --git a/scripts/check_architecture_budgets.py b/scripts/check_architecture_budgets.py new file mode 100644 index 00000000..13073de7 --- /dev/null +++ b/scripts/check_architecture_budgets.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + +LEGACY_MAX_BYTES = { + "backend/app/services/dataset_service.py": 138_951, + "backend/app/services/official_vector_acquisition_service.py": 85_921, + "backend/app/services/data_contract_validation.py": 85_783, + "backend/app/services/detection_service.py": 61_248, + "frontend/src/WorkbenchApp.tsx": 64_092, + "frontend/src/types.ts": 46_900, +} + +SERVICE_MAX_BYTES = 60_000 +ROUTE_MAX_BYTES = 45_000 +TSX_COMPONENT_MAX_BYTES = 45_000 + + +def _check_file(relative: str, max_bytes: int, violations: list[str]) -> None: + path = ROOT / relative + if not path.is_file(): + violations.append(f"{relative}: expected architecture-budget file is missing") + return + size = path.stat().st_size + if size > max_bytes: + violations.append(f"{relative}: {size} bytes exceeds budget {max_bytes}") + + +def main() -> int: + violations: list[str] = [] + for relative, max_bytes in LEGACY_MAX_BYTES.items(): + _check_file(relative, max_bytes, violations) + + legacy = set(LEGACY_MAX_BYTES) + for path in (ROOT / "backend/app/services").glob("*.py"): + relative = path.relative_to(ROOT).as_posix() + if relative not in legacy and path.stat().st_size > SERVICE_MAX_BYTES: + violations.append( + f"{relative}: {path.stat().st_size} bytes exceeds service budget {SERVICE_MAX_BYTES}" + ) + + for path in (ROOT / "backend/app/api/routes").glob("*.py"): + relative = path.relative_to(ROOT).as_posix() + if relative not in legacy and path.stat().st_size > ROUTE_MAX_BYTES: + violations.append( + f"{relative}: {path.stat().st_size} bytes exceeds route budget {ROUTE_MAX_BYTES}" + ) + + for path in (ROOT / "frontend/src/components").rglob("*.tsx"): + relative = path.relative_to(ROOT).as_posix() + if relative not in legacy and path.stat().st_size > TSX_COMPONENT_MAX_BYTES: + violations.append( + f"{relative}: {path.stat().st_size} bytes exceeds component budget {TSX_COMPONENT_MAX_BYTES}" + ) + + if violations: + print("Architecture budget violations:", file=sys.stderr) + for violation in violations: + print(f" - {violation}", file=sys.stderr) + return 1 + + print("Architecture budgets passed.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())