-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidate-lexicons.mjs
More file actions
107 lines (92 loc) · 2.99 KB
/
validate-lexicons.mjs
File metadata and controls
107 lines (92 loc) · 2.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
import { Lexicons } from '@atproto/lexicon';
const LEXICON_DIR = './lexicons/pub/layers';
function findLexiconFiles(dir) {
const results = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) results.push(...findLexiconFiles(fullPath));
else if (entry.name.endsWith('.json')) results.push(fullPath);
}
return results.sort();
}
const files = findLexiconFiles(LEXICON_DIR);
console.log(`Found ${files.length} lexicon files\n`);
// Phase 1: Parse JSON and validate each doc against the lexiconDoc zod schema
const docs = [];
let parseErrors = 0;
for (const path of files) {
const raw = JSON.parse(readFileSync(path, 'utf8'));
docs.push({ file: path, doc: raw });
}
// Phase 2: Add all to Lexicons registry (validates cross-references between lexicons)
const lexicons = new Lexicons();
let addErrors = 0;
for (const { file, doc } of docs) {
try {
lexicons.add(doc);
console.log(` ✓ ${doc.id}`);
} catch (e) {
addErrors++;
console.error(` ✗ ${file} (${doc.id}):`);
console.error(` ${e.message}`);
}
}
// Phase 3: Verify all cross-references resolve
console.log(`\n--- Checking cross-references ---\n`);
let refErrors = 0;
for (const { file, doc } of docs) {
for (const [defName, def] of Object.entries(doc.defs)) {
const uri = defName === 'main' ? doc.id : `${doc.id}#${defName}`;
const resolved = lexicons.getDef(uri);
if (!resolved) {
console.error(` ✗ Could not resolve def: ${uri}`);
refErrors++;
}
}
}
// Walk all ref fields to check they resolve
function findRefs(obj, path, docId) {
if (!obj || typeof obj !== 'object') return;
if (Array.isArray(obj)) {
obj.forEach((item, i) => findRefs(item, `${path}[${i}]`, docId));
return;
}
if (obj.type === 'ref' && obj.ref) {
const refUri = obj.ref.startsWith('#') ? `${docId}${obj.ref}` : obj.ref;
try {
const resolved = lexicons.getDef(refUri);
if (!resolved) {
console.error(` ✗ Unresolved ref: ${obj.ref} at ${path}`);
refErrors++;
}
} catch (e) {
console.error(` ✗ Error resolving ref ${obj.ref} at ${path}: ${e.message}`);
refErrors++;
}
}
for (const [key, val] of Object.entries(obj)) {
findRefs(val, `${path}.${key}`, docId);
}
}
for (const { file, doc } of docs) {
for (const [defName, def] of Object.entries(doc.defs)) {
findRefs(def, `${doc.id}#${defName}`, doc.id);
}
}
if (refErrors === 0) {
console.log(` ✓ All cross-references resolve`);
}
// Summary
const totalErrors = addErrors + refErrors;
console.log(`\n--- Summary ---`);
console.log(` Files: ${files.length}`);
console.log(` Lexicon validation errors: ${addErrors}`);
console.log(` Cross-reference errors: ${refErrors}`);
if (totalErrors > 0) {
console.log(`\n ${totalErrors} error(s) found.`);
process.exit(1);
} else {
console.log(`\n All lexicons valid!`);
}