Skip to content

Commit e322ac0

Browse files
committed
fix(upgrade): resolve 23 issues in make upgrade workflow
- Fix pip installer (was installing python3 instead of pip via brew) - Fix Python removal detecting wrong binaries (generic python3) - Fix Node.js install reporting "Failed" then succeeding - Fix Node.js removal trying to remove other nvm versions - Add go_install.sh installer for templ and other Go tools - Fix pnpm reconciliation showing "unknown" install method - Fix GAM version parsed as Python version instead of GAM version - Fix semgrep showing <none> for before-version - Suppress Homebrew auto-update noise during upgrades - Filter noisy multi-installation detection (WSL Windows paths, conda) - Clarify "a = all" prompt to "a = all categories" - Clarify "P" skip option wording for version cycles - Add normalize_version_output() for consistent version display - Add final summary with updated/removed/skipped/failed counters - Fix "<none> via unknown" display for uninstalled tools - Add SIGINT trap for partial summary on interruption - Fix terraform unnecessary sudo prompt on non-apt installs - Fix npm updated at wrong path (add nvm use default) - Fix uv cascading tool updates during reconciliation - Fix install_tool.sh uninstall leaking into universal removal
1 parent ef4b273 commit e322ac0

14 files changed

Lines changed: 326 additions & 28 deletions

catalog/gam.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"github_repo": "GAM-team/GAM",
88
"binary_name": "gam",
99
"version_flag": "version",
10+
"version_command": "gam version 2>/dev/null | head -1 | grep -oE 'GAM [0-9]+\\.[0-9]+\\.[0-9]+' | awk '{print $2}'",
11+
"version_regex": "([0-9]+\\.[0-9]+\\.[0-9]+)",
1012
"package_name": "gam7",
1113
"auto_update": true
1214
}

catalog/pip.json

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
{
22
"name": "pip",
33
"category": "python",
4-
"install_method": "package_manager",
4+
"install_method": "dedicated_script",
55
"description": "Python package installer",
66
"homepage": "https://pip.pypa.io/",
77
"package_name": "pip",
88
"binary_name": "pip",
9-
"packages": {
10-
"apt": "python3-pip",
11-
"brew": "python3",
12-
"dnf": "python3-pip",
13-
"pacman": "python-pip"
14-
},
15-
"notes": "pip typically comes with Python 3. Use python3 -m pip if pip command is not available."
9+
"script": "install_pip.sh",
10+
"notes": "pip is bundled with Python 3. Use python3 -m pip if pip command is not available."
1611
}

scripts/guide.sh

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,39 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33
trap '' PIPE
4+
# Graceful interrupt handling
5+
INTERRUPTED=0
6+
trap 'INTERRUPTED=1; echo; echo "⚠️ Interrupted. Partial summary:"; print_summary; exit 130' INT
47

58
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
69
ROOT="$(cd "$DIR/.." && pwd)"
710
VERBOSE="${VERBOSE:-0}"
11+
# Suppress Homebrew auto-update during upgrade runs to reduce noise
12+
export HOMEBREW_NO_AUTO_UPDATE=1
813
OFFLINE="${OFFLINE:-0}"
914
CLI="${PYTHON:-python3}"
1015

1116
# Ignore pins: IGNORE_PINS=1 to show all tools regardless of pin status
1217
IGNORE_PINS="${IGNORE_PINS:-0}"
1318

