diff --git a/bin/clean.sh b/bin/clean.sh index cc2f1d9..df2a658 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -917,8 +917,9 @@ perform_cleanup() { brew_tmp_file=$(create_temp_file) # Run brew cleanup in background with manual timeout - # Clean old versions with --prune=all (default 2 minutes, configurable via MO_BREW_TIMEOUT) - (brew cleanup --prune=all > "$brew_tmp_file" 2>&1) & + # Clean old versions only (default 2 minutes, configurable via MO_BREW_TIMEOUT) + # Uses default 120-day threshold to avoid breaking zsh completions + (brew cleanup > "$brew_tmp_file" 2>&1) & local brew_pid=$! local elapsed=0 @@ -1002,17 +1003,38 @@ perform_cleanup() { MOLE_SPINNER_PREFIX=" " start_inline_spinner "Searching Next.js caches..." fi - while IFS= read -r next_dir; do - if [[ -d "$next_dir/cache" ]]; then - safe_clean "$next_dir/cache"/* "Next.js build cache" || true - fi - done < <( - find "$HOME" -type d -name ".next" -maxdepth 4 \ + + # Use timeout to prevent hanging on problematic directories + local nextjs_tmp_file + nextjs_tmp_file=$(create_temp_file) + ( + find "$HOME" -P -mount -type d -name ".next" -maxdepth 3 \ -not -path "*/Library/*" \ -not -path "*/.Trash/*" \ -not -path "*/node_modules/*" \ + -not -path "*/.*" \ 2> /dev/null || true - ) + ) > "$nextjs_tmp_file" 2>&1 & + local find_pid=$! + local find_timeout=10 + local elapsed=0 + + while kill -0 $find_pid 2>/dev/null && [[ $elapsed -lt $find_timeout ]]; do + sleep 1 + ((elapsed++)) + done + + if kill -0 $find_pid 2>/dev/null; then + kill -TERM $find_pid 2>/dev/null || true + wait $find_pid 2>/dev/null || true + else + wait $find_pid 2>/dev/null || true + fi + + while IFS= read -r next_dir; do + [[ -d "$next_dir/cache" ]] && safe_clean "$next_dir/cache"/* "Next.js build cache" || true + done < "$nextjs_tmp_file" + if [[ -t 1 ]]; then stop_inline_spinner fi @@ -1022,15 +1044,38 @@ perform_cleanup() { MOLE_SPINNER_PREFIX=" " start_inline_spinner "Searching Python caches..." fi - while IFS= read -r pycache; do - safe_clean "$pycache"/* "Python bytecode cache" || true - done < <( - find "$HOME" -type d -name "__pycache__" -maxdepth 5 \ + + # Use timeout to prevent hanging on problematic directories + local pycache_tmp_file + pycache_tmp_file=$(create_temp_file) + ( + find "$HOME" -P -mount -type d -name "__pycache__" -maxdepth 3 \ -not -path "*/Library/*" \ -not -path "*/.Trash/*" \ -not -path "*/node_modules/*" \ + -not -path "*/.*" \ 2> /dev/null || true - ) + ) > "$pycache_tmp_file" 2>&1 & + local find_pid=$! + local find_timeout=10 + local elapsed=0 + + while kill -0 $find_pid 2>/dev/null && [[ $elapsed -lt $find_timeout ]]; do + sleep 1 + ((elapsed++)) + done + + if kill -0 $find_pid 2>/dev/null; then + kill -TERM $find_pid 2>/dev/null || true + wait $find_pid 2>/dev/null || true + else + wait $find_pid 2>/dev/null || true + fi + + while IFS= read -r pycache; do + [[ -d "$pycache" ]] && safe_clean "$pycache"/* "Python bytecode cache" || true + done < "$pycache_tmp_file" + if [[ -t 1 ]]; then stop_inline_spinner fi diff --git a/lib/common.sh b/lib/common.sh index 1aba377..b3eb5fb 100755 --- a/lib/common.sh +++ b/lib/common.sh @@ -575,11 +575,42 @@ update_via_homebrew() { else echo "Updating Homebrew..." fi - # Filter out common noise but show important info - brew update 2>&1 | grep -Ev "^(==>|Already up-to-date)" || true + + # Run brew update with timeout to prevent hanging + # Use background process to allow interruption + local brew_update_timeout="${MO_BREW_UPDATE_TIMEOUT:-300}" + local brew_tmp_file + brew_tmp_file=$(mktemp -t mole-brew-update 2>/dev/null || echo "/tmp/mole-brew-update.$$") + + (brew update > "$brew_tmp_file" 2>&1) & + local brew_pid=$! + local elapsed=0 + + # Wait for completion or timeout + while kill -0 $brew_pid 2>/dev/null; do + if [[ $elapsed -ge $brew_update_timeout ]]; then + kill -TERM $brew_pid 2>/dev/null || true + wait $brew_pid 2>/dev/null || true + if [[ -t 1 ]]; then stop_inline_spinner; fi + rm -f "$brew_tmp_file" + log_error "Homebrew update timed out (${brew_update_timeout}s)" + return 1 + fi + sleep 1 + ((elapsed++)) + done + + wait $brew_pid 2>/dev/null || { + if [[ -t 1 ]]; then stop_inline_spinner; fi + rm -f "$brew_tmp_file" + log_error "Homebrew update failed" + return 1 + } + if [[ -t 1 ]]; then stop_inline_spinner fi + rm -f "$brew_tmp_file" if [[ -t 1 ]]; then start_inline_spinner "Upgrading Mole..." diff --git a/mole b/mole index fecdd22..ccb7fbe 100755 --- a/mole +++ b/mole @@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/lib/common.sh" # Version info -VERSION="1.10.5" +VERSION="1.10.6" MOLE_TAGLINE="can dig deep to clean your Mac." # Check if Touch ID is already configured @@ -186,6 +186,10 @@ show_help() { # Simple update function update_mole() { + # Set up cleanup trap for update process + local update_interrupted=false + trap 'update_interrupted=true; echo ""; log_error "Update interrupted by user"; exit 130' INT TERM + # Check if installed via Homebrew if is_homebrew_install; then update_via_homebrew "$VERSION" @@ -198,6 +202,8 @@ update_mole() { if [[ -z "$latest" ]]; then log_error "Unable to check for updates. Check network connection." + echo -e "${YELLOW}Tip:${NC} Check if you can access GitHub (https://github.com)" + echo -e "${YELLOW}Tip:${NC} Try again with: ${GRAY}mo update${NC}" exit 1 fi @@ -222,25 +228,40 @@ update_mole() { exit 1 } - # Download installer with progress + # Download installer with progress and better error handling + local download_error="" if command -v curl > /dev/null 2>&1; then - if ! curl -fsSL --connect-timeout 10 --max-time 60 "$installer_url" -o "$tmp_installer" 2>&1; then + download_error=$(curl -fsSL --connect-timeout 10 --max-time 60 "$installer_url" -o "$tmp_installer" 2>&1) || { + local curl_exit=$? if [[ -t 1 ]]; then stop_inline_spinner; fi rm -f "$tmp_installer" - log_error "Update failed. Check network connection." + log_error "Update failed (curl error: $curl_exit)" + + # Provide helpful error messages based on curl exit codes + case $curl_exit in + 6) echo -e "${YELLOW}Tip:${NC} Could not resolve host. Check DNS or network connection." ;; + 7) echo -e "${YELLOW}Tip:${NC} Failed to connect. Check network or proxy settings." ;; + 22) echo -e "${YELLOW}Tip:${NC} HTTP 404 Not Found. The installer may have moved." ;; + 28) echo -e "${YELLOW}Tip:${NC} Connection timed out. Try again or check firewall." ;; + *) echo -e "${YELLOW}Tip:${NC} Check network connection and try again." ;; + esac + echo -e "${YELLOW}Tip:${NC} URL: $installer_url" exit 1 - fi + } elif command -v wget > /dev/null 2>&1; then - if ! wget --timeout=10 --tries=3 -qO "$tmp_installer" "$installer_url" 2>&1; then + download_error=$(wget --timeout=10 --tries=3 -qO "$tmp_installer" "$installer_url" 2>&1) || { if [[ -t 1 ]]; then stop_inline_spinner; fi rm -f "$tmp_installer" - log_error "Update failed. Check network connection." + log_error "Update failed (wget error)" + echo -e "${YELLOW}Tip:${NC} Check network connection and try again." + echo -e "${YELLOW}Tip:${NC} URL: $installer_url" exit 1 - fi + } else if [[ -t 1 ]]; then stop_inline_spinner; fi rm -f "$tmp_installer" log_error "curl or wget required" + echo -e "${YELLOW}Tip:${NC} Install curl with: ${GRAY}brew install curl${NC}" exit 1 fi