diff --git a/.gitea/workflows/release-plugin.yml b/.gitea/workflows/release-plugin.yml index d9781fb..0e282d0 100644 --- a/.gitea/workflows/release-plugin.yml +++ b/.gitea/workflows/release-plugin.yml @@ -13,11 +13,13 @@ name: Release Plugin # Erwartete Struktur im aufrufenden Repo: # - .php (Plugin-Haupt-PHP mit "Version: X.Y.Z" im Header) # - Irgendwo in einer PHP-Datei: define('IDF__VERSION', 'X.Y.Z') -# - readme.txt im Repo-Root mit: -# Stable tag: X.Y.Z -# == Changelog == -# = X.Y.Z = -# * ... +# - readme.txt im Repo-Root (WP-Standard-Metadaten inkl. "Stable tag: X.Y.Z") +# - CHANGELOG.md im Repo-Root mit Block "## v" +# +# CHANGELOG.md ist SSOT für den Changelog. readme.txt wird nicht +# manuell mit Changelog-Einträgen gepflegt — der Abschnitt +# "== Changelog ==" in readme.txt wird beim Build aus CHANGELOG.md +# generiert und ist nur im fertigen ZIP vollständig. # # Trigger im aufrufenden Repo: push eines Tags der Form v... @@ -85,15 +87,18 @@ jobs: fi echo "Tag-Format OK: $TAG" - - name: Pre-Flight — readme.txt vorhanden + - name: Pre-Flight — Pflichtdateien shell: bash run: | set -euo pipefail - if [[ ! -f readme.txt ]]; then - echo "::error::readme.txt fehlt im Repo-Root (Pflichtdatei, siehe idf-ci README)" + missing=() + [[ -f CHANGELOG.md ]] || missing+=("CHANGELOG.md") + [[ -f readme.txt ]] || missing+=("readme.txt") + if ((${#missing[@]})); then + echo "::error::Pflichtdateien fehlen im Repo-Root: ${missing[*]}" exit 1 fi - echo "readme.txt OK" + echo "Pflichtdateien OK" - name: Pre-Flight — Plugin-Header-Version shell: bash @@ -150,7 +155,7 @@ jobs: fi echo "Stable tag OK: $STABLE_TAG" - - name: Changelog-Block aus readme.txt extrahieren + - name: Changelog-Block aus CHANGELOG.md extrahieren id: changelog shell: bash env: @@ -160,30 +165,21 @@ jobs: BLOCK="$(python3 - <<'PY' import re, sys, os version = os.environ["VERSION"] - with open("readme.txt", encoding="utf-8") as f: + with open("CHANGELOG.md", encoding="utf-8") as f: content = f.read() - # 1) "== Changelog ==" Abschnitt finden (bis zum nächsten "==…==" oder Dateiende) - section_pat = re.compile( - r"^==\s*Changelog\s*==\s*$\n(.*?)(?=^==[^=\n]+==\s*$|\Z)", - re.MULTILINE | re.DOTALL | re.IGNORECASE, - ) - section_m = section_pat.search(content) - if not section_m: - sys.stderr.write("'== Changelog ==' section not found in readme.txt\n") - sys.exit(1) - section = section_m.group(1) - # 2) Block "= =" bis zum nächsten "= x.y.z =" oder Abschnittsende - block_pat = re.compile( - r"^=\s*" + re.escape(version) + r"\s*=\s*$\n(.*?)(?=^=\s*\d+\.\d+\.\d+\s*=\s*$|\Z)", + # "## v" optional gefolgt von " — Datum" und beliebigem Suffix, + # Inhalt bis zum nächsten "## v" oder einer "---"-Trennzeile oder EOF. + pat = re.compile( + r"^##\s+v" + re.escape(version) + r"(?:\s+.*)?$\n(.*?)(?=^##\s+v\d|^---\s*$|\Z)", re.MULTILINE | re.DOTALL, ) - block_m = block_pat.search(section) - if not block_m: - sys.stderr.write(f"No block '= {version} =' found in == Changelog == section\n") + m = pat.search(content) + if not m: + sys.stderr.write(f"No block for v{version} found in CHANGELOG.md\n") sys.exit(1) - print(block_m.group(1).strip()) + print(m.group(1).strip()) PY - )" || { echo "::error::Kein Changelog-Block für $VERSION in readme.txt (Abschnitt '== Changelog ==')"; exit 1; } + )" || { echo "::error::Kein Changelog-Block für v${VERSION} in CHANGELOG.md"; exit 1; } { echo "body<> "$GITHUB_OUTPUT" echo "Changelog-Block extrahiert (${#BLOCK} Zeichen)." - - name: ZIP bauen + - name: ZIP bauen (readme.txt Changelog-Section aus CHANGELOG.md generieren) id: zip shell: bash + env: + SLUG: ${{ steps.vars.outputs.slug }} + ZIPNAME: ${{ steps.vars.outputs.zipname }} run: | set -euo pipefail - SLUG="${{ steps.vars.outputs.slug }}" - ZIPNAME="${{ steps.vars.outputs.zipname }}" WORK="/tmp/build" rm -rf "$WORK" mkdir -p "$WORK/$SLUG" @@ -215,14 +212,82 @@ jobs: --exclude='tests' \ --exclude='phpunit.xml*' \ --exclude='phpcs.xml*' \ - --exclude='CHANGELOG.md' \ --exclude='*.zip' \ ./ "$WORK/$SLUG/" - # readme.txt muss im Build enthalten sein (WP-Standard) - if [[ ! -f "$WORK/$SLUG/readme.txt" ]]; then - echo "::error::readme.txt fehlt im gebauten Plugin-Ordner" - exit 1 - fi + # Pflicht-Artefakte im Build + for f in readme.txt CHANGELOG.md; do + if [[ ! -f "$WORK/$SLUG/$f" ]]; then + echo "::error::$f fehlt im gebauten Plugin-Ordner" + exit 1 + fi + done + # == Changelog == Section in readme.txt aus CHANGELOG.md generieren + python3 - "$WORK/$SLUG/readme.txt" "$WORK/$SLUG/CHANGELOG.md" <<'PY' + import re, sys, pathlib + + readme_path = pathlib.Path(sys.argv[1]) + changelog_path = pathlib.Path(sys.argv[2]) + + # CHANGELOG.md parsen: Einträge "## v" bis zum nächsten "## v…" oder "---" oder EOF + entry_pat = re.compile( + r"^##\s+v(\d+\.\d+\.\d+)(?:\s+—\s+(\S+))?(?:\s+.*)?$\n(.*?)(?=^##\s+v\d|^---\s*$|\Z)", + re.MULTILINE | re.DOTALL, + ) + content = changelog_path.read_text(encoding="utf-8") + entries = [(m.group(1), m.group(2), m.group(3).strip()) for m in entry_pat.finditer(content)] + if not entries: + sys.stderr.write("Keine Versions-Einträge in CHANGELOG.md gefunden\n") + sys.exit(1) + + # CHANGELOG-Body in WP-readme.txt-Style konvertieren: + # "### Untertitel" → "**Untertitel**" + # "- Text" → "* Text" + # Mehrfach-Leerzeilen → eine Leerzeile + def to_readme(body): + out = [] + for raw in body.split("\n"): + line = raw.rstrip() + if line.startswith("### "): + out.append(f"**{line[4:].strip()}**") + elif line.startswith("- "): + out.append(f"* {line[2:]}") + else: + out.append(line) + cleaned, blank = [], False + for line in out: + if line == "": + if not blank: + cleaned.append(line) + blank = True + else: + cleaned.append(line) + blank = False + return "\n".join(cleaned).strip() + + section_lines = ["== Changelog =="] + for version, date, body in entries: + section_lines.append("") + header = f"= {version} =" + section_lines.append(header) + if date: + section_lines.append(f"*{date}*") + section_lines.append("") + section_lines.append(to_readme(body)) + new_section = "\n".join(section_lines).strip() + "\n" + + readme = readme_path.read_text(encoding="utf-8") + # Vorhandene == Changelog == Section ersetzen (bis zum nächsten == Section == oder EOF) + sec_pat = re.compile( + r"^==\s*Changelog\s*==\s*$.*?(?=^==[^=\n]+==\s*$|\Z)", + re.MULTILINE | re.DOTALL | re.IGNORECASE, + ) + if sec_pat.search(readme): + readme_new = sec_pat.sub(new_section + "\n", readme) + else: + readme_new = readme.rstrip() + "\n\n" + new_section + readme_path.write_text(readme_new, encoding="utf-8") + print(f"readme.txt Changelog-Section aktualisiert ({len(entries)} Versionen).") + PY (cd "$WORK" && zip -qr "/tmp/$ZIPNAME" "$SLUG") ls -la "/tmp/$ZIPNAME" # Validierung: Top-Level im ZIP ist genau $SLUG/