实现了渠道插件的全生命周期管理能力,包括: 1. 插件清单的加载、解析与存储 2. 依赖校验与拓扑排序安装/卸载 3. 插件发现与安全沙箱执行环境 4. 插件注册中心与目录管理能力
90 lines
3.1 KiB
Python
90 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class ChannelPluginManifest:
|
|
id: str
|
|
name: str
|
|
version: str
|
|
description: str = ""
|
|
author: str = ""
|
|
dependencies: list[str] = field(default_factory=list)
|
|
entry_point: str | None = None
|
|
source_path: str | None = None
|
|
target_path: str | None = None
|
|
config_schema: dict[str, Any] = field(default_factory=dict)
|
|
capabilities: list[str] = field(default_factory=list)
|
|
tags: list[str] = field(default_factory=list)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> "ChannelPluginManifest":
|
|
return cls(
|
|
id=data["id"],
|
|
name=data.get("name", data["id"]),
|
|
version=data.get("version", "0.0.1"),
|
|
description=data.get("description", ""),
|
|
author=data.get("author", ""),
|
|
dependencies=data.get("dependencies", []),
|
|
entry_point=data.get("entry_point"),
|
|
source_path=data.get("source_path"),
|
|
target_path=data.get("target_path"),
|
|
config_schema=data.get("config_schema", {}),
|
|
capabilities=data.get("capabilities", []),
|
|
tags=data.get("tags", []),
|
|
)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"id": self.id,
|
|
"name": self.name,
|
|
"version": self.version,
|
|
"description": self.description,
|
|
"author": self.author,
|
|
"dependencies": self.dependencies,
|
|
"entry_point": self.entry_point,
|
|
"source_path": self.source_path,
|
|
"target_path": self.target_path,
|
|
"config_schema": self.config_schema,
|
|
"capabilities": self.capabilities,
|
|
"tags": self.tags,
|
|
}
|
|
|
|
|
|
def load_manifests_from_directory(manifest_dir: str | Path) -> list[ChannelPluginManifest]:
|
|
"""从目录加载所有插件清单文件 (.json)。"""
|
|
manifest_dir = Path(manifest_dir)
|
|
if not manifest_dir.exists():
|
|
logger.warning("Manifest directory does not exist: %s", manifest_dir)
|
|
return []
|
|
|
|
manifests = []
|
|
for file_path in manifest_dir.glob("*.json"):
|
|
try:
|
|
data = json.loads(file_path.read_text(encoding="utf-8"))
|
|
if isinstance(data, list):
|
|
for item in data:
|
|
manifests.append(ChannelPluginManifest.from_dict(item))
|
|
else:
|
|
manifests.append(ChannelPluginManifest.from_dict(data))
|
|
except Exception:
|
|
logger.exception("Failed to load manifest from %s", file_path)
|
|
return manifests
|
|
|
|
|
|
def save_manifest(manifest: ChannelPluginManifest, manifest_dir: str | Path) -> Path:
|
|
"""保存插件清单到指定目录。"""
|
|
manifest_dir = Path(manifest_dir)
|
|
manifest_dir.mkdir(parents=True, exist_ok=True)
|
|
file_path = manifest_dir / f"{manifest.id}.json"
|
|
file_path.write_text(json.dumps(manifest.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8")
|
|
logger.info("Saved manifest to %s", file_path)
|
|
return file_path
|