Skip to content

Commit c26251d

Browse files
committed
feat: add JSON schema validation for registry and authors files
Add schema validation to the verification script using jsonschema library. Constrain registry categories to a predefined enum list. Update CI workflow to install jsonschema dependency.
1 parent 92760b6 commit c26251d

File tree

4 files changed

+108
-20
lines changed

4 files changed

+108
-20
lines changed

.github/registry_schema.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,21 @@
2626
"categories": {
2727
"type": "array",
2828
"items": {
29-
"type": "string"
29+
"type": "string",
30+
"enum": [
31+
"Agent Patterns",
32+
"Claude Agent SDK",
33+
"Evals",
34+
"Fine-Tuning",
35+
"Images",
36+
"Integrations",
37+
"Observability",
38+
"RAG & Retrieval",
39+
"Responses",
40+
"Skills",
41+
"Thinking",
42+
"Tools"
43+
]
3044
},
3145
"minItems": 1,
3246
"description": "Categories of the cookbook"
@@ -75,7 +89,7 @@
7589
"description": "Whether the cookbook is archived (optional, defaults to false)"
7690
}
7791
},
78-
"required": ["title", "path", "slug", "categories", "github_url", "authors", "date"],
92+
"required": ["title", "path", "categories", "authors", "date"],
7993
"additionalProperties": false
8094
}
8195
}

.github/scripts/verify_registry.py

