import fs from "node:fs/promises"; import path from "node:path"; import YAML from "yaml"; const CWD = process.cwd(); const ANNO_DIR = path.join(CWD, "src", "annotations"); const PUBLIC_DIR = path.join(CWD, "public"); async function exists(p) { try { await fs.access(p); return true; } catch { return false; } } async function walk(dir) { const out = []; const ents = await fs.readdir(dir, { withFileTypes: true }); for (const e of ents) { const p = path.join(dir, e.name); if (e.isDirectory()) out.push(...(await walk(p))); else out.push(p); } return out; } function parseDoc(raw, fileAbs) { if (/\.json$/i.test(fileAbs)) return JSON.parse(raw); return YAML.parse(raw); } function isPlainObject(x) { return !!x && typeof x === "object" && !Array.isArray(x); } function toPublicPathFromUrl(urlPath) { // "/media/..." -> "public/media/..." const clean = String(urlPath || "").split("?")[0].split("#")[0]; if (!clean.startsWith("/media/")) return null; return path.join(PUBLIC_DIR, clean.replace(/^\/+/, "")); } async function main() { if (!(await exists(ANNO_DIR))) { console.log("✅ annotations-media: aucun src/annotations — rien à vérifier."); process.exit(0); } const files = (await walk(ANNO_DIR)).filter((p) => /\.(ya?ml|json)$/i.test(p)); let checked = 0; let missing = 0; const notes = []; // Optim: éviter de vérifier 100 fois le même fichier media const seenMedia = new Set(); // src string for (const f of files) { const rel = path.relative(CWD, f).replace(/\\/g, "/"); const raw = await fs.readFile(f, "utf8"); let doc; try { doc = parseDoc(raw, f); } catch (e) { missing++; notes.push(`- PARSE FAIL: ${rel} (${String(e?.message ?? e)})`); continue; } if (!isPlainObject(doc) || doc.schema !== 1 || !isPlainObject(doc.paras)) continue; for (const [paraId, entry] of Object.entries(doc.paras)) { const media = entry?.media; if (!Array.isArray(media)) continue; for (const m of media) { const src = String(m?.src || ""); if (!src.startsWith("/media/")) continue; // externes ok, ou autres conventions futures // dédupe if (seenMedia.has(src)) continue; seenMedia.add(src); checked++; const p = toPublicPathFromUrl(src); if (!p) continue; if (!(await exists(p))) { missing++; notes.push(`- MISSING MEDIA: ${src} (from ${rel} para ${paraId})`); } } } } if (missing > 0) { console.error(`FAIL: annotations media missing (checked=${checked} missing=${missing})`); for (const n of notes) console.error(n); process.exit(1); } console.log(`✅ annotations-media OK: checked=${checked}`); } main().catch((e) => { console.error("FAIL: check-annotations-media crashed:", e); process.exit(1); });