#!/usr/bin/env python3 """ SkillMate - Windows-optimierter Haupteinstiegspunkt """ import os import sys import subprocess import time import webbrowser import signal import atexit import argparse from pathlib import Path from typing import Optional, List class SkillMateStarter: def __init__(self, mode: str = 'dev'): self.processes = [] self.base_dir = Path(__file__).parent.absolute() self.mode = mode # 'dev' oder 'prod' # Ports (Backend/Frontend/Admin) self.backend_port = int(os.environ.get('PORT', '3004')) self.frontend_port = 5173 self.admin_port = 5174 # Registriere Cleanup-Handler atexit.register(self.cleanup) if sys.platform != 'win32': signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler) def signal_handler(self, signum, frame): """Handle Strg+C und andere Signale""" print("\n\n🛑 SkillMate wird beendet...") self.cleanup() sys.exit(0) def cleanup(self): """Beende alle gestarteten Prozesse""" for process in self.processes: try: if process.poll() is None: # Prozess läuft noch if sys.platform == 'win32': subprocess.call(['taskkill', '/F', '/T', '/PID', str(process.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) else: process.terminate() process.wait(timeout=5) except: pass # Sicherheitshalber Ports freigeben, falls noch Prozesse hängen if sys.platform == 'win32': for p in [self.backend_port, self.frontend_port, self.admin_port]: self._free_port_windows(p) def _popen_new_console(self, workdir: Path, command: str) -> Optional[subprocess.Popen]: try: creationflag = getattr(subprocess, 'CREATE_NEW_CONSOLE', 0x00000010) p = subprocess.Popen( ["cmd", "/k", command], cwd=str(workdir), creationflags=creationflag, ) self.processes.append(p) return p except Exception as e: print(f"❌ Konnte Konsole nicht starten: {e}") return None def _free_port_windows(self, port: int): """Versucht Prozesse auf Port zu beenden (Windows).""" try: # Finde Zeilen mit Port out = subprocess.check_output(f'netstat -ano | findstr :{port}', shell=True, text=True, stderr=subprocess.DEVNULL) pids: List[str] = [] for line in out.splitlines(): parts = line.split() if parts: pid = parts[-1] if pid.isdigit(): pids.append(pid) # Einzigartige PIDs killen for pid in sorted(set(pids)): try: subprocess.call(["taskkill", "/PID", pid, "/F"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except Exception: pass except subprocess.CalledProcessError: # Kein Treffer → Port frei pass def check_node_npm(self): """Prüfe ob Node.js und npm installiert sind""" try: # Windows-spezifische Prüfung if sys.platform == 'win32': subprocess.run(["node", "--version"], capture_output=True, check=True, shell=True) subprocess.run(["npm", "--version"], capture_output=True, check=True, shell=True) else: subprocess.run(["node", "--version"], capture_output=True, check=True) subprocess.run(["npm", "--version"], capture_output=True, check=True) return True except (subprocess.CalledProcessError, FileNotFoundError): print("❌ Node.js und npm müssen installiert sein!") print(" Bitte installieren Sie Node.js von: https://nodejs.org/") return False def start_services_windows(self): """Starte alle Services in Windows mit separaten CMD-Fenstern""" print("🚀 Starte SkillMate Services...") # Ports freimachen (wenn vorherige Läufe hingen blieben) for p in [self.backend_port, self.frontend_port, self.admin_port]: self._free_port_windows(p) # Backend backend_dir = self.base_dir / "backend" if self.mode == 'dev': self._popen_new_console(backend_dir, "npm run dev") else: # Produktionsstart: Build & Start self._popen_new_console(backend_dir, "cmd /c npm run build && npm start") # Warte bis Backend bereit ist print(" Warte auf Backend...") time.sleep(5) # Frontend frontend_dir = self.base_dir / "frontend" if self.mode == 'dev': self._popen_new_console(frontend_dir, f"npm run dev -- --port {self.frontend_port}") else: self._popen_new_console(frontend_dir, f"cmd /c npm run build && npm run preview -- --port {self.frontend_port}") # Admin Panel (optional) admin_dir = self.base_dir / "admin-panel" if admin_dir.exists(): if self.mode == 'dev': self._popen_new_console(admin_dir, f"npm run dev -- --port {self.admin_port}") else: self._popen_new_console(admin_dir, f"cmd /c npm run build && npm run preview -- --port {self.admin_port}") print("\n✨ SkillMate läuft!") print("\n📍 Zugriff:") print(f" - Frontend: http://localhost:{self.frontend_port}") print(f" - Backend: http://localhost:{self.backend_port}") if admin_dir.exists(): print(f" - Admin: http://localhost:{self.admin_port}") # Öffne Frontend im Browser time.sleep(3) webbrowser.open(f"http://localhost:{self.frontend_port}") print("\n⚡ Schließen Sie dieses Fenster, um SkillMate zu beenden") def run(self): """Hauptausführungsmethode""" print("🎯 SkillMate wird gestartet...\n") # Prüfe Voraussetzungen if not self.check_node_npm(): return False # Windows-spezifischer Start if sys.platform == 'win32': self.start_services_windows() # Halte das Hauptfenster offen try: input("\nDrücken Sie Enter zum Beenden...") except KeyboardInterrupt: pass else: print("❌ Dieses Skript ist für Windows optimiert.") print(" Nutzen Sie 'python main.py' für andere Systeme.") return False return True def main(): """Haupteinstiegspunkt""" parser = argparse.ArgumentParser(description='SkillMate Starter') parser.add_argument('--mode', choices=['dev', 'prod'], default='dev', help='Startmodus: dev oder prod') args = parser.parse_args() starter = SkillMateStarter(mode=args.mode) try: success = starter.run() if not success: sys.exit(1) except Exception as e: print(f"\n❌ Unerwarteter Fehler: {e}") starter.cleanup() sys.exit(1) if __name__ == "__main__": main()