#!/usr/bin/env node import fs from "node:fs/promises"; import path from "node:path"; const DIST = path.join(process.cwd(), "dist"); function stripNonDomRegions(html) { // Évite les faux positifs (JS/CSS/COMMENTS/TEMPLATE) return html .replace(//g, "") // ✅ NEW: commentaires HTML .replace(/]*>[\s\S]*?<\/script>/gi, "") .replace(/]*>[\s\S]*?<\/style>/gi, "") .replace(/]*>[\s\S]*?<\/template>/gi, ""); // ✅ NEW: templates } function extractRealIds(html) { const cleaned = stripNonDomRegions(html); // - exige un ESPACE avant id => match " id=" mais PAS "data-id=" // - cherche dans les tags seulement const re = /<[^>]*\sid\s*=\s*(["'])(.*?)\1/gi; const ids = []; let m; while ((m = re.exec(cleaned))) { const id = (m[2] || "").trim(); if (id) ids.push(id); } return ids; } async function listHtmlFiles(dir) { const out = []; async function walk(d) { const entries = await fs.readdir(d, { withFileTypes: true }); for (const e of entries) { const p = path.join(d, e.name); if (e.isDirectory()) await walk(p); else if (e.isFile() && p.endsWith(".html")) out.push(p); } } await walk(dir); return out; } function countDuplicates(ids) { const map = new Map(); for (const id of ids) map.set(id, (map.get(id) || 0) + 1); const dups = [...map.entries()].filter(([, c]) => c > 1); dups.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])); return dups; } let failures = 0; let files = 0; let htmlFiles = []; try { htmlFiles = await listHtmlFiles(DIST); } catch { console.error(`❌ dist introuvable: ${DIST} (as-tu fait 'npm run build' ?)`); process.exit(1); } for (const file of htmlFiles) { files++; const html = await fs.readFile(file, "utf8"); const ids = extractRealIds(html); const dups = countDuplicates(ids); if (dups.length) { failures++; console.error(`❌ DUP IDS: ${file}`); for (const [id, c] of dups) { console.error(` - ${id} x${c}`); } } } if (failures) { console.error(`\n❌ audit-dist FAILED: files=${files} failures=${failures}`); process.exit(1); } console.log(`✅ audit-dist OK: files=${files}`);