← Files Aivana Dataverse App ArchitectARCHIVED FILE

scripts/validate_app_blueprint.py

3.41 KB · Sep 30, 2026 · 23:15 UTC

↓ Download file

#!/usr/bin/env python3
"""Offline quality gates for a Dataverse model-driven app blueprint."""
import argparse
import json
from pathlib import Path
from blueprint_schema import validate_blueprint


def present(data, path):
    value = data
    for key in path.split("."):
        if not isinstance(value, dict) or not value.get(key):
            return False
        value = value[key]
    return True


def main():
    parser = argparse.ArgumentParser(description="Validate a Dataverse app blueprint")
    parser.add_argument("blueprint", type=Path)
    parser.add_argument("--json", action="store_true", dest="as_json")
    args = parser.parse_args()
    try:
        data = json.loads(args.blueprint.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as exc:
        print(f"ERROR: Cannot read blueprint: {exc}")
        return 2

    errors, warnings = validate_blueprint(data), []
    if errors:
        print(json.dumps({"valid": False, "errors": errors, "warnings": []}, indent=2))
        return 1
    required = {
        "G0 Scope": ["appName", "business.outcome", "business.successMetrics", "personas"],
        "G1 Data": ["solution.uniqueName", "solution.publisherPrefix", "dataModel.tables"],
        "G2 Security": ["security.leastPrivilegeReviewed", "security.roleTaskMatrix"],
        "G3 UX": ["navigation", "forms", "views"],
        "G5 Performance": ["performance.expectedRecordVolume", "performance.representativeDataTest"],
        "G6 Change": ["change.impactReviewed", "change.rollbackPlan"],
        "G7 ALM": ["alm.devUnmanaged", "alm.downstreamManaged", "alm.repositoryExport", "alm.version"],
    }
    for gate, paths in required.items():
        missing = [path for path in paths if not present(data, path)]
        if missing:
            errors.append(f"{gate}: fehlend/leer: {', '.join(missing)}")

    if not present(data, "testing.keyboardTest") or not present(data, "testing.responsiveTest"):
        errors.append("G4 Accessibility: keyboardTest und responsiveTest müssen geplant sein")
    if data.get("customPages") and not present(data, "testing.accessibilityPlan"):
        errors.append("G4 Accessibility: Custom Pages benötigen testing.accessibilityPlan")
    for form in data.get("forms", []):
        fields = form.get("primaryTaskFields", [])
        if len(fields) > 12:
            warnings.append(f"Form '{form.get('name', '?')}' enthält {len(fields)} Felder auf dem ersten Tab; Aufgabenfokus prüfen")
    for view in data.get("views", []):
        columns = view.get("columns", [])
        if len(columns) > 10:
            warnings.append(f"View '{view.get('name', '?')}' enthält {len(columns)} Spalten; Scanbarkeit prüfen")
    for table in data.get("dataModel", {}).get("tables", []):
        name = table.get("logicalName", "")
        if name.startswith("new_"):
            warnings.append(f"Tabelle '{name}' nutzt das generische Präfix 'new_'; bestätigten Publisher-Präfix verwenden")

    result = {"valid": not errors, "errors": errors, "warnings": warnings}
    if args.as_json:
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        print("Blueprint Quality Gates: " + ("PASS" if result["valid"] else "FAIL"))
        for message in errors:
            print("ERROR: " + message)
        for message in warnings:
            print("WARNING: " + message)
    return 0 if result["valid"] else 1


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: 08418a3a81750ff7e1110421ada1607c4825b738d8d0fe2588794f8d1131ee55