Skip to content

Commit 9854e15

Browse files
committed
fix(upgrade): resolve 31 additional issues from second upgrade run
Version output normalization: - Add version_command to isort (--version-number), black, flake8 catalogs - Add version_command to gcloud catalog for clean version extraction - Normalize version output in uv_tool.sh and github_release_binary.sh - Source common.sh in uv_tool.sh and gcloud_installer.sh for normalize fn Capability detection fixes: - Classify $HOME/.nvm/ paths as "nvm" not "npm" in detect/classify - Filter .venvs/, .virtualenvs/, env/, miniconda, anaconda from installs - Add "nvm" case in reconcile.sh remove_installation() npm_global.sh improvements: - Read version_command/version_flag/binary_name from catalog JSON - Retry with --force on EEXIST errors (fixes gemini install) - Filter "npm warn deprecated" noise from output pip installer fix: - Skip pip upgrade when uv manages Python (externally-managed error) Python removal fixes: - Skip apt removal when version matches system Python (python3 dep) - Pre-cache sudo credentials to avoid repeated password prompts Guide UX improvements: - Track auto-update exit codes; count Installed vs Updated vs Failed - Use SUMMARY_INSTALLED counter (was declared but unused) - Show "self-managed" instead of "<unknown>" for skip_upstream tools - Better partial removal messaging (system binary vs real failure) - Count partial removals as Failed instead of silently ignoring - Unified print_summary() for both interrupt and normal completion Interrupt handling: - Add cleanup trap in github_release_binary.sh for temp files
1 parent e322ac0 commit 9854e15

13 files changed

Lines changed: 147 additions & 53 deletions

catalog/black.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"install_method": "uv_tool",
55
"description": "The uncompromising Python code formatter",
66
"homepage": "https://github.com/psf/black",
7-
"package_name": "black"
7+
"package_name": "black",
8+
"version_command": "black --version 2>/dev/null | head -1"
89
}

catalog/flake8.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"install_method": "uv_tool",
55
"description": "Python style guide enforcement tool combining PyFlakes, pycodestyle, and McCabe complexity checker",
66
"homepage": "https://github.com/PyCQA/flake8",
7-
"package_name": "flake8"
7+
"package_name": "flake8",
8+
"version_command": "flake8 --version 2>/dev/null | head -1"
89
}

catalog/gcloud.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
"homepage": "https://cloud.google.com/sdk/gcloud",
77
"binary_name": "gcloud",
88
"skip_upstream": true,
9+
"version_command": "gcloud version 2>/dev/null | head -1 | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+'",
910
"notes": "Installed via Google Cloud SDK installer; self-updates with 'gcloud components update'"
1011
}

catalog/isort.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"install_method": "uv_tool",
55
"description": "A Python utility to sort imports alphabetically and automatically separate them into sections",
66
"homepage": "https://github.com/PyCQA/isort",
7-
"package_name": "isort"
7+
"package_name": "isort",
8+
"version_command": "isort --version-number 2>/dev/null"
89
}

scripts/guide.sh

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@ SUMMARY_FAILED=0
2424
SUMMARY_REMOVED=0
2525

2626
print_summary() {
27+
local label="${1:-interrupted}"
2728
echo "================================================================================"
28-
echo "Summary (interrupted)"
29+
echo "Summary${label:+ ($label)}"
2930
echo "================================================================================"
30-
printf " Updated: %d\n" "$SUMMARY_UPDATED"
31-
printf " Removed: %d\n" "$SUMMARY_REMOVED"
32-
printf " Skipped: %d\n" "$SUMMARY_SKIPPED"
33-
printf " Failed: %d\n" "$SUMMARY_FAILED"
31+
printf " Installed: %d\n" "$SUMMARY_INSTALLED"
32+
printf " Updated: %d\n" "$SUMMARY_UPDATED"
33+
printf " Removed: %d\n" "$SUMMARY_REMOVED"
34+
printf " Skipped: %d\n" "$SUMMARY_SKIPPED"
35+
printf " Failed: %d\n" "$SUMMARY_FAILED"
3436
echo
37+
echo "Re-run: make audit"
3538
}
3639

