An experimental TypeSpec syntax for Lexicon
1#!/usr/bin/env node
2
3const fs = require('fs');
4const path = require('path');
5
6// Import validation from atproto lexicon package
7const atprotoPath = path.join(__dirname, '../../atproto/packages/lexicon');
8const { Lexicons } = require(path.join(atprotoPath, 'dist/index.js'));
9
10function findJsonFiles(dir, fileList = []) {
11 const files = fs.readdirSync(dir);
12
13 files.forEach(file => {
14 // Skip node_modules, tsp-output, and common non-lexicon directories
15 if (file === 'node_modules' || file === 'tsp-output' || file === 'dist' || file === '.git') {
16 return;
17 }
18
19 const filePath = path.join(dir, file);
20 const stat = fs.statSync(filePath);
21
22 if (stat.isDirectory()) {
23 findJsonFiles(filePath, fileList);
24 } else if (file.endsWith('.json') && !file.includes('package') && !file.includes('tsconfig')) {
25 fileList.push(filePath);
26 }
27 });
28
29 return fileList;
30}
31
32function validateLexicons() {
33 const packagesDir = path.join(__dirname, '..', 'packages');
34 const files = findJsonFiles(packagesDir);
35
36 if (files.length === 0) {
37 console.log('No lexicon files found');
38 return 0;
39 }
40
41 let errorCount = 0;
42 let validCount = 0;
43 let skippedCount = 0;
44 const seenIds = new Set();
45
46 for (const filePath of files) {
47 const relPath = path.relative(path.join(__dirname, '..'), filePath);
48
49 try {
50 const content = fs.readFileSync(filePath, 'utf-8');
51 const json = JSON.parse(content);
52
53 // Skip non-lexicon JSON files (e.g., package.json, tsconfig.json)
54 if (!json.lexicon || typeof json.lexicon !== 'number') {
55 continue;
56 }
57
58 // Skip duplicates (same ID already seen)
59 if (json.id && seenIds.has(json.id)) {
60 skippedCount++;
61 continue;
62 }
63
64 // Validate individually with a fresh Lexicons instance
65 // This avoids duplicate ID errors when validating independent lexicons
66 const lexicons = new Lexicons();
67 lexicons.add(json);
68
69 if (json.id) {
70 seenIds.add(json.id);
71 }
72
73 validCount++;
74 console.log(`✓ ${relPath}`);
75 } catch (error) {
76 errorCount++;
77 console.error(`✗ ${relPath}: ${error.message}`);
78 }
79 }
80
81 console.log(`\nValidated ${validCount} lexicons: ${validCount} valid, ${errorCount} errors${skippedCount > 0 ? `, ${skippedCount} duplicates skipped` : ''}`);
82
83 return errorCount > 0 ? 1 : 0;
84}
85
86try {
87 const exitCode = validateLexicons();
88 process.exit(exitCode);
89} catch (err) {
90 console.error('Fatal error:', err);
91 process.exit(1);
92}