Run fast repository quality checks.
| 185 | |
| 186 | |
| 187 | class QualityGate: |
| 188 | """Run fast repository quality checks.""" |
| 189 | |
| 190 | def __init__(self, repo_root: Path | str, project_path: Path | str | None = None) -> None: |
| 191 | self.repo_root = Path(repo_root).resolve() |
| 192 | self.project_path = self._normalize_project_path(project_path) |
| 193 | |
| 194 | def _normalize_project_path(self, project_path: Path | str | None) -> Path | None: |
| 195 | if project_path is None: |
| 196 | return None |
| 197 | raw_path = Path(project_path) |
| 198 | full_path = raw_path if raw_path.is_absolute() else self.repo_root / raw_path |
| 199 | try: |
| 200 | relative_path = full_path.resolve().relative_to(self.repo_root) |
| 201 | except ValueError as exc: |
| 202 | raise ValueError(f"project path must be inside repo root: {project_path}") from exc |
| 203 | return relative_path |
| 204 | |
| 205 | def run(self) -> list[Finding]: |
| 206 | findings: list[Finding] = [] |
| 207 | findings.extend(self.check_tracked_ignored_files()) |
| 208 | findings.extend(self.check_secret_patterns()) |
| 209 | findings.extend(self.check_syntax()) |
| 210 | findings.extend(self.check_node_package_locks()) |
| 211 | return findings |
| 212 | |
| 213 | def git_files(self, *patterns: str) -> list[Path]: |
| 214 | cmd = ["git", "ls-files", *patterns] |
| 215 | result = subprocess.run( |
| 216 | cmd, |
| 217 | cwd=self.repo_root, |
| 218 | check=True, |
| 219 | capture_output=True, |
| 220 | text=True, |
| 221 | ) |
| 222 | paths = [Path(line) for line in result.stdout.splitlines() if line] |
| 223 | if self.project_path is None: |
| 224 | return paths |
| 225 | return [path for path in paths if path == self.project_path or self.project_path in path.parents] |
| 226 | |
| 227 | def check_tracked_ignored_files(self) -> list[Finding]: |
| 228 | result = subprocess.run( |
| 229 | ["git", "ls-files", "-ci", "--exclude-standard"], |
| 230 | cwd=self.repo_root, |
| 231 | check=True, |
| 232 | capture_output=True, |
| 233 | text=True, |
| 234 | ) |
| 235 | return [ |
| 236 | Finding( |
| 237 | check="tracked-ignored", |
| 238 | path=Path(path), |
| 239 | message=( |
| 240 | "file is tracked by git but now matches .gitignore; " |
| 241 | "remove it from the index or adjust .gitignore" |
| 242 | ), |
| 243 | ) |
| 244 | for path in result.stdout.splitlines() |
no outgoing calls