From ab83bd1ed91e0305339068414529ce77b97893c4 Mon Sep 17 00:00:00 2001 From: Michael Aristarco <75532968+mickyyy68@users.noreply.github.com> Date: Mon, 16 Feb 2026 08:40:13 +0100 Subject: [PATCH] fix: deep clean npm residual caches (#457) Fixes #454 --- lib/clean/dev.sh | 25 +++++++++++ tests/clean_dev_caches.bats | 83 +++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/lib/clean/dev.sh b/lib/clean/dev.sh index 3ea2a68..8ea47d6 100644 --- a/lib/clean/dev.sh +++ b/lib/clean/dev.sh @@ -17,10 +17,35 @@ clean_tool_cache() { } # npm/pnpm/yarn/bun caches. clean_dev_npm() { + local npm_default_cache="$HOME/.npm" + local npm_cache_path="$npm_default_cache" + if command -v npm > /dev/null 2>&1; then clean_tool_cache "npm cache" npm cache clean --force + + start_section_spinner "Checking npm cache path..." + npm_cache_path=$(run_with_timeout 2 npm config get cache 2> /dev/null) || npm_cache_path="" + stop_section_spinner + + if [[ -z "$npm_cache_path" || "$npm_cache_path" != /* ]]; then + npm_cache_path="$npm_default_cache" + fi + note_activity fi + + safe_clean "$npm_default_cache"/_cacache/* "npm cache directory" + safe_clean "$npm_default_cache"/_npx/* "npm npx cache" + safe_clean "$npm_default_cache"/_logs/* "npm logs" + safe_clean "$npm_default_cache"/_prebuilds/* "npm prebuilds" + + if [[ "$npm_cache_path" != "$npm_default_cache" ]]; then + safe_clean "$npm_cache_path"/_cacache/* "npm cache directory (custom path)" + safe_clean "$npm_cache_path"/_npx/* "npm npx cache (custom path)" + safe_clean "$npm_cache_path"/_logs/* "npm logs (custom path)" + safe_clean "$npm_cache_path"/_prebuilds/* "npm prebuilds (custom path)" + fi + # Clean pnpm store cache local pnpm_default_store=~/Library/pnpm/store # Check if pnpm is actually usable (not just Corepack shim) diff --git a/tests/clean_dev_caches.bats b/tests/clean_dev_caches.bats index c15ede5..bbe3d6d 100644 --- a/tests/clean_dev_caches.bats +++ b/tests/clean_dev_caches.bats @@ -50,6 +50,89 @@ EOF [[ "$output" == *"Orphaned pnpm store"* ]] } +@test "clean_dev_npm cleans default npm residual directories" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/clean/dev.sh" +start_section_spinner() { :; } +stop_section_spinner() { :; } +clean_tool_cache() { :; } +safe_clean() { echo "$2|$1"; } +note_activity() { :; } +run_with_timeout() { shift; "$@"; } +npm() { + if [[ "$1" == "config" && "$2" == "get" && "$3" == "cache" ]]; then + echo "$HOME/.npm" + return 0 + fi + return 0 +} +clean_dev_npm +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"npm cache directory|$HOME/.npm/_cacache/*"* ]] + [[ "$output" == *"npm npx cache|$HOME/.npm/_npx/*"* ]] + [[ "$output" == *"npm logs|$HOME/.npm/_logs/*"* ]] + [[ "$output" == *"npm prebuilds|$HOME/.npm/_prebuilds/*"* ]] +} + +@test "clean_dev_npm cleans custom npm cache path when detected" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/clean/dev.sh" +start_section_spinner() { :; } +stop_section_spinner() { :; } +clean_tool_cache() { :; } +safe_clean() { echo "$2|$1"; } +note_activity() { :; } +run_with_timeout() { shift; "$@"; } +npm() { + if [[ "$1" == "config" && "$2" == "get" && "$3" == "cache" ]]; then + echo "/tmp/mole-custom-npm-cache" + return 0 + fi + return 0 +} +clean_dev_npm +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"npm cache directory|$HOME/.npm/_cacache/*"* ]] + [[ "$output" == *"npm cache directory (custom path)|/tmp/mole-custom-npm-cache/_cacache/*"* ]] + [[ "$output" == *"npm npx cache (custom path)|/tmp/mole-custom-npm-cache/_npx/*"* ]] + [[ "$output" == *"npm logs (custom path)|/tmp/mole-custom-npm-cache/_logs/*"* ]] + [[ "$output" == *"npm prebuilds (custom path)|/tmp/mole-custom-npm-cache/_prebuilds/*"* ]] +} + +@test "clean_dev_npm falls back to default cache when npm path is invalid" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/clean/dev.sh" +start_section_spinner() { :; } +stop_section_spinner() { :; } +clean_tool_cache() { :; } +safe_clean() { echo "$2|$1"; } +note_activity() { :; } +run_with_timeout() { shift; "$@"; } +npm() { + if [[ "$1" == "config" && "$2" == "get" && "$3" == "cache" ]]; then + echo "relative-cache" + return 0 + fi + return 0 +} +clean_dev_npm +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"npm cache directory|$HOME/.npm/_cacache/*"* ]] + [[ "$output" != *"(custom path)"* ]] +} + @test "clean_dev_docker skips when daemon not running" { run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MO_DEBUG=1 DRY_RUN=false bash --noprofile --norc <<'EOF' set -euo pipefail