#!/usr/bin/env python3 """Validate the local repository structure used to package the integration.""" from __future__ import annotations import json from pathlib import Path from typing import Any REPOSITORY_ROOT = Path(__file__).resolve().parent.parent CUSTOM_COMPONENTS = REPOSITORY_ROOT / "custom_components" def load_json(path: Path) -> dict[str, Any]: try: value = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as error: raise ValueError(f"Could not read valid JSON from {path.relative_to(REPOSITORY_ROOT)}: {error}") from error if not isinstance(value, dict): raise ValueError(f"Expected a JSON object in {path.relative_to(REPOSITORY_ROOT)}") return value def main() -> None: integration_directories = sorted( path for path in CUSTOM_COMPONENTS.iterdir() if path.is_dir() and path.name != "__pycache__" ) if len(integration_directories) != 1: names = ", ".join(path.name for path in integration_directories) or "none" raise ValueError(f"Expected exactly one integration under custom_components; found: {names}") integration_directory = integration_directories[0] manifest = load_json(integration_directory / "manifest.json") required_manifest_fields = { "codeowners", "documentation", "domain", "issue_tracker", "name", "version", } missing_fields = sorted(required_manifest_fields - manifest.keys()) if missing_fields: raise ValueError(f"manifest.json is missing required fields: {', '.join(missing_fields)}") if manifest["domain"] != integration_directory.name: raise ValueError("manifest.json domain must match its custom_components directory") if not isinstance(manifest["codeowners"], list) or not manifest["codeowners"]: raise ValueError("manifest.json codeowners must be a non-empty list") if not (integration_directory / "brand" / "icon.png").is_file(): raise ValueError("The integration must include brand/icon.png") hacs_manifest = load_json(REPOSITORY_ROOT / "hacs.json") if not isinstance(hacs_manifest.get("name"), str) or not hacs_manifest["name"].strip(): raise ValueError("hacs.json must include a non-empty name") if hacs_manifest.get("zip_release") is True: filename = hacs_manifest.get("filename") if not isinstance(filename, str) or not filename.endswith(".zip"): raise ValueError("hacs.json zip_release requires a .zip filename") if not (REPOSITORY_ROOT / "README.md").is_file(): raise ValueError("The repository must include README.md") print(f"Repository structure is valid for integration {manifest['domain']} {manifest['version']}.") if __name__ == "__main__": main()