Major enhancements to Clover CLI: ✨ New Features: - AI agent with multi-turn conversation capabilities - Tool calling system with 11+ tools for file operations, Git, linting, etc. - Step-by-step AI assistance with play-by-play commentary - Enhanced interactive mode with better UX 🔧 Core Components Added: - ai_agent.py: Main AI agent with conversation management - models/: API client and model management system - Comprehensive tool system for development tasks 🛠️ Tools Available: - File operations (create, read, update, delete) - Command execution with safety checks - Git operations (status, diff, commit, push) - Code linting and formatting - Project structure analysis - Security scanning and dependency management 💡 User Experience: - Real-time tool execution summaries - File creation with full path visibility - Error handling and retry mechanisms - Clean conversation flow until task completion 🧹 Repository Cleanup: - Added comprehensive .gitignore - Removed __pycache__ directories and build artifacts - Organized project structure The AI can now actually create files, run commands, and work through complex development tasks step-by-step with full transparency.
883 lines
30 KiB
Python
883 lines
30 KiB
Python
"""
|
|
Dependency management tools for Clover - A terminal assistant for AI-powered project management
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Optional, Tuple
|
|
|
|
# Add the current directory to Python path for imports
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from config.settings import load_config
|
|
from models.api_client import APIClient
|
|
from tools.file_tools import create_file, read_file, update_file
|
|
|
|
|
|
class DependencyManager:
|
|
"""Handle dependency management operations across different package managers"""
|
|
|
|
def __init__(self):
|
|
self.config = load_config()
|
|
self.api_client = APIClient()
|
|
self.package_managers = {
|
|
"python": {
|
|
"files": ["requirements.txt", "pyproject.toml", "setup.py", "Pipfile"],
|
|
"install_cmd": ["pip", "install"],
|
|
"uninstall_cmd": ["pip", "uninstall", "-y"],
|
|
"list_cmd": ["pip", "list", "--format=json"],
|
|
"outdated_cmd": ["pip", "list", "--outdated", "--format=json"],
|
|
},
|
|
"javascript": {
|
|
"files": ["package.json", "package-lock.json", "yarn.lock"],
|
|
"install_cmd": ["npm", "install"],
|
|
"uninstall_cmd": ["npm", "uninstall"],
|
|
"list_cmd": ["npm", "list", "--json"],
|
|
"outdated_cmd": ["npm", "outdated", "--json"],
|
|
},
|
|
"rust": {
|
|
"files": ["Cargo.toml", "Cargo.lock"],
|
|
"install_cmd": ["cargo", "add"],
|
|
"uninstall_cmd": ["cargo", "remove"],
|
|
"list_cmd": ["cargo", "tree"],
|
|
"outdated_cmd": ["cargo", "outdated"],
|
|
},
|
|
"go": {
|
|
"files": ["go.mod", "go.sum"],
|
|
"install_cmd": ["go", "get"],
|
|
"uninstall_cmd": ["go", "mod", "edit", "-droprequire"],
|
|
"list_cmd": ["go", "list", "-m", "all"],
|
|
"outdated_cmd": ["go", "list", "-u", "-m", "all"],
|
|
},
|
|
"ruby": {
|
|
"files": ["Gemfile", "Gemfile.lock"],
|
|
"install_cmd": ["gem", "install"],
|
|
"uninstall_cmd": ["gem", "uninstall"],
|
|
"list_cmd": ["gem", "list"],
|
|
"outdated_cmd": ["gem", "outdated"],
|
|
},
|
|
}
|
|
|
|
def _detect_project_type(self, project_path: str = ".") -> List[str]:
|
|
"""
|
|
Detect project type(s) based on dependency files
|
|
|
|
Args:
|
|
project_path (str): Path to project directory
|
|
|
|
Returns:
|
|
List of detected project types
|
|
"""
|
|
detected_types = []
|
|
|
|
for project_type, config in self.package_managers.items():
|
|
for dep_file in config["files"]:
|
|
if os.path.exists(os.path.join(project_path, dep_file)):
|
|
detected_types.append(project_type)
|
|
break
|
|
|
|
return detected_types or ["unknown"]
|
|
|
|
def _run_command(self, cmd: List[str], cwd: str = ".") -> Dict[str, Any]:
|
|
"""
|
|
Execute a command and return structured result
|
|
|
|
Args:
|
|
cmd (List[str]): Command to execute
|
|
cwd (str): Working directory
|
|
|
|
Returns:
|
|
Dict containing command result
|
|
"""
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
cwd=cwd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=300, # 5 minute timeout
|
|
)
|
|
|
|
return {
|
|
"success": result.returncode == 0,
|
|
"stdout": result.stdout.strip(),
|
|
"stderr": result.stderr.strip(),
|
|
"return_code": result.returncode,
|
|
}
|
|
|
|
except subprocess.TimeoutExpired:
|
|
return {
|
|
"success": False,
|
|
"error": f"Command timed out: {' '.join(cmd)}",
|
|
"return_code": -1,
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"success": False,
|
|
"error": f"Error executing command: {str(e)}",
|
|
"return_code": -1,
|
|
}
|
|
|
|
def _parse_requirements_txt(self, filepath: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Parse requirements.txt file
|
|
|
|
Args:
|
|
filepath (str): Path to requirements.txt
|
|
|
|
Returns:
|
|
List of dependency dictionaries
|
|
"""
|
|
try:
|
|
content = read_file(filepath)
|
|
dependencies = []
|
|
|
|
for line in content.splitlines():
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
|
|
# Parse dependency line
|
|
# Handle various formats: package==1.0.0, package>=1.0.0, package, etc.
|
|
match = re.match(r"^([a-zA-Z0-9\-_.]+)([><=!~]*)([\d\w\-.*]*)", line)
|
|
if match:
|
|
name, operator, version = match.groups()
|
|
dependencies.append(
|
|
{
|
|
"name": name,
|
|
"version": version if version else None,
|
|
"operator": operator if operator else None,
|
|
"raw": line,
|
|
}
|
|
)
|
|
|
|
return dependencies
|
|
|
|
except Exception as e:
|
|
return [{"error": f"Error parsing requirements.txt: {str(e)}"}]
|
|
|
|
def _parse_package_json(self, filepath: str) -> Dict[str, List[Dict[str, Any]]]:
|
|
"""
|
|
Parse package.json file
|
|
|
|
Args:
|
|
filepath (str): Path to package.json
|
|
|
|
Returns:
|
|
Dict containing dependencies and devDependencies
|
|
"""
|
|
try:
|
|
content = read_file(filepath)
|
|
data = json.loads(content)
|
|
|
|
result = {"dependencies": [], "devDependencies": []}
|
|
|
|
# Parse regular dependencies
|
|
if "dependencies" in data:
|
|
for name, version in data["dependencies"].items():
|
|
result["dependencies"].append(
|
|
{"name": name, "version": version, "type": "production"}
|
|
)
|
|
|
|
# Parse dev dependencies
|
|
if "devDependencies" in data:
|
|
for name, version in data["devDependencies"].items():
|
|
result["devDependencies"].append(
|
|
{"name": name, "version": version, "type": "development"}
|
|
)
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
return {
|
|
"error": f"Error parsing package.json: {str(e)}",
|
|
"dependencies": [],
|
|
"devDependencies": [],
|
|
}
|
|
|
|
def _parse_pyproject_toml(self, filepath: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Parse pyproject.toml file
|
|
|
|
Args:
|
|
filepath (str): Path to pyproject.toml
|
|
|
|
Returns:
|
|
List of dependency dictionaries
|
|
"""
|
|
try:
|
|
content = read_file(filepath)
|
|
dependencies = []
|
|
|
|
# Simple TOML parsing for dependencies
|
|
# This is a basic implementation - for production use, consider using a TOML library
|
|
in_dependencies = False
|
|
for line in content.splitlines():
|
|
line = line.strip()
|
|
|
|
if (
|
|
line == "[tool.poetry.dependencies]"
|
|
or line == "[project.dependencies]"
|
|
):
|
|
in_dependencies = True
|
|
continue
|
|
elif line.startswith("[") and in_dependencies:
|
|
in_dependencies = False
|
|
continue
|
|
|
|
if in_dependencies and "=" in line:
|
|
parts = line.split("=", 1)
|
|
if len(parts) == 2:
|
|
name = parts[0].strip().strip('"')
|
|
version = parts[1].strip().strip('"')
|
|
dependencies.append(
|
|
{"name": name, "version": version, "raw": line}
|
|
)
|
|
|
|
return dependencies
|
|
|
|
except Exception as e:
|
|
return [{"error": f"Error parsing pyproject.toml: {str(e)}"}]
|
|
|
|
|
|
def scan_dependencies(project_path: str = ".") -> Dict[str, Any]:
|
|
"""
|
|
Parse requirements.txt, pyproject.toml, package.json etc.
|
|
|
|
Args:
|
|
project_path (str): Path to project directory
|
|
|
|
Returns:
|
|
Dict containing dependency analysis
|
|
"""
|
|
try:
|
|
manager = DependencyManager()
|
|
project_types = manager._detect_project_type(project_path)
|
|
|
|
results = {
|
|
"project_path": project_path,
|
|
"project_types": project_types,
|
|
"dependency_files": {},
|
|
"total_dependencies": 0,
|
|
}
|
|
|
|
# Scan each detected project type
|
|
for project_type in project_types:
|
|
if project_type == "unknown":
|
|
continue
|
|
|
|
config = manager.package_managers.get(project_type, {})
|
|
dep_files = config.get("files", [])
|
|
|
|
for dep_file in dep_files:
|
|
file_path = os.path.join(project_path, dep_file)
|
|
if os.path.exists(file_path):
|
|
if dep_file == "requirements.txt":
|
|
deps = manager._parse_requirements_txt(file_path)
|
|
results["dependency_files"][dep_file] = {
|
|
"type": "python",
|
|
"dependencies": deps,
|
|
"count": len([d for d in deps if "error" not in d]),
|
|
}
|
|
|
|
elif dep_file == "package.json":
|
|
deps = manager._parse_package_json(file_path)
|
|
total_deps = len(deps.get("dependencies", [])) + len(
|
|
deps.get("devDependencies", [])
|
|
)
|
|
results["dependency_files"][dep_file] = {
|
|
"type": "javascript",
|
|
"dependencies": deps,
|
|
"count": total_deps,
|
|
}
|
|
|
|
elif dep_file == "pyproject.toml":
|
|
deps = manager._parse_pyproject_toml(file_path)
|
|
results["dependency_files"][dep_file] = {
|
|
"type": "python",
|
|
"dependencies": deps,
|
|
"count": len([d for d in deps if "error" not in d]),
|
|
}
|
|
|
|
else:
|
|
# For other files, just note their presence
|
|
results["dependency_files"][dep_file] = {
|
|
"type": project_type,
|
|
"found": True,
|
|
"count": 0,
|
|
}
|
|
|
|
# Calculate total dependencies
|
|
for file_info in results["dependency_files"].values():
|
|
results["total_dependencies"] += file_info.get("count", 0)
|
|
|
|
return results
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error scanning dependencies: {str(e)}"}
|
|
|
|
|
|
def add_dependency(
|
|
package_name: str, version: str = None, project_path: str = ".", dev: bool = False
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Add a package to project dependencies
|
|
|
|
Args:
|
|
package_name (str): Name of package to add
|
|
version (str): Version specification (optional)
|
|
project_path (str): Path to project directory
|
|
dev (bool): Whether this is a development dependency
|
|
|
|
Returns:
|
|
Dict containing operation result
|
|
"""
|
|
try:
|
|
manager = DependencyManager()
|
|
project_types = manager._detect_project_type(project_path)
|
|
|
|
if "python" in project_types:
|
|
return _add_python_dependency(package_name, version, project_path, dev)
|
|
elif "javascript" in project_types:
|
|
return _add_javascript_dependency(package_name, version, project_path, dev)
|
|
else:
|
|
return {"error": f"Unsupported project type: {project_types}"}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error adding dependency: {str(e)}"}
|
|
|
|
|
|
def _add_python_dependency(
|
|
package_name: str, version: str = None, project_path: str = ".", dev: bool = False
|
|
) -> Dict[str, Any]:
|
|
"""Add Python dependency"""
|
|
try:
|
|
manager = DependencyManager()
|
|
|
|
# Try pip install first
|
|
install_cmd = manager.package_managers["python"]["install_cmd"].copy()
|
|
|
|
if version:
|
|
package_spec = f"{package_name}=={version}"
|
|
else:
|
|
package_spec = package_name
|
|
|
|
install_cmd.append(package_spec)
|
|
|
|
result = manager._run_command(install_cmd, project_path)
|
|
|
|
if not result["success"]:
|
|
return {
|
|
"error": f"Failed to install {package_name}: {result.get('stderr', 'Unknown error')}",
|
|
"package": package_name,
|
|
}
|
|
|
|
# Update requirements.txt if it exists
|
|
req_file = os.path.join(project_path, "requirements.txt")
|
|
if os.path.exists(req_file):
|
|
try:
|
|
content = read_file(req_file)
|
|
|
|
# Check if package already exists
|
|
lines = content.splitlines()
|
|
updated = False
|
|
|
|
for i, line in enumerate(lines):
|
|
if line.strip().startswith(package_name):
|
|
# Update existing entry
|
|
lines[i] = package_spec
|
|
updated = True
|
|
break
|
|
|
|
if not updated:
|
|
# Add new entry
|
|
lines.append(package_spec)
|
|
|
|
# Write back to file
|
|
update_file(req_file, "\n".join(lines))
|
|
|
|
except Exception as e:
|
|
# Installation succeeded but file update failed
|
|
return {
|
|
"warning": f"Package installed but failed to update requirements.txt: {str(e)}",
|
|
"package": package_name,
|
|
"version": version,
|
|
}
|
|
|
|
return {
|
|
"success": True,
|
|
"package": package_name,
|
|
"version": version,
|
|
"message": f"Successfully added {package_spec}",
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error adding Python dependency: {str(e)}"}
|
|
|
|
|
|
def _add_javascript_dependency(
|
|
package_name: str, version: str = None, project_path: str = ".", dev: bool = False
|
|
) -> Dict[str, Any]:
|
|
"""Add JavaScript dependency"""
|
|
try:
|
|
manager = DependencyManager()
|
|
|
|
# Use npm install
|
|
install_cmd = ["npm", "install"]
|
|
|
|
if dev:
|
|
install_cmd.append("--save-dev")
|
|
|
|
if version:
|
|
package_spec = f"{package_name}@{version}"
|
|
else:
|
|
package_spec = package_name
|
|
|
|
install_cmd.append(package_spec)
|
|
|
|
result = manager._run_command(install_cmd, project_path)
|
|
|
|
if not result["success"]:
|
|
return {
|
|
"error": f"Failed to install {package_name}: {result.get('stderr', 'Unknown error')}",
|
|
"package": package_name,
|
|
}
|
|
|
|
return {
|
|
"success": True,
|
|
"package": package_name,
|
|
"version": version,
|
|
"dev": dev,
|
|
"message": f"Successfully added {package_spec}",
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error adding JavaScript dependency: {str(e)}"}
|
|
|
|
|
|
def remove_dependency(package_name: str, project_path: str = ".") -> Dict[str, Any]:
|
|
"""
|
|
Remove a package from project dependencies
|
|
|
|
Args:
|
|
package_name (str): Name of package to remove
|
|
project_path (str): Path to project directory
|
|
|
|
Returns:
|
|
Dict containing operation result
|
|
"""
|
|
try:
|
|
manager = DependencyManager()
|
|
project_types = manager._detect_project_type(project_path)
|
|
|
|
if "python" in project_types:
|
|
return _remove_python_dependency(package_name, project_path)
|
|
elif "javascript" in project_types:
|
|
return _remove_javascript_dependency(package_name, project_path)
|
|
else:
|
|
return {"error": f"Unsupported project type: {project_types}"}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error removing dependency: {str(e)}"}
|
|
|
|
|
|
def _remove_python_dependency(
|
|
package_name: str, project_path: str = "."
|
|
) -> Dict[str, Any]:
|
|
"""Remove Python dependency"""
|
|
try:
|
|
manager = DependencyManager()
|
|
|
|
# Try pip uninstall
|
|
uninstall_cmd = manager.package_managers["python"]["uninstall_cmd"].copy()
|
|
uninstall_cmd.append(package_name)
|
|
|
|
result = manager._run_command(uninstall_cmd, project_path)
|
|
|
|
# Update requirements.txt if it exists
|
|
req_file = os.path.join(project_path, "requirements.txt")
|
|
if os.path.exists(req_file):
|
|
try:
|
|
content = read_file(req_file)
|
|
lines = content.splitlines()
|
|
|
|
# Remove lines that start with the package name
|
|
filtered_lines = [
|
|
line for line in lines if not line.strip().startswith(package_name)
|
|
]
|
|
|
|
update_file(req_file, "\n".join(filtered_lines))
|
|
|
|
except Exception as e:
|
|
return {
|
|
"warning": f"Package uninstalled but failed to update requirements.txt: {str(e)}",
|
|
"package": package_name,
|
|
}
|
|
|
|
return {
|
|
"success": result["success"],
|
|
"package": package_name,
|
|
"message": f"Removed {package_name}"
|
|
if result["success"]
|
|
else f"Failed to remove {package_name}",
|
|
"details": result.get("stderr") if not result["success"] else None,
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error removing Python dependency: {str(e)}"}
|
|
|
|
|
|
def _remove_javascript_dependency(
|
|
package_name: str, project_path: str = "."
|
|
) -> Dict[str, Any]:
|
|
"""Remove JavaScript dependency"""
|
|
try:
|
|
manager = DependencyManager()
|
|
|
|
# Use npm uninstall
|
|
uninstall_cmd = manager.package_managers["javascript"]["uninstall_cmd"].copy()
|
|
uninstall_cmd.append(package_name)
|
|
|
|
result = manager._run_command(uninstall_cmd, project_path)
|
|
|
|
return {
|
|
"success": result["success"],
|
|
"package": package_name,
|
|
"message": f"Removed {package_name}"
|
|
if result["success"]
|
|
else f"Failed to remove {package_name}",
|
|
"details": result.get("stderr") if not result["success"] else None,
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error removing JavaScript dependency: {str(e)}"}
|
|
|
|
|
|
def dependency_report(project_path: str = ".") -> Dict[str, Any]:
|
|
"""
|
|
Generate structured dependency analysis
|
|
|
|
Args:
|
|
project_path (str): Path to project directory
|
|
|
|
Returns:
|
|
Dict containing comprehensive dependency report
|
|
"""
|
|
try:
|
|
manager = DependencyManager()
|
|
|
|
# Scan current dependencies
|
|
scan_result = scan_dependencies(project_path)
|
|
|
|
if "error" in scan_result:
|
|
return scan_result
|
|
|
|
# Get installed packages info
|
|
project_types = scan_result.get("project_types", [])
|
|
installed_packages = {}
|
|
|
|
for project_type in project_types:
|
|
if project_type == "python":
|
|
result = manager._run_command(
|
|
manager.package_managers["python"]["list_cmd"], project_path
|
|
)
|
|
if result["success"]:
|
|
try:
|
|
packages = json.loads(result["stdout"])
|
|
installed_packages["python"] = packages
|
|
except json.JSONDecodeError:
|
|
installed_packages["python"] = {
|
|
"error": "Failed to parse pip list output"
|
|
}
|
|
|
|
elif project_type == "javascript":
|
|
result = manager._run_command(
|
|
manager.package_managers["javascript"]["list_cmd"], project_path
|
|
)
|
|
if result["success"]:
|
|
try:
|
|
packages = json.loads(result["stdout"])
|
|
installed_packages["javascript"] = packages
|
|
except json.JSONDecodeError:
|
|
installed_packages["javascript"] = {
|
|
"error": "Failed to parse npm list output"
|
|
}
|
|
|
|
# Check for outdated packages
|
|
outdated_packages = {}
|
|
|
|
for project_type in project_types:
|
|
if project_type == "python":
|
|
result = manager._run_command(
|
|
manager.package_managers["python"]["outdated_cmd"], project_path
|
|
)
|
|
if result["success"]:
|
|
try:
|
|
packages = json.loads(result["stdout"])
|
|
outdated_packages["python"] = packages
|
|
except json.JSONDecodeError:
|
|
outdated_packages["python"] = []
|
|
|
|
# Analyze security vulnerabilities (basic check)
|
|
security_issues = _check_security_issues(scan_result, project_path)
|
|
|
|
# Compile comprehensive report
|
|
report = {
|
|
"project_path": project_path,
|
|
"project_types": project_types,
|
|
"dependency_scan": scan_result,
|
|
"installed_packages": installed_packages,
|
|
"outdated_packages": outdated_packages,
|
|
"security_issues": security_issues,
|
|
"summary": {
|
|
"total_dependencies": scan_result.get("total_dependencies", 0),
|
|
"dependency_files": len(scan_result.get("dependency_files", {})),
|
|
"outdated_count": sum(
|
|
len(v) for v in outdated_packages.values() if isinstance(v, list)
|
|
),
|
|
"security_issues_count": len(security_issues.get("issues", [])),
|
|
},
|
|
}
|
|
|
|
return report
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error generating dependency report: {str(e)}"}
|
|
|
|
|
|
def _check_security_issues(
|
|
scan_result: Dict[str, Any], project_path: str
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Basic security vulnerability check
|
|
|
|
Args:
|
|
scan_result (Dict): Result from dependency scan
|
|
project_path (str): Path to project directory
|
|
|
|
Returns:
|
|
Dict containing security analysis
|
|
"""
|
|
try:
|
|
issues = []
|
|
|
|
# Check for common vulnerable packages (basic list)
|
|
vulnerable_patterns = {
|
|
"python": [
|
|
{
|
|
"name": "pillow",
|
|
"versions": ["<8.1.1"],
|
|
"issue": "PIL vulnerability",
|
|
},
|
|
{
|
|
"name": "urllib3",
|
|
"versions": ["<1.26.5"],
|
|
"issue": "SSL verification bypass",
|
|
},
|
|
{
|
|
"name": "requests",
|
|
"versions": ["<2.25.1"],
|
|
"issue": "Various security issues",
|
|
},
|
|
],
|
|
"javascript": [
|
|
{
|
|
"name": "lodash",
|
|
"versions": ["<4.17.21"],
|
|
"issue": "Prototype pollution",
|
|
},
|
|
{
|
|
"name": "axios",
|
|
"versions": ["<0.21.1"],
|
|
"issue": "SSRF vulnerability",
|
|
},
|
|
{
|
|
"name": "express",
|
|
"versions": ["<4.17.1"],
|
|
"issue": "Various security issues",
|
|
},
|
|
],
|
|
}
|
|
|
|
# Analyze dependencies for known vulnerabilities
|
|
for file_name, file_info in scan_result.get("dependency_files", {}).items():
|
|
project_type = file_info.get("type")
|
|
dependencies = file_info.get("dependencies", [])
|
|
|
|
if project_type in vulnerable_patterns:
|
|
for dep in dependencies:
|
|
if isinstance(dep, dict) and "name" in dep:
|
|
dep_name = dep["name"]
|
|
dep_version = dep.get("version", "")
|
|
|
|
for vuln in vulnerable_patterns[project_type]:
|
|
if dep_name == vuln["name"]:
|
|
# Simple version check (this is basic - real security scanners are much more sophisticated)
|
|
if dep_version and any(
|
|
pattern in dep_version
|
|
for pattern in vuln["versions"]
|
|
):
|
|
issues.append(
|
|
{
|
|
"package": dep_name,
|
|
"version": dep_version,
|
|
"issue": vuln["issue"],
|
|
"severity": "medium", # Default severity
|
|
"file": file_name,
|
|
}
|
|
)
|
|
|
|
return {
|
|
"issues": issues,
|
|
"total_issues": len(issues),
|
|
"note": "This is a basic security check. Use specialized tools like 'safety' (Python) or 'npm audit' (JavaScript) for comprehensive security analysis.",
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error checking security issues: {str(e)}", "issues": []}
|
|
|
|
|
|
def update_all_dependencies(
|
|
project_path: str = ".", dry_run: bool = True
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Update all dependencies to latest versions
|
|
|
|
Args:
|
|
project_path (str): Path to project directory
|
|
dry_run (bool): If True, only show what would be updated
|
|
|
|
Returns:
|
|
Dict containing update results
|
|
"""
|
|
try:
|
|
manager = DependencyManager()
|
|
project_types = manager._detect_project_type(project_path)
|
|
|
|
results = {
|
|
"project_path": project_path,
|
|
"dry_run": dry_run,
|
|
"updates": {},
|
|
}
|
|
|
|
for project_type in project_types:
|
|
if project_type == "python":
|
|
# Get outdated packages
|
|
result = manager._run_command(
|
|
manager.package_managers["python"]["outdated_cmd"], project_path
|
|
)
|
|
|
|
if result["success"]:
|
|
try:
|
|
outdated = json.loads(result["stdout"])
|
|
updates = []
|
|
|
|
for package in outdated:
|
|
package_name = package.get("name")
|
|
current_version = package.get("version")
|
|
latest_version = package.get("latest_version")
|
|
|
|
update_info = {
|
|
"package": package_name,
|
|
"current_version": current_version,
|
|
"latest_version": latest_version,
|
|
"updated": False,
|
|
}
|
|
|
|
if not dry_run:
|
|
# Actually update the package
|
|
install_result = manager._run_command(
|
|
["pip", "install", "--upgrade", package_name],
|
|
project_path,
|
|
)
|
|
update_info["updated"] = install_result["success"]
|
|
if not install_result["success"]:
|
|
update_info["error"] = install_result.get("stderr")
|
|
|
|
updates.append(update_info)
|
|
|
|
results["updates"]["python"] = updates
|
|
|
|
except json.JSONDecodeError:
|
|
results["updates"]["python"] = {
|
|
"error": "Failed to parse outdated packages"
|
|
}
|
|
|
|
elif project_type == "javascript":
|
|
if not dry_run:
|
|
# Run npm update
|
|
result = manager._run_command(["npm", "update"], project_path)
|
|
results["updates"]["javascript"] = {
|
|
"success": result["success"],
|
|
"message": "Ran npm update",
|
|
"details": result.get("stdout") or result.get("stderr"),
|
|
}
|
|
else:
|
|
results["updates"]["javascript"] = {
|
|
"message": "Would run npm update"
|
|
}
|
|
|
|
return results
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error updating dependencies: {str(e)}"}
|
|
|
|
|
|
def create_lock_file(project_path: str = ".") -> Dict[str, Any]:
|
|
"""
|
|
Create or update lock files for dependency pinning
|
|
|
|
Args:
|
|
project_path (str): Path to project directory
|
|
|
|
Returns:
|
|
Dict containing lock file creation results
|
|
"""
|
|
try:
|
|
manager = DependencyManager()
|
|
project_types = manager._detect_project_type(project_path)
|
|
|
|
results = {"project_path": project_path, "lock_files": {}}
|
|
|
|
for project_type in project_types:
|
|
if project_type == "python":
|
|
# Generate requirements-lock.txt with exact versions
|
|
result = manager._run_command(["pip", "freeze"], project_path)
|
|
|
|
if result["success"]:
|
|
lock_file = os.path.join(project_path, "requirements-lock.txt")
|
|
success = create_file(lock_file, result["stdout"])
|
|
|
|
results["lock_files"]["requirements-lock.txt"] = {
|
|
"created": success,
|
|
"path": lock_file,
|
|
}
|
|
|
|
elif project_type == "javascript":
|
|
# package-lock.json is created automatically by npm
|
|
lock_file = os.path.join(project_path, "package-lock.json")
|
|
results["lock_files"]["package-lock.json"] = {
|
|
"exists": os.path.exists(lock_file),
|
|
"path": lock_file,
|
|
}
|
|
|
|
return results
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error creating lock files: {str(e)}"}
|
|
|
|
|
|
# Example usage
|
|
if __name__ == "__main__":
|
|
# Scan project dependencies
|
|
scan_result = scan_dependencies(".")
|
|
print(f"Dependencies found: {scan_result}")
|
|
|
|
# Generate comprehensive report
|
|
report = dependency_report(".")
|
|
print(f"Dependency report: {report}")
|