b3b7b5d5e5
- CHANGELOG Unveroeffentlicht -> Neu - Anwenderhandbuch: Fenster nach einem Versionswechsel - Entwicklerdoku: erweiterte Importregel fuer @/lib/changelog, Versionsquelle, Endpunkte, Spalte, Folge fuer die Freigabe - Kopfkommentare changelog.ts und next.config.ts angepasst Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
87 lines
3.0 KiB
TypeScript
87 lines
3.0 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 Server-Code importiert werden — der Server-Seite
|
|
* (`app/(portal)/changelog/page.tsx`) und der `'use server'`-Datei
|
|
* `release-notice-actions.ts` ("Was ist neu"-Fenster, quick-260925-bow), nie
|
|
* von Client-Komponenten und auch nicht von `release-notes.ts` — 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 };
|
|
}
|