Skip to content

Commit a282bb6

Browse files
committed
feat(detection): detect and warn about multiple tool installations
- Add detect_all_installations() to find tools in all known locations: ~/.local/bin, ~/.cargo/bin, ~/go/bin, ~/.rbenv/shims, ~/.nvm/versions/*/bin, ~/.local/pipx/venvs/*/bin, /usr/local/bin, /usr/bin, /bin - Add count_installations() and has_multiple_installations() helpers - Show warning in upgrade guide when multiple installations detected - Update uninstall to remove ALL installations, not just first found - Verify removal and warn about any remaining installations
1 parent 7266880 commit a282bb6

3 files changed

Lines changed: 167 additions & 6 deletions

File tree

scripts/guide.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ done
2929
# Load config query functions (for user preferences like auto_update)
3030
. "$DIR/lib/config.sh"
3131

32+
# Load capability detection (for multi-installation detection)
33+
. "$DIR/lib/capability.sh"
34+
3235
ensure_perms() {
3336
chmod +x "$ROOT"/scripts/*.sh 2>/dev/null || true
3437
chmod +x "$ROOT"/scripts/lib/*.sh 2>/dev/null || true
@@ -245,6 +248,22 @@ process_tool() {
245248
[ -n "$description" ] && printf " %s\n" "$description"
246249
[ -n "$homepage" ] && printf " Homepage: %s\n" "$(osc8 "$homepage" "$homepage")"
247250
printf " installed: %s via %s\n" "${installed:-<none>}" "${method:-unknown}"
251+
252+
# Check for multiple installations and warn
253+
local binary_name
254+
binary_name="$(catalog_get_property "$catalog_tool" binary_name)"
255+
binary_name="${binary_name:-$catalog_tool}"
256+
local all_installs
257+
all_installs="$(detect_all_installations "$catalog_tool" "$binary_name" 2>/dev/null || true)"
258+
local install_count
259+
install_count="$(echo "$all_installs" | grep -c . || echo 0)"
260+
if [ "$install_count" -gt 1 ]; then
261+
printf " ⚠️ Multiple installations detected (%d):\n" "$install_count"
262+
echo "$all_installs" | while IFS=: read -r inst_method inst_path; do
263+
printf " • %s: %s\n" "$inst_method" "$inst_path"
264+
done
265+
fi
266+
248267
printf " target: %s\n" "$(osc8 "$url" "${latest:-<unknown>}")"
249268

250269
# Build install command from catalog metadata (use catalog_tool for script name)

scripts/install_tool.sh

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,47 @@ if [ -z "$INSTALL_METHOD" ] || [ "$INSTALL_METHOD" = "null" ]; then
4040
exit 1
4141
fi
4242

43-
# Handle uninstall universally - detect actual install method, not catalog method
43+
# Handle uninstall universally - remove ALL installations found
4444
if [ "$ACTION" = "uninstall" ]; then
4545
binary_name="$(jq -r '.binary_name // ""' "$CATALOG_FILE" 2>/dev/null || echo "$TOOL")"
46-
current_method="$(detect_install_method "$TOOL" "$binary_name")"
47-
if [ "$current_method" != "none" ]; then
48-
remove_installation "$TOOL" "$current_method" "$binary_name"
49-
echo "[$TOOL] Uninstalled (was via $current_method)"
50-
else
46+
binary_name="${binary_name:-$TOOL}"
47+
48+
# Detect all installations
49+
all_installs="$(detect_all_installations "$TOOL" "$binary_name" 2>/dev/null || true)"
50+
install_count="$(echo "$all_installs" | grep -c . || echo 0)"
51+
52+
if [ "$install_count" -eq 0 ]; then
5153
echo "[$TOOL] Not installed"
54+
exit 0
55+
fi
56+
57+
if [ "$install_count" -gt 1 ]; then
58+
echo "[$TOOL] Found $install_count installations:"
59+
echo "$all_installs" | while IFS=: read -r method path; do
60+
echo "$method: $path"
61+
done
62+
echo "[$TOOL] Removing all installations..."
63+
fi
64+
65+
# Remove each installation
66+
removed_count=0
67+
echo "$all_installs" | while IFS=: read -r method path; do
68+
[ -z "$method" ] && continue
69+
# Extract base method (remove version info like "npm(v25.3.0)")
70+
base_method="${method%%(*}"
71+
remove_installation "$TOOL" "$base_method" "$binary_name"
72+
done
73+
74+
# Verify removal
75+
remaining="$(detect_all_installations "$TOOL" "$binary_name" 2>/dev/null || true)"
76+
remaining_count="$(echo "$remaining" | grep -c . || echo 0)"
77+
if [ "$remaining_count" -eq 0 ]; then
78+
echo "[$TOOL] Successfully removed all installations"
79+
else
80+
echo "[$TOOL] Warning: $remaining_count installation(s) could not be removed:"
81+
echo "$remaining" | while IFS=: read -r method path; do
82+
echo "$method: $path"
83+
done
5284
fi
5385
exit 0
5486
fi

scripts/lib/capability.sh

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,116 @@ detect_install_method() {
9090
esac
9191
}
9292

93+
# Detect ALL installations of a tool across known locations
94+
# Args: tool_name, binary_name
95+
# Returns: newline-separated list of "method:path" pairs
96+
# Example output:
97+
# cargo:/home/user/.cargo/bin/xsv
98+
# github_release_binary:/home/user/.local/bin/xsv
99+
detect_all_installations() {
100+
local tool="$1"
101+
local binary="${2:-$tool}"
102+
local found=()
103+
104+
# Define all known binary locations to check
105+
local locations=(
106+
"$HOME/.local/bin/$binary:github_release_binary"
107+
"$HOME/.cargo/bin/$binary:cargo"
108+
"$HOME/go/bin/$binary:go"
109+
"${GOPATH:-$HOME/go}/bin/$binary:go"
110+
"$HOME/.rbenv/shims/$binary:gem"
111+
"/usr/local/bin/$binary:brew_or_manual"
112+
"/usr/bin/$binary:apt_or_system"
113+
"/bin/$binary:apt_or_system"
114+
)
115+
116+
# Check nvm locations (multiple node versions)
117+
if [ -d "$HOME/.nvm/versions/node" ]; then
118+
for node_dir in "$HOME/.nvm/versions/node/"*/bin; do
119+
if [ -x "$node_dir/$binary" ]; then
120+
local node_version="${node_dir%/bin}"
121+
node_version="${node_version##*/}"
122+
found+=("npm($node_version):$node_dir/$binary")
123+
fi
124+
done
125+
fi
126+
127+
# Check pipx locations
128+
if [ -d "$HOME/.local/pipx/venvs" ]; then
129+
for venv_dir in "$HOME/.local/pipx/venvs/"*/bin; do
130+
if [ -x "$venv_dir/$binary" ]; then
131+
local pkg_name="${venv_dir%/bin}"
132+
pkg_name="${pkg_name##*/}"
133+
found+=("pipx($pkg_name):$venv_dir/$binary")
134+
fi
135+
done
136+
fi
137+
138+
# Check standard locations
139+
for loc_spec in "${locations[@]}"; do
140+
local path="${loc_spec%%:*}"
141+
local method="${loc_spec##*:}"
142+
143+
if [ -x "$path" ]; then
144+
# Refine method detection
145+
case "$method" in
146+
brew_or_manual)
147+
if command -v brew >/dev/null 2>&1 && brew list --formula 2>/dev/null | grep -q "^${tool}\$"; then
148+
method="brew"
149+
else
150+
method="manual"
151+
fi
152+
;;
153+
apt_or_system)
154+
if command -v dpkg >/dev/null 2>&1 && dpkg -S "$path" >/dev/null 2>&1; then
155+
method="apt"
156+
else
157+
method="system"
158+
fi
159+
;;
160+
esac
161+
162+
# Avoid duplicates (go can have two paths pointing to same location)
163+
local already_found=""
164+
for existing in "${found[@]}"; do
165+
if [ "${existing##*:}" = "$path" ]; then
166+
already_found="true"
167+
break
168+
fi
169+
done
170+
171+
if [ -z "$already_found" ]; then
172+
found+=("$method:$path")
173+
fi
174+
fi
175+
done
176+
177+
# Output results
178+
for item in "${found[@]}"; do
179+
echo "$item"
180+
done
181+
}
182+
183+
# Count number of installations for a tool
184+
# Args: tool_name, binary_name
185+
# Returns: number of installations found
186+
count_installations() {
187+
local tool="$1"
188+
local binary="${2:-$tool}"
189+
detect_all_installations "$tool" "$binary" | wc -l
190+
}
191+
192+
# Check if tool has multiple installations (for warning purposes)
193+
# Args: tool_name, binary_name
194+
# Returns: 0 if multiple, 1 if single or none
195+
has_multiple_installations() {
196+
local tool="$1"
197+
local binary="${2:-$tool}"
198+
local count
199+
count="$(count_installations "$tool" "$binary")"
200+
[ "$count" -gt 1 ]
201+
}
202+
93203
# Check if an installation method is available on this system
94204
# Args: method_name
95205
# Returns: 0 if available, 1 if not

0 commit comments

Comments
 (0)