🤖 自动格式化代码 [skip ci]

This commit is contained in:
github-actions[bot]
2025-06-19 15:13:28 +00:00
parent 1fab6dc710
commit 86922f1995
9 changed files with 241 additions and 308 deletions

View File

@@ -19,39 +19,36 @@ project_root = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(project_root)) sys.path.insert(0, str(project_root))
logger = get_logger("manifest_tool") logger = get_logger("manifest_tool")
def create_minimal_manifest(plugin_dir: str, plugin_name: str, description: str = "", author: str = "") -> bool: def create_minimal_manifest(plugin_dir: str, plugin_name: str, description: str = "", author: str = "") -> bool:
"""创建最小化的manifest文件 """创建最小化的manifest文件
Args: Args:
plugin_dir: 插件目录 plugin_dir: 插件目录
plugin_name: 插件名称 plugin_name: 插件名称
description: 插件描述 description: 插件描述
author: 插件作者 author: 插件作者
Returns: Returns:
bool: 是否创建成功 bool: 是否创建成功
""" """
manifest_path = os.path.join(plugin_dir, "_manifest.json") manifest_path = os.path.join(plugin_dir, "_manifest.json")
if os.path.exists(manifest_path): if os.path.exists(manifest_path):
print(f"❌ Manifest文件已存在: {manifest_path}") print(f"❌ Manifest文件已存在: {manifest_path}")
return False return False
# 创建最小化manifest # 创建最小化manifest
minimal_manifest = { minimal_manifest = {
"manifest_version": 3, "manifest_version": 3,
"name": plugin_name, "name": plugin_name,
"version": "1.0.0", "version": "1.0.0",
"description": description or f"{plugin_name}插件", "description": description or f"{plugin_name}插件",
"author": { "author": {"name": author or "Unknown"},
"name": author or "Unknown"
}
} }
try: try:
with open(manifest_path, "w", encoding="utf-8") as f: with open(manifest_path, "w", encoding="utf-8") as f:
json.dump(minimal_manifest, f, ensure_ascii=False, indent=2) json.dump(minimal_manifest, f, ensure_ascii=False, indent=2)
@@ -64,35 +61,29 @@ def create_minimal_manifest(plugin_dir: str, plugin_name: str, description: str
def create_complete_manifest(plugin_dir: str, plugin_name: str) -> bool: def create_complete_manifest(plugin_dir: str, plugin_name: str) -> bool:
"""创建完整的manifest模板文件 """创建完整的manifest模板文件
Args: Args:
plugin_dir: 插件目录 plugin_dir: 插件目录
plugin_name: 插件名称 plugin_name: 插件名称
Returns: Returns:
bool: 是否创建成功 bool: 是否创建成功
""" """
manifest_path = os.path.join(plugin_dir, "_manifest.json") manifest_path = os.path.join(plugin_dir, "_manifest.json")
if os.path.exists(manifest_path): if os.path.exists(manifest_path):
print(f"❌ Manifest文件已存在: {manifest_path}") print(f"❌ Manifest文件已存在: {manifest_path}")
return False return False
# 创建完整模板 # 创建完整模板
complete_manifest = { complete_manifest = {
"manifest_version": 3, "manifest_version": 3,
"name": plugin_name, "name": plugin_name,
"version": "1.0.0", "version": "1.0.0",
"description": f"{plugin_name}插件描述", "description": f"{plugin_name}插件描述",
"author": { "author": {"name": "插件作者", "url": "https://github.com/your-username"},
"name": "插件作者",
"url": "https://github.com/your-username"
},
"license": "MIT", "license": "MIT",
"host_application": { "host_application": {"min_version": "1.0.0", "max_version": "4.0.0"},
"min_version": "1.0.0",
"max_version": "4.0.0"
},
"homepage_url": "https://github.com/your-repo", "homepage_url": "https://github.com/your-repo",
"repository_url": "https://github.com/your-repo", "repository_url": "https://github.com/your-repo",
"keywords": ["keyword1", "keyword2"], "keywords": ["keyword1", "keyword2"],
@@ -102,16 +93,10 @@ def create_complete_manifest(plugin_dir: str, plugin_name: str) -> bool:
"plugin_info": { "plugin_info": {
"is_built_in": False, "is_built_in": False,
"plugin_type": "general", "plugin_type": "general",
"components": [ "components": [{"type": "action", "name": "sample_action", "description": "示例动作组件"}],
{ },
"type": "action",
"name": "sample_action",
"description": "示例动作组件"
}
]
}
} }
try: try:
with open(manifest_path, "w", encoding="utf-8") as f: with open(manifest_path, "w", encoding="utf-8") as f:
json.dump(complete_manifest, f, ensure_ascii=False, indent=2) json.dump(complete_manifest, f, ensure_ascii=False, indent=2)
@@ -125,37 +110,37 @@ def create_complete_manifest(plugin_dir: str, plugin_name: str) -> bool:
def validate_manifest_file(plugin_dir: str) -> bool: def validate_manifest_file(plugin_dir: str) -> bool:
"""验证manifest文件 """验证manifest文件
Args: Args:
plugin_dir: 插件目录 plugin_dir: 插件目录
Returns: Returns:
bool: 是否验证通过 bool: 是否验证通过
""" """
manifest_path = os.path.join(plugin_dir, "_manifest.json") manifest_path = os.path.join(plugin_dir, "_manifest.json")
if not os.path.exists(manifest_path): if not os.path.exists(manifest_path):
print(f"❌ 未找到manifest文件: {manifest_path}") print(f"❌ 未找到manifest文件: {manifest_path}")
return False return False
try: try:
with open(manifest_path, "r", encoding="utf-8") as f: with open(manifest_path, "r", encoding="utf-8") as f:
manifest_data = json.load(f) manifest_data = json.load(f)
validator = ManifestValidator() validator = ManifestValidator()
is_valid = validator.validate_manifest(manifest_data) is_valid = validator.validate_manifest(manifest_data)
# 显示验证结果 # 显示验证结果
print("📋 Manifest验证结果:") print("📋 Manifest验证结果:")
print(validator.get_validation_report()) print(validator.get_validation_report())
if is_valid: if is_valid:
print("✅ Manifest文件验证通过") print("✅ Manifest文件验证通过")
else: else:
print("❌ Manifest文件验证失败") print("❌ Manifest文件验证失败")
return is_valid return is_valid
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"❌ Manifest文件格式错误: {e}") print(f"❌ Manifest文件格式错误: {e}")
return False return False
@@ -166,24 +151,24 @@ def validate_manifest_file(plugin_dir: str) -> bool:
def scan_plugins_without_manifest(root_dir: str) -> None: def scan_plugins_without_manifest(root_dir: str) -> None:
"""扫描缺少manifest文件的插件 """扫描缺少manifest文件的插件
Args: Args:
root_dir: 扫描的根目录 root_dir: 扫描的根目录
""" """
print(f"🔍 扫描目录: {root_dir}") print(f"🔍 扫描目录: {root_dir}")
plugins_without_manifest = [] plugins_without_manifest = []
for root, dirs, files in os.walk(root_dir): for root, dirs, files in os.walk(root_dir):
# 跳过隐藏目录和__pycache__ # 跳过隐藏目录和__pycache__
dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__'] dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__"]
# 检查是否包含plugin.py文件标识为插件目录 # 检查是否包含plugin.py文件标识为插件目录
if "plugin.py" in files: if "plugin.py" in files:
manifest_path = os.path.join(root, "_manifest.json") manifest_path = os.path.join(root, "_manifest.json")
if not os.path.exists(manifest_path): if not os.path.exists(manifest_path):
plugins_without_manifest.append(root) plugins_without_manifest.append(root)
if plugins_without_manifest: if plugins_without_manifest:
print(f"❌ 发现 {len(plugins_without_manifest)} 个插件缺少manifest文件:") print(f"❌ 发现 {len(plugins_without_manifest)} 个插件缺少manifest文件:")
for plugin_dir in plugins_without_manifest: for plugin_dir in plugins_without_manifest:
@@ -198,56 +183,51 @@ def main():
"""主函数""" """主函数"""
parser = argparse.ArgumentParser(description="插件Manifest管理工具") parser = argparse.ArgumentParser(description="插件Manifest管理工具")
subparsers = parser.add_subparsers(dest="command", help="可用命令") subparsers = parser.add_subparsers(dest="command", help="可用命令")
# 创建最小化manifest命令 # 创建最小化manifest命令
create_minimal_parser = subparsers.add_parser("create-minimal", help="创建最小化manifest文件") create_minimal_parser = subparsers.add_parser("create-minimal", help="创建最小化manifest文件")
create_minimal_parser.add_argument("plugin_dir", help="插件目录路径") create_minimal_parser.add_argument("plugin_dir", help="插件目录路径")
create_minimal_parser.add_argument("--name", help="插件名称") create_minimal_parser.add_argument("--name", help="插件名称")
create_minimal_parser.add_argument("--description", help="插件描述") create_minimal_parser.add_argument("--description", help="插件描述")
create_minimal_parser.add_argument("--author", help="插件作者") create_minimal_parser.add_argument("--author", help="插件作者")
# 创建完整manifest命令 # 创建完整manifest命令
create_complete_parser = subparsers.add_parser("create-complete", help="创建完整manifest模板") create_complete_parser = subparsers.add_parser("create-complete", help="创建完整manifest模板")
create_complete_parser.add_argument("plugin_dir", help="插件目录路径") create_complete_parser.add_argument("plugin_dir", help="插件目录路径")
create_complete_parser.add_argument("--name", help="插件名称") create_complete_parser.add_argument("--name", help="插件名称")
# 验证manifest命令 # 验证manifest命令
validate_parser = subparsers.add_parser("validate", help="验证manifest文件") validate_parser = subparsers.add_parser("validate", help="验证manifest文件")
validate_parser.add_argument("plugin_dir", help="插件目录路径") validate_parser.add_argument("plugin_dir", help="插件目录路径")
# 扫描插件命令 # 扫描插件命令
scan_parser = subparsers.add_parser("scan", help="扫描缺少manifest的插件") scan_parser = subparsers.add_parser("scan", help="扫描缺少manifest的插件")
scan_parser.add_argument("root_dir", help="扫描的根目录路径") scan_parser.add_argument("root_dir", help="扫描的根目录路径")
args = parser.parse_args() args = parser.parse_args()
if not args.command: if not args.command:
parser.print_help() parser.print_help()
return return
try: try:
if args.command == "create-minimal": if args.command == "create-minimal":
plugin_name = args.name or os.path.basename(os.path.abspath(args.plugin_dir)) plugin_name = args.name or os.path.basename(os.path.abspath(args.plugin_dir))
success = create_minimal_manifest( success = create_minimal_manifest(args.plugin_dir, plugin_name, args.description or "", args.author or "")
args.plugin_dir,
plugin_name,
args.description or "",
args.author or ""
)
sys.exit(0 if success else 1) sys.exit(0 if success else 1)
elif args.command == "create-complete": elif args.command == "create-complete":
plugin_name = args.name or os.path.basename(os.path.abspath(args.plugin_dir)) plugin_name = args.name or os.path.basename(os.path.abspath(args.plugin_dir))
success = create_complete_manifest(args.plugin_dir, plugin_name) success = create_complete_manifest(args.plugin_dir, plugin_name)
sys.exit(0 if success else 1) sys.exit(0 if success else 1)
elif args.command == "validate": elif args.command == "validate":
success = validate_manifest_file(args.plugin_dir) success = validate_manifest_file(args.plugin_dir)
sys.exit(0 if success else 1) sys.exit(0 if success else 1)
elif args.command == "scan": elif args.command == "scan":
scan_plugins_without_manifest(args.root_dir) scan_plugins_without_manifest(args.root_dir)
except Exception as e: except Exception as e:
print(f"❌ 执行命令时发生错误: {e}") print(f"❌ 执行命令时发生错误: {e}")
sys.exit(1) sys.exit(1)

