99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Check whether source extension files still match a 1C patch workspace manifest."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
def load_json(path: Path) -> dict[str, Any]:
|
|
return json.loads(path.read_text(encoding="utf-8-sig"))
|
|
|
|
|
|
def sha256_file(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as stream:
|
|
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def issue(severity: str, code: str, message: str, *, record: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
result = {"severity": severity, "code": code, "message": message}
|
|
if record:
|
|
result["record"] = record
|
|
return result
|
|
|
|
|
|
def check_workspace_sources(workspace: Path) -> dict[str, Any]:
|
|
manifest_path = workspace / "manifest.json"
|
|
findings = []
|
|
file_checks = []
|
|
if not manifest_path.exists():
|
|
findings.append(issue("error", "missing_manifest", f"Workspace manifest.json is missing: {manifest_path}"))
|
|
return result(workspace, findings, file_checks)
|
|
manifest = load_json(manifest_path)
|
|
for record in manifest.get("files") or []:
|
|
source = Path(str(record.get("source_path") or ""))
|
|
expected = record.get("sha256")
|
|
check = {
|
|
"relative_path": record.get("relative_path"),
|
|
"source_path": str(source),
|
|
"expected_sha256": expected,
|
|
"source_exists": source.exists(),
|
|
}
|
|
if not source.exists():
|
|
findings.append(issue("error", "source_missing", f"Source file is missing: {source}", record=record))
|
|
file_checks.append(check)
|
|
continue
|
|
actual = sha256_file(source)
|
|
check["source_sha256"] = actual
|
|
check["fresh"] = actual == expected
|
|
if expected and actual != expected:
|
|
findings.append(issue("error", "source_hash_mismatch", "Source file changed since patch workspace creation.", record={**record, "current_sha256": actual}))
|
|
file_checks.append(check)
|
|
return result(workspace, findings, file_checks, manifest=manifest)
|
|
|
|
|
|
def result(workspace: Path, findings: list[dict[str, Any]], file_checks: list[dict[str, Any]], manifest: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
errors = [row for row in findings if row.get("severity") == "error"]
|
|
warnings = [row for row in findings if row.get("severity") == "warning"]
|
|
return {
|
|
"schema": "onec_patch_source_freshness.v1",
|
|
"workspace": str(workspace),
|
|
"manifest_schema": (manifest or {}).get("schema"),
|
|
"passed": not errors,
|
|
"findings": findings,
|
|
"file_checks": file_checks,
|
|
"counts": {
|
|
"files": len(file_checks),
|
|
"errors": len(errors),
|
|
"warnings": len(warnings),
|
|
"stale": len([row for row in file_checks if row.get("fresh") is False]),
|
|
"missing": len([row for row in file_checks if not row.get("source_exists")]),
|
|
},
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Check 1C patch source freshness.")
|
|
parser.add_argument("--workspace", type=Path, required=True)
|
|
parser.add_argument("--output", type=Path)
|
|
args = parser.parse_args()
|
|
|
|
check = check_workspace_sources(args.workspace)
|
|
output = json.dumps(check, ensure_ascii=False, indent=2) + "\n"
|
|
if args.output:
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(output, encoding="utf-8")
|
|
print(json.dumps({"output": str(args.output) if args.output else None, "passed": check["passed"], "counts": check["counts"]}, ensure_ascii=False))
|
|
return 0 if check["passed"] else 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|