3740
# Category filter: CATEGORY=python,go or --category=python
@@ -293,7 +296,13 @@ process_tool() {
293296
else
294297
printf " installed: %s via %s\n" "$installed" "${method:-unknown}"
295298
fi
296-
printf " target: %s\n" "$(osc8 "$url" "${latest:-<unknown>}")"
299+
# Show target; for self-managed tools (skip_upstream) show "self-managed" instead of <unknown>
300+
local target_display="${latest:-<unknown>}"
301+
local skip_upstream="$(catalog_get_property "$catalog_tool" skip_upstream)"
302+
if [ "$target_display" = "<unknown>" ] && [ "$skip_upstream" = "true" ]; then
303+
target_display="self-managed"
304+
fi
305+
printf " target: %s\n" "$(osc8 "$url" "$target_display")"
297306
check_multi_installs "$catalog_tool"
298307
printf " auto-updating...\n"
299308

@@ -306,26 +315,33 @@ process_tool() {
306315
fi
307316

308317
# Execute the install with version-specific environment variables
318+
local auto_update_success=0
309319
if [ "$catalog_tool" = "python" ] || [ -n "$is_multi_version" ] && [ "$catalog_tool" = "python" ]; then
310-
UV_PYTHON_SPEC="$latest" "$ROOT"/scripts/$install_cmd || true
320+
UV_PYTHON_SPEC="$latest" "$ROOT"/scripts/$install_cmd && auto_update_success=1 || true
311321
elif [ "$catalog_tool" = "ruby" ]; then
312-
RUBY_VERSION="$latest" "$ROOT"/scripts/$install_cmd || true
322+
RUBY_VERSION="$latest" "$ROOT"/scripts/$install_cmd && auto_update_success=1 || true
313323
elif [ "$catalog_tool" = "php" ] && [ -n "$version_cycle" ]; then
314-
PHP_VERSION="$version_cycle" "$ROOT"/scripts/$install_cmd || true
324+
PHP_VERSION="$version_cycle" "$ROOT"/scripts/$install_cmd && auto_update_success=1 || true
315325
elif [ "$catalog_tool" = "node" ] && [ -n "$version_cycle" ]; then
316-
NODE_VERSION="$version_cycle" "$ROOT"/scripts/$install_cmd || true
326+
NODE_VERSION="$version_cycle" "$ROOT"/scripts/$install_cmd && auto_update_success=1 || true
317327
elif [ "$catalog_tool" = "go" ] && [ -n "$version_cycle" ]; then
318-
GO_VERSION="$version_cycle" "$ROOT"/scripts/$install_cmd || true
328+
GO_VERSION="$version_cycle" "$ROOT"/scripts/$install_cmd && auto_update_success=1 || true
319329
else
320-
"$ROOT"/scripts/$install_cmd || true
330+
"$ROOT"/scripts/$install_cmd && auto_update_success=1 || true
321331
fi
322332

323333
# Re-audit with fresh collection for this specific tool
324334
CLI_AUDIT_JSON=1 CLI_AUDIT_COLLECT=1 CLI_AUDIT_MERGE=1 "$CLI" audit.py "$tool" >/dev/null 2>&1 || true
325335
reload_audit_json
326336
# Clean up any already-current marker left by installer
327337
rm -f "/tmp/.cli-audit/${catalog_tool}.already-current"
328-
SUMMARY_UPDATED=$((SUMMARY_UPDATED + 1))
338+
if [ "$auto_update_success" = "0" ]; then
339+
SUMMARY_FAILED=$((SUMMARY_FAILED + 1))
340+
elif [ -z "$installed" ]; then
341+
SUMMARY_INSTALLED=$((SUMMARY_INSTALLED + 1))
342+
else
343+
SUMMARY_UPDATED=$((SUMMARY_UPDATED + 1))
344+
fi
329345
return 0
330346
fi
331347

@@ -341,7 +357,13 @@ process_tool() {
341357

342358
check_multi_installs "$catalog_tool"
343359

344-
printf " target: %s\n" "$(osc8 "$url" "${latest:-<unknown>}")"
360+
# Show target; for self-managed tools (skip_upstream) show "self-managed" instead of <unknown>
361+
local target_display_p="${latest:-<unknown>}"
362+
local skip_upstream_p="$(catalog_get_property "$catalog_tool" skip_upstream)"
363+
if [ "$target_display_p" = "<unknown>" ] && [ "$skip_upstream_p" = "true" ]; then
364+
target_display_p="self-managed"
365+
fi
366+
printf " target: %s\n" "$(osc8 "$url" "$target_display_p")"
345367

346368
# Build install command from catalog metadata (use catalog_tool for script name)
347369
local install_cmd="install_tool.sh $catalog_tool"
@@ -593,7 +615,16 @@ process_tool() {
593615
printf " ✓ %s has been removed\n" "$tool"
594616
SUMMARY_REMOVED=$((SUMMARY_REMOVED + 1))
595617
else
596-
printf " ⚠️ %s may not have been fully removed (still detected: %s)\n" "$tool" "$still_installed"
618+
# Check if remaining installation is a system/apt binary that we can't remove
619+
local remaining_method="$(json_field "$tool" installed_method)"
620+
if [ "$remaining_method" = "apt" ] || [ "$remaining_method" = "system" ]; then
621+
printf " ✓ User-managed %s removed (system %s still present at %s — managed by OS)\n" \
622+
"$tool" "$still_installed" "$remaining_method"
623+
SUMMARY_REMOVED=$((SUMMARY_REMOVED + 1))
624+
else
625+
printf " ⚠️ %s may not have been fully removed (still detected: %s via %s)\n" "$tool" "$still_installed" "${remaining_method:-unknown}"
626+
SUMMARY_FAILED=$((SUMMARY_FAILED + 1))
627+
fi
597628
fi
598629
else
599630
printf " Tool is not installed, nothing to remove\n"
@@ -980,12 +1011,4 @@ fi
9801011

9811012
# Print final summary
9821013
echo
983-
echo "================================================================================"
984-
echo "Summary"
985-
echo "================================================================================"
986-
printf " Updated: %d\n" "$SUMMARY_UPDATED"
987-
printf " Removed: %d\n" "$SUMMARY_REMOVED"
988-
printf " Skipped: %d\n" "$SUMMARY_SKIPPED"
989-
printf " Failed: %d\n" "$SUMMARY_FAILED"
990-
echo
991-
echo "Re-run: make audit"
1014+
print_summary ""

scripts/install_pip.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@ install_pip() {
3939
fi
4040

4141
# Upgrade pip to latest
42-
python3 -m pip install --upgrade pip 2>&1 || true
42+
# Use uv if available (avoids externally-managed-environment errors)
43+
if command -v uv >/dev/null 2>&1; then
44+
# uv manages Python installations; pip upgrade is best left to uv's Python
45+
echo "[pip] pip is managed by the Python distribution (uv/brew). Skipping standalone upgrade." >&2
46+
else
47+
# Only attempt pip upgrade if not in an externally-managed environment
48+
python3 -m pip install --upgrade pip 2>&1 | grep -v "externally-managed-environment" || true
49+
fi
4350

4451
after="$(get_pip_version)"
4552
path="$(command -v pip3 2>/dev/null || command -v pip 2>/dev/null || true)"

scripts/install_python.sh

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,14 +222,26 @@ uninstall_py_tools() {
222222

223223
echo "[python] Removing Python $short_ver..." >&2
224224

225+
# Pre-cache sudo credentials to avoid repeated password prompts
226+
if have sudo && have apt-get; then
227+
sudo -v 2>/dev/null || true
228+
fi
229+
225230
# Remove via uv if available
226231
if command -v uv >/dev/null 2>&1; then
227232
uv python uninstall "$short_ver" 2>/dev/null || true
228233
fi
229234

230235
# Remove version-specific apt packages (e.g., python3.10)
236+
# BUT skip if this version is the system default python3 (dependency of python3 metapackage)
231237
if have apt-get; then
232-
apt_remove_if_present "python${short_ver}" "python${short_ver}-venv" "python${short_ver}-dev" 2>/dev/null || true
238+
local system_py_ver=""
239+
system_py_ver="$(/usr/bin/python3 --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' | head -1 || true)"
240+
if [ "$short_ver" = "$system_py_ver" ]; then
241+
echo "[python] Skipping apt removal: python${short_ver} is the system Python (dependency of python3)" >&2
242+
else
243+
apt_remove_if_present "python${short_ver}" "python${short_ver}-venv" "python${short_ver}-dev" 2>/dev/null || true
244+
fi
233245
fi
234246

235247
# Remove version-specific brew formula if it exists

scripts/installers/gcloud_installer.sh

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
set -euo pipefail
44

55
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6+
. "$DIR/lib/common.sh"
67
. "$DIR/lib/install_strategy.sh"
78

89
TOOL="${1:-gcloud}"
@@ -14,6 +15,7 @@ if [ ! -f "$CATALOG_FILE" ]; then
1415
fi
1516

1617
BINARY_NAME="$(jq -r '.binary_name' "$CATALOG_FILE")"
18+
VERSION_COMMAND="$(jq -r '.version_command // empty' "$CATALOG_FILE")"
1719
GCLOUD_SDK="$HOME/google-cloud-sdk"
1820
GCLOUD_BIN="$GCLOUD_SDK/bin"
1921

@@ -22,8 +24,17 @@ if [ -d "$GCLOUD_BIN" ]; then
2224
export PATH="$GCLOUD_BIN:$PATH"
2325
fi
2426

27+
# Version detection helper
28+
get_gcloud_version() {
29+
if [ -n "$VERSION_COMMAND" ]; then
30+
timeout 5 bash -c "$VERSION_COMMAND" 2>/dev/null || true
31+
elif command -v "$BINARY_NAME" >/dev/null 2>&1; then
32+
"$BINARY_NAME" version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true
33+
fi
34+
}
35+
2536
# Get current version
26-
before="$(command -v "$BINARY_NAME" >/dev/null 2>&1 && "$BINARY_NAME" version 2>/dev/null | head -1 || true)"
37+
before="$(get_gcloud_version)"
2738

2839
if command -v "$BINARY_NAME" >/dev/null 2>&1; then
2940
# Already installed - use built-in update (quiet)
@@ -62,7 +73,7 @@ for cmd in gcloud gsutil bq; do
6273
done
6374

6475
# Report
65-
after="$(command -v "$BINARY_NAME" >/dev/null 2>&1 && "$BINARY_NAME" version 2>/dev/null | head -1 || true)"
76+
after="$(get_gcloud_version)"
6677
path="$(command -v "$BINARY_NAME" 2>/dev/null || true)"
6778
printf "[%s] before: %s\n" "$TOOL" "${before:-<none>}"
6879
printf "[%s] after: %s\n" "$TOOL" "${after:-<none>}"

scripts/installers/github_release_binary.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
77
. "$DIR/lib/common.sh"
88
. "$DIR/lib/install_strategy.sh"
99

10+
# Cleanup temp files on interrupt
11+
_grb_cleanup() {
12+
rm -f "/tmp/${BINARY_NAME:-tool}.$$" 2>/dev/null || true
13+
rm -rf "/tmp/${BINARY_NAME:-tool}-extract.$$" 2>/dev/null || true
14+
}
15+
trap '_grb_cleanup; exit 130' INT TERM
16+
1017
TOOL="${1:-}"
1118
if [ -z "$TOOL" ]; then
1219
echo "Usage: $0 TOOL_NAME" >&2
@@ -275,6 +282,9 @@ if command -v "$BINARY_NAME" >/dev/null 2>&1; then
275282
timeout 2 "$BINARY_NAME" version </dev/null 2>/dev/null | head -1 || true)"
276283
fi
277284
fi
285+
# Normalize verbose version output
286+
before="$(normalize_version_output "${before:-}")"
287+
after="$(normalize_version_output "${after:-}")"
278288
path="$(command -v "$BINARY_NAME" 2>/dev/null || true)"
279289
printf "[%s] before: %s\n" "$TOOL" "${before:-<none>}"
280290
printf "[%s] after: %s\n" "$TOOL" "${after:-<none>}"

scripts/installers/npm_global.sh

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ fi
2626

2727
# Parse catalog
2828
PACKAGE_NAME="$(jq -r '.package_name // .name' "$CATALOG_FILE")"
29+
BINARY_NAME="$(jq -r '.binary_name // empty' "$CATALOG_FILE")"
30+
BINARY_NAME="${BINARY_NAME:-$TOOL}"
31+
VERSION_COMMAND="$(jq -r '.version_command // empty' "$CATALOG_FILE")"
32+
VERSION_FLAG="$(jq -r '.version_flag // empty' "$CATALOG_FILE")"
2933

3034
# Detect available package manager (pnpm > npm > yarn)
3135
# Only use pnpm if it's properly configured with a global bin directory
@@ -47,45 +51,57 @@ if [ -z "$PKG_MANAGER" ]; then
4751
exit 1
4852
fi
4953

54+
# Version detection helper (uses catalog version_command/version_flag if available)
55+
get_npm_tool_version() {
56+
if [ -n "$VERSION_COMMAND" ]; then
57+
timeout 2 bash -c "$VERSION_COMMAND" 2>/dev/null || true
58+
return
59+
fi
60+
local bin="$1"
61+
if command -v "$bin" >/dev/null 2>&1; then
62+
if [ -n "$VERSION_FLAG" ]; then
63+
timeout 2 "$bin" $VERSION_FLAG </dev/null 2>/dev/null | head -1 || true
64+
else
65+
timeout 2 "$bin" --version </dev/null 2>/dev/null | head -1 || true
66+
fi
67+
fi
68+
}
69+
5070
# Get current version
51-
before=""
52-
if command -v "$TOOL" >/dev/null 2>&1; then
53-
before="$("$TOOL" --version 2>/dev/null || true)"
54-
fi
71+
before="$(get_npm_tool_version "$BINARY_NAME")"
5572

5673
# Install or upgrade globally
5774
echo "[$TOOL] Installing package globally via $PKG_MANAGER: $PACKAGE_NAME" >&2
5875
case "$PKG_MANAGER" in
5976
pnpm)
60-
# Use @latest to ensure we get the newest version
61-
pnpm add -g "${PACKAGE_NAME}@latest" || {
77+
pnpm add -g "${PACKAGE_NAME}@latest" 2>&1 | grep -v "^npm warn deprecated" || {
6278
echo "[$TOOL] Error: pnpm install failed" >&2
6379
exit 1
6480
}
6581
;;
6682
npm)
67-
# Use @latest to ensure we get the newest version (bypasses npm cache issues)
68-
npm install -g "${PACKAGE_NAME}@latest" || {
69-
echo "[$TOOL] Error: npm install failed" >&2
70-
exit 1
71-
}
83+
# Try normal install first; retry with --force on EEXIST errors
84+
if ! npm install -g "${PACKAGE_NAME}@latest" 2>&1 | grep -v "^npm warn deprecated"; then
85+
if npm install -g --force "${PACKAGE_NAME}@latest" 2>&1 | grep -v "^npm warn deprecated"; then
86+
: # Force install succeeded
87+
else
88+
echo "[$TOOL] Error: npm install failed" >&2
89+
exit 1
90+
fi
91+
fi
7292
;;
7393
yarn)
74-
# Use @latest to ensure we get the newest version
75-
yarn global add "${PACKAGE_NAME}@latest" || {
94+
yarn global add "${PACKAGE_NAME}@latest" 2>&1 | grep -v "^npm warn deprecated" || {
7695
echo "[$TOOL] Error: yarn install failed" >&2
7796
exit 1
7897
}
7998
;;
8099
esac
81100

82101
# Report
83-
after=""
84-
if command -v "$TOOL" >/dev/null 2>&1; then
85-
after="$("$TOOL" --version 2>/dev/null || true)"
86-
fi
102+
after="$(get_npm_tool_version "$BINARY_NAME")"
87103

88-
path="$(command -v "$TOOL" 2>/dev/null || true)"
104+
path="$(command -v "$BINARY_NAME" 2>/dev/null || true)"
89105
printf "[%s] before: %s\n" "$TOOL" "${before:-<none>}"
90106
printf "[%s] after: %s\n" "$TOOL" "${after:-<none>}"
91107
if [ -n "$path" ]; then printf "[%s] path: %s\n" "$TOOL" "$path"; fi

0 commit comments

Comments
 (0)