Files
backup-tool/tools/forbidden_v1_scan.py

97 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Reject executable v1 compatibility paths and symbols from the v2 tree."""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
LEGACY_PATHS = (
Path("backend/app"),
Path("backend/backup"),
Path("backend/tests"),
Path("frontend/src/api/client.ts"),
)
SCAN_ROOTS = (
Path("backend/src"),
Path("frontend/src"),
Path("openapi"),
Path("config"),
Path(".github/workflows"),
)
SCAN_FILES = (
Path("backend/Dockerfile"),
Path("frontend/Dockerfile"),
Path("frontend/nginx.conf"),
Path("frontend/vite.config.ts"),
Path("docker-compose.yml"),
Path("compose.yaml"),
Path("backend/pyproject.toml"),
Path("frontend/package.json"),
)
TEXT_SUFFIXES = {".json", ".py", ".toml", ".ts", ".tsx", ".yaml", ".yml"}
FORBIDDEN = (
re.compile(r"/api/v1(?:/|\b)"),
re.compile(
r"\b(?:legacy|v1)[_-]?(?:database|db|payload|backup)?[_-]?(?:reader|importer?|converter?)\b",
re.IGNORECASE,
),
re.compile(
r"\b(?:read|open|load|import|convert)[_-]?(?:legacy|v1)[_-]?(?:database|db|payload|backup)\b",
re.IGNORECASE,
),
re.compile(r"[\"']backup_tool\.db[\"']", re.IGNORECASE),
re.compile(r"%Y-%m-%d_%H%M%S"),
re.compile(r"\btimestamp[_-]?(?:directory|parser)\b", re.IGNORECASE),
re.compile(r"\b(?:app\.main|backup\.engine)\b"),
)
def scan(root: Path) -> list[str]:
findings: list[str] = []
for relative in LEGACY_PATHS:
if (root / relative).exists():
findings.append(f"legacy path exists: {relative}")
candidates = {root / relative for relative in SCAN_FILES if (root / relative).is_file()}
for relative_root in SCAN_ROOTS:
scan_root = root / relative_root
if scan_root.exists():
candidates.update(
path
for path in scan_root.rglob("*")
if path.is_file() and path.suffix in TEXT_SUFFIXES
)
for path in sorted(candidates):
try:
text = path.read_text(encoding="utf-8")
except (OSError, UnicodeError) as error:
findings.append(f"cannot read {path.relative_to(root)}: {error}")
continue
for pattern in FORBIDDEN:
if pattern.search(text):
findings.append(f"forbidden symbol {pattern.pattern!r}: {path.relative_to(root)}")
return findings
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("root", nargs="?", default=".", type=Path)
args = parser.parse_args()
root = args.root.resolve()
findings = scan(root)
if findings:
print("v1 compatibility scan failed:", file=sys.stderr)
for finding in findings:
print(f"- {finding}", file=sys.stderr)
return 1
print("v1 compatibility scan: OK")
return 0
if __name__ == "__main__":
raise SystemExit(main())