import argparse import hashlib import os from pathlib import Path import shutil import subprocess import sys ROOT = Path(__file__).resolve().parent BACKEND = ROOT / "backend" FRONTEND = ROOT / "frontend" VENV = ROOT / ".venv" def install_dependencies( inputs: tuple[Path, ...], stamp: Path, command: list[str], cwd: Path ) -> None: fingerprint = hashlib.sha256() for path in inputs: fingerprint.update(hashlib.sha256(path.read_bytes()).digest()) current = fingerprint.hexdigest() if stamp.is_file() and stamp.read_text(encoding="ascii") == current: return stamp.unlink(missing_ok=True) subprocess.run(command, cwd=cwd, check=True) stamp.write_text(current, encoding="ascii") def main() -> int: parser = argparse.ArgumentParser(description="Локальный запуск AI-заметок.") parser.add_argument("--port", type=int, default=8000, help="Порт приложения (по умолчанию 8000).") args = parser.parse_args() if not 1 <= args.port <= 65535: parser.error("Порт должен быть от 1 до 65535.") if sys.version_info < (3, 11): parser.exit(1, "Установите Python 3.11 или новее.\n") npm = shutil.which("npm.cmd" if os.name == "nt" else "npm") if not npm or not shutil.which("node"): parser.exit(1, "Установите Node.js с npm и заново откройте терминал.\n") env_file = ROOT / ".env" if not env_file.exists(): shutil.copyfile(ROOT / ".env.example", env_file) print("Создан .env. Для генерации добавьте ключ Groq в этот файл.", flush=True) python = VENV / ("Scripts/python.exe" if os.name == "nt" else "bin/python") if not python.is_file(): subprocess.run([sys.executable, "-m", "venv", str(VENV)], check=True) install_dependencies( (BACKEND / "requirements.txt",), VENV / ".notes-dependencies", [str(python), "-m", "pip", "install", "-r", "requirements.txt"], BACKEND, ) install_dependencies( (FRONTEND / "package.json", FRONTEND / "package-lock.json"), FRONTEND / "node_modules" / ".notes-dependencies", [npm, "ci"], FRONTEND, ) subprocess.run([npm, "run", "build"], cwd=FRONTEND, check=True) return subprocess.call( [ str(python), "-m", "uvicorn", "app.main:app", "--env-file", str(env_file), "--host", "127.0.0.1", "--port", str(args.port), ], cwd=BACKEND, env={**os.environ, "NOTES_WEB_DIST": str(FRONTEND / "dist")}, ) if __name__ == "__main__": try: raise SystemExit(main()) except subprocess.CalledProcessError as error: print("Запуск остановлен. Причина указана выше.", file=sys.stderr) raise SystemExit(error.returncode) from None except KeyboardInterrupt: raise SystemExit(130) from None