diff --git a/cmdlet/screen_shot.py b/cmdlet/screen_shot.py index 36d53a8..c1fc452 100644 --- a/cmdlet/screen_shot.py +++ b/cmdlet/screen_shot.py @@ -646,7 +646,7 @@ def _capture(options: ScreenshotOptions, destination: Path, warnings: List[str], debug(f"[_capture] Exception launching browser/page: {exc}") msg = str(exc).lower() if any(k in msg for k in ["executable", "not found", "no such file", "cannot find", "install"]): - raise ScreenshotError("Chromium Playwright browser binaries not found. Install them: python ./scripts/setup.py --playwright-only --browsers chromium") from exc + raise ScreenshotError("Chromium Playwright browser binaries not found. Install them: python ./scripts/bootstrap.py --playwright-only --browsers chromium") from exc raise except ScreenshotError: # Re-raise ScreenshotError raised intentionally (do not wrap) @@ -1064,7 +1064,7 @@ CMDLET = Cmdlet( ], detail=[ - "Uses Playwright Chromium engine only. Install Chromium with: python ./scripts/setup.py --playwright-only --browsers chromium", + "Uses Playwright Chromium engine only. Install Chromium with: python ./scripts/bootstrap.py --playwright-only --browsers chromium", "PDF output requires headless Chromium (the cmdlet will enforce headless mode for PDF).", "Screenshots are temporary artifacts stored in the configured `temp` directory.", ] diff --git a/docs/BOOTSTRAP.md b/docs/BOOTSTRAP.md index db9e88b..f5e20a0 100644 --- a/docs/BOOTSTRAP.md +++ b/docs/BOOTSTRAP.md @@ -41,7 +41,7 @@ The bootstrap scripts will automatically install Deno if it is not already prese Opinionated behavior -Running `python ./scripts/setup.py` is intentionally opinionated: it will create a local virtual environment at `./.venv` (repo root), install Python dependencies and the project into that venv, install Playwright browsers, install Deno, and write small launcher scripts in the project root: +Running `python ./scripts/bootstrap.py` is intentionally opinionated: it will create a local virtual environment at `./.venv` (repo root), install Python dependencies and the project into that venv, install Playwright browsers, install Deno, and write small launcher scripts in the project root: - `mm` (POSIX shell) - `mm.ps1` (PowerShell) diff --git a/readme.md b/readme.md index 6daf6e2..7b7fadc 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ Medios-Macina is a CLI media manager and toolkit focused on downloading, tagging GIT CLONE https://code.glowers.club/goyimnose/Medios-Macina -1. run python setup.py +1. run python scripts\bootstrap.py.py 2. rename config.conf.remove to config.conf the store=folder path should be empty folder with no other files in it. diff --git a/scripts/bootstrap.py b/scripts/bootstrap.py new file mode 100644 index 0000000..f93a635 --- /dev/null +++ b/scripts/bootstrap.py @@ -0,0 +1,648 @@ +#!/usr/bin/env python3 +"""scripts/bootstrap.py + +Unified project bootstrap 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). + +When invoked without any arguments, `bootstrap.py` will automatically select and +run the platform-specific bootstrap helper (`scripts/bootstrap.ps1` on Windows +or `scripts/bootstrap.sh` on POSIX) in **non-interactive (quiet)** mode so a +single `python ./scripts/bootstrap.py` call does the usual bootstrap on your OS. + +The platform bootstrap scripts also attempt (best-effort) to install `mpv` if +it is not found on your PATH, since some workflows use it. + +This file replaces the old `scripts/setup.py` to ensure the repository only has +one `setup.py` (at the repository root) for packaging. + +Usage: + python ./scripts/bootstrap.py # install deps and playwright browsers (or run platform bootstrap if no args) + python ./scripts/bootstrap.py --skip-deps + python ./scripts/bootstrap.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 + --no-deno Skip installing the Deno runtime + --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 os +import platform +from pathlib import Path +import shutil +import subprocess +import sys +import time + + +def run(cmd: list[str]) -> None: + print(f"> {' '.join(cmd)}") + subprocess.check_call(cmd) + + +# Helpers to find shell executables and to run the platform-specific +# bootstrap script (scripts/bootstrap.sh or scripts/bootstrap.ps1). +def _find_powershell() -> str | None: + for name in ("pwsh", "powershell"): + p = shutil.which(name) + if p: + return p + return None + + +def _find_shell() -> str | None: + for name in ("bash", "sh"): + p = shutil.which(name) + if p: + return p + return None + + +def run_platform_bootstrap(repo_root: Path) -> int: + """Run the platform bootstrap script in quiet/non-interactive mode if present. + + Returns the script exit code (0 on success). If no script is present this is a + no-op and returns 0. + """ + + ps1 = repo_root / "scripts" / "bootstrap.ps1" + sh_script = repo_root / "scripts" / "bootstrap.sh" + + system = platform.system().lower() + + if system == "windows" and ps1.exists(): + exe = _find_powershell() + if not exe: + print("PowerShell not found; cannot run bootstrap.ps1", file=sys.stderr) + return 1 + cmd = [exe, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", str(ps1), "-Quiet"] + elif sh_script.exists(): + shell = _find_shell() + if not shell: + print("Shell not found; cannot run bootstrap.sh", file=sys.stderr) + return 1 + # Use -q (quiet) to skip interactive prompts when supported. + cmd = [shell, str(sh_script), "-q"] + else: + # Nothing to run + return 0 + + print("Running platform bootstrap script:", " ".join(cmd)) + rc = subprocess.run(cmd, cwd=str(repo_root)) + if rc.returncode != 0: + print(f"Bootstrap script failed with exit code {rc.returncode}", file=sys.stderr) + return int(rc.returncode or 0) + + +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 + + 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="Bootstrap 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 invoked without any arguments, prefer to delegate to the platform + # bootstrap script (if present). The bootstrap scripts support a quiet/ + # non-interactive mode, which we use so "python ./scripts/bootstrap.py" just + # does the right thing on Windows and *nix without extra flags. + if len(sys.argv) == 1: + rc = run_platform_bootstrap(repo_root) + if rc != 0: + return rc + print("Platform bootstrap completed successfully.") + return 0 + + if sys.version_info < (3, 8): + print("Warning: Python 3.8+ is recommended.", file=sys.stderr) + + # Opinionated: always create or use a local venv at the project root (.venv) + venv_dir = repo_root / ".venv" + + def _venv_python(p: Path) -> Path: + if platform.system().lower() == "windows": + return p / "Scripts" / "python.exe" + return p / "bin" / "python" + + def _ensure_local_venv() -> Path: + """Create (if missing) and return the path to the venv's python executable.""" + + try: + if not venv_dir.exists(): + print(f"Creating local virtualenv at: {venv_dir}") + run([sys.executable, "-m", "venv", str(venv_dir)]) + else: + print(f"Using existing virtualenv at: {venv_dir}") + + py = _venv_python(venv_dir) + if not py.exists(): + # Try recreating venv if python is missing + print(f"Local venv python not found at {py}; recreating venv") + run([sys.executable, "-m", "venv", str(venv_dir)]) + py = _venv_python(venv_dir) + if not py.exists(): + raise RuntimeError(f"Unable to locate venv python at {py}") + return py + except subprocess.CalledProcessError as exc: + print(f"Failed to create or prepare local venv: {exc}", file=sys.stderr) + raise + + # Ensure a local venv is present and use it for subsequent installs. + venv_python = _ensure_local_venv() + print(f"Using venv python: {venv_python}") + + # Enforce opinionated behavior: install deps, playwright, deno, and install project in editable mode. + # Ignore `--skip-deps` and `--install-editable` flags to keep the setup deterministic. + args.skip_deps = False + args.install_editable = True + args.no_playwright = False + + 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 in local venv...") + run([str(venv_python), "-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 into local venv from {req_file}...") + run([str(venv_python), "-m", "pip", "install", "-r", str(req_file)]) + + if not args.no_playwright: + if not playwright_package_installed(): + print("'playwright' package not installed in venv; installing it...") + run([str(venv_python), "-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 Playwright install using the venv's python so binaries are available in venv + cmd[0] = str(venv_python) + run(cmd) + + # Install the project into the local venv (editable mode is the default, opinionated) + print("Installing project into local venv (editable mode)") + run([str(venv_python), "-m", "pip", "install", "-e", "."]) + + # Verify top-level 'CLI' import and, if missing, attempt to make it available + print("Verifying top-level 'CLI' import in venv...") + try: + rc = subprocess.run( + [str(venv_python), "-c", "import importlib; importlib.import_module('CLI')"], + check=False, + ) + if rc.returncode == 0: + print("OK: top-level 'CLI' is importable in the venv.") + else: + print( + "Top-level 'CLI' not importable; attempting to add repo path to venv site-packages via a .pth file..." + ) + cmd = [ + str(venv_python), + "-c", + ( + "import site, sysconfig\n" + "out=[]\n" + "try:\n out.extend(site.getsitepackages())\nexcept Exception:\n pass\n" + "try:\n p = sysconfig.get_paths().get('purelib')\n if p:\n out.append(p)\nexcept Exception:\n pass\n" + "seen=[]; res=[]\n" + "for x in out:\n if x and x not in seen:\n seen.append(x); res.append(x)\n" + "for s in res:\n print(s)\n" + ), + ] + out = subprocess.check_output(cmd, text=True).strip().splitlines() + site_dir: Path | None = None + for sp in out: + if sp and Path(sp).exists(): + site_dir = Path(sp) + break + if site_dir is None: + print("Could not determine venv site-packages directory; skipping .pth fallback") + else: + pth_file = site_dir / "medeia_repo.pth" + if pth_file.exists(): + txt = pth_file.read_text(encoding="utf-8") + if str(repo_root) in txt: + print(f".pth already contains repo root: {pth_file}") + else: + with pth_file.open("a", encoding="utf-8") as fh: + fh.write(str(repo_root) + "\n") + print(f"Appended repo root to existing .pth: {pth_file}") + else: + with pth_file.open("w", encoding="utf-8") as fh: + fh.write(str(repo_root) + "\n") + print(f"Wrote .pth adding repo root to venv site-packages: {pth_file}") + + # Re-check whether CLI can be imported now + rc2 = subprocess.run( + [str(venv_python), "-c", "import importlib; importlib.import_module('CLI')"], check=False + ) + if rc2.returncode == 0: + print("Top-level 'CLI' import works after adding .pth") + else: + print( + "Adding .pth did not make top-level 'CLI' importable; consider creating an egg-link or checking the venv." + ) + except Exception as exc: + print(f"Warning: failed to verify or modify site-packages for top-level CLI: {exc}") + + # 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 (local/system)...") + rc = _install_deno(args.deno_version) + if rc != 0: + print("Deno installation failed.", file=sys.stderr) + return rc + + # Write project-local launcher scripts (project root) that prefer the local .venv + def _write_launchers() -> None: + sh = repo_root / "mm" + ps1 = repo_root / "mm.ps1" + bat = repo_root / "mm.bat" + + sh_text = """#!/usr/bin/env bash +set -e +SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\" +REPO=\"$SCRIPT_DIR\" +VENV=\"$REPO/.venv\" +# Make tools installed into the local venv available in PATH for provider discovery +export PATH=\"$VENV/bin:$PATH\" +PY=\"$VENV/bin/python\" +if [ -x \"$PY\" ]; then + exec \"$PY\" -m medeia_macina.cli_entry \"$@\" +else + exec python -m medeia_macina.cli_entry \"$@\" +fi +""" + try: + sh.write_text(sh_text, encoding="utf-8") + sh.chmod(sh.stat().st_mode | 0o111) + except Exception: + pass + + ps1_text = r"""Param([Parameter(ValueFromRemainingArguments=$true)] $args) +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$repo = $scriptDir +$venv = Join-Path $repo '.venv' +# Ensure venv Scripts dir is on PATH for provider discovery +$venvScripts = Join-Path $venv 'Scripts' +if (Test-Path $venvScripts) { $env:PATH = $venvScripts + ';' + $env:PATH } +$py = Join-Path $venv 'Scripts\python.exe' +$cli = Join-Path $repo 'CLI.py' +if (Test-Path $py) { & $py -m medeia_macina.cli_entry @args; exit $LASTEXITCODE } +if (Test-Path $cli) { & python $cli @args; exit $LASTEXITCODE } +# fallback +python -m medeia_macina.cli_entry @args +""" + try: + ps1.write_text(ps1_text, encoding="utf-8") + except Exception: + pass + + bat_text = ( + "@echo off\r\n" + "set SCRIPT_DIR=%~dp0\r\n" + "set PATH=%SCRIPT_DIR%\\.venv\\Scripts;%PATH%\r\n" + "if exist \"%SCRIPT_DIR%\\.venv\\Scripts\\python.exe\" \"%SCRIPT_DIR%\\.venv\\Scripts\\python.exe\" -m medeia_macina.cli_entry %*\r\n" + "if exist \"%SCRIPT_DIR%\\CLI.py\" python \"%SCRIPT_DIR%\\CLI.py\" %*\r\n" + "python -m medeia_macina.cli_entry %*\r\n" + ) + try: + bat.write_text(bat_text, encoding="utf-8") + except Exception: + pass + + _write_launchers() + + # Install user-global shims so `mm` can be executed from any shell session. + def _install_user_shims(repo: Path) -> None: + try: + home = Path.home() + system = platform.system().lower() + + if system == "windows": + user_bin = Path(os.environ.get("USERPROFILE", str(home))) / "bin" + user_bin.mkdir(parents=True, exist_ok=True) + + # Write mm.cmd (CMD shim) + mm_cmd = user_bin / "mm.cmd" + cmd_text = ( + f"@echo off\r\n" + f"set REPO={repo}\r\n" + f"if exist \"%REPO%\\.venv\\Scripts\\mm.exe\" \"%REPO%\\.venv\\Scripts\\mm.exe\" %*\r\n" + f"if defined MM_DEBUG (\r\n" + f" echo MM_DEBUG: REPO=%REPO%\r\n" + f" if exist \"%REPO%\\.venv\\Scripts\\python.exe\" \"%REPO%\\.venv\\Scripts\\python.exe\" -c \"import sys,importlib,importlib.util; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);\" \r\n" + f")\r\n" + f"if exist \"%REPO%\\.venv\\Scripts\\python.exe\" \"%REPO%\\.venv\\Scripts\\python.exe\" -m medeia_macina.cli_entry %*\r\n" + f"python -m medeia_macina.cli_entry %*\r\n" + ) + if mm_cmd.exists(): + bak = mm_cmd.with_suffix(f".bak{int(time.time())}") + mm_cmd.replace(bak) + mm_cmd.write_text(cmd_text, encoding="utf-8") + + # Write mm.ps1 (PowerShell shim) + mm_ps1 = user_bin / "mm.ps1" + ps1_text = ( + "Param([Parameter(ValueFromRemainingArguments=$true)] $args)\n" + f"$repo = \"{repo}\"\n" + "$venv = Join-Path $repo '.venv'\n" + "$exe = Join-Path $venv 'Scripts\\mm.exe'\n" + "if (Test-Path $exe) { & $exe @args; exit $LASTEXITCODE }\n" + "$py = Join-Path $venv 'Scripts\\python.exe'\n" + "if (Test-Path $py) {\n" + " if ($env:MM_DEBUG) {\n" + " Write-Host \"MM_DEBUG: diagnostics\" -ForegroundColor Yellow\n" + " & $py -c \"import sys,importlib,importlib.util,traceback; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);\"\n" + " }\n" + " & $py -m medeia_macina.cli_entry @args; exit $LASTEXITCODE\n" + "}\n" + "python -m medeia_macina.cli_entry @args\n" + ) + if mm_ps1.exists(): + bak = mm_ps1.with_suffix(f".bak{int(time.time())}") + mm_ps1.replace(bak) + mm_ps1.write_text(ps1_text, encoding="utf-8") + + # Attempt to add user_bin to the user's PATH if it's not present. + try: + cur = os.environ.get("PATH", "") + str_bin = str(user_bin) + if str_bin not in cur: + ps_cmd = ( + "$bin = '{bin}';" + "$cur = [Environment]::GetEnvironmentVariable('PATH','User');" + "if ($cur -notlike \"*$bin*\") {[Environment]::SetEnvironmentVariable('PATH', ($bin + ';' + ($cur -ne $null ? $cur : '')), 'User')}" + ).format(bin=str_bin.replace("\\", "\\\\")) + subprocess.run(["powershell", "-NoProfile", "-Command", ps_cmd], check=False) + except Exception: + pass + + print(f"Installed global launchers to: {user_bin}") + + else: + # POSIX + user_bin = Path(os.environ.get("XDG_BIN_HOME", str(home / ".local/bin"))) + user_bin.mkdir(parents=True, exist_ok=True) + + mm_sh = user_bin / "mm" + sh_text = ( + "#!/usr/bin/env bash\n" + "set -e\n" + f"REPO=\"{repo}\"\n" + "# Prefer git top-level when available to avoid embedding a parent path.\n" + "if command -v git >/dev/null 2>&1; then\n" + " gitroot=$(git -C \"$REPO\" rev-parse --show-toplevel 2>/dev/null || true)\n" + " if [ -n \"$gitroot\" ]; then\n" + " REPO=\"$gitroot\"\n" + " fi\n" + "fi\n" + "# If git not available or didn't resolve, walk up from CWD to find a project root.\n" + "if [ ! -f \"$REPO/CLI.py\" ] && [ ! -f \"$REPO/pyproject.toml\" ]; then\n" + " CUR=\"$(pwd -P)\"\n" + " while [ \"$CUR\" != \"/\" ] && [ \"$CUR\" != \"\" ]; do\n" + " if [ -f \"$CUR/CLI.py\" ] || [ -f \"$CUR/pyproject.toml\" ]; then\n" + " REPO=\"$CUR\"\n" + " break\n" + " fi\n" + " CUR=\"$(dirname \"$CUR\")\"\n" + " done\n" + "fi\n" + "VENV=\"$REPO/.venv\"\n" + "# Debug mode: set MM_DEBUG=1 to print repository, venv, and import diagnostics\n" + "if [ -n \"${MM_DEBUG:-}\" ]; then\n" + " echo \"MM_DEBUG: diagnostics\" >&2\n" + " echo \"Resolved REPO: $REPO\" >&2\n" + " echo \"Resolved VENV: $VENV\" >&2\n" + " echo \"VENV exists: $( [ -d \"$VENV\" ] && echo yes || echo no )\" >&2\n" + " echo \"Candidates:\" >&2\n" + " echo \" VENV/bin/mm: $( [ -x \"$VENV/bin/mm\" ] && echo yes || echo no )\" >&2\n" + " echo \" VENV/bin/python3: $( [ -x \"$VENV/bin/python3\" ] && echo yes || echo no )\" >&2\n" + " echo \" VENV/bin/python: $( [ -x \"$VENV/bin/python\" ] && echo yes || echo no )\" >&2\n" + " echo \" system python3: $(command -v python3 || echo none)\" >&2\n" + " echo \" system python: $(command -v python || echo none)\" >&2\n" + " for pycmd in \"$VENV/bin/python3\" \"$VENV/bin/python\" \"$(command -v python3 2>/dev/null)\" \"$(command -v python 2>/dev/null)\"; do\n" + " if [ -n \"$pycmd\" ] && [ -x \"$pycmd\" ]; then\n" + " echo \"---- Testing with: $pycmd ----\" >&2\n" + " $pycmd - <<'PY'\nimport sys, importlib, traceback, importlib.util\nprint('sys.executable:', sys.executable)\nprint('sys.path (first 8):', sys.path[:8])\nfor mod in ('CLI','medeia_macina','medeia_macina.cli_entry'):\n try:\n spec = importlib.util.find_spec(mod)\n print(mod, 'spec:', spec)\n if spec:\n m = importlib.import_module(mod)\n print(mod, 'loaded at', getattr(m, '__file__', None))\n except Exception:\n print(mod, 'import failed')\n traceback.print_exc()\nPY\n" + " fi\n" + " done\n" + " echo \"MM_DEBUG: end diagnostics\" >&2\n" + "fi\n" + "# Packaged console script in the venv if available\n" + "if [ -x \"$VENV/bin/mm\" ]; then\n" + " exec \"$VENV/bin/mm\" \"$@\"\n" + "fi\n" + "# Prefer venv's python3, then venv's python\n" + "if [ -x \"$VENV/bin/python3\" ]; then\n" + " exec \"$VENV/bin/python3\" -m medeia_macina.cli_entry \"$@\"\n" + "fi\n" + "if [ -x \"$VENV/bin/python\" ]; then\n" + " exec \"$VENV/bin/python\" -m medeia_macina.cli_entry \"$@\"\n" + "fi\n" + "# Fallback to system python3, then system python (only if it's Python 3)\n" + "if command -v python3 >/dev/null 2>&1; then\n" + " exec python3 -m medeia_macina.cli_entry \"$@\"\n" + "fi\n" + "if command -v python >/dev/null 2>&1; then\n" + " if python -c 'import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)'; then\n" + " exec python -m medeia_macina.cli_entry \"$@\"\n" + " fi\n" + "fi\n" + "echo 'Error: no suitable Python 3 interpreter found. Please install Python 3 or use the venv.' >&2\n" + "exit 127\n" + ) + if mm_sh.exists(): + bak = mm_sh.with_suffix(f".bak{int(time.time())}") + mm_sh.replace(bak) + mm_sh.write_text(sh_text, encoding="utf-8") + mm_sh.chmod(mm_sh.stat().st_mode | 0o111) + + # Ensure the user's bin is on PATH for future sessions by adding to ~/.profile + cur_path = os.environ.get("PATH", "") + if str(user_bin) not in cur_path: + profile = home / ".profile" + snippet = ( + "# Added by Medeia-Macina setup: ensure user local bin is on PATH\n" + "if [ -d \"$HOME/.local/bin\" ] && [[ \":$PATH:\" != *\":$HOME/.local/bin:\"* ]]; then\n" + " PATH=\"$HOME/.local/bin:$PATH\"\n" + "fi\n" + ) + try: + txt = profile.read_text() if profile.exists() else "" + if snippet.strip() not in txt: + with profile.open("a", encoding="utf-8") as fh: + fh.write("\n" + snippet) + except Exception: + pass + + print(f"Installed global launcher to: {mm_sh}") + + except Exception as exc: # pragma: no cover - best effort + print(f"Failed to install global shims: {exc}", file=sys.stderr) + + _install_user_shims(repo_root) + + 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()) diff --git a/scripts/setup.py b/scripts/setup.py index 207950d..5f9b416 100644 --- a/scripts/setup.py +++ b/scripts/setup.py @@ -1,4 +1,23 @@ #!/usr/bin/env python3 +"""DEPRECATED: scripts/setup.py + +This file has been renamed to `scripts/bootstrap.py` to avoid having multiple +`setup.py` files in the repository. Please use: + + python ./scripts/bootstrap.py + +This shim remains temporarily for backwards compatibility. + +--- + +Original docstring: + +scripts/setup.py + +""" + +Unified project setup helper (Python-only). +#!/usr/bin/env python3 """scripts/setup.py Unified project setup helper (Python-only). @@ -17,7 +36,7 @@ The platform bootstrap scripts also attempt (best-effort) to install `mpv` if it is not found on your PATH, since some workflows use it. Usage: - python ./scripts/setup.py # install deps and playwright browsers (or run platform bootstrap if no args) + python ./scripts/bootstrap.py # install deps and playwright browsers (or run platform bootstrap if no args) python ./scripts/setup.py --skip-deps python ./scripts/setup.py --playwright-only @@ -418,7 +437,7 @@ python -m medeia_macina.cli_entry @args "python -m medeia_macina.cli_entry %*\r\n" ) try: - bat.write_text(bat_text, encoding="utf-8") + # non-interactive mode, which we use so "python ./scripts/bootstrap.py" just except Exception: pass