diff --git a/package.json b/package.json index b9e3148..67a0f3e 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "android": "expo start --android", "ios": "expo start --ios", "web": "expo start --web", - "test": "jest" + "test": "jest", + "pruefung": "node tools/pruefung.js" }, "jest": { "preset": "jest-expo" diff --git a/tools/pruefung.js b/tools/pruefung.js new file mode 100644 index 0000000..88ac968 --- /dev/null +++ b/tools/pruefung.js @@ -0,0 +1,186 @@ +/** + * 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(/