Return metadata validation errors.
(repo_root: Path, metadata: dict | None = None)
| 82 | |
| 83 | |
| 84 | def validate_metadata(repo_root: Path, metadata: dict | None = None) -> list[str]: |
| 85 | """Return metadata validation errors.""" |
| 86 | |
| 87 | metadata = metadata or load_metadata(repo_root) |
| 88 | errors: list[str] = [] |
| 89 | roots = metadata.get("project_roots") |
| 90 | if not isinstance(roots, list) or not roots: |
| 91 | errors.append("metadata must define a non-empty project_roots list") |
| 92 | return errors |
| 93 | |
| 94 | for index, root in enumerate(roots): |
| 95 | if not isinstance(root, dict): |
| 96 | errors.append(f"project_roots[{index}] must be an object") |
| 97 | continue |
| 98 | pattern = root.get("glob") |
| 99 | if not isinstance(pattern, str) or not pattern: |
| 100 | errors.append(f"project_roots[{index}].glob must be a non-empty string") |
| 101 | continue |
| 102 | if Path(pattern).is_absolute() or ".." in Path(pattern).parts: |
| 103 | errors.append( |
| 104 | f"project_roots[{index}].glob must stay inside the repository" |
| 105 | ) |
| 106 | |
| 107 | conventions = metadata.get("learner_metadata", {}) |
| 108 | if conventions: |
| 109 | if not isinstance(conventions, dict): |
| 110 | errors.append("learner_metadata must be an object when provided") |
| 111 | else: |
| 112 | paths = conventions.get("learning_paths", []) |
| 113 | badges = conventions.get("badges", []) |
| 114 | if not isinstance(paths, list) or set(paths) - set(LEARNING_PATHS): |
| 115 | errors.append( |
| 116 | "learner_metadata.learning_paths must use the supported learning path tags" |
| 117 | ) |
| 118 | if not isinstance(badges, list) or set(badges) - set(LEARNER_BADGES): |
| 119 | errors.append( |
| 120 | "learner_metadata.badges must use the supported learner badge tags" |
| 121 | ) |
| 122 | |
| 123 | if not discover_projects(repo_root, metadata): |
| 124 | errors.append("metadata did not discover any projects") |
| 125 | return errors |
| 126 | |
| 127 | |
| 128 | def changed_files(repo_root: Path, base_ref: str, head_ref: str = "HEAD") -> list[str]: |