code-aide 1.6.0__tar.gz → 1.7.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.6.0 → code_aide-1.7.0}/PKG-INFO +3 -2
- {code_aide-1.6.0 → code_aide-1.7.0}/README.md +2 -1
- {code_aide-1.6.0 → code_aide-1.7.0}/TODO.md +19 -18
- code_aide-1.7.0/specs/auto-migrate-deprecated-installs.md +128 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/__init__.py +1 -1
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/commands_actions.py +5 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/commands_tools.py +14 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/detection.py +60 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/operations.py +41 -2
- code_aide-1.7.0/tests/test_commands_tools.py +163 -0
- code_aide-1.7.0/tests/test_detection.py +286 -0
- code_aide-1.7.0/tests/test_operations.py +309 -0
- code_aide-1.6.0/tests/test_commands_tools.py +0 -78
- code_aide-1.6.0/tests/test_detection.py +0 -116
- code_aide-1.6.0/tests/test_operations.py +0 -159
- {code_aide-1.6.0 → code_aide-1.7.0}/.github/workflows/ci.yml +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/.github/workflows/publish.yml +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/.gitignore +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/.gitlab-ci.yml +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/.pre-commit-config.yaml +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/AGENTS.md +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/CLAUDE.md +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/LICENSE +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/pyproject.toml +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/specs/claude-native-installer-migration.md +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/specs/missing-coding-llm-cli-tools.md +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/specs/pre-commit-uv-setup.md +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/specs/remove-bundled-version-baseline.md +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/__main__.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/config.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/console.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/constants.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/data/tools.json +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/entry.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/install.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/prereqs.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/status.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/src/code_aide/versions.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/tests/test_commands_actions.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/tests/test_config.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/tests/test_console.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/tests/test_constants.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/tests/test_install.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/tests/test_status.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.0}/tests/test_versions.py +0 -0
- {code_aide-1.6.0 → code_aide-1.7.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.7.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
|
|
@@ -157,7 +157,8 @@ uv run pytest tests/test_install.py::TestDetectOsArch -v
|
|
|
157
157
|
- Example: `Fixed timeout handling in status command`
|
|
158
158
|
5. Tag and push:
|
|
159
159
|
- `git tag vX.Y.Z`
|
|
160
|
-
- `git push origin main
|
|
160
|
+
- `git push origin main`
|
|
161
|
+
- `git push origin vX.Y.Z`
|
|
161
162
|
6. Confirm GitHub Actions:
|
|
162
163
|
- CI should pass.
|
|
163
164
|
- Publish workflow should upload to PyPI and create GitHub Release notes.
|
|
@@ -131,7 +131,8 @@ uv run pytest tests/test_install.py::TestDetectOsArch -v
|
|
|
131
131
|
- Example: `Fixed timeout handling in status command`
|
|
132
132
|
5. Tag and push:
|
|
133
133
|
- `git tag vX.Y.Z`
|
|
134
|
-
- `git push origin main
|
|
134
|
+
- `git push origin main`
|
|
135
|
+
- `git push origin vX.Y.Z`
|
|
135
136
|
6. Confirm GitHub Actions:
|
|
136
137
|
- CI should pass.
|
|
137
138
|
- Publish workflow should upload to PyPI and create GitHub Release notes.
|
|
@@ -7,8 +7,8 @@ remove, status, and version metadata).
|
|
|
7
7
|
|
|
8
8
|
- [x] Standardize `--dryrun` flag: `update-versions` uses `--dry-run` but
|
|
9
9
|
`install` uses `--dryrun`. Change `update-versions` to `--dryrun`.
|
|
10
|
-
- [x] Add missing `success()` message for direct-download installs in
|
|
11
|
-
mode (`install.py`, `install_tool()`).
|
|
10
|
+
- [x] Add missing `success()` message for direct-download installs in
|
|
11
|
+
non-dryrun mode (`install.py`, `install_tool()`).
|
|
12
12
|
- [x] Replace PID-based temp directory naming with `tempfile.mkdtemp()` in
|
|
13
13
|
`install.py` `install_direct_download()`.
|
|
14
14
|
- [x] Guard `package.split("/", 1)` in `detection.py`
|
|
@@ -18,21 +18,21 @@ remove, status, and version metadata).
|
|
|
18
18
|
|
|
19
19
|
- [ ] Handle missing `node` cleanly during prerequisite checks
|
|
20
20
|
(`FileNotFoundError` path in Node version probing).
|
|
21
|
-
- [ ] Read tool version output from both stdout and stderr so status does
|
|
22
|
-
miss installed versions.
|
|
21
|
+
- [ ] Read tool version output from both stdout and stderr so status does
|
|
22
|
+
not miss installed versions.
|
|
23
23
|
- [ ] Make version cache writes atomic (write temp file + rename) to avoid
|
|
24
|
-
partial/corrupted `versions.json`.
|
|
25
|
-
- [ ] Warn when `versions.json` cache contains invalid JSON instead of
|
|
26
|
-
returning empty data.
|
|
24
|
+
partial/corrupted `versions.json`.
|
|
25
|
+
- [ ] Warn when `versions.json` cache contains invalid JSON instead of
|
|
26
|
+
silently returning empty data.
|
|
27
27
|
- [ ] Use `os.pathsep` instead of hardcoded `":"` in `prereqs.py`
|
|
28
28
|
`check_path_directories()`.
|
|
29
29
|
|
|
30
30
|
## Security and Integrity
|
|
31
31
|
|
|
32
|
-
- [ ] Add integrity verification for direct-download tarballs (not only
|
|
33
|
-
script SHA256).
|
|
34
|
-
- [ ] Extend tool metadata to support tarball checksum/signature fields
|
|
35
|
-
applicable.
|
|
32
|
+
- [ ] Add integrity verification for direct-download tarballs (not only
|
|
33
|
+
install script SHA256).
|
|
34
|
+
- [ ] Extend tool metadata to support tarball checksum/signature fields
|
|
35
|
+
where applicable.
|
|
36
36
|
|
|
37
37
|
## CLI and Automation UX
|
|
38
38
|
|
|
@@ -40,21 +40,23 @@ remove, status, and version metadata).
|
|
|
40
40
|
- [ ] Add a focused `doctor` command for environment checks (PATH,
|
|
41
41
|
prerequisites, command health).
|
|
42
42
|
- [ ] Consider `install --force` for reinstall/repair flows.
|
|
43
|
-
- [ ] Add cleanup support for stale direct-download versions no longer in
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
- [ ] Add cleanup support for stale direct-download versions no longer in
|
|
44
|
+
use.
|
|
45
|
+
- [ ] Expand `upgrade` help text to mention the default "only out-of-date
|
|
46
|
+
tools" behavior.
|
|
46
47
|
|
|
47
48
|
## Documentation
|
|
48
49
|
|
|
49
|
-
- [ ] Document that running `code-aide` with no subcommand defaults to
|
|
50
|
+
- [ ] Document that running `code-aide` with no subcommand defaults to
|
|
51
|
+
`status`.
|
|
50
52
|
- [ ] Add `install --dryrun` to the README usage examples.
|
|
51
53
|
- [ ] Document environment variables and config file paths
|
|
52
54
|
(`~/.config/code-aide/versions.json`).
|
|
53
55
|
|
|
54
56
|
## Platform and Package Detection
|
|
55
57
|
|
|
56
|
-
- [ ] Broaden system package metadata detection beyond Gentoo-specific
|
|
57
|
-
where practical.
|
|
58
|
+
- [ ] Broaden system package metadata detection beyond Gentoo-specific
|
|
59
|
+
tooling where practical.
|
|
58
60
|
|
|
59
61
|
## Maintainability and Tests
|
|
60
62
|
|
|
@@ -65,6 +67,5 @@ remove, status, and version metadata).
|
|
|
65
67
|
- `cmd_remove()` (completely untested)
|
|
66
68
|
- `fetch_url()` (completely untested)
|
|
67
69
|
- `check_prerequisites()` / `install_nodejs_npm()` (completely untested)
|
|
68
|
-
- `save_bundled_versions()` (untested)
|
|
69
70
|
- `get_system_package_info()` (completely untested)
|
|
70
71
|
- [ ] Add tests for prerequisite edge cases and cache write behavior.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Auto-migrate deprecated install methods
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
Claude Code switched from npm to a native installer. Users who installed
|
|
6
|
+
Claude via npm are stuck: `code-aide upgrade` runs `npm install -g @latest`
|
|
7
|
+
(the detected method) instead of the native script installer (the configured
|
|
8
|
+
method). `code-aide install` exits early because the command already exists.
|
|
9
|
+
There is no migration path. code-aide should detect this and fix it
|
|
10
|
+
automatically.
|
|
11
|
+
|
|
12
|
+
This is a general pattern -- any tool could change install methods in the
|
|
13
|
+
future. The solution should not be Claude-specific.
|
|
14
|
+
|
|
15
|
+
## Approach
|
|
16
|
+
|
|
17
|
+
A detected install method is "deprecated" when:
|
|
18
|
+
|
|
19
|
+
- Detected method is `npm` or `brew_npm` AND config `install_type` is
|
|
20
|
+
`script` or `direct_download`
|
|
21
|
+
|
|
22
|
+
Methods that are never considered deprecated (user-managed choices):
|
|
23
|
+
|
|
24
|
+
- `brew_formula`, `brew_cask` -- user explicitly chose Homebrew
|
|
25
|
+
- `system` -- managed by system package manager
|
|
26
|
+
|
|
27
|
+
Migration flow: remove old (npm), install new (configured method), with
|
|
28
|
+
clear messaging. If the new install fails after removal, provide recovery
|
|
29
|
+
instructions.
|
|
30
|
+
|
|
31
|
+
## Changes
|
|
32
|
+
|
|
33
|
+
### 1. `src/code_aide/data/tools.json` -- add deprecated_npm_package
|
|
34
|
+
|
|
35
|
+
Add a `deprecated_npm_package` field to Claude's entry:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
"claude": {
|
|
39
|
+
"name": "Claude CLI (Claude Code)",
|
|
40
|
+
"install_type": "script",
|
|
41
|
+
"deprecated_npm_package": "@anthropic-ai/claude-code",
|
|
42
|
+
...
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This records the old npm package name explicitly so the migration removal
|
|
47
|
+
step does not rely solely on extracting the package name from the binary
|
|
48
|
+
path.
|
|
49
|
+
|
|
50
|
+
### 2. `src/code_aide/detection.py` -- add deprecation detection
|
|
51
|
+
|
|
52
|
+
Add two functions:
|
|
53
|
+
|
|
54
|
+
- `is_deprecated_install(tool_name)` -- returns True when the detected
|
|
55
|
+
install method is `npm` or `brew_npm` but the configured `install_type` is
|
|
56
|
+
something else (script, direct_download). Also returns True when
|
|
57
|
+
`deprecated_npm_package` is set and the detected method is npm. Brew
|
|
58
|
+
formula/cask and system installs are never deprecated (user-managed).
|
|
59
|
+
- `format_migration_warning(tool_name)` -- returns a human-readable warning
|
|
60
|
+
string, or None if not deprecated. Uses existing `format_install_method()`
|
|
61
|
+
for labels.
|
|
62
|
+
|
|
63
|
+
### 3. `src/code_aide/operations.py` -- auto-migrate in upgrade
|
|
64
|
+
|
|
65
|
+
Add `_migrate_install_method()` private function that:
|
|
66
|
+
|
|
67
|
+
1. Warns about the deprecated method
|
|
68
|
+
2. Calls `remove_tool()` to remove the old npm install
|
|
69
|
+
3. Calls `install_tool()` to install via the configured method
|
|
70
|
+
4. If install fails after remove, prints recovery instructions (`code-aide
|
|
71
|
+
install <tool>` and manual curl command for script types)
|
|
72
|
+
|
|
73
|
+
Modify `upgrade_tool()`: after `detect_install_method()`, check
|
|
74
|
+
`is_deprecated_install()`. If True, call `_migrate_install_method()` instead
|
|
75
|
+
of the normal upgrade branch.
|
|
76
|
+
|
|
77
|
+
Modify `remove_tool()` npm branch: fall back to
|
|
78
|
+
`tool_config.get("deprecated_npm_package")` when looking up the npm package
|
|
79
|
+
name. Current code: `npm_package = detail or
|
|
80
|
+
tool_config.get("npm_package")`. Change to also check
|
|
81
|
+
`deprecated_npm_package`.
|
|
82
|
+
|
|
83
|
+
New imports needed: `is_deprecated_install`, `format_install_method` from
|
|
84
|
+
detection, `install_tool` from install.
|
|
85
|
+
|
|
86
|
+
Note: after `remove_tool()` succeeds, `command_exists()` returns False so
|
|
87
|
+
`install_tool()` proceeds normally with the configured `install_type`.
|
|
88
|
+
|
|
89
|
+
### 4. `src/code_aide/commands_actions.py` -- include deprecated in auto-upgrade
|
|
90
|
+
|
|
91
|
+
Modify `cmd_upgrade()` no-args path: after building the version-outdated
|
|
92
|
+
list, also add any installed tools with deprecated install methods. Without
|
|
93
|
+
this, a tool whose version is current but install method is deprecated would
|
|
94
|
+
be silently skipped by `code-aide upgrade`.
|
|
95
|
+
|
|
96
|
+
New import: `is_deprecated_install` from detection.
|
|
97
|
+
|
|
98
|
+
### 5. `src/code_aide/commands_tools.py` -- show warnings in status/list
|
|
99
|
+
|
|
100
|
+
In both `cmd_list()` and `cmd_status()`, after displaying "Installed via:",
|
|
101
|
+
call `format_migration_warning()` and display the warning if present.
|
|
102
|
+
|
|
103
|
+
At the end of `cmd_status()`, add a summary line counting tools that need
|
|
104
|
+
migration.
|
|
105
|
+
|
|
106
|
+
New imports: `format_migration_warning`, `is_deprecated_install` from
|
|
107
|
+
detection.
|
|
108
|
+
|
|
109
|
+
### 6. Tests
|
|
110
|
+
|
|
111
|
+
- `tests/test_detection.py`: Tests for `is_deprecated_install()` covering
|
|
112
|
+
npm to script (deprecated), npm to npm (not deprecated), brew_formula
|
|
113
|
+
(never deprecated), system (never deprecated), not installed, unknown
|
|
114
|
+
tool, npm to direct_download. Tests for `format_migration_warning()`.
|
|
115
|
+
- `tests/test_operations.py`: Tests for migration in `upgrade_tool()`:
|
|
116
|
+
triggers migration, normal upgrade when not deprecated, migration fails on
|
|
117
|
+
remove, migration fails on install with recovery message.
|
|
118
|
+
- `tests/test_commands_tools.py`: Test that status/list show migration
|
|
119
|
+
warnings for deprecated installs.
|
|
120
|
+
|
|
121
|
+
## Verification
|
|
122
|
+
|
|
123
|
+
1. `pre-commit run --all-files` -- formatting passes
|
|
124
|
+
2. `uv run pytest tests/ -v` -- all tests pass
|
|
125
|
+
3. `uv run python -m code_aide status` -- shows migration warning for
|
|
126
|
+
npm-installed Claude (if applicable)
|
|
127
|
+
4. `uv run python -m code_aide upgrade claude` -- performs migration (remove
|
|
128
|
+
npm, install via script)
|
|
@@ -5,6 +5,7 @@ import sys
|
|
|
5
5
|
from typing import Any, Dict, List
|
|
6
6
|
|
|
7
7
|
from code_aide.constants import TOOLS
|
|
8
|
+
from code_aide.detection import is_deprecated_install
|
|
8
9
|
from code_aide.install import install_tool
|
|
9
10
|
from code_aide.console import error, info, success, warning
|
|
10
11
|
from code_aide.operations import remove_tool, upgrade_tool, validate_tools
|
|
@@ -123,6 +124,10 @@ def cmd_upgrade(args: argparse.Namespace) -> None:
|
|
|
123
124
|
for name, config in TOOLS.items():
|
|
124
125
|
if not is_tool_installed(name):
|
|
125
126
|
continue
|
|
127
|
+
if is_deprecated_install(name):
|
|
128
|
+
if name not in tools_to_upgrade:
|
|
129
|
+
tools_to_upgrade.append(name)
|
|
130
|
+
continue
|
|
126
131
|
latest = config.get("latest_version")
|
|
127
132
|
if not latest:
|
|
128
133
|
continue
|
|
@@ -8,6 +8,7 @@ from typing import List
|
|
|
8
8
|
from code_aide.constants import Colors, PACKAGE_MANAGERS, TOOLS
|
|
9
9
|
from code_aide.detection import (
|
|
10
10
|
format_install_method,
|
|
11
|
+
format_migration_warning,
|
|
11
12
|
get_system_package_info,
|
|
12
13
|
detect_install_method,
|
|
13
14
|
)
|
|
@@ -48,6 +49,9 @@ def cmd_list(args: argparse.Namespace) -> None:
|
|
|
48
49
|
" Installed via: "
|
|
49
50
|
f"{format_install_method(install_info['method'], install_info['detail'])}"
|
|
50
51
|
)
|
|
52
|
+
migration_msg = format_migration_warning(tool_name)
|
|
53
|
+
if migration_msg:
|
|
54
|
+
warning(f" {migration_msg}")
|
|
51
55
|
|
|
52
56
|
if tool_config.get("min_node_version"):
|
|
53
57
|
print(f" Requires: Node.js v{tool_config['min_node_version']}+")
|
|
@@ -99,6 +103,7 @@ def cmd_status(args: argparse.Namespace) -> None:
|
|
|
99
103
|
print()
|
|
100
104
|
|
|
101
105
|
outdated_count = 0
|
|
106
|
+
migration_count = 0
|
|
102
107
|
config_outdated: List[str] = []
|
|
103
108
|
|
|
104
109
|
for tool_name, tool_config in TOOLS.items():
|
|
@@ -156,6 +161,10 @@ def cmd_status(args: argparse.Namespace) -> None:
|
|
|
156
161
|
" Installed via: "
|
|
157
162
|
f"{format_install_method(install_info['method'], install_info['detail'])}"
|
|
158
163
|
)
|
|
164
|
+
migration_msg = format_migration_warning(tool_name)
|
|
165
|
+
if migration_msg:
|
|
166
|
+
warning(f" {migration_msg}")
|
|
167
|
+
migration_count += 1
|
|
159
168
|
|
|
160
169
|
if status["user"]:
|
|
161
170
|
print(f" User: {status['user']}")
|
|
@@ -181,3 +190,8 @@ def cmd_status(args: argparse.Namespace) -> None:
|
|
|
181
190
|
f"{Colors.YELLOW}{outdated_count} tool(s) can be upgraded with "
|
|
182
191
|
f"'code-aide upgrade'.{Colors.NC}"
|
|
183
192
|
)
|
|
193
|
+
if migration_count > 0:
|
|
194
|
+
print(
|
|
195
|
+
f"{Colors.YELLOW}{migration_count} tool(s) need migration to a "
|
|
196
|
+
f"new install method. Run 'code-aide upgrade' to migrate.{Colors.NC}"
|
|
197
|
+
)
|
|
@@ -186,3 +186,63 @@ def format_install_method(method: Optional[str], detail: Optional[str]) -> str:
|
|
|
186
186
|
if method:
|
|
187
187
|
return method
|
|
188
188
|
return "unknown"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Install methods that are user-managed and never considered deprecated
|
|
192
|
+
_USER_MANAGED_METHODS = frozenset({"brew_formula", "brew_cask", "system"})
|
|
193
|
+
|
|
194
|
+
# Install methods that are considered deprecated when they don't match
|
|
195
|
+
# the configured install_type
|
|
196
|
+
_DEPRECATED_METHODS = frozenset({"npm", "brew_npm"})
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def is_deprecated_install(tool_name: str) -> bool:
|
|
200
|
+
"""Check if a tool's detected install method is deprecated.
|
|
201
|
+
|
|
202
|
+
Returns True when the detected install method is npm or brew_npm
|
|
203
|
+
but the configured install_type is something else (e.g. script,
|
|
204
|
+
direct_download). Brew formula/cask and system installs are
|
|
205
|
+
user-managed and never considered deprecated.
|
|
206
|
+
"""
|
|
207
|
+
tool_config = TOOLS.get(tool_name)
|
|
208
|
+
if not tool_config:
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
install_info = detect_install_method(tool_name)
|
|
212
|
+
detected = install_info["method"]
|
|
213
|
+
|
|
214
|
+
if not detected:
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
if detected in _USER_MANAGED_METHODS:
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
configured = tool_config.get("install_type")
|
|
221
|
+
if not configured:
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
if detected in _DEPRECATED_METHODS and detected != configured:
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def format_migration_warning(tool_name: str) -> Optional[str]:
|
|
231
|
+
"""Return a human-readable migration warning, or None if not needed."""
|
|
232
|
+
if not is_deprecated_install(tool_name):
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
tool_config = TOOLS.get(tool_name)
|
|
236
|
+
if not tool_config:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
install_info = detect_install_method(tool_name)
|
|
240
|
+
detected_label = format_install_method(
|
|
241
|
+
install_info["method"], install_info["detail"]
|
|
242
|
+
)
|
|
243
|
+
configured_label = format_install_method(tool_config["install_type"], None)
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
f"Installed via {detected_label} but configured method is "
|
|
247
|
+
f"{configured_label}. Run 'code-aide upgrade {tool_name}' to migrate."
|
|
248
|
+
)
|
|
@@ -8,12 +8,48 @@ import sys
|
|
|
8
8
|
from typing import List
|
|
9
9
|
|
|
10
10
|
from code_aide.constants import TOOLS
|
|
11
|
-
from code_aide.detection import
|
|
12
|
-
|
|
11
|
+
from code_aide.detection import (
|
|
12
|
+
detect_install_method,
|
|
13
|
+
format_install_method,
|
|
14
|
+
is_deprecated_install,
|
|
15
|
+
)
|
|
16
|
+
from code_aide.install import install_direct_download, install_tool, run_install_script
|
|
13
17
|
from code_aide.console import error, info, run_command, success, warning
|
|
14
18
|
from code_aide.prereqs import is_tool_installed
|
|
15
19
|
|
|
16
20
|
|
|
21
|
+
def _migrate_install_method(tool_name: str) -> bool:
|
|
22
|
+
"""Migrate a tool from a deprecated install method to the configured one."""
|
|
23
|
+
tool_config = TOOLS[tool_name]
|
|
24
|
+
install_info = detect_install_method(tool_name)
|
|
25
|
+
old_label = format_install_method(install_info["method"], install_info["detail"])
|
|
26
|
+
new_label = format_install_method(tool_config["install_type"], None)
|
|
27
|
+
|
|
28
|
+
warning(
|
|
29
|
+
f"{tool_config['name']} is installed via {old_label} "
|
|
30
|
+
f"but the configured method is {new_label}."
|
|
31
|
+
)
|
|
32
|
+
info(f"Migrating {tool_config['name']} from {old_label} to {new_label}...")
|
|
33
|
+
|
|
34
|
+
if not remove_tool(tool_name):
|
|
35
|
+
error(
|
|
36
|
+
f"Failed to remove old {old_label} install of {tool_config['name']}. "
|
|
37
|
+
"Migration aborted."
|
|
38
|
+
)
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
if not install_tool(tool_name):
|
|
42
|
+
error(f"Failed to install {tool_config['name']} via {new_label}.")
|
|
43
|
+
error(
|
|
44
|
+
f"The old {old_label} install has been removed. "
|
|
45
|
+
f"To recover, run: code-aide install {tool_name}"
|
|
46
|
+
)
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
success(f"{tool_config['name']} migrated from {old_label} to {new_label}")
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
|
|
17
53
|
def upgrade_tool(tool_name: str) -> bool:
|
|
18
54
|
"""Upgrade a tool based on its configuration."""
|
|
19
55
|
tool_config = TOOLS.get(tool_name)
|
|
@@ -25,6 +61,9 @@ def upgrade_tool(tool_name: str) -> bool:
|
|
|
25
61
|
warning(f"{tool_config['name']} is not installed. Use 'install' command first.")
|
|
26
62
|
return False
|
|
27
63
|
|
|
64
|
+
if is_deprecated_install(tool_name):
|
|
65
|
+
return _migrate_install_method(tool_name)
|
|
66
|
+
|
|
28
67
|
install_info = detect_install_method(tool_name)
|
|
29
68
|
method = install_info["method"]
|
|
30
69
|
detail = install_info["detail"]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Unit tests for read-only CLI commands."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import io
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
from code_aide import commands_tools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCmdList(unittest.TestCase):
|
|
12
|
+
"""Tests for cmd_list."""
|
|
13
|
+
|
|
14
|
+
def test_lists_tools_without_runtime_probes(self):
|
|
15
|
+
tools = {
|
|
16
|
+
"x": {
|
|
17
|
+
"name": "Example Tool",
|
|
18
|
+
"command": "example",
|
|
19
|
+
"install_type": "npm",
|
|
20
|
+
"default_install": True,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
args = type("Args", (), {})()
|
|
24
|
+
with (
|
|
25
|
+
mock.patch.dict(commands_tools.TOOLS, tools, clear=True),
|
|
26
|
+
mock.patch.object(commands_tools, "is_tool_installed", return_value=False),
|
|
27
|
+
mock.patch.object(commands_tools, "command_exists", return_value=False),
|
|
28
|
+
mock.patch.object(
|
|
29
|
+
commands_tools, "detect_package_manager", return_value=None
|
|
30
|
+
),
|
|
31
|
+
):
|
|
32
|
+
buf = io.StringIO()
|
|
33
|
+
with contextlib.redirect_stdout(buf):
|
|
34
|
+
commands_tools.cmd_list(args)
|
|
35
|
+
output = buf.getvalue()
|
|
36
|
+
self.assertIn("Example Tool", output)
|
|
37
|
+
self.assertIn("Managed by: npm (code-aide)", output)
|
|
38
|
+
self.assertIn("System Information:", output)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestCmdStatus(unittest.TestCase):
|
|
42
|
+
"""Tests for cmd_status."""
|
|
43
|
+
|
|
44
|
+
def test_shows_upgradeable_count_for_outdated_tool(self):
|
|
45
|
+
tools = {
|
|
46
|
+
"x": {
|
|
47
|
+
"name": "Example Tool",
|
|
48
|
+
"command": "example",
|
|
49
|
+
"install_type": "npm",
|
|
50
|
+
"latest_version": "2.0.0",
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
status = {
|
|
54
|
+
"installed": True,
|
|
55
|
+
"version": "1.0.0",
|
|
56
|
+
"user": None,
|
|
57
|
+
"usage": None,
|
|
58
|
+
"errors": [],
|
|
59
|
+
}
|
|
60
|
+
args = type("Args", (), {})()
|
|
61
|
+
with (
|
|
62
|
+
mock.patch.dict(commands_tools.TOOLS, tools, clear=True),
|
|
63
|
+
mock.patch.object(commands_tools, "get_tool_status", return_value=status),
|
|
64
|
+
mock.patch.object(
|
|
65
|
+
commands_tools.shutil, "which", return_value="/tmp/example"
|
|
66
|
+
),
|
|
67
|
+
mock.patch.object(
|
|
68
|
+
commands_tools,
|
|
69
|
+
"detect_install_method",
|
|
70
|
+
return_value={"method": "npm", "detail": "example-pkg"},
|
|
71
|
+
),
|
|
72
|
+
mock.patch.object(
|
|
73
|
+
commands_tools, "format_migration_warning", return_value=None
|
|
74
|
+
),
|
|
75
|
+
):
|
|
76
|
+
buf = io.StringIO()
|
|
77
|
+
with contextlib.redirect_stdout(buf):
|
|
78
|
+
commands_tools.cmd_status(args)
|
|
79
|
+
output = buf.getvalue()
|
|
80
|
+
self.assertIn("Example Tool", output)
|
|
81
|
+
self.assertIn("tool(s) can be upgraded", output)
|
|
82
|
+
|
|
83
|
+
def test_status_shows_migration_warning(self):
|
|
84
|
+
"""cmd_status shows migration warning for deprecated install."""
|
|
85
|
+
tools = {
|
|
86
|
+
"x": {
|
|
87
|
+
"name": "Example Tool",
|
|
88
|
+
"command": "example",
|
|
89
|
+
"install_type": "script",
|
|
90
|
+
"latest_version": "1.0.0",
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
status = {
|
|
94
|
+
"installed": True,
|
|
95
|
+
"version": "1.0.0",
|
|
96
|
+
"user": None,
|
|
97
|
+
"usage": None,
|
|
98
|
+
"errors": [],
|
|
99
|
+
}
|
|
100
|
+
args = type("Args", (), {})()
|
|
101
|
+
with (
|
|
102
|
+
mock.patch.dict(commands_tools.TOOLS, tools, clear=True),
|
|
103
|
+
mock.patch.object(commands_tools, "get_tool_status", return_value=status),
|
|
104
|
+
mock.patch.object(
|
|
105
|
+
commands_tools.shutil, "which", return_value="/tmp/example"
|
|
106
|
+
),
|
|
107
|
+
mock.patch.object(
|
|
108
|
+
commands_tools,
|
|
109
|
+
"detect_install_method",
|
|
110
|
+
return_value={"method": "npm", "detail": "example-pkg"},
|
|
111
|
+
),
|
|
112
|
+
mock.patch.object(
|
|
113
|
+
commands_tools,
|
|
114
|
+
"format_migration_warning",
|
|
115
|
+
return_value="Installed via npm but configured method is script. "
|
|
116
|
+
"Run 'code-aide upgrade x' to migrate.",
|
|
117
|
+
),
|
|
118
|
+
):
|
|
119
|
+
buf = io.StringIO()
|
|
120
|
+
with contextlib.redirect_stdout(buf):
|
|
121
|
+
commands_tools.cmd_status(args)
|
|
122
|
+
output = buf.getvalue()
|
|
123
|
+
self.assertIn("configured method is script", output)
|
|
124
|
+
self.assertIn("tool(s) need migration", output)
|
|
125
|
+
|
|
126
|
+
def test_list_shows_migration_warning(self):
|
|
127
|
+
"""cmd_list shows migration warning for deprecated install."""
|
|
128
|
+
tools = {
|
|
129
|
+
"x": {
|
|
130
|
+
"name": "Example Tool",
|
|
131
|
+
"command": "example",
|
|
132
|
+
"install_type": "script",
|
|
133
|
+
"default_install": True,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
args = type("Args", (), {})()
|
|
137
|
+
with (
|
|
138
|
+
mock.patch.dict(commands_tools.TOOLS, tools, clear=True),
|
|
139
|
+
mock.patch.object(commands_tools, "is_tool_installed", return_value=True),
|
|
140
|
+
mock.patch.object(
|
|
141
|
+
commands_tools.shutil, "which", return_value="/tmp/example"
|
|
142
|
+
),
|
|
143
|
+
mock.patch.object(
|
|
144
|
+
commands_tools,
|
|
145
|
+
"detect_install_method",
|
|
146
|
+
return_value={"method": "npm", "detail": "example-pkg"},
|
|
147
|
+
),
|
|
148
|
+
mock.patch.object(
|
|
149
|
+
commands_tools,
|
|
150
|
+
"format_migration_warning",
|
|
151
|
+
return_value="Installed via npm but configured method is script. "
|
|
152
|
+
"Run 'code-aide upgrade x' to migrate.",
|
|
153
|
+
),
|
|
154
|
+
mock.patch.object(commands_tools, "command_exists", return_value=False),
|
|
155
|
+
mock.patch.object(
|
|
156
|
+
commands_tools, "detect_package_manager", return_value=None
|
|
157
|
+
),
|
|
158
|
+
):
|
|
159
|
+
buf = io.StringIO()
|
|
160
|
+
with contextlib.redirect_stdout(buf):
|
|
161
|
+
commands_tools.cmd_list(args)
|
|
162
|
+
output = buf.getvalue()
|
|
163
|
+
self.assertIn("configured method is script", output)
|