edtech / packages /database /scripts /validate-content.ts
CognxSafeTrack
chore: execute Sprint 38 technical debt resolution (Type Safety, Zod validation, Vitest, Mock LLM extracted)
d9879cf
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();