rss-news/versioning.py

158 lines
5.7 KiB
Python
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.

# versioning.py
import re
import subprocess
from pathlib import Path
from datetime import datetime
import typer
app = typer.Typer()
CHANGELOG_FILE = Path("CHANGELOG.md")
VERSION_FILE = Path("__version__.py")
VERSION_PATTERN = r"## \[v?(\d+\.\d+\.\d+)\]"
def get_latest_version():
content = CHANGELOG_FILE.read_text(encoding="utf-8")
matches = re.findall(VERSION_PATTERN, content)
return matches[0] if matches else "0.0.0"
def bump_version(version: str, level: str = "patch") -> str:
major, minor, patch = map(int, version.split("."))
if level == "major":
return f"{major + 1}.0.0"
elif level == "minor":
return f"{major}.{minor + 1}.0"
return f"{major}.{minor}.{patch + 1}"
def write_version_file(version: str):
VERSION_FILE.write_text(f"VERSION = \"{version}\"\n", encoding="utf-8")
typer.echo(f"🔢 __version__.py aktualisiert auf {version}")
def prepend_changelog(version: str):
today = datetime.today().strftime("%Y-%m-%d")
new_entry = f"\n\n## [v{version}] {today}\n\n### 💡 Neue Funktionen\n- \n\n### 🔧 Änderungen & Fixes\n- \n\n### 📦 Internes\n- "
original = CHANGELOG_FILE.read_text(encoding="utf-8")
CHANGELOG_FILE.write_text(new_entry + original, encoding="utf-8")
typer.echo(f"📝 Neuer Eintrag für v{version} zu CHANGELOG.md hinzugefügt")
def validate_changelog(version: str) -> bool:
content = CHANGELOG_FILE.read_text(encoding="utf-8")
pattern = rf"## \[v?{re.escape(version)}\](.*?)^## \["
match = re.search(pattern, content + "\n## [", re.DOTALL | re.MULTILINE)
if match:
section = match.group(1).strip()
if any(line.strip() != "-" for line in section.splitlines() if line.strip()):
return True
typer.echo("⚠️ CHANGELOG-Eintrag ist noch leer oder unvollständig.")
return False
def tag_exists(tag: str) -> bool:
result = subprocess.run(["git", "tag"], capture_output=True, text=True)
return tag in result.stdout.splitlines()
def ensure_branch(expected_branch="main"):
result = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True)
branch = result.stdout.strip()
if branch != expected_branch:
typer.echo(f"⛔ Du befindest dich auf Branch '{branch}', erwartet wird '{expected_branch}'")
raise typer.Exit(code=1)
def extract_changelog_entry(version: str) -> str:
content = CHANGELOG_FILE.read_text(encoding="utf-8")
pattern = rf"## \[v?{re.escape(version)}\](.*?)^## \["
match = re.search(pattern, content + "\n## [", re.DOTALL | re.MULTILINE)
return match.group(1).strip() if match else ""
def create_git_tag(version: str):
try:
if tag_exists(f"v{version}"):
typer.echo(f"⚠️ Tag v{version} existiert bereits.")
return
subprocess.run(["git", "add", "."], check=True)
subprocess.run(["git", "commit", "-m", f"🔖 Release v{version}"], check=True)
subprocess.run(["git", "tag", f"v{version}"], check=True)
typer.echo(f"🏷️ Git-Tag 'v{version}' erstellt und commit durchgeführt.")
except subprocess.CalledProcessError:
typer.echo("⚠️ Git-Fehler beim Taggen oder Committen. Bitte manuell prüfen.")
def push_to_github():
try:
subprocess.run(["git", "push"], check=True)
subprocess.run(["git", "push", "--tags"], check=True)
typer.echo("🚀 Änderungen und Tags an GitHub gepusht.")
except subprocess.CalledProcessError:
typer.echo("⚠️ Fehler beim Pushen zu GitHub. Bitte Zugang oder Netzwerk prüfen.")
@app.command()
def list():
"Listet alle verfügbaren Versionen aus dem CHANGELOG"
typer.echo("\n📚 Verfügbare Versionen im CHANGELOG:")
content = CHANGELOG_FILE.read_text(encoding="utf-8")
versions = re.findall(VERSION_PATTERN, content)
for v in versions:
typer.echo(f"- v{v}")
@app.command()
def rollback():
"Letzte Version zurückrollen (Tag löschen + Commit zurücknehmen)"
last_version = get_latest_version()
if typer.confirm(f"⚠️ Letzte Version 'v{last_version}' wirklich zurücknehmen?"):
try:
subprocess.run(["git", "tag", "-d", f"v{last_version}"], check=True)
subprocess.run(["git", "reset", "--hard", "HEAD~1"], check=True)
typer.echo(f"🔙 Version 'v{last_version}' wurde zurückgerollt.")
except subprocess.CalledProcessError:
typer.echo("❌ Rollback fehlgeschlagen.")
else:
typer.echo("⛔ Abgebrochen.")
@app.command()
def create(level: str = typer.Option("patch", help="Versionstyp: patch, minor oder major"),
push: bool = typer.Option(False, help="Änderungen direkt an GitHub pushen")):
"Neue Version erstellen inkl. CHANGELOG, Git-Tag und optional Push"
ensure_branch()
current_version = get_latest_version()
next_version = bump_version(current_version, level)
typer.echo(f"💡 Aktuelle Version: {current_version}")
typer.echo(f"🚀 Neue Version: {next_version}")
if typer.confirm("Version übernehmen und eintragen?"):
write_version_file(next_version)
prepend_changelog(next_version)
typer.echo("\nBitte CHANGELOG.md bearbeiten und danach fortfahren.")
typer.prompt("Drücke Enter, sobald du den neuen Abschnitt ausgefüllt hast")
if not validate_changelog(next_version):
typer.echo("❌ Release abgebrochen: Bitte fülle den CHANGELOG-Eintrag aus.")
raise typer.Exit(code=1)
create_git_tag(next_version)
if push:
push_to_github()
typer.echo(f"✅ Version {next_version} erfolgreich erstellt.")
else:
typer.echo("❌ Abgebrochen.")
if __name__ == "__main__":
app()