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();