Files
dogfather-universe/server/pruef-zwischenspeicher.mjs
T
DogFatherGitandClaude Opus 5 25b5cf6224 "Unveraenderlich" stand auch auf Adressen, die sich aendern
Beim Nachmessen der Crew-Sperre von vorhin: Der Ursprung lieferte
korrekt 404 und eine bereinigte gate.css -- Cloudflare lieferte
weiterhin die ALTE Fassung, 27 806 Byte, `cf-cache-status: HIT`.

DIE URSACHE STAND IM EIGENEN KOMMENTAR. In index.js hiess es:
"`originalUrl` steht hier nicht zur Verfuegung -- geprueft wird
deshalb die Dateiart." Nachgemessen stimmt das nicht: `res.req
.originalUrl` ist da und liefert "/start.html?v=123". Die Annahme war
nie geprueft worden, und aus ihr folgte eine Zusage, die das Haus
nicht halten kann: JEDE CSS- und JS-Datei ging mit
`max-age=31536000, immutable` hinaus -- auch unter ihrer Adresse OHNE
Stempel.

"immutable" heisst woertlich: Der Inhalt unter DIESER Adresse aendert
sich nie. Fuer `gate.css?v=...` stimmt das, der Name wechselt ja mit
dem Inhalt. Fuer `gate.css` ist es falsch -- und dort haette die alte
Fassung ein Jahr im Zwischenspeicher gelegen, waehrend der Server
laengst etwas anderes sagt. Ein Zwischenspeicher, der etwas Falsches
zeigt, ist schlimmer als gar keiner: man glaubt ihm.

Jetzt haengt die Zusage an der Bedingung, die sie traegt. Mit Stempel
bleibt alles wie bisher -- die Seiten rufen ohnehin immer `?v=...`
auf, dort kostet es nichts. Ohne Stempel wird nachgefragt. Die
oeffentliche Seite benutzt ebenfalls `?v=` (mit Buchstabe am Ende);
geprueft wird nur, OB einer da ist, nicht wie er aussieht.

DIESELBE SORTE FEHLER GLEICH NEBENAN: pruef-zwischenspeicher hat einen
Abschnitt "Was einen Stempel traegt, darf liegenbleiben" -- und rief
die Dateien OHNE Stempel ab. Auch hier beschrieb der Text etwas, das
der Code nicht tat. Er liest den Stempel jetzt aus der Seite und misst
damit genau die Adresse, die ein Browser anfordert. Dazu vier neue
Zeilen fuer den umgekehrten Fall, mit Gegenprobe an derselben Datei --
sonst waere "ohne Stempel: no-cache" auch dann gruen, wenn ALLES auf
no-cache stuende, und jeder Seitenaufruf waere unnoetig langsam.

UND MEIN EIGENER FEHLER VON GESTERN, behoben: Die Stempelpruefung
verglich seit dem 20.09. den Stempel mit dem ZEITPUNKT des letzten
Commits an assets/. Das kann gar nicht aufgehen -- der Stempel wird
immer VOR dem Committen gezogen, die Commit-Zeit ist also immer die
juengere. Stempeln um 12:59 und Committen um 13:00 genuegte fuer einen
Fehlalarm; heute ist genau das passiert.

Gefragt wird jetzt die REIHENFOLGE der Commits, die hat keine Uhr:
Liegt der letzte Commit an den HTML-Seiten (dort steht der Stempel)
auf oder nach dem letzten an assets/? `merge-base --is-ancestor`
beantwortet das ohne Zeitstempel. Mit Gegenprobe ueber das Elternteil
-- und mit drittem Ausgang, wenn es keines gibt.

pruef-zwischenspeicher: 27 Pruefungen, 0 Fehler (war 22, davon 2 rot)

NOCH OFFEN und nur von Filipe zu machen: Cloudflare haelt die alten
Fassungen unter den stempellosen Adressen weiter im Zwischenspeicher.
Der Ursprung ist sauber; geleert werden muss der Rand einmal von Hand.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-21 13:04:38 +02:00

421 lines
20 KiB
JavaScript

/* WAS DARF LIEGENBLEIBEN, UND WAS NICHT (09.09.2026)
DER FALL, DER DAS AUSGELÖST HAT: Filipe meldete, eine Änderung an der
Kopfleiste sei "nicht gemacht". Gemessen war sie auf allen 18 Seiten,
in allen fünf Rollen, bei drei Breiten in Ordnung -- und die neuen
Dateien lagen nachweislich auf dem Server. Er sah trotzdem den alten
Stand.
Die Ursache: Die HTML-Seiten gingen ohne jede Cache-Angabe hinaus.
Ohne `Cache-Control` und ohne `Expires` darf ein Browser heuristisch
zwischenspeichern. Und in der HTML stehen die VERSIONSSTEMPEL der
Stilvorlagen -- eine alte HTML holt also auch alte CSS- und
JS-Dateien. Der ganze Stempelmechanismus hing an einer Datei, die
selbst liegenbleiben durfte.
DIESE PRÜFUNG IST DER GRUND, WARUM ES NICHT WIEDERKOMMT. Sie fragt
echte Adressen ab und liest die Kopfzeilen. Eine Regel im Quelltext
zu lesen wäre wertlos: Zwischen `setHeaders` und dem, was ankommt,
liegen Express, Caddy und Cloudflare.
DREI AUSGÄNGE: 0 in Ordnung · 1 Befunde · 2 konnte nicht arbeiten. */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-cache-"));
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 Zwischenspeicher-Pruefung");
process.env.PORT = `${PORT}`;
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");
notbremse(120_000, "pruef-zwischenspeicher");
await new Promise((r) => setTimeout(r, 900));
const BASIS = `http://127.0.0.1:${PORT}`;
let fehler = 0, geprueft = 0;
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
/* ANGEMELDET MESSEN, SONST MISST MAN EINE UMLEITUNG.
Der erste Anlauf fragte die Workspace-Seiten ohne Anmeldung ab und
meldete "keine Cache-Angabe" fuer drei von ihnen -- der Server hatte
aber gar keine Seite geschickt, sondern eine Umleitung zur Anmeldung,
und eine Umleitung traegt zu Recht keine.
Das ist genau die Sorte Befund, die teuer ist: Sie sieht aus wie ein
Fehler in der Sache und ist einer in der Messung. Die Pruefung
braucht ihre Voraussetzung, bevor sie misst. */
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
await fetch(BASIS + "/workspace/api/anmelden", { method: "POST",
headers: { "Content-Type": "application/json" },
body: 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 salt = randomBytes(16).toString("hex");
const hash = scryptSync("CODE-DOGI-0001", 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("Filipe", "admin", hash, salt, 32768, new Date().toISOString());
d.close();
}
const anAntwort = await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }) });
const KEKS = (anAntwort.headers.getSetCookie?.() || []).map((x) => x.split(";")[0]).join("; ");
if (!KEKS) {
console.error("\nKONNTE NICHT ARBEITEN: keine Anmeldung, also keine Seiten zu messen.");
process.exit(2);
}
const kopf = async (pfad) => {
const a = await fetch(BASIS + pfad, { redirect: "manual", headers: { cookie: KEKS } });
return { status: a.status, cc: a.headers.get("cache-control") };
};
console.log("\n=== HTML wird immer nachgefragt");
for (const p of ["/workspace/start.html", "/workspace/aufgaben.html",
"/workspace/kalender.html", "/index.html"]) {
const r = await kopf(p);
/* DIE NUMMER GEHOERT IN DIE BEDINGUNG. Eine Umleitung traegt zu Recht
keine Cache-Angabe -- wer sie mitmisst, sucht einen Fehler, den es
nicht gibt. */
ok(r.status === 200 && /no-cache|no-store|max-age=0/.test(r.cc || ""),
`${p}: HTTP ${r.status}, ${r.cc || "(keine Angabe)"}`);
}
/* DIESER ABSCHNITT RIEF BIS ZUM 21.09.2026 OHNE STEMPEL AB.
Er heisst "Was einen Stempel traegt" und fragte
"/workspace/assets/css/start.css" -- ohne `?v=`. Gemessen hat er
damit etwas anderes, als er behauptet. Aufgefallen ist es erst, als
der Server anfing, die beiden Faelle wirklich zu unterscheiden.
Der Stempel wird aus der Seite GELESEN, nicht erfunden: So misst
dieser Abschnitt genau die Adresse, die ein Browser auch anfordert. */
const seiteRoh = await (await fetch(BASIS + "/workspace/start.html",
{ headers: { cookie: KEKS } })).text();
const STEMPEL = (seiteRoh.match(/[?&]v=(\d{6,})/) || [])[1] || "";
ok(STEMPEL.length >= 6, `der Stempel der Seite ist ablesbar (${STEMPEL || "KEINER"})`);
console.log("\n=== Was einen Stempel trägt, darf liegenbleiben");
for (const p of ["/workspace/assets/css/start.css", "/workspace/assets/js/kopf.js",
"/workspace/assets/css/module.css"]) {
const r = await kopf(`${p}?v=${STEMPEL}`);
ok(/max-age=\d{6,}/.test(r.cc || ""), `${p}?v=…: ${r.cc || "(keine Angabe)"}`);
}
/* =====================================================================
OHNE STEMPEL DARF NICHTS "UNVERAENDERLICH" HEISSEN (21.09.2026)
`immutable` ist eine woertliche Zusage: Der Inhalt unter DIESER
Adresse aendert sich nie. Mit Stempel stimmt sie -- der Name wechselt
ja mit dem Inhalt. Ohne Stempel ist sie falsch, und zwar genau dort,
wo es wehtut.
WAS PASSIERT IST: Am selben Tag wurde crew-haus.css auf die zweite
Adresse begrenzt und die Crew-Adresse aus gate.css entfernt. Der
Ursprung lieferte danach 404 beziehungsweise eine bereinigte Datei.
Cloudflare lieferte weiter die ALTE -- 27 806 Byte,
`cf-cache-status: HIT`. Mit einem Jahr Zusage haette sie dort ein
Jahr gelegen, waehrend der Server laengst etwas anderes sagt.
Ein Zwischenspeicher, der etwas Falsches zeigt, ist schlimmer als
gar keiner -- man glaubt ihm ja.
===================================================================== */
console.log("\n=== Ohne Stempel wird nachgefragt");
for (const p of ["/workspace/assets/css/start.css", "/workspace/assets/js/kopf.js"]) {
const r = await kopf(p);
ok(/no-cache|no-store|max-age=0/.test(r.cc || ""),
`${p} (ohne ?v=): ${r.cc || "(keine Angabe)"}`);
/* GEGENPROBE an derselben Datei: Mit Stempel muss dieselbe Adresse
das Gegenteil sagen. Ohne diese Zeile waere der Haken darueber
auch dann gruen, wenn ALLES auf "no-cache" stuende -- und dann
waere jeder Seitenaufruf unnoetig langsam, ohne dass es jemandem
auffiele. */
const mit = await kopf(`${p}?v=${STEMPEL}`);
ok(/immutable/.test(mit.cc || ""),
` und mit Stempel sehr wohl: ${mit.cc || "(keine Angabe)"}`);
}
console.log("\n=== Der Service Worker NICHT");
{
const r = await kopf("/workspace/sw.js");
ok(/no-cache/.test(r.cc || ""),
`sw.js wird ohne Stempel geladen und muss nachgefragt werden: ${r.cc || "(keine)"}`);
}
console.log("\n=== Bilder: ein Tag, nicht ein Jahr");
{
const r = await kopf("/workspace/assets/img/marke-husky.webp");
ok(r.status === 404 || /max-age=86400/.test(r.cc || ""),
`Bild ohne Stempel: ${r.cc || "(keine Angabe)"} (HTTP ${r.status})`);
}
/* ---------------------------------------------------------------------
GEGENPROBEN
1. Die Prüfung muss eine FEHLENDE Angabe erkennen -- sonst hätte sie
genau den Zustand durchgewinkt, der den Fehler verursacht hat.
2. Und sie darf nicht alles durchwinken: "no-cache" bei einer
Stilvorlage wäre auch falsch (jeder Aufruf eine Anfrage).
--------------------------------------------------------------------- */
console.log("\n=== Gegenproben");
{
const pruefHtml = (cc) => /no-cache|no-store|max-age=0/.test(cc || "");
ok(!pruefHtml(null), "eine fehlende Angabe fällt durch");
ok(!pruefHtml("public, max-age=31536000"), "und ein langer Speicher an einer HTML ebenfalls");
const pruefFest = (cc) => /max-age=\d{6,}/.test(cc || "");
ok(!pruefFest("no-cache"), "eine Stilvorlage ohne Speicher fällt ebenfalls auf");
ok(pruefFest("public, max-age=31536000, immutable"), "und die richtige Angabe besteht");
}
/* ---------------------------------------------------------------------
UND DER STEMPEL SELBST: Jede Stilvorlage und jedes Skript, das eine
HTML einbindet, MUSS einen tragen. Ohne ihn wäre ein Jahr Speicher
ein Jahr alter Stand -- die Regel oben und diese hier gehören
zusammen, sonst ist die eine ohne die andere gefährlich.
--------------------------------------------------------------------- */
console.log("\n=== Jede eingebundene Datei trägt einen Stempel");
{
const { readdirSync, readFileSync } = await import("node:fs");
const { dirname } = await import("node:path");
const { fileURLToPath } = await import("node:url");
const WURZEL = join(dirname(fileURLToPath(import.meta.url)), "..", "workspace");
const seiten = readdirSync(WURZEL).filter((n) => n.endsWith(".html"));
const ohne = [];
let mit = 0;
for (const s of seiten) {
const text = readFileSync(join(WURZEL, s), "utf8");
for (const t of text.matchAll(/(?:href|src)="(assets\/[^"]*\.(?:css|js|mjs))(\?[^"]*)?"/g)) {
if (t[2] && /\?v=\d+/.test(t[2])) mit++;
else ohne.push(`${s} → ${t[1]}`);
}
}
ok(mit > 100, `${mit} eingebundene Dateien tragen einen Stempel`);
ok(ohne.length === 0,
ohne.length ? `OHNE Stempel: ${ohne.slice(0, 4).join(", ")}`
: `und keine einzige ohne (${seiten.length} Seiten geprüft)`);
}
/* ---------------------------------------------------------------------
DIE APP-SYMBOLE GEHÖREN DAZU (09.09.2026)
Sie standen mit absolutem Pfad in der Seite und fielen deshalb durch
das Muster des Stempel-Werkzeugs. Bilder werden einen Tag lang
zwischengespeichert -- ein neu gezeichnetes Symbol war damit bis zu
24 Stunden unsichtbar. Aufgefallen ist es erst, als das Symbol am
09.09.2026 tatsächlich neu gezeichnet wurde.
--------------------------------------------------------------------- */
console.log("\n=== Auch die App-Symbole tragen einen Stempel");
{
const { readdirSync, readFileSync } = await import("node:fs");
const { dirname } = await import("node:path");
const { fileURLToPath } = await import("node:url");
const WURZEL = join(dirname(fileURLToPath(import.meta.url)), "..", "workspace");
const seiten = readdirSync(WURZEL).filter((n) => n.endsWith(".html"));
let mit = 0; const ohne = [];
for (const s of seiten) {
const text = readFileSync(join(WURZEL, s), "utf8");
for (const t of text.matchAll(
/href="(\/assets\/img\/app-symbole\/[^"?]+)(\?[^"]*)?"/g)) {
if (t[2] && /\?v=\d+/.test(t[2])) mit++;
else ohne.push(`${s} → ${t[1]}`);
}
}
/* Die Zahl gehört in die Bedingung: Findet die Prüfung gar kein Symbol,
wäre "keines ohne Stempel" sonst wahr, ohne etwas gesehen zu haben. */
ok(mit >= seiten.length,
`${mit} Symbolverweise mit Stempel (${seiten.length} Seiten)`);
ok(ohne.length === 0,
ohne.length ? `OHNE Stempel: ${ohne.slice(0, 4).join(", ")}`
: "und keiner ohne");
/* DAS MANIFEST IST DIE WICHTIGERE HÄLFTE. Aus ihm holt das
Betriebssystem das Symbol der INSTALLIERTEN App -- steht dort eine
starre Adresse, bleibt das alte Bild auf dem Startbildschirm, egal
wie oft neu ausgeliefert wird. */
const manifest = JSON.parse(readFileSync(join(WURZEL, "app.webmanifest"), "utf8"));
const symbole = manifest.icons.map((i) => i.src);
const gestempelt = symbole.filter((s) => /\?v=\d+$/.test(s));
ok(symbole.length >= 4 && gestempelt.length === symbole.length,
gestempelt.length === symbole.length
? `alle ${symbole.length} Symbole im Manifest tragen einen Stempel`
: `im Manifest ohne Stempel: ${symbole.filter((s) => !/\?v=\d+$/.test(s)).join(", ")}`);
}
/* ---------------------------------------------------------------------
DIE EINE STELLE, AN DER DAS AUTOMATISCHE UPDATE HÄNGT
Filipe hat gefragt: "ich hoffe der update passiert bei den benutzern
alle immer automatisch mit."
Er tut es -- unter EINER Bedingung. Die Kette lautet:
HTML wird nie zwischengespeichert -> der Browser holt immer die
neueste -> darin stehen die Stempel -> neuer Stempel = neue
Adresse = neue Datei.
Das schwächste Glied ist der Stempel, denn den setze ICH von Hand.
Vergesse ich ihn, bekommt der Benutzer die neue HTML -- und darin
die ALTEN Adressen, die ein Jahr lang gültig zwischengespeichert
sind. Er sähe die Änderung dann monatelang nicht, und niemandem
fiele auf, warum.
Zwei Prüfungen schließen das:
1. ALLE Seiten tragen DENSELBEN Stempel. Der wahrscheinlichste
Fehler ist nicht "gar nicht gesetzt", sondern "auf einer Seite
vergessen" -- und dann ist genau diese eine Seite kaputt.
2. Wer eine Stilvorlage ändert, ändert auch den Stempel. Geprüft
wird am letzten COMMIT, der assets/ angefasst hat, nicht am
Arbeitsverzeichnis: Während des Bauens ist die Datei immer
neuer als der Stempel, und eine Warnung, die dauernd kommt,
wird weggeklickt.
--------------------------------------------------------------------- */
console.log("\n=== Der Versionsstempel — daran hängt das automatische Update");
{
const { readdirSync, readFileSync } = await import("node:fs");
const { dirname } = await import("node:path");
const { fileURLToPath } = await import("node:url");
const WURZEL = join(dirname(fileURLToPath(import.meta.url)), "..");
const seiten = readdirSync(join(WURZEL, "workspace")).filter((n) => n.endsWith(".html"));
const stempel = new Map();
/* Das Manifest zählt mit: Ein Stempel, der nur in den Seiten
mitgezogen wird und im Manifest stehen bleibt, wäre genau die Art
Abweichung, die niemandem auffällt -- bis das Symbol auf dem
Startbildschirm ein halbes Jahr alt ist. */
for (const s of [...seiten, "app.webmanifest"]) {
const text = readFileSync(join(WURZEL, "workspace", s), "utf8");
for (const t of text.matchAll(/\?v=(\d+)/g)) {
if (!stempel.has(t[1])) stempel.set(t[1], []);
if (!stempel.get(t[1]).includes(s)) stempel.get(t[1]).push(s);
}
}
const alle = [...stempel.keys()].sort();
ok(alle.length === 1,
alle.length === 1 ? `alle ${seiten.length} Seiten tragen denselben Stempel (${alle[0]})`
: `${alle.length} verschiedene Stempel: `
+ alle.map((x) => `${x} auf ${stempel.get(x).length} Seiten`).join(", "));
/* GEGENPROBE: Die Zählung erkennt zwei verschiedene Stempel wirklich.
Ohne sie hiesse "genau einer" nur, dass irgendetwas gezählt wurde. */
ok(new Set(["202609091626", "202609091500"]).size === 2,
"Gegenprobe: zwei verschiedene Stempel würden auffallen");
/* ---- Und wurde er beim letzten Eingriff mitgezogen? ---------------- */
const { spawnSync } = await import("node:child_process");
const git = (...a) => spawnSync("git", ["-C", WURZEL, ...a], { encoding: "utf8" });
const probe = git("rev-parse", "--is-inside-work-tree");
if (probe.status !== 0) {
/* DER DRITTE AUSGANG. Ohne Git ist die Frage nicht zu beantworten
-- und "nicht beantwortbar" als "in Ordnung" zu verbuchen wäre
genau der Fehler, den dieses Haus schon einmal wochenlang hatte. */
console.log(" -- konnte nicht nachsehen: kein Git-Arbeitsverzeichnis");
} else {
const letzterCommit = git("log", "-1", "--format=%H", "--", "workspace/assets")
.stdout.trim();
if (!letzterCommit) {
console.log(" -- konnte nicht nachsehen: kein Commit an workspace/assets gefunden");
} else {
/* DIE FRAGE IST "KOMMT DIE ÄNDERUNG AN" -- UND SIE WIRD AN DER
REIHENFOLGE BEANTWORTET, NICHT AN DER UHR (21.09.2026).
DREI FASSUNGEN, ZWEI DAVON FALSCH -- der Weg hierher gehört
in die Akte:
(1) Bis 20.09.: "derselbe Commit muss Stilvorlage UND HTML
ändern". Das ist der Normalfall, aber nicht die Regel.
Wird der Stempel in einem FOLGE-Commit nachgezogen, kommt
die Änderung genauso an -- die Prüfung blieb trotzdem rot
und beschuldigte einen Commit, an dem nichts falsch war.
(2) Am 20.09. daraufhin auf einen ZEITVERGLEICH umgestellt:
Stempel gegen Commit-Zeitpunkt. Klingt sauber und ist es
nicht -- der Stempel wird IMMER vor dem Committen gezogen,
also ist die Commit-Zeit immer die jüngere. Stempeln um
12:59 und Committen um 13:00 genügte für einen Fehlalarm.
Am 21.09. ist genau das passiert, eine Minute Versatz.
(3) Jetzt: die REIHENFOLGE der Commits. Sie hat keine Uhr, und
damit auch keinen Versatz.
Der Stempel steht in den HTML-Dateien. Die Frage "wurde
nach der letzten Änderung an assets/ neu gestempelt?"
lautet deshalb: Liegt der letzte Commit an den HTML-Seiten
auf oder nach dem letzten Commit an assets/?
gleicher Commit -> zusammen gestempelt, gut
HTML ist Nachfahre -> danach nachgezogen, gut
sonst -> assets/ ist weiter, der
Browser behält seine alte
Kopie
`merge-base --is-ancestor` beantwortet das ohne Zeitstempel:
Rückgabewert 0 heißt "A liegt auf dem Weg zu B". */
const htmlCommit = git("log", "-1", "--format=%H", "--", "workspace/*.html")
.stdout.trim();
if (!htmlCommit) {
/* DRITTER AUSGANG. Ohne HTML-Commit ist die Frage nicht zu
beantworten -- und das ist kein "in Ordnung". */
console.log(" -- konnte nicht nachsehen: kein Commit an den HTML-Seiten");
} else {
const istVorfahr = (a, b) =>
git("merge-base", "--is-ancestor", a, b).status === 0;
const gleich = htmlCommit === letzterCommit;
const danach = gleich || istVorfahr(letzterCommit, htmlCommit);
ok(danach,
danach
? (gleich
? `Stempel und Stilvorlagen stehen im selben Commit `
+ `(${letzterCommit.slice(0, 7)}) -- die Änderung kommt an`
: `der Stempel wurde nach der Änderung an assets/ nachgezogen `
+ `(${letzterCommit.slice(0, 7)} → ${htmlCommit.slice(0, 7)})`)
: `assets/ wurde zuletzt in ${letzterCommit.slice(0, 7)} geändert, die `
+ `HTML-Seiten schon in ${htmlCommit.slice(0, 7)} -- es wurde seither `
+ `NICHT neu gestempelt, der Browser behält seine alte Kopie`);
/* GEGENPROBE: Kann dieser Vergleich überhaupt "nein" sagen?
Ein Elternteil des HTML-Commits ist garantiert ÄLTER --
dreht man die Frage um, muss sie falsch herauskommen. Ohne
diese Zeile wäre der Haken darüber auch dann grün, wenn
`is-ancestor` immer 0 lieferte.
Beim allerersten Commit gibt es kein Elternteil; dann ist
die Gegenprobe nicht durchführbar und sagt das auch. */
const eltern = git("rev-parse", `${htmlCommit}^`).stdout.trim();
if (!eltern || eltern.startsWith("fatal")) {
console.log(" -- Gegenprobe nicht möglich: der Commit hat kein Elternteil");
} else {
ok(!istVorfahr(htmlCommit, eltern),
`Gegenprobe: umgekehrt gefragt sagt derselbe Vergleich nein `
+ `(${htmlCommit.slice(0, 7)} liegt nicht vor ${eltern.slice(0, 7)})`);
}
}
}
}
}
console.log(`\n${geprueft} Prüfungen`);
console.log(fehler ? `${fehler} FEHLER` : "ALLES IN ORDNUNG");
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
process.exit(fehler ? 1 : 0);