Auto-bootstrap BSIPA when re-enabling plugins

Run bootstrap automatically on enable paths when health is bad, skip bsipa
in disable-all, and add tests for the shared bootstrap gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pleb
2026-07-01 22:55:47 -07:00
parent f160e4b349
commit c86e51b85a
8 changed files with 425 additions and 37 deletions
+106 -8
View File
@@ -1,15 +1,34 @@
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
from typing import Any
from .bootstrap import ensure_healthy_bootstrap
from .config import repo_root
from .installer import apply_plan
from .models import load_lockfile, load_registry
from .models import Lockfile, Registry, load_lockfile, load_registry
from .planner import create_plan
from .state import load_installed_state
def _resolve_paths(
*,
instance: str,
registry: str,
lockfile: str | None,
repo: Path | None,
) -> tuple[Path, Path, Path, Lockfile, Registry]:
root = repo or repo_root()
registry_path = (root / registry).resolve() if not Path(registry).is_absolute() else Path(registry)
lock_path = Path(lockfile) if lockfile else root / "locks" / f"{instance}.lock.toml"
if not lock_path.is_absolute():
lock_path = (root / lock_path).resolve()
loaded_lockfile = load_lockfile(lock_path)
loaded_registry = load_registry(registry_path)
return root, registry_path, lock_path, loaded_lockfile, loaded_registry
def enable_disabled_plugin(
*,
instance: str,
@@ -19,25 +38,38 @@ def enable_disabled_plugin(
registry: str = "registry/plugins.toml",
lockfile: str | None = None,
repo: Path | None = None,
progress: Callable[[str], None] | None = None,
) -> dict[str, Any]:
installed_state = load_installed_state(state_root, instance)
if plugin_id not in installed_state.get("disabledPlugins", {}):
raise KeyError(f"plugin is not recorded as disabled: {plugin_id}")
root = repo or repo_root()
registry_path = (root / registry).resolve() if not Path(registry).is_absolute() else Path(registry)
lock_path = Path(lockfile) if lockfile else root / "locks" / f"{instance}.lock.toml"
if not lock_path.is_absolute():
lock_path = (root / lock_path).resolve()
loaded_lockfile = load_lockfile(lock_path)
root, registry_path, _lock_path, loaded_lockfile, loaded_registry = _resolve_paths(
instance=instance,
registry=registry,
lockfile=lockfile,
repo=repo,
)
if not any(plugin.id == plugin_id for plugin in loaded_lockfile.plugins):
raise KeyError(f"plugin is disabled but not locked for this instance: {plugin_id}")
ensure_healthy_bootstrap(
instance=instance,
instance_path=instance_path,
beat_saber_version=loaded_lockfile.beat_saber_version,
registry=loaded_registry,
lockfile=loaded_lockfile,
state_root=state_root,
repo_root=root,
selected_ids={plugin_id},
progress=progress,
)
plan, path = create_plan(
instance=instance,
instance_path=instance_path,
beat_saber_version=loaded_lockfile.beat_saber_version,
registry=load_registry(registry_path),
registry=loaded_registry,
lockfile=loaded_lockfile,
state_root=state_root,
repo_root=root,
@@ -45,3 +77,69 @@ def enable_disabled_plugin(
)
result = apply_plan(plan, state_root)
return {"planPath": str(path), **result}
def enable_disabled_plugins(
*,
instance: str,
instance_path: Path,
state_root: Path,
plugin_ids: list[str],
registry: str = "registry/plugins.toml",
lockfile: str | None = None,
repo: Path | None = None,
progress: Callable[[str], None] | None = None,
) -> dict[str, Any]:
if not plugin_ids:
return {"enabled": [], "errors": []}
root, _registry_path, _lock_path, loaded_lockfile, loaded_registry = _resolve_paths(
instance=instance,
registry=registry,
lockfile=lockfile,
repo=repo,
)
installed_state = load_installed_state(state_root, instance)
disabled_plugins = installed_state.get("disabledPlugins", {})
selected_ids = set(plugin_ids)
ensure_healthy_bootstrap(
instance=instance,
instance_path=instance_path,
beat_saber_version=loaded_lockfile.beat_saber_version,
registry=loaded_registry,
lockfile=loaded_lockfile,
state_root=state_root,
repo_root=root,
selected_ids=selected_ids,
progress=progress,
)
enabled: list[dict[str, Any]] = []
errors: list[dict[str, str]] = []
for plugin_id in plugin_ids:
if plugin_id not in disabled_plugins:
errors.append({"plugin": plugin_id, "error": f"plugin is not recorded as disabled: {plugin_id}"})
continue
if not any(plugin.id == plugin_id for plugin in loaded_lockfile.plugins):
errors.append(
{"plugin": plugin_id, "error": f"plugin is disabled but not locked for this instance: {plugin_id}"}
)
continue
try:
plan, path = create_plan(
instance=instance,
instance_path=instance_path,
beat_saber_version=loaded_lockfile.beat_saber_version,
registry=loaded_registry,
lockfile=loaded_lockfile,
state_root=state_root,
repo_root=root,
selected={plugin_id},
)
result = apply_plan(plan, state_root)
enabled.append({"plugin": plugin_id, "planPath": str(path), "applied": len(result["applied"])})
except Exception as exc:
errors.append({"plugin": plugin_id, "error": str(exc)})
return {"enabled": enabled, "errors": errors}