SimpleChatbot / scripts /generate_dependency_inventory.py
Amin
Deploy: HermesFace finalized project to HF Space
2e658e7
Raw
History Blame Contribute Delete
4.28 kB
#!/usr/bin/env python3
"""Generate a deterministic dependency/build-input inventory.
The inventory is intentionally offline and reproducible. It records only inputs
that are committed in this repository: exact Python pins, container image tags,
the immutable upstream commit, and runtime version declarations. External
vulnerability scanners consume this inventory in CI, but generating it never
requires network access.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
def _noncomment_lines(path: Path) -> list[str]:
return [
line.strip()
for line in path.read_text(encoding="utf-8").splitlines()
if line.strip() and not line.lstrip().startswith("#")
]
def build_inventory(root: Path = ROOT) -> dict[str, Any]:
dockerfile = (root / "Dockerfile").read_text(encoding="utf-8")
requirements = _noncomment_lines(root / "requirements.lock")
ci_requirements = _noncomment_lines(root / "requirements-ci.lock")
python_version = (root / ".python-version").read_text(encoding="utf-8").strip()
package_json = json.loads((root / "package.json").read_text(encoding="utf-8"))
package_lock = json.loads((root / "package-lock.json").read_text(encoding="utf-8"))
images = []
for raw in re.findall(r"^FROM\s+([^\s]+)", dockerfile, re.MULTILINE):
images.append(raw)
upstream = re.search(
r"^ARG HERMES_UPSTREAM_REF=([0-9a-f]{40})$", dockerfile, re.MULTILINE
)
if not upstream:
raise ValueError("Dockerfile does not contain an immutable upstream commit")
for requirement in [*requirements, *ci_requirements]:
if "==" not in requirement or any(op in requirement for op in (">=", "<=", "~=", "!=", "*")):
raise ValueError(f"non-exact dependency pin: {requirement}")
return {
"schemaVersion": 1,
"project": "Hermes Futures Desk V5",
"runtime": {
"python": python_version,
"node": package_json["engines"]["node"],
"npm": package_json["engines"]["npm"],
},
"upstream": {
"repository": "https://github.com/NousResearch/hermes-agent.git",
"commit": upstream.group(1),
},
"pythonDependencies": sorted(requirements),
"ciPythonDependencies": sorted(ci_requirements),
"containerImages": images,
"nodeDependencyPolicy": {
"upstreamLockfile": "package-lock.json",
"installCommand": "npm ci --prefer-offline --no-audit",
"localLockfile": "package-lock.json",
"localLockfileVersion": package_lock.get("lockfileVersion"),
"localNodeDependencies": sorted((package_json.get("dependencies") or {}).items()),
"localNodeDevDependencies": sorted((package_json.get("devDependencies") or {}).items()),
},
}
def render(inventory: dict[str, Any]) -> str:
return json.dumps(inventory, indent=2, sort_keys=True) + "\n"
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", default=str(ROOT))
parser.add_argument("--output")
parser.add_argument("--check", action="store_true")
args = parser.parse_args(argv)
root = Path(args.root).resolve()
output = Path(args.output).resolve() if args.output else root / "docs" / "dependency_inventory.json"
try:
rendered = render(build_inventory(root))
except (OSError, ValueError) as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 1
if args.check:
try:
current = output.read_text(encoding="utf-8")
except OSError as exc:
print(f"ERROR: dependency inventory missing: {exc}", file=sys.stderr)
return 1
if current != rendered:
print("ERROR: dependency inventory is stale", file=sys.stderr)
return 1
print(f"dependency_inventory=PASS path={output}")
return 0
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(rendered, encoding="utf-8")
print(f"wrote {output}")
return 0
if __name__ == "__main__":
raise SystemExit(main())