142 lines
4.2 KiB
Python
142 lines
4.2 KiB
Python
|
|
"""Node.js 项目依赖检测模块。"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import subprocess
|
|||
|
|
from pathlib import Path
|
|||
|
|
from typing import Any
|
|||
|
|
|
|||
|
|
from aide.env.modules.base import BaseModule, CheckResult, ModuleInfo
|
|||
|
|
|
|||
|
|
|
|||
|
|
class NodeDepsModule(BaseModule):
|
|||
|
|
"""Node.js 项目依赖检测模块(类型B:需要配置)。
|
|||
|
|
|
|||
|
|
支持多种包管理器:npm, pnpm, yarn, bun
|
|||
|
|
自动根据锁文件检测包管理器类型
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
# 锁文件到包管理器的映射
|
|||
|
|
LOCK_FILES = {
|
|||
|
|
"pnpm-lock.yaml": "pnpm",
|
|||
|
|
"yarn.lock": "yarn",
|
|||
|
|
"bun.lockb": "bun",
|
|||
|
|
"package-lock.json": "npm",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 包管理器安装命令
|
|||
|
|
INSTALL_COMMANDS = {
|
|||
|
|
"npm": ["npm", "install"],
|
|||
|
|
"pnpm": ["pnpm", "install"],
|
|||
|
|
"yarn": ["yarn", "install"],
|
|||
|
|
"bun": ["bun", "install"],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def info(self) -> ModuleInfo:
|
|||
|
|
return ModuleInfo(
|
|||
|
|
name="node_deps",
|
|||
|
|
description="Node.js 项目依赖",
|
|||
|
|
capabilities=["check", "ensure"],
|
|||
|
|
requires_config=True,
|
|||
|
|
config_keys=["path"],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def check(self, config: dict[str, Any], root: Path) -> CheckResult:
|
|||
|
|
"""检测 Node.js 项目依赖。"""
|
|||
|
|
project_path = root / config["path"]
|
|||
|
|
|
|||
|
|
# 检测 package.json 是否存在
|
|||
|
|
package_json = project_path / "package.json"
|
|||
|
|
if not package_json.exists():
|
|||
|
|
return CheckResult(
|
|||
|
|
success=False,
|
|||
|
|
message=f"package.json 不存在: {config['path']}",
|
|||
|
|
can_ensure=False,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 检测 node_modules 是否存在
|
|||
|
|
node_modules = project_path / "node_modules"
|
|||
|
|
if not node_modules.exists():
|
|||
|
|
manager = self._detect_manager(project_path, config)
|
|||
|
|
return CheckResult(
|
|||
|
|
success=False,
|
|||
|
|
message=f"node_modules 不存在",
|
|||
|
|
can_ensure=True,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 检测包管理器
|
|||
|
|
manager = self._detect_manager(project_path, config)
|
|||
|
|
|
|||
|
|
return CheckResult(
|
|||
|
|
success=True,
|
|||
|
|
version=config["path"],
|
|||
|
|
message=manager,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def ensure(self, config: dict[str, Any], root: Path) -> CheckResult:
|
|||
|
|
"""安装 Node.js 项目依赖。"""
|
|||
|
|
project_path = root / config["path"]
|
|||
|
|
manager = self._detect_manager(project_path, config)
|
|||
|
|
|
|||
|
|
# 检测包管理器是否已安装
|
|||
|
|
if not self._is_manager_installed(manager):
|
|||
|
|
return CheckResult(
|
|||
|
|
success=False,
|
|||
|
|
message=f"{manager} 未安装",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 运行安装命令
|
|||
|
|
install_cmd = self.INSTALL_COMMANDS.get(manager, ["npm", "install"])
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
subprocess.run(
|
|||
|
|
install_cmd,
|
|||
|
|
cwd=project_path,
|
|||
|
|
check=True,
|
|||
|
|
capture_output=True,
|
|||
|
|
)
|
|||
|
|
return CheckResult(
|
|||
|
|
success=True,
|
|||
|
|
version=config["path"],
|
|||
|
|
message=f"已安装 ({manager})",
|
|||
|
|
)
|
|||
|
|
except subprocess.CalledProcessError as exc:
|
|||
|
|
error_msg = exc.stderr.decode() if exc.stderr else str(exc)
|
|||
|
|
return CheckResult(
|
|||
|
|
success=False,
|
|||
|
|
message=f"安装失败: {error_msg[:100]}",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def _detect_manager(self, project_path: Path, config: dict[str, Any]) -> str:
|
|||
|
|
"""检测包管理器类型。
|
|||
|
|
|
|||
|
|
优先使用配置指定的 manager,否则根据锁文件自动检测。
|
|||
|
|
"""
|
|||
|
|
# 优先使用配置指定的 manager
|
|||
|
|
if "manager" in config:
|
|||
|
|
return config["manager"]
|
|||
|
|
|
|||
|
|
# 根据锁文件检测
|
|||
|
|
for lock_file, manager in self.LOCK_FILES.items():
|
|||
|
|
if (project_path / lock_file).exists():
|
|||
|
|
return manager
|
|||
|
|
|
|||
|
|
# 默认使用 npm
|
|||
|
|
return "npm"
|
|||
|
|
|
|||
|
|
def _is_manager_installed(self, manager: str) -> bool:
|
|||
|
|
"""检测包管理器是否已安装。"""
|
|||
|
|
try:
|
|||
|
|
subprocess.run(
|
|||
|
|
[manager, "--version"],
|
|||
|
|
capture_output=True,
|
|||
|
|
timeout=10,
|
|||
|
|
)
|
|||
|
|
return True
|
|||
|
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
module = NodeDepsModule()
|