Files
main_plugin_farm/scripts/check_baseline.py
T
2026-10-15 14:14:58 +08:00

118 lines
4.1 KiB
Python

"""Validate the preserved legacy source without starting a bot."""
import argparse
import ast
from contextlib import closing
import hashlib
import json
from pathlib import Path, PurePosixPath
import sqlite3
import sys
def check(root):
manifest = json.loads(
(root / "docs/legacy-manifest.json").read_text(encoding="utf-8")
)
if manifest.get("schema") != "farm.legacy-baseline.v1":
raise ValueError("Unsupported baseline manifest schema")
baseline = (root / manifest["destination"]).resolve()
if not baseline.is_relative_to(root.resolve()):
raise ValueError("Baseline destination escapes repository")
errors = []
expected = set()
python_count = 0
json_count = 0
for entry in manifest["files"]:
name = entry["path"]
relative = PurePosixPath(name)
if relative.is_absolute() or ".." in relative.parts:
raise ValueError("Invalid manifest path")
if name in expected:
raise ValueError("Duplicate manifest path")
expected.add(name)
path = (baseline / name).resolve()
if not path.is_relative_to(baseline):
raise ValueError("Manifest path escapes baseline")
if not path.is_file():
errors.append({"path": name, "error": "missing file"})
continue
content = path.read_bytes()
digest = hashlib.sha256(content).hexdigest()
if digest != entry["copied_sha256"]:
errors.append({"path": name, "error": "SHA-256 mismatch"})
if len(content) != entry["bytes"]:
errors.append({"path": name, "error": "size mismatch"})
if path.suffix == ".py":
python_count += 1
try:
ast.parse(content, filename=name, feature_version=(3, 10))
except SyntaxError as exc:
errors.append(
{"path": name, "error": "syntax", "line": exc.lineno}
)
if path.suffix == ".json":
json_count += 1
try:
json.loads(content)
except (ValueError, UnicodeError):
errors.append({"path": name, "error": "invalid JSON"})
actual = {
path.relative_to(baseline).as_posix()
for path in baseline.rglob("*")
if path.is_file()
and "__pycache__" not in path.parts
and path.suffix != ".pyc"
}
for name in sorted(actual - expected):
errors.append({"path": name, "error": "unmanifested file"})
catalogues = []
for name in ("plant.db", "plant-test.db"):
path = baseline / "resource/db" / name
with closing(
sqlite3.connect(
path.resolve().as_uri() + "?mode=ro", uri=True
)
) as connection:
integrity = connection.execute("PRAGMA quick_check").fetchone()[0]
tables = sorted(
row[0]
for row in connection.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
)
)
if integrity != "ok" or tables != ["plant", "sqlite_sequence"]:
errors.append({"path": name, "error": "unexpected catalogue"})
catalogues.append(
{"name": name, "quick_check": integrity, "tables": tables}
)
return {
"status": "passed" if not errors else "failed",
"files": len(expected),
"python_syntax_files": python_count,
"json_files": json_count,
"catalogues": catalogues,
"errors": errors,
"scope": "integrity and syntax only; no host or gameplay verified",
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--root",
type=Path,
default=Path(__file__).resolve().parents[1],
)
args = parser.parse_args()
try:
result = check(args.root.resolve())
except (OSError, ValueError, KeyError, sqlite3.Error) as exc:
result = {"status": "failed", "error_type": type(exc).__name__}
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0 if result["status"] == "passed" else 1
if __name__ == "__main__":
sys.exit(main())