updated installer script

This commit is contained in:
2026-04-01 13:58:06 -07:00
parent 57b595c1a4
commit 849dcbda85
3 changed files with 324 additions and 359 deletions

View File

@@ -14,22 +14,17 @@ ffmpeg-python is installed as a dependency, but requires ffmpeg itself to be on
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`).
(`scripts/bootstrap.ps1` and `scripts/bootstrap.sh`) are thin wrappers
that delegate to this script.
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.
The install flow is owned here so `bootstrap.py` remains the single global
entry point, while platform wrappers only provide shell-specific convenience.
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 # install deps and playwright browsers
python ./scripts/bootstrap.py --skip-deps
python ./scripts/bootstrap.py --playwright-only
@@ -249,10 +244,16 @@ def run_platform_bootstrap(repo_root: Path) -> int:
return int(rc.returncode or 0)
def playwright_package_installed() -> bool:
def playwright_package_installed(python_path: Optional[Path] = None) -> bool:
interpreter = str(python_path or sys.executable)
try:
return True
result = subprocess.run(
[interpreter, "-c", "import playwright"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
return result.returncode == 0
except Exception:
return False
@@ -446,7 +447,7 @@ def main() -> int:
parser.add_argument(
"--no-delegate",
action="store_true",
help="Do not delegate to platform bootstrap scripts; run the Python bootstrap directly.",
help="Legacy no-op retained for wrapper compatibility; Python bootstrap is always the canonical entry point.",
)
parser.add_argument(
"-q",
@@ -812,7 +813,7 @@ def main() -> int:
return False
def _interactive_menu() -> str | int:
"""Show a simple interactive menu to choose install/uninstall or delegate."""
"""Show a simple interactive menu to choose install/uninstall tasks."""
try:
installed = _is_installed()
while True:
@@ -878,8 +879,7 @@ def main() -> int:
if choice in ("q", "quit", "exit"):
return 0
except EOFError:
# Non-interactive, fall back to delegating to platform helper
return "delegate"
return "install"
def _prompt_hydrus_install_location() -> tuple[Path, str] | None:
"""Ask the user for the Hydrus installation root and folder name."""
@@ -907,9 +907,10 @@ def main() -> int:
def _clone_repo(url: str, dest: Path, depth: int = 1) -> bool:
"""Helper to clone a repository."""
try:
cmd = ["git", "clone", url, str(dest)]
cmd = ["git", "clone"]
if depth:
cmd.extend(["--depth", str(depth)])
cmd.extend([url, str(dest)])
subprocess.check_call(cmd)
return True
except Exception as e:
@@ -1318,25 +1319,14 @@ def main() -> int:
continue
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
elif sel == 0:
return 0
elif sel == "menu":
continue
elif not args.no_delegate and script_path is not None:
# Default non-interactive behavior: delegate to platform script
rc = run_platform_bootstrap(repo_root)
if rc != 0:
return rc
if not args.quiet:
print("Platform bootstrap completed successfully.")
return 0
if not is_in_repo:
if not _ensure_repo_available():
return 1
if sys.version_info < (3, 8):
print("Warning: Python 3.8+ is recommended.", file=sys.stderr)
@@ -1360,12 +1350,27 @@ def main() -> int:
print("Error: No project repository found. Please ensure you are running this script inside the project folder or follow the interactive install prompts.", file=sys.stderr)
return 1
# Determine total steps for progress bar
total_steps = 7 # Base: venv, pip, deps, project, cli, finalize, env
if args.upgrade_pip: total_steps += 1
if not args.no_playwright: total_steps += 1 # Playwright is combined pkg+browsers
if not getattr(args, "no_mpv", False): total_steps += 1
if not getattr(args, "no_deno", False): total_steps += 1
should_install_deps = not args.skip_deps and not args.playwright_only
should_install_playwright = not args.no_playwright
should_install_project = not args.playwright_only
total_steps = 2
if args.playwright_only:
total_steps += 1
else:
if args.upgrade_pip:
total_steps += 1
if should_install_deps:
total_steps += 1
if should_install_playwright:
total_steps += 1
if should_install_project:
total_steps += 1
total_steps += 3
if not getattr(args, "no_mpv", False):
total_steps += 1
if not getattr(args, "no_deno", False):
total_steps += 1
pb = ProgressBar(total_steps, quiet=args.quiet or args.debug)
@@ -1437,17 +1442,12 @@ def main() -> int:
pb.update("Checking for pip...")
_ensure_pip_available(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:
# Playwright browser install (short-circuit)
if not playwright_package_installed():
_run_cmd([sys.executable, "-m", "pip", "install", "--no-cache-dir", "playwright"])
pb.update("Setting up Playwright and browsers...")
if not playwright_package_installed(venv_python):
_run_cmd([str(venv_python), "-m", "pip", "install", "--no-cache-dir", "playwright"])
try:
cmd = _build_playwright_install_cmd(args.browsers)
@@ -1476,15 +1476,16 @@ def main() -> int:
)
# 4. Core Dependencies
pb.update("Installing core dependencies...")
req_file = repo_root / "scripts" / "requirements.txt"
if req_file.exists():
_run_cmd([str(venv_python), "-m", "pip", "install", "--no-cache-dir", "-r", str(req_file)])
if should_install_deps:
pb.update("Installing core dependencies...")
if req_file.exists():
_run_cmd([str(venv_python), "-m", "pip", "install", "--no-cache-dir", "-r", str(req_file)])
# 5. Playwright Setup
if not args.no_playwright:
if should_install_playwright:
pb.update("Setting up Playwright and browsers...")
if not playwright_package_installed():
if not playwright_package_installed(venv_python):
_run_cmd([str(venv_python), "-m", "pip", "install", "--no-cache-dir", "playwright"])
try:
@@ -1495,16 +1496,22 @@ def main() -> int:
pass
# 6. Internal Components
pb.update("Installing internal components...")
if platform.system() != "Windows":
old_mm = venv_dir / "bin" / "mm"
if old_mm.exists():
try:
old_mm.unlink()
except Exception:
pass
_run_cmd([str(venv_python), "-m", "pip", "install", "--no-cache-dir", "-e", str(repo_root / "scripts")])
if should_install_project:
pb.update("Installing internal components...")
if platform.system() != "Windows":
old_mm = venv_dir / "bin" / "mm"
if old_mm.exists():
try:
old_mm.unlink()
except Exception:
pass
project_cmd = [str(venv_python), "-m", "pip", "install", "--no-cache-dir"]
if args.install_editable:
project_cmd.extend(["-e", str(repo_root / "scripts")])
else:
project_cmd.append(str(repo_root / "scripts"))
_run_cmd(project_cmd)
# 7. CLI Verification
pb.update("Verifying CLI configuration...")