diff --git a/server/pruef-nachtlauf.mjs b/server/pruef-nachtlauf.mjs new file mode 100644 index 00000000..2adf609f --- /dev/null +++ b/server/pruef-nachtlauf.mjs @@ -0,0 +1,277 @@ +/* ===================================================================== + pruef-nachtlauf.mjs — Wer prüft den Prüfstand? (25.09.2026) + + Jede Nacht läuft `tools/nachtlauf.mjs` über alle Prüfdateien und + schreibt das Ergebnis als Notiz in Filipes Vault. Diese Notiz ist + das Einzige, was er von dem ganzen Aufwand zu sehen bekommt. + + Und sie war bis heute die einzige Datei im Haus, die niemand geprüft + hat. Das ist genau die Stelle, an der ein stiller Fehler am + teuersten ist: Schreibt die Notiz Unsinn, merkt es keiner — sie + sieht ja aus wie immer. + + --------------------------------------------------------------------- + WARUM DAS VORHER GAR NICHT GING + + `nachtlauf.mjs` startet beim Import SOFORT einen Lauf über 193 + Dateien. Wer die Notiz messen wollte, hätte damit eine halbe Stunde + Rechenzeit ausgelöst — und nebenbei das echte Ergebnis auf Filipes + Startseite überschrieben. Das ist keine Kleinigkeit: Am 04.09.2026 + hat genau so ein Griff bei RunOne einen roten Alarm über einer + fehlerfreien Buchhaltung erzeugt, am Morgen einer Vorstellung. + + Deshalb wurde der Textbau nach `tools/nachtlauf-notiz.mjs` + ausgelagert. Diese Prüfung importiert nur das Modul; es hat keine + Nebenwirkungen, schreibt nichts und misst nichts. + + --------------------------------------------------------------------- + WAS GEPRÜFT WIRD + + 1. Der Block „VON HAND" überlebt jeden Lauf — und nur er. + 2. Fehlt er, wird er mit seiner Erklärung neu angelegt. + 3. Ein Rückgang der Prüfungszahl steht in der Notiz (28.08.2026). + 4. Der dritte Ausgang sagt „konnte nicht nachsehen" und behauptet + NICHT, alles sei in Ordnung. + 5. Jeder Aufruf in nachtlauf.mjs reicht den Notizpfad mit — + sonst ginge der Block an genau diesem Ausgang verloren. + + Zu jeder Aussage gehört eine Gegenprobe: eine Prüfung, die immer + bestätigt, bestätigt nichts. + + Aufrufen mit: node server/pruef-nachtlauf.mjs + ===================================================================== */ +import { mkdtempSync, rmSync, writeFileSync, readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + MARKE_AUF, MARKE_ZU, handNotizLesen, notizSchreiben, zeitWort, +} from "../tools/nachtlauf-notiz.mjs"; + +const HIER = dirname(fileURLToPath(import.meta.url)); +const NACHTLAUF = join(HIER, "..", "tools", "nachtlauf.mjs"); + +let ok = 0, fehl = 0; +const pruef = (name, bedingung, zusatz = "") => { + if (bedingung) { ok++; console.log(` ok ${name}`); } + else { fehl++; console.log(` FEHL ${name}${zusatz ? " — " + zusatz : ""}`); } +}; +const melde = (t) => console.log(t); + +/* Ein Wegwerf-Ordner. In den Vault wird hier nichts geschrieben -- + die echte Notiz bleibt unberührt, auch wenn die Prüfung abstürzt. */ +const ORDNER = mkdtempSync(join(tmpdir(), "pruef-nachtlauf-")); +const NOTIZ = join(ORDNER, "Pruefstand.md"); +process.on("exit", () => { try { rmSync(ORDNER, { recursive: true, force: true }); } catch { /* egal */ } }); + +const LAGE_GRUEN = { dateien: 193, dateienOk: 193, punkte: 8900, punkteOk: 8900, rote: [] }; +const LAGE_ROT = { + dateien: 193, dateienOk: 192, punkte: 8900, punkteOk: 8899, + rote: [{ datei: "pruef-beispiel.mjs", code: 1, erste: "FEHL irgendetwas stimmt nicht" }], +}; + +/* ======================================================================= + 1. DER BLOCK „VON HAND" ÜBERLEBT + ======================================================================= */ +melde("=== 1. Was von Hand dasteht, bleibt stehen ==="); + +const HANDTEXT = "## Von Hand\n\npruef-beispiel ist rot, weil sie den Umbau vom 24.09.\n" + + "nicht mitbekommen hat. Nicht anfassen, ist in Arbeit."; + +/* Erster Lauf: keine Notiz da -- der Block muss entstehen. */ +writeFileSync(NOTIZ, notizSchreiben({ lage: LAGE_GRUEN, vorher: null, minuten: 12 }, NOTIZ), "utf8"); +{ + const t = readFileSync(NOTIZ, "utf8"); + pruef("eine neue Notiz bekommt den Block von selbst", + t.includes(MARKE_AUF) && t.includes(MARKE_ZU)); + pruef("und er erklärt, wozu er da ist", + /überlebt jeden Lauf/.test(t), + "ein leerer Block ohne Erklärung wird beim ersten Überschreiben gelöscht"); +} + +/* Jetzt schreibt ein Mensch etwas hinein. */ +{ + const t = readFileSync(NOTIZ, "utf8"); + const a = t.indexOf(MARKE_AUF) + MARKE_AUF.length; + const b = t.indexOf(MARKE_ZU); + writeFileSync(NOTIZ, t.slice(0, a) + "\n" + HANDTEXT + "\n" + t.slice(b), "utf8"); +} + +/* Und der nächste Lauf schreibt die Notiz neu -- mit ganz anderem + Ergebnis, damit auch wirklich alles andere ausgetauscht wird. */ +writeFileSync(NOTIZ, notizSchreiben({ lage: LAGE_ROT, vorher: null, minuten: 31 }, NOTIZ), "utf8"); +{ + const t = readFileSync(NOTIZ, "utf8"); + pruef("der Satz von Hand steht nach dem nächsten Lauf noch da", + t.includes("Nicht anfassen, ist in Arbeit.")); + pruef("mit seiner Überschrift", t.includes("## Von Hand")); + /* GEGENPROBE. Bliebe ALLES stehen, wäre die Prüfung oben wertlos: + Sie würde dann nur beweisen, dass die Datei nicht angefasst wurde. */ + pruef("Gegenprobe: das alte Ergebnis ist verschwunden", + !t.includes("Alles in Ordnung"), + "der grüne Stand von vorher steht noch drin -- dann wird gar nicht überschrieben"); + pruef("Gegenprobe: das neue Ergebnis ist da", t.includes("pruef-beispiel.mjs")); +} + +/* Und ein Block ohne Inhalt darf nicht als „von Hand" durchgehen -- + sonst bliebe die Erklärung für immer weg, sobald jemand sie löscht. */ +{ + writeFileSync(NOTIZ, `# x\n\n${MARKE_AUF}\n \n${MARKE_ZU}\n`, "utf8"); + pruef("ein leerer Block zählt als nicht vorhanden", + handNotizLesen(NOTIZ) === null); + writeFileSync(NOTIZ, "# x\n\nkeine Marken hier\n", "utf8"); + pruef("Gegenprobe: ohne Marken wird nichts gefunden", + handNotizLesen(NOTIZ) === null); + writeFileSync(NOTIZ, `# x\n\n${MARKE_AUF}\nhallo\n${MARKE_ZU}\n`, "utf8"); + pruef("Gegenprobe: mit Inhalt wird er gefunden", + handNotizLesen(NOTIZ) === "hallo"); +} + +/* ======================================================================= + 2. EIN RÜCKGANG DER ANZAHL STEHT IN DER NOTIZ + + Die Lehre vom 28.08.2026: Ein Lauf meldete 789 statt 804 Prüfungen + und war dabei grün. Acht Prüfungen je Gerät waren stillschweigend + weggefallen. Wer nur „bestanden" liest, hätte sie ab da dauerhaft + ungeprüft gelassen. + ======================================================================= */ +melde(""); +melde("=== 2. Weniger Prüfungen als gestern ist ein Befund ==="); +{ + const weniger = notizSchreiben({ + lage: { ...LAGE_GRUEN, punkte: 8700, punkteOk: 8700 }, + vorher: { wann: "24.09.2026 um 05:49", zeit: Date.now() - 86400000, + dateien: 193, dateienOk: 193, punkte: 8900, punkteOk: 8900 }, + minuten: 12, + }, NOTIZ); + pruef("200 Prüfungen weniger werden gemeldet, obwohl alles grün ist", + /200 Prüfungen WENIGER/.test(weniger)); + pruef("und es steht dabei, warum das trotzdem ein Befund ist", + /beweist nichts/.test(weniger)); + + const mehr = notizSchreiben({ + lage: LAGE_GRUEN, + vorher: { wann: "24.09.2026 um 05:49", zeit: Date.now() - 86400000, + dateien: 193, dateienOk: 193, punkte: 8700, punkteOk: 8700 }, + minuten: 12, + }, NOTIZ); + /* GEGENPROBE: Steigt die Zahl, darf der Satz NICHT kommen. Eine + Warnung, die immer kommt, ist keine Warnung mehr. */ + pruef("Gegenprobe: bei MEHR Prüfungen kommt die Warnung nicht", + !/Prüfungen WENIGER/.test(mehr)); + + const wenigerDateien = notizSchreiben({ + lage: { ...LAGE_GRUEN, dateien: 190, dateienOk: 190 }, + vorher: { wann: "24.09.2026 um 05:49", zeit: Date.now() - 86400000, + dateien: 193, dateienOk: 193, punkte: 8900, punkteOk: 8900 }, + minuten: 12, + }, NOTIZ); + pruef("auch eine verschwundene Prüfdatei wird genannt", + /3 Prüfdatei\(en\) weniger/.test(wenigerDateien)); +} + +/* ======================================================================= + 3. DER DRITTE AUSGANG + + „Konnte nicht nachsehen" ist weder gut noch schlecht -- es ist die + dritte Antwort, und sie darf auf keinen Fall wie „alles in Ordnung" + aussehen. + ======================================================================= */ +melde(""); +melde("=== 3. Konnte nicht nachsehen ==="); +{ + const t = notizSchreiben({ + vorher: { wann: "24.09.2026 um 05:49", zeit: Date.now() - 3 * 86400000, + dateien: 193, dateienOk: 193, punkte: 8900, punkteOk: 8900 }, + konnteNicht: "Ein Lauf läuft schon seit 01:12.", + }, NOTIZ); + pruef("es steht als eigener Zustand da", /Konnte nicht nachsehen/.test(t)); + pruef("und sagt NICHT, dass alles in Ordnung ist", !/Alles in Ordnung/.test(t)); + pruef("der Grund steht dabei", /läuft schon seit 01:12/.test(t)); + pruef("und wie alt der letzte echte Stand ist", + /3 Tage her/.test(t), t.split("\n").filter((z) => /gemessen/.test(z)).join(" | ")); + + const ohneVorher = notizSchreiben({ vorher: null, konnteNicht: "irgendein Grund" }, NOTIZ); + pruef("Gegenprobe: ohne früheres Ergebnis wird das auch gesagt", + /noch kein früheres Ergebnis/.test(ohneVorher)); +} + +/* ======================================================================= + 4. „KEINE PRÜFUNG GEZÄHLT" IST KEIN BEFUND + + Acht Dateien standen im Nachtlauf vom 25.09. als rot, ohne dass + irgendetwas kaputt war: Sie schreiben ihre Ergebnisse als ` OK ` + statt ` ok `, und der Zähler erkannte davon nichts. Die Notiz muss + diesen Unterschied benennen, sonst sucht man im Programm. + ======================================================================= */ +melde(""); +melde("=== 4. Null gezählte Prüfungen ==="); +{ + const t = notizSchreiben({ + lage: { dateien: 193, dateienOk: 192, punkte: 8900, punkteOk: 8900, + rote: [{ datei: "pruef-still.mjs", code: 0, nullPunkte: true, erste: "alles bestanden" }] }, + vorher: null, minuten: 12, + }, NOTIZ); + pruef("die Notiz nennt es beim Namen", + /Kein Befund – es wurde keine einzige Prüfung GEZÄHLT/.test(t)); + pruef("und erklärt, woran es meist liegt", /statt/.test(t) && /OK/.test(t)); + pruef("warnt aber trotzdem davor, es liegenzulassen", + /still aufhören zu prüfen/.test(t)); +} + +/* ======================================================================= + 5. JEDER AUSGANG REICHT DEN PFAD MIT + + Der Block „von Hand" wird aus der VORHANDENEN Datei gelesen. Wer + `notizSchreiben(...)` ohne den Pfad aufruft, schreibt an genau + diesem Ausgang eine Notiz ohne ihn -- und der Block ist weg. Das + sind vier Stellen in nachtlauf.mjs, und drei davon sind die + seltenen Ausgänge, die man beim Ausprobieren nie erwischt. + ======================================================================= */ +melde(""); +melde("=== 5. Alle Ausgänge in nachtlauf.mjs ==="); +{ + const quelle = readFileSync(NACHTLAUF, "utf8"); + /* `notizSchreiben(` trifft nur Aufrufe -- die Importzeile schreibt + den Namen ohne Klammer. */ + const aufrufe = (quelle.match(/notizSchreiben\(/g) || []).length; + const mitPfad = (quelle.match(/\}, NOTIZ\)/g) || []).length; + pruef(`alle ${aufrufe} Aufrufe reichen den Notizpfad mit`, + aufrufe >= 4 && mitPfad === aufrufe, + `${aufrufe} Aufrufe, davon ${mitPfad} mit Pfad`); + /* GEGENPROBE: Der Zähler oben darf nicht einfach immer gleich sein. */ + pruef("Gegenprobe: ein Aufruf ohne Pfad würde auffallen", + (("notizSchreiben({ a })").match(/\}, NOTIZ\)/g) || []).length === 0); + pruef("nachtlauf.mjs baut den Text nicht mehr selbst", + !/function notizSchreiben/.test(quelle), + "sonst gäbe es zwei Fassungen, und geprüft würde die falsche"); +} + +/* ======================================================================= + 6. KLEINIGKEITEN, DIE SONST NIEMAND MISST + ======================================================================= */ +melde(""); +melde("=== 6. Datum und Rahmen ==="); +{ + pruef("das Datum wird deutsch geschrieben", + /^\d{2}\.\d{2}\.\d{4} um \d{2}:\d{2}$/.test(zeitWort(new Date(2026, 8, 25, 5, 7))), + zeitWort(new Date(2026, 8, 25, 5, 7))); + pruef("und zweistellig, auch am Morgen", + zeitWort(new Date(2026, 0, 3, 5, 7)) === "03.01.2026 um 05:07"); + + const gruen = notizSchreiben({ lage: LAGE_GRUEN, vorher: null, minuten: 12 }, NOTIZ); + pruef("bei null roten steht der beruhigende Satz", + /Keine Datei ist rot/.test(gruen)); + const rot = notizSchreiben({ lage: LAGE_ROT, vorher: null, minuten: 31 }, NOTIZ); + pruef("bei einer roten steht die Einzahl richtig", + /## 1 Prüfung ist rot/.test(rot), rot.split("\n")[7] || ""); + pruef("und der Befehl zum Einzelnachsehen steht dabei", + /node server\/pruef-beispiel\.mjs/.test(rot)); + pruef("samt dem Hinweis, dass rot nicht automatisch kaputt heißt", + /Rot heißt nicht automatisch kaputt/.test(rot)); +} + +melde(""); +melde(` ${ok} bestanden, ${fehl} fehlgeschlagen`); +melde(""); +process.exit(fehl ? 1 : 0); diff --git a/tools/nachtlauf-notiz.mjs b/tools/nachtlauf-notiz.mjs new file mode 100644 index 00000000..d4e00c4a --- /dev/null +++ b/tools/nachtlauf-notiz.mjs @@ -0,0 +1,166 @@ +/* ===================================================================== + DIE NOTIZ FÜR DEN PRÜFSTAND (ausgelagert am 25.09.2026) + + Stand vorher mitten in `nachtlauf.mjs`. Ausgelagert, damit sie + PRÜFBAR wird: `nachtlauf.mjs` startet beim Import sofort einen + Lauf über alle Prüfdateien -- wer die Notiz messen wollte, hätte + dafür eine halbe Stunde Rechenzeit ausgelöst und nebenbei das + echte Ergebnis auf Filipes Startseite überschrieben. + + Hier drin steht nur Textbau, kein Nebeneffekt. Importieren ist + folgenlos. Geprüft von `server/pruef-nachtlauf.mjs`. + ===================================================================== */ +import { readFileSync } from "node:fs"; + +const zweiZiffern = (n) => String(n).padStart(2, "0"); +function zeitWort(d = new Date()) { + return `${zweiZiffern(d.getDate())}.${zweiZiffern(d.getMonth() + 1)}.${d.getFullYear()}` + + ` um ${zweiZiffern(d.getHours())}:${zweiZiffern(d.getMinutes())}`; +} + +export { zeitWort }; + +/* ===================================================================== + EIN STÜCK DER NOTIZ GEHÖRT DEM MENSCHEN (25.09.2026) + + Bis heute stand oben in der Notiz: "von Hand ändern bringt nichts, + beim nächsten Lauf ist es wieder überschrieben." Das war ehrlich + und trotzdem falsch herum gedacht. + + Denn das Wertvollste an diesem Prüfstand ist gerade NICHT die + Zahl, sondern der Satz daneben: "Diese Prüfung ist rot, weil sie + den Umbau vom 24.09. nicht mitbekommen hat" oder "die hier ist + echt, Finger weg". Wer das aufschreiben will, schreibt es genau + hierhin -- und fand es am nächsten Morgen gelöscht. + + Deshalb gibt es jetzt einen Bereich zwischen zwei Marken, den der + Lauf beim Schreiben WIEDER EINSETZT statt ihn zu überschreiben. + Fehlt er (neue Notiz, jemand hat ihn gelöscht), wird er leer neu + angelegt -- mit der Erklärung drin, wozu er da ist. + ===================================================================== */ +export const MARKE_AUF = ""; +export const MARKE_ZU = ""; + +export function handNotizLesen(NOTIZ) { + let alt = ""; + try { alt = readFileSync(NOTIZ, "utf8"); } catch { return null; } + const a = alt.indexOf(MARKE_AUF); + const b = alt.indexOf(MARKE_ZU, a + 1); + if (a < 0 || b < 0) return null; + const drin = alt.slice(a + MARKE_AUF.length, b).trim(); + return drin.length ? drin : null; +} + +export function notizSchreiben({ lage, vorher, minuten, konnteNicht }, notizPfad) { + const jetzt = new Date(); + const hand = handNotizLesen(notizPfad); + const zeilen = []; + zeilen.push("# Prüfstand Team Dogi"); + zeilen.push(""); + zeilen.push("*Diese Notiz schreibt `tools/nachtlauf.mjs`. Alles außerhalb des Blocks"); + zeilen.push("„VON HAND“ ist beim nächsten Lauf wieder überschrieben — was DRIN steht,"); + zeilen.push("bleibt stehen.*"); + zeilen.push(""); + zeilen.push(MARKE_AUF); + if (hand) { + zeilen.push(hand); + } else { + zeilen.push("## Von Hand"); + zeilen.push(""); + zeilen.push("*Hier hinein gehört, was die Zahlen nicht sagen können: welche rote"); + zeilen.push("Zeile schon untersucht ist, welche echt ist, woran gerade gearbeitet"); + zeilen.push("wird. Dieser Block überlebt jeden Lauf — der Rest der Notiz nicht.*"); + } + zeilen.push(MARKE_ZU); + zeilen.push(""); + + if (konnteNicht) { + zeilen.push("## ⚠ Konnte nicht nachsehen"); + zeilen.push(""); + zeilen.push(konnteNicht); + zeilen.push(""); + if (vorher?.wann) { + const tage = Math.floor((Date.now() - vorher.zeit) / 86400000); + zeilen.push(`Zuletzt wirklich gemessen: **${vorher.wann}**` + + (tage >= 1 ? ` — das ist **${tage} Tag${tage === 1 ? "" : "e"} her**.` : ".")); + } else { + zeilen.push("Es gibt noch kein früheres Ergebnis."); + } + return zeilen.join("\n") + "\n"; + } + + const rot = lage.rote.length; + zeilen.push(rot + ? `## ${rot} Prüfung${rot === 1 ? "" : "en"} ${rot === 1 ? "ist" : "sind"} rot` + : "## Alles in Ordnung"); + zeilen.push(""); + zeilen.push(`Gemessen am **${zeitWort(jetzt)}**, Dauer ${minuten} Minuten.`); + zeilen.push(""); + zeilen.push("| | jetzt | vorher |"); + zeilen.push("|---|---|---|"); + zeilen.push(`| Dateien in Ordnung | ${lage.dateienOk} von ${lage.dateien} | ` + + `${vorher ? `${vorher.dateienOk} von ${vorher.dateien}` : "—"} |`); + zeilen.push(`| Einzelprüfungen bestanden | ${lage.punkteOk} von ${lage.punkte} | ` + + `${vorher ? `${vorher.punkteOk} von ${vorher.punkte}` : "—"} |`); + zeilen.push(""); + + /* EIN RÜCKGANG DER ANZAHL IST EIN BEFUND, AUCH WENN ALLES GRÜN IST. + Am 28.08.2026 meldete ein Lauf 789 statt 804 Prüfungen und war + dabei grün: Acht Prüfungen je Gerät waren stillschweigend + weggefallen -- ausgerechnet die, die die wichtigste Funktion + absichern. Wer nur "bestanden" liest, hätte sie ab da dauerhaft + ungeprüft gelassen. */ + if (vorher && lage.punkte != null && vorher.punkte != null + && lage.punkte < vorher.punkte) { + zeilen.push(`> **Es werden ${vorher.punkte - lage.punkte} Prüfungen WENIGER gemacht als beim letzten Lauf.**`); + zeilen.push("> Das ist auch dann ein Befund, wenn alles grün ist: Eine Prüfung, die"); + zeilen.push("> still nicht mehr läuft, beweist nichts — und niemandem fällt es auf."); + zeilen.push(""); + } + if (vorher && lage.dateien != null && vorher.dateien != null + && lage.dateien < vorher.dateien) { + zeilen.push(`> **Es gibt ${vorher.dateien - lage.dateien} Prüfdatei(en) weniger als beim letzten Lauf.**`); + zeilen.push(""); + } + + if (rot) { + zeilen.push("## Was rot ist"); + zeilen.push(""); + for (const r of lage.rote) { + zeilen.push(`### \`${r.datei}\``); + zeilen.push(""); + if (r.nullPunkte) { + zeilen.push(`**Kein Befund – es wurde keine einzige Prüfung GEZÄHLT** ` + + `(Exitcode ${r.code}).` ); + zeilen.push(""); + zeilen.push("Das ist etwas anderes als „kaputt“: Die Datei ist " + + "durchgelaufen. Der Zähler hat ihre Punkte nur nicht erkannt – " + + "meist, weil sie ihre Ergebnisse anders schreibt als erwartet " + + "(` OK ` statt ` ok `, andere Einrückung, eigenes Format). " + + "Nachsehen lohnt trotzdem: Eine Datei, deren Punkte niemand " + + "zählt, kann still aufhören zu prüfen, ohne dass es auffällt. " + + "Zuletzt stand da:"); + zeilen.push(""); + } else if (r.ohneGrund) { + zeilen.push(`**Konnte nicht sagen, woran es lag** (Exitcode ${r.code}). ` + + "Das ist etwas anderes als ein Befund: Die Prüfung ist nicht " + + "durchgelaufen, hat aber auch keine FEHL-Zeile hinterlassen. " + + "Meist steckt dahinter eine Zeitüberschreitung oder ein Absturz " + + "im Vorlauf, kein Fehler im Programm. Zuletzt stand da:"); + zeilen.push(""); + } + zeilen.push(`\`\`\`\n${r.erste}\n\`\`\``); + zeilen.push(""); + zeilen.push(`Einzeln nachsehen: \`node server/${r.datei}\``); + zeilen.push(""); + } + zeilen.push("**Rot heißt nicht automatisch kaputt.** Dreimal am 23.09.2026 war"); + zeilen.push("die Prüfung veraltet und das Programm richtig — einmal war es"); + zeilen.push("umgekehrt und ein Rollenname stand offen im Netz. Erst nachsehen,"); + zeilen.push("dann entscheiden, wer recht hat."); + } else { + zeilen.push("Keine Datei ist rot. Der nächste Lauf kommt in der nächsten Nacht."); + } + zeilen.push(""); + return zeilen.join("\n") + "\n"; +} diff --git a/tools/nachtlauf.mjs b/tools/nachtlauf.mjs index 665e19f3..57e041f1 100644 --- a/tools/nachtlauf.mjs +++ b/tools/nachtlauf.mjs @@ -71,6 +71,7 @@ import { } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; +import { notizSchreiben, zeitWort } from "./nachtlauf-notiz.mjs"; const HIER = dirname(fileURLToPath(import.meta.url)); const WURZEL = join(HIER, ".."); @@ -94,11 +95,8 @@ const SCHLOSS = join(WURZEL, "tools", ".nachtlauf-laeuft"); was blockiert, wird abgeschafft. */ const SCHLOSS_FRIST_MS = 3 * 60 * 60 * 1000; -const zweiZiffern = (n) => String(n).padStart(2, "0"); -function zeitWort(d = new Date()) { - return `${zweiZiffern(d.getDate())}.${zweiZiffern(d.getMonth() + 1)}.${d.getFullYear()}` - + ` um ${zweiZiffern(d.getHours())}:${zweiZiffern(d.getMinutes())}`; -} +/* `zeitWort` kommt aus nachtlauf-notiz.mjs -- zwei Kopien derselben + Datumsschreibweise waeren zwei Staende, die auseinanderlaufen. */ function schlossNehmen() { if (existsSync(SCHLOSS)) { @@ -216,105 +214,6 @@ function auswerten(aus) { /* --------------------------------------------------------------------- Die Notiz --------------------------------------------------------------------- */ -function notizSchreiben({ lage, vorher, minuten, konnteNicht }) { - const jetzt = new Date(); - const zeilen = []; - zeilen.push("# Prüfstand Team Dogi"); - zeilen.push(""); - zeilen.push("*Diese Notiz schreibt `tools/nachtlauf.mjs` — von Hand ändern bringt nichts,"); - zeilen.push("beim nächsten Lauf ist es wieder überschrieben.*"); - zeilen.push(""); - - if (konnteNicht) { - zeilen.push("## ⚠ Konnte nicht nachsehen"); - zeilen.push(""); - zeilen.push(konnteNicht); - zeilen.push(""); - if (vorher?.wann) { - const tage = Math.floor((Date.now() - vorher.zeit) / 86400000); - zeilen.push(`Zuletzt wirklich gemessen: **${vorher.wann}**` - + (tage >= 1 ? ` — das ist **${tage} Tag${tage === 1 ? "" : "e"} her**.` : ".")); - } else { - zeilen.push("Es gibt noch kein früheres Ergebnis."); - } - return zeilen.join("\n") + "\n"; - } - - const rot = lage.rote.length; - zeilen.push(rot - ? `## ${rot} Prüfung${rot === 1 ? "" : "en"} ${rot === 1 ? "ist" : "sind"} rot` - : "## Alles in Ordnung"); - zeilen.push(""); - zeilen.push(`Gemessen am **${zeitWort(jetzt)}**, Dauer ${minuten} Minuten.`); - zeilen.push(""); - zeilen.push("| | jetzt | vorher |"); - zeilen.push("|---|---|---|"); - zeilen.push(`| Dateien in Ordnung | ${lage.dateienOk} von ${lage.dateien} | ` - + `${vorher ? `${vorher.dateienOk} von ${vorher.dateien}` : "—"} |`); - zeilen.push(`| Einzelprüfungen bestanden | ${lage.punkteOk} von ${lage.punkte} | ` - + `${vorher ? `${vorher.punkteOk} von ${vorher.punkte}` : "—"} |`); - zeilen.push(""); - - /* EIN RÜCKGANG DER ANZAHL IST EIN BEFUND, AUCH WENN ALLES GRÜN IST. - Am 28.08.2026 meldete ein Lauf 789 statt 804 Prüfungen und war - dabei grün: Acht Prüfungen je Gerät waren stillschweigend - weggefallen -- ausgerechnet die, die die wichtigste Funktion - absichern. Wer nur "bestanden" liest, hätte sie ab da dauerhaft - ungeprüft gelassen. */ - if (vorher && lage.punkte != null && vorher.punkte != null - && lage.punkte < vorher.punkte) { - zeilen.push(`> **Es werden ${vorher.punkte - lage.punkte} Prüfungen WENIGER gemacht als beim letzten Lauf.**`); - zeilen.push("> Das ist auch dann ein Befund, wenn alles grün ist: Eine Prüfung, die"); - zeilen.push("> still nicht mehr läuft, beweist nichts — und niemandem fällt es auf."); - zeilen.push(""); - } - if (vorher && lage.dateien != null && vorher.dateien != null - && lage.dateien < vorher.dateien) { - zeilen.push(`> **Es gibt ${vorher.dateien - lage.dateien} Prüfdatei(en) weniger als beim letzten Lauf.**`); - zeilen.push(""); - } - - if (rot) { - zeilen.push("## Was rot ist"); - zeilen.push(""); - for (const r of lage.rote) { - zeilen.push(`### \`${r.datei}\``); - zeilen.push(""); - if (r.nullPunkte) { - zeilen.push(`**Kein Befund – es wurde keine einzige Prüfung GEZÄHLT** ` - + `(Exitcode ${r.code}).` ); - zeilen.push(""); - zeilen.push("Das ist etwas anderes als „kaputt“: Die Datei ist " - + "durchgelaufen. Der Zähler hat ihre Punkte nur nicht erkannt – " - + "meist, weil sie ihre Ergebnisse anders schreibt als erwartet " - + "(` OK ` statt ` ok `, andere Einrückung, eigenes Format). " - + "Nachsehen lohnt trotzdem: Eine Datei, deren Punkte niemand " - + "zählt, kann still aufhören zu prüfen, ohne dass es auffällt. " - + "Zuletzt stand da:"); - zeilen.push(""); - } else if (r.ohneGrund) { - zeilen.push(`**Konnte nicht sagen, woran es lag** (Exitcode ${r.code}). ` - + "Das ist etwas anderes als ein Befund: Die Prüfung ist nicht " - + "durchgelaufen, hat aber auch keine FEHL-Zeile hinterlassen. " - + "Meist steckt dahinter eine Zeitüberschreitung oder ein Absturz " - + "im Vorlauf, kein Fehler im Programm. Zuletzt stand da:"); - zeilen.push(""); - } - zeilen.push(`\`\`\`\n${r.erste}\n\`\`\``); - zeilen.push(""); - zeilen.push(`Einzeln nachsehen: \`node server/${r.datei}\``); - zeilen.push(""); - } - zeilen.push("**Rot heißt nicht automatisch kaputt.** Dreimal am 23.09.2026 war"); - zeilen.push("die Prüfung veraltet und das Programm richtig — einmal war es"); - zeilen.push("umgekehrt und ein Rollenname stand offen im Netz. Erst nachsehen,"); - zeilen.push("dann entscheiden, wer recht hat."); - } else { - zeilen.push("Keine Datei ist rot. Der nächste Lauf kommt in der nächsten Nacht."); - } - zeilen.push(""); - return zeilen.join("\n") + "\n"; -} /* --------------------------------------------------------------------- Los @@ -326,7 +225,7 @@ const schloss = erzwingen ? { ok: true } : schlossNehmen(); if (!schloss.ok) { /* DER DRITTE AUSGANG. Kein Fehler -- nur: hier war nichts zu messen. */ mkdirSync(dirname(NOTIZ), { recursive: true }); - writeFileSync(NOTIZ, notizSchreiben({ vorher, konnteNicht: schloss.grund }), "utf8"); + writeFileSync(NOTIZ, notizSchreiben({ vorher, konnteNicht: schloss.grund }, NOTIZ), "utf8"); console.log("Konnte nicht nachsehen: " + schloss.grund); process.exit(0); } @@ -343,7 +242,7 @@ if (!ergebnis.ging) { mkdirSync(dirname(NOTIZ), { recursive: true }); writeFileSync(NOTIZ, notizSchreiben({ vorher, konnteNicht: `Der Läufer ließ sich nicht starten: ${ergebnis.grund}`, - }), "utf8"); + }, NOTIZ), "utf8"); console.log("Konnte nicht nachsehen: " + ergebnis.grund); process.exit(0); } @@ -360,13 +259,13 @@ if (lage.dateien == null || lage.punkte == null || lage.punkte === 0) { konnteNicht: "Der Lauf hat keine einzige Zahl geliefert — vermutlich ist er" + " abgebrochen, bevor er anfangen konnte.\n\nLetzte Ausgabe:\n\n```\n" + ergebnis.aus.slice(-700) + "\n```", - }), "utf8"); + }, NOTIZ), "utf8"); console.log("Konnte nicht nachsehen: keine Zahlen in der Ausgabe."); process.exit(0); } mkdirSync(dirname(NOTIZ), { recursive: true }); -writeFileSync(NOTIZ, notizSchreiben({ lage, vorher, minuten: ergebnis.minuten }), "utf8"); +writeFileSync(NOTIZ, notizSchreiben({ lage, vorher, minuten: ergebnis.minuten }, NOTIZ), "utf8"); /* Der Stand für den nächsten Vergleich -- erst NACH der Notiz, damit ein Absturz beim Schreiben nicht den alten Vergleichswert wegnimmt. */