初始提交
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .service import DEVELOPMENT_RUNTIME, DevelopmentRuntimeService
|
||||
|
||||
__all__ = ['DEVELOPMENT_RUNTIME', 'DevelopmentRuntimeService']
|
||||
@@ -0,0 +1,60 @@
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
from cli.exit_codes import RUNTIME_ERROR
|
||||
from cli.runtime.base import RuntimeEnvironmentService
|
||||
|
||||
|
||||
class DevelopmentProcessGateway:
|
||||
"""
|
||||
开发子进程执行网关。
|
||||
|
||||
该对象负责统一执行开发工具子进程命令,
|
||||
避免支持对象直接依赖 `subprocess` 细节。
|
||||
|
||||
:param runtime_environment: 运行时环境服务
|
||||
"""
|
||||
|
||||
def __init__(self, runtime_environment: RuntimeEnvironmentService) -> None:
|
||||
"""
|
||||
初始化开发子进程执行网关。
|
||||
|
||||
:param runtime_environment: 运行时环境服务
|
||||
:return: None
|
||||
"""
|
||||
self.runtime_environment = runtime_environment
|
||||
|
||||
def run_command(self, command: list[str]) -> dict[str, Any]:
|
||||
"""
|
||||
执行子进程命令并返回统一结果。
|
||||
|
||||
:param command: 待执行命令
|
||||
:return: 命令执行结果
|
||||
"""
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
command,
|
||||
cwd=self.runtime_environment.get_backend_dir(),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
except Exception as exc:
|
||||
return {
|
||||
'ok': False,
|
||||
'message': '开发命令执行失败',
|
||||
'error': str(exc),
|
||||
'command': command,
|
||||
'exit_code': RUNTIME_ERROR,
|
||||
}
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
'ok': completed.returncode == 0,
|
||||
'command': command,
|
||||
'returnCode': completed.returncode,
|
||||
}
|
||||
if completed.stdout.strip():
|
||||
payload['stdout'] = completed.stdout.strip()
|
||||
if completed.stderr.strip():
|
||||
payload['stderr'] = completed.stderr.strip()
|
||||
return payload
|
||||
@@ -0,0 +1,158 @@
|
||||
from typing import Any
|
||||
|
||||
from cli.exit_codes import DEPENDENCY_ERROR, RUNTIME_ERROR
|
||||
from cli.runtime.base import RUNTIME_ENVIRONMENT, RuntimeEnvironmentService
|
||||
|
||||
from .gateway import DevelopmentProcessGateway
|
||||
from .support import DevelopmentCommandSupport, DevelopmentToolingSupport
|
||||
|
||||
|
||||
class DevelopmentRuntimeService:
|
||||
"""
|
||||
开发运行时服务。
|
||||
|
||||
该服务作为开发运行时 facade,对外统一暴露 lint/format 和 pytest
|
||||
执行入口。
|
||||
|
||||
:param runtime_environment: 运行时环境服务
|
||||
:param tooling_support: 开发工具支持对象
|
||||
:param command_support: 开发命令构建支持对象
|
||||
:param process_gateway: 开发子进程执行网关
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
runtime_environment: RuntimeEnvironmentService | None = None,
|
||||
tooling_support: DevelopmentToolingSupport | None = None,
|
||||
command_support: DevelopmentCommandSupport | None = None,
|
||||
process_gateway: DevelopmentProcessGateway | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
初始化开发运行时服务。
|
||||
|
||||
:param runtime_environment: 运行时环境服务
|
||||
:param tooling_support: 开发工具支持对象
|
||||
:param command_support: 开发命令构建支持对象
|
||||
:param process_gateway: 开发子进程执行网关
|
||||
:return: None
|
||||
"""
|
||||
self.runtime_environment = runtime_environment or RUNTIME_ENVIRONMENT
|
||||
self.tooling_support = tooling_support or DevelopmentToolingSupport()
|
||||
self.command_support = command_support or DevelopmentCommandSupport(self.runtime_environment)
|
||||
self.process_gateway = process_gateway or DevelopmentProcessGateway(self.runtime_environment)
|
||||
|
||||
def run_lint(
|
||||
self,
|
||||
targets: list[str] | None,
|
||||
*,
|
||||
check_only: bool = False,
|
||||
fix: bool = False,
|
||||
unsafe_fixes: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
执行仓库 lint/format 检查。
|
||||
|
||||
:param targets: 目标路径列表
|
||||
:param check_only: 是否仅检查不写回
|
||||
:param fix: 是否允许 `ruff check --fix`
|
||||
:param unsafe_fixes: 是否允许不安全修复
|
||||
:return: lint 执行结果
|
||||
"""
|
||||
normalized_targets = self.tooling_support.resolve_targets(targets)
|
||||
format_command = self.command_support.build_format_command(normalized_targets, check_only=check_only)
|
||||
format_result = self.process_gateway.run_command(format_command)
|
||||
if not format_result.get('ok', False):
|
||||
return {
|
||||
'ok': False,
|
||||
'message': 'Ruff format 阶段失败',
|
||||
'targets': normalized_targets,
|
||||
'format': format_result,
|
||||
'exit_code': RUNTIME_ERROR,
|
||||
}
|
||||
|
||||
check_command = self.command_support.build_check_command(
|
||||
normalized_targets,
|
||||
check_only=check_only,
|
||||
fix=fix,
|
||||
unsafe_fixes=unsafe_fixes,
|
||||
)
|
||||
check_result = self.process_gateway.run_command(check_command)
|
||||
if not check_result.get('ok', False):
|
||||
return {
|
||||
'ok': False,
|
||||
'message': 'Ruff check 阶段失败',
|
||||
'targets': normalized_targets,
|
||||
'format': format_result,
|
||||
'check': check_result,
|
||||
'exit_code': RUNTIME_ERROR,
|
||||
}
|
||||
|
||||
return {
|
||||
'ok': True,
|
||||
'message': '开发检查已完成',
|
||||
'targets': normalized_targets,
|
||||
'checkOnly': check_only,
|
||||
'fix': fix and not check_only,
|
||||
'unsafeFixes': unsafe_fixes and fix and not check_only,
|
||||
'format': format_result,
|
||||
'check': check_result,
|
||||
}
|
||||
|
||||
def run_tests(
|
||||
self,
|
||||
targets: list[str] | None,
|
||||
*,
|
||||
keyword: str = '',
|
||||
maxfail: int = 0,
|
||||
quiet: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
执行项目测试。
|
||||
|
||||
:param targets: 目标测试路径列表
|
||||
:param keyword: pytest `-k` 过滤表达式
|
||||
:param maxfail: 最大失败数,0 表示不限制
|
||||
:param quiet: 是否启用简洁输出
|
||||
:return: 测试执行结果
|
||||
"""
|
||||
if not self.tooling_support.is_pytest_available():
|
||||
return {
|
||||
'ok': False,
|
||||
'message': 'pytest 未安装,无法执行 dev test',
|
||||
'hint': '请在当前环境安装 pytest 后重试',
|
||||
'exit_code': DEPENDENCY_ERROR,
|
||||
}
|
||||
|
||||
normalized_targets = self.tooling_support.resolve_targets(targets)
|
||||
command = self.command_support.build_pytest_command(
|
||||
normalized_targets,
|
||||
keyword=keyword,
|
||||
maxfail=maxfail,
|
||||
quiet=quiet,
|
||||
)
|
||||
test_result = self.process_gateway.run_command(command)
|
||||
if not test_result.get('ok', False):
|
||||
return {
|
||||
'ok': False,
|
||||
'message': '测试执行失败',
|
||||
'targets': normalized_targets,
|
||||
'keyword': keyword,
|
||||
'maxfail': maxfail,
|
||||
'quiet': quiet,
|
||||
'test': test_result,
|
||||
'exit_code': RUNTIME_ERROR,
|
||||
}
|
||||
|
||||
return {
|
||||
'ok': True,
|
||||
'message': '测试执行完成',
|
||||
'targets': normalized_targets,
|
||||
'keyword': keyword,
|
||||
'maxfail': maxfail,
|
||||
'quiet': quiet,
|
||||
'test': test_result,
|
||||
}
|
||||
|
||||
|
||||
DEVELOPMENT_RUNTIME = DevelopmentRuntimeService()
|
||||
@@ -0,0 +1,118 @@
|
||||
import importlib.util
|
||||
|
||||
from cli.runtime.base import RuntimeEnvironmentService
|
||||
|
||||
|
||||
class DevelopmentToolingSupport:
|
||||
"""
|
||||
开发工具支持对象。
|
||||
|
||||
该对象负责目标列表规整与开发依赖可用性检查,
|
||||
避免主运行时服务继续承载轻量工具规则。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def resolve_targets(targets: list[str] | None) -> list[str]:
|
||||
"""
|
||||
规范化命令目标列表。
|
||||
|
||||
:param targets: 原始目标列表
|
||||
:return: 规范化后的目标列表
|
||||
"""
|
||||
normalized_targets = [target.strip() for target in (targets or []) if target.strip()]
|
||||
return normalized_targets or ['.']
|
||||
|
||||
@staticmethod
|
||||
def is_pytest_available() -> bool:
|
||||
"""
|
||||
检查 pytest 是否已安装。
|
||||
|
||||
:return: pytest 是否可用
|
||||
"""
|
||||
return importlib.util.find_spec('pytest') is not None
|
||||
|
||||
|
||||
class DevelopmentCommandSupport:
|
||||
"""
|
||||
开发命令构建支持对象。
|
||||
|
||||
该对象负责开发工具命令参数拼装,
|
||||
避免主运行时服务继续承载命令行桥接细节。
|
||||
|
||||
:param runtime_environment: 运行时环境服务
|
||||
"""
|
||||
|
||||
def __init__(self, runtime_environment: RuntimeEnvironmentService) -> None:
|
||||
"""
|
||||
初始化开发命令构建支持对象。
|
||||
|
||||
:param runtime_environment: 运行时环境服务
|
||||
:return: None
|
||||
"""
|
||||
self.runtime_environment = runtime_environment
|
||||
|
||||
def build_format_command(self, normalized_targets: list[str], *, check_only: bool) -> list[str]:
|
||||
"""
|
||||
构建 Ruff format 命令。
|
||||
|
||||
:param normalized_targets: 已规整目标列表
|
||||
:param check_only: 是否仅检查
|
||||
:return: 格式化命令参数列表
|
||||
"""
|
||||
command = [self.runtime_environment.get_python_executable(), '-m', 'ruff', 'format']
|
||||
if check_only:
|
||||
command.append('--check')
|
||||
command.extend(normalized_targets)
|
||||
return command
|
||||
|
||||
def build_check_command(
|
||||
self,
|
||||
normalized_targets: list[str],
|
||||
*,
|
||||
check_only: bool,
|
||||
fix: bool,
|
||||
unsafe_fixes: bool,
|
||||
) -> list[str]:
|
||||
"""
|
||||
构建 Ruff check 命令。
|
||||
|
||||
:param normalized_targets: 已规整目标列表
|
||||
:param check_only: 是否仅检查
|
||||
:param fix: 是否允许自动修复
|
||||
:param unsafe_fixes: 是否允许不安全修复
|
||||
:return: 检查命令参数列表
|
||||
"""
|
||||
command = [self.runtime_environment.get_python_executable(), '-m', 'ruff', 'check']
|
||||
if fix and not check_only:
|
||||
command.append('--fix')
|
||||
if unsafe_fixes:
|
||||
command.append('--unsafe-fixes')
|
||||
command.extend(normalized_targets)
|
||||
return command
|
||||
|
||||
def build_pytest_command(
|
||||
self,
|
||||
normalized_targets: list[str],
|
||||
*,
|
||||
keyword: str,
|
||||
maxfail: int,
|
||||
quiet: bool,
|
||||
) -> list[str]:
|
||||
"""
|
||||
构建 pytest 命令。
|
||||
|
||||
:param normalized_targets: 已规整目标列表
|
||||
:param keyword: pytest `-k` 表达式
|
||||
:param maxfail: 最大失败数
|
||||
:param quiet: 是否启用简洁输出
|
||||
:return: pytest 命令参数列表
|
||||
"""
|
||||
command = [self.runtime_environment.get_python_executable(), '-m', 'pytest']
|
||||
if quiet:
|
||||
command.append('-q')
|
||||
if keyword:
|
||||
command.extend(['-k', keyword])
|
||||
if maxfail > 0:
|
||||
command.append(f'--maxfail={maxfail}')
|
||||
command.extend(normalized_targets)
|
||||
return command
|
||||
Reference in New Issue
Block a user