feat(plugins): discover packages and persist state
This commit is contained in:
74
app-instance/backend/beaver/plugins/discovery.py
Normal file
74
app-instance/backend/beaver/plugins/discovery.py
Normal file
@ -0,0 +1,74 @@
|
||||
"""Plugin package discovery."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
from .manifest import load_plugin_manifest
|
||||
from .models import PluginDiscoveryError, PluginDiscoveryResult, PluginManifest
|
||||
|
||||
|
||||
def discover_plugins(
|
||||
workspace: str | Path,
|
||||
*,
|
||||
search_paths: Iterable[str | Path] = (),
|
||||
) -> PluginDiscoveryResult:
|
||||
workspace_root = Path(workspace).resolve()
|
||||
candidates: list[Path] = []
|
||||
candidates.extend(_candidate_manifest_paths(workspace_root / "plugins"))
|
||||
for root in search_paths:
|
||||
candidates.extend(_candidate_manifest_paths(Path(root).expanduser()))
|
||||
|
||||
manifests_by_id: dict[str, list[PluginManifest]] = {}
|
||||
errors: list[PluginDiscoveryError] = []
|
||||
for manifest_path in candidates:
|
||||
try:
|
||||
manifest = load_plugin_manifest(manifest_path, workspace=workspace_root)
|
||||
except Exception as exc: # noqa: BLE001 - discovery reports per-path errors.
|
||||
errors.append(
|
||||
PluginDiscoveryError(
|
||||
path=manifest_path,
|
||||
display_path=_display_path(manifest_path, workspace_root),
|
||||
message=str(exc),
|
||||
plugin_id=None,
|
||||
)
|
||||
)
|
||||
continue
|
||||
manifests_by_id.setdefault(manifest.plugin_id, []).append(manifest)
|
||||
|
||||
manifests: dict[str, PluginManifest] = {}
|
||||
for plugin_id, matches in manifests_by_id.items():
|
||||
if len(matches) == 1:
|
||||
manifests[plugin_id] = matches[0]
|
||||
continue
|
||||
for manifest in matches:
|
||||
errors.append(
|
||||
PluginDiscoveryError(
|
||||
path=manifest.manifest_path,
|
||||
display_path=manifest.display_path,
|
||||
message=f"Duplicate plugin id: {plugin_id}",
|
||||
plugin_id=plugin_id,
|
||||
)
|
||||
)
|
||||
return PluginDiscoveryResult(manifests=manifests, errors=errors)
|
||||
|
||||
|
||||
def _candidate_manifest_paths(root: Path) -> list[Path]:
|
||||
if not root.exists() or not root.is_dir():
|
||||
return []
|
||||
results: list[Path] = []
|
||||
for child in sorted(root.iterdir()):
|
||||
if not child.is_dir():
|
||||
continue
|
||||
manifest = child / "beaver.plugin.json"
|
||||
if manifest.is_file():
|
||||
results.append(manifest)
|
||||
return results
|
||||
|
||||
|
||||
def _display_path(path: Path, workspace: Path) -> str:
|
||||
resolved = path.resolve()
|
||||
if resolved.is_relative_to(workspace):
|
||||
return resolved.relative_to(workspace).as_posix()
|
||||
return f"<external>/{resolved.parent.name}/{resolved.name}"
|
||||
Reference in New Issue
Block a user