19+
# Summary counters
20+
SUMMARY_UPDATED=0
21+
SUMMARY_INSTALLED=0
22+
SUMMARY_SKIPPED=0
23+
SUMMARY_FAILED=0
24+
SUMMARY_REMOVED=0
25+
26+
print_summary() {
27+
echo "================================================================================"
28+
echo "Summary (interrupted)"
29+
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"
34+
echo
35+
}
36+
1437
# Category filter: CATEGORY=python,go or --category=python
1538
CATEGORY_FILTER="${CATEGORY:-}"
1639
for arg in "$@"; do
@@ -241,6 +264,7 @@ process_tool() {
241264
printf " installed: %s via %s\n" "$installed" "$method"
242265
printf " target: %s (same)\n" "$(osc8 "$url" "$latest")"
243266
check_multi_installs "$catalog_tool"
267+
SUMMARY_SKIPPED=$((SUMMARY_SKIPPED + 1))
244268
printf " up-to-date; skipping.\n"
245269
return 0
246270
fi
@@ -264,7 +288,11 @@ process_tool() {
264288
# BUT: multi-version tools always prompt (more significant operation)
265289
if [ "$auto_update" = "true" ] && [ -z "$is_multi_version" ]; then
266290
printf "\n==> %s %s [auto-update]\n" "$icon" "$display"
267-
printf " installed: %s via %s\n" "${installed:-<none>}" "${method:-unknown}"
291+
if [ -z "$installed" ]; then
292+
printf " installed: not installed\n"
293+
else
294+
printf " installed: %s via %s\n" "$installed" "${method:-unknown}"
295+
fi
268296
printf " target: %s\n" "$(osc8 "$url" "${latest:-<unknown>}")"
269297
check_multi_installs "$catalog_tool"
270298
printf " auto-updating...\n"
@@ -297,14 +325,19 @@ process_tool() {
297325
reload_audit_json
298326
# Clean up any already-current marker left by installer
299327
rm -f "/tmp/.cli-audit/${catalog_tool}.already-current"
328+
SUMMARY_UPDATED=$((SUMMARY_UPDATED + 1))
300329
return 0
301330
fi
302331

303332
# Prompt for installation/update
304333
printf "\n==> %s %s\n" "$icon" "$display"
305334
[ -n "$description" ] && printf " %s\n" "$description"
306335
[ -n "$homepage" ] && printf " Homepage: %s\n" "$(osc8 "$homepage" "$homepage")"
307-
printf " installed: %s via %s\n" "${installed:-<none>}" "${method:-unknown}"
336+
if [ -z "$installed" ]; then
337+
printf " installed: not installed\n"
338+
else
339+
printf " installed: %s via %s\n" "$installed" "${method:-unknown}"
340+
fi
308341

309342
check_multi_installs "$catalog_tool"
310343

@@ -335,7 +368,7 @@ process_tool() {
335368
fi
336369
printf " r = Remove/uninstall this tool\n"
337370
if [ -n "$is_multi_version" ]; then
338-
printf " P = Skip ALL %s cycles (never install any %s)\n" "$catalog_tool" "$catalog_tool"
371+
printf " P = Skip ALL outdated %s cycles\n" "$catalog_tool"
339372
fi
340373
else
341374
printf " y = Install now\n"
@@ -344,7 +377,7 @@ process_tool() {
344377
printf " s = Skip only %s (ask again when newer patch available)\n" "$latest"
345378
if [ -n "$is_multi_version" ]; then
346379
printf " p = Never install %s (skip entire %s.x cycle)\n" "$display" "$version_cycle"
347-
printf " P = Skip ALL %s cycles (never install any %s)\n" "$catalog_tool" "$catalog_tool"
380+
printf " P = Skip ALL outdated %s cycles\n" "$catalog_tool"
348381
else
349382
printf " p = Never install (permanently skip this tool)\n"
350383
fi
@@ -419,6 +452,7 @@ process_tool() {
419452
if [ "$upgrade_success" = "0" ]; then
420453
# Install script failed
421454
printf "\n ⚠️ Upgrade failed (install script error)\n"
455+
SUMMARY_FAILED=$((SUMMARY_FAILED + 1))
422456
prompt_pin_version "$tool" "$installed"
423457
elif [ -n "$binary_already_current" ]; then
424458
# Binary hash matches target release - upgrade succeeded despite version string
@@ -435,6 +469,7 @@ process_tool() {
435469
fi
436470
else
437471
# Upgrade succeeded - remove any existing pin to avoid stale pins
472+
SUMMARY_UPDATED=$((SUMMARY_UPDATED + 1))
438473
local existing_pin="$(pins_get "$tool")"
439474
if [ -n "$existing_pin" ] && [ "$existing_pin" != "never" ]; then
440475
"$ROOT"/scripts/unpin_version.sh "$tool" || true
@@ -478,18 +513,23 @@ process_tool() {
478513
if [ "$upgrade_success_a" = "0" ]; then
479514
printf "\n ⚠️ Upgrade failed (install script error)\n"
480515
printf " Auto-update is still enabled - will try again next time.\n"
516+
SUMMARY_FAILED=$((SUMMARY_FAILED + 1))
481517
elif [ -n "$binary_already_current_a" ]; then
482518
printf " ✓ Auto-update enabled. Binary already matches target release.\n"
519+
SUMMARY_UPDATED=$((SUMMARY_UPDATED + 1))
483520
elif [ "$new_installed_a" = "$installed" ] && [ "$new_installed_a" != "$latest" ]; then
484521
# Version didn't change - but check for prefix match (e.g., 3.13 vs 3.13.11)
485522
if [[ "$latest" == "$new_installed_a"* ]] || [[ "$new_installed_a" == "$latest"* ]]; then
486523
printf " ✓ Auto-update enabled. This tool will update automatically in future.\n"
524+
SUMMARY_UPDATED=$((SUMMARY_UPDATED + 1))
487525
else
488526
printf "\n ⚠️ Upgrade did not succeed (version unchanged)\n"
489527
printf " Auto-update is still enabled - will try again next time.\n"
528+
SUMMARY_FAILED=$((SUMMARY_FAILED + 1))
490529
fi
491530
else
492531
printf " ✓ Auto-update enabled. This tool will update automatically in future.\n"
532+
SUMMARY_UPDATED=$((SUMMARY_UPDATED + 1))
493533
# Remove any existing pin
494534
local existing_pin_a="$(pins_get "$tool")"
495535
if [ -n "$existing_pin_a" ]; then
@@ -501,6 +541,7 @@ process_tool() {
501541
# Skip this specific patch version only
502542
printf " Skipping only %s (will prompt again when newer patch available)\n" "$latest"
503543
"$ROOT"/scripts/pin_version.sh "$tool" "$latest" || true
544+
SUMMARY_SKIPPED=$((SUMMARY_SKIPPED + 1))
504545
;;
505546
[p])
506547
if [ -n "$installed" ]; then
@@ -550,6 +591,7 @@ process_tool() {
550591
local still_installed="$(json_field "$tool" installed)"
551592
if [ -z "$still_installed" ]; then
552593
printf " ✓ %s has been removed\n" "$tool"
594+
SUMMARY_REMOVED=$((SUMMARY_REMOVED + 1))
553595
else
554596
printf " ⚠️ %s may not have been fully removed (still detected: %s)\n" "$tool" "$still_installed"
555597
fi
@@ -570,6 +612,7 @@ process_tool() {
570612
;;
571613
*)
572614
# User declined (N or empty)
615+
SUMMARY_SKIPPED=$((SUMMARY_SKIPPED + 1))
573616
;;
574617
esac
575618
}
@@ -863,7 +906,7 @@ for category in $(printf '%s\n' "${!CATEGORY_TOOLS[@]}" | while read c; do echo
863906
# Category-level prompt (skip if auto-yes mode)
864907
if [ "${AUTO_YES_ALL:-}" != "1" ]; then
865908
printf " Tools: %s\n" "$(echo $tools | tr ' ' ', ' | sed 's/^, //')"
866-
printf " Process this category? [Y/n/a=all/s=skip-all] "
909+
printf " Process this category? [Y/n/a=all categories/s=skip-all] "
867910

868911
cat_ans=""
869912
if [ -t 0 ]; then
@@ -935,5 +978,14 @@ if [ -n "$DEPRECATED_TOOLS" ]; then
935978
fi
936979
fi
937980

981+
# Print final summary
982+
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"
938990
echo
939-
echo "All done. Re-run: make audit"
991+
echo "Re-run: make audit"

scripts/install_node.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ get_specific_node_version() {
3838
install_node() {
3939
ensure_nvm
4040
nvm install "$NODE_CHANNEL"
41+
# Re-source nvm to ensure the new version is active in this shell
42+
ensure_nvm_loaded
4143

4244
# Only set default if this is NOT a multi-version install
4345
# (multi-version = specific major version like 24, 25)
@@ -66,6 +68,8 @@ install_node() {
6668
update_node() {
6769
ensure_nvm
6870
nvm install "$NODE_CHANNEL"
71+
# Re-source nvm to ensure the new version is active in this shell
72+
ensure_nvm_loaded
6973

7074
# Only set default and update global packages if NOT a multi-version install
7175
if [ -z "${NODE_VERSION:-}" ]; then

scripts/install_pip.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env bash
2+
# pip installer - ensures pip is available via Python's ensurepip
3+
set -euo pipefail
4+
5+
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
. "$DIR/lib/common.sh"
7+
. "$DIR/lib/install_strategy.sh"
8+
9+
ACTION="${1:-install}"
10+
11+
get_pip_version() {
12+
if command -v pip3 >/dev/null 2>&1; then
13+
pip3 --version 2>/dev/null | awk '{print $2}' || true
14+
elif command -v pip >/dev/null 2>&1; then
15+
pip --version 2>/dev/null | awk '{print $2}' || true
16+
elif command -v python3 >/dev/null 2>&1; then
17+
python3 -m pip --version 2>/dev/null | awk '{print $2}' || true
18+
fi
19+
}
20+
21+
install_pip() {
22+
local before after path
23+
24+
before="$(get_pip_version)"
25+
26+
# Ensure Python is available
27+
if ! command -v python3 >/dev/null 2>&1; then
28+
echo "[pip] Error: python3 not found. Install Python first." >&2
29+
exit 1
30+
fi
31+
32+
# Use ensurepip to bootstrap pip if not present
33+
if ! python3 -m pip --version >/dev/null 2>&1; then
34+
echo "[pip] Bootstrapping pip via ensurepip..." >&2
35+
python3 -m ensurepip --upgrade 2>&1 || {
36+
echo "[pip] ensurepip failed, trying apt..." >&2
37+
apt_install_if_missing python3-pip || true
38+
}
39+
fi
40+
41+
# Upgrade pip to latest
42+
python3 -m pip install --upgrade pip 2>&1 || true
43+
44+
after="$(get_pip_version)"
45+
path="$(command -v pip3 2>/dev/null || command -v pip 2>/dev/null || true)"
46+
47+
printf "[%s] before: %s\n" "pip" "${before:-<none>}"
48+
printf "[%s] after: %s\n" "pip" "${after:-<none>}"
49+
if [ -n "$path" ]; then printf "[%s] path: %s\n" "pip" "$path"; fi
50+
51+
refresh_snapshot "pip"
52+
}
53+
54+
uninstall_pip() {
55+
echo "[pip] pip is bundled with Python and cannot be uninstalled separately" >&2
56+
}
57+
58+
case "$ACTION" in
59+
install|update|reconcile) install_pip ;;
60+
uninstall) uninstall_pip ;;
61+
*) echo "Usage: $0 {install|update|uninstall|reconcile}" ; exit 2 ;;
62+
esac

scripts/install_python.sh

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,42 @@ update_py_stack() {
210210
}
211211

212212
uninstall_py_tools() {
213-
local tools=(black isort flake8 bandit httpie pre-commit poetry semgrep)
214-
if command -v uv >/dev/null 2>&1; then
215-
for p in "${tools[@]}"; do uv tool uninstall "$p" >/dev/null 2>&1 || true; done
213+
local PY_SPEC="${UV_PYTHON_SPEC:-}"
214+
215+
if [ -n "$PY_SPEC" ]; then
216+
# Version-specific removal (e.g., UV_PYTHON_SPEC=3.10)
217+
local short_ver="${PY_SPEC}"
218+
# Extract major.minor from full version like 3.10.19
219+
case "$PY_SPEC" in
220+
*.*.*) short_ver="${PY_SPEC%.*}" ;;
221+
esac
222+
223+
echo "[python] Removing Python $short_ver..." >&2
224+
225+
# Remove via uv if available
226+
if command -v uv >/dev/null 2>&1; then
227+
uv python uninstall "$short_ver" 2>/dev/null || true
228+
fi
229+
230+
# Remove version-specific apt packages (e.g., python3.10)
231+
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
233+
fi
234+
235+
# Remove version-specific brew formula if it exists
236+
if have brew; then
237+
brew uninstall "python@${short_ver}" 2>/dev/null || true
238+
fi
239+
240+
echo "[python] Python $short_ver removal complete" >&2
216241
else
217-
for p in "${tools[@]}"; do pipx uninstall "$p" >/dev/null 2>&1 || true; done
242+
# Full uninstall: remove all CLI tools
243+
local tools=(black isort flake8 bandit httpie pre-commit poetry semgrep)
244+
if command -v uv >/dev/null 2>&1; then
245+
for p in "${tools[@]}"; do uv tool uninstall "$p" >/dev/null 2>&1 || true; done
246+
else
247+
for p in "${tools[@]}"; do pipx uninstall "$p" >/dev/null 2>&1 || true; done
248+
fi
218249
fi
219250
}
220251

scripts/install_tool.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ if [ "$ACTION" = "uninstall" ]; then
4848
if [ -n "$script_name" ] && [ -f "$DIR/$script_name" ]; then
4949
"$DIR/$script_name" uninstall || true
5050
fi
51+
# Dedicated scripts handle their own cleanup completely
52+
# Don't try to detect and remove additional installations
53+
# (especially important for multi-version tools like node, python, go)
54+
exit 0
5155
fi
5256

5357
binary_name="$(jq -r '.binary_name // ""' "$CATALOG_FILE" 2>/dev/null || echo "$TOOL")"

scripts/install_uv.sh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ self_update_uv() {
6262
upgrade_uv_tools() {
6363
command -v uv >/dev/null 2>&1 || return 0
6464
echo "Checking uv-managed tools..."
65-
uv tool upgrade --all || true
65+
# Only show tool name and version changes, not dependency details
66+
uv tool upgrade --all 2>&1 | grep -E '^(Updated |Modified |Installed |No updates)' || true
6667
}
6768

6869
reconcile_uv() {
@@ -75,8 +76,6 @@ reconcile_uv() {
7576
fi
7677
# Try self-update to latest stable
7778
self_update_uv || true
78-
# Upgrade all uv-managed tools
79-
upgrade_uv_tools || true
8079
# Verify final state
8180
echo "uv path: $(command -v uv 2>/dev/null || echo '<none>')"
8281
uv --version 2>/dev/null || true

0 commit comments

Comments
 (0)