Files
florian.wildeandClaude Opus 5 0d16dfd379 ci: Pruefung bei jedem Push und Konsistenzpruefung der Doku (#019)
Gitea-Actions-Workflow mit Lint, Typen, Tests gegen PostgreSQL, Build,
ShellCheck und einem Skript, das Version, Issue-Uebersicht und Handbuch-Bilder
abgleicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:47:38 +02:00

89 lines
3.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Prüft, ob Dokumentation und Version zusammenpassen.
*
* npm run pruefe:doku
*
* Geprüft wird:
* 1. Die Version aus package.json hat einen Abschnitt im Changelog.
* 2. Jede Issue-Datei steht in der Übersicht – und umgekehrt.
* 3. Status und tatsächliche Zeit stimmen zwischen Issue-Datei und Übersicht überein.
* 4. Alle im Handbuch verlinkten Bilder sind vorhanden.
*/
import { readFile, readdir } from "node:fs/promises";
import path from "node:path";
const WURZEL = process.cwd();
const fehler = [];
function pruefe(bedingung, meldung) {
if (!bedingung) fehler.push(meldung);
}
async function versionImChangelog() {
const { version } = JSON.parse(await readFile(path.join(WURZEL, "package.json"), "utf8"));
const changelog = await readFile(path.join(WURZEL, "CHANGELOG.md"), "utf8");
pruefe(
changelog.includes(`## [${version}]`),
`Version ${version} aus package.json fehlt im CHANGELOG.md.`,
);
}
async function issuesUndUebersicht() {
const verzeichnis = path.join(WURZEL, "docs", "issues");
const dateien = (await readdir(verzeichnis)).filter((name) => /^\d{3}-.*\.md$/.test(name)).sort();
const uebersicht = await readFile(path.join(verzeichnis, "README.md"), "utf8");
for (const datei of dateien) {
pruefe(uebersicht.includes(`(${datei})`), `Issue ${datei} fehlt in docs/issues/README.md.`);
const inhalt = await readFile(path.join(verzeichnis, datei), "utf8");
const status = /^\*\*Status:\*\*\s*(.+)$/m.exec(inhalt)?.[1]?.trim();
const zeit = /^\*\*Geschätzt:\*\*.*\*\*Tatsächlich:\*\*\s*(.+)$/m.exec(inhalt)?.[1]?.trim();
const zeile = uebersicht.split("\n").find((z) => z.includes(`(${datei})`));
if (!zeile) continue;
pruefe(
zeile.includes(`| ${status} |`),
`Status von ${datei} ist „${status}“, die Übersicht sagt etwas anderes: ${zeile.trim()}`,
);
if (zeit && zeit !== "–") {
pruefe(
zeile.includes(zeit),
`Zeit von ${datei} ist „${zeit}“, steht so aber nicht in der Übersicht.`,
);
}
}
// Umgekehrt: Zeilen in der Übersicht ohne zugehörige Datei.
for (const treffer of uebersicht.matchAll(/\((\d{3}-[^)]+\.md)\)/g)) {
pruefe(dateien.includes(treffer[1]), `Übersicht verweist auf ${treffer[1]}, die Datei fehlt.`);
}
}
async function bilderImHandbuch() {
const handbuch = path.join(WURZEL, "docs", "anleitung");
const text = await readFile(path.join(handbuch, "README.md"), "utf8");
const bilder = [...text.matchAll(/!\[[^\]]*\]\((bilder\/[^)]+)\)/g)].map((t) => t[1]);
const vorhanden = new Set(await readdir(path.join(handbuch, "bilder")));
for (const bild of bilder) {
pruefe(vorhanden.has(path.basename(bild)), `Handbuch verweist auf ${bild}, die Datei fehlt.`);
}
}
await versionImChangelog();
await issuesUndUebersicht();
await bilderImHandbuch();
if (fehler.length > 0) {
console.error("Die Dokumentation passt nicht zusammen:\n");
for (const meldung of fehler) console.error(` · ${meldung}`);
process.exit(1);
}
console.log("Dokumentation, Versionen und Bilder passen zusammen.");