View File

@@ -18,7 +18,7 @@ from src.plugin_system.utils.manifest_utils import VersionComparator
def test_version_normalization(): def test_version_normalization():
"""测试版本号标准化功能""" """测试版本号标准化功能"""
print("🧪 测试版本号标准化...") print("🧪 测试版本号标准化...")
test_cases = [ test_cases = [
("0.8.0-snapshot.1", "0.8.0"), ("0.8.0-snapshot.1", "0.8.0"),
("0.8.0-snapshot.2", "0.8.0"), ("0.8.0-snapshot.2", "0.8.0"),
@@ -30,7 +30,7 @@ def test_version_normalization():
("", "0.0.0"), ("", "0.0.0"),
("invalid", "0.0.0"), ("invalid", "0.0.0"),
] ]
for input_version, expected in test_cases: for input_version, expected in test_cases:
result = VersionComparator.normalize_version(input_version) result = VersionComparator.normalize_version(input_version)
status = "" if result == expected else "" status = "" if result == expected else ""
@@ -40,16 +40,16 @@ def test_version_normalization():
def test_version_comparison(): def test_version_comparison():
"""测试版本号比较功能""" """测试版本号比较功能"""
print("\n🧪 测试版本号比较...") print("\n🧪 测试版本号比较...")
test_cases = [ test_cases = [
("0.8.0", "0.9.0", -1), # 0.8.0 < 0.9.0 ("0.8.0", "0.9.0", -1), # 0.8.0 < 0.9.0
("0.9.0", "0.8.0", 1), # 0.9.0 > 0.8.0 ("0.9.0", "0.8.0", 1), # 0.9.0 > 0.8.0
("1.0.0", "1.0.0", 0), # 1.0.0 == 1.0.0 ("1.0.0", "1.0.0", 0), # 1.0.0 == 1.0.0
("0.8.0-snapshot.1", "0.8.0", 0), # 标准化后相等 ("0.8.0-snapshot.1", "0.8.0", 0), # 标准化后相等
("1.2.3", "1.2.4", -1), # 1.2.3 < 1.2.4 ("1.2.3", "1.2.4", -1), # 1.2.3 < 1.2.4
("2.0.0", "1.9.9", 1), # 2.0.0 > 1.9.9 ("2.0.0", "1.9.9", 1), # 2.0.0 > 1.9.9
] ]
for v1, v2, expected in test_cases: for v1, v2, expected in test_cases:
result = VersionComparator.compare_versions(v1, v2) result = VersionComparator.compare_versions(v1, v2)
status = "" if result == expected else "" status = "" if result == expected else ""
@@ -60,17 +60,17 @@ def test_version_comparison():
def test_version_range_check(): def test_version_range_check():
"""测试版本范围检查功能""" """测试版本范围检查功能"""
print("\n🧪 测试版本范围检查...") print("\n🧪 测试版本范围检查...")
test_cases = [ test_cases = [
("0.8.0", "0.7.0", "0.9.0", True), # 在范围内 ("0.8.0", "0.7.0", "0.9.0", True), # 在范围内
("0.6.0", "0.7.0", "0.9.0", False), # 低于最小版本 ("0.6.0", "0.7.0", "0.9.0", False), # 低于最小版本
("1.0.0", "0.7.0", "0.9.0", False), # 高于最大版本 ("1.0.0", "0.7.0", "0.9.0", False), # 高于最大版本
("0.8.0", "0.8.0", "0.8.0", True), # 等于边界 ("0.8.0", "0.8.0", "0.8.0", True), # 等于边界
("0.8.0", "", "0.9.0", True), # 只有最大版本限制 ("0.8.0", "", "0.9.0", True), # 只有最大版本限制
("0.8.0", "0.7.0", "", True), # 只有最小版本限制 ("0.8.0", "0.7.0", "", True), # 只有最小版本限制
("0.8.0", "", "", True), # 无版本限制 ("0.8.0", "", "", True), # 无版本限制
] ]
for version, min_ver, max_ver, expected in test_cases: for version, min_ver, max_ver, expected in test_cases:
is_compatible, error_msg = VersionComparator.is_version_in_range(version, min_ver, max_ver) is_compatible, error_msg = VersionComparator.is_version_in_range(version, min_ver, max_ver)
status = "" if is_compatible == expected else "" status = "" if is_compatible == expected else ""
@@ -83,18 +83,18 @@ def test_version_range_check():
def test_current_version(): def test_current_version():
"""测试获取当前版本功能""" """测试获取当前版本功能"""
print("\n🧪 测试获取当前主机版本...") print("\n🧪 测试获取当前主机版本...")
try: try:
current_version = VersionComparator.get_current_host_version() current_version = VersionComparator.get_current_host_version()
print(f" ✅ 当前主机版本: {current_version}") print(f" ✅ 当前主机版本: {current_version}")
# 验证版本号格式 # 验证版本号格式
parts = current_version.split('.') parts = current_version.split(".")
if len(parts) == 3 and all(part.isdigit() for part in parts): if len(parts) == 3 and all(part.isdigit() for part in parts):
print(f" ✅ 版本号格式正确") print(" ✅ 版本号格式正确")
else: else:
print(f" ❌ 版本号格式错误") print(" ❌ 版本号格式错误")
except Exception as e: except Exception as e:
print(f" ❌ 获取当前版本失败: {e}") print(f" ❌ 获取当前版本失败: {e}")
@@ -102,52 +102,32 @@ def test_current_version():
def test_manifest_compatibility(): def test_manifest_compatibility():
"""测试manifest兼容性检查""" """测试manifest兼容性检查"""
print("\n🧪 测试manifest兼容性检查...") print("\n🧪 测试manifest兼容性检查...")
# 模拟manifest数据 # 模拟manifest数据
test_manifests = [ test_manifests = [
{ {"name": "兼容插件", "host_application": {"min_version": "0.1.0", "max_version": "2.0.0"}},
"name": "兼容插件", {"name": "版本过高插件", "host_application": {"min_version": "10.0.0", "max_version": "20.0.0"}},
"host_application": { {"name": "版本过低插件", "host_application": {"min_version": "0.1.0", "max_version": "0.2.0"}},
"min_version": "0.1.0",
"max_version": "2.0.0"
}
},
{
"name": "版本过高插件",
"host_application": {
"min_version": "10.0.0",
"max_version": "20.0.0"
}
},
{
"name": "版本过低插件",
"host_application": {
"min_version": "0.1.0",
"max_version": "0.2.0"
}
},
{ {
"name": "无版本要求插件", "name": "无版本要求插件",
# 没有host_application字段 # 没有host_application字段
} },
] ]
# 这里需要导入PluginManager来测试但可能会有依赖问题 # 这里需要导入PluginManager来测试但可能会有依赖问题
# 所以我们直接使用VersionComparator进行测试 # 所以我们直接使用VersionComparator进行测试
current_version = VersionComparator.get_current_host_version() current_version = VersionComparator.get_current_host_version()
for manifest in test_manifests: for manifest in test_manifests:
plugin_name = manifest["name"] plugin_name = manifest["name"]
if "host_application" in manifest: if "host_application" in manifest:
host_app = manifest["host_application"] host_app = manifest["host_application"]
min_version = host_app.get("min_version", "") min_version = host_app.get("min_version", "")
max_version = host_app.get("max_version", "") max_version = host_app.get("max_version", "")
is_compatible, error_msg = VersionComparator.is_version_in_range( is_compatible, error_msg = VersionComparator.is_version_in_range(current_version, min_version, max_version)
current_version, min_version, max_version
)
status = "" if is_compatible else "" status = "" if is_compatible else ""
print(f" {status} {plugin_name}: {is_compatible}") print(f" {status} {plugin_name}: {is_compatible}")
if error_msg: if error_msg:
@@ -159,24 +139,23 @@ def test_manifest_compatibility():
def test_additional_snapshot_formats(): def test_additional_snapshot_formats():
"""测试额外的snapshot版本格式""" """测试额外的snapshot版本格式"""
print("\n🧪 测试额外的snapshot版本格式...") print("\n🧪 测试额外的snapshot版本格式...")
test_cases = [ test_cases = [
# 用户提到的版本格式 # 用户提到的版本格式
("0.8.0-snapshot.1", "0.8.0"), ("0.8.0-snapshot.1", "0.8.0"),
("0.8.0-snapshot.2", "0.8.0"), ("0.8.0-snapshot.2", "0.8.0"),
("0.8.0", "0.8.0"), ("0.8.0", "0.8.0"),
("0.9.0-snapshot.1", "0.9.0"), ("0.9.0-snapshot.1", "0.9.0"),
# 边界情况 # 边界情况
("1.0.0-snapshot.999", "1.0.0"), ("1.0.0-snapshot.999", "1.0.0"),
("2.15.3-snapshot.42", "2.15.3"), ("2.15.3-snapshot.42", "2.15.3"),
("10.5.0-snapshot.1", "10.5.0"), ("10.5.0-snapshot.1", "10.5.0"),
# 不正确的snapshot格式应该被忽略或正确处理 # 不正确的snapshot格式应该被忽略或正确处理
("0.8.0-snapshot", "0.0.0"), # 无数字后缀应该标准化为0.0.0 ("0.8.0-snapshot", "0.0.0"), # 无数字后缀应该标准化为0.0.0
("0.8.0-snapshot.abc", "0.0.0"), # 非数字后缀应该标准化为0.0.0 ("0.8.0-snapshot.abc", "0.0.0"), # 非数字后缀应该标准化为0.0.0
("0.8.0-beta.1", "0.0.0"), # 其他预发布版本应该标准化为0.0.0 ("0.8.0-beta.1", "0.0.0"), # 其他预发布版本应该标准化为0.0.0
] ]
for input_version, expected in test_cases: for input_version, expected in test_cases:
result = VersionComparator.normalize_version(input_version) result = VersionComparator.normalize_version(input_version)
status = "" if result == expected else "" status = "" if result == expected else ""
@@ -186,23 +165,21 @@ def test_additional_snapshot_formats():
def test_snapshot_version_comparison(): def test_snapshot_version_comparison():
"""测试snapshot版本的比较功能""" """测试snapshot版本的比较功能"""
print("\n🧪 测试snapshot版本比较...") print("\n🧪 测试snapshot版本比较...")
test_cases = [ test_cases = [
# snapshot版本与正式版本比较 # snapshot版本与正式版本比较
("0.8.0-snapshot.1", "0.8.0", 0), # 应该相等 ("0.8.0-snapshot.1", "0.8.0", 0), # 应该相等
("0.8.0-snapshot.2", "0.8.0", 0), # 应该相等 ("0.8.0-snapshot.2", "0.8.0", 0), # 应该相等
("0.9.0-snapshot.1", "0.8.0", 1), # 应该大于 ("0.9.0-snapshot.1", "0.8.0", 1), # 应该大于
("0.7.0-snapshot.1", "0.8.0", -1), # 应该小于 ("0.7.0-snapshot.1", "0.8.0", -1), # 应该小于
# snapshot版本之间比较 # snapshot版本之间比较
("0.8.0-snapshot.1", "0.8.0-snapshot.2", 0), # 都标准化为0.8.0,相等 ("0.8.0-snapshot.1", "0.8.0-snapshot.2", 0), # 都标准化为0.8.0,相等
("0.9.0-snapshot.1", "0.8.0-snapshot.1", 1), # 0.9.0 > 0.8.0 ("0.9.0-snapshot.1", "0.8.0-snapshot.1", 1), # 0.9.0 > 0.8.0
# 边界情况 # 边界情况
("1.0.0-snapshot.1", "0.9.9", 1), # 主版本更高 ("1.0.0-snapshot.1", "0.9.9", 1), # 主版本更高
("0.9.0-snapshot.1", "0.8.99", 1), # 次版本更高 ("0.9.0-snapshot.1", "0.8.99", 1), # 次版本更高
] ]
for version1, version2, expected in test_cases: for version1, version2, expected in test_cases:
result = VersionComparator.compare_versions(version1, version2) result = VersionComparator.compare_versions(version1, version2)
status = "" if result == expected else "" status = "" if result == expected else ""
@@ -214,7 +191,7 @@ def main():
"""主函数""" """主函数"""
print("🔧 MaiBot插件版本兼容性检查测试") print("🔧 MaiBot插件版本兼容性检查测试")
print("=" * 50) print("=" * 50)
try: try:
test_version_normalization() test_version_normalization()
test_version_comparison() test_version_comparison()
@@ -223,12 +200,13 @@ def main():
test_manifest_compatibility() test_manifest_compatibility()
test_additional_snapshot_formats() test_additional_snapshot_formats()
test_snapshot_version_comparison() test_snapshot_version_comparison()
print("\n🎉 所有测试完成!") print("\n🎉 所有测试完成!")
except Exception as e: except Exception as e:
print(f"\n❌ 测试过程中发生错误: {e}") print(f"\n❌ 测试过程中发生错误: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
sys.exit(1) sys.exit(1)

View File

@@ -28,7 +28,7 @@ from src.plugin_system.utils import (
ManifestValidator, ManifestValidator,
ManifestGenerator, ManifestGenerator,
validate_plugin_manifest, validate_plugin_manifest,
generate_plugin_manifest generate_plugin_manifest,
) )
@@ -37,7 +37,7 @@ __version__ = "1.0.0"
__all__ = [ __all__ = [
# 基础类 # 基础类
"BasePlugin", "BasePlugin",
"BaseAction", "BaseAction",
"BaseCommand", "BaseCommand",
# 类型定义 # 类型定义
"ComponentType", "ComponentType",

View File

@@ -63,7 +63,7 @@ class BasePlugin(ABC):
self._validate_plugin_info() self._validate_plugin_info()
# 加载插件配置 # 加载插件配置
self._load_plugin_config() # 创建插件信息对象 self._load_plugin_config() # 创建插件信息对象
self.plugin_info = PluginInfo( self.plugin_info = PluginInfo(
name=self.plugin_name, name=self.plugin_name,
description=self.plugin_description, description=self.plugin_description,
@@ -88,7 +88,7 @@ class BasePlugin(ABC):
logger.debug(f"{self.log_prefix} 插件基类初始化完成") logger.debug(f"{self.log_prefix} 插件基类初始化完成")
def _validate_plugin_info(self): def _validate_plugin_info(self):
"""验证插件基本信息""" """验证插件基本信息"""
if not self.plugin_name: if not self.plugin_name:
raise ValueError(f"插件类 {self.__class__.__name__} 必须定义 plugin_name") raise ValueError(f"插件类 {self.__class__.__name__} 必须定义 plugin_name")
if not self.plugin_description: if not self.plugin_description:
@@ -100,7 +100,7 @@ class BasePlugin(ABC):
raise ValueError(f"{self.log_prefix} 没有插件目录路径无法加载manifest") raise ValueError(f"{self.log_prefix} 没有插件目录路径无法加载manifest")
manifest_path = os.path.join(self.plugin_dir, self.manifest_file_name) manifest_path = os.path.join(self.plugin_dir, self.manifest_file_name)
if not os.path.exists(manifest_path): if not os.path.exists(manifest_path):
error_msg = f"{self.log_prefix} 缺少必需的manifest文件: {manifest_path}" error_msg = f"{self.log_prefix} 缺少必需的manifest文件: {manifest_path}"
logger.error(error_msg) logger.error(error_msg)
@@ -109,23 +109,23 @@ class BasePlugin(ABC):
try: try:
with open(manifest_path, "r", encoding="utf-8") as f: with open(manifest_path, "r", encoding="utf-8") as f:
self.manifest_data = json.load(f) self.manifest_data = json.load(f)
logger.debug(f"{self.log_prefix} 成功加载manifest文件: {manifest_path}") logger.debug(f"{self.log_prefix} 成功加载manifest文件: {manifest_path}")
# 验证manifest格式 # 验证manifest格式
self._validate_manifest() self._validate_manifest()
# 从manifest覆盖插件基本信息如果插件类中未定义 # 从manifest覆盖插件基本信息如果插件类中未定义
self._apply_manifest_overrides() self._apply_manifest_overrides()
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
error_msg = f"{self.log_prefix} manifest文件格式错误: {e}" error_msg = f"{self.log_prefix} manifest文件格式错误: {e}"
logger.error(error_msg) logger.error(error_msg)
raise ValueError(error_msg) #noqa raise ValueError(error_msg) # noqa
except IOError as e: except IOError as e:
error_msg = f"{self.log_prefix} 读取manifest文件失败: {e}" error_msg = f"{self.log_prefix} 读取manifest文件失败: {e}"
logger.error(error_msg) logger.error(error_msg)
raise IOError(error_msg) #noqa raise IOError(error_msg) # noqa
def _apply_manifest_overrides(self): def _apply_manifest_overrides(self):
"""从manifest文件覆盖插件信息""" """从manifest文件覆盖插件信息"""
@@ -145,7 +145,7 @@ class BasePlugin(ABC):
if not self.plugin_author: if not self.plugin_author:
author_info = self.manifest_data.get("author", {}) author_info = self.manifest_data.get("author", {})
if isinstance(author_info, dict): if isinstance(author_info, dict):
self.plugin_author = author_info.get("name", "") self.plugin_author = author_info.get("name", "")
else: else:
self.plugin_author = str(author_info) self.plugin_author = str(author_info)
@@ -153,18 +153,18 @@ class BasePlugin(ABC):
"""验证manifest文件格式使用强化的验证器""" """验证manifest文件格式使用强化的验证器"""
if not self.manifest_data: if not self.manifest_data:
return return
# 导入验证器 # 导入验证器
from src.plugin_system.utils.manifest_utils import ManifestValidator from src.plugin_system.utils.manifest_utils import ManifestValidator
validator = ManifestValidator() validator = ManifestValidator()
is_valid = validator.validate_manifest(self.manifest_data) is_valid = validator.validate_manifest(self.manifest_data)
# 记录验证结果 # 记录验证结果
if validator.validation_errors or validator.validation_warnings: if validator.validation_errors or validator.validation_warnings:
report = validator.get_validation_report() report = validator.get_validation_report()
logger.info(f"{self.log_prefix} Manifest验证结果:\n{report}") logger.info(f"{self.log_prefix} Manifest验证结果:\n{report}")
# 如果有验证错误,抛出异常 # 如果有验证错误,抛出异常
if not is_valid: if not is_valid:
error_msg = f"{self.log_prefix} Manifest文件验证失败" error_msg = f"{self.log_prefix} Manifest文件验证失败"
@@ -183,19 +183,13 @@ class BasePlugin(ABC):
"name": self.plugin_name, "name": self.plugin_name,
"version": self.plugin_version, "version": self.plugin_version,
"description": self.plugin_description or "插件描述", "description": self.plugin_description or "插件描述",
"author": { "author": {"name": self.plugin_author or "Unknown", "url": ""},
"name": self.plugin_author or "Unknown",
"url": ""
},
"license": "MIT", "license": "MIT",
"host_application": { "host_application": {"min_version": "1.0.0", "max_version": "4.0.0"},
"min_version": "1.0.0",
"max_version": "4.0.0"
},
"keywords": [], "keywords": [],
"categories": [], "categories": [],
"default_locale": "zh-CN", "default_locale": "zh-CN",
"locales_path": "_locales" "locales_path": "_locales",
} }
try: try:

View File

@@ -131,7 +131,7 @@ class PluginInfo:
python_dependencies: List[PythonDependency] = None # Python包依赖 python_dependencies: List[PythonDependency] = None # Python包依赖
config_file: str = "" # 配置文件路径 config_file: str = "" # 配置文件路径
metadata: Dict[str, Any] = None # 额外元数据 metadata: Dict[str, Any] = None # 额外元数据
# 新增manifest相关信息 # 新增manifest相关信息
manifest_data: Dict[str, Any] = None # manifest文件数据 manifest_data: Dict[str, Any] = None # manifest文件数据
license: str = "" # 插件许可证 license: str = "" # 插件许可证
homepage_url: str = "" # 插件主页 homepage_url: str = "" # 插件主页

View File

@@ -76,7 +76,8 @@ class PluginManager:
logger.debug(f"插件模块加载完成 - 成功: {total_loaded_modules}, 失败: {total_failed_modules}") logger.debug(f"插件模块加载完成 - 成功: {total_loaded_modules}, 失败: {total_failed_modules}")
# 第二阶段:实例化所有已注册的插件类 # 第二阶段:实例化所有已注册的插件类
from src.plugin_system.base.base_plugin import get_registered_plugin_classes from src.plugin_system.base.base_plugin import get_registered_plugin_classes
plugin_classes = get_registered_plugin_classes() plugin_classes = get_registered_plugin_classes()
total_registered = 0 total_registered = 0
total_failed_registration = 0 total_failed_registration = 0
@@ -90,7 +91,7 @@ class PluginManager:
if not plugin_dir: if not plugin_dir:
plugin_dir = self._find_plugin_directory(plugin_class) plugin_dir = self._find_plugin_directory(plugin_class)
if plugin_dir: if plugin_dir:
self.plugin_paths[plugin_name] = plugin_dir # 实例化插件可能因为缺少manifest而失败 self.plugin_paths[plugin_name] = plugin_dir # 实例化插件可能因为缺少manifest而失败
plugin_instance = plugin_class(plugin_dir=plugin_dir) plugin_instance = plugin_class(plugin_dir=plugin_dir)
# 检查插件是否启用 # 检查插件是否启用
@@ -121,7 +122,7 @@ class PluginManager:
component_types[comp_type] = component_types.get(comp_type, 0) + 1 component_types[comp_type] = component_types.get(comp_type, 0) + 1
components_str = ", ".join([f"{count}{ctype}" for ctype, count in component_types.items()]) components_str = ", ".join([f"{count}{ctype}" for ctype, count in component_types.items()])
# 显示manifest信息 # 显示manifest信息
manifest_info = "" manifest_info = ""
if plugin_info.license: if plugin_info.license:
@@ -130,7 +131,7 @@ class PluginManager:
manifest_info += f" 关键词: {', '.join(plugin_info.keywords[:3])}" # 只显示前3个关键词 manifest_info += f" 关键词: {', '.join(plugin_info.keywords[:3])}" # 只显示前3个关键词
if len(plugin_info.keywords) > 3: if len(plugin_info.keywords) > 3:
manifest_info += "..." manifest_info += "..."
logger.info( logger.info(
f"✅ 插件加载成功: {plugin_name} v{plugin_info.version} ({components_str}){manifest_info} - {plugin_info.description}" f"✅ 插件加载成功: {plugin_name} v{plugin_info.version} ({components_str}){manifest_info} - {plugin_info.description}"
) )
@@ -140,21 +141,21 @@ class PluginManager:
total_failed_registration += 1 total_failed_registration += 1
self.failed_plugins[plugin_name] = "插件注册失败" self.failed_plugins[plugin_name] = "插件注册失败"
logger.error(f"❌ 插件注册失败: {plugin_name}") logger.error(f"❌ 插件注册失败: {plugin_name}")
except FileNotFoundError as e: except FileNotFoundError as e:
# manifest文件缺失 # manifest文件缺失
total_failed_registration += 1 total_failed_registration += 1
error_msg = f"缺少manifest文件: {str(e)}" error_msg = f"缺少manifest文件: {str(e)}"
self.failed_plugins[plugin_name] = error_msg self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
except ValueError as e: except ValueError as e:
# manifest文件格式错误或验证失败 # manifest文件格式错误或验证失败
total_failed_registration += 1 total_failed_registration += 1
error_msg = f"manifest验证失败: {str(e)}" error_msg = f"manifest验证失败: {str(e)}"
self.failed_plugins[plugin_name] = error_msg self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
except Exception as e: except Exception as e:
# 其他错误 # 其他错误
total_failed_registration += 1 total_failed_registration += 1
@@ -517,46 +518,45 @@ class PluginManager:
def check_plugin_version_compatibility(self, plugin_name: str, manifest_data: Dict[str, Any]) -> Tuple[bool, str]: def check_plugin_version_compatibility(self, plugin_name: str, manifest_data: Dict[str, Any]) -> Tuple[bool, str]:
"""检查插件版本兼容性 """检查插件版本兼容性
Args: Args:
plugin_name: 插件名称 plugin_name: 插件名称
manifest_data: manifest数据 manifest_data: manifest数据
Returns: Returns:
Tuple[bool, str]: (是否兼容, 错误信息) Tuple[bool, str]: (是否兼容, 错误信息)
""" """
if "host_application" not in manifest_data: if "host_application" not in manifest_data:
# 没有版本要求,默认兼容 # 没有版本要求,默认兼容
return True, "" return True, ""
host_app = manifest_data["host_application"] host_app = manifest_data["host_application"]
if not isinstance(host_app, dict): if not isinstance(host_app, dict):
return True, "" return True, ""
min_version = host_app.get("min_version", "") min_version = host_app.get("min_version", "")
max_version = host_app.get("max_version", "") max_version = host_app.get("max_version", "")
if not min_version and not max_version: if not min_version and not max_version:
return True, "" return True, ""
try: try:
from src.plugin_system.utils.manifest_utils import VersionComparator from src.plugin_system.utils.manifest_utils import VersionComparator
current_version = VersionComparator.get_current_host_version() current_version = VersionComparator.get_current_host_version()
is_compatible, error_msg = VersionComparator.is_version_in_range( is_compatible, error_msg = VersionComparator.is_version_in_range(current_version, min_version, max_version)
current_version, min_version, max_version
)
if not is_compatible: if not is_compatible:
return False, f"版本不兼容: {error_msg}" return False, f"版本不兼容: {error_msg}"
else: else:
logger.debug(f"插件 {plugin_name} 版本兼容性检查通过") logger.debug(f"插件 {plugin_name} 版本兼容性检查通过")
return True, "" return True, ""
except Exception as e: except Exception as e:
logger.warning(f"插件 {plugin_name} 版本兼容性检查失败: {e}") logger.warning(f"插件 {plugin_name} 版本兼容性检查失败: {e}")
return True, "" # 检查失败时默认允许加载 return True, "" # 检查失败时默认允许加载
# 全局插件管理器实例 # 全局插件管理器实例
plugin_manager = PluginManager() plugin_manager = PluginManager()

View File

@@ -6,14 +6,9 @@
from src.plugin_system.utils.manifest_utils import ( from src.plugin_system.utils.manifest_utils import (
ManifestValidator, ManifestValidator,
ManifestGenerator, ManifestGenerator,
validate_plugin_manifest, validate_plugin_manifest,
generate_plugin_manifest generate_plugin_manifest,
) )
__all__ = [ __all__ = ["ManifestValidator", "ManifestGenerator", "validate_plugin_manifest", "generate_plugin_manifest"]
"ManifestValidator",
"ManifestGenerator",
"validate_plugin_manifest",
"generate_plugin_manifest"
]

View File

@@ -7,7 +7,7 @@
import json import json
import os import os
import re import re
from typing import Dict, Any, Optional, Tuple, List from typing import Dict, Any, Optional, Tuple
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import MMC_VERSION from src.config.config import MMC_VERSION
@@ -16,113 +16,113 @@ logger = get_logger("manifest_utils")
class VersionComparator: class VersionComparator:
"""版本号比较器 """版本号比较器
支持语义化版本号比较自动处理snapshot版本 支持语义化版本号比较自动处理snapshot版本
""" """
@staticmethod @staticmethod
def normalize_version(version: str) -> str: def normalize_version(version: str) -> str:
"""标准化版本号移除snapshot标识 """标准化版本号移除snapshot标识
Args: Args:
version: 原始版本号,如 "0.8.0-snapshot.1" version: 原始版本号,如 "0.8.0-snapshot.1"
Returns: Returns:
str: 标准化后的版本号,如 "0.8.0" str: 标准化后的版本号,如 "0.8.0"
""" """
if not version: if not version:
return "0.0.0" return "0.0.0"
# 移除snapshot部分 # 移除snapshot部分
normalized = re.sub(r'-snapshot\.\d+', '', version.strip()) normalized = re.sub(r"-snapshot\.\d+", "", version.strip())
# 确保版本号格式正确 # 确保版本号格式正确
if not re.match(r'^\d+(\.\d+){0,2}$', normalized): if not re.match(r"^\d+(\.\d+){0,2}$", normalized):
# 如果不是有效的版本号格式,返回默认版本 # 如果不是有效的版本号格式,返回默认版本
return "0.0.0" return "0.0.0"
# 尝试补全版本号 # 尝试补全版本号
parts = normalized.split('.') parts = normalized.split(".")
while len(parts) < 3: while len(parts) < 3:
parts.append('0') parts.append("0")
normalized = '.'.join(parts[:3]) normalized = ".".join(parts[:3])
return normalized return normalized
@staticmethod @staticmethod
def parse_version(version: str) -> Tuple[int, int, int]: def parse_version(version: str) -> Tuple[int, int, int]:
"""解析版本号为元组 """解析版本号为元组
Args: Args:
version: 版本号字符串 version: 版本号字符串
Returns: Returns:
Tuple[int, int, int]: (major, minor, patch) Tuple[int, int, int]: (major, minor, patch)
""" """
normalized = VersionComparator.normalize_version(version) normalized = VersionComparator.normalize_version(version)
try: try:
parts = normalized.split('.') parts = normalized.split(".")
return (int(parts[0]), int(parts[1]), int(parts[2])) return (int(parts[0]), int(parts[1]), int(parts[2]))
except (ValueError, IndexError): except (ValueError, IndexError):
logger.warning(f"无法解析版本号: {version},使用默认版本 0.0.0") logger.warning(f"无法解析版本号: {version},使用默认版本 0.0.0")
return (0, 0, 0) return (0, 0, 0)
@staticmethod @staticmethod
def compare_versions(version1: str, version2: str) -> int: def compare_versions(version1: str, version2: str) -> int:
"""比较两个版本号 """比较两个版本号
Args: Args:
version1: 第一个版本号 version1: 第一个版本号
version2: 第二个版本号 version2: 第二个版本号
Returns: Returns:
int: -1 if version1 < version2, 0 if equal, 1 if version1 > version2 int: -1 if version1 < version2, 0 if equal, 1 if version1 > version2
""" """
v1_tuple = VersionComparator.parse_version(version1) v1_tuple = VersionComparator.parse_version(version1)
v2_tuple = VersionComparator.parse_version(version2) v2_tuple = VersionComparator.parse_version(version2)
if v1_tuple < v2_tuple: if v1_tuple < v2_tuple:
return -1 return -1
elif v1_tuple > v2_tuple: elif v1_tuple > v2_tuple:
return 1 return 1
else: else:
return 0 return 0
@staticmethod @staticmethod
def is_version_in_range(version: str, min_version: str = "", max_version: str = "") -> Tuple[bool, str]: def is_version_in_range(version: str, min_version: str = "", max_version: str = "") -> Tuple[bool, str]:
"""检查版本是否在指定范围内 """检查版本是否在指定范围内
Args: Args:
version: 要检查的版本号 version: 要检查的版本号
min_version: 最小版本号(可选) min_version: 最小版本号(可选)
max_version: 最大版本号(可选) max_version: 最大版本号(可选)
Returns: Returns:
Tuple[bool, str]: (是否兼容, 错误信息) Tuple[bool, str]: (是否兼容, 错误信息)
""" """
if not min_version and not max_version: if not min_version and not max_version:
return True, "" return True, ""
version_normalized = VersionComparator.normalize_version(version) version_normalized = VersionComparator.normalize_version(version)
# 检查最小版本 # 检查最小版本
if min_version: if min_version:
min_normalized = VersionComparator.normalize_version(min_version) min_normalized = VersionComparator.normalize_version(min_version)
if VersionComparator.compare_versions(version_normalized, min_normalized) < 0: if VersionComparator.compare_versions(version_normalized, min_normalized) < 0:
return False, f"版本 {version_normalized} 低于最小要求版本 {min_normalized}" return False, f"版本 {version_normalized} 低于最小要求版本 {min_normalized}"
# 检查最大版本 # 检查最大版本
if max_version: if max_version:
max_normalized = VersionComparator.normalize_version(max_version) max_normalized = VersionComparator.normalize_version(max_version)
if VersionComparator.compare_versions(version_normalized, max_normalized) > 0: if VersionComparator.compare_versions(version_normalized, max_normalized) > 0:
return False, f"版本 {version_normalized} 高于最大支持版本 {max_normalized}" return False, f"版本 {version_normalized} 高于最大支持版本 {max_normalized}"
return True, "" return True, ""
@staticmethod @staticmethod
def get_current_host_version() -> str: def get_current_host_version() -> str:
"""获取当前主机应用版本 """获取当前主机应用版本
Returns: Returns:
str: 当前版本号 str: 当前版本号
""" """
@@ -131,67 +131,59 @@ class VersionComparator:
class ManifestValidator: class ManifestValidator:
"""Manifest文件验证器""" """Manifest文件验证器"""
# 必需字段(必须存在且不能为空) # 必需字段(必须存在且不能为空)
REQUIRED_FIELDS = [ REQUIRED_FIELDS = ["manifest_version", "name", "version", "description", "author"]
"manifest_version",
"name",
"version",
"description",
"author"
]
# 可选字段(可以不存在或为空) # 可选字段(可以不存在或为空)
OPTIONAL_FIELDS = [ OPTIONAL_FIELDS = [
"license", "license",
"host_application", "host_application",
"homepage_url", "homepage_url",
"repository_url", "repository_url",
"keywords", "keywords",
"categories", "categories",
"default_locale", "default_locale",
"locales_path", "locales_path",
"plugin_info" "plugin_info",
] ]
# 建议填写的字段(会给出警告但不会导致验证失败) # 建议填写的字段(会给出警告但不会导致验证失败)
RECOMMENDED_FIELDS = [ RECOMMENDED_FIELDS = ["license", "keywords", "categories"]
"license",
"keywords",
"categories"
]
SUPPORTED_MANIFEST_VERSIONS = [3] SUPPORTED_MANIFEST_VERSIONS = [3]
def __init__(self): def __init__(self):
self.validation_errors = [] self.validation_errors = []
self.validation_warnings = [] self.validation_warnings = []
def validate_manifest(self, manifest_data: Dict[str, Any]) -> bool: def validate_manifest(self, manifest_data: Dict[str, Any]) -> bool:
"""验证manifest数据 """验证manifest数据
Args: Args:
manifest_data: manifest数据字典 manifest_data: manifest数据字典
Returns: Returns:
bool: 是否验证通过(只有错误会导致验证失败,警告不会) bool: 是否验证通过(只有错误会导致验证失败,警告不会)
""" """
self.validation_errors.clear() self.validation_errors.clear()
self.validation_warnings.clear() self.validation_warnings.clear()
# 检查必需字段 # 检查必需字段
for field in self.REQUIRED_FIELDS: for field in self.REQUIRED_FIELDS:
if field not in manifest_data: if field not in manifest_data:
self.validation_errors.append(f"缺少必需字段: {field}") self.validation_errors.append(f"缺少必需字段: {field}")
elif not manifest_data[field]: elif not manifest_data[field]:
self.validation_errors.append(f"必需字段不能为空: {field}") self.validation_errors.append(f"必需字段不能为空: {field}")
# 检查manifest版本 # 检查manifest版本
if "manifest_version" in manifest_data: if "manifest_version" in manifest_data:
version = manifest_data["manifest_version"] version = manifest_data["manifest_version"]
if version not in self.SUPPORTED_MANIFEST_VERSIONS: if version not in self.SUPPORTED_MANIFEST_VERSIONS:
self.validation_errors.append(f"不支持的manifest版本: {version},支持的版本: {self.SUPPORTED_MANIFEST_VERSIONS}") self.validation_errors.append(
f"不支持的manifest版本: {version},支持的版本: {self.SUPPORTED_MANIFEST_VERSIONS}"
)
# 检查作者信息格式 # 检查作者信息格式
if "author" in manifest_data: if "author" in manifest_data:
author = manifest_data["author"] author = manifest_data["author"]
@@ -208,39 +200,41 @@ class ManifestValidator:
self.validation_errors.append("作者信息不能为空") self.validation_errors.append("作者信息不能为空")
else: else:
self.validation_errors.append("作者信息格式错误应为字符串或包含name字段的对象") self.validation_errors.append("作者信息格式错误应为字符串或包含name字段的对象")
# 检查主机应用版本要求(可选) # 检查主机应用版本要求(可选)
if "host_application" in manifest_data: if "host_application" in manifest_data:
host_app = manifest_data["host_application"] host_app = manifest_data["host_application"]
if isinstance(host_app, dict): if isinstance(host_app, dict):
min_version = host_app.get("min_version", "") min_version = host_app.get("min_version", "")
max_version = host_app.get("max_version", "") max_version = host_app.get("max_version", "")
# 验证版本字段格式 # 验证版本字段格式
for version_field in ["min_version", "max_version"]: for version_field in ["min_version", "max_version"]:
if version_field in host_app and not host_app[version_field]: if version_field in host_app and not host_app[version_field]:
self.validation_warnings.append(f"host_application.{version_field}为空") self.validation_warnings.append(f"host_application.{version_field}为空")
# 检查当前主机版本兼容性 # 检查当前主机版本兼容性
if min_version or max_version: if min_version or max_version:
current_version = VersionComparator.get_current_host_version() current_version = VersionComparator.get_current_host_version()
is_compatible, error_msg = VersionComparator.is_version_in_range( is_compatible, error_msg = VersionComparator.is_version_in_range(
current_version, min_version, max_version current_version, min_version, max_version
) )
if not is_compatible: if not is_compatible:
self.validation_errors.append(f"版本兼容性检查失败: {error_msg} (当前版本: {current_version})") self.validation_errors.append(f"版本兼容性检查失败: {error_msg} (当前版本: {current_version})")
else: else:
logger.debug(f"版本兼容性检查通过: 当前版本 {current_version} 符合要求 [{min_version}, {max_version}]") logger.debug(
f"版本兼容性检查通过: 当前版本 {current_version} 符合要求 [{min_version}, {max_version}]"
)
else: else:
self.validation_errors.append("host_application格式错误应为对象") self.validation_errors.append("host_application格式错误应为对象")
# 检查URL格式可选字段 # 检查URL格式可选字段
for url_field in ["homepage_url", "repository_url"]: for url_field in ["homepage_url", "repository_url"]:
if url_field in manifest_data and manifest_data[url_field]: if url_field in manifest_data and manifest_data[url_field]:
url = manifest_data[url_field] url = manifest_data[url_field]
if not (url.startswith("http://") or url.startswith("https://")): if not (url.startswith("http://") or url.startswith("https://")):
self.validation_warnings.append(f"{url_field}建议使用完整的URL格式") self.validation_warnings.append(f"{url_field}建议使用完整的URL格式")
# 检查数组字段格式(可选字段) # 检查数组字段格式(可选字段)
for list_field in ["keywords", "categories"]: for list_field in ["keywords", "categories"]:
if list_field in manifest_data: if list_field in manifest_data:
@@ -252,12 +246,12 @@ class ManifestValidator:
for i, item in enumerate(field_value): for i, item in enumerate(field_value):
if not isinstance(item, str): if not isinstance(item, str):
self.validation_warnings.append(f"{list_field}[{i}]应为字符串") self.validation_warnings.append(f"{list_field}[{i}]应为字符串")
# 检查建议字段(给出警告) # 检查建议字段(给出警告)
for field in self.RECOMMENDED_FIELDS: for field in self.RECOMMENDED_FIELDS:
if field not in manifest_data or not manifest_data[field]: if field not in manifest_data or not manifest_data[field]:
self.validation_warnings.append(f"建议填写字段: {field}") self.validation_warnings.append(f"建议填写字段: {field}")
# 检查plugin_info结构可选 # 检查plugin_info结构可选
if "plugin_info" in manifest_data: if "plugin_info" in manifest_data:
plugin_info = manifest_data["plugin_info"] plugin_info = manifest_data["plugin_info"]
@@ -275,121 +269,113 @@ class ManifestValidator:
# 检查组件必需字段 # 检查组件必需字段
for comp_field in ["type", "name", "description"]: for comp_field in ["type", "name", "description"]:
if comp_field not in component or not component[comp_field]: if comp_field not in component or not component[comp_field]:
self.validation_errors.append(f"plugin_info.components[{i}]缺少必需字段: {comp_field}") self.validation_errors.append(
f"plugin_info.components[{i}]缺少必需字段: {comp_field}"
)
else: else:
self.validation_errors.append("plugin_info应为对象格式") self.validation_errors.append("plugin_info应为对象格式")
return len(self.validation_errors) == 0 return len(self.validation_errors) == 0
def get_validation_report(self) -> str: def get_validation_report(self) -> str:
"""获取验证报告""" """获取验证报告"""
report = [] report = []
if self.validation_errors: if self.validation_errors:
report.append("❌ 验证错误:") report.append("❌ 验证错误:")
for error in self.validation_errors: for error in self.validation_errors:
report.append(f" - {error}") report.append(f" - {error}")
if self.validation_warnings: if self.validation_warnings:
report.append("⚠️ 验证警告:") report.append("⚠️ 验证警告:")
for warning in self.validation_warnings: for warning in self.validation_warnings:
report.append(f" - {warning}") report.append(f" - {warning}")
if not self.validation_errors and not self.validation_warnings: if not self.validation_errors and not self.validation_warnings:
report.append("✅ Manifest文件验证通过") report.append("✅ Manifest文件验证通过")
return "\n".join(report) return "\n".join(report)
class ManifestGenerator: class ManifestGenerator:
"""Manifest文件生成器""" """Manifest文件生成器"""
def __init__(self): def __init__(self):
self.template = { self.template = {
"manifest_version": 3, "manifest_version": 3,
"name": "", "name": "",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"author": { "author": {"name": "", "url": ""},
"name": "",
"url": ""
},
"license": "MIT", "license": "MIT",
"host_application": { "host_application": {"min_version": "1.0.0", "max_version": "4.0.0"},
"min_version": "1.0.0",
"max_version": "4.0.0"
},
"homepage_url": "", "homepage_url": "",
"repository_url": "", "repository_url": "",
"keywords": [], "keywords": [],
"categories": [], "categories": [],
"default_locale": "zh-CN", "default_locale": "zh-CN",
"locales_path": "_locales" "locales_path": "_locales",
} }
def generate_from_plugin(self, plugin_instance) -> Dict[str, Any]: def generate_from_plugin(self, plugin_instance) -> Dict[str, Any]:
"""从插件实例生成manifest """从插件实例生成manifest
Args: Args:
plugin_instance: BasePlugin实例 plugin_instance: BasePlugin实例
Returns: Returns:
Dict[str, Any]: 生成的manifest数据 Dict[str, Any]: 生成的manifest数据
""" """
manifest = self.template.copy() manifest = self.template.copy()
# 基本信息 # 基本信息
manifest["name"] = plugin_instance.plugin_name manifest["name"] = plugin_instance.plugin_name
manifest["version"] = plugin_instance.plugin_version manifest["version"] = plugin_instance.plugin_version
manifest["description"] = plugin_instance.plugin_description manifest["description"] = plugin_instance.plugin_description
# 作者信息 # 作者信息
if plugin_instance.plugin_author: if plugin_instance.plugin_author:
manifest["author"]["name"] = plugin_instance.plugin_author manifest["author"]["name"] = plugin_instance.plugin_author
# 组件信息 # 组件信息
components = [] components = []
plugin_components = plugin_instance.get_plugin_components() plugin_components = plugin_instance.get_plugin_components()
for component_info, component_class in plugin_components: for component_info, component_class in plugin_components:
component_data = { component_data = {
"type": component_info.component_type.value, "type": component_info.component_type.value,
"name": component_info.name, "name": component_info.name,
"description": component_info.description "description": component_info.description,
} }
# 添加激活模式信息对于Action组件 # 添加激活模式信息对于Action组件
if hasattr(component_class, 'focus_activation_type'): if hasattr(component_class, "focus_activation_type"):
activation_modes = [] activation_modes = []
if hasattr(component_class, 'focus_activation_type'): if hasattr(component_class, "focus_activation_type"):
activation_modes.append(component_class.focus_activation_type.value) activation_modes.append(component_class.focus_activation_type.value)
if hasattr(component_class, 'normal_activation_type'): if hasattr(component_class, "normal_activation_type"):
activation_modes.append(component_class.normal_activation_type.value) activation_modes.append(component_class.normal_activation_type.value)
component_data["activation_modes"] = list(set(activation_modes)) component_data["activation_modes"] = list(set(activation_modes))
# 添加关键词信息 # 添加关键词信息
if hasattr(component_class, 'activation_keywords'): if hasattr(component_class, "activation_keywords"):
keywords = getattr(component_class, 'activation_keywords', []) keywords = getattr(component_class, "activation_keywords", [])
if keywords: if keywords:
component_data["keywords"] = keywords component_data["keywords"] = keywords
components.append(component_data) components.append(component_data)
manifest["plugin_info"] = { manifest["plugin_info"] = {"is_built_in": True, "plugin_type": "general", "components": components}
"is_built_in": True,
"plugin_type": "general",
"components": components
}
return manifest return manifest
def save_manifest(self, manifest_data: Dict[str, Any], plugin_dir: str) -> bool: def save_manifest(self, manifest_data: Dict[str, Any], plugin_dir: str) -> bool:
"""保存manifest文件 """保存manifest文件
Args: Args:
manifest_data: manifest数据 manifest_data: manifest数据
plugin_dir: 插件目录 plugin_dir: 插件目录
Returns: Returns:
bool: 是否保存成功 bool: 是否保存成功
""" """
@@ -406,30 +392,30 @@ class ManifestGenerator:
def validate_plugin_manifest(plugin_dir: str) -> bool: def validate_plugin_manifest(plugin_dir: str) -> bool:
"""验证插件目录中的manifest文件 """验证插件目录中的manifest文件
Args: Args:
plugin_dir: 插件目录路径 plugin_dir: 插件目录路径
Returns: Returns:
bool: 是否验证通过 bool: 是否验证通过
""" """
manifest_path = os.path.join(plugin_dir, "_manifest.json") manifest_path = os.path.join(plugin_dir, "_manifest.json")
if not os.path.exists(manifest_path): if not os.path.exists(manifest_path):
logger.warning(f"未找到manifest文件: {manifest_path}") logger.warning(f"未找到manifest文件: {manifest_path}")
return False return False
try: try:
with open(manifest_path, "r", encoding="utf-8") as f: with open(manifest_path, "r", encoding="utf-8") as f:
manifest_data = json.load(f) manifest_data = json.load(f)
validator = ManifestValidator() validator = ManifestValidator()
is_valid = validator.validate_manifest(manifest_data) is_valid = validator.validate_manifest(manifest_data)
logger.info(f"Manifest验证结果:\n{validator.get_validation_report()}") logger.info(f"Manifest验证结果:\n{validator.get_validation_report()}")
return is_valid return is_valid
except Exception as e: except Exception as e:
logger.error(f"读取或验证manifest文件失败: {e}") logger.error(f"读取或验证manifest文件失败: {e}")
return False return False
@@ -437,23 +423,23 @@ def validate_plugin_manifest(plugin_dir: str) -> bool:
def generate_plugin_manifest(plugin_instance, save_to_file: bool = True) -> Optional[Dict[str, Any]]: def generate_plugin_manifest(plugin_instance, save_to_file: bool = True) -> Optional[Dict[str, Any]]:
"""为插件生成manifest文件 """为插件生成manifest文件
Args: Args:
plugin_instance: BasePlugin实例 plugin_instance: BasePlugin实例
save_to_file: 是否保存到文件 save_to_file: 是否保存到文件
Returns: Returns:
Optional[Dict[str, Any]]: 生成的manifest数据 Optional[Dict[str, Any]]: 生成的manifest数据
""" """
try: try:
generator = ManifestGenerator() generator = ManifestGenerator()
manifest_data = generator.generate_from_plugin(plugin_instance) manifest_data = generator.generate_from_plugin(plugin_instance)
if save_to_file and plugin_instance.plugin_dir: if save_to_file and plugin_instance.plugin_dir:
generator.save_manifest(manifest_data, plugin_instance.plugin_dir) generator.save_manifest(manifest_data, plugin_instance.plugin_dir)
return manifest_data return manifest_data
except Exception as e: except Exception as e:
logger.error(f"生成manifest文件失败: {e}") logger.error(f"生成manifest文件失败: {e}")
return None return None

View File

@@ -340,7 +340,7 @@ class CoreActionsPlugin(BasePlugin):
- Reply: 回复动作 - Reply: 回复动作
- NoReply: 不回复动作 - NoReply: 不回复动作
- Emoji: 表情动作 - Emoji: 表情动作
注意插件基本信息优先从_manifest.json文件中读取 注意插件基本信息优先从_manifest.json文件中读取
""" """