mirror of
https://github.com/aljazceru/Auditor.git
synced 2025-12-17 03:24:18 +01:00
153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
"""Tool version detection and reporting."""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
def detect_tool_version(cmd: list[str]) -> str:
|
|
"""
|
|
Detect tool version by running command.
|
|
|
|
Returns version string or "missing" if not found.
|
|
"""
|
|
try:
|
|
# Use temp files to avoid buffer overflow
|
|
with tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='_stdout.txt', encoding='utf-8') as stdout_fp, \
|
|
tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='_stderr.txt', encoding='utf-8') as stderr_fp:
|
|
|
|
stdout_path = stdout_fp.name
|
|
stderr_path = stderr_fp.name
|
|
|
|
result = subprocess.run(cmd, stdout=stdout_fp, stderr=stderr_fp, text=True, timeout=5, check=False)
|
|
|
|
# Read the outputs back
|
|
with open(stdout_path, 'r', encoding='utf-8') as f:
|
|
result.stdout = f.read()
|
|
with open(stderr_path, 'r', encoding='utf-8') as f:
|
|
result.stderr = f.read()
|
|
|
|
# Clean up temp files
|
|
os.unlink(stdout_path)
|
|
os.unlink(stderr_path)
|
|
|
|
if result.returncode == 0:
|
|
output = result.stdout.strip()
|
|
# Extract version from common patterns
|
|
# Handle formats like "ruff 0.1.0", "black, 23.1.0", "mypy 1.0.0", "Version 5.0.0"
|
|
import re
|
|
|
|
# Look for version patterns like x.y.z
|
|
version_pattern = r"(\d+\.\d+(?:\.\d+)?)"
|
|
match = re.search(version_pattern, output)
|
|
if match:
|
|
return match.group(1)
|
|
|
|
# Fallback to simple version extraction
|
|
parts = output.split()
|
|
for part in parts:
|
|
if any(c.isdigit() for c in part):
|
|
# Found version-like string
|
|
version = part.strip(",").strip()
|
|
# Clean up common prefixes
|
|
if version.startswith("v"):
|
|
version = version[1:]
|
|
return version
|
|
|
|
return output.split()[0] if output else "unknown"
|
|
return "missing"
|
|
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
return "missing"
|
|
|
|
|
|
def write_tools_report(out_dir: str) -> dict[str, Any]:
|
|
"""
|
|
Detect all tool versions and write reports.
|
|
|
|
Writes:
|
|
- {out_dir}/TOOLS.md - Human-readable markdown
|
|
- {out_dir}/tools.json - Machine-readable JSON
|
|
|
|
Returns the tools dictionary.
|
|
"""
|
|
# Ensure output directory exists
|
|
Path(out_dir).mkdir(parents=True, exist_ok=True)
|
|
|
|
# Detect Python tools
|
|
python_tools = {
|
|
"ruff": detect_tool_version(["ruff", "--version"]),
|
|
"black": detect_tool_version(["black", "--version"]),
|
|
"pytest": detect_tool_version(["pytest", "--version"]),
|
|
"mypy": detect_tool_version(["mypy", "--version"]),
|
|
}
|
|
|
|
# Detect Node tools from TheAuditor's sandboxed environment
|
|
# Check in .auditor_venv/.theauditor_tools/ for our bundled tools
|
|
sandbox_base = Path(".auditor_venv/.theauditor_tools")
|
|
|
|
# Try sandboxed tools first, fallback to system
|
|
if sandbox_base.exists():
|
|
# Use our sandboxed Node.js and tools
|
|
node_exe = sandbox_base / "node-runtime" / ("node.exe" if os.name == "nt" else "bin/node")
|
|
npx_exe = sandbox_base / "node-runtime" / ("npx.cmd" if os.name == "nt" else "bin/npx")
|
|
|
|
if node_exe.exists() and npx_exe.exists():
|
|
# Run npx from sandbox with proper paths
|
|
node_tools = {
|
|
"eslint": detect_tool_version([str(npx_exe), "--prefix", str(sandbox_base), "eslint", "--version"]),
|
|
"typescript": detect_tool_version([str(npx_exe), "--prefix", str(sandbox_base), "tsc", "--version"]),
|
|
"prettier": detect_tool_version([str(npx_exe), "--prefix", str(sandbox_base), "prettier", "--version"]),
|
|
}
|
|
else:
|
|
# Sandbox exists but Node not found
|
|
node_tools = {
|
|
"eslint": "missing (sandbox incomplete)",
|
|
"typescript": "missing (sandbox incomplete)",
|
|
"prettier": "missing (sandbox incomplete)",
|
|
}
|
|
else:
|
|
# No sandbox, check system (fallback)
|
|
node_tools = {
|
|
"eslint": detect_tool_version(["npx", "eslint", "--version"]),
|
|
"typescript": detect_tool_version(["npx", "tsc", "--version"]),
|
|
"prettier": detect_tool_version(["npx", "prettier", "--version"]),
|
|
}
|
|
|
|
# Build report structure
|
|
tools = {
|
|
"python": python_tools,
|
|
"node": node_tools,
|
|
}
|
|
|
|
# Write JSON (machine-readable)
|
|
json_path = Path(out_dir) / "tools.json"
|
|
with open(json_path, "w") as f:
|
|
json.dump(tools, f, indent=2, sort_keys=True)
|
|
|
|
# Write Markdown (human-readable)
|
|
md_path = Path(out_dir) / "TOOLS.md"
|
|
with open(md_path, "w") as f:
|
|
f.write("# Tool Versions Report\n\n")
|
|
|
|
f.write("## Python Tools\n\n")
|
|
f.write("| Tool | Version |\n")
|
|
f.write("|------|--------|\n")
|
|
for tool, version in sorted(python_tools.items()):
|
|
status = "[OK]" if version != "missing" else "[MISSING]"
|
|
f.write(f"| {tool} | {status} {version} |\n")
|
|
|
|
f.write("\n## Node Tools\n\n")
|
|
f.write("| Tool | Version |\n")
|
|
f.write("|------|--------|\n")
|
|
for tool, version in sorted(node_tools.items()):
|
|
status = "[OK]" if version != "missing" else "[MISSING]"
|
|
f.write(f"| {tool} | {status} {version} |\n")
|
|
|
|
f.write("\n---\n")
|
|
f.write("*Generated by TheAuditor*\n")
|
|
|
|
return tools
|