70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
#!/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()
|