Lines changed: 90 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,22 @@
1414
authors Verify GitHub handles and author URLs only
1515
paths Verify registry paths only
1616
registry Verify registry authors exist in authors.yaml
17+
schema Verify YAML files match their JSON schemas
1718
"""
1819

1920
import yaml
2021
import requests
2122
import sys
23+
import json
2224
from pathlib import Path
2325

26+
try:
27+
from jsonschema import validate, ValidationError
28+
29+
HAS_JSONSCHEMA = True
30+
except ImportError:
31+
HAS_JSONSCHEMA = False
32+
2433

2534
def check_github_handle(username):
2635
"""Check if a GitHub handle exists."""
@@ -38,7 +47,7 @@ def check_github_handle(username):
3847
def check_url(url):
3948
"""Check if a URL is accessible."""
4049
# Skip x.com URLs as they block HEAD requests
41-
if 'x.com' in url:
50+
if "x.com" in url:
4251
return True, "skipped (x.com)"
4352

4453
try:
@@ -64,16 +73,16 @@ def verify_authors(authors):
6473
failed_handles.append(f"{username} ({error})")
6574
print(f" ❌ {error}")
6675
else:
67-
print(f" ✓ OK")
76+
print(" ✓ OK")
6877

6978
# Verify URLs
7079
print("\n=== Verifying Author URLs ===\n")
7180
for username, details in authors.items():
7281
print(f"Checking URLs for {username}...")
7382

7483
# Check website URL
75-
if 'website' in details:
76-
url = details['website']
84+
if "website" in details:
85+
url = details["website"]
7786
success, error = check_url(url)
7887
if not success:
7988
failed_urls.append(f"{username}.website: {url} ({error})")
@@ -84,8 +93,8 @@ def verify_authors(authors):
8493
print(f" ✓ Website URL OK: {url}")
8594

8695
# Check avatar URL
87-
if 'avatar' in details:
88-
url = details['avatar']
96+
if "avatar" in details:
97+
url = details["avatar"]
8998
success, error = check_url(url)
9099
if not success:
91100
failed_urls.append(f"{username}.avatar: {url} ({error})")
@@ -105,8 +114,8 @@ def verify_registry_authors(registry, authors):
105114
print("\n=== Verifying Registry Authors ===\n")
106115
registry_authors = set()
107116
for entry in registry:
108-
if 'authors' in entry:
109-
for author in entry['authors']:
117+
if "authors" in entry:
118+
for author in entry["authors"]:
110119
registry_authors.add(author)
111120

112121
print(f"Found {len(registry_authors)} unique authors in registry.yaml")
@@ -129,28 +138,82 @@ def verify_paths(registry, repo_root):
129138
print(f"Found {len(registry)} cookbooks in registry.yaml")
130139

131140
for entry in registry:
132-
if 'path' in entry:
133-
path = entry['path']
141+
if "path" in entry:
142+
path = entry["path"]
134143
full_path = repo_root / path
135-
title = entry.get('title', 'Unknown')
144+
title = entry.get("title", "Unknown")
136145

137146
if not full_path.exists():
138147
missing_paths.append(f"{path} (title: {title})")
139148
print(f" ❌ Path not found: {path}")
140149
else:
141150
print(f" ✓ {path}")
142151
else:
143-
missing_paths.append(f"Entry missing 'path' field (title: {entry.get('title', 'Unknown')})")
152+
missing_paths.append(
153+
f"Entry missing 'path' field (title: {entry.get('title', 'Unknown')})"
154+
)
144155
print(f" ❌ Entry missing 'path' field: {entry.get('title', 'Unknown')}")
145156

146157
return missing_paths
147158

148159

160+
def verify_schemas(repo_root, authors, registry):
161+
"""Verify YAML files match their JSON schemas."""
162+
if not HAS_JSONSCHEMA:
163+
print("\n⚠️ Skipping schema validation (jsonschema not installed)")
164+
return []
165+
166+
schema_errors = []
167+
168+
print("\n=== Verifying JSON Schemas ===\n")
169+
170+
# Verify authors.yaml against schema
171+
authors_schema_path = repo_root / ".github" / "authors_schema.json"
172+
if authors_schema_path.exists():
173+
print("Checking authors.yaml against schema...")
174+
try:
175+
with open(authors_schema_path, "r") as f:
176+
authors_schema = json.load(f)
177+
validate(instance=authors, schema=authors_schema)
178+
print(" ✓ authors.yaml matches schema")
179+
except ValidationError as e:
180+
schema_errors.append(f"authors.yaml: {e.message} at {'.'.join(str(p) for p in e.path)}")
181+
print(f" ❌ authors.yaml schema validation failed: {e.message}")
182+
except json.JSONDecodeError as e:
183+
schema_errors.append(f"authors_schema.json: Invalid JSON - {e}")
184+
print(f" ❌ authors_schema.json is invalid: {e}")
185+
else:
186+
print(" ⊘ authors_schema.json not found, skipping")
187+
188+
# Verify registry.yaml against schema
189+
registry_schema_path = repo_root / ".github" / "registry_schema.json"
190+
if registry_schema_path.exists():
191+
print("\nChecking registry.yaml against schema...")
192+
try:
193+
with open(registry_schema_path, "r") as f:
194+
registry_schema = json.load(f)
195+
validate(instance=registry, schema=registry_schema)
196+
print(" ✓ registry.yaml matches schema")
197+
except ValidationError as e:
198+
path_str = ".".join(str(p) for p in e.path) if e.path else "root"
199+
schema_errors.append(f"registry.yaml: {e.message} at {path_str}")
200+
print(f" ❌ registry.yaml schema validation failed: {e.message}")
201+
if e.path:
202+
print(f" at path: {path_str}")
203+
except json.JSONDecodeError as e:
204+
schema_errors.append(f"registry_schema.json: Invalid JSON - {e}")
205+
print(f" ❌ registry_schema.json is invalid: {e}")
206+
else:
207+
print(" ⊘ registry_schema.json not found, skipping")
208+
209+
return schema_errors
210+
211+
149212
def main():
150213
# Parse command line argument
151214
command = sys.argv[1] if len(sys.argv) > 1 else "all"
152215

153-
if command not in ["all", "authors", "paths", "registry"]:
216+
if command not in ["all", "authors", "paths", "registry", "schema"]:
154217
print(f"Unknown command: {command}")
155218
print(__doc__)
156219
sys.exit(1)
@@ -163,19 +226,20 @@ def main():
163226
authors = None
164227
registry = None
165228

166-
if command in ["all", "authors", "registry"]:
167-
with open(authors_path, 'r') as f:
229+
if command in ["all", "authors", "registry", "schema"]:
230+
with open(authors_path, "r") as f:
168231
authors = yaml.safe_load(f)
169232

170-
if command in ["all", "paths", "registry"]:
171-
with open(registry_path, 'r') as f:
233+
if command in ["all", "paths", "registry", "schema"]:
234+
with open(registry_path, "r") as f:
172235
registry = yaml.safe_load(f)
173236

174237
# Run verifications based on command
175238
failed_handles = []
176239
failed_urls = []
177240
missing_authors = []
178241
missing_paths = []
242+
schema_errors = []
179243

180244
if command in ["all", "authors"]:
181245
failed_handles, failed_urls = verify_authors(authors)
@@ -186,6 +250,9 @@ def main():
186250
if command in ["all", "paths"]:
187251
missing_paths = verify_paths(registry, repo_root)
188252

253+
if command in ["all", "schema"]:
254+
schema_errors = verify_schemas(repo_root, authors, registry)
255+
189256
# Report results
190257
has_failures = False
191258

@@ -213,6 +280,12 @@ def main():
213280
print(f" - {path}")
214281
has_failures = True
215282

283+
if schema_errors:
284+
print("\n❌ The following schema validation errors occurred:")
285+
for error in schema_errors:
286+
print(f" - {error}")
287+
has_failures = True
288+
216289
if has_failures:
217290
sys.exit(1)
218291
else:

.github/workflows/verify-authors.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626

2727
- name: Install dependencies
2828
run: |
29-
pip install pyyaml requests
29+
pip install pyyaml requests jsonschema
3030
3131
- name: Verify authors and registry
3232
run: |

registry.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# yaml-language-server: $schema=./.github/registry_schema.json
12
- title: Prompting for Frontend Aesthetics
23
description: Guide to prompting Claude for distinctive, polished frontend designs
34
avoiding generic aesthetics.

0 commit comments

Comments
 (0)