chore: add ops scripts + diagrams PNG renders
This commit is contained in:
97
scripts/check-annotations-media.mjs
Normal file
97
scripts/check-annotations-media.mjs
Normal file
@@ -0,0 +1,97 @@
|
||||
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 = [];
|
||||
|
||||
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
|
||||
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user