Skip to content

Commit 357ae39

Browse files
CybotTMclaude
andcommitted
refactor(detection): consolidate on version_flag/version_command, remove duplication
- Replace version_args with existing version_flag field - Implement support for both version_flag and version_command from catalog - Add version_flag to curlie, go, gam catalog entries - Remove hardcoded special cases for go, gam, git-absorb (now catalog-driven) - Preserve complex special cases (entr, fx, docker-compose) Fixes design issue where version_flag and version_command existed in catalog but were never implemented, leading to duplicate version_args field and continued hardcoded special cases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 46bca69 commit 357ae39

5 files changed

Lines changed: 40 additions & 30 deletions

File tree

audit.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,17 @@ def audit_tool(tool: Tool, offline_cache: dict[str, tuple[str, str]] | None = No
124124
# Load catalog metadata for version detection
125125
from cli_audit.catalog import ToolCatalog
126126
catalog = ToolCatalog()
127-
version_args = None
127+
version_flag = None
128+
version_command = None
128129
if catalog.has_tool(tool.name):
129130
catalog_data = catalog.get_raw_data(tool.name)
130-
version_args = catalog_data.get("version_args")
131+
version_flag = catalog_data.get("version_flag")
132+
version_command = catalog_data.get("version_command")
131133

132134
# Detect installed version
133135
deep_scan = tool.name in {"node", "python", "semgrep", "pre-commit", "bandit", "black", "flake8", "isort"}
134136
version_num, version_line, path, install_method = audit_tool_installation(
135-
tool.name, tool.candidates, deep=deep_scan, version_args=version_args
137+
tool.name, tool.candidates, deep=deep_scan, version_flag=version_flag, version_command=version_command
136138
)
137139

138140
installed = version_num if version_num else (version_line if version_line != "X" else "")

catalog/curlie.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"homepage": "https://github.com/rs/curlie",
66
"github_repo": "rs/curlie",
77
"binary_name": "curlie",
8-
"version_args": ["version"],
8+
"version_flag": "version",
99
"download_url_template": "https://github.com/rs/curlie/releases/download/{version}/curlie_{version_nov}_linux_{arch}.tar.gz",
1010
"arch_map": {
1111
"x86_64": "amd64",

catalog/gam.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"homepage": "https://github.com/GAM-team/GAM",
66
"github_repo": "GAM-team/GAM",
77
"binary_name": "gam",
8+
"version_flag": "version",
89
"package_name": "gam7",
910
"notes": "Version detected via uv tool list. Binary requires googleapiclient dependencies and cannot run standalone for version check."
1011
}

catalog/go.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"description": "Go programming language",
55
"homepage": "https://go.dev",
66
"binary_name": "go",
7+
"version_flag": "version",
78
"script": "install_go.sh"
89
,"guide": {
910
"display_name": "Go toolchain",

cli_audit/detection.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -144,37 +144,49 @@ def extract_version_number(s: str) -> str:
144144
return m2.group(1) if m2 else ""
145145

146146

147-
def get_version_line(path: str, tool_name: str, version_args: list[str] | None = None) -> str:
147+
def get_version_line(path: str, tool_name: str, version_flag: str | None = None, version_command: str | None = None) -> str:
148148
"""Get version string for installed tool.
149149
150150
Args:
151151
path: Path to executable
152152
tool_name: Tool name for special-case handling
153-
version_args: Custom version arguments from catalog (e.g., ["version"])
153+
version_flag: Custom version flag/arg from catalog (e.g., "version", "--version")
154+
version_command: Custom shell command from catalog (ignores path)
154155
155156
Returns:
156157
Version line string or empty string
157158
"""
158-
# If catalog specifies custom version args, use them first
159-
if version_args:
160-
line = run_with_timeout([path] + version_args)
159+
# Priority 1: If catalog specifies custom shell command, run it directly
160+
if version_command:
161+
try:
162+
proc = subprocess.run(
163+
version_command,
164+
shell=True,
165+
stdout=subprocess.PIPE,
166+
stderr=subprocess.DEVNULL,
167+
stdin=subprocess.DEVNULL,
168+
text=True,
169+
timeout=TIMEOUT_SECONDS,
170+
check=False,
171+
env={**os.environ, "TERM": "dumb"},
172+
)
173+
line = (proc.stdout or "").strip()
174+
if line:
175+
return line
176+
except Exception:
177+
pass
178+
179+
# Priority 2: If catalog specifies custom version flag, use it
180+
if version_flag:
181+
line = run_with_timeout([path, version_flag])
161182
if line:
162183
return line
163184

164-
# Special cases
165-
if tool_name == "go":
166-
line = run_with_timeout([path, "version"])
167-
return line if line else ""
168-
185+
# Priority 3: Special cases (only truly complex cases that can't be handled by catalog)
169186
if tool_name == "sponge":
170-
# sponge reads stdin and can block
187+
# sponge reads stdin and can block - catalog has version_command to query dpkg
171188
return "installed"
172189

173-
if tool_name == "git-absorb":
174-
# git-absorb outputs WARN messages without --version flag
175-
line = run_with_timeout([path, "--version"])
176-
return line if line else ""
177-
178190
if tool_name == "docker-compose":
179191
base = os.path.basename(path)
180192
if base == "docker":
@@ -228,13 +240,6 @@ def get_version_line(path: str, tool_name: str, version_args: list[str] | None =
228240
pass
229241
return "installed"
230242

231-
if tool_name == "gam":
232-
# gam often has import errors - try version command specifically
233-
line = run_with_timeout([path, "version"])
234-
if line and not line.startswith("Traceback"):
235-
return line
236-
return ""
237-
238243
# Generic version flags
239244
for flags in VERSION_FLAG_SETS:
240245
line = run_with_timeout([path, *flags])
@@ -322,15 +327,16 @@ def choose_highest(candidates: list[tuple[str, str, str]]) -> tuple[str, str, st
322327

323328

324329
def audit_tool_installation(
325-
tool_name: str, candidates: tuple[str, ...], deep: bool = False, version_args: list[str] | None = None
330+
tool_name: str, candidates: tuple[str, ...], deep: bool = False, version_flag: str | None = None, version_command: str | None = None
326331
) -> tuple[str, str, str, str]:
327332
"""Audit a single tool's installation.
328333
329334
Args:
330335
tool_name: Tool name
331336
candidates: Binary names to search for
332337
deep: If True, find all installations
333-
version_args: Custom version arguments from catalog (e.g., ["version"])
338+
version_flag: Custom version flag from catalog (e.g., "version", "--version")
339+
version_command: Custom shell command from catalog
334340
335341
Returns:
336342
Tuple of (version_num, version_line, path, install_method)
@@ -339,7 +345,7 @@ def audit_tool_installation(
339345

340346
for cand in candidates:
341347
for path in find_paths(cand, deep=deep):
342-
line = get_version_line(path, tool_name, version_args)
348+
line = get_version_line(path, tool_name, version_flag, version_command)
343349
if line:
344350
num = extract_version_number(line)
345351
tuples.append((num, line, path))

0 commit comments

Comments
 (0)