Add plugin update and install reporting
This commit is contained in:
@@ -8,12 +8,15 @@ from typing import Any
|
||||
|
||||
from .config import instances_root, repo_root, state_root
|
||||
from .checker import check_lock
|
||||
from .github import fetch_releases
|
||||
from .installer import apply_plan, uninstall_plugin
|
||||
from .instances import get_instance, list_instances
|
||||
from .models import load_lockfile, load_registry
|
||||
from .models import Lockfile, Registry
|
||||
from .planner import create_plan
|
||||
from .scanner import scan_instance
|
||||
from .state import load_installed_state
|
||||
from .updates import check_updates
|
||||
from .userdata import backup_userdata
|
||||
|
||||
|
||||
@@ -21,6 +24,100 @@ def _json(data: Any) -> None:
|
||||
print(json.dumps(data, indent=2, sort_keys=True))
|
||||
|
||||
|
||||
def installed_plugins_report(
|
||||
*,
|
||||
installed_state: dict[str, Any],
|
||||
registry: Registry,
|
||||
lockfile: Lockfile,
|
||||
) -> dict[str, Any]:
|
||||
locked_by_id = {plugin.id: plugin for plugin in lockfile.plugins}
|
||||
plugins: list[dict[str, Any]] = []
|
||||
for plugin_id, plugin_state in sorted(installed_state.get("plugins", {}).items()):
|
||||
registry_plugin = registry.get(plugin_id)
|
||||
locked = locked_by_id.get(plugin_id)
|
||||
files = plugin_state.get("files", [])
|
||||
plugins.append(
|
||||
{
|
||||
"id": plugin_id,
|
||||
"name": registry_plugin.name if registry_plugin else plugin_id,
|
||||
"version": locked.tag if locked and locked.tag else "(not locked)",
|
||||
"asset": locked.asset if locked and locked.asset else "(unknown)",
|
||||
"repo": (locked.repo if locked and locked.repo else None)
|
||||
or (registry_plugin.repo if registry_plugin else None)
|
||||
or "(unknown)",
|
||||
"installedAt": plugin_state.get("installedAt", "(unknown)"),
|
||||
"fileCount": len(files),
|
||||
"files": files,
|
||||
}
|
||||
)
|
||||
return {
|
||||
"instance": installed_state.get("instance", lockfile.instance),
|
||||
"beatSaberVersion": installed_state.get("beatSaberVersion", lockfile.beat_saber_version),
|
||||
"plugins": plugins,
|
||||
}
|
||||
|
||||
|
||||
def print_installed_plugins(report: dict[str, Any]) -> None:
|
||||
plugins = report["plugins"]
|
||||
print(f"{report['instance']} managed plugins ({len(plugins)})")
|
||||
if not plugins:
|
||||
print("No plugins have been installed by plugin-helper yet.")
|
||||
return
|
||||
|
||||
headers = ("Plugin", "Version", "Asset", "Files", "Installed")
|
||||
rows = [
|
||||
(
|
||||
f"{plugin['name']} ({plugin['id']})",
|
||||
plugin["version"],
|
||||
plugin["asset"],
|
||||
str(plugin["fileCount"]),
|
||||
plugin["installedAt"],
|
||||
)
|
||||
for plugin in plugins
|
||||
]
|
||||
widths = [
|
||||
max(len(headers[index]), *(len(row[index]) for row in rows))
|
||||
for index in range(len(headers))
|
||||
]
|
||||
header = " ".join(label.ljust(widths[index]) for index, label in enumerate(headers))
|
||||
print(header)
|
||||
print(" ".join("-" * width for width in widths))
|
||||
for row in rows:
|
||||
print(" ".join(value.ljust(widths[index]) for index, value in enumerate(row)))
|
||||
|
||||
|
||||
def print_updates(report: dict[str, Any]) -> None:
|
||||
plugins = report["plugins"]
|
||||
summary = report["summary"]
|
||||
print(
|
||||
f"{report['instance']} updates: "
|
||||
f"{summary['updates']} available, {summary['current']} current, "
|
||||
f"{summary['warnings']} warnings, {summary['errors']} errors"
|
||||
)
|
||||
if not plugins:
|
||||
return
|
||||
|
||||
headers = ("Plugin", "Current", "Latest", "Asset", "Status")
|
||||
rows = [
|
||||
(
|
||||
f"{plugin['name']} ({plugin['id']})",
|
||||
plugin.get("currentTag") or "(none)",
|
||||
plugin.get("latestTag") or "(unknown)",
|
||||
plugin.get("latestAsset") or plugin.get("currentAsset") or "(unknown)",
|
||||
plugin["status"],
|
||||
)
|
||||
for plugin in plugins
|
||||
]
|
||||
widths = [
|
||||
max(len(headers[index]), *(len(row[index]) for row in rows))
|
||||
for index in range(len(headers))
|
||||
]
|
||||
print(" ".join(label.ljust(widths[index]) for index, label in enumerate(headers)))
|
||||
print(" ".join("-" * width for width in widths))
|
||||
for row in rows:
|
||||
print(" ".join(value.ljust(widths[index]) for index, value in enumerate(row)))
|
||||
|
||||
|
||||
def _add_common(parser: argparse.ArgumentParser, *, suppress_default: bool = False) -> None:
|
||||
default = argparse.SUPPRESS if suppress_default else None
|
||||
parser.add_argument("--instances-root", default=default, help="BSManager instances root")
|
||||
@@ -54,6 +151,16 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
)
|
||||
state.add_argument("--instance", required=True)
|
||||
|
||||
installed = subcommands.add_parser(
|
||||
"installed",
|
||||
help="List plugins installed by plugin-helper with locked release versions",
|
||||
parents=[_common_parent()],
|
||||
)
|
||||
installed.add_argument("--instance", required=True)
|
||||
installed.add_argument("--registry", default="registry/plugins.toml")
|
||||
installed.add_argument("--lockfile")
|
||||
installed.add_argument("--json", action="store_true", help="Print full JSON output")
|
||||
|
||||
check = subcommands.add_parser(
|
||||
"check",
|
||||
help="Validate local registry, lockfile, and release asset readiness",
|
||||
@@ -64,6 +171,18 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
check.add_argument("--lockfile")
|
||||
check.add_argument("--json", action="store_true", help="Print full JSON check output")
|
||||
|
||||
updates = subcommands.add_parser(
|
||||
"updates",
|
||||
help="Check GitHub for newer matching releases for locked plugins",
|
||||
parents=[_common_parent()],
|
||||
)
|
||||
updates.add_argument("--instance", required=True)
|
||||
updates.add_argument("--registry", default="registry/plugins.toml")
|
||||
updates.add_argument("--lockfile")
|
||||
updates.add_argument("--plugin", action="append", help="Check only this locked plugin id; repeatable")
|
||||
updates.add_argument("--include-prerelease", action="store_true", help="Include prerelease GitHub releases")
|
||||
updates.add_argument("--json", action="store_true", help="Print full JSON update output")
|
||||
|
||||
plan = subcommands.add_parser(
|
||||
"plan",
|
||||
help="Create a dry-run install plan from registry and lockfile",
|
||||
@@ -147,6 +266,23 @@ def run(argv: list[str] | None = None) -> int:
|
||||
_json(load_installed_state(st_root, args.instance))
|
||||
return 0
|
||||
|
||||
if args.command == "installed":
|
||||
root = repo_root()
|
||||
registry_path = (root / args.registry).resolve() if not Path(args.registry).is_absolute() else Path(args.registry)
|
||||
lock_path = Path(args.lockfile) if args.lockfile else root / "locks" / f"{args.instance}.lock.toml"
|
||||
if not lock_path.is_absolute():
|
||||
lock_path = (root / lock_path).resolve()
|
||||
result = installed_plugins_report(
|
||||
installed_state=load_installed_state(st_root, args.instance),
|
||||
registry=load_registry(registry_path),
|
||||
lockfile=load_lockfile(lock_path),
|
||||
)
|
||||
if args.json:
|
||||
_json(result)
|
||||
else:
|
||||
print_installed_plugins(result)
|
||||
return 0
|
||||
|
||||
if args.command == "check":
|
||||
root = repo_root()
|
||||
registry_path = (root / args.registry).resolve() if not Path(args.registry).is_absolute() else Path(args.registry)
|
||||
@@ -176,6 +312,25 @@ def run(argv: list[str] | None = None) -> int:
|
||||
print(f" {message['level']}: {message['message']}")
|
||||
return 2 if result["summary"]["errors"] else 0
|
||||
|
||||
if args.command == "updates":
|
||||
root = repo_root()
|
||||
registry_path = (root / args.registry).resolve() if not Path(args.registry).is_absolute() else Path(args.registry)
|
||||
lock_path = Path(args.lockfile) if args.lockfile else root / "locks" / f"{args.instance}.lock.toml"
|
||||
if not lock_path.is_absolute():
|
||||
lock_path = (root / lock_path).resolve()
|
||||
result = check_updates(
|
||||
registry=load_registry(registry_path),
|
||||
lockfile=load_lockfile(lock_path),
|
||||
fetch_releases=fetch_releases,
|
||||
selected=set(args.plugin) if args.plugin else None,
|
||||
include_prerelease=args.include_prerelease,
|
||||
)
|
||||
if args.json:
|
||||
_json(result)
|
||||
else:
|
||||
print_updates(result)
|
||||
return 2 if result["summary"]["errors"] else 0
|
||||
|
||||
if args.command == "plan":
|
||||
instance = get_instance(inst_root, args.instance)
|
||||
root = repo_root()
|
||||
|
||||
Reference in New Issue
Block a user