#!/usr/bin/env python3 """scripts/setup.py Unified project setup helper (Python-only). This script installs Python dependencies from `requirements.txt` and then downloads Playwright browser binaries by running `python -m playwright install`. By default this script installs **Chromium** only to conserve space; pass `--browsers all` to install all supported engines (chromium, firefox, webkit). Usage: python ./scripts/setup.py # install deps and playwright browsers python ./scripts/setup.py --skip-deps python ./scripts/setup.py --playwright-only Optional flags: --skip-deps Skip `pip install -r requirements.txt` step --no-playwright Skip running `python -m playwright install` (still installs deps) --playwright-only Install only Playwright browsers (installs playwright package if missing) --browsers Comma-separated list of Playwright browsers to install (default: chromium) --install-editable Install the project in editable mode (pip install -e .) for running tests --install-deno Install the Deno runtime using the official installer --deno-version Pin a specific Deno version to install (e.g., v1.34.3) --upgrade-pip Upgrade pip, setuptools, and wheel before installing deps """ from __future__ import annotations import argparse import subprocess import sys from pathlib import Path import platform import shutil def run(cmd: list[str]) -> None: print(f"> {' '.join(cmd)}") subprocess.check_call(cmd) def playwright_package_installed() -> bool: try: import playwright # type: ignore return True except Exception: return False def _build_playwright_install_cmd(browsers: str | None) -> list[str]: """Return the command to install Playwright browsers. - If browsers is None or empty: default to install Chromium only. - If browsers contains 'all': install all engines by running 'playwright install' with no extra args. - Otherwise, validate entries and return a command that installs the named engines. """ base = [sys.executable, "-m", "playwright", "install"] if not browsers: return base + ["chromium"] items = [b.strip().lower() for b in browsers.split(",") if b.strip()] if not items: return base + ["chromium"] if "all" in items: return base allowed = {"chromium", "firefox", "webkit"} invalid = [b for b in items if b not in allowed] if invalid: raise ValueError(f"invalid browsers specified: {invalid}. Valid choices: chromium, firefox, webkit, or 'all'") return base + items def _install_deno(version: str | None = None) -> int: """Install Deno runtime for the current platform. Uses the official Deno install scripts: - Unix/macOS: curl -fsSL https://deno.land/x/install/install.sh | sh [-s ] - Windows: powershell iwr https://deno.land/x/install/install.ps1 -useb | iex; Install-Deno [-Version ] Returns exit code 0 on success, non-zero otherwise. """ system = platform.system().lower() try: if system == "windows": # Use official PowerShell installer if version: ver = version if version.startswith("v") else f"v{version}" ps_cmd = f"iwr https://deno.land/x/install/install.ps1 -useb | iex; Install-Deno -Version {ver}" else: ps_cmd = "iwr https://deno.land/x/install/install.ps1 -useb | iex" run(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ps_cmd]) else: # POSIX: use curl + sh installer if version: ver = version if version.startswith("v") else f"v{version}" cmd = f"curl -fsSL https://deno.land/x/install/install.sh | sh -s {ver}" else: cmd = "curl -fsSL https://deno.land/x/install/install.sh | sh" run(["sh", "-c", cmd]) # Check that 'deno' is now available in PATH if shutil.which("deno"): print(f"Deno installed at: {shutil.which('deno')}") return 0 else: print("Deno installation completed but 'deno' not found in PATH. You may need to add Deno's bin directory to your PATH manually.", file=sys.stderr) return 1 except subprocess.CalledProcessError as exc: print(f"Deno install failed: {exc}", file=sys.stderr) return int(exc.returncode or 1) def main() -> int: parser = argparse.ArgumentParser(description="Setup Medios-Macina: install deps and Playwright browsers") parser.add_argument("--skip-deps", action="store_true", help="Skip installing Python dependencies from requirements.txt") parser.add_argument("--no-playwright", action="store_true", help="Skip running 'playwright install' (only install packages)") parser.add_argument("--playwright-only", action="store_true", help="Only run 'playwright install' (skips dependency installation)") parser.add_argument("--browsers", type=str, default="chromium", help="Comma-separated list of browsers to install: chromium,firefox,webkit or 'all' (default: chromium)") parser.add_argument("--install-editable", action="store_true", help="Install the project in editable mode (pip install -e .) for running tests") deno_group = parser.add_mutually_exclusive_group() deno_group.add_argument("--install-deno", action="store_true", help="Install the Deno runtime (default behavior; kept for explicitness)") deno_group.add_argument("--no-deno", action="store_true", help="Skip installing Deno runtime (opt out)") parser.add_argument("--deno-version", type=str, default=None, help="Specific Deno version to install (e.g., v1.34.3)") parser.add_argument("--upgrade-pip", action="store_true", help="Upgrade pip/setuptools/wheel before installing requirements") args = parser.parse_args() repo_root = Path(__file__).resolve().parent.parent if sys.version_info < (3, 8): print("Warning: Python 3.8+ is recommended.", file=sys.stderr) try: if args.playwright_only: if not playwright_package_installed(): print("'playwright' package not found; installing it via pip...") run([sys.executable, "-m", "pip", "install", "playwright"]) print("Installing Playwright browsers (this may download several hundred MB)...") try: cmd = _build_playwright_install_cmd(args.browsers) except ValueError as exc: print(f"Error: {exc}", file=sys.stderr) return 2 run(cmd) print("Playwright browsers installed successfully.") return 0 if args.upgrade_pip: print("Upgrading pip, setuptools, and wheel...") run([sys.executable, "-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"]) if not args.skip_deps: req_file = repo_root / "requirements.txt" if not req_file.exists(): print(f"requirements.txt not found at {req_file}; skipping dependency installation.", file=sys.stderr) else: print(f"Installing Python dependencies from {req_file}...") run([sys.executable, "-m", "pip", "install", "-r", str(req_file)]) if not args.no_playwright: if not playwright_package_installed(): print("'playwright' package not installed; installing it...") run([sys.executable, "-m", "pip", "install", "playwright"]) print("Installing Playwright browsers (this may download several hundred MB)...") try: cmd = _build_playwright_install_cmd(args.browsers) except ValueError as exc: print(f"Error: {exc}", file=sys.stderr) return 2 run(cmd) # Optional: install the project in editable mode so tests can import the package if args.install_editable: print("Installing project in editable mode (pip install -e .) ...") run([sys.executable, "-m", "pip", "install", "-e", "."]) # Optional: install Deno runtime (default: install unless --no-deno is passed) install_deno_requested = True if getattr(args, "no_deno", False): install_deno_requested = False elif getattr(args, "install_deno", False): install_deno_requested = True if install_deno_requested: print("Installing Deno runtime...") rc = _install_deno(args.deno_version) if rc != 0: print("Deno installation failed.", file=sys.stderr) return rc print("Setup complete.") return 0 except subprocess.CalledProcessError as exc: print(f"Error: command failed with exit {exc.returncode}: {exc}", file=sys.stderr) return int(exc.returncode or 1) except Exception as exc: # pragma: no cover - defensive print(f"Unexpected error: {exc}", file=sys.stderr) return 2 if __name__ == "__main__": raise SystemExit(main())