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