Filipe: „die rechte hand soll das auch sehen. und die selben rechte
da haben wie dogfather. das einzige was sie nicht kann ist die
dogfather rolle oder leute anfassen. also da kann sie nichts
verändern."
ZUERST EIN IRRTUM VON MIR. Ich hatte den Bildschirmfoto-Ausschnitt
fuer die Rechtetafel gehalten und dort gebaut. „Vertritt dich im
Alltag und koordiniert das Team" steht aber in workspace-personen.js:
Gemeint war die PERSONENSEITE. Die Arbeit an der Rechtetafel ist
trotzdem drin (siehe unten) -- sie loeste dasselbe Problem an einer
zweiten Stelle.
=== DIE PERSONENSEITE ===
DIE OBERFLAECHE WAR STRENGER ALS DER SERVER. In personen.js stand
`if (ich.rolle === 'hand') { keine Knoepfe }` mit der Begruendung
„Der Server antwortet ihr auf jeden davon mit 404". Am 22.09. stimmte
das. Seither wurde der Server ZWEIMAL erweitert -- sie durfte
Personen anlegen, Codes neu erzeugen und Rollen aendern -- und diese
Zeile blieb stehen. Sie hatte drei Rechte und sah keinen einzigen
Knopf. Ein Rollenvergleich im Browser ist genau die zweite Wahrheit,
die still veraltet.
Jetzt fragt die Oberflaeche den Server (`darf_zugaenge_verwalten`,
`darf_personen_loeschen`, `rollen_anfassbar`). Dazu kommen SPERREN
und LOESCHEN, die bis heute ausdruecklich bei DogFather lagen --
Filipes „das einzige" ist juenger und eindeutig.
Die Knoepfe „Neuer Code" und „Sperren" standen inline im
DogFather-Zweig; sie sind jetzt Funktionen und werden von beiden
Stellen benutzt. Eine zweite Abschrift waere die geworden, die beim
naechsten Umbau nur halb nachgezogen wird.
ZWEI ECHTE LOECHER FAND DIE NEUE PRUEFUNG:
* `PUT /personen/:id/rolle` hatte `nurDogFatherBeiLeitung` NICHT.
Bis heute folgenlos; mit den neuen Rechten konnte die rechte Hand
darueber die Rolle eines MANAGERS aendern -- waehrend derselbe
Manager fuer DogFather auf crew. gar nicht in der Liste steht.
Zwei Wege, zwei Antworten, und der laxere galt fuer die Rolle mit
weniger Rechten.
* Die Loesch-Route hatte dieselbe Middleware ebenfalls nicht. Das
war harmlos, solange die Route selbst nur DogFather durchliess --
seit die rechte Hand loescht, ist es die Stelle, an der DogFather
geschuetzt wird.
=== DIE RECHTETAFEL (nicht bestellt, aber dasselbe Problem) ===
Dort durfte sie sehen, aber nichts umstellen. Jetzt umstellen wie
DogFather -- ausser der Spalte „DogFather" und der Zeile „Personen &
Zugaenge" (wer die freischaltet, hat Zugaenge vergeben, ohne einen
anzulegen). Zuruecksetzen bleibt bei DogFather: Der Knopf naehme
genau diese zwei Sperren mit, und eine Sperre, die ein zweiter Knopf
daneben aufhebt, ist keine.
Sie kann sich auch selbst nicht aussperren -- das hat er nicht
gesagt, aber eine Sperre, aus der man sich aussperren kann, ist eine
Falle. Ein festes Feld ist jetzt gar kein Knopf mehr und nennt den
Grund, der fuer DIESE Person gilt.
=== DREI MESSFEHLER VON MIR ===
Ein Manager ist auf crew. fuer die LISTE unsichtbar, fuer DogFathers
direkten Zugriff aber nicht -- ich hielt das eine fuer das andere und
erwartete, dass beide abgewiesen werden. Die Loesch-Vorschau (GET)
fehlte in meinem Waechter. Und `rollen_anfassbar` beantwortet eine
andere Frage als „wen sehe ich".
Gemessen: pruef-hand-personen, 40 Pruefungen, 0 Fehler -- jeder der
fuenf Wege einzeln gegen DogFather und die linke Hand, plus die
Gegenprobe, dass DogFather es kann. pruef-rechte-umstellen von 46 auf
56 Pruefungen. Gruen: pruef-personen-liste, pruef-personen-loeschen,
pruef-personen-kachel, pruef-rollen-anlegen, pruef-rechtetafel.
Co-Authored-By: Claude Opus 5 <[email protected]>
334 lines
16 KiB
JavaScript
334 lines
16 KiB
JavaScript
/* =====================================================================
|
|
DIE RECHTE HAND AUF DER PERSONENSEITE (24.09.2026)
|
|
|
|
Filipe: „die rechte hand soll das auch sehen. und die selben rechte
|
|
da haben wie dogfather. das einzige was sie nicht kann ist die
|
|
dogfather rolle oder leute anfassen. also da kann sie nichts
|
|
verändern."
|
|
|
|
WAS HIER GEPRUEFT WIRD, und warum jede Zeile:
|
|
|
|
* SIE DARF, was DogFather darf -- anlegen, Code, Rolle, sperren,
|
|
loeschen. Bis heute war die Oberflaeche strenger als der Server:
|
|
Sie hatte drei dieser Rechte und sah keinen einzigen Knopf.
|
|
* SIE DARF NICHT an DogFather und an die Manager. Das ist die
|
|
Grenze aus seinem Satz, und sie wird EINZELN geprueft -- fuer
|
|
jeden der fuenf Wege.
|
|
* DER LETZTE DOGFATHER-ZUGANG bleibt. Diese Sicherung stand
|
|
vorher hinter „nur DogFather darf loeschen"; seit die rechte
|
|
Hand loescht, muss sie allein tragen.
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-hp-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
const { eigenerPort } = await import("./helfer-port.mjs");
|
|
const PORT = await eigenerPort(import.meta, "die Hand-Personen-Pruefung");
|
|
|
|
process.env.PORT = `${PORT}`;
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
|
|
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");
|
|
notbremse(240_000, "pruef-hand-personen");
|
|
await new Promise((r) => setTimeout(r, 900));
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
const melde = (t) => console.log(t);
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
const ks = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", ks, jetzt);
|
|
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,"
|
|
+ " code_kennung, aktiv, erstellt) VALUES (?,?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768,
|
|
createHmac("sha256", ks).update(code).digest("hex"), jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
const idHand = anlegen("VanVan", "hand", "CODE-HAND-0001");
|
|
const idLinke = anlegen("Funny", "linke", "CODE-LINK-0001");
|
|
const idModi = anlegen("Miss", "modi", "CODE-MODI-0001");
|
|
const idModi2 = anlegen("Ghost", "modi", "CODE-MODI-0002");
|
|
const idMana = anlegen("Patrick", "manager", "CODE-MANA-0001");
|
|
const idGast = anlegen("Kessi", "gast", "CODE-GAST-0001");
|
|
d.close();
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const { request } = await import("node:http");
|
|
|
|
/** Ein Aufruf mit Hausadresse -- `fetch` verwirft den Host-Kopf. */
|
|
function roh(pfad, keks, koerper, methode) {
|
|
return new Promise((fertig, schief) => {
|
|
const daten = koerper ? JSON.stringify(koerper) : null;
|
|
const a = request({
|
|
host: "127.0.0.1", port: PORT, path: pfad,
|
|
method: methode || (daten ? "POST" : "GET"),
|
|
headers: {
|
|
Host: CREW, Origin: `https://${CREW}`,
|
|
...(keks ? { Cookie: keks } : {}),
|
|
...(daten ? { "Content-Type": "application/json",
|
|
"Content-Length": Buffer.byteLength(daten) } : {}),
|
|
},
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, text }));
|
|
});
|
|
a.on("error", schief);
|
|
if (daten) a.write(daten);
|
|
a.end();
|
|
});
|
|
}
|
|
const json = (r) => { try { return JSON.parse(r.text); } catch { return {}; } };
|
|
|
|
async function anmelden(rolle, code) {
|
|
const r = await new Promise((fertig, schief) => {
|
|
const daten = JSON.stringify({ rolle, code, alter_ok: true });
|
|
const a = request({
|
|
host: "127.0.0.1", port: PORT, path: "/workspace/api/anmelden",
|
|
method: "POST",
|
|
headers: { Host: CREW, Origin: `https://${CREW}`,
|
|
"Content-Type": "application/json", "Content-Length": Buffer.byteLength(daten) },
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, kopf: antwort.headers }));
|
|
});
|
|
a.on("error", schief);
|
|
a.write(daten); a.end();
|
|
});
|
|
if (r.code !== 200) return null;
|
|
return [].concat(r.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
|
|
}
|
|
|
|
const kDogi = await anmelden("admin", "CODE-DOGI-0001");
|
|
const kHand = await anmelden("hand", "CODE-HAND-0001");
|
|
const kLinke = await anmelden("linke", "CODE-LINK-0001");
|
|
const kModi = await anmelden("modi", "CODE-MODI-0001");
|
|
|
|
melde("\n=== 0. Alle vier kommen herein ===");
|
|
ok(!!kDogi && !!kHand && !!kLinke && !!kModi, "DogFather, rechte Hand, linke Hand, Modi");
|
|
|
|
/* =====================================================================
|
|
1. WAS DIE OBERFLAECHE ERFAEHRT
|
|
===================================================================== */
|
|
melde("\n=== 1. Die Auskunft an die Oberflaeche ===");
|
|
{
|
|
const ichDogi = json(await roh("/workspace/api/ich", kDogi));
|
|
const ichHand = json(await roh("/workspace/api/ich", kHand));
|
|
const ichLinke = json(await roh("/workspace/api/ich", kLinke));
|
|
const ichModi = json(await roh("/workspace/api/ich", kModi));
|
|
|
|
ok(ichDogi.darf_zugaenge_verwalten === true, "DogFather verwaltet Zugaenge");
|
|
ok(ichHand.darf_zugaenge_verwalten === true, "die rechte Hand auch");
|
|
ok(ichLinke.darf_zugaenge_verwalten === false,
|
|
"die LINKE Hand nicht -- das war eine eigene Entscheidung vom 21.09.");
|
|
ok(ichModi.darf_zugaenge_verwalten !== true, "ein Modi erst recht nicht");
|
|
|
|
ok(ichHand.darf_personen_loeschen === true, "sie darf loeschen");
|
|
ok(ichLinke.darf_personen_loeschen !== true, "die linke Hand nicht");
|
|
|
|
/* WEN sie anfassen darf -- die Grenze aus Filipes Satz. */
|
|
const anf = ichHand.rollen_anfassbar || [];
|
|
ok(!anf.includes("admin"), `DogFather steht NICHT darin (${anf.join(", ")})`);
|
|
/* DIE MANAGER STEHEN IN `rollen_anfassbar` -- und das ist KEIN
|
|
Mangel, sondern die falsche Frage. `rollen_anfassbar` sagt, welche
|
|
Rollen sie grundsaetzlich aendern duerfte; ob ein bestimmter
|
|
Mensch fuer sie existiert, entscheidet die Haustrennung: Auf
|
|
crew. liefert `hausBedingung` nur Team-Dogi-Rollen, ein Manager
|
|
ist dort fuer NIEMANDEN sichtbar, auch nicht fuer DogFather.
|
|
Gemessen wird das in Abschnitt 3 an den echten Wegen. */
|
|
ok(Array.isArray(anf), `rollen_anfassbar ist eine Liste (${anf.join(", ")})`);
|
|
ok(anf.includes("modi") || anf.includes("gast"),
|
|
"Modis und Community schon -- sonst waere das Recht leer");
|
|
}
|
|
|
|
/* =====================================================================
|
|
2. WAS SIE DARF
|
|
===================================================================== */
|
|
melde("\n=== 2. Was sie darf ===");
|
|
let neuerModi = 0;
|
|
{
|
|
const anlegen2 = await roh("/workspace/api/verwaltung/personen", kHand,
|
|
{ name: "Neue Modi", rolle: "modi" });
|
|
ok(anlegen2.code === 200 || anlegen2.code === 201,
|
|
`sie legt einen Modi an (${anlegen2.code})`);
|
|
neuerModi = json(anlegen2).id || 0;
|
|
ok(!!json(anlegen2).code, "und sieht den Code genau einmal");
|
|
|
|
const code = await roh(`/workspace/api/verwaltung/personen/${idModi}/code`, kHand, {}, "POST");
|
|
ok(code.code === 200, `sie erzeugt einen Code neu (${code.code})`);
|
|
|
|
const rolle = await roh(`/workspace/api/verwaltung/personen/${idModi2}/rolle`, kHand,
|
|
{ rolle: "gast" }, "PUT");
|
|
ok(rolle.code === 200, `sie aendert eine Rolle (${rolle.code})`);
|
|
|
|
const sperren = await roh(`/workspace/api/verwaltung/personen/${idModi}`, kHand,
|
|
{ aktiv: false }, "PATCH");
|
|
ok(sperren.code === 200, `sie sperrt (${sperren.code}) -- das ist neu seit heute`);
|
|
|
|
const entsperren = await roh(`/workspace/api/verwaltung/personen/${idModi}`, kHand,
|
|
{ aktiv: true }, "PATCH");
|
|
ok(entsperren.code === 200, `und entsperrt wieder (${entsperren.code})`);
|
|
|
|
if (neuerModi) {
|
|
const vorschau = await roh(`/workspace/api/verwaltung/personen/${neuerModi}/loeschbar`, kHand);
|
|
ok(vorschau.code === 200, `sie sieht die Loesch-Vorschau (${vorschau.code})`);
|
|
const weg = await roh(`/workspace/api/verwaltung/personen/${neuerModi}`, kHand,
|
|
{ name: "Neue Modi" }, "DELETE");
|
|
ok(weg.code === 200, `sie loescht (${weg.code}) -- das ist neu seit heute`);
|
|
} else {
|
|
ok(false, "kein angelegter Modi zum Loeschen");
|
|
}
|
|
}
|
|
|
|
/* =====================================================================
|
|
3. WAS SIE NICHT DARF -- jeden Weg einzeln
|
|
===================================================================== */
|
|
melde("\n=== 3. An DogFather und den Managern nicht ===");
|
|
{
|
|
/* GEPRUEFT WIRD MIT TEAM-DOGI-ROLLEN. Ein Manager gehoert zur
|
|
Agentur und ist auf crew. fuer niemanden sichtbar -- ihn hier zu
|
|
nehmen haette 404 gemessen und „geschuetzt" genannt, obwohl es
|
|
nur die Haustrennung war. Die linke Hand ist der richtige Fall:
|
|
Sie IST auf dieser Adresse, und die rechte Hand darf sie
|
|
trotzdem nicht anfassen. */
|
|
for (const [wen, id, name] of [
|
|
["DogFather", idDogi, "Filipe"], ["die linke Hand", idLinke, "Funny"]]) {
|
|
const code = await roh(`/workspace/api/verwaltung/personen/${id}/code`, kHand, {}, "POST");
|
|
ok(code.code === 403 || code.code === 404,
|
|
`kein neuer Code fuer ${wen} (${code.code})`);
|
|
|
|
const rolle = await roh(`/workspace/api/verwaltung/personen/${id}/rolle`, kHand,
|
|
{ rolle: "modi" }, "PUT");
|
|
ok(rolle.code === 403 || rolle.code === 404,
|
|
`keine Rollenaenderung bei ${wen} (${rolle.code})`);
|
|
|
|
const sperren = await roh(`/workspace/api/verwaltung/personen/${id}`, kHand,
|
|
{ aktiv: false }, "PATCH");
|
|
ok(sperren.code === 403 || sperren.code === 404,
|
|
`kein Sperren von ${wen} (${sperren.code})`);
|
|
|
|
const weg = await roh(`/workspace/api/verwaltung/personen/${id}`, kHand,
|
|
{ name }, "DELETE");
|
|
ok(weg.code === 403 || weg.code === 404,
|
|
`kein Loeschen von ${wen} (${weg.code})`);
|
|
|
|
const vorschau = await roh(`/workspace/api/verwaltung/personen/${id}/loeschbar`, kHand);
|
|
ok(vorschau.code === 403 || vorschau.code === 404,
|
|
`nicht einmal die Loesch-Vorschau von ${wen} (${vorschau.code})`);
|
|
}
|
|
|
|
/* UND DIE MANAGER -- hier zeigt sich der Unterschied am deutlichsten.
|
|
|
|
Mein erster Anlauf erwartete, dass BEIDE abgewiesen werden ("auf
|
|
crew. gibt es keine Manager"). Gemessen: Hand 403, DogFather 200.
|
|
Das war kein Loch, sondern genau die bestellte Ordnung -- die
|
|
Haustrennung gilt fuer die LISTE, nicht fuer den direkten Zugriff
|
|
von DogFather. Ich hatte das eine fuer das andere gehalten.
|
|
|
|
Beide Zeilen stehen jetzt getrennt da, und gerade ihr Unterschied
|
|
ist die Aussage: Derselbe Weg, zwei Rollen, zwei Antworten. */
|
|
const handMana = await roh(`/workspace/api/verwaltung/personen/${idMana}`, kHand,
|
|
{ aktiv: false }, "PATCH");
|
|
ok(handMana.code === 403 || handMana.code === 404,
|
|
`die rechte Hand kommt an einen Manager nicht heran (${handMana.code})`);
|
|
const dogiMana = await roh(`/workspace/api/verwaltung/personen/${idMana}`, kDogi,
|
|
{ aktiv: false }, "PATCH");
|
|
ok(dogiMana.code === 200,
|
|
`DogFather schon (${dogiMana.code}) -- sonst hiesse "sie darf es nicht" nur, `
|
|
+ `dass es niemand darf`);
|
|
/* Wieder entsperren, damit die Schlussprobe den Grundstand sieht. */
|
|
await roh(`/workspace/api/verwaltung/personen/${idMana}`, kDogi, { aktiv: true }, "PATCH");
|
|
|
|
/* SIE KANN KEINEN DOGFATHER ERSCHAFFEN. Die Rolle ist fuer niemanden
|
|
vergebbar, auch nicht fuer DogFather selbst -- hier zaehlt, dass
|
|
der Weg ueber sie nicht offensteht. */
|
|
const machDogi = await roh(`/workspace/api/verwaltung/personen/${idModi}/rolle`, kHand,
|
|
{ rolle: "admin" }, "PUT");
|
|
ok(machDogi.code >= 400, `sie macht niemanden zum DogFather (${machDogi.code})`);
|
|
|
|
const anlegeDogi = await roh("/workspace/api/verwaltung/personen", kHand,
|
|
{ name: "Zweiter Chef", rolle: "admin" });
|
|
ok(anlegeDogi.code >= 400, `und legt keinen an (${anlegeDogi.code})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
4. DIE ANDEREN ROLLEN KOMMEN GAR NICHT HERAN
|
|
===================================================================== */
|
|
melde("\n=== 4. Alle anderen ===");
|
|
{
|
|
for (const [wer, keks] of [["die linke Hand", kLinke], ["ein Modi", kModi]]) {
|
|
const anl = await roh("/workspace/api/verwaltung/personen", keks,
|
|
{ name: "Versuch", rolle: "modi" });
|
|
ok(anl.code >= 400, `${wer} legt niemanden an (${anl.code})`);
|
|
const weg = await roh(`/workspace/api/verwaltung/personen/${idGast}`, keks,
|
|
{ name: "Kessi" }, "DELETE");
|
|
ok(weg.code >= 400, `${wer} loescht niemanden (${weg.code})`);
|
|
}
|
|
}
|
|
|
|
/* =====================================================================
|
|
5. GEGENPROBEN -- kann das hier ueberhaupt rot werden?
|
|
===================================================================== */
|
|
melde("\n=== 5. Gegenproben (jede MUSS anschlagen) ===");
|
|
{
|
|
/* DogFather selbst kann alles -- sonst hiesse „sie darf dasselbe"
|
|
nur, dass beide nichts duerfen. */
|
|
const dogiCode = await roh(`/workspace/api/verwaltung/personen/${idLinke}/code`, kDogi, {}, "POST");
|
|
ok(dogiCode.code === 200,
|
|
`DogFather erzeugt einen Code fuer die linke Hand (${dogiCode.code}) -- `
|
|
+ `sie kann es nicht, er schon`);
|
|
|
|
/* DER LETZTE DOGFATHER-ZUGANG BLEIBT -- auch fuer ihn selbst.
|
|
Diese Sicherung stand vorher hinter „nur DogFather darf
|
|
loeschen"; jetzt traegt sie allein. */
|
|
const letzter = await roh(`/workspace/api/verwaltung/personen/${idDogi}`, kDogi,
|
|
{ name: "Filipe" }, "DELETE");
|
|
ok(letzter.code >= 400,
|
|
`der letzte DogFather-Zugang laesst sich nicht loeschen (${letzter.code})`);
|
|
|
|
/* Ohne Anmeldung gar nichts. */
|
|
const ohne = await roh("/workspace/api/verwaltung/personen", null,
|
|
{ name: "Fremd", rolle: "modi" });
|
|
ok(ohne.code >= 400, `ohne Anmeldung nichts (${ohne.code})`);
|
|
|
|
/* UND DIE TAFEL IST DANACH UNVERSEHRT: DogFather, Manager und die
|
|
linke Hand stehen noch da. Ein abgewiesener Versuch, der trotzdem
|
|
etwas aendert, waere der schlimmste Ausgang. */
|
|
const liste = json(await roh("/workspace/api/verwaltung/personen", kDogi));
|
|
const namen = (liste.personen || []).map((x) => x.rolle);
|
|
/* Ohne "manager" -- der ist auf crew. gar nicht in der Liste, siehe
|
|
oben. Ihn hier zu erwarten hiesse, die Haustrennung fuer einen
|
|
Fehler zu halten. */
|
|
ok(namen.includes("admin") && namen.includes("linke") && namen.includes("hand"),
|
|
`nach allen Versuchen stehen sie noch da (${namen.length} Personen: `
|
|
+ `${[...new Set(namen)].join(", ")})`);
|
|
}
|
|
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
|
|
|
console.log("");
|
|
console.log(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
console.log(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
process.exit(fehler ? 1 : 0);
|