Vier stillgelegte Pruefungen, zwei kaputte Zeichen -- und die Wache dagegen
Gefunden beim Vorbereiten der KLIPY-Anbindung, nicht gesucht.
DER VERLORENE BACKSLASH
In fuenf Dateien stand in einem Suchmuster das BYTE 0x08 statt der
zwei Zeichen \ und b. Gemeint war die Wortgrenze; 0x08 ist das
Rueckschritt-Zeichen und kommt in keinem Text vor. Gemessen:
/<0x08>modis?<0x08>/i.test("Die Modis sind da") -> false
/\bmodis?\b/i .test("Die Modis sind da") -> true
Folgen, je Stelle:
pruef-nachwuchs Das Muster stand in einer VERNEINUNG. Die
Zeile lautete damit ok(!false) -- dauerhaft
gruen, ohne etwas zu messen. Ausgerechnet
die Zeile, die das Durchsickern des
Rollennamens verhindern soll.
pruef-personen-formular dasselbe Muster, dieselbe Verneinung
pruef-nachfrage hatDialogMitFeld war immer falsch. Seiten
mit einem Formular-Dialog galten als "totes
Gewicht" -- der Kommentar direkt darueber
warnt woertlich vor genau diesem Schaden.
pruef-entwicklung zwei von drei Alternativen tot
workspace-treff.js nur ein Kommentar, aber unlesbar
ZWEI KAPUTTE ZEICHEN, BEIDE SICHTBAR
content: "<0x15>C9<0x00>A0" in chat.css. Gemeint war U+25C9 und
ein geschuetztes Leerzeichen. Live stand woertlich "C9 A0" vor
jedem hervorgehobenen @rudel -- am echten Server nachgemessen.
content: "¹3" in chat.css UND entwicklung.css. Gemeint war ein
Haken. Auf der gewaehlten Kachel stand "¹3"; zu sehen in Filipes
Bildschirmfoto.
Repariert wird mit der Escape-Schreibweise ("\2713"), nicht mit dem
Zeichen selbst: Sie besteht nur aus ASCII und ueberlebt jede
Kodierung.
WAS DIESE FEHLERART BESONDERS MACHT
Niemand sieht sie beim Lesen. Der erste war committet, ausgeliefert
und lag live -- und als ich in der Datei danach suchte, meldete grep
"Binary file matches" und zeigte die Zeile gar nicht erst an. Wer den
Unterschied durchsieht, liest content: "C9 A0" und denkt sich nichts.
Deshalb neu: server/pruef-zeichen.mjs (7 Pruefungen). Sie sucht rohe
Steuerzeichen in allen ausgelieferten und allen Serverdateien, dazu
Ersatzzeichen und Kodierungstruemmer in CSS-content-Angaben. Mit
Gegenprobe in beide Richtungen -- sie weist an einer selbst gebauten
Datei nach, dass sie anschlaegt, UND dass sie bei einer sauberen
schweigt.
Die erste Fassung der Regel war zu weit: Sie meldete acht KORREKTE
Zeichen (✓, ↯, ↻, „, ⚠). Eine Warnung, die immer kommt, ist keine
Warnung mehr. Die engere Regel kommt aus dem Befund selbst -- der
Latin-1-Block U+0080..U+00BF, in dem echte Typografie nie steht.
Nachgemessen ueber alle 203 content-Angaben im Haus: keine einzige
benutzt ihn.
GEHEIMNISSE STEHEN NICHT MEHR IM PROTOKOLL
einstellungSetzen() schrieb immer Name UND Wert. Nachgemessen in der
echten Datenbank: kopie_schluessel steht vollstaendig drin,
vapid_paar wurde bei 120 Zeichen abgeschnitten -- kurz VOR dem
privaten Teil. Dass der heil blieb, lag an einer Laengengrenze, nicht
an einer Absicht. Keine offene Tuer (das Protokoll liegt hinter
nurAdmin), aber der Wert wandert in jede Sicherung.
Es ist eine REGEL und keine Liste: Wer eine Einstellung so benennt,
meint ein Geheimnis. Stehen bleibt, DASS sich etwas geaendert hat,
wann und durch wen -- nur der Wert fehlt.
EINE VERALTETE PRUEFUNG, UMGEDREHT STATT GELOESCHT
pruef-nachwuchs verlangte, dass die rechte Hand keine Zugaenge
anlegen kann. Das war bis zum 22.09. richtig; dann hat Filipe es
umgedreht ("damit sie das auch machen kann wenn er live ist"). Die
Pruefung war seither rot, ohne dass es auffiel -- sie lief nicht.
Jetzt prueft sie beides: dass die rechte Hand es darf UND dass die
Grenzen stehen (sperren 404, Protokoll 404). Eine Erlaubnis ohne
Grenze ist keine Entscheidung, sondern ein Loch.
Und tools/nachtlauf.mjs wird nachgetragen -- bisher unversioniert.
Zahlen: pruef-nachwuchs 260, pruef-entwicklung 48,
pruef-personen-formular 43, pruef-zeichen 7 (neu), pruef-ports 8.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
/* =====================================================================
|
||||
nachtlauf.mjs — einmal in der Nacht alles prüfen und es AUFSCHREIBEN
|
||||
|
||||
Filipe am 23.09.2026 auf die Frage, ob ein nächtlicher Lauf die
|
||||
Prüfungen selbst anstoßen und morgens sagen soll, welche rot sind:
|
||||
„ja".
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WARUM ES DAS BRAUCHT — drei Funde an EINEM Tag
|
||||
|
||||
Am 23.09.2026 sind mir drei Prüfungen aufgefallen, die rot waren,
|
||||
ohne dass es jemandem aufgefallen wäre:
|
||||
|
||||
pruef-modi-wortleck seit dem 22.09. — der verborgene Rollenname
|
||||
stand offen im Netz
|
||||
pruef-zuteilung stürzte ab und prüfte danach gar nichts mehr
|
||||
pruef-push-ziel meldete einen Befund, der in Wahrheit eine
|
||||
erfüllte Bestellung war
|
||||
|
||||
Alle drei habe ich durch ZUFALL gefunden — weil sie neben dem lagen,
|
||||
woran ich gerade gebaut habe. Prüfungen laufen nur, wenn jemand sie
|
||||
startet, und man startet die, an denen man arbeitet.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WOHIN DAS ERGEBNIS GEHT — und wohin ausdrücklich NICHT
|
||||
|
||||
In eine Notiz im Vault. NICHT in die laufende Anwendung.
|
||||
|
||||
Das ist keine Bequemlichkeit, sondern die Lehre vom 04.09.2026: Ein
|
||||
Prüflauf ist kein harmloser Lesevorgang, sein Ergebnis wird
|
||||
VERÖFFENTLICHT. Damals hat ein Netzproblem bei npm einen nächtlichen
|
||||
Selbsttest rot gemacht, und ab da stand VanVan in roter Schrift auf
|
||||
dem Startbildschirm, die Buchführung sei womöglich verletzt. An der
|
||||
Buchführung war nichts.
|
||||
|
||||
Wer einen Prüflauf anstößt, muss vorher überlegen, was der
|
||||
SCHLECHTESTE Ausgang auf dem Bildschirm einer Nutzerin anrichtet.
|
||||
Hier richtet er nichts an: Er schreibt eine Notiz, die Filipe und ich
|
||||
lesen. Die Community sieht davon nichts.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
DREI AUSGÄNGE, NICHT ZWEI
|
||||
|
||||
in Ordnung alles grün
|
||||
nicht in Ordnung mindestens eine Datei rot
|
||||
KONNTE NICHT NACHSEHEN der Lauf hat gar nicht stattgefunden
|
||||
|
||||
Der dritte ist der wichtige. Wenn der Rechner nachts aus war, gibt es
|
||||
kein Ergebnis — und ein FEHLENDES Ergebnis sieht sonst genauso aus
|
||||
wie ein altes gutes. Die Notiz sagt deshalb immer, WANN zuletzt
|
||||
wirklich gemessen wurde, und ab zwei Tagen ohne Lauf steht das ganz
|
||||
oben.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
DIE ZAHL WIRD MIT GESTERN VERGLICHEN
|
||||
|
||||
Ein grüner Lauf ist kein Beweis, solange nicht auch die ANZAHL
|
||||
stimmt (28.08.2026, RunOne: 789 statt 804 Prüfungen, weil acht je
|
||||
Gerät stillschweigend weggefallen waren). Diese Datei merkt sich
|
||||
deshalb den letzten Stand und meldet jeden Rückgang — auch wenn
|
||||
alles grün ist.
|
||||
|
||||
AUFRUF:
|
||||
node tools/nachtlauf.mjs normal
|
||||
node tools/nachtlauf.mjs --jetzt auch wenn gerade gearbeitet wird
|
||||
===================================================================== */
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import {
|
||||
writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync, statSync,
|
||||
} from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const HIER = dirname(fileURLToPath(import.meta.url));
|
||||
const WURZEL = join(HIER, "..");
|
||||
|
||||
/* Der Vault -- dort liest Filipe. Absichtlich fest und nicht geraten:
|
||||
Wenn der Ordner umzieht, soll das auffallen und nicht still in einen
|
||||
neuen Ordner schreiben, den niemand aufmacht. */
|
||||
const VAULT = "C:/Users/qciga/Documents/Obelix/DogFather";
|
||||
const NOTIZ = join(VAULT, "02 Projekte", "Pruefstand.md");
|
||||
const ZUSTAND = join(WURZEL, "tools", ".nachtlauf-stand.json");
|
||||
const SCHLOSS = join(WURZEL, "tools", ".nachtlauf-laeuft");
|
||||
|
||||
/* ZWEI LÄUFE GLEICHZEITIG SIND KEIN LAUF, SONDERN ZWEI KAPUTTE
|
||||
(06.09.2026, RunOne). Beide belegen dieselben Ports und legen
|
||||
dieselben Testdaten an; heraus kommen Fehler, die es nicht gibt --
|
||||
und wer sie für echt hält, sucht stundenlang an Stellen, an denen
|
||||
nichts kaputt ist.
|
||||
|
||||
DREI STUNDEN, dann gilt das Schloss als vergessen. Ein Schloss, das
|
||||
nie verfällt, blockiert beim nächsten Stromausfall für immer -- und
|
||||
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())}`;
|
||||
}
|
||||
|
||||
function schlossNehmen() {
|
||||
if (existsSync(SCHLOSS)) {
|
||||
let alter = Infinity;
|
||||
try { alter = Date.now() - statSync(SCHLOSS).mtimeMs; } catch { /* egal */ }
|
||||
if (alter < SCHLOSS_FRIST_MS) {
|
||||
let wer = "";
|
||||
try { wer = readFileSync(SCHLOSS, "utf8").trim(); } catch { /* egal */ }
|
||||
return { ok: false, grund: `Es läuft schon einer (seit ${Math.round(alter / 60000)} min, ${wer}).` };
|
||||
}
|
||||
/* Älter als die Frist: Der gehört niemandem mehr. */
|
||||
try { unlinkSync(SCHLOSS); } catch { /* egal */ }
|
||||
}
|
||||
try {
|
||||
writeFileSync(SCHLOSS, `PID ${process.pid}, gestartet ${zeitWort()}\n`, { flag: "wx" });
|
||||
return { ok: true };
|
||||
} catch {
|
||||
return { ok: false, grund: "Das Schloss ließ sich nicht setzen." };
|
||||
}
|
||||
}
|
||||
function schlossAbgeben() { try { unlinkSync(SCHLOSS); } catch { /* egal */ } }
|
||||
|
||||
function standLesen() {
|
||||
try { return JSON.parse(readFileSync(ZUSTAND, "utf8")); } catch { return null; }
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
Der Lauf selbst -- alles-pruefen.mjs macht die eigentliche Arbeit.
|
||||
|
||||
KEIN ZWEITER LÄUFER: Dort stecken die Frist je Datei, das Zählen der
|
||||
Einzelpunkte und die Regel "null Prüfungen ist ein Fehler". Das hier
|
||||
noch einmal zu bauen hiesse, zwei Rechnungen über dieselbe Frage zu
|
||||
haben -- und eine davon wäre irgendwann anders.
|
||||
--------------------------------------------------------------------- */
|
||||
function laufen() {
|
||||
return new Promise((fertig) => {
|
||||
const start = Date.now();
|
||||
const kind = spawn(process.execPath, [join("tools", "alles-pruefen.mjs")],
|
||||
{ cwd: WURZEL, stdio: ["ignore", "pipe", "pipe"] });
|
||||
let aus = "";
|
||||
kind.stdout.on("data", (d) => { aus += d; });
|
||||
kind.stderr.on("data", (d) => { aus += d; });
|
||||
kind.on("error", (f) => fertig({ ging: false, grund: String(f?.message || f), aus }));
|
||||
kind.on("close", (code) => fertig({
|
||||
ging: true, code, aus,
|
||||
minuten: Math.round((Date.now() - start) / 60000),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/** Aus der Ausgabe die Zahlen holen -- gelesen, nicht geschätzt. */
|
||||
function auswerten(aus) {
|
||||
const dateien = aus.match(/Dateien:\s+(\d+) von (\d+)/);
|
||||
const punkte = aus.match(/Einzelpunkte:\s+(\d+) von (\d+)/);
|
||||
const rote = [];
|
||||
for (const treffer of aus.matchAll(/^### (pruef-[\w-]+\.mjs) \(Exitcode (-?\d+)\)$/gm)) {
|
||||
/* Die erste FEHL-Zeile darunter ist die Auskunft, die man braucht --
|
||||
der Rest ist Zusammenhang. */
|
||||
const ab = aus.indexOf(treffer[0]) + treffer[0].length;
|
||||
const stueck = aus.slice(ab, ab + 900);
|
||||
const erste = (stueck.split("\n").find((z) => /FEHL|Error|error:/.test(z)) || "").trim();
|
||||
rote.push({ datei: treffer[1], code: Number(treffer[2]), erste: erste.slice(0, 160) });
|
||||
}
|
||||
return {
|
||||
dateienOk: dateien ? Number(dateien[1]) : null,
|
||||
dateien: dateien ? Number(dateien[2]) : null,
|
||||
punkteOk: punkte ? Number(punkte[1]) : null,
|
||||
punkte: punkte ? Number(punkte[2]) : null,
|
||||
rote,
|
||||
};
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
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("");
|
||||
zeilen.push(r.erste ? `\`\`\`\n${r.erste}\n\`\`\`` : "*(keine Fehlerzeile gefunden)*");
|
||||
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
|
||||
--------------------------------------------------------------------- */
|
||||
const vorher = standLesen();
|
||||
const erzwingen = process.argv.includes("--jetzt");
|
||||
|
||||
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");
|
||||
console.log("Konnte nicht nachsehen: " + schloss.grund);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let ergebnis;
|
||||
try {
|
||||
console.log("Nachtlauf gestartet — das dauert. " + zeitWort());
|
||||
ergebnis = await laufen();
|
||||
} finally {
|
||||
if (!erzwingen) schlossAbgeben();
|
||||
}
|
||||
|
||||
if (!ergebnis.ging) {
|
||||
mkdirSync(dirname(NOTIZ), { recursive: true });
|
||||
writeFileSync(NOTIZ, notizSchreiben({
|
||||
vorher, konnteNicht: `Der Läufer ließ sich nicht starten: ${ergebnis.grund}`,
|
||||
}), "utf8");
|
||||
console.log("Konnte nicht nachsehen: " + ergebnis.grund);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const lage = auswerten(ergebnis.aus);
|
||||
|
||||
/* KONNTE DER LAUF ÜBERHAUPT ETWAS MESSEN? Steht keine Zahl in der
|
||||
Ausgabe, ist etwas anderes schiefgegangen als eine rote Prüfung --
|
||||
und "0 von 0 in Ordnung" wäre die beruhigendste Lüge von allen. */
|
||||
if (lage.dateien == null || lage.punkte == null || lage.punkte === 0) {
|
||||
mkdirSync(dirname(NOTIZ), { recursive: true });
|
||||
writeFileSync(NOTIZ, notizSchreiben({
|
||||
vorher,
|
||||
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");
|
||||
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");
|
||||
|
||||
/* Der Stand für den nächsten Vergleich -- erst NACH der Notiz, damit
|
||||
ein Absturz beim Schreiben nicht den alten Vergleichswert wegnimmt. */
|
||||
writeFileSync(ZUSTAND, JSON.stringify({
|
||||
wann: zeitWort(), zeit: Date.now(),
|
||||
dateien: lage.dateien, dateienOk: lage.dateienOk,
|
||||
punkte: lage.punkte, punkteOk: lage.punkteOk,
|
||||
rote: lage.rote.map((r) => r.datei),
|
||||
}, null, 1), "utf8");
|
||||
|
||||
/* Die ganze Ausgabe daneben -- für den Fall, dass die Notiz nicht
|
||||
reicht. Sie wird jedes Mal überschrieben; zwei Läufe zu vergleichen
|
||||
ist Sache der Zahlen oben, nicht eines wachsenden Protokolls. */
|
||||
writeFileSync(join(WURZEL, "tools", ".nachtlauf-letzte-ausgabe.txt"), ergebnis.aus, "utf8");
|
||||
|
||||
console.log(`Fertig nach ${ergebnis.minuten} min: `
|
||||
+ `${lage.dateienOk} von ${lage.dateien} Dateien, `
|
||||
+ `${lage.punkteOk} von ${lage.punkte} Einzelprüfungen.`);
|
||||
console.log(`Ergebnis steht in: ${NOTIZ}`);
|
||||
process.exit(lage.rote.length ? 1 : 0);
|
||||
Reference in New Issue
Block a user