mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-10-14 13:06:46 +00:00
* feat: 支持 v2 插件、原生扩展与签名发布 * fix: 修复测试 * feat: 增强宿主 SDK、运行观测与制品维护能力 * feat: 增强文件传输、配置观测与本地开发能力 * feat: 支持 SSE 实时事件流并完善开发脚手架 * feat: 完善长连接鉴权复核与关闭回收 * feat: 增强生命周期与任务关闭可靠性 * feat: 收紧健康检查契约并补齐前端与真实网络验收 * feat: 增加SDK溯源与离线更新工具并验证跨版本兼容 * feat: 增加持久化业务示例与受控 Python 交付 * feat: 增加统一运行诊断与多worker状态观测 * feat: 增加固定目标发布等待与自动验收 * fix: 修复多环境插件部署前缀与代理配置不一致 * fix: 修复测试
1001 lines
42 KiB
Python
1001 lines
42 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 {}
|
|
if check.get('supported') is False:
|
|
lines.append('check: unavailable (database_only)')
|
|
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) and menu_plan.get('supported') is not False:
|
|
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) and config.get('supported') is False:
|
|
lines.append('configs: unavailable (database_only)')
|
|
elif 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()}')
|
|
runtime = payload.get('runtime')
|
|
if isinstance(runtime, dict):
|
|
lines.extend(self._build_runtime_diagnostics_lines(runtime))
|
|
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)
|
|
|
|
@staticmethod
|
|
def _build_runtime_diagnostics_lines(runtime: dict[str, object]) -> list[str]:
|
|
"""
|
|
显示观测范围及实际进程状态,缺失快照不得呈现为零计数。
|
|
|
|
:param runtime: 运行诊断聚合负载
|
|
:return: 包含范围、时效和实际状态的文本行
|
|
"""
|
|
|
|
def value(item: object) -> str:
|
|
"""
|
|
将未观测值显示为未知,并保留真实零计数。
|
|
|
|
:param item: 待显示的状态值
|
|
:return: 适合 CLI 文本的状态字符串
|
|
"""
|
|
return 'unknown' if item is None else str(item).lower() if isinstance(item, bool) else str(item)
|
|
|
|
lines = [
|
|
f'runtime_state: {runtime.get("state", "unavailable")}',
|
|
f'runtime_scope: {runtime.get("scope", "unavailable")}',
|
|
(
|
|
f'runtime_workers: observed={value(runtime.get("observedWorkers"))} | '
|
|
f'expected={value(runtime.get("expectedWorkers"))} | missing={value(runtime.get("missingWorkers"))}'
|
|
),
|
|
(
|
|
f'runtime_samples: ttl_seconds={value(runtime.get("sampleTtlSeconds"))} | '
|
|
f'stale={value(runtime.get("staleSnapshots"))} | invalid={value(runtime.get("invalidSnapshots"))}'
|
|
),
|
|
]
|
|
release = runtime.get('release')
|
|
summary = release.get('summary') if isinstance(release, dict) else None
|
|
if isinstance(summary, dict):
|
|
lines.append(
|
|
f'release_target: status={summary.get("status", "unknown")} | '
|
|
f'digest={value(summary.get("targetDigest"))} | generation={value(summary.get("generation"))}'
|
|
)
|
|
workers = runtime.get('workers')
|
|
for worker in workers if isinstance(workers, list) else []:
|
|
actual = worker.get('actual') or {}
|
|
connections = actual.get('connections') or {}
|
|
health = actual.get('activationHealth') or {}
|
|
lines.extend(
|
|
[
|
|
(
|
|
f'worker: {worker.get("workerId")} | freshness={worker.get("freshness")} | '
|
|
f'observation={worker.get("observation")} | age_seconds={value(worker.get("ageSeconds"))}'
|
|
),
|
|
(
|
|
f' actual: version={value(actual.get("version"))} | digest={value(actual.get("digest"))} | '
|
|
f'generation={value(actual.get("generation"))} | ready={value(actual.get("ready"))} | '
|
|
f'closing={value(actual.get("closing"))}'
|
|
),
|
|
(
|
|
f' running: jobs={value(actual.get("activeJobs"))} | sse={value(connections.get("sse"))} | '
|
|
f'websocket={value(connections.get("websocket"))} | '
|
|
f'pending_connection_tasks={value(actual.get("pendingConnectionTasks"))} | '
|
|
f'pending_job_tasks={value(actual.get("pendingJobTasks"))} | '
|
|
f'worker_pending_cleanup={value(worker.get("workerPendingCleanupTasks"))}'
|
|
),
|
|
(
|
|
f' activation_health: {health.get("status", "unknown")} | '
|
|
f'checked_at={value(health.get("checkedAt"))}'
|
|
),
|
|
]
|
|
)
|
|
return 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", "-")}'
|