调整部分前端布局和后端增删改逻辑
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProdCheckPayloadSupport:
|
||||
"""
|
||||
`wizard prod-check` 结果提取支持对象。
|
||||
|
||||
该对象负责从多路只读结果中提取 runtime、doctor、config
|
||||
片段,避免聚合器本体继续堆叠细碎字段判定逻辑。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def extract_runtime_payload(runtime_payload: dict[str, Any] | None) -> dict[str, Any] | None:
|
||||
"""
|
||||
提取运行环境结果中的 runtime 片段。
|
||||
|
||||
:param runtime_payload: 运行环境结果
|
||||
:return: runtime 片段
|
||||
"""
|
||||
return runtime_payload.get('runtime') if isinstance(runtime_payload, dict) else None
|
||||
|
||||
@staticmethod
|
||||
def extract_doctor_payload(doctor_payload: dict[str, Any] | None) -> dict[str, Any] | None:
|
||||
"""
|
||||
提取应用检查结果片段。
|
||||
|
||||
:param doctor_payload: 应用检查结果
|
||||
:return: doctor 片段
|
||||
"""
|
||||
return doctor_payload if isinstance(doctor_payload, dict) else None
|
||||
|
||||
@staticmethod
|
||||
def extract_config_payload(config_payload: dict[str, Any] | None) -> dict[str, Any] | None:
|
||||
"""
|
||||
提取配置快照结果中的 config 片段。
|
||||
|
||||
:param config_payload: 配置快照结果
|
||||
:return: config 片段
|
||||
"""
|
||||
return config_payload.get('config') if isinstance(config_payload, dict) else None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProdCheckStatusEvaluator:
|
||||
"""
|
||||
`wizard prod-check` 状态评估器。
|
||||
|
||||
该对象负责统一计算聚合结果的成功状态和标准消息。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def is_ok(
|
||||
runtime_payload: dict[str, Any] | None,
|
||||
doctor_payload: dict[str, Any] | None,
|
||||
config_payload: dict[str, Any] | None,
|
||||
) -> bool:
|
||||
"""
|
||||
计算生产巡检聚合结果是否成功。
|
||||
|
||||
:param runtime_payload: 运行环境结果
|
||||
:param doctor_payload: 应用检查结果
|
||||
:param config_payload: 配置快照结果
|
||||
:return: 是否成功
|
||||
"""
|
||||
return (
|
||||
bool(runtime_payload and runtime_payload.get('ok', False))
|
||||
and bool(doctor_payload and doctor_payload.get('ok', False))
|
||||
and (config_payload is None or bool(config_payload.get('ok', False)))
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_message() -> str:
|
||||
"""
|
||||
构建生产巡检统一结果消息。
|
||||
|
||||
:return: 结果消息
|
||||
"""
|
||||
return '生产巡检完成'
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProdCheckAggregator:
|
||||
"""
|
||||
`wizard prod-check` 聚合器。
|
||||
|
||||
该对象负责聚合运行环境、应用检查和配置快照结果,
|
||||
让 flow 本体只保留交互编排职责。
|
||||
|
||||
:param payload_support: 结果提取支持对象
|
||||
:param status_evaluator: 状态评估器
|
||||
"""
|
||||
|
||||
payload_support: ProdCheckPayloadSupport = ProdCheckPayloadSupport()
|
||||
status_evaluator: ProdCheckStatusEvaluator = ProdCheckStatusEvaluator()
|
||||
|
||||
def build_payload(
|
||||
self,
|
||||
*,
|
||||
env: str,
|
||||
runtime_payload: dict[str, Any] | None,
|
||||
doctor_payload: dict[str, Any] | None,
|
||||
config_payload: dict[str, Any] | None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
构建生产巡检聚合结果。
|
||||
|
||||
:param env: 当前运行环境
|
||||
:param runtime_payload: 运行环境结果
|
||||
:param doctor_payload: 应用检查结果
|
||||
:param config_payload: 配置快照结果
|
||||
:return: 聚合结果负载
|
||||
"""
|
||||
return {
|
||||
'ok': self.status_evaluator.is_ok(runtime_payload, doctor_payload, config_payload),
|
||||
'env': env,
|
||||
'message': self.status_evaluator.build_message(),
|
||||
'runtime': self.payload_support.extract_runtime_payload(runtime_payload),
|
||||
'doctor': self.payload_support.extract_doctor_payload(doctor_payload),
|
||||
'config': self.payload_support.extract_config_payload(config_payload),
|
||||
}
|
||||
@@ -0,0 +1,909 @@
|
||||
import inspect
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Callable
|
||||
from typing import Any, ClassVar, Generic, TypeVar
|
||||
|
||||
import typer
|
||||
|
||||
from cli.context import CliContext, OutputOption
|
||||
from cli.core import DEFAULT_CORE_SERVICES, CliContextFactory, CliExecutionService
|
||||
from cli.output import CommandResult
|
||||
from cli.utils import NESTED_CLI_SUPPORT
|
||||
from cli.wizard.preview import WIZARD_PREVIEW_RENDERER, WizardPreviewRenderer
|
||||
from cli.wizard.prompts import WIZARD_PROMPT_SERVICE, WizardPromptService
|
||||
|
||||
SelectionT = TypeVar('SelectionT')
|
||||
|
||||
|
||||
class WizardInteractionSupport:
|
||||
"""
|
||||
向导交互与上下文构建支持对象。
|
||||
|
||||
:param context_factory: CLI 上下文工厂
|
||||
:param prompt_service: 向导提示服务
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
context_factory: CliContextFactory,
|
||||
prompt_service: WizardPromptService,
|
||||
) -> None:
|
||||
"""
|
||||
初始化交互支持对象。
|
||||
|
||||
:param context_factory: CLI 上下文工厂
|
||||
:param prompt_service: 向导提示服务
|
||||
:return: None
|
||||
"""
|
||||
self.context_factory = context_factory
|
||||
self.prompt_service = prompt_service
|
||||
|
||||
def build_regular_context(
|
||||
self,
|
||||
env: str,
|
||||
output: OutputOption,
|
||||
*,
|
||||
allow_prod: bool,
|
||||
yes: bool,
|
||||
dry_run: bool,
|
||||
) -> CliContext:
|
||||
"""
|
||||
构建普通可写命令上下文。
|
||||
|
||||
:param env: 运行环境
|
||||
:param output: 输出格式
|
||||
:param allow_prod: 是否允许生产环境执行
|
||||
:param yes: 是否跳过确认
|
||||
:param dry_run: 是否执行预演
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
return self.context_factory.build_regular(env, output, allow_prod, yes, dry_run)
|
||||
|
||||
def build_readonly_context(self, env: str, output: OutputOption) -> CliContext:
|
||||
"""
|
||||
构建只读命令上下文。
|
||||
|
||||
:param env: 运行环境
|
||||
:param output: 输出格式
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
return self.context_factory.build_readonly(env, output)
|
||||
|
||||
def prompt_confirm(self, prompt_text: str, *, default_value: bool) -> bool:
|
||||
"""
|
||||
执行标准确认交互。
|
||||
|
||||
:param prompt_text: 提示文本
|
||||
:param default_value: 默认值
|
||||
:return: 是否确认
|
||||
"""
|
||||
return self.prompt_service.prompt_confirm(prompt_text, default_value=default_value)
|
||||
|
||||
|
||||
class WizardResultSupport:
|
||||
"""
|
||||
向导结果收口支持对象。
|
||||
|
||||
:param execution_service: CLI 执行服务
|
||||
:param preview_renderer: 向导预览渲染器
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
execution_service: CliExecutionService,
|
||||
preview_renderer: WizardPreviewRenderer,
|
||||
) -> None:
|
||||
"""
|
||||
初始化结果收口支持对象。
|
||||
|
||||
:param execution_service: CLI 执行服务
|
||||
:param preview_renderer: 向导预览渲染器
|
||||
:return: None
|
||||
"""
|
||||
self.execution_service = execution_service
|
||||
self.preview_renderer = preview_renderer
|
||||
|
||||
def complete_result(self, ctx: CliContext, result: CommandResult) -> None:
|
||||
"""
|
||||
输出命令结果对象。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param result: 命令结果对象
|
||||
:return: None
|
||||
"""
|
||||
self.execution_service.complete_result(ctx, result)
|
||||
|
||||
def complete_payload(
|
||||
self,
|
||||
ctx: CliContext,
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
default_exit_code: int = 0,
|
||||
) -> None:
|
||||
"""
|
||||
输出标准结果负载。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param payload: 标准结果负载
|
||||
:param default_exit_code: 默认退出码
|
||||
:return: None
|
||||
"""
|
||||
self.execution_service.complete_payload(ctx, payload, default_exit_code=default_exit_code)
|
||||
|
||||
def complete_payload_result(
|
||||
self,
|
||||
ctx: CliContext,
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
text_builder: Callable[[dict[str, Any]], Any] | None = None,
|
||||
default_exit_code: int = 0,
|
||||
text_condition: Callable[[dict[str, Any]], bool] | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
统一按输出格式收口标准结果负载。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param payload: 标准结果负载
|
||||
:param text_builder: 文本结果构建函数
|
||||
:param default_exit_code: 默认退出码
|
||||
:param text_condition: 文本模式结果构建判定函数
|
||||
:return: None
|
||||
"""
|
||||
self.execution_service.complete_payload_result(
|
||||
ctx,
|
||||
payload,
|
||||
text_builder=text_builder,
|
||||
default_exit_code=default_exit_code,
|
||||
text_condition=text_condition,
|
||||
)
|
||||
|
||||
def build_cancel_result(self, wizard_name: str) -> CommandResult:
|
||||
"""
|
||||
构建统一的向导取消结果。
|
||||
|
||||
:param wizard_name: 向导名称
|
||||
:return: 命令结果对象
|
||||
"""
|
||||
return self.preview_renderer.build_cancel_result(wizard_name)
|
||||
|
||||
def get_preview_renderer(self) -> WizardPreviewRenderer:
|
||||
"""
|
||||
返回当前项目默认的向导预览渲染器。
|
||||
|
||||
:return: 预览渲染器
|
||||
"""
|
||||
return self.preview_renderer
|
||||
|
||||
|
||||
class WizardNestedCommandSupport:
|
||||
"""
|
||||
向导内部 CLI 调用支持对象。
|
||||
|
||||
:param command_runner: 内部 CLI 子进程调用函数
|
||||
:param live_command_runner: 内部 CLI 实时执行函数
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
command_runner: Callable[..., Any],
|
||||
live_command_runner: Callable[..., Any],
|
||||
) -> None:
|
||||
"""
|
||||
初始化内部 CLI 调用支持对象。
|
||||
|
||||
:param command_runner: 内部 CLI 子进程调用函数
|
||||
:param live_command_runner: 内部 CLI 实时执行函数
|
||||
:return: None
|
||||
"""
|
||||
self.command_runner = command_runner
|
||||
self.live_command_runner = live_command_runner
|
||||
|
||||
@staticmethod
|
||||
def resolve_callable_override(owner: type['BaseCliWizardFlow[Any]'], attribute_name: str) -> Callable[..., Any]:
|
||||
"""
|
||||
解析类级可调用注入点,避免普通函数在实例访问时被绑定为方法。
|
||||
|
||||
:param owner: 向导类
|
||||
:param attribute_name: 类属性名称
|
||||
:return: 可直接调用的函数对象
|
||||
"""
|
||||
attribute = inspect.getattr_static(owner, attribute_name)
|
||||
if isinstance(attribute, staticmethod):
|
||||
return attribute.__func__
|
||||
return attribute
|
||||
|
||||
def run_nested_command(self, *arguments: str, parse_json: bool = False) -> Any:
|
||||
"""
|
||||
调用内部 CLI 并按需解析 JSON。
|
||||
|
||||
:param arguments: CLI 参数列表
|
||||
:param parse_json: 是否解析 JSON
|
||||
:return: 内部 CLI 调用结果
|
||||
"""
|
||||
return self.command_runner(*arguments, parse_json=parse_json)
|
||||
|
||||
def exec_nested_command(self, *arguments: str) -> Any:
|
||||
"""
|
||||
直接切换到内部 CLI 的实时执行路径。
|
||||
|
||||
:param arguments: CLI 参数列表
|
||||
:return: 内部 CLI 调用结果
|
||||
"""
|
||||
return self.live_command_runner(*arguments)
|
||||
|
||||
|
||||
class WizardExecutionResultSupport:
|
||||
"""
|
||||
向导执行结果翻译支持对象。
|
||||
|
||||
该对象负责提取内部 CLI 结果的 payload、退出码与错误文本,并构建
|
||||
默认失败负载,让模板基类不再直接持有结果翻译细节。
|
||||
|
||||
:param failure_message: 默认失败提示
|
||||
"""
|
||||
|
||||
def __init__(self, *, failure_message: str) -> None:
|
||||
"""
|
||||
初始化执行结果翻译支持对象。
|
||||
|
||||
:param failure_message: 默认失败提示
|
||||
:return: None
|
||||
"""
|
||||
self.failure_message = failure_message
|
||||
|
||||
@staticmethod
|
||||
def extract_payload(nested_result: Any) -> dict[str, Any] | None:
|
||||
"""
|
||||
提取内部 CLI JSON 负载。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: JSON 负载
|
||||
"""
|
||||
payload = getattr(nested_result, 'payload', None)
|
||||
return payload if isinstance(payload, dict) else None
|
||||
|
||||
@staticmethod
|
||||
def extract_returncode(nested_result: Any) -> int:
|
||||
"""
|
||||
提取内部 CLI 退出码。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: 退出码
|
||||
"""
|
||||
return int(getattr(nested_result, 'returncode', 0) or 0)
|
||||
|
||||
@staticmethod
|
||||
def extract_stdout(nested_result: Any) -> str:
|
||||
"""
|
||||
提取内部 CLI 标准输出。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: 标准输出文本
|
||||
"""
|
||||
return str(getattr(nested_result, 'stdout', '') or '').strip()
|
||||
|
||||
@staticmethod
|
||||
def extract_stderr(nested_result: Any) -> str:
|
||||
"""
|
||||
提取内部 CLI 标准错误。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: 标准错误文本
|
||||
"""
|
||||
return str(getattr(nested_result, 'stderr', '') or '').strip()
|
||||
|
||||
def build_failure_payload(self, nested_result: Any) -> dict[str, Any]:
|
||||
"""
|
||||
为内部 CLI 非 JSON 结果构建失败负载。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: 失败负载
|
||||
"""
|
||||
error_text = self.extract_stderr(nested_result) or self.extract_stdout(nested_result)
|
||||
return {
|
||||
'ok': False,
|
||||
'message': self.failure_message,
|
||||
'error': error_text,
|
||||
'exit_code': self.extract_returncode(nested_result),
|
||||
}
|
||||
|
||||
|
||||
class WizardSupportCache:
|
||||
"""
|
||||
向导协作者缓存。
|
||||
|
||||
该对象负责延迟构建并复用当前 flow 需要的 interaction/result/nested
|
||||
support collaborator,让 `BaseCliWizardFlow` 回到模板骨架与桥接层角色。
|
||||
|
||||
:param owner: 当前向导 flow
|
||||
"""
|
||||
|
||||
def __init__(self, owner: 'BaseCliWizardFlow[Any]') -> None:
|
||||
"""
|
||||
初始化向导协作者缓存。
|
||||
|
||||
:param owner: 当前向导 flow
|
||||
:return: None
|
||||
"""
|
||||
self.owner = owner
|
||||
self._interaction_support: WizardInteractionSupport | None = None
|
||||
self._result_support: WizardResultSupport | None = None
|
||||
self._nested_command_support: WizardNestedCommandSupport | None = None
|
||||
self._execution_result_support: WizardExecutionResultSupport | None = None
|
||||
|
||||
def get_interaction_support(self) -> WizardInteractionSupport:
|
||||
"""
|
||||
返回当前向导使用的交互支持对象。
|
||||
|
||||
:return: 交互支持对象
|
||||
"""
|
||||
if self._interaction_support is None:
|
||||
self._interaction_support = WizardInteractionSupport(
|
||||
context_factory=self.owner.context_factory,
|
||||
prompt_service=self.owner.prompt_service,
|
||||
)
|
||||
return self._interaction_support
|
||||
|
||||
def get_result_support(self) -> WizardResultSupport:
|
||||
"""
|
||||
返回当前向导使用的结果收口支持对象。
|
||||
|
||||
:return: 结果收口支持对象
|
||||
"""
|
||||
if self._result_support is None:
|
||||
self._result_support = WizardResultSupport(
|
||||
execution_service=self.owner.execution_service,
|
||||
preview_renderer=self.owner.preview_renderer,
|
||||
)
|
||||
return self._result_support
|
||||
|
||||
def get_nested_command_support(self) -> WizardNestedCommandSupport:
|
||||
"""
|
||||
返回当前向导使用的内部 CLI 调用支持对象。
|
||||
|
||||
:return: 内部 CLI 调用支持对象
|
||||
"""
|
||||
if self._nested_command_support is None:
|
||||
self._nested_command_support = WizardNestedCommandSupport(
|
||||
command_runner=WizardNestedCommandSupport.resolve_callable_override(
|
||||
type(self.owner), 'nested_command_runner'
|
||||
),
|
||||
live_command_runner=WizardNestedCommandSupport.resolve_callable_override(
|
||||
type(self.owner),
|
||||
'nested_live_command_runner',
|
||||
),
|
||||
)
|
||||
return self._nested_command_support
|
||||
|
||||
def get_execution_result_support(self) -> WizardExecutionResultSupport:
|
||||
"""
|
||||
返回当前向导使用的执行结果翻译支持对象。
|
||||
|
||||
:return: 执行结果翻译支持对象
|
||||
"""
|
||||
if self._execution_result_support is None:
|
||||
self._execution_result_support = WizardExecutionResultSupport(
|
||||
failure_message=self.owner.failure_message,
|
||||
)
|
||||
return self._execution_result_support
|
||||
|
||||
|
||||
class BaseNestedCommandWizardFlow(ABC, Generic[SelectionT]):
|
||||
"""
|
||||
统一封装通过内部 CLI 执行的向导流程。
|
||||
|
||||
子类需要提供参数采集、上下文构建、预览摘要、执行参数和
|
||||
结果收口策略,从而复用向导的通用交互主链路。
|
||||
"""
|
||||
|
||||
wizard_name: str
|
||||
preview_title: str
|
||||
failure_message: str
|
||||
|
||||
def run(self, output: OutputOption = 'text') -> None:
|
||||
"""
|
||||
执行向导主流程。
|
||||
|
||||
:param output: 输出格式
|
||||
:return: None
|
||||
"""
|
||||
selection = self.collect_selection()
|
||||
self.prepare_execution_state(selection, output)
|
||||
ctx = self.prepare_context(selection, output)
|
||||
typer.echo(self.render_preview(selection))
|
||||
if not self.prompt_confirm(self.confirm_prompt(selection), default_value=self.confirm_default_value(selection)):
|
||||
self.complete_result(ctx, self.build_cancel_result())
|
||||
return
|
||||
|
||||
nested_result = self.execute(selection, output)
|
||||
if self.handle_execution_result(ctx, nested_result, output):
|
||||
return
|
||||
payload = self.extract_payload(nested_result)
|
||||
if payload is not None:
|
||||
self.complete_payload(ctx, payload, default_exit_code=self.extract_returncode(nested_result))
|
||||
return
|
||||
self.complete_payload(ctx, self.build_failure_payload(nested_result))
|
||||
|
||||
def render_preview(self, selection: SelectionT) -> str:
|
||||
"""
|
||||
生成向导预览文本。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览文本
|
||||
"""
|
||||
return self.get_preview_renderer().render_preview(
|
||||
self.preview_title,
|
||||
summary=self.build_preview_summary(selection),
|
||||
command=self.build_preview_command(selection),
|
||||
notes=self.build_preview_notes(selection),
|
||||
)
|
||||
|
||||
def confirm_default_value(self, selection: SelectionT) -> bool:
|
||||
"""
|
||||
返回最终确认默认值。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 默认确认值
|
||||
"""
|
||||
del selection
|
||||
return False
|
||||
|
||||
def build_preview_notes(self, selection: SelectionT) -> list[str] | None:
|
||||
"""
|
||||
构建预览附加说明。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览附加说明
|
||||
"""
|
||||
del selection
|
||||
return None
|
||||
|
||||
def prepare_execution_state(self, selection: SelectionT, output: OutputOption) -> None:
|
||||
"""
|
||||
在渲染预览前准备 flow 级执行状态。
|
||||
|
||||
该钩子用于少量需要在预览、确认默认值或执行前共享状态的 flow,
|
||||
例如预先探测只读诊断结果,但不允许子类因此重写整个模板主链路。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: None
|
||||
"""
|
||||
del selection, output
|
||||
|
||||
def handle_execution_result(self, ctx: CliContext, nested_result: Any, output: OutputOption) -> bool:
|
||||
"""
|
||||
允许子类在默认 payload 收口前接管执行结果。
|
||||
|
||||
该钩子适用于需要切换到 live exec、实时终端占用或其他非标准 payload
|
||||
收口路径的少数 flow。返回 `True` 表示结果已处理完成。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param nested_result: 执行结果
|
||||
:param output: 输出格式
|
||||
:return: 是否已完成结果处理
|
||||
"""
|
||||
del ctx, nested_result, output
|
||||
return False
|
||||
|
||||
def build_failure_payload(self, nested_result: Any) -> dict[str, Any]:
|
||||
"""
|
||||
为内部 CLI 非 JSON 结果构建失败负载。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: 失败负载
|
||||
"""
|
||||
return self.get_execution_result_support().build_failure_payload(nested_result)
|
||||
|
||||
@staticmethod
|
||||
def extract_payload(nested_result: Any) -> dict[str, Any] | None:
|
||||
"""
|
||||
提取内部 CLI JSON 负载。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: JSON 负载
|
||||
"""
|
||||
return WizardExecutionResultSupport.extract_payload(nested_result)
|
||||
|
||||
@staticmethod
|
||||
def extract_returncode(nested_result: Any) -> int:
|
||||
"""
|
||||
提取内部 CLI 退出码。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: 退出码
|
||||
"""
|
||||
return WizardExecutionResultSupport.extract_returncode(nested_result)
|
||||
|
||||
@staticmethod
|
||||
def extract_stdout(nested_result: Any) -> str:
|
||||
"""
|
||||
提取内部 CLI 标准输出。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: 标准输出文本
|
||||
"""
|
||||
return WizardExecutionResultSupport.extract_stdout(nested_result)
|
||||
|
||||
@staticmethod
|
||||
def extract_stderr(nested_result: Any) -> str:
|
||||
"""
|
||||
提取内部 CLI 标准错误。
|
||||
|
||||
:param nested_result: 内部 CLI 调用结果
|
||||
:return: 标准错误文本
|
||||
"""
|
||||
return WizardExecutionResultSupport.extract_stderr(nested_result)
|
||||
|
||||
@abstractmethod
|
||||
def collect_selection(self) -> SelectionT:
|
||||
"""
|
||||
采集向导参数。
|
||||
|
||||
:return: 向导采集结果
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def prepare_context(self, selection: SelectionT, output: OutputOption) -> CliContext:
|
||||
"""
|
||||
构建命令上下文。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def build_preview_summary(self, selection: SelectionT) -> dict[str, Any]:
|
||||
"""
|
||||
构建预览摘要。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览摘要
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def build_preview_command(self, selection: SelectionT) -> list[str]:
|
||||
"""
|
||||
构建用户视角命令。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 用户视角命令参数
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def confirm_prompt(self, selection: SelectionT) -> str:
|
||||
"""
|
||||
返回最终确认提示文本。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 确认提示
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def build_execute_arguments(self, selection: SelectionT, output: OutputOption) -> list[str]:
|
||||
"""
|
||||
构建内部 CLI 执行参数。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: 内部 CLI 参数
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def prompt_confirm(self, prompt_text: str, *, default_value: bool) -> bool:
|
||||
"""
|
||||
执行确认交互。
|
||||
|
||||
:param prompt_text: 提示文本
|
||||
:param default_value: 默认值
|
||||
:return: 是否确认
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def complete_result(self, ctx: CliContext, result: CommandResult) -> None:
|
||||
"""
|
||||
输出命令结果对象。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param result: 命令结果
|
||||
:return: None
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def build_cancel_result(self) -> CommandResult:
|
||||
"""
|
||||
构建向导取消结果。
|
||||
|
||||
:return: 命令结果对象
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_preview_renderer(self) -> WizardPreviewRenderer:
|
||||
"""
|
||||
获取当前向导使用的预览渲染器。
|
||||
|
||||
:return: 预览渲染器
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def complete_payload(
|
||||
self,
|
||||
ctx: CliContext,
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
default_exit_code: int = 0,
|
||||
) -> None:
|
||||
"""
|
||||
输出标准负载。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param payload: 结果负载
|
||||
:param default_exit_code: 默认退出码
|
||||
:return: None
|
||||
"""
|
||||
|
||||
def execute(self, selection: SelectionT, output: OutputOption) -> Any:
|
||||
"""
|
||||
执行内部 CLI 命令。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: 内部 CLI 调用结果
|
||||
"""
|
||||
return self.run_nested_command(*self.build_execute_arguments(selection, output), parse_json=True)
|
||||
|
||||
@abstractmethod
|
||||
def run_nested_command(self, *arguments: str, parse_json: bool = False) -> Any:
|
||||
"""
|
||||
调用内部 CLI。
|
||||
|
||||
:param arguments: 参数列表
|
||||
:param parse_json: 是否解析 JSON
|
||||
:return: 内部 CLI 调用结果
|
||||
"""
|
||||
|
||||
|
||||
class BaseCliWizardFlow(BaseNestedCommandWizardFlow[SelectionT], ABC):
|
||||
"""
|
||||
当前项目使用的 CLI 向导基类。
|
||||
|
||||
该基类在通用向导主链路之上继续下沉当前项目稳定复用的桥接逻辑,
|
||||
包括确认交互、上下文构建、命令结果收口与内部 CLI 调用封装。
|
||||
"""
|
||||
|
||||
context_factory: ClassVar[CliContextFactory] = DEFAULT_CORE_SERVICES.context_factory
|
||||
execution_service: ClassVar[CliExecutionService] = DEFAULT_CORE_SERVICES.execution_service
|
||||
prompt_service: ClassVar[WizardPromptService] = WIZARD_PROMPT_SERVICE
|
||||
preview_renderer: ClassVar[WizardPreviewRenderer] = WIZARD_PREVIEW_RENDERER
|
||||
nested_command_runner: ClassVar[Callable[..., Any]] = staticmethod(NESTED_CLI_SUPPORT.run)
|
||||
nested_live_command_runner: ClassVar[Callable[..., Any]] = staticmethod(NESTED_CLI_SUPPORT.exec)
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
初始化当前项目向导基类。
|
||||
|
||||
该基类通过协作者缓存稳定持有交互、结果收口和内部 CLI 调用 support,
|
||||
避免模板方法执行过程中重复构建同类对象。
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self._support_cache: WizardSupportCache | None = None
|
||||
|
||||
def get_support_cache(self) -> WizardSupportCache:
|
||||
"""
|
||||
返回当前向导使用的协作者缓存。
|
||||
|
||||
保持惰性创建,以兼容未显式调用基类 `__init__` 的现有 flow。
|
||||
|
||||
:return: 协作者缓存
|
||||
"""
|
||||
support_cache = getattr(self, '_support_cache', None)
|
||||
if support_cache is None:
|
||||
self._support_cache = WizardSupportCache(self)
|
||||
return self._support_cache
|
||||
|
||||
def get_interaction_support(self) -> WizardInteractionSupport:
|
||||
"""
|
||||
返回当前向导使用的交互支持对象。
|
||||
|
||||
:return: 交互支持对象
|
||||
"""
|
||||
return self.get_support_cache().get_interaction_support()
|
||||
|
||||
def get_result_support(self) -> WizardResultSupport:
|
||||
"""
|
||||
返回当前向导使用的结果收口支持对象。
|
||||
|
||||
:return: 结果收口支持对象
|
||||
"""
|
||||
return self.get_support_cache().get_result_support()
|
||||
|
||||
def get_nested_command_support(self) -> WizardNestedCommandSupport:
|
||||
"""
|
||||
返回当前向导使用的内部 CLI 调用支持对象。
|
||||
|
||||
:return: 内部 CLI 调用支持对象
|
||||
"""
|
||||
return self.get_support_cache().get_nested_command_support()
|
||||
|
||||
def get_execution_result_support(self) -> WizardExecutionResultSupport:
|
||||
"""
|
||||
返回当前向导使用的执行结果翻译支持对象。
|
||||
|
||||
:return: 执行结果翻译支持对象
|
||||
"""
|
||||
return self.get_support_cache().get_execution_result_support()
|
||||
|
||||
def build_regular_context(
|
||||
self,
|
||||
env: str,
|
||||
output: OutputOption,
|
||||
*,
|
||||
allow_prod: bool,
|
||||
yes: bool,
|
||||
dry_run: bool,
|
||||
) -> CliContext:
|
||||
"""
|
||||
构建普通可写命令上下文。
|
||||
|
||||
:param env: 运行环境
|
||||
:param output: 输出格式
|
||||
:param allow_prod: 是否允许生产环境执行
|
||||
:param yes: 是否跳过确认
|
||||
:param dry_run: 是否执行预演
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
return self.get_interaction_support().build_regular_context(
|
||||
env,
|
||||
output,
|
||||
allow_prod=allow_prod,
|
||||
yes=yes,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
|
||||
def build_readonly_context(self, env: str, output: OutputOption) -> CliContext:
|
||||
"""
|
||||
构建只读命令上下文。
|
||||
|
||||
:param env: 运行环境
|
||||
:param output: 输出格式
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
return self.get_interaction_support().build_readonly_context(env, output)
|
||||
|
||||
def prompt_confirm(self, prompt_text: str, *, default_value: bool) -> bool:
|
||||
"""
|
||||
执行标准确认交互。
|
||||
|
||||
:param prompt_text: 提示文本
|
||||
:param default_value: 默认值
|
||||
:return: 是否确认
|
||||
"""
|
||||
return self.get_interaction_support().prompt_confirm(prompt_text, default_value=default_value)
|
||||
|
||||
def complete_result(self, ctx: CliContext, result: CommandResult) -> None:
|
||||
"""
|
||||
输出命令结果对象。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param result: 命令结果对象
|
||||
:return: None
|
||||
"""
|
||||
self.get_result_support().complete_result(ctx, result)
|
||||
|
||||
def complete_payload(
|
||||
self,
|
||||
ctx: CliContext,
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
default_exit_code: int = 0,
|
||||
) -> None:
|
||||
"""
|
||||
输出标准结果负载。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param payload: 标准结果负载
|
||||
:param default_exit_code: 默认退出码
|
||||
:return: None
|
||||
"""
|
||||
self.get_result_support().complete_payload(ctx, payload, default_exit_code=default_exit_code)
|
||||
|
||||
def complete_payload_result(
|
||||
self,
|
||||
ctx: CliContext,
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
text_builder: Callable[[dict[str, Any]], Any] | None = None,
|
||||
default_exit_code: int = 0,
|
||||
text_condition: Callable[[dict[str, Any]], bool] | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
统一按输出格式收口标准结果负载。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param payload: 标准结果负载
|
||||
:param text_builder: 文本结果构建函数
|
||||
:param default_exit_code: 默认退出码
|
||||
:param text_condition: 文本模式结果构建判定函数
|
||||
:return: None
|
||||
"""
|
||||
self.get_result_support().complete_payload_result(
|
||||
ctx,
|
||||
payload,
|
||||
text_builder=text_builder,
|
||||
default_exit_code=default_exit_code,
|
||||
text_condition=text_condition,
|
||||
)
|
||||
|
||||
def build_cancel_result(self) -> CommandResult:
|
||||
"""
|
||||
构建统一的向导取消结果。
|
||||
|
||||
:return: 命令结果对象
|
||||
"""
|
||||
return self.get_result_support().build_cancel_result(self.wizard_name)
|
||||
|
||||
def get_preview_renderer(self) -> WizardPreviewRenderer:
|
||||
"""
|
||||
返回当前项目默认的向导预览渲染器。
|
||||
|
||||
:return: 预览渲染器
|
||||
"""
|
||||
return self.get_result_support().get_preview_renderer()
|
||||
|
||||
def run_nested_command(self, *arguments: str, parse_json: bool = False) -> Any:
|
||||
"""
|
||||
调用内部 CLI 并按需解析 JSON。
|
||||
|
||||
:param arguments: CLI 参数列表
|
||||
:param parse_json: 是否解析 JSON
|
||||
:return: 内部 CLI 调用结果
|
||||
"""
|
||||
return self.get_nested_command_support().run_nested_command(*arguments, parse_json=parse_json)
|
||||
|
||||
def exec_nested_command(self, *arguments: str) -> Any:
|
||||
"""
|
||||
直接切换到内部 CLI 的实时执行路径。
|
||||
|
||||
:param arguments: CLI 参数列表
|
||||
:return: 内部 CLI 调用结果
|
||||
"""
|
||||
return self.get_nested_command_support().exec_nested_command(*arguments)
|
||||
|
||||
|
||||
class BaseLiveExecCliWizardFlow(BaseCliWizardFlow[SelectionT], ABC):
|
||||
"""
|
||||
统一封装“确认后切换到实时终端执行路径”的 CLI 向导基类。
|
||||
|
||||
该特化基类用于少量不走 JSON payload 收口,而是在确认后直接
|
||||
切换到现有命令实时执行链路的向导场景,例如 `wizard app-run`。
|
||||
它在保留预览与确认模板骨架的前提下,显式表达“执行阶段即完成收口”,
|
||||
避免子类再通过覆写 `complete_payload()` 或 `handle_execution_result()`
|
||||
实现规避式短路。
|
||||
"""
|
||||
|
||||
def execute(self, selection: SelectionT, output: OutputOption) -> None:
|
||||
"""
|
||||
切换到内部 CLI 的实时执行路径。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: None
|
||||
"""
|
||||
self.exec_nested_command(*self.build_execute_arguments(selection, output))
|
||||
|
||||
def handle_execution_result(self, ctx: CliContext, nested_result: Any, output: OutputOption) -> bool:
|
||||
"""
|
||||
标记实时执行路径已完成结果收口。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param nested_result: 执行结果
|
||||
:param output: 输出格式
|
||||
:return: 是否已完成结果处理
|
||||
"""
|
||||
del ctx, nested_result, output
|
||||
return True
|
||||
@@ -0,0 +1,316 @@
|
||||
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()
|
||||
@@ -0,0 +1,152 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from cli.context import CliContext
|
||||
from cli.wizard.base import BaseLiveExecCliWizardFlow
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AppRunWizardSelection:
|
||||
"""
|
||||
`wizard app-run` 向导采集结果。
|
||||
|
||||
:param env: 运行环境
|
||||
:param run_doctor: 启动前是否先执行应用检查
|
||||
"""
|
||||
|
||||
env: str
|
||||
run_doctor: bool
|
||||
|
||||
|
||||
class AppRunWizardFlow(BaseLiveExecCliWizardFlow[AppRunWizardSelection]):
|
||||
"""
|
||||
`wizard app-run` 向导流程。
|
||||
|
||||
:param doctor_payload: 启动前检查结果缓存
|
||||
"""
|
||||
|
||||
wizard_name = 'wizard app-run'
|
||||
preview_title = 'wizard app-run preview'
|
||||
failure_message = '应用启动向导执行失败'
|
||||
|
||||
def collect_selection(self) -> AppRunWizardSelection:
|
||||
"""
|
||||
采集应用启动向导参数。
|
||||
|
||||
:return: 向导采集结果
|
||||
"""
|
||||
env = self.prompt_service.prompt_env('dev')
|
||||
run_doctor = self.prompt_service.prompt_confirm('启动前是否先执行应用检查', default_value=True)
|
||||
return AppRunWizardSelection(env=env, run_doctor=run_doctor)
|
||||
|
||||
def prepare_context(self, selection: AppRunWizardSelection, output: str) -> CliContext:
|
||||
"""
|
||||
构建应用启动向导上下文。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
del output
|
||||
return self.build_readonly_context(selection.env, 'text')
|
||||
|
||||
def build_preview_summary(self, selection: AppRunWizardSelection) -> dict[str, Any]:
|
||||
"""
|
||||
构建应用启动预览摘要。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览摘要
|
||||
"""
|
||||
return {
|
||||
'env': selection.env,
|
||||
'run_doctor': str(selection.run_doctor).lower(),
|
||||
'doctor_ok': '-' if self.doctor_payload is None else str(self.doctor_payload.get('ok', False)).lower(),
|
||||
}
|
||||
|
||||
def build_preview_command(self, selection: AppRunWizardSelection) -> list[str]:
|
||||
"""
|
||||
构建应用启动预览命令。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 用户视角命令参数
|
||||
"""
|
||||
return ['shierfeng', 'app', 'run', f'--env={selection.env}']
|
||||
|
||||
def build_preview_notes(self, selection: AppRunWizardSelection) -> list[str] | None:
|
||||
"""
|
||||
构建应用启动预览附加说明。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览附加说明
|
||||
"""
|
||||
del selection
|
||||
notes: list[str] = []
|
||||
if isinstance(self.doctor_payload, dict) and not self.doctor_payload.get('ok', False):
|
||||
notes.append(self.doctor_payload.get('message', '启动前检查未通过'))
|
||||
return notes or None
|
||||
|
||||
def confirm_prompt(self, selection: AppRunWizardSelection) -> str:
|
||||
"""
|
||||
返回应用启动最终确认提示。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 确认提示
|
||||
"""
|
||||
del selection
|
||||
return '确认启动应用吗'
|
||||
|
||||
def confirm_default_value(self, selection: AppRunWizardSelection) -> bool:
|
||||
"""
|
||||
返回应用启动确认默认值。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 默认确认值
|
||||
"""
|
||||
del selection
|
||||
return bool(self.doctor_payload is None or self.doctor_payload.get('ok', False))
|
||||
|
||||
def build_execute_arguments(self, selection: AppRunWizardSelection, output: str) -> list[str]:
|
||||
"""
|
||||
构建应用启动内部 CLI 参数。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: 内部 CLI 参数
|
||||
"""
|
||||
del output
|
||||
return ['app', 'run', f'--env={selection.env}']
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
初始化应用启动向导流程。
|
||||
|
||||
:return: None
|
||||
"""
|
||||
super().__init__()
|
||||
self.doctor_payload: dict[str, Any] | None = None
|
||||
|
||||
def prepare_execution_state(self, selection: AppRunWizardSelection, output: str) -> None:
|
||||
"""
|
||||
在预览与确认前准备启动前检查结果。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: None
|
||||
"""
|
||||
del output
|
||||
self.doctor_payload = None
|
||||
if selection.run_doctor:
|
||||
doctor_result = self.run_nested_command(
|
||||
'app', 'doctor', f'--env={selection.env}', '--output=json', parse_json=True
|
||||
)
|
||||
payload = getattr(doctor_result, 'payload', None)
|
||||
self.doctor_payload = payload if isinstance(payload, dict) else None
|
||||
|
||||
|
||||
def run_app_run_wizard() -> None:
|
||||
"""
|
||||
执行 `wizard app-run` 向导。
|
||||
|
||||
:return: None
|
||||
"""
|
||||
AppRunWizardFlow().run('text')
|
||||
@@ -0,0 +1,213 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cli.context import CliContext, OutputOption
|
||||
from cli.wizard.base import BaseCliWizardFlow
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CacheClearWizardSelection:
|
||||
"""
|
||||
`wizard cache-clear` 向导采集结果。
|
||||
|
||||
:param env: 运行环境
|
||||
:param mode: 清理模式
|
||||
:param cache_name: 缓存名称前缀
|
||||
:param cache_key: 缓存键名关键字
|
||||
:param dry_run: 是否演练执行
|
||||
:param allow_prod: 是否允许生产环境执行
|
||||
"""
|
||||
|
||||
env: str
|
||||
mode: str
|
||||
cache_name: str
|
||||
cache_key: str
|
||||
dry_run: bool
|
||||
allow_prod: bool
|
||||
|
||||
|
||||
class CacheClearWizardFlow(BaseCliWizardFlow[CacheClearWizardSelection]):
|
||||
"""
|
||||
`wizard cache-clear` 向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_mode: 默认清理模式
|
||||
:param default_cache_name: 默认缓存名称前缀
|
||||
:param default_cache_key: 默认缓存键名关键字
|
||||
:param default_dry_run: 默认是否先执行 dry-run
|
||||
"""
|
||||
|
||||
wizard_name = 'wizard cache-clear'
|
||||
preview_title = 'wizard cache-clear preview'
|
||||
failure_message = '缓存清理向导执行失败'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
default_env: str = 'dev',
|
||||
default_mode: str = 'cache-name',
|
||||
default_cache_name: str = '',
|
||||
default_cache_key: str = '',
|
||||
default_dry_run: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
初始化缓存清理向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_mode: 默认清理模式
|
||||
:param default_cache_name: 默认缓存名称前缀
|
||||
:param default_cache_key: 默认缓存键名关键字
|
||||
:param default_dry_run: 默认是否先执行 dry-run
|
||||
:return: None
|
||||
"""
|
||||
self.default_env = default_env
|
||||
self.default_mode = default_mode
|
||||
self.default_cache_name = default_cache_name
|
||||
self.default_cache_key = default_cache_key
|
||||
self.default_dry_run = default_dry_run
|
||||
|
||||
def collect_selection(self) -> CacheClearWizardSelection:
|
||||
"""
|
||||
采集缓存清理向导参数。
|
||||
|
||||
:return: 向导采集结果
|
||||
"""
|
||||
env = self.prompt_service.prompt_env(self.default_env)
|
||||
mode = self.prompt_service.prompt_choice(
|
||||
'清理模式',
|
||||
['cache-name', 'cache-key', 'all'],
|
||||
self.default_mode,
|
||||
)
|
||||
cache_name = ''
|
||||
cache_key = ''
|
||||
if mode == 'cache-name':
|
||||
cache_name = self.prompt_service.prompt_required_text('缓存名称前缀', self.default_cache_name)
|
||||
elif mode == 'cache-key':
|
||||
cache_key = self.prompt_service.prompt_required_text('缓存键名关键字', self.default_cache_key)
|
||||
else:
|
||||
self.prompt_service.prompt_optional_text('当前将清理全部缓存,按回车继续', '')
|
||||
dry_run = self.prompt_service.prompt_confirm('是否先执行 dry-run 预演', default_value=self.default_dry_run)
|
||||
allow_prod = (
|
||||
self.prompt_service.prompt_confirm('当前为 prod 环境,是否允许继续执行', default_value=False)
|
||||
if env == 'prod'
|
||||
else False
|
||||
)
|
||||
return CacheClearWizardSelection(
|
||||
env=env,
|
||||
mode=mode,
|
||||
cache_name=cache_name,
|
||||
cache_key=cache_key,
|
||||
dry_run=dry_run,
|
||||
allow_prod=allow_prod,
|
||||
)
|
||||
|
||||
def prepare_context(self, selection: CacheClearWizardSelection, output: OutputOption) -> CliContext:
|
||||
"""
|
||||
构建缓存清理向导上下文。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
return self.build_regular_context(
|
||||
selection.env,
|
||||
output,
|
||||
allow_prod=selection.allow_prod,
|
||||
yes=True,
|
||||
dry_run=selection.dry_run,
|
||||
)
|
||||
|
||||
def build_preview_summary(self, selection: CacheClearWizardSelection) -> dict[str, str]:
|
||||
"""
|
||||
构建缓存清理预览摘要。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览摘要
|
||||
"""
|
||||
return {
|
||||
'env': selection.env,
|
||||
'mode': selection.mode,
|
||||
'cache_name': selection.cache_name or '-',
|
||||
'cache_key': selection.cache_key or '-',
|
||||
'dry_run': str(selection.dry_run).lower(),
|
||||
'allow_prod': str(selection.allow_prod).lower(),
|
||||
}
|
||||
|
||||
def build_preview_command(self, selection: CacheClearWizardSelection) -> list[str]:
|
||||
"""
|
||||
构建缓存清理预览命令。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 用户视角命令参数
|
||||
"""
|
||||
command = ['shierfeng', 'cache', 'clear', f'--env={selection.env}']
|
||||
if selection.mode == 'cache-name':
|
||||
command.append(f'--cache-name={selection.cache_name}')
|
||||
elif selection.mode == 'cache-key':
|
||||
command.append(f'--cache-key={selection.cache_key}')
|
||||
else:
|
||||
command.append('--all')
|
||||
if selection.dry_run:
|
||||
command.append('--dry-run')
|
||||
if selection.allow_prod:
|
||||
command.append('--allow-prod')
|
||||
return command
|
||||
|
||||
def confirm_prompt(self, selection: CacheClearWizardSelection) -> str:
|
||||
"""
|
||||
返回缓存清理最终确认提示。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 确认提示
|
||||
"""
|
||||
del selection
|
||||
return '确认执行缓存清理向导吗'
|
||||
|
||||
def build_execute_arguments(self, selection: CacheClearWizardSelection, output: OutputOption) -> list[str]:
|
||||
"""
|
||||
构建缓存清理内部 CLI 参数。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: 内部 CLI 参数
|
||||
"""
|
||||
arguments = ['cache', 'clear', f'--env={selection.env}', f'--output={output}', '--yes']
|
||||
if selection.mode == 'cache-name':
|
||||
arguments.append(f'--cache-name={selection.cache_name}')
|
||||
elif selection.mode == 'cache-key':
|
||||
arguments.append(f'--cache-key={selection.cache_key}')
|
||||
else:
|
||||
arguments.append('--all')
|
||||
if selection.dry_run:
|
||||
arguments.append('--dry-run')
|
||||
if selection.allow_prod:
|
||||
arguments.append('--allow-prod')
|
||||
return arguments
|
||||
|
||||
|
||||
def run_cache_clear_wizard(
|
||||
output: OutputOption = 'text',
|
||||
*,
|
||||
default_env: str = 'dev',
|
||||
default_mode: str = 'cache-name',
|
||||
default_cache_name: str = '',
|
||||
default_cache_key: str = '',
|
||||
default_dry_run: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
执行 `wizard cache-clear` 向导。
|
||||
|
||||
:param output: 输出格式
|
||||
:param default_env: 默认环境
|
||||
:param default_mode: 默认清理模式
|
||||
:param default_cache_name: 默认缓存名称前缀
|
||||
:param default_cache_key: 默认缓存键关键字
|
||||
:param default_dry_run: 默认是否执行 dry-run
|
||||
:return: None
|
||||
"""
|
||||
CacheClearWizardFlow(
|
||||
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,
|
||||
).run(output)
|
||||
@@ -0,0 +1,169 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cli.context import CliContext, OutputOption
|
||||
from cli.wizard.base import BaseCliWizardFlow
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DbUpgradeWizardSelection:
|
||||
"""
|
||||
`wizard db-upgrade` 向导采集结果。
|
||||
|
||||
:param env: 运行环境
|
||||
:param revision: 目标迁移版本
|
||||
:param dry_run: 是否演练执行
|
||||
:param allow_prod: 是否允许生产环境执行
|
||||
"""
|
||||
|
||||
env: str
|
||||
revision: str
|
||||
dry_run: bool
|
||||
allow_prod: bool
|
||||
|
||||
|
||||
class DbUpgradeWizardFlow(BaseCliWizardFlow[DbUpgradeWizardSelection]):
|
||||
"""
|
||||
`wizard db-upgrade` 向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_revision: 默认目标 revision
|
||||
:param default_dry_run: 默认是否先执行 dry-run
|
||||
"""
|
||||
|
||||
wizard_name = 'wizard db-upgrade'
|
||||
preview_title = 'wizard db-upgrade preview'
|
||||
failure_message = '数据库升级向导执行失败'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
default_env: str = 'dev',
|
||||
default_revision: str = 'head',
|
||||
default_dry_run: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
初始化数据库升级向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_revision: 默认目标 revision
|
||||
:param default_dry_run: 默认是否先执行 dry-run
|
||||
:return: None
|
||||
"""
|
||||
self.default_env = default_env
|
||||
self.default_revision = default_revision
|
||||
self.default_dry_run = default_dry_run
|
||||
|
||||
def collect_selection(self) -> DbUpgradeWizardSelection:
|
||||
"""
|
||||
采集数据库升级向导参数。
|
||||
|
||||
:return: 向导采集结果
|
||||
"""
|
||||
env = self.prompt_service.prompt_env(self.default_env)
|
||||
revision = self.prompt_service.prompt_required_text('目标迁移版本', self.default_revision)
|
||||
dry_run = self.prompt_service.prompt_confirm('是否先执行 dry-run 预演', default_value=self.default_dry_run)
|
||||
allow_prod = (
|
||||
self.prompt_service.prompt_confirm('当前为 prod 环境,是否允许继续执行', default_value=False)
|
||||
if env == 'prod'
|
||||
else False
|
||||
)
|
||||
return DbUpgradeWizardSelection(env=env, revision=revision, dry_run=dry_run, allow_prod=allow_prod)
|
||||
|
||||
def prepare_context(self, selection: DbUpgradeWizardSelection, output: OutputOption) -> CliContext:
|
||||
"""
|
||||
构建数据库升级向导上下文。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
return self.build_regular_context(
|
||||
selection.env,
|
||||
output,
|
||||
allow_prod=selection.allow_prod,
|
||||
yes=True,
|
||||
dry_run=selection.dry_run,
|
||||
)
|
||||
|
||||
def build_preview_summary(self, selection: DbUpgradeWizardSelection) -> dict[str, str]:
|
||||
"""
|
||||
构建数据库升级预览摘要。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览摘要
|
||||
"""
|
||||
return {
|
||||
'env': selection.env,
|
||||
'revision': selection.revision,
|
||||
'dry_run': str(selection.dry_run).lower(),
|
||||
'allow_prod': str(selection.allow_prod).lower(),
|
||||
}
|
||||
|
||||
def build_preview_command(self, selection: DbUpgradeWizardSelection) -> list[str]:
|
||||
"""
|
||||
构建数据库升级预览命令。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 用户视角命令参数
|
||||
"""
|
||||
command = ['shierfeng', 'db', 'upgrade', f'--env={selection.env}', f'--revision={selection.revision}']
|
||||
if selection.dry_run:
|
||||
command.append('--dry-run')
|
||||
if selection.allow_prod:
|
||||
command.append('--allow-prod')
|
||||
return command
|
||||
|
||||
def confirm_prompt(self, selection: DbUpgradeWizardSelection) -> str:
|
||||
"""
|
||||
返回数据库升级最终确认提示。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 确认提示
|
||||
"""
|
||||
del selection
|
||||
return '确认执行数据库升级向导吗'
|
||||
|
||||
def build_execute_arguments(self, selection: DbUpgradeWizardSelection, output: OutputOption) -> list[str]:
|
||||
"""
|
||||
构建数据库升级内部 CLI 参数。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: 内部 CLI 参数
|
||||
"""
|
||||
arguments = [
|
||||
'db',
|
||||
'upgrade',
|
||||
f'--env={selection.env}',
|
||||
f'--output={output}',
|
||||
f'--revision={selection.revision}',
|
||||
'--yes',
|
||||
]
|
||||
if selection.dry_run:
|
||||
arguments.append('--dry-run')
|
||||
if selection.allow_prod:
|
||||
arguments.append('--allow-prod')
|
||||
return arguments
|
||||
|
||||
|
||||
def run_db_upgrade_wizard(
|
||||
output: OutputOption = 'text',
|
||||
*,
|
||||
default_env: str = 'dev',
|
||||
default_revision: str = 'head',
|
||||
default_dry_run: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
执行 `wizard db-upgrade` 向导。
|
||||
|
||||
:param output: 输出格式
|
||||
:param default_env: 默认环境
|
||||
:param default_revision: 默认目标 revision
|
||||
:param default_dry_run: 默认是否先 dry-run
|
||||
:return: None
|
||||
"""
|
||||
DbUpgradeWizardFlow(
|
||||
default_env=default_env,
|
||||
default_revision=default_revision,
|
||||
default_dry_run=default_dry_run,
|
||||
).run(output)
|
||||
@@ -0,0 +1,239 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import typer
|
||||
|
||||
from cli.context import CliContext, OutputOption
|
||||
from cli.wizard.base import BaseCliWizardFlow
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GenExportWizardSelection:
|
||||
"""
|
||||
`wizard gen-export` 向导采集结果。
|
||||
|
||||
:param env: 运行环境
|
||||
:param table_names: 业务表名称列表
|
||||
:param mode: 导出模式
|
||||
:param output_file: zip 导出目标文件路径
|
||||
:param dry_run: 是否演练执行
|
||||
:param allow_prod: 是否允许生产环境执行
|
||||
"""
|
||||
|
||||
env: str
|
||||
table_names: list[str]
|
||||
mode: str
|
||||
output_file: str
|
||||
dry_run: bool
|
||||
allow_prod: bool
|
||||
|
||||
|
||||
class GenExportWizardFlow(BaseCliWizardFlow[GenExportWizardSelection]):
|
||||
"""
|
||||
`wizard gen-export` 向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_table_names: 默认业务表名称列表文本
|
||||
:param default_mode: 默认导出模式
|
||||
:param default_output_file: 默认 zip 导出目标文件路径
|
||||
:param default_dry_run: 默认是否先执行 dry-run
|
||||
"""
|
||||
|
||||
wizard_name = 'wizard gen-export'
|
||||
preview_title = 'wizard gen-export preview'
|
||||
failure_message = '代码导出向导执行失败'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
default_env: str = 'dev',
|
||||
default_table_names: str = '',
|
||||
default_mode: str = 'zip',
|
||||
default_output_file: str = '',
|
||||
default_dry_run: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
初始化代码导出向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_table_names: 默认业务表名称列表文本
|
||||
:param default_mode: 默认导出模式
|
||||
:param default_output_file: 默认 zip 导出目标文件路径
|
||||
:param default_dry_run: 默认是否先执行 dry-run
|
||||
:return: None
|
||||
"""
|
||||
self.default_env = default_env
|
||||
self.default_table_names = default_table_names
|
||||
self.default_mode = default_mode
|
||||
self.default_output_file = default_output_file
|
||||
self.default_dry_run = default_dry_run
|
||||
|
||||
def collect_selection(self) -> GenExportWizardSelection:
|
||||
"""
|
||||
采集代码导出向导参数。
|
||||
|
||||
:return: 向导采集结果
|
||||
"""
|
||||
env = self.prompt_service.prompt_env(self.default_env)
|
||||
raw_table_names = self.prompt_service.prompt_required_text(
|
||||
'业务表名称列表(多个表请使用逗号分隔)',
|
||||
self.default_table_names,
|
||||
)
|
||||
table_names = self.parse_table_names(raw_table_names)
|
||||
while not table_names:
|
||||
typer.echo('至少需要输入一个业务表名称,请重新输入。')
|
||||
raw_table_names = self.prompt_service.prompt_required_text(
|
||||
'业务表名称列表(多个表请使用逗号分隔)',
|
||||
self.default_table_names,
|
||||
)
|
||||
table_names = self.parse_table_names(raw_table_names)
|
||||
|
||||
mode = self.prompt_service.prompt_choice('导出模式', ['zip', 'local'], self.default_mode)
|
||||
output_file = ''
|
||||
if mode == 'zip':
|
||||
output_file = self.prompt_service.prompt_optional_text(
|
||||
'zip 导出目标文件路径(留空则使用默认文件名)',
|
||||
self.default_output_file,
|
||||
)
|
||||
dry_run = self.prompt_service.prompt_confirm('是否先执行 dry-run 预演', default_value=self.default_dry_run)
|
||||
allow_prod = (
|
||||
self.prompt_service.prompt_confirm('当前为 prod 环境,是否允许继续执行', default_value=False)
|
||||
if env == 'prod'
|
||||
else False
|
||||
)
|
||||
return GenExportWizardSelection(
|
||||
env=env,
|
||||
table_names=table_names,
|
||||
mode=mode,
|
||||
output_file=output_file,
|
||||
dry_run=dry_run,
|
||||
allow_prod=allow_prod,
|
||||
)
|
||||
|
||||
def prepare_context(self, selection: GenExportWizardSelection, output: OutputOption) -> CliContext:
|
||||
"""
|
||||
构建代码导出向导上下文。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
return self.build_regular_context(
|
||||
selection.env,
|
||||
output,
|
||||
allow_prod=selection.allow_prod,
|
||||
yes=True,
|
||||
dry_run=selection.dry_run,
|
||||
)
|
||||
|
||||
def build_preview_summary(self, selection: GenExportWizardSelection) -> dict[str, str]:
|
||||
"""
|
||||
构建代码导出预览摘要。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览摘要
|
||||
"""
|
||||
return {
|
||||
'env': selection.env,
|
||||
'table_names': ','.join(selection.table_names),
|
||||
'mode': selection.mode,
|
||||
'output_file': selection.output_file or '-',
|
||||
'dry_run': str(selection.dry_run).lower(),
|
||||
'allow_prod': str(selection.allow_prod).lower(),
|
||||
}
|
||||
|
||||
def build_preview_command(self, selection: GenExportWizardSelection) -> list[str]:
|
||||
"""
|
||||
构建代码导出预览命令。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 用户视角命令参数
|
||||
"""
|
||||
command = [
|
||||
'shierfeng',
|
||||
'gen',
|
||||
'export',
|
||||
*selection.table_names,
|
||||
f'--env={selection.env}',
|
||||
f'--mode={selection.mode}',
|
||||
]
|
||||
if selection.output_file:
|
||||
command.append(f'--output-file={selection.output_file}')
|
||||
if selection.dry_run:
|
||||
command.append('--dry-run')
|
||||
if selection.allow_prod:
|
||||
command.append('--allow-prod')
|
||||
return command
|
||||
|
||||
def confirm_prompt(self, selection: GenExportWizardSelection) -> str:
|
||||
"""
|
||||
返回代码导出最终确认提示。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 确认提示
|
||||
"""
|
||||
del selection
|
||||
return '确认执行代码导出向导吗'
|
||||
|
||||
def build_execute_arguments(self, selection: GenExportWizardSelection, output: OutputOption) -> list[str]:
|
||||
"""
|
||||
构建代码导出内部 CLI 参数。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: 内部 CLI 参数
|
||||
"""
|
||||
arguments = [
|
||||
'gen',
|
||||
'export',
|
||||
*selection.table_names,
|
||||
f'--env={selection.env}',
|
||||
f'--output={output}',
|
||||
f'--mode={selection.mode}',
|
||||
'--yes',
|
||||
]
|
||||
if selection.output_file:
|
||||
arguments.append(f'--output-file={selection.output_file}')
|
||||
if selection.dry_run:
|
||||
arguments.append('--dry-run')
|
||||
if selection.allow_prod:
|
||||
arguments.append('--allow-prod')
|
||||
return arguments
|
||||
|
||||
@staticmethod
|
||||
def parse_table_names(raw_value: str) -> list[str]:
|
||||
"""
|
||||
将逗号分隔的业务表名称文本解析为列表。
|
||||
|
||||
:param raw_value: 原始输入文本
|
||||
:return: 去空白后的业务表名称列表
|
||||
"""
|
||||
return [table_name.strip() for table_name in raw_value.split(',') if table_name.strip()]
|
||||
|
||||
|
||||
def run_gen_export_wizard(
|
||||
output: OutputOption = 'text',
|
||||
*,
|
||||
default_env: str = 'dev',
|
||||
default_table_names: str = '',
|
||||
default_mode: str = 'zip',
|
||||
default_output_file: str = '',
|
||||
default_dry_run: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
执行 `wizard gen-export` 向导。
|
||||
|
||||
:param output: 输出格式
|
||||
:param default_env: 默认环境
|
||||
:param default_table_names: 默认业务表名称列表文本
|
||||
:param default_mode: 默认导出模式
|
||||
:param default_output_file: 默认 zip 导出目标文件路径
|
||||
:param default_dry_run: 默认是否先 dry-run
|
||||
:return: None
|
||||
"""
|
||||
GenExportWizardFlow(
|
||||
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,
|
||||
).run(output)
|
||||
@@ -0,0 +1,208 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import typer
|
||||
|
||||
from cli.context import CliContext, OutputOption
|
||||
from cli.wizard.base import BaseCliWizardFlow
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GenImportWizardSelection:
|
||||
"""
|
||||
`wizard gen-import` 向导采集结果。
|
||||
|
||||
:param env: 运行环境
|
||||
:param table_names: 待导入物理表名称列表
|
||||
:param dry_run: 是否演练执行
|
||||
:param allow_prod: 是否允许生产环境执行
|
||||
"""
|
||||
|
||||
env: str
|
||||
table_names: list[str]
|
||||
dry_run: bool
|
||||
allow_prod: bool
|
||||
|
||||
|
||||
class GenImportWizardFlow(BaseCliWizardFlow[GenImportWizardSelection]):
|
||||
"""
|
||||
`wizard gen-import` 向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_table_names: 默认物理表名称列表文本
|
||||
:param default_dry_run: 默认是否先执行 dry-run
|
||||
"""
|
||||
|
||||
wizard_name = 'wizard gen-import'
|
||||
preview_title = 'wizard gen-import preview'
|
||||
failure_message = '代码生成导入向导执行失败'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
default_env: str = 'dev',
|
||||
default_table_names: str = '',
|
||||
default_dry_run: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
初始化代码生成导入向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_table_names: 默认物理表名称列表文本
|
||||
:param default_dry_run: 默认是否先执行 dry-run
|
||||
:return: None
|
||||
"""
|
||||
self.default_env = default_env
|
||||
self.default_table_names = default_table_names
|
||||
self.default_dry_run = default_dry_run
|
||||
|
||||
def collect_selection(self) -> GenImportWizardSelection:
|
||||
"""
|
||||
采集代码生成导入向导参数。
|
||||
|
||||
:return: 向导采集结果
|
||||
"""
|
||||
env = self.prompt_service.prompt_env(self.default_env)
|
||||
raw_table_names = self.prompt_service.prompt_required_text(
|
||||
'物理表名称列表(多个表请使用逗号分隔)',
|
||||
self.default_table_names,
|
||||
)
|
||||
table_names = self.parse_table_names(raw_table_names)
|
||||
while not table_names:
|
||||
typer.echo('至少需要输入一个物理表名称,请重新输入。')
|
||||
raw_table_names = self.prompt_service.prompt_required_text(
|
||||
'物理表名称列表(多个表请使用逗号分隔)',
|
||||
self.default_table_names,
|
||||
)
|
||||
table_names = self.parse_table_names(raw_table_names)
|
||||
|
||||
dry_run = self.prompt_service.prompt_confirm('是否先执行 dry-run 预演', default_value=self.default_dry_run)
|
||||
allow_prod = (
|
||||
self.prompt_service.prompt_confirm('当前为 prod 环境,是否允许继续执行', default_value=False)
|
||||
if env == 'prod'
|
||||
else False
|
||||
)
|
||||
return GenImportWizardSelection(
|
||||
env=env,
|
||||
table_names=table_names,
|
||||
dry_run=dry_run,
|
||||
allow_prod=allow_prod,
|
||||
)
|
||||
|
||||
def prepare_context(self, selection: GenImportWizardSelection, output: OutputOption) -> CliContext:
|
||||
"""
|
||||
构建代码生成导入向导上下文。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
return self.build_regular_context(
|
||||
selection.env,
|
||||
output,
|
||||
allow_prod=selection.allow_prod,
|
||||
yes=True,
|
||||
dry_run=selection.dry_run,
|
||||
)
|
||||
|
||||
def build_preview_summary(self, selection: GenImportWizardSelection) -> dict[str, str]:
|
||||
"""
|
||||
构建代码生成导入预览摘要。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览摘要
|
||||
"""
|
||||
return {
|
||||
'env': selection.env,
|
||||
'table_names': ','.join(selection.table_names),
|
||||
'dry_run': str(selection.dry_run).lower(),
|
||||
'allow_prod': str(selection.allow_prod).lower(),
|
||||
}
|
||||
|
||||
def build_preview_command(self, selection: GenImportWizardSelection) -> list[str]:
|
||||
"""
|
||||
构建代码生成导入预览命令。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 用户视角命令参数
|
||||
"""
|
||||
command = ['shierfeng', 'gen', 'import-table', *selection.table_names, f'--env={selection.env}']
|
||||
if selection.dry_run:
|
||||
command.append('--dry-run')
|
||||
if selection.allow_prod:
|
||||
command.append('--allow-prod')
|
||||
return command
|
||||
|
||||
def build_preview_notes(self, selection: GenImportWizardSelection) -> list[str] | None:
|
||||
"""
|
||||
构建代码生成导入预览附加说明。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览附加说明
|
||||
"""
|
||||
del selection
|
||||
return ['建议先确认物理表注释和字段规模,再决定是否执行真实导入。']
|
||||
|
||||
def confirm_prompt(self, selection: GenImportWizardSelection) -> str:
|
||||
"""
|
||||
返回代码生成导入最终确认提示。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 确认提示
|
||||
"""
|
||||
del selection
|
||||
return '确认执行代码生成导入向导吗'
|
||||
|
||||
def build_execute_arguments(self, selection: GenImportWizardSelection, output: OutputOption) -> list[str]:
|
||||
"""
|
||||
构建代码生成导入内部 CLI 参数。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: 内部 CLI 参数
|
||||
"""
|
||||
arguments = [
|
||||
'gen',
|
||||
'import-table',
|
||||
*selection.table_names,
|
||||
f'--env={selection.env}',
|
||||
f'--output={output}',
|
||||
'--yes',
|
||||
]
|
||||
if selection.dry_run:
|
||||
arguments.append('--dry-run')
|
||||
if selection.allow_prod:
|
||||
arguments.append('--allow-prod')
|
||||
return arguments
|
||||
|
||||
@staticmethod
|
||||
def parse_table_names(raw_value: str) -> list[str]:
|
||||
"""
|
||||
将逗号分隔的物理表名称文本解析为列表。
|
||||
|
||||
:param raw_value: 原始输入文本
|
||||
:return: 去空白后的物理表名称列表
|
||||
"""
|
||||
return [table_name.strip() for table_name in raw_value.split(',') if table_name.strip()]
|
||||
|
||||
|
||||
def run_gen_import_wizard(
|
||||
output: OutputOption = 'text',
|
||||
*,
|
||||
default_env: str = 'dev',
|
||||
default_table_names: str = '',
|
||||
default_dry_run: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
执行 `wizard gen-import` 向导。
|
||||
|
||||
:param output: 输出格式
|
||||
:param default_env: 默认环境
|
||||
:param default_table_names: 默认物理表名称列表文本
|
||||
:param default_dry_run: 默认是否先 dry-run
|
||||
:return: None
|
||||
"""
|
||||
GenImportWizardFlow(
|
||||
default_env=default_env,
|
||||
default_table_names=default_table_names,
|
||||
default_dry_run=default_dry_run,
|
||||
).run(output)
|
||||
@@ -0,0 +1,224 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from cli.context import CliContext, OutputOption
|
||||
from cli.wizard.aggregators import ProdCheckAggregator
|
||||
from cli.wizard.base import BaseCliWizardFlow
|
||||
from cli.wizard.presenters import ProdCheckPresenter
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProdCheckWizardSelection:
|
||||
"""
|
||||
`wizard prod-check` 向导采集结果。
|
||||
|
||||
:param env: 运行环境
|
||||
:param include_config: 是否附带配置快照
|
||||
"""
|
||||
|
||||
env: str
|
||||
include_config: bool
|
||||
|
||||
|
||||
class ProdCheckWizardFlow(BaseCliWizardFlow[ProdCheckWizardSelection]):
|
||||
"""
|
||||
`wizard prod-check` 向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_include_config: 默认是否附带配置快照
|
||||
"""
|
||||
|
||||
wizard_name = 'wizard prod-check'
|
||||
preview_title = 'wizard prod-check preview'
|
||||
failure_message = '生产巡检向导执行失败'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
default_env: str = 'prod',
|
||||
default_include_config: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
初始化生产巡检向导流程。
|
||||
|
||||
:param default_env: 默认环境
|
||||
:param default_include_config: 默认是否附带配置快照
|
||||
:return: None
|
||||
"""
|
||||
super().__init__()
|
||||
self.default_env = default_env
|
||||
self.default_include_config = default_include_config
|
||||
self.aggregator = ProdCheckAggregator()
|
||||
self.presenter = ProdCheckPresenter()
|
||||
|
||||
def collect_selection(self) -> ProdCheckWizardSelection:
|
||||
"""
|
||||
采集生产巡检向导参数。
|
||||
|
||||
:return: 向导采集结果
|
||||
"""
|
||||
env = self.prompt_service.prompt_env(self.default_env)
|
||||
include_config = self.prompt_service.prompt_confirm(
|
||||
'是否附带应用配置快照',
|
||||
default_value=self.default_include_config,
|
||||
)
|
||||
return ProdCheckWizardSelection(env=env, include_config=include_config)
|
||||
|
||||
def prepare_context(self, selection: ProdCheckWizardSelection, output: OutputOption) -> CliContext:
|
||||
"""
|
||||
构建生产巡检向导上下文。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: CLI 上下文
|
||||
"""
|
||||
return self.build_readonly_context(selection.env, output)
|
||||
|
||||
def build_preview_summary(self, selection: ProdCheckWizardSelection) -> dict[str, str]:
|
||||
"""
|
||||
构建生产巡检预览摘要。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 预览摘要
|
||||
"""
|
||||
return {
|
||||
'env': selection.env,
|
||||
'include_config': str(selection.include_config).lower(),
|
||||
}
|
||||
|
||||
def build_preview_command(self, selection: ProdCheckWizardSelection) -> list[str]:
|
||||
"""
|
||||
构建生产巡检预览命令。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 用户视角命令参数
|
||||
"""
|
||||
return ['shierfeng', 'app', 'doctor', f'--env={selection.env}']
|
||||
|
||||
def confirm_prompt(self, selection: ProdCheckWizardSelection) -> str:
|
||||
"""
|
||||
返回生产巡检最终确认提示。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 确认提示
|
||||
"""
|
||||
del selection
|
||||
return '确认执行生产巡检向导吗'
|
||||
|
||||
def confirm_default_value(self, selection: ProdCheckWizardSelection) -> bool:
|
||||
"""
|
||||
返回生产巡检确认默认值。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:return: 默认确认值
|
||||
"""
|
||||
del selection
|
||||
return True
|
||||
|
||||
def build_execute_arguments(self, selection: ProdCheckWizardSelection, output: OutputOption) -> list[str]:
|
||||
"""
|
||||
返回生产巡检占位执行参数。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: 内部 CLI 参数
|
||||
"""
|
||||
del selection, output
|
||||
return []
|
||||
|
||||
def execute(self, selection: ProdCheckWizardSelection, output: OutputOption) -> dict[str, Any]:
|
||||
"""
|
||||
执行生产巡检聚合逻辑。
|
||||
|
||||
:param selection: 向导采集结果
|
||||
:param output: 输出格式
|
||||
:return: 聚合后的巡检结果
|
||||
"""
|
||||
del output
|
||||
runtime_result = self.run_nested_command(
|
||||
'app', 'env', f'--env={selection.env}', '--output=json', parse_json=True
|
||||
)
|
||||
doctor_result = self.run_nested_command(
|
||||
'app',
|
||||
'doctor',
|
||||
f'--env={selection.env}',
|
||||
'--output=json',
|
||||
parse_json=True,
|
||||
)
|
||||
config_payload = None
|
||||
if selection.include_config:
|
||||
config_result = self.run_nested_command(
|
||||
'app',
|
||||
'config',
|
||||
f'--env={selection.env}',
|
||||
'--output=json',
|
||||
parse_json=True,
|
||||
)
|
||||
config_payload = config_result.payload
|
||||
return self.aggregator.build_payload(
|
||||
env=selection.env,
|
||||
runtime_payload=runtime_result.payload if isinstance(runtime_result.payload, dict) else None,
|
||||
doctor_payload=doctor_result.payload if isinstance(doctor_result.payload, dict) else None,
|
||||
config_payload=config_payload,
|
||||
)
|
||||
|
||||
def complete_payload(
|
||||
self,
|
||||
ctx: CliContext,
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
default_exit_code: int = 0,
|
||||
) -> None:
|
||||
"""
|
||||
以聚合结果展示器收口生产巡检结果。
|
||||
|
||||
:param ctx: CLI 上下文
|
||||
:param payload: 聚合结果负载
|
||||
:param default_exit_code: 默认退出码
|
||||
:return: None
|
||||
"""
|
||||
self.complete_payload_result(
|
||||
ctx,
|
||||
payload,
|
||||
text_builder=self.presenter.build_text,
|
||||
default_exit_code=default_exit_code,
|
||||
)
|
||||
|
||||
def extract_payload(self, nested_result: Any) -> dict[str, Any] | None:
|
||||
"""
|
||||
提取生产巡检聚合结果负载。
|
||||
|
||||
:param nested_result: 聚合执行结果
|
||||
:return: 结构化结果负载
|
||||
"""
|
||||
return nested_result if isinstance(nested_result, dict) else None
|
||||
|
||||
def extract_returncode(self, nested_result: Any) -> int:
|
||||
"""
|
||||
提取生产巡检聚合结果退出码。
|
||||
|
||||
:param nested_result: 聚合执行结果
|
||||
:return: 退出码
|
||||
"""
|
||||
del nested_result
|
||||
return 0
|
||||
|
||||
|
||||
def run_prod_check_wizard(
|
||||
output: OutputOption = 'text',
|
||||
*,
|
||||
default_env: str = 'prod',
|
||||
default_include_config: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
执行 `wizard prod-check` 向导。
|
||||
|
||||
:param output: 输出格式
|
||||
:param default_env: 默认环境
|
||||
:param default_include_config: 默认是否附带配置快照
|
||||
:return: None
|
||||
"""
|
||||
ProdCheckWizardFlow(
|
||||
default_env=default_env,
|
||||
default_include_config=default_include_config,
|
||||
).run(output)
|
||||
@@ -0,0 +1,95 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProdCheckRenderingSupport:
|
||||
"""
|
||||
`wizard prod-check` 文本渲染支持对象。
|
||||
|
||||
该对象负责构建 runtime、doctor、config 分区文本行,
|
||||
避免 presenter 本体继续维护全部分区细节。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_runtime_lines(runtime: dict[str, Any]) -> list[str]:
|
||||
"""
|
||||
构建 runtime 分区文本行。
|
||||
|
||||
:param runtime: runtime 结果片段
|
||||
:return: 文本行列表
|
||||
"""
|
||||
return [
|
||||
'runtime:',
|
||||
f' cli_env: {runtime.get("cliEnv", "-")}',
|
||||
f' config_env: {runtime.get("configEnv", "-")}',
|
||||
f' env_file: {runtime.get("envFile", "-")}',
|
||||
f' env_file_exists: {str(runtime.get("envFileExists", False)).lower()}',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def build_doctor_lines(doctor: dict[str, Any]) -> list[str]:
|
||||
"""
|
||||
构建 doctor 分区文本行。
|
||||
|
||||
:param doctor: doctor 结果片段
|
||||
:return: 文本行列表
|
||||
"""
|
||||
return [
|
||||
'doctor:',
|
||||
f' ok: {str(doctor.get("ok", False)).lower()}',
|
||||
f' message: {doctor.get("message", "-")}',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def build_config_lines(config: dict[str, Any]) -> list[str]:
|
||||
"""
|
||||
构建 config 分区文本行。
|
||||
|
||||
:param config: config 结果片段
|
||||
:return: 文本行列表
|
||||
"""
|
||||
return [
|
||||
'config:',
|
||||
f' name: {config.get("name", "-")}',
|
||||
f' host: {config.get("host", "-")}:{config.get("port", "-")}',
|
||||
f' db_type: {config.get("dbType", "-")}',
|
||||
f' redis_host: {config.get("redisHost", "-")}:{config.get("redisPort", "-")}',
|
||||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProdCheckPresenter:
|
||||
"""
|
||||
`wizard prod-check` 结果展示器。
|
||||
|
||||
该对象负责将聚合结果转换为文本摘要,
|
||||
避免在 flow 内继续维护文本渲染细节。
|
||||
|
||||
:param rendering_support: 文本渲染支持对象
|
||||
"""
|
||||
|
||||
rendering_support: ProdCheckRenderingSupport = ProdCheckRenderingSupport()
|
||||
|
||||
def build_text(self, payload: dict[str, Any]) -> str:
|
||||
"""
|
||||
将生产巡检聚合结果渲染为文本摘要。
|
||||
|
||||
:param payload: 聚合结果字典
|
||||
:return: 文本摘要
|
||||
"""
|
||||
lines = [
|
||||
f'ok: {str(payload.get("ok", False)).lower()}',
|
||||
f'env: {payload.get("env", "-")}',
|
||||
f'message: {payload.get("message", "-")}',
|
||||
]
|
||||
runtime = payload.get('runtime')
|
||||
if isinstance(runtime, dict):
|
||||
lines.extend(self.rendering_support.build_runtime_lines(runtime))
|
||||
doctor = payload.get('doctor')
|
||||
if isinstance(doctor, dict):
|
||||
lines.extend(self.rendering_support.build_doctor_lines(doctor))
|
||||
config = payload.get('config')
|
||||
if isinstance(config, dict):
|
||||
lines.extend(self.rendering_support.build_config_lines(config))
|
||||
return '\n'.join(lines)
|
||||
@@ -0,0 +1,65 @@
|
||||
from typing import Any
|
||||
|
||||
from cli.exit_codes import GUARD_REJECTED
|
||||
from cli.output import CommandResult
|
||||
from cli.utils import SHELL_TEXT_FORMATTER
|
||||
|
||||
|
||||
class WizardPreviewRenderer:
|
||||
"""
|
||||
向导预览渲染器。
|
||||
|
||||
该对象负责统一构建向导预览文本和取消结果,
|
||||
为各个 flow 提供稳定的类式渲染入口。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def render_preview(
|
||||
title: str,
|
||||
*,
|
||||
summary: dict[str, Any],
|
||||
command: list[str],
|
||||
notes: list[str] | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
渲染向导执行预览文本。
|
||||
|
||||
:param title: 预览标题
|
||||
:param summary: 关键参数摘要
|
||||
:param command: 用户视角命令参数列表
|
||||
:param notes: 额外提示列表
|
||||
:return: 可直接输出的预览文本
|
||||
"""
|
||||
lines = [title, 'summary:']
|
||||
for key, value in summary.items():
|
||||
lines.append(f' {key}: {value}')
|
||||
lines.extend(
|
||||
[
|
||||
'command:',
|
||||
f' {SHELL_TEXT_FORMATTER.format_shell_command(command)}',
|
||||
]
|
||||
)
|
||||
if notes:
|
||||
lines.append('notes:')
|
||||
lines.extend(f' - {note}' for note in notes if note)
|
||||
return '\n'.join(lines)
|
||||
|
||||
@staticmethod
|
||||
def build_cancel_result(wizard_name: str) -> CommandResult:
|
||||
"""
|
||||
构建向导取消结果。
|
||||
|
||||
:param wizard_name: 向导名称
|
||||
:return: 命令结果对象
|
||||
"""
|
||||
return CommandResult(
|
||||
data={
|
||||
'ok': False,
|
||||
'message': f'已取消向导执行:{wizard_name}',
|
||||
'hint': '可重新运行向导并调整参数后再次执行',
|
||||
},
|
||||
exit_code=GUARD_REJECTED,
|
||||
)
|
||||
|
||||
|
||||
WIZARD_PREVIEW_RENDERER = WizardPreviewRenderer()
|
||||
@@ -0,0 +1,86 @@
|
||||
import typer
|
||||
|
||||
from cli.metadata import ENVIRONMENT_OPTION_SERVICE
|
||||
|
||||
|
||||
class WizardPromptService:
|
||||
"""
|
||||
向导交互输入服务。
|
||||
|
||||
该对象集中封装向导中稳定复用的环境选择、枚举选择、
|
||||
文本采集和确认交互逻辑,避免继续扩散模块级散函数入口。
|
||||
"""
|
||||
|
||||
def prompt_env(self, default_env: str = 'dev') -> str:
|
||||
"""
|
||||
交互采集运行环境名称。
|
||||
|
||||
:param default_env: 默认环境名称
|
||||
:return: 用户选择的环境名称
|
||||
"""
|
||||
env_names = ENVIRONMENT_OPTION_SERVICE.discover_env_names()
|
||||
prompt_message = f'运行环境(可选值: {", ".join(env_names)})'
|
||||
while True:
|
||||
env_value = typer.prompt(prompt_message, default=default_env).strip()
|
||||
if env_value in env_names:
|
||||
return env_value
|
||||
typer.echo(f'不支持的环境名称:{env_value},请重新输入。')
|
||||
|
||||
@staticmethod
|
||||
def prompt_choice(prompt_text: str, choices: list[str], default_value: str) -> str:
|
||||
"""
|
||||
交互采集枚举型文本输入。
|
||||
|
||||
:param prompt_text: 提示文案
|
||||
:param choices: 可选值列表
|
||||
:param default_value: 默认值
|
||||
:return: 用户选择的枚举值
|
||||
"""
|
||||
while True:
|
||||
value = typer.prompt(
|
||||
f'{prompt_text}(可选值: {", ".join(choices)})',
|
||||
default=default_value,
|
||||
).strip()
|
||||
if value in choices:
|
||||
return value
|
||||
typer.echo(f'不支持的选项:{value},请重新输入。')
|
||||
|
||||
@staticmethod
|
||||
def prompt_required_text(prompt_text: str, default_value: str = '') -> str:
|
||||
"""
|
||||
交互采集必填文本输入。
|
||||
|
||||
:param prompt_text: 提示文案
|
||||
:param default_value: 默认值
|
||||
:return: 用户输入文本
|
||||
"""
|
||||
while True:
|
||||
value = typer.prompt(prompt_text, default=default_value).strip()
|
||||
if value:
|
||||
return value
|
||||
typer.echo('该字段不能为空,请重新输入。')
|
||||
|
||||
@staticmethod
|
||||
def prompt_optional_text(prompt_text: str, default_value: str = '') -> str:
|
||||
"""
|
||||
交互采集可选文本输入。
|
||||
|
||||
:param prompt_text: 提示文案
|
||||
:param default_value: 默认值
|
||||
:return: 用户输入文本
|
||||
"""
|
||||
return typer.prompt(prompt_text, default=default_value).strip()
|
||||
|
||||
@staticmethod
|
||||
def prompt_confirm(prompt_text: str, default_value: bool = False) -> bool:
|
||||
"""
|
||||
交互采集确认型输入。
|
||||
|
||||
:param prompt_text: 提示文案
|
||||
:param default_value: 默认值
|
||||
:return: 用户确认结果
|
||||
"""
|
||||
return typer.confirm(prompt_text, default=default_value)
|
||||
|
||||
|
||||
WIZARD_PROMPT_SERVICE = WizardPromptService()
|
||||
Reference in New Issue
Block a user