mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
84381efab9
* feat(ci): add global hash skip planning * refactor(ci): remove superseded hash skip cache * refactor(ci): keep scope trust in planner * fix(ci): run scope goldens for planner changes Generated-By: looper 0.11.2 (runner=fixer, agent=codex) * fix(ci): publish hash state after successful validation Generated-By: looper 0.11.2 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com>
56 lines
1.5 KiB
Python
56 lines
1.5 KiB
Python
import json
|
|
from pathlib import Path
|
|
|
|
|
|
class ConfigError(ValueError):
|
|
pass
|
|
|
|
|
|
def repository_root(script_file: str) -> Path:
|
|
return Path(script_file).resolve().parents[2]
|
|
|
|
|
|
def load_json(path: Path):
|
|
try:
|
|
with path.open(encoding="utf-8") as source:
|
|
return json.load(source)
|
|
except (OSError, json.JSONDecodeError) as error:
|
|
raise ConfigError(f"cannot load {path}: {error}") from error
|
|
|
|
|
|
def object_value(value, label: str):
|
|
if not isinstance(value, dict):
|
|
raise ConfigError(f"{label} must be an object")
|
|
return value
|
|
|
|
|
|
def array_value(value, label: str):
|
|
if not isinstance(value, list):
|
|
raise ConfigError(f"{label} must be an array")
|
|
return value
|
|
|
|
|
|
def string_value(value, label: str):
|
|
if not isinstance(value, str) or not value:
|
|
raise ConfigError(f"{label} must be a non-empty string")
|
|
return value
|
|
|
|
|
|
def exact_keys(value: dict, expected: set[str], label: str):
|
|
actual = set(value)
|
|
if actual != expected:
|
|
missing = sorted(expected - actual)
|
|
extra = sorted(actual - expected)
|
|
raise ConfigError(f"{label} keys differ (missing={missing}, extra={extra})")
|
|
|
|
|
|
def schema_v1(value: dict, label: str):
|
|
schema = object_value(value.get("schema"), f"{label}.schema")
|
|
exact_keys(schema, {"version"}, f"{label}.schema")
|
|
if schema["version"] != 1:
|
|
raise ConfigError(f"{label}.schema.version must be 1")
|
|
|
|
|
|
def compact_json(value) -> str:
|
|
return json.dumps(value, separators=(",", ":"), sort_keys=True)
|