mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-09-23 21:18:10 +00:00
* feat: 初始化插件系统 * refactor: 收口插件系统运行时重构 * perf: 优化插件系统类型提示 * fix&perf: 修复和优化插件系统 * fix: 修复gitignore规则误忽略插件文件的问题 * fix: 修复运行时插件根路径算错的问题 * fix: 加强插件发现和路由注册的防护措施 * revert: 回滚定时任务白名单 * fix: 移除未使用的应用路由注册探测 * revert: 恢复部分代码 * perf: 优化插件系统 * docs: 新增插件开发文档 * perf: 优化插件管理模块 * perf: 提升插件系统核心能力 * refactor: 重构生命周期 step runner * fix: 修复lint错误 * test: 清理测试用例 * test: 调整测试目录名称 * fix: 修复前后端目录硬编码的问题 * fix: 修复插件系统安全性缺口 * refactor: 重新设计插件生命周期 Migration 事务与回滚 * perf: 优化插件系统边界问题 * refactor: 重构当前插件系统的依赖体系设计 * perf: 优化代码 * perf: 优化代码 * fix: 修复代码合并问题 * fix: 修复bug * perf: 优化代码 * perf&fix: 优化代码和修复bug * docs: 优化文档格式 * feat: 适配Vue2版本 * docs: 更新README文档 * fix: 修复ruff lint错误 * chore: 更新后端依赖文件
925 lines
38 KiB
Python
925 lines
38 KiB
Python
from cli.utils import SHELL_TEXT_FORMATTER
|
|
|
|
|
|
class PluginCommandPresenter:
|
|
"""
|
|
插件命令文本渲染器。
|
|
"""
|
|
|
|
def build_list_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件列表负载渲染为文本。
|
|
|
|
:param payload: 插件列表负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'count: {payload.get("count", 0)}',
|
|
]
|
|
if 'databaseAvailable' in payload:
|
|
database_available = bool(payload.get('databaseAvailable', False))
|
|
lines.append(f'database_available: {str(database_available).lower()}')
|
|
if not database_available:
|
|
database_error = SHELL_TEXT_FORMATTER.truncate_text(payload.get('databaseError', '') or '', 120)
|
|
lines.append(f'database_error: {database_error or "-"}')
|
|
plugins = payload.get('plugins')
|
|
if not isinstance(plugins, list) or not plugins:
|
|
lines.append('plugins: none')
|
|
return '\n'.join(lines)
|
|
|
|
lines.append('plugins:')
|
|
lines.extend(self._build_plugin_summary_line(plugin) for plugin in plugins if isinstance(plugin, dict))
|
|
return '\n'.join(lines)
|
|
|
|
def build_info_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件详情负载渲染为文本。
|
|
|
|
:param payload: 插件详情负载
|
|
:return: 文本输出
|
|
"""
|
|
plugin = payload.get('plugin')
|
|
if not isinstance(plugin, dict):
|
|
return '\n'.join(
|
|
[
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
]
|
|
)
|
|
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'plugin_id: {plugin.get("pluginId", "-")}',
|
|
f'name: {plugin.get("name", "-")}',
|
|
f'version: {plugin.get("version", "-")}',
|
|
f'installed_version: {plugin.get("installedVersion", "-") or "-"}',
|
|
f'runtime_enabled: {str(plugin.get("runtimeEnabled", False)).lower()}',
|
|
f'status: {plugin.get("status", "-")}',
|
|
f'source: {plugin.get("source", "-")}',
|
|
f'last_error: {SHELL_TEXT_FORMATTER.truncate_text(plugin.get("lastError", "") or "", 100) or "-"}',
|
|
f'description: {SHELL_TEXT_FORMATTER.truncate_text(plugin.get("description", ""), 100)}',
|
|
f'backend_path: {SHELL_TEXT_FORMATTER.truncate_text(plugin.get("backendPath", ""), 120)}',
|
|
f'frontend_path: {SHELL_TEXT_FORMATTER.truncate_text(plugin.get("frontendPath", "") or "", 120) or "-"}',
|
|
f'menu_count: {plugin.get("menuCount", 0)}',
|
|
f'permission_count: {plugin.get("permissionCount", 0)}',
|
|
]
|
|
database = plugin.get('database')
|
|
if isinstance(database, dict):
|
|
lines.append(f'database_available: {str(database.get("available", False)).lower()}')
|
|
lines.append(f'database_installed: {str(database.get("installed", False)).lower()}')
|
|
if 'configuredEnabled' in database:
|
|
lines.append(f'configured_enabled: {str(database.get("configuredEnabled", False)).lower()}')
|
|
dependencies = plugin.get('dependencies')
|
|
if isinstance(dependencies, list):
|
|
lines.append(f'dependencies: {len(dependencies)}')
|
|
lines.extend(self._build_dependency_lines(dependencies))
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_check_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件检查负载渲染为文本。
|
|
|
|
:param payload: 插件检查负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'count: {payload.get("count", 0)}',
|
|
]
|
|
checks = payload.get('checks')
|
|
if not isinstance(checks, list) or not checks:
|
|
lines.append('checks: none')
|
|
return '\n'.join(lines)
|
|
|
|
lines.append('checks:')
|
|
lines.extend(self._build_check_summary_line(check) for check in checks if isinstance(check, dict))
|
|
return '\n'.join(lines)
|
|
|
|
def build_dependency_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件依赖检查负载渲染为文本。
|
|
|
|
:param payload: 插件依赖检查负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'dependency_ok: {str(payload.get("dependencyOk", False)).lower()}',
|
|
]
|
|
dependencies = payload.get('dependencies')
|
|
if isinstance(dependencies, list):
|
|
lines.append(f'dependencies: {len(dependencies)}')
|
|
lines.extend(self._build_dependency_lines(dependencies))
|
|
else:
|
|
lines.append('dependencies: none')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_precheck_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件操作预检负载渲染为文本。
|
|
|
|
:param payload: 插件操作预检负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'operation: {payload.get("operation", "-")}',
|
|
f'dependency_ok: {str(payload.get("dependencyOk", False)).lower()}',
|
|
f'plugin_dependency_ok: {str(payload.get("pluginDependencyOk", False)).lower()}',
|
|
f'structure_ok: {str(payload.get("structureOk", False)).lower()}',
|
|
f'menu_conflict_ok: {str(payload.get("menuConflictOk", False)).lower()}',
|
|
f'database_available: {str(payload.get("databaseAvailable", False)).lower()}',
|
|
]
|
|
actions = payload.get('actions')
|
|
lines.append(f'actions: {len(actions) if isinstance(actions, list) else 0}')
|
|
precheck = payload.get('precheck')
|
|
if isinstance(precheck, dict):
|
|
lines.append(f'dependencies: {len(precheck.get("dependencies", []))}')
|
|
lines.append(f'structure_errors: {len(precheck.get("structureErrors", []))}')
|
|
lines.append(f'menu_conflicts: {len(precheck.get("menuConflicts", []))}')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_health_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件健康检查负载渲染为文本。
|
|
|
|
:param payload: 插件健康检查负载
|
|
:return: 文本输出
|
|
"""
|
|
health = payload.get('health')
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
]
|
|
if not isinstance(health, dict):
|
|
return '\n'.join(lines)
|
|
|
|
lines.extend(
|
|
[
|
|
f'status: {health.get("status", "-")}',
|
|
f'checker: {health.get("checker", "-") or "-"}',
|
|
f'duration_ms: {health.get("durationMs", 0)}',
|
|
]
|
|
)
|
|
if health.get('error'):
|
|
lines.append(f'error: {SHELL_TEXT_FORMATTER.truncate_text(health.get("error", ""), 120)}')
|
|
details = health.get('details')
|
|
if isinstance(details, dict):
|
|
lines.append(f'details: {len(details)}')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_diagnose_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件诊断包负载渲染为文本。
|
|
|
|
:param payload: 插件诊断包负载
|
|
:return: 文本输出
|
|
"""
|
|
info = payload.get('info')
|
|
check = payload.get('check')
|
|
menu_plan = payload.get('menuPlan')
|
|
config = payload.get('config')
|
|
audit = payload.get('audit')
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
]
|
|
if isinstance(info, dict):
|
|
lines.append(f'status: {info.get("status", "-")}')
|
|
lines.append(f'installed_version: {info.get("installedVersion", "-") or "-"}')
|
|
lines.append(
|
|
f'last_error: {SHELL_TEXT_FORMATTER.truncate_text(info.get("lastError", "") or "", 100) or "-"}'
|
|
)
|
|
if isinstance(check, dict):
|
|
checks = check.get('checks')
|
|
check_items = checks if isinstance(checks, list) else []
|
|
first_check = check_items[0] if check_items and isinstance(check_items[0], dict) else {}
|
|
lines.append(f'check_ok: {str(check.get("ok", False)).lower()}')
|
|
lines.append(f'dependency_count: {len(first_check.get("dependencies", [])) if first_check else 0}')
|
|
lines.append(f'structure_errors: {len(first_check.get("structureErrors", [])) if first_check else 0}')
|
|
lines.append(f'menu_conflicts: {len(first_check.get("menuConflicts", [])) if first_check else 0}')
|
|
if isinstance(menu_plan, dict):
|
|
lines.append(
|
|
f'menu_plan: total={menu_plan.get("total", 0)} | '
|
|
f'permissions={menu_plan.get("permissionCount", 0)} | '
|
|
f'enabled={menu_plan.get("enabledCount", 0)}'
|
|
)
|
|
if isinstance(config, dict):
|
|
configs = config.get('configs')
|
|
lines.append(f'configs: {len(configs) if isinstance(configs, list) else 0}')
|
|
summary = config.get('summary')
|
|
if isinstance(summary, dict):
|
|
lines.append(
|
|
f'config_summary: total={summary.get("total", 0)} | '
|
|
f'secret={summary.get("secretCount", 0)} | '
|
|
f'missing_required={summary.get("missingRequiredCount", 0)}'
|
|
)
|
|
if isinstance(audit, dict):
|
|
lines.append(f'audit_available: {str(audit.get("available", False)).lower()}')
|
|
if payload.get('outputFile'):
|
|
lines.append(f'output_file: {payload.get("outputFile")}')
|
|
lines.append(f'exported: {str(payload.get("exported", False)).lower()}')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_docs_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件文档生成负载渲染为文本。
|
|
|
|
:param payload: 插件文档生成负载
|
|
:return: 文本输出
|
|
"""
|
|
if payload.get('ok') and isinstance(payload.get('markdown'), str) and not payload.get('outputFile'):
|
|
return str(payload.get('markdown', ''))
|
|
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'format: {payload.get("format", "-")}',
|
|
f'length: {payload.get("length", 0)}',
|
|
]
|
|
if payload.get('outputFile'):
|
|
lines.append(f'output_file: {payload.get("outputFile")}')
|
|
lines.append(f'exported: {str(payload.get("exported", False)).lower()}')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_test_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件测试负载渲染为文本。
|
|
|
|
:param payload: 插件测试负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'keyword: {payload.get("keyword", "") or "-"}',
|
|
f'maxfail: {payload.get("maxfail", 0)}',
|
|
f'quiet: {str(payload.get("quiet", False)).lower()}',
|
|
f'frontend_build: {str(payload.get("frontendBuild", False)).lower()}',
|
|
]
|
|
targets = payload.get('targets')
|
|
if isinstance(targets, list):
|
|
lines.append(f'targets: {len(targets)}')
|
|
lines.extend(f' - {target}' for target in targets)
|
|
command = payload.get('command')
|
|
if isinstance(command, list):
|
|
lines.append(f'command: {" ".join(str(item) for item in command)}')
|
|
results = payload.get('results')
|
|
if isinstance(results, list):
|
|
lines.append(f'results: {len(results)}')
|
|
for item in results:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
test_result = item.get('test') if isinstance(item.get('test'), dict) else {}
|
|
lines.append(
|
|
f' - {item.get("kind", "-")}: {item.get("target", "-")} [{test_result.get("returnCode", "-")}]'
|
|
)
|
|
test_payload = payload.get('test')
|
|
if isinstance(test_payload, dict):
|
|
lines.append(f'return_code: {test_payload.get("returnCode", "-")}')
|
|
stdout = str(test_payload.get('stdout', '') or '').strip()
|
|
stderr = str(test_payload.get('stderr', '') or '').strip()
|
|
if stdout:
|
|
lines.append(f'stdout: {SHELL_TEXT_FORMATTER.truncate_text(stdout, 300)}')
|
|
if stderr:
|
|
lines.append(f'stderr: {SHELL_TEXT_FORMATTER.truncate_text(stderr, 300)}')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_dependency_install_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件依赖安装负载渲染为文本。
|
|
|
|
:param payload: 插件依赖安装负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'env: {payload.get("env", "-")}',
|
|
f'dry_run: {str(payload.get("dryRun", False)).lower()}',
|
|
f'plan_count: {payload.get("planCount", 0)}',
|
|
]
|
|
plan = payload.get('plan')
|
|
if isinstance(plan, list) and plan:
|
|
lines.append('plan:')
|
|
lines.extend(self._build_dependency_plan_line(item) for item in plan if isinstance(item, dict))
|
|
else:
|
|
lines.append('plan: none')
|
|
|
|
results = payload.get('results')
|
|
if isinstance(results, list):
|
|
lines.append(f'results: {len(results)}')
|
|
policy = payload.get('policy')
|
|
if isinstance(policy, dict):
|
|
lines.extend(
|
|
[
|
|
f'policy_mode: {policy.get("mode", "-")}',
|
|
f'policy_allowed: {str(policy.get("allowed", False)).lower()}',
|
|
]
|
|
)
|
|
reasons = policy.get('reasons')
|
|
if isinstance(reasons, list) and reasons:
|
|
lines.append('policy_reasons:')
|
|
lines.extend(f' - {reason}' for reason in reasons)
|
|
requirements = policy.get('requirements')
|
|
if isinstance(requirements, list) and requirements:
|
|
lines.append('policy_requirements:')
|
|
lines.extend(f' - {requirement}' for requirement in requirements)
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_dependency_lock_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件依赖锁文件模板负载渲染为文本。
|
|
|
|
:param payload: 插件依赖锁文件模板负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'env: {payload.get("env", "-")}',
|
|
f'dry_run: {str(payload.get("dryRun", False)).lower()}',
|
|
f'output_file: {payload.get("outputFile", "-")}',
|
|
f'written: {str(payload.get("written", False)).lower()}',
|
|
f'overwritten: {str(payload.get("overwritten", False)).lower()}',
|
|
f'entry_count: {payload.get("entryCount", 0)}',
|
|
f'artifact_count: {payload.get("artifactCount", 0)}',
|
|
]
|
|
warnings = payload.get('warnings')
|
|
if isinstance(warnings, list) and warnings:
|
|
lines.append('warnings:')
|
|
lines.extend(f' - {warning}' for warning in warnings)
|
|
return '\n'.join(lines)
|
|
|
|
def build_dependency_allowlist_example_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件依赖允许列表示例负载渲染为文本。
|
|
|
|
:param payload: 插件依赖允许列表示例负载
|
|
:return: 文本输出
|
|
"""
|
|
if payload.get('ok') and isinstance(payload.get('allowlist'), str) and payload.get('dryRun'):
|
|
return str(payload.get('allowlist', ''))
|
|
|
|
return '\n'.join(
|
|
[
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'env: {payload.get("env", "-")}',
|
|
f'dry_run: {str(payload.get("dryRun", False)).lower()}',
|
|
f'output_file: {payload.get("outputFile", "-")}',
|
|
f'written: {str(payload.get("written", False)).lower()}',
|
|
f'overwritten: {str(payload.get("overwritten", False)).lower()}',
|
|
]
|
|
)
|
|
|
|
def build_plan_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件批量操作拓扑计划负载渲染为文本。
|
|
|
|
:param payload: 插件批量操作拓扑计划负载
|
|
:return: 文本输出
|
|
"""
|
|
plan = payload.get('plan')
|
|
items = plan.get('items') if isinstance(plan, dict) else None
|
|
blockers = plan.get('blockers') if isinstance(plan, dict) else None
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'operation: {payload.get("operation", "-")}',
|
|
f'blocker_count: {plan.get("blockerCount", 0) if isinstance(plan, dict) else 0}',
|
|
]
|
|
if isinstance(items, list) and items:
|
|
lines.append('plan:')
|
|
lines.extend(self._build_plugin_plan_line(item) for item in items if isinstance(item, dict))
|
|
else:
|
|
lines.append('plan: none')
|
|
if isinstance(blockers, list) and blockers:
|
|
lines.append('blockers:')
|
|
lines.extend(self._build_plugin_plan_blocker_line(item) for item in blockers if isinstance(item, dict))
|
|
else:
|
|
lines.append('blockers: none')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_batch_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件批量执行负载渲染为文本。
|
|
|
|
:param payload: 插件批量执行负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'operation: {payload.get("operation", "-")}',
|
|
f'env: {payload.get("env", "-")}',
|
|
f'dry_run: {str(payload.get("dryRun", False)).lower()}',
|
|
f'continue_on_error: {str(payload.get("continueOnError", False)).lower()}',
|
|
]
|
|
plan = payload.get('plan')
|
|
if isinstance(plan, dict):
|
|
lines.append(f'plan_items: {len(plan.get("items", []))}')
|
|
lines.append(f'blocker_count: {plan.get("blockerCount", 0)}')
|
|
summary = payload.get('summary')
|
|
if isinstance(summary, dict):
|
|
lines.append(
|
|
f'summary: total={summary.get("total", 0)} | succeeded={summary.get("succeeded", 0)} | '
|
|
f'failed={summary.get("failed", 0)} | skipped={summary.get("skipped", 0)}'
|
|
)
|
|
executed = payload.get('executed')
|
|
if isinstance(executed, list) and executed:
|
|
lines.append('executed:')
|
|
lines.extend(self._build_batch_result_line(item) for item in executed if isinstance(item, dict))
|
|
else:
|
|
lines.append('executed: none')
|
|
failed = payload.get('failed')
|
|
if isinstance(failed, dict):
|
|
lines.append(
|
|
f'failed: {failed.get("pluginId", "-")} | '
|
|
f'operation: {failed.get("operation", "-")} | message: {failed.get("message", "-")}'
|
|
)
|
|
if failed.get('suggestion'):
|
|
lines.append(f'suggestion: {failed.get("suggestion", "-")}')
|
|
else:
|
|
lines.append('failed: none')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_create_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件创建负载渲染为文本。
|
|
|
|
:param payload: 插件创建负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'dry_run: {str(payload.get("dryRun", False)).lower()}',
|
|
f'template: {payload.get("template", "-")}',
|
|
f'backend: {str(payload.get("backend", False)).lower()}',
|
|
f'frontend: {str(payload.get("frontend", False)).lower()}',
|
|
f'frontend_version: {payload.get("frontendVersion") or "-"}',
|
|
f'migration: {str(payload.get("migration", False)).lower()}',
|
|
f'seed: {str(payload.get("seed", False)).lower()}',
|
|
f'job: {str(payload.get("job", False)).lower()}',
|
|
f'config: {str(payload.get("config", False)).lower()}',
|
|
f'test: {str(payload.get("test", False)).lower()}',
|
|
]
|
|
conflicts = payload.get('conflicts')
|
|
if isinstance(conflicts, list) and conflicts:
|
|
lines.append('conflicts:')
|
|
lines.extend(f' - {conflict}' for conflict in conflicts)
|
|
|
|
files = payload.get('files')
|
|
if not isinstance(files, list) or not files:
|
|
lines.append('files: none')
|
|
return '\n'.join(lines)
|
|
|
|
lines.append(f'files: {len(files)}')
|
|
lines.extend(f' - {file_payload.get("path", "-")}' for file_payload in files if isinstance(file_payload, dict))
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_install_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件安装负载渲染为文本。
|
|
|
|
:param payload: 插件安装负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'env: {payload.get("env", "-")}',
|
|
f'dry_run: {str(payload.get("dryRun", False)).lower()}',
|
|
f'dependency_ok: {str(payload.get("dependencyOk", False)).lower()}',
|
|
f'structure_ok: {str(payload.get("structureOk", True)).lower()}',
|
|
f'menu_conflict_ok: {str(payload.get("menuConflictOk", True)).lower()}',
|
|
]
|
|
actions = payload.get('actions')
|
|
if isinstance(actions, list) and actions:
|
|
lines.append('actions:')
|
|
lines.extend(self._build_action_summary_line(action) for action in actions if isinstance(action, dict))
|
|
else:
|
|
lines.append('actions: none')
|
|
|
|
dependencies = payload.get('dependencies')
|
|
if isinstance(dependencies, list) and dependencies:
|
|
lines.append(f'dependencies: {len(dependencies)}')
|
|
else:
|
|
lines.append('dependencies: none')
|
|
|
|
structure_errors = payload.get('structureErrors')
|
|
if isinstance(structure_errors, list) and structure_errors:
|
|
lines.append(f'structure_errors: {len(structure_errors)}')
|
|
else:
|
|
lines.append('structure_errors: none')
|
|
|
|
menu_conflicts = payload.get('menuConflicts')
|
|
if isinstance(menu_conflicts, list) and menu_conflicts:
|
|
lines.append(f'menu_conflicts: {len(menu_conflicts)}')
|
|
else:
|
|
lines.append('menu_conflicts: none')
|
|
|
|
self._append_lifecycle_migration_lines(lines, payload)
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_upgrade_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件升级负载渲染为文本。
|
|
|
|
:param payload: 插件升级负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'env: {payload.get("env", "-")}',
|
|
f'dry_run: {str(payload.get("dryRun", False)).lower()}',
|
|
f'installed: {str(payload.get("installed", False)).lower()}',
|
|
f'installed_version: {payload.get("installedVersion", "-") or "-"}',
|
|
f'current_version: {payload.get("currentVersion", "-") or "-"}',
|
|
f'needs_upgrade: {str(payload.get("needsUpgrade", False)).lower()}',
|
|
f'database_available: {str(payload.get("databaseAvailable", True)).lower()}',
|
|
f'dependency_ok: {str(payload.get("dependencyOk", False)).lower()}',
|
|
f'structure_ok: {str(payload.get("structureOk", True)).lower()}',
|
|
f'menu_conflict_ok: {str(payload.get("menuConflictOk", True)).lower()}',
|
|
]
|
|
actions = payload.get('actions')
|
|
if isinstance(actions, list) and actions:
|
|
lines.append('actions:')
|
|
lines.extend(self._build_action_summary_line(action) for action in actions if isinstance(action, dict))
|
|
else:
|
|
lines.append('actions: none')
|
|
|
|
structure_errors = payload.get('structureErrors')
|
|
if isinstance(structure_errors, list) and structure_errors:
|
|
lines.append(f'structure_errors: {len(structure_errors)}')
|
|
else:
|
|
lines.append('structure_errors: none')
|
|
|
|
menu_conflicts = payload.get('menuConflicts')
|
|
if isinstance(menu_conflicts, list) and menu_conflicts:
|
|
lines.append(f'menu_conflicts: {len(menu_conflicts)}')
|
|
else:
|
|
lines.append('menu_conflicts: none')
|
|
|
|
self._append_lifecycle_migration_lines(lines, payload)
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_enabled_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件启停负载渲染为文本。
|
|
|
|
:param payload: 插件启停负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'env: {payload.get("env", "-")}',
|
|
f'operation: {payload.get("operation", "-")}',
|
|
f'target_enabled: {str(payload.get("targetEnabled", False)).lower()}',
|
|
f'dry_run: {str(payload.get("dryRun", False)).lower()}',
|
|
]
|
|
actions = payload.get('actions')
|
|
if isinstance(actions, list) and actions:
|
|
lines.append('actions:')
|
|
lines.extend(self._build_action_summary_line(action) for action in actions if isinstance(action, dict))
|
|
else:
|
|
lines.append('actions: none')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_purge_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件物理清理负载渲染为文本。
|
|
|
|
:param payload: 插件物理清理负载
|
|
:return: 文本输出
|
|
"""
|
|
plan = payload.get('plan')
|
|
plan_items = plan.get('items') if isinstance(plan, dict) else None
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'env: {payload.get("env", "-")}',
|
|
f'operation: {payload.get("operation", "-")}',
|
|
f'dry_run: {str(payload.get("dryRun", False)).lower()}',
|
|
f'safe_mode: {str(payload.get("safeMode", False)).lower()}',
|
|
f'removes_source: {str(payload.get("removesSource", False)).lower()}',
|
|
f'destructive_count: {plan.get("destructiveCount", 0) if isinstance(plan, dict) else 0}',
|
|
]
|
|
if isinstance(plan_items, list) and plan_items:
|
|
lines.append('plan:')
|
|
lines.extend(self._build_purge_plan_line(item) for item in plan_items if isinstance(item, dict))
|
|
else:
|
|
lines.append('plan: none')
|
|
|
|
hooks = payload.get('hooks')
|
|
if isinstance(hooks, list) and hooks:
|
|
lines.append(f'hooks: {len(hooks)}')
|
|
else:
|
|
lines.append('hooks: none')
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def build_migration_list_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件 migration 历史负载渲染为文本。
|
|
|
|
:param payload: 插件 migration 历史负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'status: {payload.get("status", "-") or "-"}',
|
|
f'count: {payload.get("count", 0)}',
|
|
]
|
|
migrations = payload.get('migrations')
|
|
if not isinstance(migrations, list) or not migrations:
|
|
lines.append('migrations: none')
|
|
return '\n'.join(lines)
|
|
|
|
lines.append('migrations:')
|
|
lines.extend(
|
|
self._build_migration_summary_line(migration) for migration in migrations if isinstance(migration, dict)
|
|
)
|
|
return '\n'.join(lines)
|
|
|
|
def build_migration_mark_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件 migration 人工标记负载渲染为文本。
|
|
|
|
:param payload: 插件 migration 人工标记负载
|
|
:return: 文本输出
|
|
"""
|
|
return '\n'.join(
|
|
[
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
f'env: {payload.get("env", "-")}',
|
|
f'operation: {payload.get("operation", "-")}',
|
|
f'migration_path: {payload.get("migrationPath", "-")}',
|
|
f'status: {payload.get("status", "-")}',
|
|
]
|
|
)
|
|
|
|
def build_config_text(self, payload: dict[str, object]) -> str:
|
|
"""
|
|
将插件配置负载渲染为文本。
|
|
|
|
:param payload: 插件配置负载
|
|
:return: 文本输出
|
|
"""
|
|
lines = [
|
|
f'ok: {str(payload.get("ok", False)).lower()}',
|
|
f'message: {payload.get("message", "-")}',
|
|
f'plugin_id: {payload.get("pluginId", "-")}',
|
|
]
|
|
configs = payload.get('configs')
|
|
if not isinstance(configs, list) or not configs:
|
|
lines.append('configs: none')
|
|
return '\n'.join(lines)
|
|
|
|
lines.append(f'configs: {len(configs)}')
|
|
lines.extend(self._build_config_summary_line(config) for config in configs if isinstance(config, dict))
|
|
|
|
return '\n'.join(lines)
|
|
|
|
@staticmethod
|
|
def _build_dependency_lines(dependencies: list[object]) -> list[str]:
|
|
"""
|
|
构建依赖检查文本行。
|
|
|
|
:param dependencies: 依赖检查负载列表
|
|
:return: 文本行列表
|
|
"""
|
|
if not dependencies:
|
|
return ['dependencies_detail: none']
|
|
lines = ['dependencies_detail:']
|
|
lines.extend(
|
|
PluginCommandPresenter._build_dependency_summary_line(dependency)
|
|
for dependency in dependencies
|
|
if isinstance(dependency, dict)
|
|
)
|
|
return lines
|
|
|
|
@staticmethod
|
|
def _build_plugin_summary_line(plugin: dict[str, object]) -> str:
|
|
"""
|
|
构建插件摘要文本行。
|
|
|
|
:param plugin: 插件摘要负载
|
|
:return: 文本行
|
|
"""
|
|
return (
|
|
f' - {plugin.get("pluginId", "-")} | {plugin.get("name", "-")} | '
|
|
f'version: {plugin.get("version", "-")} | '
|
|
f'runtime_enabled: {str(plugin.get("runtimeEnabled", False)).lower()} | '
|
|
f'status: {plugin.get("status", "-")}'
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_config_summary_line(config: dict[str, object]) -> str:
|
|
"""
|
|
构建插件配置摘要文本行。
|
|
|
|
:param config: 插件配置负载
|
|
:return: 文本行
|
|
"""
|
|
return (
|
|
f' - {config.get("key", "-")} | {config.get("label", "-") or "-"} | '
|
|
f'type: {config.get("type", "-")} | value: {config.get("value", "-")}'
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_migration_summary_line(migration: dict[str, object]) -> str:
|
|
"""
|
|
构建插件 migration 历史摘要文本行。
|
|
|
|
:param migration: migration 历史负载
|
|
:return: 文本行
|
|
"""
|
|
checksum = str(migration.get('migrationChecksum') or migration.get('checksum') or '')
|
|
migration_path = migration.get('migrationPath') or migration.get('migration_path') or '-'
|
|
status = migration.get('status', '-')
|
|
attempts = migration.get('attemptCount', migration.get('attempt_count', 0))
|
|
version = migration.get('version', '-') or '-'
|
|
duration_ms = migration.get('durationMs', migration.get('duration_ms'))
|
|
duration_text = f' | duration_ms: {duration_ms}' if duration_ms is not None else ''
|
|
return (
|
|
f' - {migration_path} | status: {status} | attempts: {attempts} | version: {version} | '
|
|
f'checksum: {checksum[:12] or "-"}{duration_text}'
|
|
)
|
|
|
|
def _append_lifecycle_migration_lines(self, lines: list[str], payload: dict[str, object]) -> None:
|
|
"""
|
|
追加生命周期 migration 结果和恢复建议文本。
|
|
|
|
:param lines: 文本行列表
|
|
:param payload: 生命周期负载
|
|
:return: None
|
|
"""
|
|
migration_recovery = payload.get('migrationRecovery')
|
|
if isinstance(migration_recovery, dict):
|
|
lines.append(
|
|
'migration_recovery: '
|
|
f'{migration_recovery.get("migrationPath", "-")} | '
|
|
f'status: {migration_recovery.get("status", "-")} | '
|
|
f'suggestion: {migration_recovery.get("suggestion", "-")}'
|
|
)
|
|
|
|
migrations = payload.get('migrations')
|
|
if not isinstance(migrations, list) or not migrations:
|
|
lines.append('migrations: none')
|
|
return
|
|
|
|
lines.append('migrations:')
|
|
lines.extend(
|
|
self._build_migration_summary_line(migration) for migration in migrations if isinstance(migration, dict)
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_dependency_plan_line(item: dict[str, object]) -> str:
|
|
"""
|
|
构建依赖安装计划文本行。
|
|
|
|
:param item: 依赖安装计划项
|
|
:return: 文本行
|
|
"""
|
|
return (
|
|
f' - {item.get("kind", "-")} | {item.get("requirement", "-")} | '
|
|
f'workdir: {item.get("workdir", "-")} | command: {item.get("commandText", "-")}'
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_purge_plan_line(item: dict[str, object]) -> str:
|
|
"""
|
|
构建插件物理清理计划文本行。
|
|
|
|
:param item: 插件物理清理计划项
|
|
:return: 文本行
|
|
"""
|
|
will_run = item.get('willRun', item.get('enabled', False))
|
|
return (
|
|
f' - {item.get("name", "-")} | will_run: {str(will_run).lower()} | '
|
|
f'destructive: {str(item.get("destructive", False)).lower()} | '
|
|
f'count: {item.get("count", "-") if item.get("count") is not None else "-"} | '
|
|
f'label: {item.get("label", "-")}'
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_plugin_plan_line(item: dict[str, object]) -> str:
|
|
"""
|
|
构建插件批量操作计划文本行。
|
|
|
|
:param item: 插件批量操作计划项
|
|
:return: 文本行
|
|
"""
|
|
return (
|
|
f' - #{item.get("order", "-")} {item.get("pluginId", "-")} | '
|
|
f'ready: {str(item.get("ready", False)).lower()} | '
|
|
f'requested: {str(item.get("requested", False)).lower()} | '
|
|
f'deps: {len(item.get("dependencies", []))}'
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_plugin_plan_blocker_line(item: dict[str, object]) -> str:
|
|
"""
|
|
构建插件批量操作计划阻塞项文本行。
|
|
|
|
:param item: 插件批量操作计划阻塞项
|
|
:return: 文本行
|
|
"""
|
|
return (
|
|
f' - {item.get("pluginId", "-")} -> {item.get("dependencyId", "-")} | '
|
|
f'status: {item.get("status", "-")} | message: {item.get("message", "-")}'
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_batch_result_line(item: dict[str, object]) -> str:
|
|
"""
|
|
构建插件批量执行结果文本行。
|
|
|
|
:param item: 插件批量执行结果项
|
|
:return: 文本行
|
|
"""
|
|
return (
|
|
f' - {item.get("pluginId", "-")} | operation: {item.get("operation", "-")} | '
|
|
f'ok: {str(item.get("ok", False)).lower()} | status: {item.get("status", "-")} | '
|
|
f'duration_ms: {item.get("durationMs", 0)} | message: {item.get("message", "-")}'
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_check_summary_line(check: dict[str, object]) -> str:
|
|
"""
|
|
构建插件检查摘要文本行。
|
|
|
|
:param check: 插件检查负载
|
|
:return: 文本行
|
|
"""
|
|
missing_dependencies = check.get('missingDependencies', [])
|
|
unsatisfied_dependencies = check.get('unsatisfiedDependencies', [])
|
|
structure_errors = check.get('structureErrors', [])
|
|
menu_conflicts = check.get('menuConflicts', [])
|
|
return (
|
|
f' - {check.get("pluginId", "-")} | ok: {str(check.get("ok", False)).lower()} | '
|
|
f'missing: {len(missing_dependencies)} | unsatisfied: {len(unsatisfied_dependencies)} | '
|
|
f'structure_errors: {len(structure_errors)} | menu_conflicts: {len(menu_conflicts)}'
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_dependency_summary_line(dependency: dict[str, object]) -> str:
|
|
"""
|
|
构建依赖检查摘要文本行。
|
|
|
|
:param dependency: 依赖检查负载
|
|
:return: 文本行
|
|
"""
|
|
return (
|
|
f' - {dependency.get("kind", "-")}:{dependency.get("name", "-")} | '
|
|
f'ok: {str(dependency.get("ok", False)).lower()} | '
|
|
f'required: {dependency.get("requiredVersion", "-") or "-"} | '
|
|
f'installed: {dependency.get("installedVersion", "-") or "-"}'
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_action_summary_line(action: dict[str, object]) -> str:
|
|
"""
|
|
构建安装动作摘要文本行。
|
|
|
|
:param action: 安装动作负载
|
|
:return: 文本行
|
|
"""
|
|
will_run = action.get('willRun', action.get('enabled', False))
|
|
return f' - {action.get("name", "-")} | will_run: {str(will_run).lower()} | label: {action.get("label", "-")}'
|