Skip to content

Commit d3c5326

Browse files
committed
fix(guide): improve reliability of upgrade prompts and tool removal
- Clarify p/P/s option text to unambiguously show scope (patch vs cycle vs all) - Fix grep -c || echo 0 producing "0\n0" causing integer expression errors - Add requires dependency checking (npm/pnpm/codex/eslint/prettier/yarn require node) - Load nvm in npm_self_update.sh and reconcile.sh so node tools work after apt removal - Add missing apt_install_if_missing function to common.sh - Make node uninstall version-aware: removing node@24 no longer nukes all of nvm - Pass version env vars (NODE_VERSION, etc.) when removing multi-version tools
1 parent 46060f1 commit d3c5326

13 files changed

Lines changed: 149 additions & 18 deletions

File tree

catalog/codex.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"github_repo": "openai/codex",
88
"package_name": "@openai/codex",
99
"binary_name": "codex",
10+
"requires": ["node"],
1011
"version_command": "codex --version 2>/dev/null | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+' | head -1",
1112
"guide": {
1213
"display_name": "OpenAI Codex CLI",

catalog/eslint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"homepage": "https://eslint.org",
77
"github_repo": "eslint/eslint",
88
"binary_name": "eslint",
9+
"requires": ["node"],
910
"package_name": "eslint",
1011
"guide": {
1112
"display_name": "ESLint",

catalog/npm.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
"homepage": "https://www.npmjs.com/",
77
"package_name": "npm",
88
"binary_name": "npm",
9+
"requires": ["node"],
910
"notes": "npm comes bundled with Node.js but can be upgraded independently using 'npm install -g npm@latest'"
1011
}

catalog/pnpm.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"homepage": "https://pnpm.io/",
77
"package_name": "pnpm",
88
"binary_name": "pnpm",
9+
"requires": ["node"],
910
"available_methods": [
1011
{
1112
"method": "npm",

catalog/prettier.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"description": "Opinionated code formatter",
66
"homepage": "https://prettier.io",
77
"package_name": "prettier",
8+
"requires": ["node"],
89
"notes": "Prettier is a JavaScript/Node.js tool installed via npm"
910
}

catalog/yarn.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"homepage": "https://yarnpkg.com/",
77
"package_name": "yarn",
88
"binary_name": "yarn",
9+
"requires": ["node"],
910
"script": "install_yarn.sh",
1011
"notes": "Installed via Node.js corepack or npm. Requires Node.js to be installed via nvm. Do NOT install via apt (conflicts with cmdtest package).",
1112
"guide": {

scripts/guide.sh

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,15 @@ process_tool() {
176176
local homepage="$(catalog_get_property "$catalog_tool" homepage)"
177177
local auto_update="$(config_get_auto_update "$catalog_tool")"
178178

179+
# Check if runtime requirements are satisfied (e.g., npm requires node)
180+
local missing_req
181+
missing_req="$(catalog_check_requires "$catalog_tool")" || true
182+
if [ -n "$missing_req" ]; then
183+
printf "\n==> ⏭️ %s\n" "$display"
184+
printf " skipped: requires '%s' which is not installed\n" "$missing_req"
185+
return 0
186+
fi
187+
179188
# Check if migration needed (deprecated install method)
180189
# But skip migration if native binary already exists and works
181190
local needs_migration=""
@@ -276,7 +285,7 @@ process_tool() {
276285
local all_installs
277286
all_installs="$(detect_all_installations "$catalog_tool" "$binary_name" 2>/dev/null || true)"
278287
local install_count
279-
install_count="$(echo "$all_installs" | grep -c . || echo 0)"
288+
install_count="$(echo "$all_installs" | grep -c . || true)"
280289
if [ "$install_count" -gt 1 ]; then
281290
printf " ⚠️ Multiple installations detected (%d):\n" "$install_count"
282291
echo "$all_installs" | while IFS=: read -r inst_method inst_path; do
@@ -303,20 +312,26 @@ process_tool() {
303312
printf " Y = Upgrade now (default)\n"
304313
printf " a = Always update (upgrade now + auto-update in future)\n"
305314
printf " n = Skip (ask again next time)\n"
306-
printf " s = Skip version %s (ask again if newer available)\n" "$latest"
307-
printf " p = Pin to %s (don't ask for upgrades)\n" "$installed"
315+
printf " s = Skip only %s (ask again when newer patch available)\n" "$latest"
316+
if [ -n "$is_multi_version" ]; then
317+
printf " p = Pin %s cycle to %s (don't upgrade)\n" "$version_cycle" "$installed"
318+
else
319+
printf " p = Pin to %s (don't ask for upgrades)\n" "$installed"
320+
fi
308321
printf " r = Remove/uninstall this tool\n"
309322
if [ -n "$is_multi_version" ]; then
310-
printf " P = Skip all %s versions (never install any)\n" "$catalog_tool"
323+
printf " P = Skip ALL %s cycles (never install any %s)\n" "$catalog_tool" "$catalog_tool"
311324
fi
312325
else
313326
printf " y = Install now\n"
314327
printf " a = Always update (install now + auto-update in future)\n"
315328
printf " N = Skip (default, ask again next time)\n"
316-
printf " s = Skip version %s (ask again if newer available)\n" "$latest"
317-
printf " p = Never install (permanently skip this version)\n"
329+
printf " s = Skip only %s (ask again when newer patch available)\n" "$latest"
318330
if [ -n "$is_multi_version" ]; then
319-
printf " P = Skip all %s versions (never install any)\n" "$catalog_tool"
331+
printf " p = Never install %s (skip entire %s.x cycle)\n" "$display" "$version_cycle"
332+
printf " P = Skip ALL %s cycles (never install any %s)\n" "$catalog_tool" "$catalog_tool"
333+
else
334+
printf " p = Never install (permanently skip this tool)\n"
320335
fi
321336
fi
322337

@@ -453,26 +468,49 @@ process_tool() {
453468
fi
454469
;;
455470
[Ss])
456-
# Skip this specific version
457-
printf " Skipping version %s (will prompt again if newer version available)\n" "$latest"
471+
# Skip this specific patch version only
472+
printf " Skipping only %s (will prompt again when newer patch available)\n" "$latest"
458473
"$ROOT"/scripts/pin_version.sh "$tool" "$latest" || true
459474
;;
460475
[p])
461476
if [ -n "$installed" ]; then
462477
# Pin to current version
463-
printf " Pinning to current version %s\n" "$installed"
478+
if [ -n "$is_multi_version" ]; then
479+
printf " Pinning %s cycle to %s\n" "$version_cycle" "$installed"
480+
else
481+
printf " Pinning to current version %s\n" "$installed"
482+
fi
464483
"$ROOT"/scripts/pin_version.sh "$tool" "$installed" || true
465484
else
466-
# Never install - pin to "never" for this specific version
467-
printf " Marking as 'never install' (permanently skip this version)\n"
485+
# Never install - pin to "never" for this version cycle
486+
if [ -n "$is_multi_version" ]; then
487+
printf " Marking %s cycle as 'never install'\n" "$version_cycle"
488+
else
489+
printf " Marking as 'never install' (permanently skip this tool)\n"
490+
fi
468491
"$ROOT"/scripts/pin_version.sh "$tool" "never" || true
469492
fi
470493
;;
471494
[r])
472495
# Remove/uninstall this tool (only for installed tools)
473496
if [ -n "$installed" ]; then
474497
printf " Removing %s...\n" "$tool"
475-
"$ROOT"/scripts/install_tool.sh "$catalog_tool" uninstall || true
498+
# Pass version cycle for multi-version tools so only that cycle is removed
499+
if [ -n "$is_multi_version" ] && [ -n "$version_cycle" ]; then
500+
if [ "$catalog_tool" = "node" ]; then
501+
NODE_VERSION="$version_cycle" "$ROOT"/scripts/install_tool.sh "$catalog_tool" uninstall || true
502+
elif [ "$catalog_tool" = "python" ]; then
503+
UV_PYTHON_SPEC="$version_cycle" "$ROOT"/scripts/install_tool.sh "$catalog_tool" uninstall || true
504+
elif [ "$catalog_tool" = "go" ]; then
505+
GO_VERSION="$version_cycle" "$ROOT"/scripts/install_tool.sh "$catalog_tool" uninstall || true
506+
elif [ "$catalog_tool" = "php" ]; then
507+
PHP_VERSION="$version_cycle" "$ROOT"/scripts/install_tool.sh "$catalog_tool" uninstall || true
508+
else
509+
"$ROOT"/scripts/install_tool.sh "$catalog_tool" uninstall || true
510+
fi
511+
else
512+
"$ROOT"/scripts/install_tool.sh "$catalog_tool" uninstall || true
513+
fi
476514

477515
# Re-audit to update snapshot
478516
CLI_AUDIT_JSON=1 CLI_AUDIT_COLLECT=1 CLI_AUDIT_MERGE=1 "$CLI" audit.py "$tool" >/dev/null 2>&1 || true

scripts/install_node.sh

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,39 @@ update_node() {
9292
}
9393

9494
uninstall_node() {
95-
# remove nvm-managed node
96-
if [ -d "$HOME/.nvm" ]; then rm -rf "$HOME/.nvm"; fi
97-
apt_remove_if_present nodejs npm || true
95+
if [ -n "${NODE_VERSION:-}" ]; then
96+
# Multi-version: only remove the specific version cycle, keep nvm and other versions
97+
ensure_nvm_loaded
98+
if have nvm; then
99+
local resolved
100+
resolved="$(nvm version "$NODE_VERSION" 2>/dev/null || true)"
101+
if [ -n "$resolved" ] && [ "$resolved" != "N/A" ]; then
102+
# Don't remove the default/active version without warning
103+
local current_default
104+
current_default="$(nvm version default 2>/dev/null || true)"
105+
if [ "$resolved" = "$current_default" ]; then
106+
echo "[node] Warning: node $NODE_VERSION is the current default, switching default first" >&2
107+
# Find another installed version to become default
108+
local other_ver
109+
other_ver="$(nvm ls --no-colors 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | grep -v "$resolved" | head -1 || true)"
110+
if [ -n "$other_ver" ]; then
111+
nvm alias default "$other_ver" || true
112+
nvm use "$other_ver" || true
113+
fi
114+
fi
115+
nvm uninstall "$resolved" || true
116+
echo "[node] Removed node $resolved (cycle $NODE_VERSION)" >&2
117+
else
118+
echo "[node] Node $NODE_VERSION not found in nvm" >&2
119+
fi
120+
fi
121+
# Also remove apt if it matches this major version
122+
apt_remove_if_present nodejs npm || true
123+
else
124+
# Full uninstall: remove everything
125+
if [ -d "$HOME/.nvm" ]; then rm -rf "$HOME/.nvm"; fi
126+
apt_remove_if_present nodejs npm || true
127+
fi
98128
}
99129

100130
reconcile_node() {

scripts/install_tool.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ if [ "$ACTION" = "uninstall" ]; then
5555

5656
# Detect remaining installations
5757
all_installs="$(detect_all_installations "$TOOL" "$binary_name" 2>/dev/null || true)"
58-
install_count="$(echo "$all_installs" | grep -c . || echo 0)"
58+
install_count="$(echo "$all_installs" | grep -c . || true)"
5959

6060
if [ "$install_count" -eq 0 ]; then
6161
echo "[$TOOL] Successfully removed"
@@ -86,7 +86,7 @@ if [ "$ACTION" = "uninstall" ]; then
8686
# Verify removal (ignore system entries in the check)
8787
remaining="$(detect_all_installations "$TOOL" "$binary_name" 2>/dev/null || true)"
8888
remaining_nonsystem="$(echo "$remaining" | grep -v "^system:" || true)"
89-
remaining_count="$(echo "$remaining_nonsystem" | grep -c . || echo 0)"
89+
remaining_count="$(echo "$remaining_nonsystem" | grep -c . || true)"
9090
if [ "$remaining_count" -eq 0 ]; then
9191
echo "[$TOOL] Successfully removed all installations"
9292
else

scripts/installers/npm_self_update.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ fi
2020

2121
BINARY_NAME="npm"
2222

23+
# Load nvm if available (npm is bundled with nvm-managed Node.js)
24+
ensure_nvm_loaded
25+
2326
# Get current version
2427
before="$(timeout 2 npm --version </dev/null 2>/dev/null || echo '<none>')"
2528

0 commit comments

Comments
 (0)