Merge branch 'dev' of https://github.com/MaiM-with-u/MaiBot into dev
This commit is contained in:
@@ -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": 1,
|
"manifest_version": 1,
|
||||||
"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": 1,
|
"manifest_version": 1,
|
||||||
"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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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 = "" # 插件主页
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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": 1,
|
"manifest_version": 1,
|
||||||
"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
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ class CoreActionsPlugin(BasePlugin):
|
|||||||
- Reply: 回复动作
|
- Reply: 回复动作
|
||||||
- NoReply: 不回复动作
|
- NoReply: 不回复动作
|
||||||
- Emoji: 表情动作
|
- Emoji: 表情动作
|
||||||
|
|
||||||
注意:插件基本信息优先从_manifest.json文件中读取
|
注意:插件基本信息优先从_manifest.json文件中读取
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user