Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/cli/wizard/commands.py
T
insistence 9f7a0daa3b feat: 新增cli系统 (#103)
* feat: 新增cli系统

* feat: ruoyi completion支持powershell

* perf: 优化tui显示

* fix: 修复前端构建异常
2026-05-13 11:19:31 +08:00

317 lines
12 KiB
Python

from collections.abc import Callable
from dataclasses import dataclass, field
from importlib import import_module
import typer
from cli.context import OutputOption
WizardCommandRegistrar = Callable[[typer.Typer, 'WizardCommandRegistration'], None]
@dataclass(frozen=True)
class WizardCommandRegistration:
"""
wizard 子命令注册元数据。
:param name: 子命令名称
:param help_text: 子命令帮助文案
:param registrar: 对应的注册器函数
"""
name: str
help_text: str
class WizardFlowLoader:
"""
wizard flow 惰性加载器。
该对象负责在命令真正执行时导入对应 flow,
避免 `wizard` 入口在模块导入阶段提前拉起全部向导依赖。
"""
def load_runner(self, module_name: str, function_name: str) -> Callable[..., None]:
"""
按模块名和函数名加载向导执行入口。
:param module_name: flow 模块名
:param function_name: flow 执行函数名
:return: 可调用的向导执行函数
"""
flow_module = import_module(module_name)
return getattr(flow_module, function_name)
@dataclass
class WizardCommandBuilder:
"""
wizard 子命令构建器。
:param flow_loader: wizard flow 惰性加载器
"""
flow_loader: WizardFlowLoader = field(default_factory=WizardFlowLoader)
def build(self) -> typer.Typer:
"""
构建 wizard 命令组。
:return: wizard 子应用
"""
app = typer.Typer(
help='交互式向导命令',
no_args_is_help=True,
context_settings={'help_option_names': ['-h', '--help']},
)
self.register_commands(app)
return app
def register_commands(self, app: typer.Typer) -> None:
"""
向 Typer 子应用注册全部 wizard 子命令。
:param app: wizard 子应用
:return: None
"""
for registration, registrar in self.iter_command_registrars():
registrar(app, registration)
def iter_command_registrars(self) -> tuple[tuple[WizardCommandRegistration, WizardCommandRegistrar], ...]:
"""
返回向导命令注册元数据与显式注册器映射。
该方法将命令声明与注册函数绑定关系显式化,避免继续依赖
`registrar_name -> getattr(self, ...)` 的字符串分发。
:return: 注册元数据与注册器映射列表
"""
return (
(
WizardCommandRegistration('app-run', '通过交互方式启动应用'),
self._register_app_run_command,
),
(
WizardCommandRegistration('db-upgrade', '通过交互方式执行数据库升级'),
self._register_db_upgrade_command,
),
(
WizardCommandRegistration('cache-clear', '通过交互方式执行缓存清理'),
self._register_cache_clear_command,
),
(
WizardCommandRegistration('gen-export', '通过交互方式执行代码导出'),
self._register_gen_export_command,
),
(
WizardCommandRegistration('gen-import', '通过交互方式执行物理表导入'),
self._register_gen_import_command,
),
(
WizardCommandRegistration('prod-check', '通过交互方式执行生产巡检'),
self._register_prod_check_command,
),
)
def _register_app_run_command(self, app: typer.Typer, registration: WizardCommandRegistration) -> None:
"""
注册 `app-run` 子命令。
:param app: wizard 子应用
:param registration: 子命令注册元数据
:return: None
"""
@app.command(registration.name, help=registration.help_text)
def app_run() -> None:
"""
通过交互方式启动应用。
:return: None
"""
self.flow_loader.load_runner('cli.wizard.flows.app_run', 'run_app_run_wizard')()
def _register_db_upgrade_command(self, app: typer.Typer, registration: WizardCommandRegistration) -> None:
"""
注册 `db-upgrade` 子命令。
:param app: wizard 子应用
:param registration: 子命令注册元数据
:return: None
"""
@app.command(registration.name, help=registration.help_text)
def db_upgrade(
output: OutputOption = 'text',
default_env: str = typer.Option('dev', '--default-env', help='向导默认环境'),
default_revision: str = typer.Option('head', '--default-revision', help='向导默认目标 revision'),
default_dry_run: bool = typer.Option(
True, '--default-dry-run/--no-default-dry-run', help='向导默认 dry-run 选项'
),
) -> None:
"""
通过交互方式执行数据库升级。
:param output: 输出格式
:param default_env: 向导默认环境
:param default_revision: 向导默认目标 revision
:param default_dry_run: 向导默认 dry-run 选项
:return: None
"""
self.flow_loader.load_runner('cli.wizard.flows.db_upgrade', 'run_db_upgrade_wizard')(
output,
default_env=default_env,
default_revision=default_revision,
default_dry_run=default_dry_run,
)
def _register_cache_clear_command(self, app: typer.Typer, registration: WizardCommandRegistration) -> None:
"""
注册 `cache-clear` 子命令。
:param app: wizard 子应用
:param registration: 子命令注册元数据
:return: None
"""
@app.command(registration.name, help=registration.help_text)
def cache_clear(
output: OutputOption = 'text',
default_env: str = typer.Option('dev', '--default-env', help='向导默认环境'),
default_mode: str = typer.Option('cache-name', '--default-mode', help='向导默认清理模式'),
default_cache_name: str = typer.Option('', '--default-cache-name', help='向导默认缓存名称前缀'),
default_cache_key: str = typer.Option('', '--default-cache-key', help='向导默认缓存键关键字'),
default_dry_run: bool = typer.Option(
True, '--default-dry-run/--no-default-dry-run', help='向导默认 dry-run 选项'
),
) -> None:
"""
通过交互方式执行缓存清理。
:param output: 输出格式
:param default_env: 向导默认环境
:param default_mode: 向导默认清理模式
:param default_cache_name: 向导默认缓存名称前缀
:param default_cache_key: 向导默认缓存键关键字
:param default_dry_run: 向导默认 dry-run 选项
:return: None
"""
self.flow_loader.load_runner('cli.wizard.flows.cache_clear', 'run_cache_clear_wizard')(
output,
default_env=default_env,
default_mode=default_mode,
default_cache_name=default_cache_name,
default_cache_key=default_cache_key,
default_dry_run=default_dry_run,
)
def _register_gen_export_command(self, app: typer.Typer, registration: WizardCommandRegistration) -> None:
"""
注册 `gen-export` 子命令。
:param app: wizard 子应用
:param registration: 子命令注册元数据
:return: None
"""
@app.command(registration.name, help=registration.help_text)
def gen_export(
output: OutputOption = 'text',
default_env: str = typer.Option('dev', '--default-env', help='向导默认环境'),
default_table_names: str = typer.Option('', '--default-table-names', help='向导默认业务表名称列表'),
default_mode: str = typer.Option('zip', '--default-mode', help='向导默认导出模式'),
default_output_file: str = typer.Option('', '--default-output-file', help='向导默认导出目标文件路径'),
default_dry_run: bool = typer.Option(
True, '--default-dry-run/--no-default-dry-run', help='向导默认 dry-run 选项'
),
) -> None:
"""
通过交互方式执行代码导出。
:param output: 输出格式
:param default_env: 向导默认环境
:param default_table_names: 向导默认业务表名称列表
:param default_mode: 向导默认导出模式
:param default_output_file: 向导默认导出目标文件路径
:param default_dry_run: 向导默认 dry-run 选项
:return: None
"""
self.flow_loader.load_runner('cli.wizard.flows.gen_export', 'run_gen_export_wizard')(
output,
default_env=default_env,
default_table_names=default_table_names,
default_mode=default_mode,
default_output_file=default_output_file,
default_dry_run=default_dry_run,
)
def _register_gen_import_command(self, app: typer.Typer, registration: WizardCommandRegistration) -> None:
"""
注册 `gen-import` 子命令。
:param app: wizard 子应用
:param registration: 子命令注册元数据
:return: None
"""
@app.command(registration.name, help=registration.help_text)
def gen_import(
output: OutputOption = 'text',
default_env: str = typer.Option('dev', '--default-env', help='向导默认环境'),
default_table_names: str = typer.Option('', '--default-table-names', help='向导默认物理表名称列表'),
default_dry_run: bool = typer.Option(
True, '--default-dry-run/--no-default-dry-run', help='向导默认 dry-run 选项'
),
) -> None:
"""
通过交互方式执行物理表导入。
:param output: 输出格式
:param default_env: 向导默认环境
:param default_table_names: 向导默认物理表名称列表
:param default_dry_run: 向导默认 dry-run 选项
:return: None
"""
self.flow_loader.load_runner('cli.wizard.flows.gen_import', 'run_gen_import_wizard')(
output,
default_env=default_env,
default_table_names=default_table_names,
default_dry_run=default_dry_run,
)
def _register_prod_check_command(self, app: typer.Typer, registration: WizardCommandRegistration) -> None:
"""
注册 `prod-check` 子命令。
:param app: wizard 子应用
:param registration: 子命令注册元数据
:return: None
"""
@app.command(registration.name, help=registration.help_text)
def prod_check(
output: OutputOption = 'text',
default_env: str = typer.Option('prod', '--default-env', help='向导默认环境'),
default_include_config: bool = typer.Option(
True,
'--default-include-config/--no-default-include-config',
help='向导默认是否附带配置快照',
),
) -> None:
"""
通过交互方式执行生产巡检。
:param output: 输出格式
:param default_env: 向导默认环境
:param default_include_config: 向导默认是否附带配置快照
:return: None
"""
self.flow_loader.load_runner('cli.wizard.flows.prod_check', 'run_prod_check_wizard')(
output,
default_env=default_env,
default_include_config=default_include_config,
)
WIZARD_COMMAND_BUILDER = WizardCommandBuilder()