Sets up the Step 1 (Project Foundation) and Step 2 (Content Schemas) scaffold from the implementation sequence. Includes Docker Compose stack, Nakama TypeScript module stubs, PostgreSQL migration, Godot 4 project shell with scene stubs and NakamaClient singleton, YAML content schemas for all entity types, and example data for 3 characters, 2 jobs, and all 7 core resources. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
127 lines
3.5 KiB
Python
127 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""validate_content.py — Validate all content YAML files against their schemas.
|
|
|
|
Usage:
|
|
python3 tools/validate_content.py
|
|
|
|
Requires:
|
|
pip install pyyaml jsonschema
|
|
|
|
Exit codes:
|
|
0 — all files valid
|
|
1 — one or more validation errors
|
|
"""
|
|
|
|
import sys
|
|
import pathlib
|
|
import yaml
|
|
import json
|
|
|
|
try:
|
|
import jsonschema
|
|
except ImportError:
|
|
print("ERROR: jsonschema not installed. Run: pip install pyyaml jsonschema")
|
|
sys.exit(1)
|
|
|
|
CONTENT_ROOT = pathlib.Path(__file__).parent.parent / "content"
|
|
SCHEMA_DIR = CONTENT_ROOT / "schema"
|
|
DATA_DIR = CONTENT_ROOT / "data"
|
|
|
|
# Map schema file names to data subdirectories
|
|
SCHEMA_DATA_MAP = {
|
|
"character.yaml": DATA_DIR / "characters",
|
|
"job.yaml": DATA_DIR / "jobs",
|
|
"resource.yaml": DATA_DIR / "resources",
|
|
"banner.yaml": DATA_DIR / "banners",
|
|
"event.yaml": DATA_DIR / "events",
|
|
}
|
|
|
|
errors_found = 0
|
|
|
|
|
|
def load_yaml(path: pathlib.Path) -> list[dict]:
|
|
"""Load a YAML file, returning a list of documents (multi-doc files are split)."""
|
|
with open(path) as f:
|
|
docs = list(yaml.safe_load_all(f))
|
|
return [d for d in docs if d is not None]
|
|
|
|
|
|
def yaml_schema_to_jsonschema(schema_doc: dict) -> dict:
|
|
"""
|
|
Our schema files are JSON-Schema-like YAML. Extract the JSON Schema portion.
|
|
We strip the custom '$schema' key and pass the rest to jsonschema.
|
|
"""
|
|
s = dict(schema_doc)
|
|
s.pop("$schema", None)
|
|
return s
|
|
|
|
|
|
def validate_directory(schema_path: pathlib.Path, data_dir: pathlib.Path) -> int:
|
|
"""Validate all YAML files in data_dir against schema_path. Returns error count."""
|
|
global errors_found
|
|
|
|
if not data_dir.exists():
|
|
print(f" [SKIP] Directory not found: {data_dir}")
|
|
return 0
|
|
|
|
schema_docs = load_yaml(schema_path)
|
|
if not schema_docs:
|
|
print(f" [WARN] Empty schema: {schema_path.name}")
|
|
return 0
|
|
|
|
schema = yaml_schema_to_jsonschema(schema_docs[0])
|
|
|
|
local_errors = 0
|
|
yaml_files = sorted(data_dir.glob("*.yaml"))
|
|
|
|
if not yaml_files:
|
|
print(f" [INFO] No data files in {data_dir.name}/")
|
|
return 0
|
|
|
|
for data_file in yaml_files:
|
|
docs = load_yaml(data_file)
|
|
for i, doc in enumerate(docs):
|
|
try:
|
|
# jsonschema uses JSON Schema draft-07 by default
|
|
jsonschema.validate(instance=doc, schema=schema)
|
|
except jsonschema.ValidationError as e:
|
|
print(f" [FAIL] {data_file.name} (doc {i+1}): {e.message}")
|
|
local_errors += 1
|
|
except jsonschema.SchemaError as e:
|
|
print(f" [SCHEMA ERROR] {schema_path.name}: {e.message}")
|
|
local_errors += 1
|
|
break
|
|
|
|
if local_errors == 0:
|
|
pass # silent success; only print failures
|
|
|
|
if local_errors == 0:
|
|
print(f" [OK] {data_dir.name}/ — {len(yaml_files)} file(s) valid")
|
|
|
|
return local_errors
|
|
|
|
|
|
def main() -> None:
|
|
print("Xyvera content validation\n")
|
|
|
|
for schema_name, data_dir in SCHEMA_DATA_MAP.items():
|
|
schema_path = SCHEMA_DIR / schema_name
|
|
if not schema_path.exists():
|
|
print(f" [WARN] Schema not found: {schema_name}")
|
|
continue
|
|
|
|
print(f"Schema: {schema_name}")
|
|
errs = validate_directory(schema_path, data_dir)
|
|
errors_found += errs
|
|
|
|
print()
|
|
if errors_found:
|
|
print(f"FAILED — {errors_found} validation error(s) found.")
|
|
sys.exit(1)
|
|
else:
|
|
print("All content valid.")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|