feat: 添加新的插件和清单管理工具
- 引入了“hello_world_plugin”和“take_picture_plugin”及其各自的清单文件。 - 实现了“manifest_tool.py”,用于创建、验证和管理插件清单。 - 添加了“test_version_compatibility.py”,用于测试版本规范化、比较和兼容性检查。 - 增强了“manifest_utils.py”,增加了版本比较和验证功能。
This commit is contained in:
257
scripts/manifest_tool.py
Normal file
257
scripts/manifest_tool.py
Normal file
@@ -0,0 +1,257 @@
|
||||
"""
|
||||
插件Manifest管理命令行工具
|
||||
|
||||
提供插件manifest文件的创建、验证和管理功能
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.utils.manifest_utils import (
|
||||
ManifestValidator,
|
||||
)
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
|
||||
|
||||
logger = get_logger("manifest_tool")
|
||||
|
||||
|
||||
def create_minimal_manifest(plugin_dir: str, plugin_name: str, description: str = "", author: str = "") -> bool:
|
||||
"""创建最小化的manifest文件
|
||||
|
||||
Args:
|
||||
plugin_dir: 插件目录
|
||||
plugin_name: 插件名称
|
||||
description: 插件描述
|
||||
author: 插件作者
|
||||
|
||||
Returns:
|
||||
bool: 是否创建成功
|
||||
"""
|
||||
manifest_path = os.path.join(plugin_dir, "_manifest.json")
|
||||
|
||||
if os.path.exists(manifest_path):
|
||||
print(f"❌ Manifest文件已存在: {manifest_path}")
|
||||
return False
|
||||
|
||||
# 创建最小化manifest
|
||||
minimal_manifest = {
|
||||
"manifest_version": 3,
|
||||
"name": plugin_name,
|
||||
"version": "1.0.0",
|
||||
"description": description or f"{plugin_name}插件",
|
||||
"author": {
|
||||
"name": author or "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
with open(manifest_path, "w", encoding="utf-8") as f:
|
||||
json.dump(minimal_manifest, f, ensure_ascii=False, indent=2)
|
||||
print(f"✅ 已创建最小化manifest文件: {manifest_path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 创建manifest文件失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def create_complete_manifest(plugin_dir: str, plugin_name: str) -> bool:
|
||||
"""创建完整的manifest模板文件
|
||||
|
||||
Args:
|
||||
plugin_dir: 插件目录
|
||||
plugin_name: 插件名称
|
||||
|
||||
Returns:
|
||||
bool: 是否创建成功
|
||||
"""
|
||||
manifest_path = os.path.join(plugin_dir, "_manifest.json")
|
||||
|
||||
if os.path.exists(manifest_path):
|
||||
print(f"❌ Manifest文件已存在: {manifest_path}")
|
||||
return False
|
||||
|
||||
# 创建完整模板
|
||||
complete_manifest = {
|
||||
"manifest_version": 3,
|
||||
"name": plugin_name,
|
||||
"version": "1.0.0",
|
||||
"description": f"{plugin_name}插件描述",
|
||||
"author": {
|
||||
"name": "插件作者",
|
||||
"url": "https://github.com/your-username"
|
||||
},
|
||||
"license": "MIT",
|
||||
"host_application": {
|
||||
"min_version": "1.0.0",
|
||||
"max_version": "4.0.0"
|
||||
},
|
||||
"homepage_url": "https://github.com/your-repo",
|
||||
"repository_url": "https://github.com/your-repo",
|
||||
"keywords": ["keyword1", "keyword2"],
|
||||
"categories": ["Category1"],
|
||||
"default_locale": "zh-CN",
|
||||
"locales_path": "_locales",
|
||||
"plugin_info": {
|
||||
"is_built_in": False,
|
||||
"plugin_type": "general",
|
||||
"components": [
|
||||
{
|
||||
"type": "action",
|
||||
"name": "sample_action",
|
||||
"description": "示例动作组件"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
with open(manifest_path, "w", encoding="utf-8") as f:
|
||||
json.dump(complete_manifest, f, ensure_ascii=False, indent=2)
|
||||
print(f"✅ 已创建完整manifest模板: {manifest_path}")
|
||||
print("💡 请根据实际情况修改manifest文件中的内容")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 创建manifest文件失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def validate_manifest_file(plugin_dir: str) -> bool:
|
||||
"""验证manifest文件
|
||||
|
||||
Args:
|
||||
plugin_dir: 插件目录
|
||||
|
||||
Returns:
|
||||
bool: 是否验证通过
|
||||
"""
|
||||
manifest_path = os.path.join(plugin_dir, "_manifest.json")
|
||||
|
||||
if not os.path.exists(manifest_path):
|
||||
print(f"❌ 未找到manifest文件: {manifest_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(manifest_path, "r", encoding="utf-8") as f:
|
||||
manifest_data = json.load(f)
|
||||
|
||||
validator = ManifestValidator()
|
||||
is_valid = validator.validate_manifest(manifest_data)
|
||||
|
||||
# 显示验证结果
|
||||
print("📋 Manifest验证结果:")
|
||||
print(validator.get_validation_report())
|
||||
|
||||
if is_valid:
|
||||
print("✅ Manifest文件验证通过")
|
||||
else:
|
||||
print("❌ Manifest文件验证失败")
|
||||
|
||||
return is_valid
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"❌ Manifest文件格式错误: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 验证过程中发生错误: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def scan_plugins_without_manifest(root_dir: str) -> None:
|
||||
"""扫描缺少manifest文件的插件
|
||||
|
||||
Args:
|
||||
root_dir: 扫描的根目录
|
||||
"""
|
||||
print(f"🔍 扫描目录: {root_dir}")
|
||||
|
||||
plugins_without_manifest = []
|
||||
|
||||
for root, dirs, files in os.walk(root_dir):
|
||||
# 跳过隐藏目录和__pycache__
|
||||
dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__']
|
||||
|
||||
# 检查是否包含plugin.py文件(标识为插件目录)
|
||||
if "plugin.py" in files:
|
||||
manifest_path = os.path.join(root, "_manifest.json")
|
||||
if not os.path.exists(manifest_path):
|
||||
plugins_without_manifest.append(root)
|
||||
|
||||
if plugins_without_manifest:
|
||||
print(f"❌ 发现 {len(plugins_without_manifest)} 个插件缺少manifest文件:")
|
||||
for plugin_dir in plugins_without_manifest:
|
||||
plugin_name = os.path.basename(plugin_dir)
|
||||
print(f" - {plugin_name}: {plugin_dir}")
|
||||
print("💡 使用 'python manifest_tool.py create-minimal <插件目录>' 创建manifest文件")
|
||||
else:
|
||||
print("✅ 所有插件都有manifest文件")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
parser = argparse.ArgumentParser(description="插件Manifest管理工具")
|
||||
subparsers = parser.add_subparsers(dest="command", 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("--name", help="插件名称")
|
||||
create_minimal_parser.add_argument("--description", help="插件描述")
|
||||
create_minimal_parser.add_argument("--author", 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("--name", help="插件名称")
|
||||
|
||||
# 验证manifest命令
|
||||
validate_parser = subparsers.add_parser("validate", help="验证manifest文件")
|
||||
validate_parser.add_argument("plugin_dir", help="插件目录路径")
|
||||
|
||||
# 扫描插件命令
|
||||
scan_parser = subparsers.add_parser("scan", help="扫描缺少manifest的插件")
|
||||
scan_parser.add_argument("root_dir", help="扫描的根目录路径")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
try:
|
||||
if args.command == "create-minimal":
|
||||
plugin_name = args.name or os.path.basename(os.path.abspath(args.plugin_dir))
|
||||
success = create_minimal_manifest(
|
||||
args.plugin_dir,
|
||||
plugin_name,
|
||||
args.description or "",
|
||||
args.author or ""
|
||||
)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.command == "create-complete":
|
||||
plugin_name = args.name or os.path.basename(os.path.abspath(args.plugin_dir))
|
||||
success = create_complete_manifest(args.plugin_dir, plugin_name)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.command == "validate":
|
||||
success = validate_manifest_file(args.plugin_dir)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.command == "scan":
|
||||
scan_plugins_without_manifest(args.root_dir)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 执行命令时发生错误: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
237
scripts/test_version_compatibility.py
Normal file
237
scripts/test_version_compatibility.py
Normal file
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
版本兼容性检查测试脚本
|
||||
|
||||
测试版本号标准化、比较和兼容性检查功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
from src.plugin_system.utils.manifest_utils import VersionComparator
|
||||
|
||||
|
||||
def test_version_normalization():
|
||||
"""测试版本号标准化功能"""
|
||||
print("🧪 测试版本号标准化...")
|
||||
|
||||
test_cases = [
|
||||
("0.8.0-snapshot.1", "0.8.0"),
|
||||
("0.8.0-snapshot.2", "0.8.0"),
|
||||
("0.8.0", "0.8.0"),
|
||||
("0.9.0-snapshot.1", "0.9.0"),
|
||||
("1.0.0", "1.0.0"),
|
||||
("2.1", "2.1.0"),
|
||||
("3", "3.0.0"),
|
||||
("", "0.0.0"),
|
||||
("invalid", "0.0.0"),
|
||||
]
|
||||
|
||||
for input_version, expected in test_cases:
|
||||
result = VersionComparator.normalize_version(input_version)
|
||||
status = "✅" if result == expected else "❌"
|
||||
print(f" {status} {input_version} -> {result} (期望: {expected})")
|
||||
|
||||
|
||||
def test_version_comparison():
|
||||
"""测试版本号比较功能"""
|
||||
print("\n🧪 测试版本号比较...")
|
||||
|
||||
test_cases = [
|
||||
("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
|
||||
("1.0.0", "1.0.0", 0), # 1.0.0 == 1.0.0
|
||||
("0.8.0-snapshot.1", "0.8.0", 0), # 标准化后相等
|
||||
("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
|
||||
]
|
||||
|
||||
for v1, v2, expected in test_cases:
|
||||
result = VersionComparator.compare_versions(v1, v2)
|
||||
status = "✅" if result == expected else "❌"
|
||||
comparison = "<" if expected == -1 else ">" if expected == 1 else "=="
|
||||
print(f" {status} {v1} {comparison} {v2} (结果: {result})")
|
||||
|
||||
|
||||
def test_version_range_check():
|
||||
"""测试版本范围检查功能"""
|
||||
print("\n🧪 测试版本范围检查...")
|
||||
|
||||
test_cases = [
|
||||
("0.8.0", "0.7.0", "0.9.0", True), # 在范围内
|
||||
("0.6.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.9.0", True), # 只有最大版本限制
|
||||
("0.8.0", "0.7.0", "", True), # 只有最小版本限制
|
||||
("0.8.0", "", "", True), # 无版本限制
|
||||
]
|
||||
|
||||
for version, min_ver, max_ver, expected in test_cases:
|
||||
is_compatible, error_msg = VersionComparator.is_version_in_range(version, min_ver, max_ver)
|
||||
status = "✅" if is_compatible == expected else "❌"
|
||||
range_str = f"[{min_ver or '无限制'}, {max_ver or '无限制'}]"
|
||||
print(f" {status} {version} 在范围 {range_str}: {is_compatible}")
|
||||
if error_msg:
|
||||
print(f" 错误信息: {error_msg}")
|
||||
|
||||
|
||||
def test_current_version():
|
||||
"""测试获取当前版本功能"""
|
||||
print("\n🧪 测试获取当前主机版本...")
|
||||
|
||||
try:
|
||||
current_version = VersionComparator.get_current_host_version()
|
||||
print(f" ✅ 当前主机版本: {current_version}")
|
||||
|
||||
# 验证版本号格式
|
||||
parts = current_version.split('.')
|
||||
if len(parts) == 3 and all(part.isdigit() for part in parts):
|
||||
print(f" ✅ 版本号格式正确")
|
||||
else:
|
||||
print(f" ❌ 版本号格式错误")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 获取当前版本失败: {e}")
|
||||
|
||||
|
||||
def test_manifest_compatibility():
|
||||
"""测试manifest兼容性检查"""
|
||||
print("\n🧪 测试manifest兼容性检查...")
|
||||
|
||||
# 模拟manifest数据
|
||||
test_manifests = [
|
||||
{
|
||||
"name": "兼容插件",
|
||||
"host_application": {
|
||||
"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": "无版本要求插件",
|
||||
# 没有host_application字段
|
||||
}
|
||||
]
|
||||
|
||||
# 这里需要导入PluginManager来测试,但可能会有依赖问题
|
||||
# 所以我们直接使用VersionComparator进行测试
|
||||
current_version = VersionComparator.get_current_host_version()
|
||||
|
||||
for manifest in test_manifests:
|
||||
plugin_name = manifest["name"]
|
||||
|
||||
if "host_application" in manifest:
|
||||
host_app = manifest["host_application"]
|
||||
min_version = host_app.get("min_version", "")
|
||||
max_version = host_app.get("max_version", "")
|
||||
|
||||
is_compatible, error_msg = VersionComparator.is_version_in_range(
|
||||
current_version, min_version, max_version
|
||||
)
|
||||
|
||||
status = "✅" if is_compatible else "❌"
|
||||
print(f" {status} {plugin_name}: {is_compatible}")
|
||||
if error_msg:
|
||||
print(f" {error_msg}")
|
||||
else:
|
||||
print(f" ✅ {plugin_name}: True (无版本要求)")
|
||||
|
||||
|
||||
def test_additional_snapshot_formats():
|
||||
"""测试额外的snapshot版本格式"""
|
||||
print("\n🧪 测试额外的snapshot版本格式...")
|
||||
|
||||
test_cases = [
|
||||
# 用户提到的版本格式
|
||||
("0.8.0-snapshot.1", "0.8.0"),
|
||||
("0.8.0-snapshot.2", "0.8.0"),
|
||||
("0.8.0", "0.8.0"),
|
||||
("0.9.0-snapshot.1", "0.9.0"),
|
||||
|
||||
# 边界情况
|
||||
("1.0.0-snapshot.999", "1.0.0"),
|
||||
("2.15.3-snapshot.42", "2.15.3"),
|
||||
("10.5.0-snapshot.1", "10.5.0"),
|
||||
# 不正确的snapshot格式(应该被忽略或正确处理)
|
||||
("0.8.0-snapshot", "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
|
||||
]
|
||||
|
||||
for input_version, expected in test_cases:
|
||||
result = VersionComparator.normalize_version(input_version)
|
||||
status = "✅" if result == expected else "❌"
|
||||
print(f" {status} {input_version} -> {result} (期望: {expected})")
|
||||
|
||||
|
||||
def test_snapshot_version_comparison():
|
||||
"""测试snapshot版本的比较功能"""
|
||||
print("\n🧪 测试snapshot版本比较...")
|
||||
|
||||
test_cases = [
|
||||
# snapshot版本与正式版本比较
|
||||
("0.8.0-snapshot.1", "0.8.0", 0), # 应该相等
|
||||
("0.8.0-snapshot.2", "0.8.0", 0), # 应该相等
|
||||
("0.9.0-snapshot.1", "0.8.0", 1), # 应该大于
|
||||
("0.7.0-snapshot.1", "0.8.0", -1), # 应该小于
|
||||
|
||||
# snapshot版本之间比较
|
||||
("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
|
||||
|
||||
# 边界情况
|
||||
("1.0.0-snapshot.1", "0.9.9", 1), # 主版本更高
|
||||
("0.9.0-snapshot.1", "0.8.99", 1), # 次版本更高
|
||||
]
|
||||
|
||||
for version1, version2, expected in test_cases:
|
||||
result = VersionComparator.compare_versions(version1, version2)
|
||||
status = "✅" if result == expected else "❌"
|
||||
comparison = "<" if expected < 0 else "==" if expected == 0 else ">"
|
||||
print(f" {status} {version1} {comparison} {version2} (结果: {result})")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🔧 MaiBot插件版本兼容性检查测试")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
test_version_normalization()
|
||||
test_version_comparison()
|
||||
test_version_range_check()
|
||||
test_current_version()
|
||||
test_manifest_compatibility()
|
||||
test_additional_snapshot_formats()
|
||||
test_snapshot_version_comparison()
|
||||
|
||||
print("\n🎉 所有测试完成!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试过程中发生错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user