ba06db98c5
- CHANGELOG.md (Wurzel): Unveroeffentlicht (bwo + dyv + diese Seite, 8 Punkte) und 1.0.0 – 2026-09-15 (11 Punkte aus den Handbuechern), Alltagssprache, echte Umlaute - next.config.ts liest CHANGELOG.md zur Bauzeit nach env.TESSERA_CHANGELOG_MD; .dockerignore-Ausnahme !CHANGELOG.md und COPY-Zeile im Web-Dockerfile - lib/changelog.ts: filterChangelogForChannel (live ohne Unveroeffentlicht, beta/dev mit "Noch nicht freigegeben (Beta)", leerer Abschnitt ausgeblendet), 10 Tests - Server-Seite /changelog (MDEditor.Markdown + rehype-sanitize, Hinweis auf Beta, Leer-Text), 3 Tests; Versionszeile ist Link mit aria-label, 2 Tests - de/en: sidebar.whatsNew und Namensraum changelog; Web-Suite 49/309, tsc 0 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
import type { AppChannel } from './app-version';
|
|
|
|
/**
|
|
* Aenderungsliste fuer die Seite "Was ist neu" (quick-260916-dcz).
|
|
*
|
|
* Quelle ist CHANGELOG.md im Wurzelverzeichnis; `apps/web/next.config.ts` liest
|
|
* die Datei zur Bauzeit und legt den Text als `env.TESSERA_CHANGELOG_MD` ab.
|
|
* Dieses Modul darf NUR von der Server-Seite (`app/(portal)/changelog/page.tsx`)
|
|
* importiert werden — sonst landet der Text in oeffentlich abrufbaren
|
|
* Client-Chunks unter /_next/static.
|
|
*
|
|
* Kanalregel (`filterChangelogForChannel`): auf `live` fehlt der Abschnitt
|
|
* "Unveröffentlicht" vollstaendig; auf `beta` und `dev` bleibt er und traegt
|
|
* die Ueberschrift "Noch nicht freigegeben (Beta)" (uebersetzbar). Ein leerer
|
|
* Abschnitt (ohne Listenpunkt) wird auf allen Kanaelen ausgeblendet. H1 und
|
|
* Vorspann vor der ersten `## `-Ueberschrift entfallen — die Seite hat ihren
|
|
* eigenen Titel. Reine Funktion ohne React/Next, deshalb direkt testbar.
|
|
*/
|
|
|
|
export type ChangelogChannel = AppChannel;
|
|
|
|
export interface FilteredChangelog {
|
|
markdown: string;
|
|
hasUnreleased: boolean;
|
|
}
|
|
|
|
export const UNRELEASED_HEADING = 'Unveröffentlicht';
|
|
|
|
const DEFAULT_UNRELEASED_LABEL = 'Noch nicht freigegeben (Beta)';
|
|
|
|
/**
|
|
* Wie in app-version.ts: Next.js ersetzt `process.env.<NAME>` zur Bauzeit nur,
|
|
* wenn der Ausdruck woertlich mit vollem Namen im Code steht — kein
|
|
* Destructuring, kein `process.env[name]`.
|
|
*/
|
|
export const changelogMarkdown: string = process.env.TESSERA_CHANGELOG_MD ?? '';
|
|
|
|
const SECTION_RE = /^## /;
|
|
const UNRELEASED_RE = new RegExp(`^## ${UNRELEASED_HEADING}\\s*$`);
|
|
const LIST_ITEM_RE = /^\s*[-*] /;
|
|
|
|
function splitSections(lines: string[]): string[][] {
|
|
const sections: string[][] = [];
|
|
let current: string[] | null = null;
|
|
for (const line of lines) {
|
|
if (SECTION_RE.test(line)) {
|
|
current = [line];
|
|
sections.push(current);
|
|
} else if (current) {
|
|
current.push(line);
|
|
}
|
|
// Zeilen vor der ersten `## `-Ueberschrift (H1, Vorspann) werden verworfen.
|
|
}
|
|
return sections;
|
|
}
|
|
|
|
export function filterChangelogForChannel(
|
|
markdown: string,
|
|
channel: AppChannel,
|
|
options?: { unreleasedHeading?: string },
|
|
): FilteredChangelog {
|
|
const normalized = markdown.replace(/\r\n?/g, '\n');
|
|
const sections = splitSections(normalized.split('\n'));
|
|
|
|
let hasUnreleased = false;
|
|
const kept: string[][] = [];
|
|
|
|
for (const section of sections) {
|
|
if (!UNRELEASED_RE.test(section[0])) {
|
|
kept.push(section);
|
|
continue;
|
|
}
|
|
const isEmpty = !section.slice(1).some((line) => LIST_ITEM_RE.test(line));
|
|
if (isEmpty || channel === 'live') {
|
|
continue;
|
|
}
|
|
hasUnreleased = true;
|
|
const label = options?.unreleasedHeading ?? DEFAULT_UNRELEASED_LABEL;
|
|
kept.push([`## ${label}`, ...section.slice(1)]);
|
|
}
|
|
|
|
const joined = kept.map((section) => section.join('\n')).join('\n').trim();
|
|
return { markdown: joined ? `${joined}\n` : '', hasUnreleased };
|
|
}
|