"""Baut das Upload-Paket für cPanel: nur die Dateien, die der Server braucht. Aufruf (im Projektordner, beliebiges Betriebssystem):: python scripts/build_deploy_zip.py # -> dist/glory_defense.zip python scripts/build_deploy_zip.py -o paket.zip Warum ein eigenes Skript statt „ZIP-Datei erstellen“: Windows PowerShell 5.1 (`Compress-Archive`) schreibt Pfade mit Backslash ins Archiv. Manche Entpacker unter Linux erzeugen daraus flache Dateinamen wie ``app\\api.py`` – dann findet Passenger das Paket ``app`` nicht. Außerdem landen hier garantiert weder ``.env`` (Geheimnisse) noch ``instance/`` (Datenbank) noch Caches im Paket. """ from __future__ import annotations import argparse import sys import zipfile from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent DEFAULT_OUTPUT = PROJECT_ROOT / "dist" / "glory_defense.zip" #: Einzeldateien und Ordner, die auf den Server gehören. INCLUDE_FILES = ("passenger_wsgi.py", "requirements.txt", ".env.example") INCLUDE_DIRS = ("app",) #: Wird nie mitgepackt, auch wenn es unterhalb von INCLUDE_DIRS liegt. EXCLUDED_DIR_NAMES = frozenset({"__pycache__", ".pytest_cache", ".ruff_cache", "node_modules"}) EXCLUDED_SUFFIXES = frozenset({".pyc", ".pyo"}) def iter_deploy_files(root: Path = PROJECT_ROOT) -> list[Path]: """Alle Dateien des Upload-Pakets, sortiert (für reproduzierbare Archive).""" files = [root / name for name in INCLUDE_FILES if (root / name).is_file()] for directory in INCLUDE_DIRS: for path in (root / directory).rglob("*"): relative_parts = path.relative_to(root).parts if not path.is_file() or EXCLUDED_DIR_NAMES.intersection(relative_parts): continue if path.suffix in EXCLUDED_SUFFIXES: continue files.append(path) return sorted(files) def build_zip(target: Path, root: Path = PROJECT_ROOT) -> list[str]: """Schreibt das ZIP und gibt die enthaltenen Pfade (mit ``/``) zurück.""" target.parent.mkdir(parents=True, exist_ok=True) names = [] with zipfile.ZipFile(target, "w", compression=zipfile.ZIP_DEFLATED) as archive: for path in iter_deploy_files(root): # as_posix(): immer „/“ als Trenner, unabhängig vom Betriebssystem. name = path.relative_to(root).as_posix() archive.write(path, name) names.append(name) return names def main(argv: list[str] | None = None) -> int: """Kommandozeilen-Einstieg.""" parser = argparse.ArgumentParser(description="Upload-Paket für cPanel bauen.") parser.add_argument("-o", "--output", type=Path, default=DEFAULT_OUTPUT, help="Ziel-ZIP") args = parser.parse_args(argv) names = build_zip(args.output) size_kb = args.output.stat().st_size / 1024 print(f"{args.output} erstellt: {len(names)} Dateien, {size_kb:.0f} KB") return 0 if __name__ == "__main__": sys.exit(main())