Skip to content

Commit 0c484e6

Browse files
committed
feat(audit): add --versions flag for multi-version runtime status
Adds a new --versions flag to audit.py that displays: - All configured multi-version runtimes (PHP, Python, Node.js, Ruby, Go) - Supported version cycles from endoflife.date - Which versions are installed vs available - Active/security support status - Outdated version indicators with upgrade targets Supports filtering to specific runtimes: audit.py --versions php
1 parent 06a4213 commit 0c484e6

1 file changed

Lines changed: 117 additions & 3 deletions

File tree

audit.py

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525

2626
# Import modules
2727
from cli_audit.tools import Tool, all_tools, filter_tools, tool_homepage_url, latest_target_url # noqa: E402
28-
from cli_audit.detection import audit_tool_installation # noqa: E402
28+
from cli_audit.detection import audit_tool_installation, detect_multi_versions # noqa: E402
2929
from cli_audit.snapshot import load_snapshot, write_snapshot, render_from_snapshot, get_snapshot_path # noqa: E402
3030
from cli_audit.render import render_table, print_summary, status_icon # noqa: E402
31-
from cli_audit.collectors import get_github_rate_limit, get_github_rate_limit_help, get_gitlab_rate_limit, is_wsl # noqa: E402
31+
from cli_audit.collectors import get_github_rate_limit, get_github_rate_limit_help, get_gitlab_rate_limit, is_wsl, collect_endoflife # noqa: E402
3232
from cli_audit import collectors # noqa: E402
3333
from cli_audit.logging_config import setup_logging # noqa: E402
3434
# Split file support (Phase 2.1)
@@ -806,6 +806,113 @@ def cmd_upgrade(args: argparse.Namespace) -> int:
806806
return 1
807807

808808

809+
def cmd_versions(args: argparse.Namespace) -> int:
810+
"""Show multi-version runtime status.
811+
812+
Displays all runtimes that support multiple concurrent versions (PHP, Python,
813+
Node.js, Ruby, Go) and shows which versions are installed vs available.
814+
"""
815+
from cli_audit.catalog import ToolCatalog
816+
817+
# ANSI colors
818+
GREEN = "\033[32m"
819+
YELLOW = "\033[33m"
820+
RED = "\033[31m"
821+
BLUE = "\033[34m"
822+
BOLD = "\033[1m"
823+
RESET = "\033[0m"
824+
825+
print("=" * 80, file=sys.stderr)
826+
print("Runtime Versions", file=sys.stderr)
827+
print("=" * 80, file=sys.stderr)
828+
print("", file=sys.stderr)
829+
830+
catalog = ToolCatalog()
831+
832+
# Find all tools with multi_version enabled
833+
multi_version_tools = []
834+
for tool_name in catalog.all_tools():
835+
data = catalog.get_raw_data(tool_name)
836+
mv_config = data.get("multi_version", {})
837+
if mv_config.get("enabled"):
838+
multi_version_tools.append((tool_name, data, mv_config))
839+
840+
if not multi_version_tools:
841+
print("No multi-version runtimes configured.", file=sys.stderr)
842+
return 0
843+
844+
# Filter to specific tools if requested
845+
if args.tools:
846+
requested = set(args.tools)
847+
multi_version_tools = [
848+
(name, data, config) for name, data, config in multi_version_tools
849+
if name in requested
850+
]
851+
852+
# Process each runtime
853+
for tool_name, data, mv_config in multi_version_tools:
854+
product = mv_config.get("product", tool_name)
855+
max_versions = mv_config.get("max_versions", 4)
856+
display_name = data.get("description", tool_name.upper())
857+
858+
print(f"{BOLD}🔧 {display_name}{RESET}", file=sys.stderr)
859+
print("-" * 40, file=sys.stderr)
860+
861+
# Fetch supported versions from endoflife.date
862+
try:
863+
supported = collect_endoflife(product, max_versions=max_versions)
864+
except Exception as e:
865+
print(f" {RED}✗ Failed to fetch version info: {e}{RESET}", file=sys.stderr)
866+
print("", file=sys.stderr)
867+
continue
868+
869+
if not supported:
870+
print(f" {YELLOW}⚠ No supported versions found{RESET}", file=sys.stderr)
871+
print("", file=sys.stderr)
872+
continue
873+
874+
# Detect installed versions
875+
detected = detect_multi_versions(tool_name, mv_config, supported)
876+
877+
# Display results
878+
for version_info in detected:
879+
cycle = version_info.get("cycle", "?")
880+
latest = version_info.get("latest_upstream", "")
881+
installed = version_info.get("installed")
882+
status = version_info.get("status", "unknown")
883+
path = version_info.get("path", "")
884+
method = version_info.get("install_method", "")
885+
886+
# Status indicator
887+
if status == "active":
888+
status_str = f"{GREEN}active{RESET}"
889+
elif status == "security":
890+
status_str = f"{YELLOW}security{RESET}"
891+
else:
892+
status_str = f"{RED}eol{RESET}"
893+
894+
# Installed indicator
895+
if installed:
896+
if installed == latest:
897+
inst_str = f"{GREEN}{installed}{RESET}"
898+
else:
899+
inst_str = f"{YELLOW}{installed}{RESET}{GREEN}{latest}{RESET}"
900+
method_str = f" ({method})" if method else ""
901+
else:
902+
inst_str = f"{BLUE}○ not installed{RESET}"
903+
method_str = ""
904+
905+
print(f" {cycle:6} [{status_str:16}] {inst_str}{method_str}", file=sys.stderr)
906+
907+
print("", file=sys.stderr)
908+
909+
# Summary
910+
print("=" * 80, file=sys.stderr)
911+
print(f"Total: {len(multi_version_tools)} runtimes configured", file=sys.stderr)
912+
913+
return 0
914+
915+
809916
def main() -> int:
810917
"""Main entry point for audit system."""
811918
parser = argparse.ArgumentParser(
@@ -838,6 +945,11 @@ def main() -> int:
838945
action="store_true",
839946
help="Upgrade outdated tools",
840947
)
948+
parser.add_argument(
949+
"--versions",
950+
action="store_true",
951+
help="Show multi-version runtime status (PHP, Python, Node.js, Ruby, Go)",
952+
)
841953
parser.add_argument(
842954
"--verbose", "-v",
843955
action="store_true",
@@ -855,7 +967,9 @@ def main() -> int:
855967
setup_logging(verbose=args.verbose)
856968

857969
# Route to appropriate command
858-
if args.update:
970+
if args.versions:
971+
return cmd_versions(args)
972+
elif args.update:
859973
# Explicit --update flag: full update of all tools
860974
return cmd_update(args)
861975
elif getattr(args, 'update_local', False) or UPDATE_LOCAL_ONLY:

0 commit comments

Comments
 (0)