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>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
#!/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.");
|
||||
Reference in New Issue
Block a user