code-aide 1.12.2__tar.gz → 1.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {code_aide-1.12.2 → code_aide-1.13.0}/PKG-INFO +1 -1
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/__init__.py +1 -1
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/commands_tools.py +7 -6
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/config.py +17 -5
- code_aide-1.13.0/src/code_aide/constants.py +51 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/operations.py +40 -0
- code_aide-1.13.0/src/code_aide/package_managers.py +188 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/prereqs.py +19 -28
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_commands_tools.py +2 -2
- code_aide-1.13.0/tests/test_package_managers.py +277 -0
- code_aide-1.12.2/src/code_aide/constants.py +0 -104
- {code_aide-1.12.2 → code_aide-1.13.0}/.github/workflows/ci.yml +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/.github/workflows/publish.yml +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/.gitignore +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/.gitlab-ci.yml +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/.pre-commit-config.yaml +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/AGENTS.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/CLAUDE.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/LICENSE +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/README.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/TODO.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/pyproject.toml +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/script-archive/README.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/script-archive/amp-install.sh +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/script-archive/claude-install.sh +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/script-archive/cursor-install.sh +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/specs/auto-migrate-deprecated-installs.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/specs/claude-native-installer-migration.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/specs/missing-coding-llm-cli-tools.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/specs/pre-commit-uv-setup.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/specs/remove-bundled-version-baseline.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/specs/unify-upgrade-eligibility-with-shared-evaluator.md +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/__main__.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/commands_actions.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/console.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/data/tools.json +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/detection.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/entry.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/install.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/install_types.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/status.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/src/code_aide/versions.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_commands_actions.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_config.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_console.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_constants.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_detection.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_install.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_install_types.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_operations.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_status.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/tests/test_versions.py +0 -0
- {code_aide-1.12.2 → code_aide-1.13.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-aide
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.13.0
|
|
4
4
|
Summary: Manage AI coding CLI tools (Claude, Copilot, Cursor, Gemini, Amp, Codex)
|
|
5
5
|
Project-URL: Homepage, https://github.com/dajobe/code-aide
|
|
6
6
|
Project-URL: Repository, https://github.com/dajobe/code-aide
|
|
@@ -8,7 +8,7 @@ import shutil
|
|
|
8
8
|
from typing import List
|
|
9
9
|
|
|
10
10
|
from code_aide.config import ensure_versions_cache
|
|
11
|
-
from code_aide.constants import Colors,
|
|
11
|
+
from code_aide.constants import Colors, TOOLS
|
|
12
12
|
from code_aide.detection import (
|
|
13
13
|
format_install_method,
|
|
14
14
|
format_migration_warning,
|
|
@@ -20,7 +20,10 @@ from code_aide.install_types import (
|
|
|
20
20
|
parse_install_method,
|
|
21
21
|
parse_install_type,
|
|
22
22
|
)
|
|
23
|
-
from code_aide.
|
|
23
|
+
from code_aide.package_managers import (
|
|
24
|
+
detect_package_manager as _detect_package_manager,
|
|
25
|
+
)
|
|
26
|
+
from code_aide.prereqs import is_tool_installed
|
|
24
27
|
from code_aide.detection import is_freebsd
|
|
25
28
|
from code_aide.status import (
|
|
26
29
|
print_brew_version_status,
|
|
@@ -107,11 +110,9 @@ def cmd_list(args: argparse.Namespace) -> None:
|
|
|
107
110
|
except Exception:
|
|
108
111
|
pass
|
|
109
112
|
|
|
110
|
-
pkg_mgr =
|
|
113
|
+
pkg_mgr = _detect_package_manager()
|
|
111
114
|
if pkg_mgr:
|
|
112
|
-
print(
|
|
113
|
-
f" Package manager: {PACKAGE_MANAGERS[pkg_mgr]['description']} ({pkg_mgr})"
|
|
114
|
-
)
|
|
115
|
+
print(f" Package manager: {pkg_mgr.description} ({pkg_mgr.detect_command})")
|
|
115
116
|
|
|
116
117
|
|
|
117
118
|
def _short_install_method(method: str | None) -> str:
|
|
@@ -135,15 +135,27 @@ def save_updated_versions(tools: dict) -> None:
|
|
|
135
135
|
save_versions_cache(cache_data)
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
def versions_cache_is_fresh() -> bool:
|
|
139
|
-
"""Return True if the versions cache exists
|
|
138
|
+
def versions_cache_is_fresh(tools: dict) -> bool:
|
|
139
|
+
"""Return True if the versions cache exists, is recent, and complete."""
|
|
140
140
|
cache_path = get_versions_cache_path()
|
|
141
141
|
try:
|
|
142
142
|
age = time.time() - os.path.getmtime(cache_path)
|
|
143
|
-
|
|
143
|
+
if age >= CACHE_MAX_AGE_SECONDS:
|
|
144
|
+
return False
|
|
144
145
|
except OSError:
|
|
145
146
|
return False
|
|
146
147
|
|
|
148
|
+
# Check that every versionable tool has latest_version populated.
|
|
149
|
+
for tool_config in tools.values():
|
|
150
|
+
install_type = parse_install_type(tool_config.get("install_type"))
|
|
151
|
+
if install_type == InstallType.NPM:
|
|
152
|
+
if not tool_config.get("latest_version"):
|
|
153
|
+
return False
|
|
154
|
+
elif install_type in (InstallType.SCRIPT, InstallType.DIRECT_DOWNLOAD):
|
|
155
|
+
if tool_config.get("version_url") and not tool_config.get("latest_version"):
|
|
156
|
+
return False
|
|
157
|
+
return True
|
|
158
|
+
|
|
147
159
|
|
|
148
160
|
def refresh_versions_cache(tools: dict) -> None:
|
|
149
161
|
"""Fetch latest versions from upstream and update tools dict in-place.
|
|
@@ -187,6 +199,6 @@ def refresh_versions_cache(tools: dict) -> None:
|
|
|
187
199
|
|
|
188
200
|
|
|
189
201
|
def ensure_versions_cache(tools: dict) -> None:
|
|
190
|
-
"""Refresh versions cache if missing
|
|
191
|
-
if not versions_cache_is_fresh():
|
|
202
|
+
"""Refresh versions cache if missing, stale, or incomplete."""
|
|
203
|
+
if not versions_cache_is_fresh(tools):
|
|
192
204
|
refresh_versions_cache(tools)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Shared constants and mutable tool configuration for CLI modules."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from code_aide.config import load_tools_config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _use_color() -> bool:
|
|
11
|
+
"""Determine whether to emit ANSI color codes.
|
|
12
|
+
|
|
13
|
+
Checks (in priority order):
|
|
14
|
+
NO_COLOR — if set (any value), no color (https://no-color.org/)
|
|
15
|
+
FORCE_COLOR — if set (any value), force color
|
|
16
|
+
CLICOLOR_FORCE — if non-empty, force color
|
|
17
|
+
TERM=dumb — no color
|
|
18
|
+
CLICOLOR=0 — no color
|
|
19
|
+
stdout is not a TTY — no color
|
|
20
|
+
"""
|
|
21
|
+
if os.environ.get("NO_COLOR") is not None:
|
|
22
|
+
return False
|
|
23
|
+
if os.environ.get("FORCE_COLOR") is not None:
|
|
24
|
+
return True
|
|
25
|
+
if os.environ.get("CLICOLOR_FORCE", ""):
|
|
26
|
+
return True
|
|
27
|
+
if os.environ.get("TERM") == "dumb":
|
|
28
|
+
return False
|
|
29
|
+
if os.environ.get("CLICOLOR") == "0":
|
|
30
|
+
return False
|
|
31
|
+
return sys.stdout.isatty()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Colors:
|
|
35
|
+
if _use_color():
|
|
36
|
+
RED = "\033[0;31m"
|
|
37
|
+
GREEN = "\033[0;32m"
|
|
38
|
+
YELLOW = "\033[1;33m"
|
|
39
|
+
BLUE = "\033[0;34m"
|
|
40
|
+
BOLD = "\033[1m"
|
|
41
|
+
NC = "\033[0m"
|
|
42
|
+
else:
|
|
43
|
+
RED = ""
|
|
44
|
+
GREEN = ""
|
|
45
|
+
YELLOW = ""
|
|
46
|
+
BLUE = ""
|
|
47
|
+
BOLD = ""
|
|
48
|
+
NC = ""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
TOOLS: Dict[str, Dict[str, Any]] = load_tools_config()
|
|
@@ -14,6 +14,7 @@ from code_aide.detection import (
|
|
|
14
14
|
format_install_method,
|
|
15
15
|
is_deprecated_install,
|
|
16
16
|
)
|
|
17
|
+
from code_aide.package_managers import query_package_owner
|
|
17
18
|
from code_aide.install import (
|
|
18
19
|
install_direct_download,
|
|
19
20
|
install_tool,
|
|
@@ -83,6 +84,44 @@ def _upgrade_result_from_snapshots(
|
|
|
83
84
|
return UpgradeResult.CHANGED
|
|
84
85
|
|
|
85
86
|
|
|
87
|
+
def _warn_duplicate_system_install(tool_name: str) -> None:
|
|
88
|
+
"""Warn if a duplicate system-packaged binary shadows or coexists."""
|
|
89
|
+
tool_config = TOOLS[tool_name]
|
|
90
|
+
command = tool_config["command"]
|
|
91
|
+
|
|
92
|
+
# Find all instances of the command in PATH
|
|
93
|
+
seen = set()
|
|
94
|
+
paths = []
|
|
95
|
+
for directory in os.environ.get("PATH", "").split(os.pathsep):
|
|
96
|
+
candidate = os.path.join(directory, command)
|
|
97
|
+
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
|
98
|
+
real = os.path.realpath(candidate)
|
|
99
|
+
if real not in seen:
|
|
100
|
+
seen.add(real)
|
|
101
|
+
paths.append(real)
|
|
102
|
+
|
|
103
|
+
if len(paths) < 2:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
system_prefixes = ("/opt/", "/usr/bin/", "/usr/sbin/", "/usr/local/bin/")
|
|
107
|
+
for path in paths:
|
|
108
|
+
if not any(path.startswith(p) for p in system_prefixes):
|
|
109
|
+
continue
|
|
110
|
+
package, remove_cmd = query_package_owner(path)
|
|
111
|
+
if package and remove_cmd:
|
|
112
|
+
warning(
|
|
113
|
+
f"A system-packaged {command} is also installed at {path} "
|
|
114
|
+
f"(package: {package})."
|
|
115
|
+
)
|
|
116
|
+
info(f"To remove it, run: {remove_cmd}")
|
|
117
|
+
else:
|
|
118
|
+
warning(
|
|
119
|
+
f"A system-packaged {command} is also installed at {path}. "
|
|
120
|
+
"You may want to remove it with your package manager."
|
|
121
|
+
)
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
|
|
86
125
|
def _migrate_install_method(tool_name: str) -> UpgradeResult:
|
|
87
126
|
"""Migrate a tool from a deprecated install method to the configured one.
|
|
88
127
|
|
|
@@ -129,6 +168,7 @@ def _migrate_install_method(tool_name: str) -> UpgradeResult:
|
|
|
129
168
|
return UpgradeResult.FAILED
|
|
130
169
|
|
|
131
170
|
success(f"{tool_config['name']} migrated from {old_label} to {new_label}")
|
|
171
|
+
_warn_duplicate_system_install(tool_name)
|
|
132
172
|
return UpgradeResult.CHANGED
|
|
133
173
|
|
|
134
174
|
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""System package manager definitions and helpers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PackageManager(Enum):
|
|
13
|
+
"""Known system package managers."""
|
|
14
|
+
|
|
15
|
+
APT = "apt-get"
|
|
16
|
+
DNF = "dnf"
|
|
17
|
+
YUM = "yum"
|
|
18
|
+
PACMAN = "pacman"
|
|
19
|
+
ZYPPER = "zypper"
|
|
20
|
+
EMERGE = "emerge"
|
|
21
|
+
PKG = "pkg"
|
|
22
|
+
HOMEBREW = "brew"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class PackageManagerInfo:
|
|
27
|
+
"""Configuration for a system package manager."""
|
|
28
|
+
|
|
29
|
+
manager: PackageManager
|
|
30
|
+
description: str
|
|
31
|
+
detect_command: str
|
|
32
|
+
packages: List[str]
|
|
33
|
+
install_command: List[str]
|
|
34
|
+
pre_install: List[List[str]] = field(default_factory=list)
|
|
35
|
+
query_owner_command: List[str] = field(default_factory=list)
|
|
36
|
+
remove_command: List[str] = field(default_factory=list)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_MANAGERS: List[PackageManagerInfo] = [
|
|
40
|
+
PackageManagerInfo(
|
|
41
|
+
manager=PackageManager.APT,
|
|
42
|
+
description="Debian/Ubuntu",
|
|
43
|
+
detect_command="apt-get",
|
|
44
|
+
packages=["nodejs", "npm"],
|
|
45
|
+
pre_install=[["sudo", "apt-get", "update"]],
|
|
46
|
+
install_command=["sudo", "apt-get", "install", "-y"],
|
|
47
|
+
query_owner_command=["dpkg", "-S"],
|
|
48
|
+
remove_command=["sudo", "apt-get", "remove"],
|
|
49
|
+
),
|
|
50
|
+
PackageManagerInfo(
|
|
51
|
+
manager=PackageManager.DNF,
|
|
52
|
+
description="Fedora/RHEL 8+",
|
|
53
|
+
detect_command="dnf",
|
|
54
|
+
packages=["nodejs", "npm"],
|
|
55
|
+
install_command=["sudo", "dnf", "install", "-y"],
|
|
56
|
+
query_owner_command=["rpm", "-qf"],
|
|
57
|
+
remove_command=["sudo", "dnf", "remove"],
|
|
58
|
+
),
|
|
59
|
+
PackageManagerInfo(
|
|
60
|
+
manager=PackageManager.YUM,
|
|
61
|
+
description="RHEL/CentOS 7",
|
|
62
|
+
detect_command="yum",
|
|
63
|
+
packages=["nodejs", "npm"],
|
|
64
|
+
install_command=["sudo", "yum", "install", "-y"],
|
|
65
|
+
query_owner_command=["rpm", "-qf"],
|
|
66
|
+
remove_command=["sudo", "yum", "remove"],
|
|
67
|
+
),
|
|
68
|
+
PackageManagerInfo(
|
|
69
|
+
manager=PackageManager.PACMAN,
|
|
70
|
+
description="Arch Linux",
|
|
71
|
+
detect_command="pacman",
|
|
72
|
+
packages=["nodejs", "npm"],
|
|
73
|
+
install_command=["sudo", "pacman", "-S", "--noconfirm"],
|
|
74
|
+
query_owner_command=["pacman", "-Qo"],
|
|
75
|
+
remove_command=["sudo", "pacman", "-R"],
|
|
76
|
+
),
|
|
77
|
+
PackageManagerInfo(
|
|
78
|
+
manager=PackageManager.ZYPPER,
|
|
79
|
+
description="openSUSE",
|
|
80
|
+
detect_command="zypper",
|
|
81
|
+
packages=["nodejs", "npm"],
|
|
82
|
+
install_command=["sudo", "zypper", "install", "-y"],
|
|
83
|
+
query_owner_command=["rpm", "-qf"],
|
|
84
|
+
remove_command=["sudo", "zypper", "remove"],
|
|
85
|
+
),
|
|
86
|
+
PackageManagerInfo(
|
|
87
|
+
manager=PackageManager.EMERGE,
|
|
88
|
+
description="Gentoo",
|
|
89
|
+
detect_command="emerge",
|
|
90
|
+
packages=["net-libs/nodejs"],
|
|
91
|
+
install_command=["sudo", "emerge", "--quiet-build"],
|
|
92
|
+
query_owner_command=["qfile", "-qC"],
|
|
93
|
+
remove_command=["sudo", "emerge", "--unmerge"],
|
|
94
|
+
),
|
|
95
|
+
PackageManagerInfo(
|
|
96
|
+
manager=PackageManager.PKG,
|
|
97
|
+
description="FreeBSD",
|
|
98
|
+
detect_command="pkg",
|
|
99
|
+
packages=["node22", "npm-node22"],
|
|
100
|
+
install_command=["sudo", "pkg", "install", "-y"],
|
|
101
|
+
query_owner_command=["pkg", "which", "-q"],
|
|
102
|
+
remove_command=["sudo", "pkg", "delete"],
|
|
103
|
+
),
|
|
104
|
+
PackageManagerInfo(
|
|
105
|
+
manager=PackageManager.HOMEBREW,
|
|
106
|
+
description="macOS/Linux (Homebrew)",
|
|
107
|
+
detect_command="brew",
|
|
108
|
+
packages=["node"],
|
|
109
|
+
install_command=["brew", "install"],
|
|
110
|
+
query_owner_command=["brew", "which-formula"],
|
|
111
|
+
remove_command=["brew", "uninstall"],
|
|
112
|
+
),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
PACKAGE_MANAGER_BY_ENUM = {info.manager: info for info in _MANAGERS}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def detect_package_manager() -> Optional[PackageManagerInfo]:
|
|
119
|
+
"""Detect the system package manager, or None on unsupported platforms."""
|
|
120
|
+
if platform.system() not in ("Linux", "FreeBSD", "Darwin"):
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
for info in _MANAGERS:
|
|
124
|
+
if shutil.which(info.detect_command):
|
|
125
|
+
return info
|
|
126
|
+
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _parse_package_name(manager: PackageManager, query_output: str) -> Optional[str]:
|
|
131
|
+
"""Extract a package name from query-owner output."""
|
|
132
|
+
line = query_output.strip().split("\n")[0]
|
|
133
|
+
if not line:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
if manager == PackageManager.APT:
|
|
137
|
+
# dpkg -S output: "package: /path/to/file"
|
|
138
|
+
return line.split(":")[0].strip() or None
|
|
139
|
+
if manager == PackageManager.PACMAN:
|
|
140
|
+
# pacman -Qo output: "/path is owned by package version"
|
|
141
|
+
parts = line.split("is owned by")
|
|
142
|
+
if len(parts) > 1:
|
|
143
|
+
return parts[1].strip().rsplit(" ", 1)[0] or None
|
|
144
|
+
return None
|
|
145
|
+
# rpm -qf (dnf/yum/zypper): "package-version-release.arch"
|
|
146
|
+
# qfile -qC (emerge): "category/package"
|
|
147
|
+
# pkg which -q (FreeBSD): "package-name"
|
|
148
|
+
return line.strip() or None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def query_package_owner(
|
|
152
|
+
binary_path: str,
|
|
153
|
+
) -> tuple:
|
|
154
|
+
"""Identify the system package that owns a binary path.
|
|
155
|
+
|
|
156
|
+
Returns (package_name, remove_command_str) or (None, None).
|
|
157
|
+
"""
|
|
158
|
+
mgr = detect_package_manager()
|
|
159
|
+
if not mgr or not mgr.query_owner_command or not mgr.remove_command:
|
|
160
|
+
return None, None
|
|
161
|
+
|
|
162
|
+
# brew which-formula takes the command name, not the full path.
|
|
163
|
+
query_arg = (
|
|
164
|
+
os.path.basename(binary_path)
|
|
165
|
+
if mgr.manager == PackageManager.HOMEBREW
|
|
166
|
+
else binary_path
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
proc = subprocess.run(
|
|
171
|
+
[*mgr.query_owner_command, query_arg],
|
|
172
|
+
capture_output=True,
|
|
173
|
+
text=True,
|
|
174
|
+
timeout=10,
|
|
175
|
+
check=False,
|
|
176
|
+
stdin=subprocess.DEVNULL,
|
|
177
|
+
)
|
|
178
|
+
if proc.returncode != 0 or not proc.stdout.strip():
|
|
179
|
+
return None, None
|
|
180
|
+
except Exception:
|
|
181
|
+
return None, None
|
|
182
|
+
|
|
183
|
+
package = _parse_package_name(mgr.manager, proc.stdout)
|
|
184
|
+
if not package:
|
|
185
|
+
return None, None
|
|
186
|
+
|
|
187
|
+
remove_cmd = " ".join([*mgr.remove_command, package])
|
|
188
|
+
return package, remove_cmd
|
|
@@ -6,7 +6,7 @@ import subprocess
|
|
|
6
6
|
import sys
|
|
7
7
|
from typing import List, Optional
|
|
8
8
|
|
|
9
|
-
from code_aide.constants import
|
|
9
|
+
from code_aide.constants import TOOLS
|
|
10
10
|
from code_aide.console import (
|
|
11
11
|
command_exists,
|
|
12
12
|
error,
|
|
@@ -15,42 +15,38 @@ from code_aide.console import (
|
|
|
15
15
|
success,
|
|
16
16
|
warning,
|
|
17
17
|
)
|
|
18
|
+
from code_aide.package_managers import (
|
|
19
|
+
_MANAGERS,
|
|
20
|
+
detect_package_manager,
|
|
21
|
+
)
|
|
18
22
|
|
|
19
23
|
|
|
20
|
-
def
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if command_exists(config["detect_command"]):
|
|
27
|
-
return pkg_mgr_name
|
|
28
|
-
|
|
29
|
-
return None
|
|
24
|
+
def _print_all_install_hints() -> None:
|
|
25
|
+
"""Print install commands for all known package managers."""
|
|
26
|
+
for mgr in _MANAGERS:
|
|
27
|
+
install_cmd = " ".join(mgr.install_command + mgr.packages)
|
|
28
|
+
print(f" {mgr.description}: {install_cmd}")
|
|
29
|
+
print(" Or visit: https://nodejs.org/")
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def install_nodejs_npm() -> bool:
|
|
33
33
|
"""Install Node.js and npm using the system package manager."""
|
|
34
|
-
|
|
34
|
+
mgr = detect_package_manager()
|
|
35
35
|
|
|
36
|
-
if not
|
|
36
|
+
if not mgr:
|
|
37
37
|
error("Could not detect package manager. Please install Node.js manually:")
|
|
38
|
-
|
|
39
|
-
install_cmd = " ".join(config["install_command"] + config["packages"])
|
|
40
|
-
print(f" {config['description']}: {install_cmd}")
|
|
41
|
-
print(" Or visit: https://nodejs.org/")
|
|
38
|
+
_print_all_install_hints()
|
|
42
39
|
return False
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
info(f"Detected package manager: {pkg_mgr_name} ({config['description']})")
|
|
41
|
+
info(f"Detected package manager: {mgr.detect_command} ({mgr.description})")
|
|
46
42
|
info("Installing Node.js and npm...")
|
|
47
43
|
|
|
48
44
|
try:
|
|
49
|
-
for pre_cmd in
|
|
45
|
+
for pre_cmd in mgr.pre_install:
|
|
50
46
|
info(f"Running: {' '.join(pre_cmd)}")
|
|
51
|
-
run_command(pre_cmd, check=True, capture=False)
|
|
47
|
+
run_command(list(pre_cmd), check=True, capture=False)
|
|
52
48
|
|
|
53
|
-
install_cmd =
|
|
49
|
+
install_cmd = list(mgr.install_command) + list(mgr.packages)
|
|
54
50
|
run_command(install_cmd, check=True, capture=False)
|
|
55
51
|
|
|
56
52
|
if not command_exists("npm"):
|
|
@@ -102,12 +98,7 @@ def check_prerequisites(
|
|
|
102
98
|
else:
|
|
103
99
|
error("npm is required but not installed.")
|
|
104
100
|
error("Please install Node.js and npm first:")
|
|
105
|
-
|
|
106
|
-
install_cmd = " ".join(
|
|
107
|
-
config["install_command"] + config["packages"]
|
|
108
|
-
)
|
|
109
|
-
print(f" {config['description']}: {install_cmd}")
|
|
110
|
-
print(" Or visit: https://nodejs.org/")
|
|
101
|
+
_print_all_install_hints()
|
|
111
102
|
print("\nOr use -p/--install-prerequisites to install automatically")
|
|
112
103
|
sys.exit(1)
|
|
113
104
|
|
|
@@ -26,7 +26,7 @@ class TestCmdList(unittest.TestCase):
|
|
|
26
26
|
mock.patch.object(commands_tools, "is_tool_installed", return_value=False),
|
|
27
27
|
mock.patch.object(commands_tools, "command_exists", return_value=False),
|
|
28
28
|
mock.patch.object(
|
|
29
|
-
commands_tools, "
|
|
29
|
+
commands_tools, "_detect_package_manager", return_value=None
|
|
30
30
|
),
|
|
31
31
|
):
|
|
32
32
|
buf = io.StringIO()
|
|
@@ -314,7 +314,7 @@ class TestCmdStatus(unittest.TestCase):
|
|
|
314
314
|
),
|
|
315
315
|
mock.patch.object(commands_tools, "command_exists", return_value=False),
|
|
316
316
|
mock.patch.object(
|
|
317
|
-
commands_tools, "
|
|
317
|
+
commands_tools, "_detect_package_manager", return_value=None
|
|
318
318
|
),
|
|
319
319
|
):
|
|
320
320
|
buf = io.StringIO()
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""Unit tests for the package_managers module."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import unittest
|
|
5
|
+
from unittest import mock
|
|
6
|
+
|
|
7
|
+
from code_aide.package_managers import (
|
|
8
|
+
PACKAGE_MANAGER_BY_ENUM,
|
|
9
|
+
PackageManager,
|
|
10
|
+
_MANAGERS,
|
|
11
|
+
_parse_package_name,
|
|
12
|
+
detect_package_manager,
|
|
13
|
+
query_package_owner,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestPackageManagerEnum(unittest.TestCase):
|
|
18
|
+
"""Tests for the PackageManager enum."""
|
|
19
|
+
|
|
20
|
+
def test_all_managers_have_entries(self):
|
|
21
|
+
for mgr in PackageManager:
|
|
22
|
+
self.assertIn(mgr, PACKAGE_MANAGER_BY_ENUM)
|
|
23
|
+
|
|
24
|
+
def test_manager_list_matches_enum(self):
|
|
25
|
+
managers_in_list = {info.manager for info in _MANAGERS}
|
|
26
|
+
self.assertEqual(managers_in_list, set(PackageManager))
|
|
27
|
+
|
|
28
|
+
def test_no_duplicate_detect_commands(self):
|
|
29
|
+
commands = [info.detect_command for info in _MANAGERS]
|
|
30
|
+
self.assertEqual(len(commands), len(set(commands)))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestPackageManagerInfo(unittest.TestCase):
|
|
34
|
+
"""Tests for PackageManagerInfo completeness."""
|
|
35
|
+
|
|
36
|
+
def test_all_managers_have_install_command(self):
|
|
37
|
+
for info in _MANAGERS:
|
|
38
|
+
self.assertTrue(
|
|
39
|
+
len(info.install_command) > 0,
|
|
40
|
+
f"{info.manager.name} missing install_command",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def test_all_managers_have_query_owner_command(self):
|
|
44
|
+
for info in _MANAGERS:
|
|
45
|
+
self.assertTrue(
|
|
46
|
+
len(info.query_owner_command) > 0,
|
|
47
|
+
f"{info.manager.name} missing query_owner_command",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def test_all_managers_have_remove_command(self):
|
|
51
|
+
for info in _MANAGERS:
|
|
52
|
+
self.assertTrue(
|
|
53
|
+
len(info.remove_command) > 0,
|
|
54
|
+
f"{info.manager.name} missing remove_command",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def test_all_managers_have_description(self):
|
|
58
|
+
for info in _MANAGERS:
|
|
59
|
+
self.assertTrue(
|
|
60
|
+
len(info.description) > 0,
|
|
61
|
+
f"{info.manager.name} missing description",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def test_all_managers_have_packages(self):
|
|
65
|
+
for info in _MANAGERS:
|
|
66
|
+
self.assertTrue(
|
|
67
|
+
len(info.packages) > 0,
|
|
68
|
+
f"{info.manager.name} missing packages",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def test_info_is_frozen(self):
|
|
72
|
+
info = PACKAGE_MANAGER_BY_ENUM[PackageManager.APT]
|
|
73
|
+
with self.assertRaises(AttributeError):
|
|
74
|
+
info.description = "changed"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestDetectPackageManager(unittest.TestCase):
|
|
78
|
+
"""Tests for detect_package_manager."""
|
|
79
|
+
|
|
80
|
+
@mock.patch("code_aide.package_managers.platform.system", return_value="Windows")
|
|
81
|
+
def test_returns_none_on_windows(self, _mock_sys):
|
|
82
|
+
self.assertIsNone(detect_package_manager())
|
|
83
|
+
|
|
84
|
+
@mock.patch("code_aide.package_managers.shutil.which", return_value=None)
|
|
85
|
+
@mock.patch("code_aide.package_managers.platform.system", return_value="Linux")
|
|
86
|
+
def test_returns_none_when_no_manager_found(self, _mock_sys, _mock_which):
|
|
87
|
+
self.assertIsNone(detect_package_manager())
|
|
88
|
+
|
|
89
|
+
@mock.patch("code_aide.package_managers.shutil.which")
|
|
90
|
+
@mock.patch("code_aide.package_managers.platform.system", return_value="Linux")
|
|
91
|
+
def test_detects_apt(self, _mock_sys, mock_which):
|
|
92
|
+
mock_which.side_effect = lambda cmd: (
|
|
93
|
+
"/usr/bin/apt-get" if cmd == "apt-get" else None
|
|
94
|
+
)
|
|
95
|
+
result = detect_package_manager()
|
|
96
|
+
self.assertIsNotNone(result)
|
|
97
|
+
self.assertEqual(result.manager, PackageManager.APT)
|
|
98
|
+
|
|
99
|
+
@mock.patch("code_aide.package_managers.shutil.which")
|
|
100
|
+
@mock.patch("code_aide.package_managers.platform.system", return_value="Linux")
|
|
101
|
+
def test_detects_emerge(self, _mock_sys, mock_which):
|
|
102
|
+
mock_which.side_effect = lambda cmd: (
|
|
103
|
+
"/usr/bin/emerge" if cmd == "emerge" else None
|
|
104
|
+
)
|
|
105
|
+
result = detect_package_manager()
|
|
106
|
+
self.assertIsNotNone(result)
|
|
107
|
+
self.assertEqual(result.manager, PackageManager.EMERGE)
|
|
108
|
+
|
|
109
|
+
@mock.patch("code_aide.package_managers.shutil.which")
|
|
110
|
+
@mock.patch("code_aide.package_managers.platform.system", return_value="FreeBSD")
|
|
111
|
+
def test_detects_pkg_on_freebsd(self, _mock_sys, mock_which):
|
|
112
|
+
mock_which.side_effect = lambda cmd: "/usr/sbin/pkg" if cmd == "pkg" else None
|
|
113
|
+
result = detect_package_manager()
|
|
114
|
+
self.assertIsNotNone(result)
|
|
115
|
+
self.assertEqual(result.manager, PackageManager.PKG)
|
|
116
|
+
|
|
117
|
+
@mock.patch("code_aide.package_managers.shutil.which")
|
|
118
|
+
@mock.patch("code_aide.package_managers.platform.system", return_value="Darwin")
|
|
119
|
+
def test_detects_brew_on_macos(self, _mock_sys, mock_which):
|
|
120
|
+
mock_which.side_effect = lambda cmd: (
|
|
121
|
+
"/opt/homebrew/bin/brew" if cmd == "brew" else None
|
|
122
|
+
)
|
|
123
|
+
result = detect_package_manager()
|
|
124
|
+
self.assertIsNotNone(result)
|
|
125
|
+
self.assertEqual(result.manager, PackageManager.HOMEBREW)
|
|
126
|
+
|
|
127
|
+
@mock.patch("code_aide.package_managers.shutil.which")
|
|
128
|
+
@mock.patch("code_aide.package_managers.platform.system", return_value="Linux")
|
|
129
|
+
def test_returns_first_match(self, _mock_sys, mock_which):
|
|
130
|
+
# apt-get is first in the list
|
|
131
|
+
mock_which.side_effect = lambda cmd: f"/usr/bin/{cmd}"
|
|
132
|
+
result = detect_package_manager()
|
|
133
|
+
self.assertEqual(result.manager, PackageManager.APT)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class TestParsePackageName(unittest.TestCase):
|
|
137
|
+
"""Tests for _parse_package_name output parsing."""
|
|
138
|
+
|
|
139
|
+
def test_apt_dpkg_output(self):
|
|
140
|
+
self.assertEqual(
|
|
141
|
+
_parse_package_name(PackageManager.APT, "libfoo:amd64: /usr/lib/libfoo.so"),
|
|
142
|
+
"libfoo",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def test_apt_simple_output(self):
|
|
146
|
+
self.assertEqual(
|
|
147
|
+
_parse_package_name(PackageManager.APT, "claude-code: /usr/bin/claude"),
|
|
148
|
+
"claude-code",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def test_dnf_rpm_output(self):
|
|
152
|
+
self.assertEqual(
|
|
153
|
+
_parse_package_name(PackageManager.DNF, "claude-code-2.1.50-1.x86_64"),
|
|
154
|
+
"claude-code-2.1.50-1.x86_64",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def test_pacman_output(self):
|
|
158
|
+
self.assertEqual(
|
|
159
|
+
_parse_package_name(
|
|
160
|
+
PackageManager.PACMAN,
|
|
161
|
+
"/usr/bin/claude is owned by claude-code 2.1.50",
|
|
162
|
+
),
|
|
163
|
+
"claude-code",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def test_pacman_no_match(self):
|
|
167
|
+
self.assertIsNone(
|
|
168
|
+
_parse_package_name(PackageManager.PACMAN, "error: no package"),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def test_emerge_output(self):
|
|
172
|
+
self.assertEqual(
|
|
173
|
+
_parse_package_name(PackageManager.EMERGE, "dev-util/claude-code"),
|
|
174
|
+
"dev-util/claude-code",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def test_pkg_output(self):
|
|
178
|
+
self.assertEqual(
|
|
179
|
+
_parse_package_name(PackageManager.PKG, "claude-code-2.1.50"),
|
|
180
|
+
"claude-code-2.1.50",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def test_brew_output(self):
|
|
184
|
+
self.assertEqual(
|
|
185
|
+
_parse_package_name(PackageManager.HOMEBREW, "gemini-cli"),
|
|
186
|
+
"gemini-cli",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def test_empty_output(self):
|
|
190
|
+
self.assertIsNone(_parse_package_name(PackageManager.APT, ""))
|
|
191
|
+
self.assertIsNone(_parse_package_name(PackageManager.APT, "\n"))
|
|
192
|
+
|
|
193
|
+
def test_multiline_takes_first(self):
|
|
194
|
+
self.assertEqual(
|
|
195
|
+
_parse_package_name(PackageManager.EMERGE, "dev-util/foo\ndev-util/bar"),
|
|
196
|
+
"dev-util/foo",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class TestQueryPackageOwner(unittest.TestCase):
|
|
201
|
+
"""Tests for query_package_owner."""
|
|
202
|
+
|
|
203
|
+
@mock.patch("code_aide.package_managers.detect_package_manager", return_value=None)
|
|
204
|
+
def test_returns_none_when_no_manager(self, _mock):
|
|
205
|
+
pkg, cmd = query_package_owner("/usr/bin/example")
|
|
206
|
+
self.assertIsNone(pkg)
|
|
207
|
+
self.assertIsNone(cmd)
|
|
208
|
+
|
|
209
|
+
@mock.patch("code_aide.package_managers.subprocess.run")
|
|
210
|
+
@mock.patch("code_aide.package_managers.detect_package_manager")
|
|
211
|
+
def test_emerge_query(self, mock_detect, mock_run):
|
|
212
|
+
mock_detect.return_value = PACKAGE_MANAGER_BY_ENUM[PackageManager.EMERGE]
|
|
213
|
+
mock_run.return_value = subprocess.CompletedProcess(
|
|
214
|
+
args=[], returncode=0, stdout="dev-util/claude-code\n"
|
|
215
|
+
)
|
|
216
|
+
pkg, cmd = query_package_owner("/opt/bin/claude")
|
|
217
|
+
self.assertEqual(pkg, "dev-util/claude-code")
|
|
218
|
+
self.assertEqual(cmd, "sudo emerge --unmerge dev-util/claude-code")
|
|
219
|
+
mock_run.assert_called_once_with(
|
|
220
|
+
["qfile", "-qC", "/opt/bin/claude"],
|
|
221
|
+
capture_output=True,
|
|
222
|
+
text=True,
|
|
223
|
+
timeout=10,
|
|
224
|
+
check=False,
|
|
225
|
+
stdin=subprocess.DEVNULL,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
@mock.patch("code_aide.package_managers.subprocess.run")
|
|
229
|
+
@mock.patch("code_aide.package_managers.detect_package_manager")
|
|
230
|
+
def test_apt_query(self, mock_detect, mock_run):
|
|
231
|
+
mock_detect.return_value = PACKAGE_MANAGER_BY_ENUM[PackageManager.APT]
|
|
232
|
+
mock_run.return_value = subprocess.CompletedProcess(
|
|
233
|
+
args=[], returncode=0, stdout="claude-code: /usr/bin/claude\n"
|
|
234
|
+
)
|
|
235
|
+
pkg, cmd = query_package_owner("/usr/bin/claude")
|
|
236
|
+
self.assertEqual(pkg, "claude-code")
|
|
237
|
+
self.assertEqual(cmd, "sudo apt-get remove claude-code")
|
|
238
|
+
|
|
239
|
+
@mock.patch("code_aide.package_managers.subprocess.run")
|
|
240
|
+
@mock.patch("code_aide.package_managers.detect_package_manager")
|
|
241
|
+
def test_brew_uses_basename(self, mock_detect, mock_run):
|
|
242
|
+
mock_detect.return_value = PACKAGE_MANAGER_BY_ENUM[PackageManager.HOMEBREW]
|
|
243
|
+
mock_run.return_value = subprocess.CompletedProcess(
|
|
244
|
+
args=[], returncode=0, stdout="gemini-cli\n"
|
|
245
|
+
)
|
|
246
|
+
pkg, cmd = query_package_owner("/opt/homebrew/bin/gemini")
|
|
247
|
+
self.assertEqual(pkg, "gemini-cli")
|
|
248
|
+
self.assertEqual(cmd, "brew uninstall gemini-cli")
|
|
249
|
+
# Verify basename was passed, not full path
|
|
250
|
+
mock_run.assert_called_once_with(
|
|
251
|
+
["brew", "which-formula", "gemini"],
|
|
252
|
+
capture_output=True,
|
|
253
|
+
text=True,
|
|
254
|
+
timeout=10,
|
|
255
|
+
check=False,
|
|
256
|
+
stdin=subprocess.DEVNULL,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
@mock.patch("code_aide.package_managers.subprocess.run")
|
|
260
|
+
@mock.patch("code_aide.package_managers.detect_package_manager")
|
|
261
|
+
def test_returns_none_on_query_failure(self, mock_detect, mock_run):
|
|
262
|
+
mock_detect.return_value = PACKAGE_MANAGER_BY_ENUM[PackageManager.APT]
|
|
263
|
+
mock_run.return_value = subprocess.CompletedProcess(
|
|
264
|
+
args=[], returncode=1, stdout=""
|
|
265
|
+
)
|
|
266
|
+
pkg, cmd = query_package_owner("/usr/bin/example")
|
|
267
|
+
self.assertIsNone(pkg)
|
|
268
|
+
self.assertIsNone(cmd)
|
|
269
|
+
|
|
270
|
+
@mock.patch("code_aide.package_managers.subprocess.run")
|
|
271
|
+
@mock.patch("code_aide.package_managers.detect_package_manager")
|
|
272
|
+
def test_returns_none_on_exception(self, mock_detect, mock_run):
|
|
273
|
+
mock_detect.return_value = PACKAGE_MANAGER_BY_ENUM[PackageManager.APT]
|
|
274
|
+
mock_run.side_effect = OSError("timeout")
|
|
275
|
+
pkg, cmd = query_package_owner("/usr/bin/example")
|
|
276
|
+
self.assertIsNone(pkg)
|
|
277
|
+
self.assertIsNone(cmd)
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"""Shared constants and mutable tool configuration for CLI modules."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
from typing import Any, Dict
|
|
6
|
-
|
|
7
|
-
from code_aide.config import load_tools_config
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _use_color() -> bool:
|
|
11
|
-
"""Determine whether to emit ANSI color codes.
|
|
12
|
-
|
|
13
|
-
Checks (in priority order):
|
|
14
|
-
NO_COLOR — if set (any value), no color (https://no-color.org/)
|
|
15
|
-
FORCE_COLOR — if set (any value), force color
|
|
16
|
-
CLICOLOR_FORCE — if non-empty, force color
|
|
17
|
-
TERM=dumb — no color
|
|
18
|
-
CLICOLOR=0 — no color
|
|
19
|
-
stdout is not a TTY — no color
|
|
20
|
-
"""
|
|
21
|
-
if os.environ.get("NO_COLOR") is not None:
|
|
22
|
-
return False
|
|
23
|
-
if os.environ.get("FORCE_COLOR") is not None:
|
|
24
|
-
return True
|
|
25
|
-
if os.environ.get("CLICOLOR_FORCE", ""):
|
|
26
|
-
return True
|
|
27
|
-
if os.environ.get("TERM") == "dumb":
|
|
28
|
-
return False
|
|
29
|
-
if os.environ.get("CLICOLOR") == "0":
|
|
30
|
-
return False
|
|
31
|
-
return sys.stdout.isatty()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class Colors:
|
|
35
|
-
if _use_color():
|
|
36
|
-
RED = "\033[0;31m"
|
|
37
|
-
GREEN = "\033[0;32m"
|
|
38
|
-
YELLOW = "\033[1;33m"
|
|
39
|
-
BLUE = "\033[0;34m"
|
|
40
|
-
BOLD = "\033[1m"
|
|
41
|
-
NC = "\033[0m"
|
|
42
|
-
else:
|
|
43
|
-
RED = ""
|
|
44
|
-
GREEN = ""
|
|
45
|
-
YELLOW = ""
|
|
46
|
-
BLUE = ""
|
|
47
|
-
BOLD = ""
|
|
48
|
-
NC = ""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
PACKAGE_MANAGERS: Dict[str, Dict[str, Any]] = {
|
|
52
|
-
"apt-get": {
|
|
53
|
-
"detect_command": "apt-get",
|
|
54
|
-
"packages": ["nodejs", "npm"],
|
|
55
|
-
"pre_install": [["sudo", "apt-get", "update"]],
|
|
56
|
-
"install_command": ["sudo", "apt-get", "install", "-y"],
|
|
57
|
-
"description": "Debian/Ubuntu",
|
|
58
|
-
},
|
|
59
|
-
"dnf": {
|
|
60
|
-
"detect_command": "dnf",
|
|
61
|
-
"packages": ["nodejs", "npm"],
|
|
62
|
-
"pre_install": [],
|
|
63
|
-
"install_command": ["sudo", "dnf", "install", "-y"],
|
|
64
|
-
"description": "Fedora/RHEL 8+",
|
|
65
|
-
},
|
|
66
|
-
"yum": {
|
|
67
|
-
"detect_command": "yum",
|
|
68
|
-
"packages": ["nodejs", "npm"],
|
|
69
|
-
"pre_install": [],
|
|
70
|
-
"install_command": ["sudo", "yum", "install", "-y"],
|
|
71
|
-
"description": "RHEL/CentOS 7",
|
|
72
|
-
},
|
|
73
|
-
"pacman": {
|
|
74
|
-
"detect_command": "pacman",
|
|
75
|
-
"packages": ["nodejs", "npm"],
|
|
76
|
-
"pre_install": [],
|
|
77
|
-
"install_command": ["sudo", "pacman", "-S", "--noconfirm"],
|
|
78
|
-
"description": "Arch Linux",
|
|
79
|
-
},
|
|
80
|
-
"zypper": {
|
|
81
|
-
"detect_command": "zypper",
|
|
82
|
-
"packages": ["nodejs", "npm"],
|
|
83
|
-
"pre_install": [],
|
|
84
|
-
"install_command": ["sudo", "zypper", "install", "-y"],
|
|
85
|
-
"description": "openSUSE",
|
|
86
|
-
},
|
|
87
|
-
"emerge": {
|
|
88
|
-
"detect_command": "emerge",
|
|
89
|
-
"packages": ["net-libs/nodejs"],
|
|
90
|
-
"pre_install": [],
|
|
91
|
-
"install_command": ["sudo", "emerge", "--quiet-build"],
|
|
92
|
-
"description": "Gentoo",
|
|
93
|
-
},
|
|
94
|
-
"pkg": {
|
|
95
|
-
"detect_command": "pkg",
|
|
96
|
-
"packages": ["node22", "npm-node22"],
|
|
97
|
-
"pre_install": [],
|
|
98
|
-
"install_command": ["sudo", "pkg", "install", "-y"],
|
|
99
|
-
"description": "FreeBSD",
|
|
100
|
-
},
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
TOOLS: Dict[str, Dict[str, Any]] = load_tools_config()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_aide-1.12.2 → code_aide-1.13.0}/specs/unify-upgrade-eligibility-with-shared-evaluator.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|