Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/cli/groups/plugin/commands/dependency.py
T
insistence 2a055ba648 feat: 新增插件系统 (#112)
* 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: 更新后端依赖文件
2026-07-28 20:35:18 +08:00

206 lines
7.7 KiB
Python

from collections.abc import Callable
from typing import Annotated, Any, Literal
import typer
from cli.context import AllowProdOption, DryRunOption, EnvOption, OutputOption, YesOption
from cli.groups.plugin.options import (
PluginDependencyAllowlistExampleCommandOptions,
PluginDependencyInstallCommandOptions,
PluginDependencyLockCommandOptions,
)
PluginPrecheckOperation = Literal['install', 'enable', 'upgrade', 'uninstall', 'purge']
PluginPlanOperation = Literal['install', 'enable', 'upgrade']
def register_dependency_commands(app: typer.Typer, get_controller: Callable[[], Any]) -> None:
"""
注册插件依赖、预检和计划命令。
:param app: Typer 命令组
:param get_controller: 插件命令控制器工厂
:return: None
"""
@app.command('check-deps', help='检查插件依赖')
def check_deps_command(
plugin_id: Annotated[str, typer.Argument(help='插件ID')],
env: EnvOption = 'dev',
output: OutputOption = 'text',
) -> None:
"""
检查插件依赖。
:param plugin_id: 插件ID
:param env: 当前命令运行环境
:param output: 输出格式
:return: None
"""
get_controller().check_plugin_dependencies(plugin_id, env, output)
@app.command('precheck', help='执行插件操作预检')
def precheck_command(
operation: Annotated[
PluginPrecheckOperation,
typer.Argument(help='预检操作类型:install、enable、upgrade、uninstall 或 purge'),
],
plugin_id: Annotated[str, typer.Argument(help='插件ID')],
env: EnvOption = 'dev',
output: OutputOption = 'text',
) -> None:
"""
执行插件操作预检。
:param operation: 预检操作类型
:param plugin_id: 插件ID
:param env: 当前命令运行环境
:param output: 输出格式
:return: None
"""
get_controller().precheck_plugin(operation, plugin_id, env, output)
@app.command('plan', help='生成插件批量操作拓扑计划')
def plan_command(
operation: Annotated[PluginPlanOperation, typer.Argument(help='计划操作类型:install、enable 或 upgrade')],
plugin_ids: Annotated[list[str] | None, typer.Argument(help='插件ID列表,不传则计划全部插件')] = None,
env: EnvOption = 'dev',
output: OutputOption = 'text',
) -> None:
"""
生成插件批量操作拓扑计划。
:param operation: 计划操作类型
:param plugin_ids: 插件ID列表
:param env: 当前命令运行环境
:param output: 输出格式
:return: None
"""
get_controller().plan_plugins(operation, plugin_ids or [], env, output)
@app.command('install-deps', help='安装插件依赖')
def install_deps_command( # noqa: PLR0913
plugin_id: Annotated[str, typer.Argument(help='插件ID')],
*,
env: EnvOption = 'dev',
output: OutputOption = 'text',
allow_prod: AllowProdOption = False,
yes: YesOption = False,
dry_run: DryRunOption = False,
policy_mode: Annotated[
Literal['disabled', 'plan_only', 'explicit', 'locked', 'offline'] | None,
typer.Option('--policy-mode', help='临时覆盖插件依赖安装策略模式'),
] = None,
allow_unlisted: Annotated[
bool,
typer.Option('--allow-unlisted', help='dev 环境允许未命中 allowlist 的依赖仅告警'),
] = False,
lockfile: Annotated[str, typer.Option('--lockfile', help='指定插件依赖锁文件路径')] = '',
offline_dir: Annotated[str, typer.Option('--offline-dir', help='指定插件离线依赖制品目录')] = '',
require_lockfile: Annotated[
bool | None,
typer.Option('--require-lockfile/--no-require-lockfile', help='临时切换锁文件要求'),
] = None,
) -> None:
"""
安装插件依赖。
:param plugin_id: 插件ID
:param env: 当前命令运行环境
:param output: 输出格式
:param allow_prod: 是否允许生产环境危险命令
:param yes: 是否跳过确认
:param dry_run: 是否仅预演
:param policy_mode: 临时覆盖策略模式
:param allow_unlisted: dev 环境是否允许未命中 allowlist 的依赖仅告警
:param lockfile: 锁文件路径
:param offline_dir: 离线制品目录
:param require_lockfile: 是否要求锁文件
:return: None
"""
get_controller().install_plugin_dependencies(
plugin_id,
env,
output,
options=PluginDependencyInstallCommandOptions(
allow_prod=allow_prod,
yes=yes,
dry_run=dry_run,
policy_mode=policy_mode,
allow_unlisted=allow_unlisted,
lockfile=lockfile,
offline_dir=offline_dir,
require_lockfile=require_lockfile,
),
)
@app.command('lock-deps', help='生成插件依赖锁文件模板')
def lock_deps_command(
plugin_id: Annotated[str, typer.Argument(help='插件ID')],
env: EnvOption = 'dev',
output: OutputOption = 'text',
dry_run: DryRunOption = False,
output_path: Annotated[
str,
typer.Option('--output-path', help='输出锁文件路径,默认写入插件目录 plugin.lock.yaml'),
] = '',
offline_dir: Annotated[
str, typer.Option('--offline-dir', help='从本地离线制品目录反填版本和 hash/integrity')
] = '',
overwrite: Annotated[bool, typer.Option('--overwrite', help='覆盖已有锁文件')] = False,
) -> None:
"""
生成插件依赖锁文件模板。
:param plugin_id: 插件ID
:param env: 当前命令运行环境
:param output: 输出格式
:param dry_run: 是否仅预演
:param output_path: 输出锁文件路径
:param offline_dir: 离线制品目录
:param overwrite: 是否覆盖已有锁文件
:return: None
"""
get_controller().lock_plugin_dependencies(
plugin_id,
env,
output,
options=PluginDependencyLockCommandOptions(
output_path=output_path,
offline_dir=offline_dir,
dry_run=dry_run,
overwrite=overwrite,
),
)
@app.command('allowlist-example', help='生成插件依赖允许列表示例')
def allowlist_example_command(
env: EnvOption = 'dev',
output: OutputOption = 'text',
dry_run: DryRunOption = False,
output_path: Annotated[
str,
typer.Option('--output-path', help='输出允许列表路径,默认写入 config/plugin_dependency_allowlist.yaml'),
] = '',
overwrite: Annotated[bool, typer.Option('--overwrite', help='覆盖已有允许列表文件')] = False,
) -> None:
"""
生成插件依赖允许列表示例。
:param env: 当前命令运行环境
:param output: 输出格式
:param dry_run: 是否仅预演
:param output_path: 输出允许列表路径
:param overwrite: 是否覆盖已有允许列表文件
:return: None
"""
get_controller().generate_plugin_dependency_allowlist_example(
env,
output,
options=PluginDependencyAllowlistExampleCommandOptions(
output_path=output_path,
dry_run=dry_run,
overwrite=overwrite,
),
)