This commit is contained in:
2026-01-09 15:41:38 -08:00
parent 94aca4d3d4
commit a70482fdf1
2 changed files with 68 additions and 11 deletions

View File

@@ -302,6 +302,11 @@ def main() -> int:
# This prevents issues when bootstrap.py is run from different directories # This prevents issues when bootstrap.py is run from different directories
script_dir = Path(__file__).resolve().parent script_dir = Path(__file__).resolve().parent
repo_root = script_dir.parent repo_root = script_dir.parent
if not args.quiet:
print(f"Bootstrap script location: {script_dir}")
print(f"Detected project root: {repo_root}")
print(f"Current working directory: {Path.cwd()}")
# Helpers for interactive menu and uninstall detection # Helpers for interactive menu and uninstall detection
def _venv_python_path(p: Path) -> Path | None: def _venv_python_path(p: Path) -> Path | None:
@@ -554,6 +559,14 @@ def main() -> int:
# Opinionated: always create or use a local venv at the project root (.venv) # Opinionated: always create or use a local venv at the project root (.venv)
venv_dir = repo_root / ".venv" venv_dir = repo_root / ".venv"
# Validate that venv_dir is where we expect it to be
if not args.quiet:
print(f"Planned venv location: {venv_dir}")
if venv_dir.parent != repo_root:
print(f"WARNING: venv parent is {venv_dir.parent}, expected {repo_root}", file=sys.stderr)
if "scripts" in str(venv_dir).lower():
print(f"WARNING: venv path contains 'scripts': {venv_dir}", file=sys.stderr)
def _venv_python(p: Path) -> Path: def _venv_python(p: Path) -> Path:
if platform.system().lower() == "windows": if platform.system().lower() == "windows":
@@ -834,22 +847,25 @@ if (Test-Path (Join-Path $repo 'CLI.py')) {
user_bin = Path(os.environ.get("USERPROFILE", str(home))) / "bin" user_bin = Path(os.environ.get("USERPROFILE", str(home))) / "bin"
user_bin.mkdir(parents=True, exist_ok=True) user_bin.mkdir(parents=True, exist_ok=True)
# Convert repo path to string with proper escaping
repo_str = str(repo).replace("\\", "\\\\")
# Write mm.ps1 (PowerShell shim) # Write mm.ps1 (PowerShell shim)
mm_ps1 = user_bin / "mm.ps1" mm_ps1 = user_bin / "mm.ps1"
ps1_text = ( ps1_text = (
"Param([Parameter(ValueFromRemainingArguments=$true)] $args)\n" "Param([Parameter(ValueFromRemainingArguments=$true)] $args)\n"
f'$repo = "{repo}"\n' f'$repo = "{repo_str}"\n'
"$venv = Join-Path $repo '.venv'\n" "$venv = Join-Path $repo '.venv'\n"
"$py = Join-Path $venv 'Scripts\\python.exe'\n" "$py = Join-Path $venv 'Scripts\\python.exe'\n"
"if (Test-Path $py) {\n" "if (Test-Path $py) {\n"
" if ($env:MM_DEBUG) {\n" " if ($env:MM_DEBUG) {\n"
' Write-Host "MM_DEBUG: using venv python at $py" -ForegroundColor Yellow\n' ' Write-Host "MM_DEBUG: using venv python at $py" -ForegroundColor Yellow\n'
" & $py -c \"import sys; print('sys.executable:', sys.executable)\"\n" " & $py -c \"import sys; print('sys.executable:', sys.executable); print('sys.path:', sys.path[:5])\"\n"
" }\n" " }\n"
" & $py -m scripts.cli_entry @args; exit $LASTEXITCODE\n" " & $py -m scripts.cli_entry @args; exit $LASTEXITCODE\n"
"}\n" "}\n"
"# Fallback to system python if venv doesn't exist\n" "# Fallback to system python if venv doesn't exist\n"
"if ($env:MM_DEBUG) { Write-Host 'MM_DEBUG: venv python not found, trying system python' -ForegroundColor Yellow }\n" "if ($env:MM_DEBUG) { Write-Host 'MM_DEBUG: venv python not found at' $py ', trying system python' -ForegroundColor Yellow }\n"
"python -m scripts.cli_entry @args\n" "python -m scripts.cli_entry @args\n"
) )
if mm_ps1.exists(): if mm_ps1.exists():
@@ -859,21 +875,22 @@ if (Test-Path (Join-Path $repo 'CLI.py')) {
# Write mm.bat (CMD shim for better compatibility) # Write mm.bat (CMD shim for better compatibility)
mm_bat = user_bin / "mm.bat" mm_bat = user_bin / "mm.bat"
repo_bat_str = str(repo)
bat_text = ( bat_text = (
"@echo off\n" "@echo off\n"
"setlocal enabledelayedexpansion\n" "setlocal enabledelayedexpansion\n"
f'set "REPO={repo}"\n' f'set "REPO={repo_bat_str}"\n'
"set \"VENV=!REPO!\\.venv\"\n" "set \"VENV=!REPO!\\.venv\"\n"
"set \"PY=!VENV!\\Scripts\\python.exe\"\n" "set \"PY=!VENV!\\Scripts\\python.exe\"\n"
"if exist \"!PY!\" (\n" "if exist \"!PY!\" (\n"
" if defined MM_DEBUG (\n" " if defined MM_DEBUG (\n"
" echo MM_DEBUG: using venv python at !PY!\n" " echo MM_DEBUG: using venv python at !PY!\n"
" \"!PY!\" -c \"import sys; print('sys.executable:', sys.executable)\"\n" " \"!PY!\" -c \"import sys; print('sys.executable:', sys.executable); print('sys.path:', sys.path[:5])\"\n"
" )\n" " )\n"
" \"!PY!\" -m scripts.cli_entry %*\n" " \"!PY!\" -m scripts.cli_entry %*\n"
" exit /b !ERRORLEVEL!\n" " exit /b !ERRORLEVEL!\n"
")\n" ")\n"
"echo MM: venv not found at !VENV!, trying system python\n" "echo MM: venv python not found at !PY!\n"
"if defined MM_DEBUG echo MM_DEBUG: venv python not found, trying system python\n" "if defined MM_DEBUG echo MM_DEBUG: venv python not found, trying system python\n"
"python -m scripts.cli_entry %*\n" "python -m scripts.cli_entry %*\n"
"exit /b !ERRORLEVEL!\n" "exit /b !ERRORLEVEL!\n"

View File

@@ -11,6 +11,7 @@ from __future__ import annotations
from typing import Optional, List, Tuple from typing import Optional, List, Tuple
import importlib import importlib
import importlib.util import importlib.util
import os
import sys import sys
from pathlib import Path from pathlib import Path
import shlex import shlex
@@ -25,7 +26,7 @@ def _ensure_repo_root_on_sys_path(pkg_file: Optional[Path] = None) -> Optional[P
not necessarily on `sys.path`, which breaks `import CLI`. not necessarily on `sys.path`, which breaks `import CLI`.
We infer the repo root by walking up from this package location and looking We infer the repo root by walking up from this package location and looking
for a sibling `CLI.py`. for a sibling `CLI.py` or checking parent directories.
`pkg_file` exists for unit tests; production uses this module's `__file__`. `pkg_file` exists for unit tests; production uses this module's `__file__`.
""" """
@@ -34,6 +35,7 @@ def _ensure_repo_root_on_sys_path(pkg_file: Optional[Path] = None) -> Optional[P
except Exception: except Exception:
return return
# Strategy 1: Look for CLI.py in parent directories (starting from scripts parent)
for parent in pkg_dir.parents: for parent in pkg_dir.parents:
try: try:
if (parent / "CLI.py").exists(): if (parent / "CLI.py").exists():
@@ -43,6 +45,22 @@ def _ensure_repo_root_on_sys_path(pkg_file: Optional[Path] = None) -> Optional[P
return parent return parent
except Exception: except Exception:
continue continue
# Strategy 2: If in a venv, check the .venv/.. path (project root)
try:
# If this file is in .../venv/lib/python3.x/site-packages/scripts/
# then we want to go up to find the project root
current = pkg_dir.resolve()
for _ in range(20): # Safety limit
current = current.parent
if (current / "CLI.py").exists():
parent_str = str(current)
if parent_str not in sys.path:
sys.path.insert(0, parent_str)
return current
except Exception:
pass
return None return None
@@ -165,13 +183,35 @@ def _run_cli(clean_args: List[str]) -> int:
# 2) If no in-memory module provided the class, try importing the repo-root CLI # 2) If no in-memory module provided the class, try importing the repo-root CLI
if MedeiaCLI is None: if MedeiaCLI is None:
try: try:
_ensure_repo_root_on_sys_path() repo_root = _ensure_repo_root_on_sys_path()
from CLI import MedeiaCLI as _M # type: ignore from CLI import MedeiaCLI as _M # type: ignore
MedeiaCLI = _M MedeiaCLI = _M
except Exception: except Exception as exc:
raise ImportError( # Provide diagnostic information
"Could not import 'MedeiaCLI'. This often means the project is not available on sys.path (run 'pip install -e scripts' or re-run the bootstrap script)." import traceback
error_msg = (
"Could not import 'MedeiaCLI'. This often means the project is not available on sys.path.\n"
"Diagnostic info:\n"
f" - sys.executable: {sys.executable}\n"
f" - sys.path (first 5): {sys.path[:5]}\n"
f" - current working directory: {Path.cwd()}\n"
f" - this file: {Path(__file__).resolve()}\n"
) )
try:
repo = _ensure_repo_root_on_sys_path()
if repo:
error_msg += f" - detected repo root: {repo}\n"
cli_path = repo / "CLI.py"
error_msg += f" - CLI.py exists at {cli_path}: {cli_path.exists()}\n"
except:
pass
error_msg += (
"\nRemedy: Run 'pip install -e scripts' from the project root or re-run the bootstrap script.\n"
"Set MM_DEBUG=1 to enable detailed diagnostics."
)
if os.environ.get("MM_DEBUG"):
error_msg += f"\n\nTraceback:\n{traceback.format_exc()}"
raise ImportError(error_msg) from exc
try: try:
app = MedeiaCLI() app = MedeiaCLI()