File size: 3,199 Bytes
d9879cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { z } from 'zod';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const SuccessMustIncludeSchema = z.object({
    id: z.string(),
    desc: z.string(),
    weight: z.number()
});

const CriteriaSchema = z.object({
    version: z.string().optional(),
    type: z.string().optional(),
    goal: z.string().optional(),
    success: z.object({
        mustInclude: z.array(SuccessMustIncludeSchema),
        threshold: z.object({ minScore: z.number(), minMustPass: z.number() }).optional()
    }).optional(),
    evaluation: z.object({
        tone: z.string().optional(),
        format: z.string().optional(),
        examples: z.string().optional()
    }).optional(),
    remediation: z.object({
        dayNumber: z.number().optional(),
        hint: z.string().optional()
    }).optional()
});

const TrackDaySchema = z.object({
    dayNumber: z.number(),
    title: z.string(),
    lessonText: z.string(),
    exerciseType: z.string().optional(),
    exercisePrompt: z.string().optional(),
    exerciseCriteria: CriteriaSchema.optional(),
    buttonsJson: z.array(z.object({ id: z.string(), title: z.string() })).optional(),
    badges: z.array(z.string()).optional(),
    imageUrl: z.string().optional(),
    videoUrl: z.string().optional(),
    videoCaption: z.string().optional()
});

const TrackSchema = z.object({
    trackId: z.string(),
    title: z.string(),
    language: z.enum(['WOLOF', 'FR', 'EN']),
    description: z.string().optional(),
    totalDays: z.number().optional(),
    version: z.string().optional(),
    days: z.array(TrackDaySchema)
});

function validateContent() {
    console.log('πŸ” Starting Content Validation (Zod Pre-commit)...');
    const contentDir = path.resolve(__dirname, '../content/tracks');

    if (!fs.existsSync(contentDir)) {
        console.warn(`⚠️ Content directory not found: ${contentDir}`);
        return;
    }

    const files = fs.readdirSync(contentDir).filter(f => f.endsWith('.json'));
    let hasErrors = false;

    for (const file of files) {
        const filePath = path.join(contentDir, file);
        try {
            const fileContent = fs.readFileSync(filePath, 'utf-8');
            const jsonData = JSON.parse(fileContent);
            const r = TrackSchema.safeParse(jsonData);

            if (!r.success) {
                console.error(`❌ Validation Failed: ${file}`);
                r.error.issues.forEach((e: z.ZodIssue) => {
                    console.error(`   - Path: ${e.path.join('.')}`);
                    console.error(`   - Error: ${e.message}`);
                });
                hasErrors = true;
            } else {
                console.log(`βœ… Validated: ${file}`);
            }
        } catch (e: unknown) {
            hasErrors = true;
            console.error(`❌ Parse Error in ${file}: ${(e instanceof Error ? e.message : String(e))}`);
        }
    }

    if (hasErrors) {
        process.exit(1);
    } else {
        console.log('πŸŽ‰ All 10 JSON Track files are strictly valid Zod structures.');
    }
}

validateContent();