d
This commit is contained in:
@@ -8,6 +8,11 @@ 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).
|
||||
|
||||
Note: This Python script is the canonical installer for the project — prefer
|
||||
running `python ./scripts/bootstrap.py` locally. The platform scripts
|
||||
(`scripts/bootstrap.ps1` and `scripts/bootstrap.sh`) are now thin wrappers
|
||||
that delegate to this script (they call it with `--no-delegate -q`).
|
||||
|
||||
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
|
||||
@@ -230,6 +235,17 @@ def main() -> int:
|
||||
action="store_true",
|
||||
help="Only run 'playwright install' (skips dependency installation)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-delegate",
|
||||
action="store_true",
|
||||
help="Do not delegate to platform bootstrap scripts; run the Python bootstrap directly.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_true",
|
||||
help="Quiet mode: minimize informational output (useful when called from platform wrappers)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--browsers",
|
||||
type=str,
|
||||
@@ -264,21 +280,170 @@ def main() -> int:
|
||||
action="store_true",
|
||||
help="Upgrade pip/setuptools/wheel before installing requirements",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--uninstall",
|
||||
action="store_true",
|
||||
help="Uninstall local .venv and user shims (non-interactive)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-y",
|
||||
"--yes",
|
||||
action="store_true",
|
||||
help="Assume yes for confirmation prompts during uninstall",
|
||||
)
|
||||
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.")
|
||||
# Helpers for interactive menu and uninstall detection
|
||||
def _venv_python_path(p: Path) -> Path | None:
|
||||
"""Return the path to a python executable inside a venv directory if present."""
|
||||
if (p / "Scripts" / "python.exe").exists():
|
||||
return p / "Scripts" / "python.exe"
|
||||
if (p / "bin" / "python").exists():
|
||||
return p / "bin" / "python"
|
||||
return None
|
||||
|
||||
def _is_installed() -> bool:
|
||||
"""Return True if the project appears installed into the local .venv."""
|
||||
vdir = repo_root / ".venv"
|
||||
py = _venv_python_path(vdir)
|
||||
if py is None:
|
||||
return False
|
||||
try:
|
||||
rc = subprocess.run([str(py), "-m", "pip", "show", "medeia-macina"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
|
||||
return rc.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _do_uninstall() -> int:
|
||||
"""Attempt to remove the local venv and any shims written to the user's bin."""
|
||||
vdir = repo_root / ".venv"
|
||||
if not vdir.exists():
|
||||
if not args.quiet:
|
||||
print("No local .venv found; nothing to uninstall.")
|
||||
return 0
|
||||
if not args.yes:
|
||||
try:
|
||||
prompt = input(f"Remove local virtualenv at {vdir} and installed user shims? [y/N]: ")
|
||||
except EOFError:
|
||||
print("Non-interactive environment; pass --uninstall --yes to uninstall without prompts.", file=sys.stderr)
|
||||
return 2
|
||||
if prompt.strip().lower() not in ("y", "yes"):
|
||||
print("Uninstall aborted.")
|
||||
return 1
|
||||
|
||||
# Remove repo-local launchers
|
||||
for name in ("mm", "mm.ps1", "mm.bat"):
|
||||
p = repo_root / name
|
||||
if p.exists():
|
||||
try:
|
||||
p.unlink()
|
||||
if not args.quiet:
|
||||
print(f"Removed local launcher: {p}")
|
||||
except Exception as exc:
|
||||
print(f"Warning: failed to remove {p}: {exc}", file=sys.stderr)
|
||||
|
||||
# Remove user shims that the installer may have written
|
||||
try:
|
||||
system = platform.system().lower()
|
||||
if system == "windows":
|
||||
user_bin = Path(os.environ.get("USERPROFILE", str(Path.home()))) / "bin"
|
||||
if user_bin.exists():
|
||||
for name in ("mm.cmd", "mm.ps1"):
|
||||
p = user_bin / name
|
||||
if p.exists():
|
||||
p.unlink()
|
||||
if not args.quiet:
|
||||
print(f"Removed user shim: {p}")
|
||||
else:
|
||||
user_bin = Path(os.environ.get("XDG_BIN_HOME", str(Path.home() / ".local/bin")))
|
||||
if user_bin.exists():
|
||||
p = user_bin / "mm"
|
||||
if p.exists():
|
||||
p.unlink()
|
||||
if not args.quiet:
|
||||
print(f"Removed user shim: {p}")
|
||||
except Exception as exc:
|
||||
print(f"Warning: failed to remove user shims: {exc}", file=sys.stderr)
|
||||
|
||||
# Remove .venv directory
|
||||
try:
|
||||
shutil.rmtree(vdir)
|
||||
if not args.quiet:
|
||||
print(f"Removed local virtualenv: {vdir}")
|
||||
except Exception as exc:
|
||||
print(f"Failed to remove venv: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def _interactive_menu() -> str | int:
|
||||
"""Show a simple interactive menu to choose install/uninstall or delegate."""
|
||||
try:
|
||||
installed = _is_installed()
|
||||
while True:
|
||||
print("\nMedeia-Macina bootstrap - interactive menu")
|
||||
if installed:
|
||||
print("1) Install / Reinstall")
|
||||
print("2) Uninstall")
|
||||
print("3) Status")
|
||||
print("q) Quit")
|
||||
choice = input("Choose an option: ").strip().lower()
|
||||
if not choice or choice in ("1", "install", "reinstall"):
|
||||
return "install"
|
||||
if choice in ("2", "uninstall"):
|
||||
return "uninstall"
|
||||
if choice in ("3", "status"):
|
||||
print("Installation detected." if installed else "Not installed.")
|
||||
continue
|
||||
if choice in ("q", "quit", "exit"):
|
||||
return 0
|
||||
else:
|
||||
print("1) Install")
|
||||
print("q) Quit")
|
||||
choice = input("Choose an option: ").strip().lower()
|
||||
if not choice or choice in ("1", "install"):
|
||||
return "install"
|
||||
if choice in ("q", "quit", "exit"):
|
||||
return 0
|
||||
except EOFError:
|
||||
# Non-interactive, fall back to delegating to platform helper
|
||||
return "delegate"
|
||||
|
||||
# If the user passed --uninstall explicitly, perform non-interactive uninstall and exit
|
||||
if args.uninstall:
|
||||
return _do_uninstall()
|
||||
|
||||
# If invoked without any arguments and not asked to skip delegation, prefer
|
||||
# the interactive menu when running in a TTY; otherwise delegate to the
|
||||
# platform-specific bootstrap helper (non-interactive).
|
||||
if len(sys.argv) == 1 and not args.no_delegate:
|
||||
if sys.stdin.isatty() and not args.quiet:
|
||||
sel = _interactive_menu()
|
||||
if sel == "install":
|
||||
# user chose to install/reinstall; set defaults and continue
|
||||
args.skip_deps = False
|
||||
args.install_editable = True
|
||||
args.no_playwright = False
|
||||
elif sel == "uninstall":
|
||||
return _do_uninstall()
|
||||
elif sel == "delegate":
|
||||
rc = run_platform_bootstrap(repo_root)
|
||||
if rc != 0:
|
||||
return rc
|
||||
if not args.quiet:
|
||||
print("Platform bootstrap completed successfully.")
|
||||
return 0
|
||||
else:
|
||||
return int(sel or 0)
|
||||
else:
|
||||
rc = run_platform_bootstrap(repo_root)
|
||||
if rc != 0:
|
||||
return rc
|
||||
if not args.quiet:
|
||||
print("Platform bootstrap completed successfully.")
|
||||
return 0
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
print("Warning: Python 3.8+ is recommended.", file=sys.stderr)
|
||||
|
||||
@@ -295,15 +460,18 @@ def main() -> int:
|
||||
|
||||
try:
|
||||
if not venv_dir.exists():
|
||||
print(f"Creating local virtualenv at: {venv_dir}")
|
||||
if not args.quiet:
|
||||
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}")
|
||||
if not args.quiet:
|
||||
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")
|
||||
if not args.quiet:
|
||||
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():
|
||||
@@ -315,7 +483,8 @@ def main() -> int:
|
||||
|
||||
# Ensure a local venv is present and use it for subsequent installs.
|
||||
venv_python = _ensure_local_venv()
|
||||
print(f"Using venv python: {venv_python}")
|
||||
if not args.quiet:
|
||||
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.
|
||||
@@ -326,12 +495,14 @@ def main() -> int:
|
||||
try:
|
||||
if args.playwright_only:
|
||||
if not playwright_package_installed():
|
||||
print("'playwright' package not found; installing it via pip...")
|
||||
if not args.quiet:
|
||||
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)..."
|
||||
)
|
||||
if not args.quiet:
|
||||
print(
|
||||
"Installing Playwright browsers (this may download several hundred MB)..."
|
||||
)
|
||||
try:
|
||||
cmd = _build_playwright_install_cmd(args.browsers)
|
||||
except ValueError as exc:
|
||||
@@ -339,11 +510,13 @@ def main() -> int:
|
||||
return 2
|
||||
|
||||
run(cmd)
|
||||
print("Playwright browsers installed successfully.")
|
||||
if not args.quiet:
|
||||
print("Playwright browsers installed successfully.")
|
||||
return 0
|
||||
|
||||
if args.upgrade_pip:
|
||||
print("Upgrading pip, setuptools, and wheel in local venv...")
|
||||
if not args.quiet:
|
||||
print("Upgrading pip, setuptools, and wheel in local venv...")
|
||||
run(
|
||||
[
|
||||
str(venv_python),
|
||||
@@ -365,19 +538,22 @@ def main() -> int:
|
||||
file=sys.stderr,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Installing Python dependencies into local venv from {req_file}..."
|
||||
)
|
||||
if not args.quiet:
|
||||
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...")
|
||||
if not args.quiet:
|
||||
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)..."
|
||||
)
|
||||
if not args.quiet:
|
||||
print(
|
||||
"Installing Playwright browsers (this may download several hundred MB)..."
|
||||
)
|
||||
try:
|
||||
cmd = _build_playwright_install_cmd(args.browsers)
|
||||
except ValueError as exc:
|
||||
@@ -389,11 +565,13 @@ def main() -> int:
|
||||
run(cmd)
|
||||
|
||||
# Install the project into the local venv (editable mode is the default, opinionated)
|
||||
print("Installing project into local venv (editable mode)")
|
||||
if not args.quiet:
|
||||
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...")
|
||||
if not args.quiet:
|
||||
print("Verifying top-level 'CLI' import in venv...")
|
||||
try:
|
||||
rc = subprocess.run(
|
||||
[
|
||||
@@ -477,7 +655,8 @@ def main() -> int:
|
||||
install_deno_requested = True
|
||||
|
||||
if install_deno_requested:
|
||||
print("Installing Deno runtime (local/system)...")
|
||||
if not args.quiet:
|
||||
print("Installing Deno runtime (local/system)...")
|
||||
rc = _install_deno(args.deno_version)
|
||||
if rc != 0:
|
||||
print("Deno installation failed.", file=sys.stderr)
|
||||
@@ -615,7 +794,8 @@ python -m medeia_macina.cli_entry @args
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"Installed global launchers to: {user_bin}")
|
||||
if not args.quiet:
|
||||
print(f"Installed global launchers to: {user_bin}")
|
||||
|
||||
else:
|
||||
# POSIX
|
||||
@@ -716,7 +896,8 @@ python -m medeia_macina.cli_entry @args
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"Installed global launcher to: {mm_sh}")
|
||||
if not args.quiet:
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user