mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 17:55:08 +00:00
Improve update checks and cleanup UX, add timeout regressions
This commit is contained in:
104
lib/check/all.sh
104
lib/check/all.sh
@@ -243,6 +243,99 @@ get_software_updates() {
|
||||
fi
|
||||
}
|
||||
|
||||
check_homebrew_updates() {
|
||||
# Check whitelist
|
||||
if command -v is_whitelisted > /dev/null && is_whitelisted "check_homebrew_updates"; then return; fi
|
||||
|
||||
export BREW_OUTDATED_COUNT=0
|
||||
export BREW_FORMULA_OUTDATED_COUNT=0
|
||||
export BREW_CASK_OUTDATED_COUNT=0
|
||||
|
||||
if ! command -v brew > /dev/null 2>&1; then
|
||||
printf " ${GRAY}${ICON_EMPTY}${NC} %-12s %s\n" "Homebrew" "Not installed"
|
||||
return
|
||||
fi
|
||||
|
||||
local cache_file="$CACHE_DIR/brew_updates"
|
||||
local formula_count=0
|
||||
local cask_count=0
|
||||
local total_count=0
|
||||
local use_cache=false
|
||||
|
||||
if is_cache_valid "$cache_file"; then
|
||||
local cached_formula=""
|
||||
local cached_cask=""
|
||||
IFS=' ' read -r cached_formula cached_cask < "$cache_file" || true
|
||||
if [[ "$cached_formula" =~ ^[0-9]+$ && "$cached_cask" =~ ^[0-9]+$ ]]; then
|
||||
formula_count="$cached_formula"
|
||||
cask_count="$cached_cask"
|
||||
use_cache=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$use_cache" == "false" ]]; then
|
||||
local formula_outdated=""
|
||||
local cask_outdated=""
|
||||
local formula_status=0
|
||||
local cask_status=0
|
||||
local spinner_started=false
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Checking Homebrew updates..."
|
||||
spinner_started=true
|
||||
fi
|
||||
|
||||
if formula_outdated=$(run_with_timeout 8 brew outdated --formula --quiet 2> /dev/null); then
|
||||
:
|
||||
else
|
||||
formula_status=$?
|
||||
fi
|
||||
|
||||
if cask_outdated=$(run_with_timeout 8 brew outdated --cask --quiet 2> /dev/null); then
|
||||
:
|
||||
else
|
||||
cask_status=$?
|
||||
fi
|
||||
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
if [[ $formula_status -eq 0 || $cask_status -eq 0 ]]; then
|
||||
formula_count=$(printf '%s\n' "$formula_outdated" | awk 'NF {count++} END {print count + 0}')
|
||||
cask_count=$(printf '%s\n' "$cask_outdated" | awk 'NF {count++} END {print count + 0}')
|
||||
ensure_user_file "$cache_file"
|
||||
printf '%s %s\n' "$formula_count" "$cask_count" > "$cache_file" 2> /dev/null || true
|
||||
elif [[ $formula_status -eq 124 || $cask_status -eq 124 ]]; then
|
||||
printf " ${GRAY}${ICON_WARNING}${NC} %-12s ${YELLOW}%s${NC}\n" "Homebrew" "Check timed out"
|
||||
return
|
||||
else
|
||||
printf " ${GRAY}${ICON_WARNING}${NC} %-12s ${YELLOW}%s${NC}\n" "Homebrew" "Check failed"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
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 detail=""
|
||||
if [[ $formula_count -gt 0 ]]; then
|
||||
detail="${formula_count} formula"
|
||||
fi
|
||||
if [[ $cask_count -gt 0 ]]; then
|
||||
[[ -n "$detail" ]] && detail="${detail}, "
|
||||
detail="${detail}${cask_count} cask"
|
||||
fi
|
||||
[[ -z "$detail" ]] && detail="${total_count} updates"
|
||||
printf " ${GRAY}%s${NC} %-12s ${YELLOW}%s${NC}\n" "$ICON_WARNING" "Homebrew" "${detail} available"
|
||||
else
|
||||
printf " ${GREEN}✓${NC} %-12s %s\n" "Homebrew" "Up to date"
|
||||
fi
|
||||
}
|
||||
|
||||
check_appstore_updates() {
|
||||
# Skipped for speed optimization - consolidated into check_macos_update
|
||||
# We can't easily distinguish app store vs macos updates without the slow softwareupdate -l call
|
||||
@@ -298,9 +391,9 @@ check_macos_update() {
|
||||
export MACOS_UPDATE_AVAILABLE="$updates_available"
|
||||
|
||||
if [[ "$updates_available" == "true" ]]; then
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} macOS ${YELLOW}Update available${NC}"
|
||||
printf " ${GRAY}%s${NC} %-12s ${YELLOW}%s${NC}\n" "$ICON_WARNING" "macOS" "Update available"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} macOS System up to date"
|
||||
printf " ${GREEN}✓${NC} %-12s %s\n" "macOS" "System up to date"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -366,12 +459,12 @@ check_mole_update() {
|
||||
# Compare versions
|
||||
if [[ "$(printf '%s\n' "$current_version" "$latest_version" | sort -V | head -1)" == "$current_version" ]]; then
|
||||
export MOLE_UPDATE_AVAILABLE="true"
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Mole ${YELLOW}${latest_version} available${NC}, running ${current_version}"
|
||||
printf " ${GRAY}%s${NC} %-12s ${YELLOW}%s${NC}, running %s\n" "$ICON_WARNING" "Mole" "${latest_version} available" "${current_version}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Mole Latest version ${current_version}"
|
||||
printf " ${GREEN}✓${NC} %-12s %s\n" "Mole" "Latest version ${current_version}"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Mole Latest version ${current_version}"
|
||||
printf " ${GREEN}✓${NC} %-12s %s\n" "Mole" "Latest version ${current_version}"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -384,6 +477,7 @@ check_all_updates() {
|
||||
get_software_updates > /dev/null
|
||||
|
||||
echo -e "${BLUE}${ICON_ARROW}${NC} System Updates"
|
||||
check_homebrew_updates
|
||||
check_appstore_updates
|
||||
check_macos_update
|
||||
check_mole_update
|
||||
|
||||
202
lib/clean/dev.sh
202
lib/clean/dev.sh
@@ -645,127 +645,129 @@ clean_dev_mobile() {
|
||||
local unavailable_udid=""
|
||||
|
||||
# Check if simctl is accessible and working
|
||||
local simctl_available=true
|
||||
if ! xcrun simctl list devices > /dev/null 2>&1; then
|
||||
debug_log "simctl not accessible or CoreSimulator service not running"
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode unavailable simulators · simctl not available"
|
||||
note_activity
|
||||
return 0
|
||||
simctl_available=false
|
||||
fi
|
||||
|
||||
unavailable_before=$(xcrun simctl list devices unavailable 2> /dev/null | command awk '/\(unavailable/ { count++ } END { print count+0 }' || echo "0")
|
||||
[[ "$unavailable_before" =~ ^[0-9]+$ ]] || unavailable_before=0
|
||||
while IFS= read -r unavailable_udid; do
|
||||
[[ -n "$unavailable_udid" ]] && unavailable_udids+=("$unavailable_udid")
|
||||
done < <(
|
||||
xcrun simctl list devices unavailable 2> /dev/null |
|
||||
command sed -nE 's/.*\(([0-9A-Fa-f-]{36})\).*\(unavailable.*/\1/p' || true
|
||||
)
|
||||
if [[ ${#unavailable_udids[@]} -gt 0 ]]; then
|
||||
local udid
|
||||
for udid in "${unavailable_udids[@]}"; do
|
||||
local simulator_device_path="$HOME/Library/Developer/CoreSimulator/Devices/$udid"
|
||||
if [[ -d "$simulator_device_path" ]]; then
|
||||
unavailable_size_kb=$((unavailable_size_kb + $(get_path_size_kb "$simulator_device_path")))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
unavailable_size_human=$(bytes_to_human "$((unavailable_size_kb * 1024))")
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
if ((unavailable_before > 0)); then
|
||||
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Xcode unavailable simulators · would clean ${unavailable_before}, ${unavailable_size_human}"
|
||||
else
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · already clean"
|
||||
fi
|
||||
else
|
||||
# Skip if no unavailable simulators
|
||||
if ((unavailable_before == 0)); then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · already clean"
|
||||
note_activity
|
||||
return 0
|
||||
if [[ "$simctl_available" == "true" ]]; then
|
||||
unavailable_before=$(xcrun simctl list devices unavailable 2> /dev/null | command awk '/\(unavailable/ { count++ } END { print count+0 }' || echo "0")
|
||||
[[ "$unavailable_before" =~ ^[0-9]+$ ]] || unavailable_before=0
|
||||
while IFS= read -r unavailable_udid; do
|
||||
[[ -n "$unavailable_udid" ]] && unavailable_udids+=("$unavailable_udid")
|
||||
done < <(
|
||||
xcrun simctl list devices unavailable 2> /dev/null |
|
||||
command sed -nE 's/.*\(([0-9A-Fa-f-]{36})\).*\(unavailable.*/\1/p' || true
|
||||
)
|
||||
if [[ ${#unavailable_udids[@]} -gt 0 ]]; then
|
||||
local udid
|
||||
for udid in "${unavailable_udids[@]}"; do
|
||||
local simulator_device_path="$HOME/Library/Developer/CoreSimulator/Devices/$udid"
|
||||
if [[ -d "$simulator_device_path" ]]; then
|
||||
unavailable_size_kb=$((unavailable_size_kb + $(get_path_size_kb "$simulator_device_path")))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
unavailable_size_human=$(bytes_to_human "$((unavailable_size_kb * 1024))")
|
||||
|
||||
start_section_spinner "Checking unavailable simulators..."
|
||||
|
||||
# Capture error output for diagnostics
|
||||
local delete_output
|
||||
local delete_exit_code=0
|
||||
delete_output=$(xcrun simctl delete unavailable 2>&1) || delete_exit_code=$?
|
||||
|
||||
if [[ $delete_exit_code -eq 0 ]]; then
|
||||
stop_section_spinner
|
||||
unavailable_after=$(xcrun simctl list devices unavailable 2> /dev/null | command awk '/\(unavailable/ { count++ } END { print count+0 }' || echo "0")
|
||||
[[ "$unavailable_after" =~ ^[0-9]+$ ]] || unavailable_after=0
|
||||
|
||||
removed_unavailable=$((unavailable_before - unavailable_after))
|
||||
if ((removed_unavailable < 0)); then
|
||||
removed_unavailable=0
|
||||
fi
|
||||
|
||||
if ((removed_unavailable > 0)); then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${removed_unavailable}, ${unavailable_size_human}"
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
if ((unavailable_before > 0)); then
|
||||
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Xcode unavailable simulators · would clean ${unavailable_before}, ${unavailable_size_human}"
|
||||
else
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · cleanup completed, ${unavailable_size_human}"
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · already clean"
|
||||
fi
|
||||
else
|
||||
stop_section_spinner
|
||||
# Skip if no unavailable simulators
|
||||
if ((unavailable_before == 0)); then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · already clean"
|
||||
note_activity
|
||||
else
|
||||
start_section_spinner "Checking unavailable simulators..."
|
||||
|
||||
# Analyze error and provide helpful message
|
||||
local error_hint=""
|
||||
if echo "$delete_output" | grep -qi "permission denied"; then
|
||||
error_hint=" (permission denied)"
|
||||
elif echo "$delete_output" | grep -qi "in use\|busy"; then
|
||||
error_hint=" (device in use)"
|
||||
elif echo "$delete_output" | grep -qi "unable to boot\|failed to boot"; then
|
||||
error_hint=" (boot failure)"
|
||||
elif echo "$delete_output" | grep -qi "service"; then
|
||||
error_hint=" (CoreSimulator service issue)"
|
||||
fi
|
||||
# Capture error output for diagnostics
|
||||
local delete_output
|
||||
local delete_exit_code=0
|
||||
delete_output=$(xcrun simctl delete unavailable 2>&1) || delete_exit_code=$?
|
||||
|
||||
# Try fallback: manual deletion of unavailable device directories
|
||||
if [[ ${#unavailable_udids[@]} -gt 0 ]]; then
|
||||
debug_log "Attempting fallback: manual deletion of unavailable simulators"
|
||||
local manually_removed=0
|
||||
local manual_failed=0
|
||||
if [[ $delete_exit_code -eq 0 ]]; then
|
||||
stop_section_spinner
|
||||
unavailable_after=$(xcrun simctl list devices unavailable 2> /dev/null | command awk '/\(unavailable/ { count++ } END { print count+0 }' || echo "0")
|
||||
[[ "$unavailable_after" =~ ^[0-9]+$ ]] || unavailable_after=0
|
||||
|
||||
for udid in "${unavailable_udids[@]}"; do
|
||||
# Validate UUID format (36 chars: 8-4-4-4-12 hex pattern)
|
||||
if [[ ! "$udid" =~ ^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$ ]]; then
|
||||
debug_log "Invalid UUID format, skipping: $udid"
|
||||
((manual_failed++)) || true
|
||||
continue
|
||||
removed_unavailable=$((unavailable_before - unavailable_after))
|
||||
if ((removed_unavailable < 0)); then
|
||||
removed_unavailable=0
|
||||
fi
|
||||
|
||||
local device_path="$HOME/Library/Developer/CoreSimulator/Devices/$udid"
|
||||
if [[ -d "$device_path" ]]; then
|
||||
# Use safe_remove for validated simulator device directory
|
||||
if safe_remove "$device_path" true; then
|
||||
((manually_removed++)) || true
|
||||
debug_log "Manually removed simulator: $udid"
|
||||
else
|
||||
((manual_failed++)) || true
|
||||
debug_log "Failed to manually remove simulator: $udid"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if ((manually_removed > 0)); then
|
||||
if ((manual_failed == 0)); then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${manually_removed} (fallback), ${unavailable_size_human}"
|
||||
if ((removed_unavailable > 0)); then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${removed_unavailable}, ${unavailable_size_human}"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Xcode unavailable simulators · partially cleaned ${manually_removed}/${#unavailable_udids[@]}, ${unavailable_size_human}"
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · cleanup completed, ${unavailable_size_human}"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode unavailable simulators cleanup failed${error_hint}"
|
||||
debug_log "simctl delete error: $delete_output"
|
||||
stop_section_spinner
|
||||
|
||||
# Analyze error and provide helpful message
|
||||
local error_hint=""
|
||||
if echo "$delete_output" | grep -qi "permission denied"; then
|
||||
error_hint=" (permission denied)"
|
||||
elif echo "$delete_output" | grep -qi "in use\|busy"; then
|
||||
error_hint=" (device in use)"
|
||||
elif echo "$delete_output" | grep -qi "unable to boot\|failed to boot"; then
|
||||
error_hint=" (boot failure)"
|
||||
elif echo "$delete_output" | grep -qi "service"; then
|
||||
error_hint=" (CoreSimulator service issue)"
|
||||
fi
|
||||
|
||||
# Try fallback: manual deletion of unavailable device directories
|
||||
if [[ ${#unavailable_udids[@]} -gt 0 ]]; then
|
||||
debug_log "Attempting fallback: manual deletion of unavailable simulators"
|
||||
local manually_removed=0
|
||||
local manual_failed=0
|
||||
|
||||
for udid in "${unavailable_udids[@]}"; do
|
||||
# Validate UUID format (36 chars: 8-4-4-4-12 hex pattern)
|
||||
if [[ ! "$udid" =~ ^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$ ]]; then
|
||||
debug_log "Invalid UUID format, skipping: $udid"
|
||||
((manual_failed++)) || true
|
||||
continue
|
||||
fi
|
||||
|
||||
local device_path="$HOME/Library/Developer/CoreSimulator/Devices/$udid"
|
||||
if [[ -d "$device_path" ]]; then
|
||||
# Use safe_remove for validated simulator device directory
|
||||
if safe_remove "$device_path" true; then
|
||||
((manually_removed++)) || true
|
||||
debug_log "Manually removed simulator: $udid"
|
||||
else
|
||||
((manual_failed++)) || true
|
||||
debug_log "Failed to manually remove simulator: $udid"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if ((manually_removed > 0)); then
|
||||
if ((manual_failed == 0)); then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${manually_removed} (fallback), ${unavailable_size_human}"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Xcode unavailable simulators · partially cleaned ${manually_removed}/${#unavailable_udids[@]}, ${unavailable_size_human}"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode unavailable simulators cleanup failed${error_hint}"
|
||||
debug_log "simctl delete error: $delete_output"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode unavailable simulators cleanup failed${error_hint}"
|
||||
debug_log "simctl delete error: $delete_output"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode unavailable simulators cleanup failed${error_hint}"
|
||||
debug_log "simctl delete error: $delete_output"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
note_activity
|
||||
fi # Close if ((unavailable_before == 0))
|
||||
note_activity
|
||||
fi # End of simctl_available check
|
||||
fi
|
||||
# Old iOS/watchOS/tvOS DeviceSupport versions (debug symbols for connected devices).
|
||||
# Each iOS version creates a 1-3 GB folder of debug symbols. Only the versions
|
||||
|
||||
@@ -10,16 +10,23 @@ clean_user_essentials() {
|
||||
|
||||
if ! is_path_whitelisted "$HOME/.Trash"; then
|
||||
local trash_count
|
||||
trash_count=$(osascript -e 'tell application "Finder" to count items in trash' 2> /dev/null || echo "0")
|
||||
local trash_count_status=0
|
||||
trash_count=$(run_with_timeout 3 osascript -e 'tell application "Finder" to count items in trash' 2> /dev/null) || trash_count_status=$?
|
||||
if [[ $trash_count_status -eq 124 ]]; then
|
||||
debug_log "Finder trash count timed out, using direct .Trash scan"
|
||||
trash_count=$(command find "$HOME/.Trash" -mindepth 1 -maxdepth 1 -exec printf '.' ';' 2> /dev/null |
|
||||
wc -c | awk '{print $1}' || echo "0")
|
||||
fi
|
||||
[[ "$trash_count" =~ ^[0-9]+$ ]] || trash_count="0"
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
[[ $trash_count -gt 0 ]] && echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Trash · would empty, $trash_count items" || echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · already empty"
|
||||
elif [[ $trash_count -gt 0 ]]; then
|
||||
if osascript -e 'tell application "Finder" to empty trash' > /dev/null 2>&1; then
|
||||
if run_with_timeout 5 osascript -e 'tell application "Finder" to empty trash' > /dev/null 2>&1; then
|
||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Trash · emptied, $trash_count items"
|
||||
note_activity
|
||||
else
|
||||
debug_log "Finder trash empty failed or timed out, falling back to direct deletion"
|
||||
local cleaned_count=0
|
||||
while IFS= read -r -d '' item; do
|
||||
if safe_remove "$item" true; then
|
||||
@@ -435,6 +442,8 @@ clean_support_app_data() {
|
||||
|
||||
# App caches (merged: macOS system caches + Sandboxed apps).
|
||||
clean_app_caches() {
|
||||
start_section_spinner "Scanning app caches..."
|
||||
|
||||
# macOS system caches (merged from clean_macos_system_caches)
|
||||
safe_clean ~/Library/Saved\ Application\ State/* "Saved application states" || true
|
||||
safe_clean ~/Library/Caches/com.apple.photoanalysisd "Photo analysis cache" || true
|
||||
@@ -454,8 +463,10 @@ clean_app_caches() {
|
||||
safe_clean ~/Library/Application\ Support/AddressBook/Sources/*/Photos.cache "Address Book photo cache" || true
|
||||
clean_support_app_data
|
||||
|
||||
# Sandboxed app caches
|
||||
# Stop initial scan indicator before entering per-group scans.
|
||||
stop_section_spinner
|
||||
|
||||
# Sandboxed app caches
|
||||
safe_clean ~/Library/Containers/com.apple.wallpaper.agent/Data/Library/Caches/* "Wallpaper agent cache"
|
||||
safe_clean ~/Library/Containers/com.apple.mediaanalysisd/Data/Library/Caches/* "Media analysis cache"
|
||||
safe_clean ~/Library/Containers/com.apple.AppStore/Data/Library/Caches/* "App Store cache"
|
||||
|
||||
@@ -59,6 +59,7 @@ show_uninstall_help() {
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --dry-run Preview app uninstallation without making changes"
|
||||
echo " --whitelist Not supported for uninstall (use clean/optimize)"
|
||||
echo " --debug Show detailed operation logs"
|
||||
echo " -h, --help Show this help message"
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Format Homebrew update label for display
|
||||
format_brew_update_label() {
|
||||
# Format Homebrew update details for display
|
||||
format_brew_update_detail() {
|
||||
local total="${BREW_OUTDATED_COUNT:-0}"
|
||||
if [[ -z "$total" || "$total" -le 0 ]]; then
|
||||
return
|
||||
@@ -18,14 +18,50 @@ format_brew_update_label() {
|
||||
((formulas > 0)) && details+=("${formulas} formula")
|
||||
((casks > 0)) && details+=("${casks} cask")
|
||||
|
||||
local detail_str=", ${total} updates"
|
||||
local detail_str="${total} updates"
|
||||
if ((${#details[@]} > 0)); then
|
||||
detail_str=", $(
|
||||
detail_str="$(
|
||||
IFS=', '
|
||||
printf '%s' "${details[*]}"
|
||||
)"
|
||||
fi
|
||||
printf " %s Homebrew%s" "$ICON_LIST" "$detail_str"
|
||||
printf "%s" "$detail_str"
|
||||
}
|
||||
|
||||
# Keep for compatibility with existing callers/tests.
|
||||
format_brew_update_label() {
|
||||
local detail
|
||||
detail=$(format_brew_update_detail || true)
|
||||
[[ -n "$detail" ]] && printf "Homebrew, %s" "$detail"
|
||||
}
|
||||
|
||||
populate_brew_update_counts_if_unset() {
|
||||
local need_probe=false
|
||||
[[ -z "${BREW_OUTDATED_COUNT:-}" ]] && need_probe=true
|
||||
[[ -z "${BREW_FORMULA_OUTDATED_COUNT:-}" ]] && need_probe=true
|
||||
[[ -z "${BREW_CASK_OUTDATED_COUNT:-}" ]] && need_probe=true
|
||||
|
||||
if [[ "$need_probe" == "false" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local formula_count="${BREW_FORMULA_OUTDATED_COUNT:-0}"
|
||||
local cask_count="${BREW_CASK_OUTDATED_COUNT:-0}"
|
||||
|
||||
if command -v brew > /dev/null 2>&1; then
|
||||
local formula_outdated=""
|
||||
local cask_outdated=""
|
||||
|
||||
formula_outdated=$(run_with_timeout 8 brew outdated --formula --quiet 2> /dev/null || true)
|
||||
cask_outdated=$(run_with_timeout 8 brew outdated --cask --quiet 2> /dev/null || true)
|
||||
|
||||
formula_count=$(printf '%s\n' "$formula_outdated" | awk 'NF {count++} END {print count + 0}')
|
||||
cask_count=$(printf '%s\n' "$cask_outdated" | awk 'NF {count++} END {print count + 0}')
|
||||
fi
|
||||
|
||||
BREW_FORMULA_OUTDATED_COUNT="$formula_count"
|
||||
BREW_CASK_OUTDATED_COUNT="$cask_count"
|
||||
BREW_OUTDATED_COUNT="$((formula_count + cask_count))"
|
||||
}
|
||||
|
||||
brew_has_outdated() {
|
||||
@@ -42,61 +78,53 @@ brew_has_outdated() {
|
||||
# Ask user if they want to update
|
||||
# Returns: 0 if yes, 1 if no
|
||||
ask_for_updates() {
|
||||
local has_updates=false
|
||||
local -a update_list=()
|
||||
populate_brew_update_counts_if_unset
|
||||
|
||||
local brew_entry
|
||||
brew_entry=$(format_brew_update_label || true)
|
||||
if [[ -n "$brew_entry" ]]; then
|
||||
local has_updates=false
|
||||
if [[ -n "${BREW_OUTDATED_COUNT:-}" && "${BREW_OUTDATED_COUNT:-0}" -gt 0 ]]; then
|
||||
has_updates=true
|
||||
update_list+=("$brew_entry")
|
||||
fi
|
||||
|
||||
if [[ -n "${APPSTORE_UPDATE_COUNT:-}" && "${APPSTORE_UPDATE_COUNT:-0}" -gt 0 ]]; then
|
||||
has_updates=true
|
||||
update_list+=(" ${ICON_LIST} App Store, ${APPSTORE_UPDATE_COUNT} apps")
|
||||
fi
|
||||
|
||||
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" ]]; then
|
||||
has_updates=true
|
||||
update_list+=(" ${ICON_LIST} macOS system")
|
||||
fi
|
||||
|
||||
if [[ -n "${MOLE_UPDATE_AVAILABLE:-}" && "${MOLE_UPDATE_AVAILABLE}" == "true" ]]; then
|
||||
has_updates=true
|
||||
update_list+=(" ${ICON_LIST} Mole")
|
||||
fi
|
||||
|
||||
if [[ "$has_updates" == "false" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}AVAILABLE UPDATES${NC}"
|
||||
for item in "${update_list[@]}"; do
|
||||
echo -e "$item"
|
||||
done
|
||||
echo ""
|
||||
# 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
|
||||
if ! key=$(read_key); then
|
||||
echo "skip"
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$key" == "ENTER" ]]; then
|
||||
echo "yes"
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${ICON_REVIEW} Run ${GREEN}brew upgrade${NC} to update"
|
||||
if [[ -n "${BREW_OUTDATED_COUNT:-}" && "${BREW_OUTDATED_COUNT:-0}" -gt 0 ]]; then
|
||||
echo -e " ${GRAY}${ICON_REVIEW}${NC} Run ${GREEN}brew upgrade${NC} to update"
|
||||
fi
|
||||
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" ]]; then
|
||||
echo -e " ${GRAY}${ICON_REVIEW}${NC} Open ${GREEN}System Settings${NC} → ${GREEN}General${NC} → ${GREEN}Software Update${NC}"
|
||||
fi
|
||||
if [[ -n "${APPSTORE_UPDATE_COUNT:-}" && "${APPSTORE_UPDATE_COUNT:-0}" -gt 0 ]]; then
|
||||
echo -e " ${GRAY}${ICON_REVIEW}${NC} Open ${GREEN}App Store${NC} → ${GREEN}Updates${NC}"
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -403,7 +403,7 @@ opt_launch_services_rebuild() {
|
||||
fi
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner ""
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Repairing LaunchServices..."
|
||||
fi
|
||||
|
||||
local lsregister
|
||||
|
||||
@@ -148,16 +148,24 @@ refresh_launch_services_after_uninstall() {
|
||||
|
||||
local success=0
|
||||
set +e
|
||||
"$lsregister" -gc > /dev/null 2>&1 || true
|
||||
"$lsregister" -r -f -domain local -domain user -domain system > /dev/null 2>&1
|
||||
# Add 10s timeout to prevent hanging (gc is usually fast)
|
||||
# run_with_timeout falls back to shell implementation if timeout command unavailable
|
||||
run_with_timeout 10 "$lsregister" -gc > /dev/null 2>&1 || true
|
||||
# Add 15s timeout for rebuild (can be slow on some systems)
|
||||
run_with_timeout 15 "$lsregister" -r -f -domain local -domain user -domain system > /dev/null 2>&1
|
||||
success=$?
|
||||
if [[ $success -ne 0 ]]; then
|
||||
"$lsregister" -r -f -domain local -domain user > /dev/null 2>&1
|
||||
# 124 = timeout exit code (from run_with_timeout or timeout command)
|
||||
if [[ $success -eq 124 ]]; then
|
||||
debug_log "LaunchServices rebuild timed out, trying lighter version"
|
||||
run_with_timeout 10 "$lsregister" -r -f -domain local -domain user > /dev/null 2>&1
|
||||
success=$?
|
||||
elif [[ $success -ne 0 ]]; then
|
||||
run_with_timeout 10 "$lsregister" -r -f -domain local -domain user > /dev/null 2>&1
|
||||
success=$?
|
||||
fi
|
||||
set -e
|
||||
|
||||
[[ $success -eq 0 ]]
|
||||
[[ $success -eq 0 || $success -eq 124 ]]
|
||||
}
|
||||
|
||||
# Remove macOS Login Items for an app
|
||||
@@ -789,7 +797,9 @@ batch_uninstall_applications() {
|
||||
fi
|
||||
|
||||
local autoremove_output removed_count
|
||||
autoremove_output=$(HOMEBREW_NO_ENV_HINTS=1 brew autoremove 2> /dev/null) || true
|
||||
# Add 30s timeout to prevent hanging on slow brew operations
|
||||
# Use run_with_timeout for consistent cross-platform behavior (has shell fallback)
|
||||
autoremove_output=$(run_with_timeout 30 bash -c 'HOMEBREW_NO_ENV_HINTS=1 brew autoremove 2>/dev/null' || true)
|
||||
removed_count=$(printf '%s\n' "$autoremove_output" | grep -c "^Uninstalling" || true)
|
||||
removed_count=${removed_count:-0}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user