Filipe, zum Bildschirmfoto der Kachel: "ich will dass du diese seite
perfektionnierst den gerade wenn ich drauf druecke geht die
entwicklungsseite auf."
Der Fehler war schlimmer als ein falscher Verweis. Auf der Kachel
steht "die Antworten sieht nur du" -- und sie oeffnete eine Seite
voller Namen und Beobachtungen ueber ANDERE Menschen. Wer ihr glaubt
und drauftippt, sieht im ersten Moment das Gegenteil dessen, was
draufsteht.
Jetzt: eine Seite, ein Zweck.
workspace/befinden.html + assets/js/befinden.js die eigene Seite
server/workspace-befinden.js Rhythmus + Verlauf
entwicklung.html nur noch ein Verweis
Was die Recherche zu Puls-Abfragen ergeben hat (Quellen im Kopf des
Moduls): kurz halten (fuenf bis fuenfzehn Fragen -- sechs bleiben
sechs), WIEDERHOLEN (zweiwoechentlich), und der VERLAUF ist die
Auskunft, nicht der Einzelwert. Bisher beantwortete man die sechs
Fragen einmal, und die Antwort stand fuer immer -- ein Befinden von
vor drei Monaten ist keine Auskunft mehr, sondern ein Andenken.
Der Verlauf gehoert ihr allein: eigene Tabelle, kein von_id, kein
anderer Weg im Haus liest sie. Im Protokoll steht nur, DASS eine
Runde war, nie was darin stand. Die Ampel zaehlt weiterhin nur den
aktuellen Stand und kennt keine Namen.
Zwei Dinge, die erst die Pruefung gefunden hat:
- Wer die neue Seite oeffnete, ohne dass vorher jemand die
Entwicklungsseite besucht hatte, bekam "no such table" und eine
503. entwicklung_stand wird beim ersten Aufruf angelegt, nicht
beim Start. Die Tabelle wird jetzt angefordert, nicht ein zweites
Mal abgeschrieben.
- pruef-entwicklung suchte die Kachel ueber ziel ===
"entwicklung.html" und hat den gemeldeten Fehler damit
mitgetragen. Sie sucht jetzt die eigene Seite -- und prueft
zusaetzlich, dass keine Kachel mehr ersatzweise dorthin fuehrt.
Ausserdem weg: die tote Funktion meins() in entwicklung.js (sie
zeichnete in drei Stellen, die es nicht mehr gibt) und der Titel
"Wie geht's dir?", den diese Seite fuer einen Modi trug -- er
versprach etwas anderes als die Seite zeigt, also derselbe Fehler
wie an der Kachel.
server/pruef-befinden.mjs 48 Pruefungen, 0 Fehler (Port 4419)
pruef-entwicklung 43 (vorher 40), 0 Fehler
pruef-nachwuchs 123, pruef-rechtetafel 19, pruef-rechte-umstellen 46,
pruef-css-klassen -- alle 0 Fehler
Co-Authored-By: Claude Opus 5 <[email protected]>
332 lines
16 KiB
JavaScript
332 lines
16 KiB
JavaScript
/* ENTWICKLUNG UND TALENTE (11.09.2026)
|
|
|
|
Filipe: "ich will dass ich genau so eine kategorie habe in der
|
|
dogfather und rechten hand rollen, wo wir aufgaben oder bewertungen
|
|
über modis eingeben können. auch für zuschauer die vielleicht modis
|
|
werden könnten wäre auch geil."
|
|
|
|
---------------------------------------------------------------------
|
|
DIE PRÜFUNG, DERENTWEGEN ES DIESE DATEI GIBT
|
|
|
|
In „Entwicklung" steht, was über Menschen aufgeschrieben wird. Wenn
|
|
diese beiden Bereiche irgendwo durchsickern, ist der Schaden nicht
|
|
technisch, sondern menschlich -- und er lässt sich nicht zurücknehmen.
|
|
|
|
Zwei Schlösser sichern das: der ZUGANG (fuehrtTeamDogi) und die
|
|
SICHTBARKEIT der Zeilen. Geprüft werden beide, und zwar von der
|
|
falschen Seite aus: Ein Modi klopft an die Liste, an eine einzelne
|
|
Zeile, ans Ändern und ans Löschen -- und muss überall dieselbe
|
|
Antwort bekommen wie jemand, für den es diese Bereiche nicht gibt.
|
|
|
|
Dazu die Brücke: Ein Eintrag lässt sich EINMAL an die Person
|
|
schicken. Gemessen wird, dass die Nachricht wirklich in ihrem Chat
|
|
ankommt (sonst wäre „geschickt" nur ein Datum) und dass ein zweiter
|
|
Versuch abgelehnt wird.
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { request } from "node:http";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-entw-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
|
|
const { portMussFreiSein } = await import("./helfer-port.mjs");
|
|
await portMussFreiSein(4389, "die Entwicklungspruefung");
|
|
|
|
process.env.PORT = "4389";
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
|
|
|
const express = (await import("express")).default;
|
|
const ec = express.response.cookie;
|
|
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
|
|
await import("./index.js");
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
|
|
for (const art of ["uncaughtException", "unhandledRejection"]) {
|
|
process.on(art, (f) => { console.error(""); console.error("ABBRUCH:", f?.stack || f); process.exit(1); });
|
|
}
|
|
setTimeout(() => { console.error(""); console.error("ABBRUCH: 150 s vorbei."); process.exit(1); },
|
|
150000).unref?.();
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const WS = "workspace.dogfather-universe.com";
|
|
|
|
function hole(pfad, { host = WS, methode = "GET", rumpf = null, keks = "" } = {}) {
|
|
return new Promise((fertig, schief) => {
|
|
const kopf = { Host: host };
|
|
if (rumpf) { kopf["Content-Type"] = "application/json"; kopf["Content-Length"] = Buffer.byteLength(rumpf); }
|
|
if (keks) kopf.Cookie = keks;
|
|
const a = request({ host: "127.0.0.1", port: 4389, path: pfad, method: methode, headers: kopf },
|
|
(antwort) => {
|
|
let text = "";
|
|
antwort.setEncoding("utf8");
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => {
|
|
let daten = null;
|
|
try { daten = JSON.parse(text); } catch { /* kein JSON */ }
|
|
fertig({ status: antwort.statusCode, text, daten,
|
|
keks: (antwort.headers["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ") });
|
|
});
|
|
});
|
|
a.on("error", schief);
|
|
a.setTimeout(15000, () => { a.destroy(new Error("Zeitueberschreitung")); });
|
|
if (rumpf) a.write(rumpf);
|
|
a.end();
|
|
});
|
|
}
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
await hole("/workspace/api/anmelden", { methode: "POST",
|
|
rumpf: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
|
|
for (let i = 0; i < 60; i++) {
|
|
try {
|
|
const p = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); }
|
|
} catch { await new Promise((r) => setTimeout(r, 250)); }
|
|
}
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
function anlegen(name, rolle, code) {
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
|
|
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768, jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
anlegen("VanVan", "hand", "CODE-HAND-0001");
|
|
const idModi = anlegen("Ayla", "modi", "CODE-TEAM-0001");
|
|
anlegen("Cigdem", "manager", "CODE-MANA-0001");
|
|
|
|
async function anmelden(rolle, code, wand) {
|
|
const a = await hole("/workspace/api/anmelden", { host: wand, methode: "POST",
|
|
rumpf: JSON.stringify({ rolle, code }) });
|
|
return { ok: a.status === 200, keks: a.keks };
|
|
}
|
|
const dogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
|
|
const hand = await anmelden("hand", "CODE-HAND-0001", CREW);
|
|
const ayla = await anmelden("modi", "CODE-TEAM-0001", CREW);
|
|
const mana = await anmelden("manager", "CODE-MANA-0001", WS);
|
|
ok(dogi.ok && hand.ok && ayla.ok && mana.ok, "alle vier sind angemeldet");
|
|
|
|
const ENTW = "/workspace/api/bereich/entwicklung";
|
|
const TAL = "/workspace/api/bereich/talente";
|
|
|
|
/* =======================================================================
|
|
1. Es gibt die beiden Bereiche -- und ihre Felder
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Die beiden Bereiche ===");
|
|
{
|
|
const a = await hole(ENTW, { host: CREW, keks: dogi.keks });
|
|
ok(a.daten?.einstellung?.name === "Entwicklung",
|
|
`DogFather kann Entwicklung oeffnen (${a.daten?.einstellung?.name})`);
|
|
const arten = Object.keys(a.daten?.einstellung?.arten || {});
|
|
ok(arten.length === 5, `fuenf Arten (${arten.join(", ")})`);
|
|
/* DIE REIHENFOLGE IST ABSICHT: Wer ein Formular oeffnet, dessen
|
|
erstes Feld "Problem" heisst, schreibt Probleme auf. */
|
|
ok(arten[0] === "staerke" && arten[1] === "dank",
|
|
"und die ersten beiden sind die guten");
|
|
ok(arten.includes("last"),
|
|
"„Zu viel gerade“ ist dabei -- der haeufigste Grund aufzuhoeren");
|
|
|
|
/* DAS NOTIZBRETT MUSS ERREICHBAR BLEIBEN -- gerade WEIL die Kachel
|
|
nicht mehr darauf zeigt.
|
|
|
|
Genau hier ist am 11.09.2026 etwas kaputtgegangen: Die Liste der
|
|
Bretter, die ein Modi oder die rechte Hand oeffnen darf, wird aus
|
|
den KACHELZIELEN abgeleitet. Als die Kacheln auf die Katalogseiten
|
|
umgestellt wurden, fielen beide Bretter aus der Liste -- und die
|
|
Seite verlinkte auf ein 404. Kein Fehler im Riegel, sondern in
|
|
seiner Quelle.
|
|
|
|
Diese Zeile ist die einzige, die das gemerkt hat. */
|
|
const b = await hole(TAL, { host: CREW, keks: hand.keks });
|
|
ok(b.daten?.einstellung?.name === "Talente",
|
|
`die rechte Hand kann Talente oeffnen (${b.daten?.einstellung?.name})`);
|
|
const bE = await hole(ENTW, { host: CREW, keks: hand.keks });
|
|
ok(bE.daten?.einstellung?.name === "Entwicklung",
|
|
`und das Notizbrett Entwicklung auch (${bE.daten?.einstellung?.name})`);
|
|
const tArten = Object.keys(b.daten?.einstellung?.arten || {});
|
|
ok(tArten.includes("empfohlen"),
|
|
`die Empfehlung aus dem Team ist dabei (${tArten.join(", ")})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. DIE EIGENTLICHE PRUEFUNG: Wer NICHT hineinkommt
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Wer nicht hineinkommt ===");
|
|
let eintragId = 0;
|
|
{
|
|
/* Erst ein Eintrag, damit es ueberhaupt etwas zu verbergen gibt --
|
|
sonst waere jede Sperre unten gruen ueber einer leeren Liste. */
|
|
const a = await hole(ENTW, { host: CREW, methode: "POST", keks: dogi.keks,
|
|
rumpf: JSON.stringify({
|
|
art: "staerke", titel: "Ruhig geblieben, als es kippte",
|
|
text: "Hat den Streit im Chat gedreht, ohne jemanden bloßzustellen.",
|
|
datum: "2026-09-11", creator_id: idModi, einsatz: "Beim nächsten Live wieder ihr überlassen",
|
|
}) });
|
|
eintragId = a.daten?.id;
|
|
ok(a.status === 201 && eintragId > 0,
|
|
`ein Eintrag ueber Ayla (${a.status}) ${a.status === 201 ? "" : a.text.slice(0, 160)}`);
|
|
|
|
for (const [wer, keks, wand] of [["ein Teammitglied", ayla.keks, CREW],
|
|
["ein Manager", mana.keks, WS]]) {
|
|
const liste = await hole(ENTW, { host: wand, keks });
|
|
ok(liste.status === 404, `${wer}: die Liste gibt es nicht (${liste.status})`);
|
|
const aendern = await hole(`${ENTW}/${eintragId}`, { host: wand, methode: "PATCH",
|
|
keks, rumpf: JSON.stringify({ titel: "umgeschrieben" }) });
|
|
ok(aendern.status === 404, `${wer}: aendern geht nicht (${aendern.status})`);
|
|
const weg = await hole(`${ENTW}/${eintragId}`, { host: wand, methode: "DELETE", keks });
|
|
ok(weg.status === 404, `${wer}: loeschen auch nicht (${weg.status})`);
|
|
const tal = await hole(TAL, { host: wand, keks });
|
|
ok(tal.status === 404, `${wer}: Talente ebenfalls nicht (${tal.status})`);
|
|
}
|
|
|
|
/* Und die Zeile steht unveraendert da -- sonst waeren die 404 oben
|
|
Antworten auf Anfragen, die trotzdem gewirkt haben. */
|
|
const heil = d.prepare("SELECT titel FROM eintraege WHERE id = ?").get(eintragId);
|
|
ok(heil?.titel === "Ruhig geblieben, als es kippte", "der Eintrag ist unveraendert");
|
|
|
|
/* GEGENPROBE: Die beiden, die duerfen, sehen ihn sehr wohl. Ohne sie
|
|
hiesse "niemand sieht ihn" auch dann gruen, wenn er gar nicht
|
|
angekommen waere. */
|
|
for (const [wer, keks] of [["DogFather", dogi.keks], ["die rechte Hand", hand.keks]]) {
|
|
const a = await hole(ENTW, { host: CREW, keks });
|
|
ok((a.daten?.eintraege || []).some((e) => e.id === eintragId),
|
|
`${wer} sieht ihn`);
|
|
}
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. Die Bruecke: einmal an die Person schicken
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== An die Person schicken ===");
|
|
{
|
|
const a = await hole(`${ENTW}/${eintragId}/senden`, { host: CREW, methode: "POST",
|
|
keks: dogi.keks, rumpf: "{}" });
|
|
ok(a.status === 200, `geschickt (${a.status})`);
|
|
|
|
/* KOMMT SIE WIRKLICH AN? Ohne diese Zeile waere "geschickt" nur ein
|
|
Datum in einer Spalte. Nachgesehen wird in AYLAS Chat, nicht in
|
|
der Antwort der Route. */
|
|
const raeume = await hole("/workspace/api/chat/raeume", { host: CREW, keks: ayla.keks });
|
|
const raum = (raeume.daten?.raeume || [])[0];
|
|
ok(!!raum, `Ayla hat ein Gespraech (${(raeume.daten?.raeume || []).length})`);
|
|
const verlauf = await hole(`/workspace/api/chat/raeume/${raum?.id}/nachrichten`,
|
|
{ host: CREW, keks: ayla.keks });
|
|
const texte = (verlauf.daten?.nachrichten || []).map((n) => n.text || "").join(" ");
|
|
ok(/Ruhig geblieben, als es kippte/.test(texte), "und die Nachricht steht darin");
|
|
ok(/Das läuft gut/.test(texte),
|
|
"mit der Art davor -- sonst waere es ein Fetzen ohne Zusammenhang");
|
|
ok(/Beim nächsten Live wieder ihr überlassen/.test(texte),
|
|
"und dem, was daraus folgen soll");
|
|
|
|
/* EINMAL IST EINMAL. Zweimal dasselbe Lob ist kein doppeltes Lob. */
|
|
const b = await hole(`${ENTW}/${eintragId}/senden`, { host: CREW, methode: "POST",
|
|
keks: dogi.keks, rumpf: "{}" });
|
|
ok(b.status === 409, `ein zweites Mal wird abgelehnt (${b.status})`);
|
|
const nachher = await hole(`/workspace/api/chat/raeume/${raum?.id}/nachrichten`,
|
|
{ host: CREW, keks: ayla.keks });
|
|
ok((nachher.daten?.nachrichten || []).length === (verlauf.daten?.nachrichten || []).length,
|
|
"und es kam keine zweite Nachricht an");
|
|
|
|
/* Ein Eintrag ohne Person laesst sich nicht schicken -- an wen auch. */
|
|
const ohne = await hole(ENTW, { host: CREW, methode: "POST", keks: dogi.keks,
|
|
rumpf: JSON.stringify({ art: "wachsen", titel: "Allgemeine Notiz", datum: "2026-09-11" }) });
|
|
const c = await hole(`${ENTW}/${ohne.daten?.id}/senden`, { host: CREW, methode: "POST",
|
|
keks: dogi.keks, rumpf: "{}" });
|
|
ok(c.status === 400, `ein Eintrag ohne Person geht nicht raus (${c.status})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. Die Kacheln
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Die Kacheln ===");
|
|
{
|
|
/* DIE KACHELN ZEIGEN SEIT DEM 11.09.2026 AUF DIE KATALOGSEITEN.
|
|
|
|
Vorher stand hier `b=(entwicklung|talente)` -- das Ziel war ein
|
|
Brett. Filipe: "ich will dass fertige aufgaben da stehen und so".
|
|
Seither fuehrt die Kachel auf entwicklung.html bzw. talente.html;
|
|
das Notizbrett gibt es weiter und ist von dort verlinkt.
|
|
|
|
GEPRUEFT WIRD DER NAME DER SEITE UND NICHT MEHR DAS BRETT -- eine
|
|
Kachel, die auf ein Brett zeigt, waere ab jetzt der Rueckschritt,
|
|
den diese Zeile melden soll. */
|
|
for (const [wer, keks] of [["DogFather", dogi.keks], ["die rechte Hand", hand.keks]]) {
|
|
const a = await hole("/workspace/api/ich", { host: CREW, keks });
|
|
const alle = [...(a.daten?.bereiche || []), ...(a.daten?.bereiche_zusatz || [])];
|
|
/* NACH DEM NAMEN UND NICHT NACH DEM ZIEL.
|
|
|
|
Seit dem 11.09.2026 zeigen DREI Kacheln auf entwicklung.html: die
|
|
beiden Katalogkacheln und „Wie geht's dir?" -- dieselbe Seite,
|
|
drei verschiedene Zwecke, weil der Server entscheidet, was ein
|
|
Modi dort sieht. Ein Filter nach dem Ziel zaehlt sie alle und
|
|
meldet einen Fehler, wo keiner ist.
|
|
|
|
Der Name ist hier die ehrlichere Frage: Es geht um DIESE zwei
|
|
Kacheln, nicht um alles, was zufaellig dieselbe Adresse hat. */
|
|
const beide = alle.filter((k) => ["Entwicklung", "Talente"].includes(k.name));
|
|
ok(beide.length === 2, `${wer}: beide Kacheln (${beide.map((k) => k.name).join(", ") || "keine"})`);
|
|
ok(beide.every((k) => k.gruppe === "Entwicklung & Nachwuchs"),
|
|
`${wer}: in einer eigenen Gruppe (${[...new Set(beide.map((k) => k.gruppe))].join(", ")})`);
|
|
}
|
|
/* DIE DRITTE KACHEL -- und sie gehoert ausdruecklich ALLEN im Team.
|
|
|
|
„Wie geht's dir?" ist die einzige Kachel des Hauses, deren Inhalt
|
|
niemand ausser der Person selbst je sieht. Sie steht auch bei
|
|
DogFather und der rechten Hand: Wenn es jemandem zu viel wird,
|
|
dann ihnen genauso. Eine Kachel, die nur die anderen ausfuellen,
|
|
waere genau die Schieflage, die dieses Haus nicht haben soll. */
|
|
for (const [wer, keks] of [["DogFather", dogi.keks], ["die rechte Hand", hand.keks],
|
|
["ein Modi", ayla.keks]]) {
|
|
const x = await hole("/workspace/api/ich", { host: CREW, keks });
|
|
const alleX = [...(x.daten?.bereiche || []), ...(x.daten?.bereiche_zusatz || [])];
|
|
/* SEIT DEM 15.09.2026 HAT SIE EINE EIGENE SEITE.
|
|
|
|
Bis dahin zeigte sie auf entwicklung.html -- und damit auf Namen
|
|
und Beobachtungen ueber ANDERE, waehrend auf der Kachel steht
|
|
"die Antworten sieht nur du". Filipe hat es gemeldet, und die
|
|
Zeile hier hat den Fehler mitgetragen, weil sie genau danach
|
|
gesucht hat. */
|
|
const befinden = alleX.find((k) => k.ziel === "befinden.html");
|
|
ok(!!befinden, `${wer} hat „${befinden?.name || "—"}"`);
|
|
ok(befinden?.gruppe === "Für dich",
|
|
`und sie steht bei „Für dich" (${befinden?.gruppe})`);
|
|
/* GEGENPROBE: Sie fuehrt NICHT mehr dorthin, wo andere Menschen
|
|
stehen -- sonst waere der gemeldete Fehler beim naechsten Umbau
|
|
still wieder da. */
|
|
ok(!alleX.some((k) => k.ziel === "entwicklung.html"
|
|
&& !["Entwicklung", "Talente"].includes(k.name)),
|
|
`${wer}: keine Kachel fuehrt mehr ersatzweise auf die Entwicklungsseite`);
|
|
}
|
|
|
|
const a = await hole("/workspace/api/ich", { host: CREW, keks: ayla.keks });
|
|
const alle = [...(a.daten?.bereiche || []), ...(a.daten?.bereiche_zusatz || [])];
|
|
ok(!alle.some((k) => ["Entwicklung", "Talente"].includes(k.name)),
|
|
`ein Teammitglied bekommt sie nicht angeboten (${alle.length} Kacheln)`);
|
|
|
|
/* Kein Farbton doppelt -- zwei Kacheln in derselben Farbe sind ein
|
|
Fehler, den man sonst erst am Bildschirm findet. */
|
|
const dogiIch = await hole("/workspace/api/ich", { host: CREW, keks: dogi.keks });
|
|
const toene = [...(dogiIch.daten?.bereiche || [])].map((k) => k.ton).filter((t) => t !== undefined);
|
|
ok(new Set(toene).size === toene.length, `kein Farbton doppelt (${toene.length} Kacheln)`);
|
|
}
|
|
|
|
d.close();
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
|
console.log("");
|
|
console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"} -- ${geprueft} geprueft, ${fehler} Fehler`);
|
|
process.exit(fehler ? 1 : 0);
|