/** * Gesundheits-Check für BehördenKlar. * * node tools/pruefung.js * * Prüft in EINEM Durchlauf alles, was in der Vergangenheit schon einmal * still kaputt war — ohne dass es jemand gemerkt hat: * * - Warteliste-Formular (war 6 Wochen kaputt: Adresse abgeschnitten) * - abgeschnittene Zeilen "[...]" (ein Werkzeug kürzte auf 200 Zeichen) * - ungültige strukturierte Daten (Google meldete "Fehlerhafter Aufbau") * - tote Verweise auf Dateien, die es nicht gibt (manifest.json lieferte HTML) * - Bezahlung, KI-Analyse und Webhook — die drei Dinge, an denen Geld hängt * * Rückgabewert 0 = alles in Ordnung, 1 = mindestens ein Problem. * Nach jedem Deploy ausführen. Dauert etwa 30 Sekunden. */ const { execFileSync } = require('child_process'); const fs = require('fs'); const path = require('path'); const WEB = path.join(__dirname, '..', 'webseite'); const BASIS = 'https://behoerdenklar.de'; const WORKER = 'https://behoerdenklar-proxy.behoerdenbrief.workers.dev'; const BREVO_LAENGE = 248; // vollständige Formular-Adresse let fehler = 0; let warnungen = 0; function ok(text) { console.log(' \x1b[32m✓\x1b[0m ' + text); } function schlecht(text) { console.log(' \x1b[31m✗ ' + text + '\x1b[0m'); fehler++; } function warnung(text) { console.log(' \x1b[33m!\x1b[0m ' + text); warnungen++; } function titel(text) { console.log('\n\x1b[1m' + text + '\x1b[0m'); } /** Holt eine Adresse. curl statt fetch, damit es überall gleich läuft. */ function hole(url, optionen = []) { try { return execFileSync('curl', ['-s', '--max-time', '30', ...optionen, url], { encoding: 'utf8', maxBuffer: 20 * 1024 * 1024 }); } catch { return ''; } } function status(url) { try { return execFileSync('curl', ['-s', '-o', '/dev/null', '-w', '%{http_code}', '--max-time', '30', url], { encoding: 'utf8' }).trim(); } catch { return '000'; } } function htmlDateien() { const raus = []; (function lauf(ordner) { for (const eintrag of fs.readdirSync(ordner, { withFileTypes: true })) { const p = path.join(ordner, eintrag.name); if (eintrag.isDirectory()) lauf(p); else if (eintrag.name.endsWith('.html')) raus.push(p); } })(WEB); return raus; } // ───────────────────────────────────────────────────────────── titel('1. Abgeschnittene Texte'); { const betroffen = htmlDateien().filter((p) => fs.readFileSync(p, 'utf8').includes('[...]')); if (betroffen.length) betroffen.forEach((p) => schlecht('abgeschnitten: ' + path.relative(WEB, p))); else ok('keine Datei enthält "[...]"'); } titel('2. Warteliste-Formular'); { let geprueft = 0; for (const p of htmlDateien()) { const s = fs.readFileSync(p, 'utf8'); const t = s.match(/action="(https:\/\/86550585[^"]*)"/); if (!t) continue; geprueft++; if (t[1].length !== BREVO_LAENGE) { schlecht(`${path.relative(WEB, p)}: Adresse ${t[1].length} statt ${BREVO_LAENGE} Zeichen — Anmeldungen gehen ins Leere!`); } } if (geprueft && !fehler) ok(`${geprueft} Formulare, alle Adressen vollständig`); if (!geprueft) warnung('kein Warteliste-Formular gefunden'); } titel('3. Strukturierte Daten (Google)'); { let anzahl = 0, kaputt = 0; for (const p of htmlDateien()) { const s = fs.readFileSync(p, 'utf8'); for (const m of s.matchAll(/