mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 19:09:43 +00:00
feat: Enhance file deletion safety with path protection, streamline update prompts, and remove automated Homebrew update checks.
This commit is contained in:
@@ -207,68 +207,6 @@ is_cache_valid() {
|
||||
[[ $cache_age -lt $ttl ]]
|
||||
}
|
||||
|
||||
check_homebrew_updates() {
|
||||
# Check whitelist
|
||||
if command -v is_whitelisted > /dev/null && is_whitelisted "check_brew_updates"; then return; fi
|
||||
if ! command -v brew > /dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
local cache_file="$CACHE_DIR/brew_updates"
|
||||
local formula_count=0
|
||||
local cask_count=0
|
||||
|
||||
if is_cache_valid "$cache_file"; then
|
||||
read -r formula_count cask_count < "$cache_file" 2> /dev/null || true
|
||||
formula_count=${formula_count:-0}
|
||||
cask_count=${cask_count:-0}
|
||||
else
|
||||
# Show spinner while checking
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Checking Homebrew..."
|
||||
fi
|
||||
|
||||
local outdated_list=""
|
||||
outdated_list=$(brew outdated --quiet 2> /dev/null || echo "")
|
||||
if [[ -n "$outdated_list" ]]; then
|
||||
formula_count=$(echo "$outdated_list" | wc -l | tr -d ' ')
|
||||
fi
|
||||
|
||||
local cask_list=""
|
||||
cask_list=$(brew outdated --cask --quiet 2> /dev/null || echo "")
|
||||
if [[ -n "$cask_list" ]]; then
|
||||
cask_count=$(echo "$cask_list" | wc -l | tr -d ' ')
|
||||
fi
|
||||
|
||||
echo "$formula_count $cask_count" > "$cache_file" 2> /dev/null || true
|
||||
|
||||
# Stop spinner before output
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
fi
|
||||
|
||||
local total_count=$((formula_count + cask_count))
|
||||
export BREW_FORMULA_OUTDATED_COUNT=$formula_count
|
||||
export BREW_CASK_OUTDATED_COUNT=$cask_count
|
||||
export BREW_OUTDATED_COUNT=$total_count
|
||||
|
||||
if [[ $total_count -gt 0 ]]; then
|
||||
local breakdown=""
|
||||
if [[ $formula_count -gt 0 && $cask_count -gt 0 ]]; then
|
||||
breakdown=" (${formula_count} formula, ${cask_count} cask)"
|
||||
elif [[ $formula_count -gt 0 ]]; then
|
||||
breakdown=" (${formula_count} formula)"
|
||||
elif [[ $cask_count -gt 0 ]]; then
|
||||
breakdown=" (${cask_count} cask)"
|
||||
fi
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Homebrew ${YELLOW}${total_count} updates${NC}${breakdown}"
|
||||
echo -e " ${GRAY}Run: ${GREEN}brew upgrade${NC} ${GRAY}and/or${NC} ${GREEN}brew upgrade --cask${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Homebrew Up to date"
|
||||
fi
|
||||
}
|
||||
|
||||
# Cache software update list to avoid calling softwareupdate twice
|
||||
SOFTWARE_UPDATE_LIST=""
|
||||
|
||||
@@ -300,13 +238,36 @@ check_macos_update() {
|
||||
local updates_available="false"
|
||||
if [[ $(get_software_updates) == "Updates Available" ]]; then
|
||||
updates_available="true"
|
||||
|
||||
# Verify with softwareupdate -l (short timeout) to reduce false positives
|
||||
local sw_output=""
|
||||
local sw_status=0
|
||||
local spinner_started=false
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Checking macOS updates..."
|
||||
spinner_started=true
|
||||
fi
|
||||
|
||||
if ! sw_output=$(run_with_timeout 5 softwareupdate -l 2> /dev/null); then
|
||||
sw_status=$?
|
||||
fi
|
||||
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
# If command failed, timed out, or returned empty, treat as no updates to avoid false positives
|
||||
if [[ $sw_status -ne 0 || -z "$sw_output" ]]; then
|
||||
updates_available="false"
|
||||
elif echo "$sw_output" | grep -q "No new software available"; then
|
||||
updates_available="false"
|
||||
fi
|
||||
fi
|
||||
|
||||
export MACOS_UPDATE_AVAILABLE="$updates_available"
|
||||
|
||||
if [[ "$updates_available" == "true" ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} macOS ${YELLOW}Update available${NC}"
|
||||
echo -e " ${GRAY}update available in final step${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} macOS Up to date"
|
||||
fi
|
||||
@@ -375,8 +336,6 @@ check_all_updates() {
|
||||
# Reset spinner flag for softwareupdate
|
||||
unset SOFTWAREUPDATE_SPINNER_SHOWN
|
||||
|
||||
check_homebrew_updates
|
||||
|
||||
# Preload software update data to avoid delays between subsequent checks
|
||||
# Only redirect stdout, keep stderr for spinner display
|
||||
get_software_updates > /dev/null
|
||||
@@ -601,11 +560,6 @@ check_swap_usage() {
|
||||
check_brew_health() {
|
||||
# Check whitelist
|
||||
if command -v is_whitelisted > /dev/null && is_whitelisted "check_brew_health"; then return; fi
|
||||
# Check Homebrew status (fast)
|
||||
if command -v brew > /dev/null 2>&1; then
|
||||
# Skip slow 'brew doctor' check by default
|
||||
echo -e " ${GREEN}✓${NC} Homebrew Installed"
|
||||
fi
|
||||
}
|
||||
|
||||
check_system_health() {
|
||||
@@ -615,5 +569,4 @@ check_system_health() {
|
||||
check_login_items
|
||||
check_cache_size
|
||||
# Time Machine check is optional; skip by default to avoid noise on systems without backups
|
||||
check_brew_health
|
||||
}
|
||||
|
||||
@@ -160,24 +160,21 @@ safe_find_delete() {
|
||||
|
||||
debug_log "Finding in $base_dir: $pattern (age: ${age_days}d, type: $type_filter)"
|
||||
|
||||
# Execute find with safety limits (maxdepth 5 covers most app cache structures)
|
||||
if [[ "$age_days" -eq 0 ]]; then
|
||||
# Delete all matching files without time restriction
|
||||
command find "$base_dir" \
|
||||
-maxdepth 5 \
|
||||
-name "$pattern" \
|
||||
-type "$type_filter" \
|
||||
-delete 2> /dev/null || true # Suppress expected errors when files are removed or protected by other processes
|
||||
else
|
||||
# Delete files older than age_days
|
||||
command find "$base_dir" \
|
||||
-maxdepth 5 \
|
||||
-name "$pattern" \
|
||||
-type "$type_filter" \
|
||||
-mtime "+$age_days" \
|
||||
-delete 2> /dev/null || true # Suppress expected errors when files are removed or protected by other processes
|
||||
local find_args=("-maxdepth" "5" "-name" "$pattern" "-type" "$type_filter")
|
||||
if [[ "$age_days" -gt 0 ]]; then
|
||||
find_args+=("-mtime" "+$age_days")
|
||||
fi
|
||||
|
||||
# Iterate results to respect should_protect_path when available
|
||||
while IFS= read -r -d '' match; do
|
||||
if command -v should_protect_path > /dev/null 2>&1; then
|
||||
if should_protect_path "$match"; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
safe_remove "$match" true || true
|
||||
done < <(command find "$base_dir" "${find_args[@]}" -print0 2> /dev/null || true)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -207,22 +204,21 @@ safe_sudo_find_delete() {
|
||||
|
||||
debug_log "Finding (sudo) in $base_dir: $pattern (age: ${age_days}d, type: $type_filter)"
|
||||
|
||||
# Execute find with sudo
|
||||
if [[ "$age_days" -eq 0 ]]; then
|
||||
sudo find "$base_dir" \
|
||||
-maxdepth 5 \
|
||||
-name "$pattern" \
|
||||
-type "$type_filter" \
|
||||
-delete 2> /dev/null || true # Ignore transient errors for system files that might be in use or protected
|
||||
else
|
||||
sudo find "$base_dir" \
|
||||
-maxdepth 5 \
|
||||
-name "$pattern" \
|
||||
-type "$type_filter" \
|
||||
-mtime "+$age_days" \
|
||||
-delete 2> /dev/null || true # Ignore transient errors for system files that might be in use or protected
|
||||
local find_args=("-maxdepth" "5" "-name" "$pattern" "-type" "$type_filter")
|
||||
if [[ "$age_days" -gt 0 ]]; then
|
||||
find_args+=("-mtime" "+$age_days")
|
||||
fi
|
||||
|
||||
# Iterate results to respect should_protect_path when available
|
||||
while IFS= read -r -d '' match; do
|
||||
if command -v should_protect_path > /dev/null 2>&1; then
|
||||
if should_protect_path "$match"; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
safe_sudo_remove "$match" || true
|
||||
done < <(sudo find "$base_dir" "${find_args[@]}" -print0 2> /dev/null || true)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -76,9 +76,9 @@ ask_for_updates() {
|
||||
echo -e "$item"
|
||||
done
|
||||
echo ""
|
||||
|
||||
# If Mole has updates, offer to update it
|
||||
# If only Mole is relevant for automation, prompt just for Mole
|
||||
if [[ "${MOLE_UPDATE_AVAILABLE:-}" == "true" ]]; then
|
||||
echo ""
|
||||
echo -ne "${YELLOW}Update Mole now?${NC} ${GRAY}Enter confirm / ESC cancel${NC}: "
|
||||
|
||||
local key
|
||||
@@ -92,55 +92,33 @@ ask_for_updates() {
|
||||
echo "yes"
|
||||
echo ""
|
||||
return 0
|
||||
else
|
||||
echo "skip"
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# For other updates, just show instructions
|
||||
# (Mole update check above handles the return 0 case, so we only get here if no Mole update)
|
||||
if [[ "${BREW_OUTDATED_COUNT:-0}" -gt 0 ]]; then
|
||||
echo -e "${YELLOW}Tip:${NC} Run ${GREEN}brew upgrade${NC} to update Homebrew packages"
|
||||
fi
|
||||
if [[ "${APPSTORE_UPDATE_COUNT:-0}" -gt 0 ]]; then
|
||||
echo -e "${YELLOW}Tip:${NC} Open ${BLUE}App Store${NC} to update apps"
|
||||
fi
|
||||
if [[ "${MACOS_UPDATE_AVAILABLE:-}" == "true" ]]; then
|
||||
echo -e "${YELLOW}Tip:${NC} Open ${BLUE}System Settings${NC} to update macOS"
|
||||
fi
|
||||
echo ""
|
||||
echo -e "${YELLOW}Tip:${NC} Homebrew: brew upgrade / brew upgrade --cask"
|
||||
echo -e "${YELLOW}Tip:${NC} App Store: open App Store → Updates"
|
||||
echo -e "${YELLOW}Tip:${NC} macOS: System Settings → General → Software Update"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Perform all pending updates
|
||||
# Returns: 0 if all succeeded, 1 if some failed
|
||||
perform_updates() {
|
||||
# Only handle Mole updates here
|
||||
# Other updates are now informational-only in ask_for_updates
|
||||
|
||||
# Only handle Mole updates here; Homebrew/App Store/macOS are manual (tips shown in ask_for_updates)
|
||||
local updated_count=0
|
||||
local total_count=0
|
||||
|
||||
# Update Mole
|
||||
if [[ -n "${MOLE_UPDATE_AVAILABLE:-}" && "${MOLE_UPDATE_AVAILABLE}" == "true" ]]; then
|
||||
echo -e "${BLUE}Updating Mole...${NC}"
|
||||
# Try to find mole executable
|
||||
local mole_bin="${SCRIPT_DIR}/../../mole"
|
||||
[[ ! -f "$mole_bin" ]] && mole_bin=$(command -v mole 2> /dev/null || echo "")
|
||||
|
||||
if [[ -x "$mole_bin" ]]; then
|
||||
# We use exec here or just run it?
|
||||
# If we run 'mole update', it replaces the script.
|
||||
# Since this function is part of a sourced script, replacing the file on disk is risky while running.
|
||||
# However, 'mole update' script usually handles this by downloading to a temp file and moving it.
|
||||
# But the shell might not like the file changing under it.
|
||||
# The original code ran it this way, so we assume it's safe enough or handled by mole update implementation.
|
||||
|
||||
if "$mole_bin" update 2>&1 | grep -qE "(Updated|latest version)"; then
|
||||
echo -e "${GREEN}✓${NC} Mole updated"
|
||||
reset_mole_cache
|
||||
updated_count=1
|
||||
((updated_count++))
|
||||
else
|
||||
echo -e "${RED}✗${NC} Mole update failed"
|
||||
fi
|
||||
@@ -148,11 +126,17 @@ perform_updates() {
|
||||
echo -e "${RED}✗${NC} Mole executable not found"
|
||||
fi
|
||||
echo ""
|
||||
total_count=1
|
||||
fi
|
||||
|
||||
if [[ $updated_count -gt 0 ]]; then
|
||||
if [[ $total_count -eq 0 ]]; then
|
||||
echo -e "${GRAY}No updates to perform${NC}"
|
||||
return 0
|
||||
elif [[ $updated_count -eq $total_count ]]; then
|
||||
echo -e "${GREEN}All updates completed (${updated_count}/${total_count})${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}Update failed (${updated_count}/${total_count})${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
2
mole
2
mole
@@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/lib/core/common.sh"
|
||||
|
||||
# Version info
|
||||
VERSION="1.13.12"
|
||||
VERSION="1.13.13"
|
||||
MOLE_TAGLINE="Deep clean and optimize your Mac."
|
||||
|
||||
# Check TouchID configuration
|
||||
|
||||
@@ -156,39 +156,6 @@ EOF
|
||||
[[ "$output" == *"Homebrew cleanup"* ]]
|
||||
}
|
||||
|
||||
@test "check_homebrew_updates reports counts and uses cache" {
|
||||
run bash --noprofile --norc <<'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/check/all.sh"
|
||||
|
||||
brew() {
|
||||
if [[ "$1" == "outdated" && "$2" == "--quiet" ]]; then
|
||||
echo "pkg1"
|
||||
echo "pkg2"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$1" == "outdated" && "$2" == "--cask" ]]; then
|
||||
echo "cask1"
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
start_inline_spinner(){ :; }
|
||||
stop_inline_spinner(){ :; }
|
||||
|
||||
check_homebrew_updates
|
||||
|
||||
# second call should read cache (no spinner)
|
||||
check_homebrew_updates
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"Homebrew"* ]]
|
||||
[[ "$output" == *"2 formula"* ]]
|
||||
}
|
||||
|
||||
@test "check_appstore_updates is skipped for performance" {
|
||||
run bash --noprofile --norc <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
Reference in New Issue
Block a user