mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 19:09:43 +00:00
Reconstruct clean lib code
This commit is contained in:
13
bin/check.sh
13
bin/check.sh
@@ -4,15 +4,12 @@ set -euo pipefail
|
||||
|
||||
# Load common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
source "$SCRIPT_DIR/lib/sudo_manager.sh"
|
||||
source "$SCRIPT_DIR/lib/update_manager.sh"
|
||||
source "$SCRIPT_DIR/lib/autofix_manager.sh"
|
||||
source "$SCRIPT_DIR/lib/core/common.sh"
|
||||
source "$SCRIPT_DIR/lib/core/sudo.sh"
|
||||
source "$SCRIPT_DIR/lib/manage/update.sh"
|
||||
source "$SCRIPT_DIR/lib/manage/autofix.sh"
|
||||
|
||||
source "$SCRIPT_DIR/lib/check_updates.sh"
|
||||
source "$SCRIPT_DIR/lib/check_health.sh"
|
||||
source "$SCRIPT_DIR/lib/check_security.sh"
|
||||
source "$SCRIPT_DIR/lib/check_config.sh"
|
||||
source "$SCRIPT_DIR/lib/check/all.sh"
|
||||
|
||||
cleanup_all() {
|
||||
stop_sudo_session
|
||||
|
||||
24
bin/clean.sh
24
bin/clean.sh
@@ -10,16 +10,16 @@ export LANG=C
|
||||
|
||||
# Get script directory and source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
source "$SCRIPT_DIR/../lib/sudo_manager.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean_brew.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean_caches.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean_apps.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean_dev.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean_user_apps.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean_system.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean_user_data.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean_maintenance.sh"
|
||||
source "$SCRIPT_DIR/../lib/core/common.sh"
|
||||
source "$SCRIPT_DIR/../lib/core/sudo.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean/brew.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean/caches.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean/apps.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean/dev.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean/app_caches.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean/system.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean/user.sh"
|
||||
source "$SCRIPT_DIR/../lib/clean/maintenance.sh"
|
||||
|
||||
# Configuration
|
||||
SYSTEM_CLEAN=false
|
||||
@@ -35,7 +35,7 @@ readonly PROTECTED_SW_DOMAINS=(
|
||||
)
|
||||
|
||||
# Whitelist patterns (loaded from common.sh)
|
||||
# FINDER_METADATA_SENTINEL and DEFAULT_WHITELIST_PATTERNS defined in lib/common.sh
|
||||
# FINDER_METADATA_SENTINEL and DEFAULT_WHITELIST_PATTERNS defined in lib/core/common.sh
|
||||
declare -a WHITELIST_PATTERNS=()
|
||||
WHITELIST_WARNINGS=()
|
||||
|
||||
@@ -846,7 +846,7 @@ main() {
|
||||
DRY_RUN=true
|
||||
;;
|
||||
"--whitelist")
|
||||
source "$SCRIPT_DIR/../lib/whitelist_manager.sh"
|
||||
source "$SCRIPT_DIR/../lib/manage/whitelist.sh"
|
||||
manage_whitelist
|
||||
exit 0
|
||||
;;
|
||||
|
||||
@@ -4,18 +4,14 @@ set -euo pipefail
|
||||
|
||||
# Load common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
source "$SCRIPT_DIR/lib/optimize_health.sh"
|
||||
source "$SCRIPT_DIR/lib/sudo_manager.sh"
|
||||
source "$SCRIPT_DIR/lib/update_manager.sh"
|
||||
source "$SCRIPT_DIR/lib/autofix_manager.sh"
|
||||
source "$SCRIPT_DIR/lib/optimization_tasks.sh"
|
||||
source "$SCRIPT_DIR/lib/core/common.sh"
|
||||
source "$SCRIPT_DIR/lib/core/sudo.sh"
|
||||
source "$SCRIPT_DIR/lib/manage/update.sh"
|
||||
source "$SCRIPT_DIR/lib/manage/autofix.sh"
|
||||
source "$SCRIPT_DIR/lib/optimize/tasks.sh"
|
||||
|
||||
# Load check modules
|
||||
source "$SCRIPT_DIR/lib/check_updates.sh"
|
||||
source "$SCRIPT_DIR/lib/check_health.sh"
|
||||
source "$SCRIPT_DIR/lib/check_security.sh"
|
||||
source "$SCRIPT_DIR/lib/check_config.sh"
|
||||
source "$SCRIPT_DIR/lib/check/all.sh"
|
||||
|
||||
# Colors and icons from common.sh
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LIB_DIR="$(cd "$SCRIPT_DIR/../lib" && pwd)"
|
||||
|
||||
# Source common functions
|
||||
# shellcheck source=../lib/common.sh
|
||||
source "$LIB_DIR/common.sh"
|
||||
# shellcheck source=../lib/core/common.sh
|
||||
source "$LIB_DIR/core/common.sh"
|
||||
|
||||
readonly PAM_SUDO_FILE="/etc/pam.d/sudo"
|
||||
readonly PAM_TID_LINE="auth sufficient pam_tid.so"
|
||||
|
||||
@@ -14,12 +14,12 @@ export LANG=C
|
||||
|
||||
# Get script directory and source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
source "$SCRIPT_DIR/../lib/menu_paginated.sh"
|
||||
source "$SCRIPT_DIR/../lib/ui_app_selector.sh"
|
||||
source "$SCRIPT_DIR/../lib/uninstall_batch.sh"
|
||||
source "$SCRIPT_DIR/../lib/core/common.sh"
|
||||
source "$SCRIPT_DIR/../lib/ui/menu_paginated.sh"
|
||||
source "$SCRIPT_DIR/../lib/ui/app_selector.sh"
|
||||
source "$SCRIPT_DIR/../lib/uninstall.sh"
|
||||
|
||||
# Note: Bundle preservation logic is now in lib/common.sh
|
||||
# Note: Bundle preservation logic is now in lib/core/common.sh
|
||||
|
||||
# Initialize global variables
|
||||
selected_apps=() # Global array for app selection
|
||||
@@ -404,7 +404,7 @@ load_applications() {
|
||||
# Read a single key with proper escape sequence handling
|
||||
# This function has been replaced by the menu.sh library
|
||||
|
||||
# Note: App file discovery and size calculation functions moved to lib/common.sh
|
||||
# Note: App file discovery and size calculation functions moved to lib/core/common.sh
|
||||
# Use find_app_files() and calculate_total_size() from common.sh
|
||||
|
||||
# Uninstall selected applications
|
||||
|
||||
@@ -39,7 +39,7 @@ fi; }
|
||||
# Verbosity (0 = quiet, 1 = verbose)
|
||||
VERBOSE=1
|
||||
|
||||
# Icons (duplicated from lib/common.sh - necessary as install.sh runs standalone)
|
||||
# Icons (duplicated from lib/core/common.sh - necessary as install.sh runs standalone)
|
||||
readonly ICON_SUCCESS="✓"
|
||||
readonly ICON_ADMIN="●"
|
||||
readonly ICON_CONFIRM="◎"
|
||||
@@ -349,7 +349,7 @@ install_files() {
|
||||
# Verify installation
|
||||
verify_installation() {
|
||||
|
||||
if [[ -x "$INSTALL_DIR/mole" ]] && [[ -f "$CONFIG_DIR/lib/common.sh" ]]; then
|
||||
if [[ -x "$INSTALL_DIR/mole" ]] && [[ -f "$CONFIG_DIR/lib/core/common.sh" ]]; then
|
||||
|
||||
# Test if mole command works
|
||||
if "$INSTALL_DIR/mole" --help > /dev/null 2>&1; then
|
||||
@@ -522,9 +522,9 @@ perform_update() {
|
||||
if command -v brew > /dev/null 2>&1 && brew list mole > /dev/null 2>&1; then
|
||||
# Try to use shared function if available (when running from installed Mole)
|
||||
resolve_source_dir 2> /dev/null || true
|
||||
if [[ -f "$SOURCE_DIR/lib/common.sh" ]]; then
|
||||
if [[ -f "$SOURCE_DIR/lib/core/common.sh" ]]; then
|
||||
# shellcheck disable=SC1090,SC1091
|
||||
source "$SOURCE_DIR/lib/common.sh"
|
||||
source "$SOURCE_DIR/lib/core/common.sh"
|
||||
update_via_homebrew "$VERSION"
|
||||
else
|
||||
# Fallback: inline implementation
|
||||
|
||||
656
lib/check/all.sh
Normal file
656
lib/check/all.sh
Normal file
@@ -0,0 +1,656 @@
|
||||
#!/bin/bash
|
||||
# System Checks Module
|
||||
# Combines configuration, security, updates, and health checks
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Checks
|
||||
# ============================================================================
|
||||
|
||||
check_touchid_sudo() {
|
||||
# Check if Touch ID is configured for sudo
|
||||
local pam_file="/etc/pam.d/sudo"
|
||||
if [[ -f "$pam_file" ]] && grep -q "pam_tid.so" "$pam_file" 2> /dev/null; then
|
||||
echo -e " ${GREEN}✓${NC} Touch ID Enabled for sudo"
|
||||
else
|
||||
# Check if Touch ID is supported
|
||||
local is_supported=false
|
||||
if command -v bioutil > /dev/null 2>&1; then
|
||||
if bioutil -r 2> /dev/null | grep -q "Touch ID"; then
|
||||
is_supported=true
|
||||
fi
|
||||
elif [[ "$(uname -m)" == "arm64" ]]; then
|
||||
is_supported=true
|
||||
fi
|
||||
|
||||
if [[ "$is_supported" == "true" ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Touch ID ${YELLOW}Not configured${NC} for sudo"
|
||||
export TOUCHID_NOT_CONFIGURED=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_rosetta() {
|
||||
# Check Rosetta 2 (for Apple Silicon Macs)
|
||||
if [[ "$(uname -m)" == "arm64" ]]; then
|
||||
if [[ -f "/Library/Apple/usr/share/rosetta/rosetta" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} Rosetta 2 Installed"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Rosetta 2 ${YELLOW}Not installed${NC}"
|
||||
export ROSETTA_NOT_INSTALLED=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_git_config() {
|
||||
# Check basic Git configuration
|
||||
if command -v git > /dev/null 2>&1; then
|
||||
local git_name=$(git config --global user.name 2> /dev/null || echo "")
|
||||
local git_email=$(git config --global user.email 2> /dev/null || echo "")
|
||||
|
||||
if [[ -n "$git_name" && -n "$git_email" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} Git Config Configured"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Git Config ${YELLOW}Not configured${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_config() {
|
||||
check_touchid_sudo
|
||||
check_rosetta
|
||||
check_git_config
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Security Checks
|
||||
# ============================================================================
|
||||
|
||||
check_filevault() {
|
||||
# Check FileVault encryption status
|
||||
if command -v fdesetup > /dev/null 2>&1; then
|
||||
local fv_status=$(fdesetup status 2> /dev/null || echo "")
|
||||
if echo "$fv_status" | grep -q "FileVault is On"; then
|
||||
echo -e " ${GREEN}✓${NC} FileVault Enabled"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} FileVault ${RED}Disabled${NC} (Recommend enabling)"
|
||||
export FILEVAULT_DISABLED=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_firewall() {
|
||||
# Check firewall status
|
||||
unset FIREWALL_DISABLED
|
||||
local firewall_status=$(defaults read /Library/Preferences/com.apple.alf globalstate 2> /dev/null || echo "0")
|
||||
if [[ "$firewall_status" == "1" || "$firewall_status" == "2" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} Firewall Enabled"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Firewall ${YELLOW}Disabled${NC} (Consider enabling)"
|
||||
echo -e " ${GRAY}System Settings → Network → Firewall, or run:${NC}"
|
||||
echo -e " ${GRAY}sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1${NC}"
|
||||
export FIREWALL_DISABLED=true
|
||||
fi
|
||||
}
|
||||
|
||||
check_gatekeeper() {
|
||||
# Check Gatekeeper status
|
||||
if command -v spctl > /dev/null 2>&1; then
|
||||
local gk_status=$(spctl --status 2> /dev/null || echo "")
|
||||
if echo "$gk_status" | grep -q "enabled"; then
|
||||
echo -e " ${GREEN}✓${NC} Gatekeeper Active"
|
||||
unset GATEKEEPER_DISABLED
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Gatekeeper ${YELLOW}Disabled${NC}"
|
||||
echo -e " ${GRAY}Enable via System Settings → Privacy & Security, or:${NC}"
|
||||
echo -e " ${GRAY}sudo spctl --master-enable${NC}"
|
||||
export GATEKEEPER_DISABLED=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_sip() {
|
||||
# Check System Integrity Protection
|
||||
if command -v csrutil > /dev/null 2>&1; then
|
||||
local sip_status=$(csrutil status 2> /dev/null || echo "")
|
||||
if echo "$sip_status" | grep -q "enabled"; then
|
||||
echo -e " ${GREEN}✓${NC} SIP Enabled"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} SIP ${YELLOW}Disabled${NC}"
|
||||
echo -e " ${GRAY}Restart into Recovery → Utilities → Terminal → run: csrutil enable${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_security() {
|
||||
check_filevault
|
||||
check_firewall
|
||||
check_gatekeeper
|
||||
check_sip
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Software Update Checks
|
||||
# ============================================================================
|
||||
|
||||
# Cache configuration
|
||||
CACHE_DIR="${HOME}/.cache/mole"
|
||||
CACHE_TTL=600 # 10 minutes in seconds
|
||||
|
||||
# Ensure cache directory exists
|
||||
mkdir -p "$CACHE_DIR" 2> /dev/null || true
|
||||
|
||||
clear_cache_file() {
|
||||
local file="$1"
|
||||
rm -f "$file" 2> /dev/null || true
|
||||
}
|
||||
|
||||
reset_brew_cache() {
|
||||
clear_cache_file "$CACHE_DIR/brew_updates"
|
||||
}
|
||||
|
||||
reset_softwareupdate_cache() {
|
||||
clear_cache_file "$CACHE_DIR/softwareupdate_list"
|
||||
SOFTWARE_UPDATE_LIST=""
|
||||
}
|
||||
|
||||
reset_mole_cache() {
|
||||
clear_cache_file "$CACHE_DIR/mole_version"
|
||||
}
|
||||
|
||||
# Check if cache is still valid
|
||||
is_cache_valid() {
|
||||
local cache_file="$1"
|
||||
local ttl="${2:-$CACHE_TTL}"
|
||||
|
||||
if [[ ! -f "$cache_file" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file")))
|
||||
[[ $cache_age -lt $ttl ]]
|
||||
}
|
||||
|
||||
check_homebrew_updates() {
|
||||
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=""
|
||||
|
||||
get_software_updates() {
|
||||
local cache_file="$CACHE_DIR/softwareupdate_list"
|
||||
|
||||
if [[ -z "$SOFTWARE_UPDATE_LIST" ]]; then
|
||||
# Check cache first
|
||||
if is_cache_valid "$cache_file"; then
|
||||
SOFTWARE_UPDATE_LIST=$(cat "$cache_file" 2> /dev/null || echo "")
|
||||
else
|
||||
# Show spinner while checking (only on first call)
|
||||
local show_spinner=false
|
||||
if [[ -t 1 && -z "${SOFTWAREUPDATE_SPINNER_SHOWN:-}" ]]; then
|
||||
start_inline_spinner "Checking system updates..."
|
||||
show_spinner=true
|
||||
export SOFTWAREUPDATE_SPINNER_SHOWN="true"
|
||||
fi
|
||||
|
||||
SOFTWARE_UPDATE_LIST=$(softwareupdate -l 2> /dev/null || echo "")
|
||||
# Save to cache
|
||||
echo "$SOFTWARE_UPDATE_LIST" > "$cache_file" 2> /dev/null || true
|
||||
|
||||
# Stop spinner
|
||||
if [[ "$show_spinner" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "$SOFTWARE_UPDATE_LIST"
|
||||
}
|
||||
|
||||
check_appstore_updates() {
|
||||
local spinner_started=false
|
||||
if [[ -t 1 ]]; then
|
||||
printf " Checking App Store updates...\r"
|
||||
start_inline_spinner "Checking App Store updates..."
|
||||
spinner_started=true
|
||||
export SOFTWAREUPDATE_SPINNER_SHOWN="external"
|
||||
else
|
||||
echo "Checking App Store updates..."
|
||||
fi
|
||||
|
||||
local update_list=""
|
||||
update_list=$(get_software_updates | grep -v "Software Update Tool" | grep "^\*" | grep -vi "macOS" || echo "")
|
||||
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
unset SOFTWAREUPDATE_SPINNER_SHOWN
|
||||
fi
|
||||
|
||||
local update_count=0
|
||||
if [[ -n "$update_list" ]]; then
|
||||
update_count=$(echo "$update_list" | wc -l | tr -d ' ')
|
||||
fi
|
||||
|
||||
export APPSTORE_UPDATE_COUNT=$update_count
|
||||
|
||||
if [[ $update_count -gt 0 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} App Store ${YELLOW}${update_count} apps${NC} need update"
|
||||
echo -e " ${GRAY}Run: ${GREEN}softwareupdate -i <label>${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} App Store Up to date"
|
||||
fi
|
||||
}
|
||||
|
||||
check_macos_update() {
|
||||
local spinner_started=false
|
||||
if [[ -t 1 ]]; then
|
||||
printf " Checking macOS updates...\r"
|
||||
start_inline_spinner "Checking macOS updates..."
|
||||
spinner_started=true
|
||||
export SOFTWAREUPDATE_SPINNER_SHOWN="external"
|
||||
else
|
||||
echo "Checking macOS updates..."
|
||||
fi
|
||||
|
||||
# Check for macOS system update using cached list
|
||||
local macos_update=""
|
||||
macos_update=$(get_software_updates | grep -i "macOS" | head -1 || echo "")
|
||||
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
unset SOFTWAREUPDATE_SPINNER_SHOWN
|
||||
fi
|
||||
|
||||
export MACOS_UPDATE_AVAILABLE="false"
|
||||
|
||||
if [[ -n "$macos_update" ]]; then
|
||||
export MACOS_UPDATE_AVAILABLE="true"
|
||||
local version=$(echo "$macos_update" | grep -o '[0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?' | head -1)
|
||||
if [[ -n "$version" ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} macOS ${YELLOW}${version} available${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} macOS ${YELLOW}Update available${NC}"
|
||||
fi
|
||||
echo -e " ${GRAY}Run: ${GREEN}softwareupdate -i <label>${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} macOS Up to date"
|
||||
fi
|
||||
}
|
||||
|
||||
check_mole_update() {
|
||||
# Check if Mole has updates
|
||||
# Auto-detect version from mole main script
|
||||
local current_version
|
||||
if [[ -f "${SCRIPT_DIR:-/usr/local/bin}/mole" ]]; then
|
||||
current_version=$(grep '^VERSION=' "${SCRIPT_DIR:-/usr/local/bin}/mole" 2> /dev/null | head -1 | sed 's/VERSION="\(.*\)"/\1/' || echo "unknown")
|
||||
else
|
||||
current_version="${VERSION:-unknown}"
|
||||
fi
|
||||
|
||||
local latest_version=""
|
||||
local cache_file="$CACHE_DIR/mole_version"
|
||||
|
||||
export MOLE_UPDATE_AVAILABLE="false"
|
||||
|
||||
# Check cache first
|
||||
if is_cache_valid "$cache_file"; then
|
||||
latest_version=$(cat "$cache_file" 2> /dev/null || echo "")
|
||||
else
|
||||
# Show spinner while checking
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Checking Mole version..."
|
||||
fi
|
||||
|
||||
# Try to get latest version from GitHub
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
latest_version=$(curl -fsSL https://api.github.com/repos/tw93/mole/releases/latest 2> /dev/null | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/' || echo "")
|
||||
# Save to cache
|
||||
if [[ -n "$latest_version" ]]; then
|
||||
echo "$latest_version" > "$cache_file" 2> /dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Stop spinner
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
fi
|
||||
|
||||
# Normalize version strings (remove leading 'v' or 'V')
|
||||
current_version=$(echo "$current_version" | sed 's/^[vV]//')
|
||||
latest_version=$(echo "$latest_version" | sed 's/^[vV]//')
|
||||
|
||||
if [[ -n "$latest_version" && "$current_version" != "$latest_version" ]]; then
|
||||
# Compare versions
|
||||
if [[ "$(printf '%s\n' "$current_version" "$latest_version" | sort -V | head -1)" == "$current_version" ]]; then
|
||||
export MOLE_UPDATE_AVAILABLE="true"
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Mole ${YELLOW}${latest_version} available${NC} (current: ${current_version})"
|
||||
echo -e " ${GRAY}Run: ${GREEN}mo update${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Mole Up to date (${current_version})"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Mole Up to date (${current_version})"
|
||||
fi
|
||||
}
|
||||
|
||||
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
|
||||
get_software_updates > /dev/null 2>&1
|
||||
|
||||
check_appstore_updates
|
||||
check_macos_update
|
||||
check_mole_update
|
||||
}
|
||||
|
||||
get_appstore_update_labels() {
|
||||
get_software_updates | awk '
|
||||
/^\*/ {
|
||||
label=$0
|
||||
sub(/^[[:space:]]*\* Label: */, "", label)
|
||||
sub(/,.*/, "", label)
|
||||
lower=tolower(label)
|
||||
if (index(lower, "macos") == 0) {
|
||||
print label
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
get_macos_update_labels() {
|
||||
get_software_updates | awk '
|
||||
/^\*/ {
|
||||
label=$0
|
||||
sub(/^[[:space:]]*\* Label: */, "", label)
|
||||
sub(/,.*/, "", label)
|
||||
lower=tolower(label)
|
||||
if (index(lower, "macos") != 0) {
|
||||
print label
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# System Health Checks
|
||||
# ============================================================================
|
||||
|
||||
check_disk_space() {
|
||||
local free_gb=$(df -H / | awk 'NR==2 {print $4}' | sed 's/G//')
|
||||
local free_num=$(echo "$free_gb" | tr -d 'G' | cut -d'.' -f1)
|
||||
|
||||
export DISK_FREE_GB=$free_num
|
||||
|
||||
if [[ $free_num -lt 20 ]]; then
|
||||
echo -e " ${RED}✗${NC} Disk Space ${RED}${free_gb}GB free${NC} (Critical)"
|
||||
elif [[ $free_num -lt 50 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Disk Space ${YELLOW}${free_gb}GB free${NC} (Low)"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Disk Space ${free_gb}GB free"
|
||||
fi
|
||||
}
|
||||
|
||||
check_memory_usage() {
|
||||
local mem_total
|
||||
mem_total=$(sysctl -n hw.memsize 2> /dev/null || echo "0")
|
||||
if [[ -z "$mem_total" || "$mem_total" -le 0 ]]; then
|
||||
echo -e " ${GRAY}-${NC} Memory Unable to determine"
|
||||
return
|
||||
fi
|
||||
|
||||
local vm_output
|
||||
vm_output=$(vm_stat 2> /dev/null || echo "")
|
||||
|
||||
local page_size
|
||||
page_size=$(echo "$vm_output" | awk '/page size of/ {print $8}')
|
||||
[[ -z "$page_size" ]] && page_size=4096
|
||||
|
||||
local free_pages inactive_pages spec_pages
|
||||
free_pages=$(echo "$vm_output" | awk '/Pages free/ {gsub(/\./,"",$3); print $3}')
|
||||
inactive_pages=$(echo "$vm_output" | awk '/Pages inactive/ {gsub(/\./,"",$3); print $3}')
|
||||
spec_pages=$(echo "$vm_output" | awk '/Pages speculative/ {gsub(/\./,"",$3); print $3}')
|
||||
|
||||
free_pages=${free_pages:-0}
|
||||
inactive_pages=${inactive_pages:-0}
|
||||
spec_pages=${spec_pages:-0}
|
||||
|
||||
# Estimate used percent: (total - free - inactive - speculative) / total
|
||||
local total_pages=$((mem_total / page_size))
|
||||
local free_total=$((free_pages + inactive_pages + spec_pages))
|
||||
local used_pages=$((total_pages - free_total))
|
||||
if ((used_pages < 0)); then
|
||||
used_pages=0
|
||||
fi
|
||||
|
||||
local used_percent
|
||||
used_percent=$(awk "BEGIN {printf \"%.0f\", ($used_pages / $total_pages) * 100}")
|
||||
((used_percent > 100)) && used_percent=100
|
||||
((used_percent < 0)) && used_percent=0
|
||||
|
||||
if [[ $used_percent -gt 90 ]]; then
|
||||
echo -e " ${RED}✗${NC} Memory ${RED}${used_percent}% used${NC} (Critical)"
|
||||
elif [[ $used_percent -gt 80 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Memory ${YELLOW}${used_percent}% used${NC} (High)"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Memory ${used_percent}% used"
|
||||
fi
|
||||
}
|
||||
|
||||
check_login_items() {
|
||||
local login_items_count=0
|
||||
local -a login_items_list=()
|
||||
|
||||
if [[ -t 0 ]]; then
|
||||
# Show spinner while getting login items
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Checking login items..."
|
||||
fi
|
||||
|
||||
while IFS= read -r login_item; do
|
||||
[[ -n "$login_item" ]] && login_items_list+=("$login_item")
|
||||
done < <(list_login_items || true)
|
||||
login_items_count=${#login_items_list[@]}
|
||||
|
||||
# Stop spinner before output
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $login_items_count -gt 15 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Login Items ${YELLOW}${login_items_count} apps${NC} auto-start (High)"
|
||||
elif [[ $login_items_count -gt 0 ]]; then
|
||||
echo -e " ${GREEN}✓${NC} Login Items ${login_items_count} apps auto-start"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Login Items None"
|
||||
return
|
||||
fi
|
||||
|
||||
# Show items in a single line
|
||||
local preview_limit=5
|
||||
((preview_limit > login_items_count)) && preview_limit=$login_items_count
|
||||
|
||||
local items_display=""
|
||||
for ((i = 0; i < preview_limit; i++)); do
|
||||
if [[ $i -eq 0 ]]; then
|
||||
items_display="${login_items_list[$i]}"
|
||||
else
|
||||
items_display="${items_display}, ${login_items_list[$i]}"
|
||||
fi
|
||||
done
|
||||
|
||||
if ((login_items_count > preview_limit)); then
|
||||
local remaining=$((login_items_count - preview_limit))
|
||||
items_display="${items_display}, and ${remaining} more"
|
||||
fi
|
||||
|
||||
echo -e " ${GRAY}${items_display}${NC}"
|
||||
echo -e " ${GRAY}Manage in System Settings → Login Items${NC}"
|
||||
}
|
||||
|
||||
check_cache_size() {
|
||||
local cache_size_kb=0
|
||||
|
||||
# Check common cache locations
|
||||
local -a cache_paths=(
|
||||
"$HOME/Library/Caches"
|
||||
"$HOME/Library/Logs"
|
||||
)
|
||||
|
||||
# Show spinner while calculating cache size
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Scanning cache..."
|
||||
fi
|
||||
|
||||
for cache_path in "${cache_paths[@]}"; do
|
||||
if [[ -d "$cache_path" ]]; then
|
||||
local size_output
|
||||
size_output=$(du -sk "$cache_path" 2> /dev/null | awk 'NR==1 {print $1}' | tr -d '[:space:]' || echo "")
|
||||
[[ "$size_output" =~ ^[0-9]+$ ]] || size_output=0
|
||||
cache_size_kb=$((cache_size_kb + size_output))
|
||||
fi
|
||||
done
|
||||
|
||||
local cache_size_gb=$(echo "scale=1; $cache_size_kb / 1024 / 1024" | bc)
|
||||
export CACHE_SIZE_GB=$cache_size_gb
|
||||
|
||||
# Stop spinner before output
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
# Convert to integer for comparison
|
||||
local cache_size_int=$(echo "$cache_size_gb" | cut -d'.' -f1)
|
||||
|
||||
if [[ $cache_size_int -gt 10 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Cache Size ${YELLOW}${cache_size_gb}GB${NC} cleanable"
|
||||
elif [[ $cache_size_int -gt 5 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Cache Size ${YELLOW}${cache_size_gb}GB${NC} cleanable"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Cache Size ${cache_size_gb}GB"
|
||||
fi
|
||||
}
|
||||
|
||||
check_swap_usage() {
|
||||
# Check swap usage
|
||||
if command -v sysctl > /dev/null 2>&1; then
|
||||
local swap_info=$(sysctl vm.swapusage 2> /dev/null || echo "")
|
||||
if [[ -n "$swap_info" ]]; then
|
||||
local swap_used=$(echo "$swap_info" | grep -o "used = [0-9.]*[GM]" | awk '{print $3}' || echo "0M")
|
||||
local swap_num=$(echo "$swap_used" | sed 's/[GM]//')
|
||||
|
||||
if [[ "$swap_used" == *"G"* ]]; then
|
||||
local swap_gb=${swap_num%.*}
|
||||
if [[ $swap_gb -gt 2 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Swap Usage ${YELLOW}${swap_used}${NC} (High)"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Swap Usage ${swap_used}"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Swap Usage ${swap_used}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_brew_health() {
|
||||
# Check Homebrew doctor
|
||||
if command -v brew > /dev/null 2>&1; then
|
||||
# Show spinner while running brew doctor
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Running brew doctor..."
|
||||
fi
|
||||
|
||||
local brew_doctor=$(brew doctor 2>&1 || echo "")
|
||||
|
||||
# Stop spinner before output
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
if echo "$brew_doctor" | grep -q "ready to brew"; then
|
||||
echo -e " ${GREEN}✓${NC} Homebrew Healthy"
|
||||
else
|
||||
local warning_count=$(echo "$brew_doctor" | grep -c "Warning:" || echo "0")
|
||||
if [[ $warning_count -gt 0 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Homebrew ${YELLOW}${warning_count} warnings${NC}"
|
||||
echo -e " ${GRAY}Run: ${GREEN}brew doctor${NC} to see fixes, then rerun until clean${NC}"
|
||||
export BREW_HAS_WARNINGS=true
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Homebrew Healthy"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_system_health() {
|
||||
check_disk_space
|
||||
check_memory_usage
|
||||
check_swap_usage
|
||||
check_login_items
|
||||
check_cache_size
|
||||
# Time Machine check is optional; skip by default to avoid noise on systems without backups
|
||||
check_brew_health
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration checks
|
||||
|
||||
check_touchid_sudo() {
|
||||
# Check if Touch ID is configured for sudo
|
||||
local pam_file="/etc/pam.d/sudo"
|
||||
if [[ -f "$pam_file" ]] && grep -q "pam_tid.so" "$pam_file" 2> /dev/null; then
|
||||
echo -e " ${GREEN}✓${NC} Touch ID Enabled for sudo"
|
||||
else
|
||||
# Check if Touch ID is supported
|
||||
local is_supported=false
|
||||
if command -v bioutil > /dev/null 2>&1; then
|
||||
if bioutil -r 2> /dev/null | grep -q "Touch ID"; then
|
||||
is_supported=true
|
||||
fi
|
||||
elif [[ "$(uname -m)" == "arm64" ]]; then
|
||||
is_supported=true
|
||||
fi
|
||||
|
||||
if [[ "$is_supported" == "true" ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Touch ID ${YELLOW}Not configured${NC} for sudo"
|
||||
export TOUCHID_NOT_CONFIGURED=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_rosetta() {
|
||||
# Check Rosetta 2 (for Apple Silicon Macs)
|
||||
if [[ "$(uname -m)" == "arm64" ]]; then
|
||||
if [[ -f "/Library/Apple/usr/share/rosetta/rosetta" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} Rosetta 2 Installed"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Rosetta 2 ${YELLOW}Not installed${NC}"
|
||||
export ROSETTA_NOT_INSTALLED=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_git_config() {
|
||||
# Check basic Git configuration
|
||||
if command -v git > /dev/null 2>&1; then
|
||||
local git_name=$(git config --global user.name 2> /dev/null || echo "")
|
||||
local git_email=$(git config --global user.email 2> /dev/null || echo "")
|
||||
|
||||
if [[ -n "$git_name" && -n "$git_email" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} Git Config Configured"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Git Config ${YELLOW}Not configured${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_config() {
|
||||
check_touchid_sudo
|
||||
check_rosetta
|
||||
check_git_config
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# System health checks
|
||||
# Sets global variables for use in suggestions
|
||||
|
||||
check_disk_space() {
|
||||
local free_gb=$(df -H / | awk 'NR==2 {print $4}' | sed 's/G//')
|
||||
local free_num=$(echo "$free_gb" | tr -d 'G' | cut -d'.' -f1)
|
||||
|
||||
export DISK_FREE_GB=$free_num
|
||||
|
||||
if [[ $free_num -lt 20 ]]; then
|
||||
echo -e " ${RED}✗${NC} Disk Space ${RED}${free_gb}GB free${NC} (Critical)"
|
||||
elif [[ $free_num -lt 50 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Disk Space ${YELLOW}${free_gb}GB free${NC} (Low)"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Disk Space ${free_gb}GB free"
|
||||
fi
|
||||
}
|
||||
|
||||
check_memory_usage() {
|
||||
local mem_total
|
||||
mem_total=$(sysctl -n hw.memsize 2> /dev/null || echo "0")
|
||||
if [[ -z "$mem_total" || "$mem_total" -le 0 ]]; then
|
||||
echo -e " ${GRAY}-${NC} Memory Unable to determine"
|
||||
return
|
||||
fi
|
||||
|
||||
local vm_output
|
||||
vm_output=$(vm_stat 2> /dev/null || echo "")
|
||||
|
||||
local page_size
|
||||
page_size=$(echo "$vm_output" | awk '/page size of/ {print $8}')
|
||||
[[ -z "$page_size" ]] && page_size=4096
|
||||
|
||||
local free_pages inactive_pages spec_pages
|
||||
free_pages=$(echo "$vm_output" | awk '/Pages free/ {gsub(/\./,"",$3); print $3}')
|
||||
inactive_pages=$(echo "$vm_output" | awk '/Pages inactive/ {gsub(/\./,"",$3); print $3}')
|
||||
spec_pages=$(echo "$vm_output" | awk '/Pages speculative/ {gsub(/\./,"",$3); print $3}')
|
||||
|
||||
free_pages=${free_pages:-0}
|
||||
inactive_pages=${inactive_pages:-0}
|
||||
spec_pages=${spec_pages:-0}
|
||||
|
||||
# Estimate used percent: (total - free - inactive - speculative) / total
|
||||
local total_pages=$((mem_total / page_size))
|
||||
local free_total=$((free_pages + inactive_pages + spec_pages))
|
||||
local used_pages=$((total_pages - free_total))
|
||||
if ((used_pages < 0)); then
|
||||
used_pages=0
|
||||
fi
|
||||
|
||||
local used_percent
|
||||
used_percent=$(awk "BEGIN {printf \"%.0f\", ($used_pages / $total_pages) * 100}")
|
||||
((used_percent > 100)) && used_percent=100
|
||||
((used_percent < 0)) && used_percent=0
|
||||
|
||||
if [[ $used_percent -gt 90 ]]; then
|
||||
echo -e " ${RED}✗${NC} Memory ${RED}${used_percent}% used${NC} (Critical)"
|
||||
elif [[ $used_percent -gt 80 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Memory ${YELLOW}${used_percent}% used${NC} (High)"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Memory ${used_percent}% used"
|
||||
fi
|
||||
}
|
||||
|
||||
check_login_items() {
|
||||
local login_items_count=0
|
||||
local -a login_items_list=()
|
||||
|
||||
if [[ -t 0 ]]; then
|
||||
# Show spinner while getting login items
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Checking login items..."
|
||||
fi
|
||||
|
||||
while IFS= read -r login_item; do
|
||||
[[ -n "$login_item" ]] && login_items_list+=("$login_item")
|
||||
done < <(list_login_items || true)
|
||||
login_items_count=${#login_items_list[@]}
|
||||
|
||||
# Stop spinner before output
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $login_items_count -gt 15 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Login Items ${YELLOW}${login_items_count} apps${NC} auto-start (High)"
|
||||
elif [[ $login_items_count -gt 0 ]]; then
|
||||
echo -e " ${GREEN}✓${NC} Login Items ${login_items_count} apps auto-start"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Login Items None"
|
||||
return
|
||||
fi
|
||||
|
||||
# Show items in a single line
|
||||
local preview_limit=5
|
||||
((preview_limit > login_items_count)) && preview_limit=$login_items_count
|
||||
|
||||
local items_display=""
|
||||
for ((i = 0; i < preview_limit; i++)); do
|
||||
if [[ $i -eq 0 ]]; then
|
||||
items_display="${login_items_list[$i]}"
|
||||
else
|
||||
items_display="${items_display}, ${login_items_list[$i]}"
|
||||
fi
|
||||
done
|
||||
|
||||
if ((login_items_count > preview_limit)); then
|
||||
local remaining=$((login_items_count - preview_limit))
|
||||
items_display="${items_display}, and ${remaining} more"
|
||||
fi
|
||||
|
||||
echo -e " ${GRAY}${items_display}${NC}"
|
||||
echo -e " ${GRAY}Manage in System Settings → Login Items${NC}"
|
||||
}
|
||||
|
||||
check_cache_size() {
|
||||
local cache_size_kb=0
|
||||
|
||||
# Check common cache locations
|
||||
local -a cache_paths=(
|
||||
"$HOME/Library/Caches"
|
||||
"$HOME/Library/Logs"
|
||||
)
|
||||
|
||||
# Show spinner while calculating cache size
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Scanning cache..."
|
||||
fi
|
||||
|
||||
for cache_path in "${cache_paths[@]}"; do
|
||||
if [[ -d "$cache_path" ]]; then
|
||||
local size_output
|
||||
size_output=$(du -sk "$cache_path" 2> /dev/null | awk 'NR==1 {print $1}' | tr -d '[:space:]' || echo "")
|
||||
[[ "$size_output" =~ ^[0-9]+$ ]] || size_output=0
|
||||
cache_size_kb=$((cache_size_kb + size_output))
|
||||
fi
|
||||
done
|
||||
|
||||
local cache_size_gb=$(echo "scale=1; $cache_size_kb / 1024 / 1024" | bc)
|
||||
export CACHE_SIZE_GB=$cache_size_gb
|
||||
|
||||
# Stop spinner before output
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
# Convert to integer for comparison
|
||||
local cache_size_int=$(echo "$cache_size_gb" | cut -d'.' -f1)
|
||||
|
||||
if [[ $cache_size_int -gt 10 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Cache Size ${YELLOW}${cache_size_gb}GB${NC} cleanable"
|
||||
elif [[ $cache_size_int -gt 5 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Cache Size ${YELLOW}${cache_size_gb}GB${NC} cleanable"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Cache Size ${cache_size_gb}GB"
|
||||
fi
|
||||
}
|
||||
|
||||
check_swap_usage() {
|
||||
# Check swap usage
|
||||
if command -v sysctl > /dev/null 2>&1; then
|
||||
local swap_info=$(sysctl vm.swapusage 2> /dev/null || echo "")
|
||||
if [[ -n "$swap_info" ]]; then
|
||||
local swap_used=$(echo "$swap_info" | grep -o "used = [0-9.]*[GM]" | awk '{print $3}' || echo "0M")
|
||||
local swap_num=$(echo "$swap_used" | sed 's/[GM]//')
|
||||
|
||||
if [[ "$swap_used" == *"G"* ]]; then
|
||||
local swap_gb=${swap_num%.*}
|
||||
if [[ $swap_gb -gt 2 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Swap Usage ${YELLOW}${swap_used}${NC} (High)"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Swap Usage ${swap_used}"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Swap Usage ${swap_used}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_brew_health() {
|
||||
# Check Homebrew doctor
|
||||
if command -v brew > /dev/null 2>&1; then
|
||||
# Show spinner while running brew doctor
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Running brew doctor..."
|
||||
fi
|
||||
|
||||
local brew_doctor=$(brew doctor 2>&1 || echo "")
|
||||
|
||||
# Stop spinner before output
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
if echo "$brew_doctor" | grep -q "ready to brew"; then
|
||||
echo -e " ${GREEN}✓${NC} Homebrew Healthy"
|
||||
else
|
||||
local warning_count=$(echo "$brew_doctor" | grep -c "Warning:" || echo "0")
|
||||
if [[ $warning_count -gt 0 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Homebrew ${YELLOW}${warning_count} warnings${NC}"
|
||||
echo -e " ${GRAY}Run: ${GREEN}brew doctor${NC} to see fixes, then rerun until clean${NC}"
|
||||
export BREW_HAS_WARNINGS=true
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Homebrew Healthy"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_system_health() {
|
||||
check_disk_space
|
||||
check_memory_usage
|
||||
check_swap_usage
|
||||
check_login_items
|
||||
check_cache_size
|
||||
# Time Machine check is optional; skip by default to avoid noise on systems without backups
|
||||
check_brew_health
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Security checks
|
||||
|
||||
check_filevault() {
|
||||
# Check FileVault encryption status
|
||||
if command -v fdesetup > /dev/null 2>&1; then
|
||||
local fv_status=$(fdesetup status 2> /dev/null || echo "")
|
||||
if echo "$fv_status" | grep -q "FileVault is On"; then
|
||||
echo -e " ${GREEN}✓${NC} FileVault Enabled"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} FileVault ${RED}Disabled${NC} (Recommend enabling)"
|
||||
export FILEVAULT_DISABLED=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_firewall() {
|
||||
# Check firewall status
|
||||
unset FIREWALL_DISABLED
|
||||
local firewall_status=$(defaults read /Library/Preferences/com.apple.alf globalstate 2> /dev/null || echo "0")
|
||||
if [[ "$firewall_status" == "1" || "$firewall_status" == "2" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} Firewall Enabled"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Firewall ${YELLOW}Disabled${NC} (Consider enabling)"
|
||||
echo -e " ${GRAY}System Settings → Network → Firewall, or run:${NC}"
|
||||
echo -e " ${GRAY}sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1${NC}"
|
||||
export FIREWALL_DISABLED=true
|
||||
fi
|
||||
}
|
||||
|
||||
check_gatekeeper() {
|
||||
# Check Gatekeeper status
|
||||
if command -v spctl > /dev/null 2>&1; then
|
||||
local gk_status=$(spctl --status 2> /dev/null || echo "")
|
||||
if echo "$gk_status" | grep -q "enabled"; then
|
||||
echo -e " ${GREEN}✓${NC} Gatekeeper Active"
|
||||
unset GATEKEEPER_DISABLED
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Gatekeeper ${YELLOW}Disabled${NC}"
|
||||
echo -e " ${GRAY}Enable via System Settings → Privacy & Security, or:${NC}"
|
||||
echo -e " ${GRAY}sudo spctl --master-enable${NC}"
|
||||
export GATEKEEPER_DISABLED=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_sip() {
|
||||
# Check System Integrity Protection
|
||||
if command -v csrutil > /dev/null 2>&1; then
|
||||
local sip_status=$(csrutil status 2> /dev/null || echo "")
|
||||
if echo "$sip_status" | grep -q "enabled"; then
|
||||
echo -e " ${GREEN}✓${NC} SIP Enabled"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} SIP ${YELLOW}Disabled${NC}"
|
||||
echo -e " ${GRAY}Restart into Recovery → Utilities → Terminal → run: csrutil enable${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_security() {
|
||||
check_filevault
|
||||
check_firewall
|
||||
check_gatekeeper
|
||||
check_sip
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check for software updates
|
||||
# Sets global variables for use in suggestions
|
||||
|
||||
# Cache configuration
|
||||
CACHE_DIR="${HOME}/.cache/mole"
|
||||
CACHE_TTL=600 # 10 minutes in seconds
|
||||
|
||||
# Ensure cache directory exists
|
||||
mkdir -p "$CACHE_DIR" 2> /dev/null || true
|
||||
|
||||
clear_cache_file() {
|
||||
local file="$1"
|
||||
rm -f "$file" 2> /dev/null || true
|
||||
}
|
||||
|
||||
reset_brew_cache() {
|
||||
clear_cache_file "$CACHE_DIR/brew_updates"
|
||||
}
|
||||
|
||||
reset_softwareupdate_cache() {
|
||||
clear_cache_file "$CACHE_DIR/softwareupdate_list"
|
||||
SOFTWARE_UPDATE_LIST=""
|
||||
}
|
||||
|
||||
reset_mole_cache() {
|
||||
clear_cache_file "$CACHE_DIR/mole_version"
|
||||
}
|
||||
|
||||
# Check if cache is still valid
|
||||
is_cache_valid() {
|
||||
local cache_file="$1"
|
||||
local ttl="${2:-$CACHE_TTL}"
|
||||
|
||||
if [[ ! -f "$cache_file" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file")))
|
||||
[[ $cache_age -lt $ttl ]]
|
||||
}
|
||||
|
||||
check_homebrew_updates() {
|
||||
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=""
|
||||
|
||||
get_software_updates() {
|
||||
local cache_file="$CACHE_DIR/softwareupdate_list"
|
||||
|
||||
if [[ -z "$SOFTWARE_UPDATE_LIST" ]]; then
|
||||
# Check cache first
|
||||
if is_cache_valid "$cache_file"; then
|
||||
SOFTWARE_UPDATE_LIST=$(cat "$cache_file" 2> /dev/null || echo "")
|
||||
else
|
||||
# Show spinner while checking (only on first call)
|
||||
local show_spinner=false
|
||||
if [[ -t 1 && -z "${SOFTWAREUPDATE_SPINNER_SHOWN:-}" ]]; then
|
||||
start_inline_spinner "Checking system updates..."
|
||||
show_spinner=true
|
||||
export SOFTWAREUPDATE_SPINNER_SHOWN="true"
|
||||
fi
|
||||
|
||||
SOFTWARE_UPDATE_LIST=$(softwareupdate -l 2> /dev/null || echo "")
|
||||
# Save to cache
|
||||
echo "$SOFTWARE_UPDATE_LIST" > "$cache_file" 2> /dev/null || true
|
||||
|
||||
# Stop spinner
|
||||
if [[ "$show_spinner" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "$SOFTWARE_UPDATE_LIST"
|
||||
}
|
||||
|
||||
check_appstore_updates() {
|
||||
local spinner_started=false
|
||||
if [[ -t 1 ]]; then
|
||||
printf " Checking App Store updates...\r"
|
||||
start_inline_spinner "Checking App Store updates..."
|
||||
spinner_started=true
|
||||
export SOFTWAREUPDATE_SPINNER_SHOWN="external"
|
||||
else
|
||||
echo "Checking App Store updates..."
|
||||
fi
|
||||
|
||||
local update_list=""
|
||||
update_list=$(get_software_updates | grep -v "Software Update Tool" | grep "^\*" | grep -vi "macOS" || echo "")
|
||||
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
unset SOFTWAREUPDATE_SPINNER_SHOWN
|
||||
fi
|
||||
|
||||
local update_count=0
|
||||
if [[ -n "$update_list" ]]; then
|
||||
update_count=$(echo "$update_list" | wc -l | tr -d ' ')
|
||||
fi
|
||||
|
||||
export APPSTORE_UPDATE_COUNT=$update_count
|
||||
|
||||
if [[ $update_count -gt 0 ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} App Store ${YELLOW}${update_count} apps${NC} need update"
|
||||
echo -e " ${GRAY}Run: ${GREEN}softwareupdate -i <label>${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} App Store Up to date"
|
||||
fi
|
||||
}
|
||||
|
||||
check_macos_update() {
|
||||
local spinner_started=false
|
||||
if [[ -t 1 ]]; then
|
||||
printf " Checking macOS updates...\r"
|
||||
start_inline_spinner "Checking macOS updates..."
|
||||
spinner_started=true
|
||||
export SOFTWAREUPDATE_SPINNER_SHOWN="external"
|
||||
else
|
||||
echo "Checking macOS updates..."
|
||||
fi
|
||||
|
||||
# Check for macOS system update using cached list
|
||||
local macos_update=""
|
||||
macos_update=$(get_software_updates | grep -i "macOS" | head -1 || echo "")
|
||||
|
||||
if [[ "$spinner_started" == "true" ]]; then
|
||||
stop_inline_spinner
|
||||
unset SOFTWAREUPDATE_SPINNER_SHOWN
|
||||
fi
|
||||
|
||||
export MACOS_UPDATE_AVAILABLE="false"
|
||||
|
||||
if [[ -n "$macos_update" ]]; then
|
||||
export MACOS_UPDATE_AVAILABLE="true"
|
||||
local version=$(echo "$macos_update" | grep -o '[0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?' | head -1)
|
||||
if [[ -n "$version" ]]; then
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} macOS ${YELLOW}${version} available${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} macOS ${YELLOW}Update available${NC}"
|
||||
fi
|
||||
echo -e " ${GRAY}Run: ${GREEN}softwareupdate -i <label>${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} macOS Up to date"
|
||||
fi
|
||||
}
|
||||
|
||||
check_mole_update() {
|
||||
# Check if Mole has updates
|
||||
# Auto-detect version from mole main script
|
||||
local current_version
|
||||
if [[ -f "${SCRIPT_DIR:-/usr/local/bin}/mole" ]]; then
|
||||
current_version=$(grep '^VERSION=' "${SCRIPT_DIR:-/usr/local/bin}/mole" 2> /dev/null | head -1 | sed 's/VERSION="\(.*\)"/\1/' || echo "unknown")
|
||||
else
|
||||
current_version="${VERSION:-unknown}"
|
||||
fi
|
||||
|
||||
local latest_version=""
|
||||
local cache_file="$CACHE_DIR/mole_version"
|
||||
|
||||
export MOLE_UPDATE_AVAILABLE="false"
|
||||
|
||||
# Check cache first
|
||||
if is_cache_valid "$cache_file"; then
|
||||
latest_version=$(cat "$cache_file" 2> /dev/null || echo "")
|
||||
else
|
||||
# Show spinner while checking
|
||||
if [[ -t 1 ]]; then
|
||||
start_inline_spinner "Checking Mole version..."
|
||||
fi
|
||||
|
||||
# Try to get latest version from GitHub
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
latest_version=$(curl -fsSL https://api.github.com/repos/tw93/mole/releases/latest 2> /dev/null | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/' || echo "")
|
||||
# Save to cache
|
||||
if [[ -n "$latest_version" ]]; then
|
||||
echo "$latest_version" > "$cache_file" 2> /dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Stop spinner
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
fi
|
||||
|
||||
# Normalize version strings (remove leading 'v' or 'V')
|
||||
current_version=$(echo "$current_version" | sed 's/^[vV]//')
|
||||
latest_version=$(echo "$latest_version" | sed 's/^[vV]//')
|
||||
|
||||
if [[ -n "$latest_version" && "$current_version" != "$latest_version" ]]; then
|
||||
# Compare versions
|
||||
if [[ "$(printf '%s\n' "$current_version" "$latest_version" | sort -V | head -1)" == "$current_version" ]]; then
|
||||
export MOLE_UPDATE_AVAILABLE="true"
|
||||
echo -e " ${YELLOW}${ICON_WARNING}${NC} Mole ${YELLOW}${latest_version} available${NC} (current: ${current_version})"
|
||||
echo -e " ${GRAY}Run: ${GREEN}mo update${NC}"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Mole Up to date (${current_version})"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Mole Up to date (${current_version})"
|
||||
fi
|
||||
}
|
||||
|
||||
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
|
||||
get_software_updates > /dev/null 2>&1
|
||||
|
||||
check_appstore_updates
|
||||
check_macos_update
|
||||
check_mole_update
|
||||
}
|
||||
|
||||
get_appstore_update_labels() {
|
||||
get_software_updates | awk '
|
||||
/^\*/ {
|
||||
label=$0
|
||||
sub(/^[[:space:]]*\* Label: */, "", label)
|
||||
sub(/,.*/, "", label)
|
||||
lower=tolower(label)
|
||||
if (index(lower, "macos") == 0) {
|
||||
print label
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
get_macos_update_labels() {
|
||||
get_software_updates | awk '
|
||||
/^\*/ {
|
||||
label=$0
|
||||
sub(/^[[:space:]]*\* Label: */, "", label)
|
||||
sub(/,.*/, "", label)
|
||||
lower=tolower(label)
|
||||
if (index(lower, "macos") != 0) {
|
||||
print label
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
@@ -6,13 +6,13 @@ set -euo pipefail
|
||||
|
||||
# Get script directory and source dependencies
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
source "$SCRIPT_DIR/menu_simple.sh"
|
||||
source "$SCRIPT_DIR/../core/common.sh"
|
||||
source "$SCRIPT_DIR/../ui/menu_simple.sh"
|
||||
|
||||
# Config file path
|
||||
WHITELIST_CONFIG="$HOME/.config/mole/whitelist"
|
||||
|
||||
# Default whitelist patterns defined in lib/common.sh:
|
||||
# Default whitelist patterns defined in lib/core/common.sh:
|
||||
# - DEFAULT_WHITELIST_PATTERNS
|
||||
# - FINDER_METADATA_SENTINEL
|
||||
|
||||
@@ -417,3 +417,282 @@ execute_optimization() {
|
||||
;;
|
||||
esac
|
||||
}
|
||||
#!/bin/bash
|
||||
# System Health Check - Pure Bash Implementation
|
||||
# Replaces optimize-go
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Get memory info in GB
|
||||
get_memory_info() {
|
||||
local total_bytes used_gb total_gb
|
||||
|
||||
# Total memory
|
||||
total_bytes=$(sysctl -n hw.memsize 2> /dev/null || echo "0")
|
||||
total_gb=$(awk "BEGIN {printf \"%.2f\", $total_bytes / (1024*1024*1024)}" 2> /dev/null || echo "0")
|
||||
[[ -z "$total_gb" || "$total_gb" == "" ]] && total_gb="0"
|
||||
|
||||
# Used memory from vm_stat
|
||||
local vm_output active wired compressed page_size
|
||||
vm_output=$(vm_stat 2> /dev/null || echo "")
|
||||
page_size=4096
|
||||
|
||||
active=$(echo "$vm_output" | awk '/Pages active:/ {print $NF}' | tr -d '.' 2> /dev/null || echo "0")
|
||||
wired=$(echo "$vm_output" | awk '/Pages wired down:/ {print $NF}' | tr -d '.' 2> /dev/null || echo "0")
|
||||
compressed=$(echo "$vm_output" | awk '/Pages occupied by compressor:/ {print $NF}' | tr -d '.' 2> /dev/null || echo "0")
|
||||
|
||||
active=${active:-0}
|
||||
wired=${wired:-0}
|
||||
compressed=${compressed:-0}
|
||||
|
||||
local used_bytes=$(((active + wired + compressed) * page_size))
|
||||
used_gb=$(awk "BEGIN {printf \"%.2f\", $used_bytes / (1024*1024*1024)}" 2> /dev/null || echo "0")
|
||||
[[ -z "$used_gb" || "$used_gb" == "" ]] && used_gb="0"
|
||||
|
||||
echo "$used_gb $total_gb"
|
||||
}
|
||||
|
||||
# Get disk info
|
||||
get_disk_info() {
|
||||
local home="${HOME:-/}"
|
||||
local df_output total_gb used_gb used_percent
|
||||
|
||||
df_output=$(df -k "$home" 2> /dev/null | tail -1)
|
||||
|
||||
local total_kb used_kb
|
||||
total_kb=$(echo "$df_output" | awk '{print $2}' 2> /dev/null || echo "0")
|
||||
used_kb=$(echo "$df_output" | awk '{print $3}' 2> /dev/null || echo "0")
|
||||
|
||||
total_kb=${total_kb:-0}
|
||||
used_kb=${used_kb:-0}
|
||||
[[ "$total_kb" == "0" ]] && total_kb=1 # Avoid division by zero
|
||||
|
||||
total_gb=$(awk "BEGIN {printf \"%.2f\", $total_kb / (1024*1024)}" 2> /dev/null || echo "0")
|
||||
used_gb=$(awk "BEGIN {printf \"%.2f\", $used_kb / (1024*1024)}" 2> /dev/null || echo "0")
|
||||
used_percent=$(awk "BEGIN {printf \"%.1f\", ($used_kb / $total_kb) * 100}" 2> /dev/null || echo "0")
|
||||
|
||||
[[ -z "$total_gb" || "$total_gb" == "" ]] && total_gb="0"
|
||||
[[ -z "$used_gb" || "$used_gb" == "" ]] && used_gb="0"
|
||||
[[ -z "$used_percent" || "$used_percent" == "" ]] && used_percent="0"
|
||||
|
||||
echo "$used_gb $total_gb $used_percent"
|
||||
}
|
||||
|
||||
# Get uptime in days
|
||||
get_uptime_days() {
|
||||
local boot_output boot_time uptime_days
|
||||
|
||||
boot_output=$(sysctl -n kern.boottime 2> /dev/null || echo "")
|
||||
boot_time=$(echo "$boot_output" | sed -n 's/.*sec = \([0-9]*\).*/\1/p' 2> /dev/null || echo "")
|
||||
|
||||
if [[ -n "$boot_time" && "$boot_time" =~ ^[0-9]+$ ]]; then
|
||||
local now=$(date +%s 2> /dev/null || echo "0")
|
||||
local uptime_sec=$((now - boot_time))
|
||||
uptime_days=$(awk "BEGIN {printf \"%.1f\", $uptime_sec / 86400}" 2> /dev/null || echo "0")
|
||||
else
|
||||
uptime_days="0"
|
||||
fi
|
||||
|
||||
[[ -z "$uptime_days" || "$uptime_days" == "" ]] && uptime_days="0"
|
||||
echo "$uptime_days"
|
||||
}
|
||||
|
||||
# Get directory size in KB
|
||||
dir_size_kb() {
|
||||
local path="$1"
|
||||
[[ ! -e "$path" ]] && echo "0" && return
|
||||
du -sk "$path" 2> /dev/null | awk '{print $1}' || echo "0"
|
||||
}
|
||||
|
||||
# Format size from KB
|
||||
format_size_kb() {
|
||||
local kb="$1"
|
||||
[[ "$kb" -le 0 ]] && echo "0B" && return
|
||||
|
||||
local mb gb
|
||||
mb=$(awk "BEGIN {printf \"%.1f\", $kb / 1024}")
|
||||
gb=$(awk "BEGIN {printf \"%.2f\", $mb / 1024}")
|
||||
|
||||
if awk "BEGIN {exit !($gb >= 1)}"; then
|
||||
echo "${gb}GB"
|
||||
elif awk "BEGIN {exit !($mb >= 1)}"; then
|
||||
printf "%.0fMB\n" "$mb"
|
||||
else
|
||||
echo "${kb}KB"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check cache size
|
||||
check_cache_refresh() {
|
||||
local cache_dir="$HOME/Library/Caches"
|
||||
local size_kb=$(dir_size_kb "$cache_dir")
|
||||
local desc="Refresh Finder previews, Quick Look, and Safari caches"
|
||||
|
||||
if [[ $size_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$size_kb")
|
||||
desc="Refresh ${size_str} of Finder/Safari caches"
|
||||
fi
|
||||
|
||||
echo "cache_refresh|User Cache Refresh|${desc}|true"
|
||||
}
|
||||
|
||||
# Check Mail downloads
|
||||
check_mail_downloads() {
|
||||
local dirs=(
|
||||
"$HOME/Library/Mail Downloads"
|
||||
"$HOME/Library/Containers/com.apple.mail/Data/Library/Mail Downloads"
|
||||
)
|
||||
|
||||
local total_kb=0
|
||||
for dir in "${dirs[@]}"; do
|
||||
total_kb=$((total_kb + $(dir_size_kb "$dir")))
|
||||
done
|
||||
|
||||
if [[ $total_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$total_kb")
|
||||
echo "mail_downloads|Mail Downloads|Recover ${size_str} of Mail attachments|true"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check saved state
|
||||
check_saved_state() {
|
||||
local state_dir="$HOME/Library/Saved Application State"
|
||||
local size_kb=$(dir_size_kb "$state_dir")
|
||||
|
||||
if [[ $size_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$size_kb")
|
||||
echo "saved_state_cleanup|Saved State|Clear ${size_str} of stale saved states|true"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check swap files
|
||||
check_swap_cleanup() {
|
||||
local total_kb=0
|
||||
local file
|
||||
|
||||
for file in /private/var/vm/swapfile*; do
|
||||
[[ -f "$file" ]] && total_kb=$((total_kb + $(get_file_size "$file") / 1024))
|
||||
done
|
||||
|
||||
if [[ $total_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$total_kb")
|
||||
echo "swap_cleanup|Memory & Swap|Purge swap (${size_str}) & inactive memory|false"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check local snapshots
|
||||
check_local_snapshots() {
|
||||
command -v tmutil > /dev/null 2>&1 || return
|
||||
|
||||
local snapshots
|
||||
snapshots=$(tmutil listlocalsnapshots / 2> /dev/null || echo "")
|
||||
|
||||
local count
|
||||
count=$(echo "$snapshots" | grep -c "com.apple.TimeMachine" 2> /dev/null)
|
||||
count=$(echo "$count" | tr -d ' \n')
|
||||
count=${count:-0}
|
||||
[[ "$count" =~ ^[0-9]+$ ]] && [[ $count -gt 0 ]] && echo "local_snapshots|Local Snapshots|${count} APFS local snapshots detected|true"
|
||||
}
|
||||
|
||||
# Check developer cleanup
|
||||
check_developer_cleanup() {
|
||||
local dirs=(
|
||||
"$HOME/Library/Developer/Xcode/DerivedData"
|
||||
"$HOME/Library/Developer/Xcode/Archives"
|
||||
"$HOME/Library/Developer/Xcode/iOS DeviceSupport"
|
||||
"$HOME/Library/Developer/CoreSimulator/Caches"
|
||||
)
|
||||
|
||||
local total_kb=0
|
||||
for dir in "${dirs[@]}"; do
|
||||
total_kb=$((total_kb + $(dir_size_kb "$dir")))
|
||||
done
|
||||
|
||||
if [[ $total_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$total_kb")
|
||||
echo "developer_cleanup|Developer Cleanup|Recover ${size_str} of Xcode/simulator data|false"
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate JSON output
|
||||
generate_health_json() {
|
||||
# System info
|
||||
read -r mem_used mem_total <<< "$(get_memory_info)"
|
||||
read -r disk_used disk_total disk_percent <<< "$(get_disk_info)"
|
||||
local uptime=$(get_uptime_days)
|
||||
|
||||
# Ensure all values are valid numbers (fallback to 0)
|
||||
mem_used=${mem_used:-0}
|
||||
mem_total=${mem_total:-0}
|
||||
disk_used=${disk_used:-0}
|
||||
disk_total=${disk_total:-0}
|
||||
disk_percent=${disk_percent:-0}
|
||||
uptime=${uptime:-0}
|
||||
|
||||
# Start JSON
|
||||
cat << EOF
|
||||
{
|
||||
"memory_used_gb": $mem_used,
|
||||
"memory_total_gb": $mem_total,
|
||||
"disk_used_gb": $disk_used,
|
||||
"disk_total_gb": $disk_total,
|
||||
"disk_used_percent": $disk_percent,
|
||||
"uptime_days": $uptime,
|
||||
"optimizations": [
|
||||
EOF
|
||||
|
||||
# Collect all optimization items
|
||||
local -a items=()
|
||||
|
||||
# Always-on items
|
||||
items+=('system_maintenance|System Maintenance|Rebuild system databases & flush caches|true')
|
||||
items+=('maintenance_scripts|Maintenance Scripts|Run daily/weekly/monthly scripts & rotate logs|true')
|
||||
items+=('radio_refresh|Bluetooth & Wi-Fi Refresh|Reset wireless preference caches|true')
|
||||
items+=('recent_items|Recent Items|Clear recent apps/documents/servers lists|true')
|
||||
items+=('log_cleanup|Diagnostics Cleanup|Purge old diagnostic & crash logs|true')
|
||||
items+=('startup_cache|Startup Cache Rebuild|Rebuild kext caches & prelinked kernel|true')
|
||||
|
||||
# Conditional items
|
||||
local item
|
||||
item=$(check_cache_refresh || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_mail_downloads || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_saved_state || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_swap_cleanup || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_local_snapshots || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_developer_cleanup || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
|
||||
# Output items as JSON
|
||||
local first=true
|
||||
for item in "${items[@]}"; do
|
||||
IFS='|' read -r action name desc safe <<< "$item"
|
||||
|
||||
[[ "$first" == "true" ]] && first=false || echo ","
|
||||
|
||||
cat << EOF
|
||||
{
|
||||
"category": "system",
|
||||
"name": "$name",
|
||||
"description": "$desc",
|
||||
"action": "$action",
|
||||
"safe": $safe
|
||||
}
|
||||
EOF
|
||||
done
|
||||
|
||||
# Close JSON
|
||||
cat << 'EOF'
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main execution
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
generate_health_json
|
||||
fi
|
||||
@@ -1,279 +0,0 @@
|
||||
#!/bin/bash
|
||||
# System Health Check - Pure Bash Implementation
|
||||
# Replaces optimize-go
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Get memory info in GB
|
||||
get_memory_info() {
|
||||
local total_bytes used_gb total_gb
|
||||
|
||||
# Total memory
|
||||
total_bytes=$(sysctl -n hw.memsize 2> /dev/null || echo "0")
|
||||
total_gb=$(awk "BEGIN {printf \"%.2f\", $total_bytes / (1024*1024*1024)}" 2> /dev/null || echo "0")
|
||||
[[ -z "$total_gb" || "$total_gb" == "" ]] && total_gb="0"
|
||||
|
||||
# Used memory from vm_stat
|
||||
local vm_output active wired compressed page_size
|
||||
vm_output=$(vm_stat 2> /dev/null || echo "")
|
||||
page_size=4096
|
||||
|
||||
active=$(echo "$vm_output" | awk '/Pages active:/ {print $NF}' | tr -d '.' 2> /dev/null || echo "0")
|
||||
wired=$(echo "$vm_output" | awk '/Pages wired down:/ {print $NF}' | tr -d '.' 2> /dev/null || echo "0")
|
||||
compressed=$(echo "$vm_output" | awk '/Pages occupied by compressor:/ {print $NF}' | tr -d '.' 2> /dev/null || echo "0")
|
||||
|
||||
active=${active:-0}
|
||||
wired=${wired:-0}
|
||||
compressed=${compressed:-0}
|
||||
|
||||
local used_bytes=$(((active + wired + compressed) * page_size))
|
||||
used_gb=$(awk "BEGIN {printf \"%.2f\", $used_bytes / (1024*1024*1024)}" 2> /dev/null || echo "0")
|
||||
[[ -z "$used_gb" || "$used_gb" == "" ]] && used_gb="0"
|
||||
|
||||
echo "$used_gb $total_gb"
|
||||
}
|
||||
|
||||
# Get disk info
|
||||
get_disk_info() {
|
||||
local home="${HOME:-/}"
|
||||
local df_output total_gb used_gb used_percent
|
||||
|
||||
df_output=$(df -k "$home" 2> /dev/null | tail -1)
|
||||
|
||||
local total_kb used_kb
|
||||
total_kb=$(echo "$df_output" | awk '{print $2}' 2> /dev/null || echo "0")
|
||||
used_kb=$(echo "$df_output" | awk '{print $3}' 2> /dev/null || echo "0")
|
||||
|
||||
total_kb=${total_kb:-0}
|
||||
used_kb=${used_kb:-0}
|
||||
[[ "$total_kb" == "0" ]] && total_kb=1 # Avoid division by zero
|
||||
|
||||
total_gb=$(awk "BEGIN {printf \"%.2f\", $total_kb / (1024*1024)}" 2> /dev/null || echo "0")
|
||||
used_gb=$(awk "BEGIN {printf \"%.2f\", $used_kb / (1024*1024)}" 2> /dev/null || echo "0")
|
||||
used_percent=$(awk "BEGIN {printf \"%.1f\", ($used_kb / $total_kb) * 100}" 2> /dev/null || echo "0")
|
||||
|
||||
[[ -z "$total_gb" || "$total_gb" == "" ]] && total_gb="0"
|
||||
[[ -z "$used_gb" || "$used_gb" == "" ]] && used_gb="0"
|
||||
[[ -z "$used_percent" || "$used_percent" == "" ]] && used_percent="0"
|
||||
|
||||
echo "$used_gb $total_gb $used_percent"
|
||||
}
|
||||
|
||||
# Get uptime in days
|
||||
get_uptime_days() {
|
||||
local boot_output boot_time uptime_days
|
||||
|
||||
boot_output=$(sysctl -n kern.boottime 2> /dev/null || echo "")
|
||||
boot_time=$(echo "$boot_output" | sed -n 's/.*sec = \([0-9]*\).*/\1/p' 2> /dev/null || echo "")
|
||||
|
||||
if [[ -n "$boot_time" && "$boot_time" =~ ^[0-9]+$ ]]; then
|
||||
local now=$(date +%s 2> /dev/null || echo "0")
|
||||
local uptime_sec=$((now - boot_time))
|
||||
uptime_days=$(awk "BEGIN {printf \"%.1f\", $uptime_sec / 86400}" 2> /dev/null || echo "0")
|
||||
else
|
||||
uptime_days="0"
|
||||
fi
|
||||
|
||||
[[ -z "$uptime_days" || "$uptime_days" == "" ]] && uptime_days="0"
|
||||
echo "$uptime_days"
|
||||
}
|
||||
|
||||
# Get directory size in KB
|
||||
dir_size_kb() {
|
||||
local path="$1"
|
||||
[[ ! -e "$path" ]] && echo "0" && return
|
||||
du -sk "$path" 2> /dev/null | awk '{print $1}' || echo "0"
|
||||
}
|
||||
|
||||
# Format size from KB
|
||||
format_size_kb() {
|
||||
local kb="$1"
|
||||
[[ "$kb" -le 0 ]] && echo "0B" && return
|
||||
|
||||
local mb gb
|
||||
mb=$(awk "BEGIN {printf \"%.1f\", $kb / 1024}")
|
||||
gb=$(awk "BEGIN {printf \"%.2f\", $mb / 1024}")
|
||||
|
||||
if awk "BEGIN {exit !($gb >= 1)}"; then
|
||||
echo "${gb}GB"
|
||||
elif awk "BEGIN {exit !($mb >= 1)}"; then
|
||||
printf "%.0fMB\n" "$mb"
|
||||
else
|
||||
echo "${kb}KB"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check cache size
|
||||
check_cache_refresh() {
|
||||
local cache_dir="$HOME/Library/Caches"
|
||||
local size_kb=$(dir_size_kb "$cache_dir")
|
||||
local desc="Refresh Finder previews, Quick Look, and Safari caches"
|
||||
|
||||
if [[ $size_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$size_kb")
|
||||
desc="Refresh ${size_str} of Finder/Safari caches"
|
||||
fi
|
||||
|
||||
echo "cache_refresh|User Cache Refresh|${desc}|true"
|
||||
}
|
||||
|
||||
# Check Mail downloads
|
||||
check_mail_downloads() {
|
||||
local dirs=(
|
||||
"$HOME/Library/Mail Downloads"
|
||||
"$HOME/Library/Containers/com.apple.mail/Data/Library/Mail Downloads"
|
||||
)
|
||||
|
||||
local total_kb=0
|
||||
for dir in "${dirs[@]}"; do
|
||||
total_kb=$((total_kb + $(dir_size_kb "$dir")))
|
||||
done
|
||||
|
||||
if [[ $total_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$total_kb")
|
||||
echo "mail_downloads|Mail Downloads|Recover ${size_str} of Mail attachments|true"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check saved state
|
||||
check_saved_state() {
|
||||
local state_dir="$HOME/Library/Saved Application State"
|
||||
local size_kb=$(dir_size_kb "$state_dir")
|
||||
|
||||
if [[ $size_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$size_kb")
|
||||
echo "saved_state_cleanup|Saved State|Clear ${size_str} of stale saved states|true"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check swap files
|
||||
check_swap_cleanup() {
|
||||
local total_kb=0
|
||||
local file
|
||||
|
||||
for file in /private/var/vm/swapfile*; do
|
||||
[[ -f "$file" ]] && total_kb=$((total_kb + $(get_file_size "$file") / 1024))
|
||||
done
|
||||
|
||||
if [[ $total_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$total_kb")
|
||||
echo "swap_cleanup|Memory & Swap|Purge swap (${size_str}) & inactive memory|false"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check local snapshots
|
||||
check_local_snapshots() {
|
||||
command -v tmutil > /dev/null 2>&1 || return
|
||||
|
||||
local snapshots
|
||||
snapshots=$(tmutil listlocalsnapshots / 2> /dev/null || echo "")
|
||||
|
||||
local count
|
||||
count=$(echo "$snapshots" | grep -c "com.apple.TimeMachine" 2> /dev/null)
|
||||
count=$(echo "$count" | tr -d ' \n')
|
||||
count=${count:-0}
|
||||
[[ "$count" =~ ^[0-9]+$ ]] && [[ $count -gt 0 ]] && echo "local_snapshots|Local Snapshots|${count} APFS local snapshots detected|true"
|
||||
}
|
||||
|
||||
# Check developer cleanup
|
||||
check_developer_cleanup() {
|
||||
local dirs=(
|
||||
"$HOME/Library/Developer/Xcode/DerivedData"
|
||||
"$HOME/Library/Developer/Xcode/Archives"
|
||||
"$HOME/Library/Developer/Xcode/iOS DeviceSupport"
|
||||
"$HOME/Library/Developer/CoreSimulator/Caches"
|
||||
)
|
||||
|
||||
local total_kb=0
|
||||
for dir in "${dirs[@]}"; do
|
||||
total_kb=$((total_kb + $(dir_size_kb "$dir")))
|
||||
done
|
||||
|
||||
if [[ $total_kb -gt 0 ]]; then
|
||||
local size_str=$(format_size_kb "$total_kb")
|
||||
echo "developer_cleanup|Developer Cleanup|Recover ${size_str} of Xcode/simulator data|false"
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate JSON output
|
||||
generate_health_json() {
|
||||
# System info
|
||||
read -r mem_used mem_total <<< "$(get_memory_info)"
|
||||
read -r disk_used disk_total disk_percent <<< "$(get_disk_info)"
|
||||
local uptime=$(get_uptime_days)
|
||||
|
||||
# Ensure all values are valid numbers (fallback to 0)
|
||||
mem_used=${mem_used:-0}
|
||||
mem_total=${mem_total:-0}
|
||||
disk_used=${disk_used:-0}
|
||||
disk_total=${disk_total:-0}
|
||||
disk_percent=${disk_percent:-0}
|
||||
uptime=${uptime:-0}
|
||||
|
||||
# Start JSON
|
||||
cat << EOF
|
||||
{
|
||||
"memory_used_gb": $mem_used,
|
||||
"memory_total_gb": $mem_total,
|
||||
"disk_used_gb": $disk_used,
|
||||
"disk_total_gb": $disk_total,
|
||||
"disk_used_percent": $disk_percent,
|
||||
"uptime_days": $uptime,
|
||||
"optimizations": [
|
||||
EOF
|
||||
|
||||
# Collect all optimization items
|
||||
local -a items=()
|
||||
|
||||
# Always-on items
|
||||
items+=('system_maintenance|System Maintenance|Rebuild system databases & flush caches|true')
|
||||
items+=('maintenance_scripts|Maintenance Scripts|Run daily/weekly/monthly scripts & rotate logs|true')
|
||||
items+=('radio_refresh|Bluetooth & Wi-Fi Refresh|Reset wireless preference caches|true')
|
||||
items+=('recent_items|Recent Items|Clear recent apps/documents/servers lists|true')
|
||||
items+=('log_cleanup|Diagnostics Cleanup|Purge old diagnostic & crash logs|true')
|
||||
items+=('startup_cache|Startup Cache Rebuild|Rebuild kext caches & prelinked kernel|true')
|
||||
|
||||
# Conditional items
|
||||
local item
|
||||
item=$(check_cache_refresh || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_mail_downloads || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_saved_state || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_swap_cleanup || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_local_snapshots || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
item=$(check_developer_cleanup || true)
|
||||
[[ -n "$item" ]] && items+=("$item")
|
||||
|
||||
# Output items as JSON
|
||||
local first=true
|
||||
for item in "${items[@]}"; do
|
||||
IFS='|' read -r action name desc safe <<< "$item"
|
||||
|
||||
[[ "$first" == "true" ]] && first=false || echo ","
|
||||
|
||||
cat << EOF
|
||||
{
|
||||
"category": "system",
|
||||
"name": "$name",
|
||||
"description": "$desc",
|
||||
"action": "$action",
|
||||
"safe": $safe
|
||||
}
|
||||
EOF
|
||||
done
|
||||
|
||||
# Close JSON
|
||||
cat << 'EOF'
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main execution
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
generate_health_json
|
||||
fi
|
||||
@@ -4,7 +4,7 @@ set -euo pipefail
|
||||
|
||||
# Ensure common.sh is loaded
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
[[ -z "${MOLE_COMMON_LOADED:-}" ]] && source "$SCRIPT_DIR/lib/common.sh"
|
||||
[[ -z "${MOLE_COMMON_LOADED:-}" ]] && source "$SCRIPT_DIR/lib/core/common.sh"
|
||||
|
||||
# Batch uninstall functionality with minimal confirmations
|
||||
# Replaces the overly verbose individual confirmation approach
|
||||
@@ -42,7 +42,7 @@ decode_file_list() {
|
||||
echo "$decoded"
|
||||
return 0
|
||||
}
|
||||
# Note: find_app_files() and calculate_total_size() functions now in lib/common.sh
|
||||
# Note: find_app_files() and calculate_total_size() functions now in lib/core/common.sh
|
||||
|
||||
# Batch uninstall with single confirmation
|
||||
batch_uninstall_applications() {
|
||||
4
mole
4
mole
@@ -19,10 +19,10 @@ set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Source common functions
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
source "$SCRIPT_DIR/lib/core/common.sh"
|
||||
|
||||
# Version info
|
||||
VERSION="1.11.6"
|
||||
VERSION="1.11.7"
|
||||
MOLE_TAGLINE="can dig deep to clean your Mac."
|
||||
|
||||
# Check if Touch ID is already configured
|
||||
|
||||
@@ -69,7 +69,7 @@ TOTAL_CHECKS=0
|
||||
|
||||
# Check 1: Keyboard input handling (restored to 1s for reliability)
|
||||
((TOTAL_CHECKS++))
|
||||
if grep -q "read -r -s -n 1 -t 1" lib/common.sh; then
|
||||
if grep -q "read -r -s -n 1 -t 1" lib/core/common.sh; then
|
||||
echo -e "${GREEN} ✓ Keyboard timeout properly configured (1s)${NC}"
|
||||
((OPTIMIZATION_SCORE++))
|
||||
else
|
||||
@@ -78,7 +78,7 @@ fi
|
||||
|
||||
# Check 2: Single-pass drain_pending_input
|
||||
((TOTAL_CHECKS++))
|
||||
DRAIN_PASSES=$(grep -c "while IFS= read -r -s -n 1" lib/common.sh || echo 0)
|
||||
DRAIN_PASSES=$(grep -c "while IFS= read -r -s -n 1" lib/core/common.sh || echo 0)
|
||||
if [[ $DRAIN_PASSES -eq 1 ]]; then
|
||||
echo -e "${GREEN} ✓ drain_pending_input optimized (single-pass)${NC}"
|
||||
((OPTIMIZATION_SCORE++))
|
||||
@@ -88,7 +88,7 @@ fi
|
||||
|
||||
# Check 3: Log rotation once per session
|
||||
((TOTAL_CHECKS++))
|
||||
if grep -q "rotate_log_once" lib/common.sh && ! grep "rotate_log()" lib/common.sh | grep -v "rotate_log_once" > /dev/null 2>&1; then
|
||||
if grep -q "rotate_log_once" lib/core/common.sh && ! grep "rotate_log()" lib/core/common.sh | grep -v "rotate_log_once" > /dev/null 2>&1; then
|
||||
echo -e "${GREEN} ✓ Log rotation optimized (once per session)${NC}"
|
||||
((OPTIMIZATION_SCORE++))
|
||||
else
|
||||
|
||||
@@ -24,8 +24,8 @@ teardown_file() {
|
||||
}
|
||||
|
||||
setup() {
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/clean_caches.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/clean/caches.sh"
|
||||
|
||||
# Clean permission flag for each test
|
||||
rm -f "$HOME/.cache/mole/permissions_granted"
|
||||
@@ -34,7 +34,7 @@ setup() {
|
||||
# Test check_tcc_permissions in non-interactive mode
|
||||
@test "check_tcc_permissions skips in non-interactive mode" {
|
||||
# Redirect stdin to simulate non-TTY
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; check_tcc_permissions" < /dev/null
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; check_tcc_permissions" < /dev/null
|
||||
[ "$status" -eq 0 ]
|
||||
# Should not create permission flag in non-interactive mode
|
||||
[[ ! -f "$HOME/.cache/mole/permissions_granted" ]]
|
||||
@@ -47,7 +47,7 @@ setup() {
|
||||
touch "$HOME/.cache/mole/permissions_granted"
|
||||
|
||||
# Even in TTY mode, should skip if flag exists
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; [[ -t 1 ]] || true; check_tcc_permissions"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; [[ -t 1 ]] || true; check_tcc_permissions"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@@ -66,13 +66,13 @@ setup() {
|
||||
[[ -d "$HOME/.cache/mole" ]]
|
||||
|
||||
# Function should handle missing directories gracefully
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; check_tcc_permissions < /dev/null"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; check_tcc_permissions < /dev/null"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
# Test clean_service_worker_cache with non-existent path
|
||||
@test "clean_service_worker_cache returns early when path doesn't exist" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; clean_service_worker_cache 'TestBrowser' '/nonexistent/path'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; clean_service_worker_cache 'TestBrowser' '/nonexistent/path'"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ setup() {
|
||||
local test_cache="$HOME/test_sw_cache"
|
||||
mkdir -p "$test_cache"
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/clean_caches.sh'; clean_service_worker_cache 'TestBrowser' '$test_cache'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/clean/caches.sh'; clean_service_worker_cache 'TestBrowser' '$test_cache'"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
rm -rf "$test_cache"
|
||||
@@ -100,8 +100,8 @@ setup() {
|
||||
run bash -c "
|
||||
export DRY_RUN=true
|
||||
export PROTECTED_SW_DOMAINS=(capcut.com photopea.com)
|
||||
source '$PROJECT_ROOT/lib/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean_caches.sh'
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean/caches.sh'
|
||||
clean_service_worker_cache 'TestBrowser' '$test_cache'
|
||||
"
|
||||
[ "$status" -eq 0 ]
|
||||
@@ -124,8 +124,8 @@ setup() {
|
||||
|
||||
run bash -c "
|
||||
export DRY_RUN=true
|
||||
source '$PROJECT_ROOT/lib/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean_caches.sh'
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean/caches.sh'
|
||||
clean_project_caches
|
||||
"
|
||||
[ "$status" -eq 0 ]
|
||||
@@ -147,8 +147,8 @@ setup() {
|
||||
|
||||
# Should complete within reasonable time even with slow find
|
||||
run timeout 15 bash -c "
|
||||
source '$PROJECT_ROOT/lib/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean_caches.sh'
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean/caches.sh'
|
||||
clean_project_caches
|
||||
"
|
||||
# Either succeeds or times out gracefully (both acceptable)
|
||||
@@ -168,8 +168,8 @@ setup() {
|
||||
# We can't easily test this without mocking, but we can verify no crashes
|
||||
run bash -c "
|
||||
export DRY_RUN=true
|
||||
source '$PROJECT_ROOT/lib/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean_caches.sh'
|
||||
source '$PROJECT_ROOT/lib/core/common.sh'
|
||||
source '$PROJECT_ROOT/lib/clean/caches.sh'
|
||||
clean_project_caches
|
||||
"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
@@ -46,8 +46,11 @@ setup() {
|
||||
}
|
||||
|
||||
@test "touchid status reports current configuration" {
|
||||
# Don't test actual Touch ID config (system-dependent, may trigger prompts)
|
||||
# Just verify the command exists and can run
|
||||
run env HOME="$HOME" "$PROJECT_ROOT/mole" touchid status
|
||||
[ "$status" -eq 0 ]
|
||||
# Should output either "enabled" or "not configured" message
|
||||
[[ "$output" == *"Touch ID"* ]]
|
||||
}
|
||||
|
||||
|
||||
@@ -30,13 +30,13 @@ teardown() {
|
||||
}
|
||||
|
||||
@test "mo_spinner_chars returns default sequence when unset" {
|
||||
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; mo_spinner_chars")"
|
||||
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; mo_spinner_chars")"
|
||||
[ "$result" = "|/-\\" ]
|
||||
}
|
||||
|
||||
@test "mo_spinner_chars respects MO_SPINNER_CHARS override" {
|
||||
export MO_SPINNER_CHARS="abcd"
|
||||
result="$(HOME="$HOME" MO_SPINNER_CHARS="$MO_SPINNER_CHARS" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; mo_spinner_chars")"
|
||||
result="$(HOME="$HOME" MO_SPINNER_CHARS="$MO_SPINNER_CHARS" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; mo_spinner_chars")"
|
||||
[ "$result" = "abcd" ]
|
||||
}
|
||||
|
||||
@@ -45,19 +45,19 @@ teardown() {
|
||||
if [[ "$(uname -m)" == "arm64" ]]; then
|
||||
expected="Apple Silicon"
|
||||
fi
|
||||
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; detect_architecture")"
|
||||
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; detect_architecture")"
|
||||
[ "$result" = "$expected" ]
|
||||
}
|
||||
|
||||
@test "get_free_space returns a non-empty value" {
|
||||
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; get_free_space")"
|
||||
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; get_free_space")"
|
||||
[[ -n "$result" ]]
|
||||
}
|
||||
|
||||
@test "log_info prints message and appends to log file" {
|
||||
local message="Informational message from test"
|
||||
local stdout_output
|
||||
stdout_output="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; log_info '$message'")"
|
||||
stdout_output="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; log_info '$message'")"
|
||||
[[ "$stdout_output" == *"$message"* ]]
|
||||
|
||||
local log_file="$HOME/.config/mole/mole.log"
|
||||
@@ -69,7 +69,7 @@ teardown() {
|
||||
local message="Something went wrong"
|
||||
local stderr_file="$HOME/log_error_stderr.txt"
|
||||
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; log_error '$message' 1>/dev/null 2>'$stderr_file'"
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; log_error '$message' 1>/dev/null 2>'$stderr_file'"
|
||||
|
||||
[[ -s "$stderr_file" ]]
|
||||
grep -q "$message" "$stderr_file"
|
||||
@@ -86,18 +86,18 @@ teardown() {
|
||||
dd if=/dev/zero of="$log_file" bs=1024 count=1100 2> /dev/null
|
||||
|
||||
# First call should rotate
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'"
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'"
|
||||
[[ -f "${log_file}.old" ]]
|
||||
|
||||
# Verify MOLE_LOG_ROTATED was set (rotation happened)
|
||||
result=$(HOME="$HOME" MOLE_LOG_ROTATED=1 bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; echo \$MOLE_LOG_ROTATED")
|
||||
result=$(HOME="$HOME" MOLE_LOG_ROTATED=1 bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; echo \$MOLE_LOG_ROTATED")
|
||||
[[ "$result" == "1" ]]
|
||||
}
|
||||
|
||||
@test "drain_pending_input clears stdin buffer" {
|
||||
# Test that drain_pending_input doesn't hang (using background job with timeout)
|
||||
result=$(
|
||||
(echo -e "test\ninput" | HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; drain_pending_input; echo done") &
|
||||
(echo -e "test\ninput" | HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; drain_pending_input; echo done") &
|
||||
pid=$!
|
||||
sleep 2
|
||||
if kill -0 "$pid" 2> /dev/null; then
|
||||
@@ -114,7 +114,7 @@ teardown() {
|
||||
@test "bytes_to_human converts byte counts into readable units" {
|
||||
output="$(
|
||||
HOME="$HOME" bash --noprofile --norc << 'EOF'
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
bytes_to_human 512
|
||||
bytes_to_human 2048
|
||||
bytes_to_human $((5 * 1024 * 1024))
|
||||
@@ -135,7 +135,7 @@ EOF
|
||||
|
||||
@test "create_temp_file and create_temp_dir are tracked and cleaned" {
|
||||
HOME="$HOME" bash --noprofile --norc << 'EOF'
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
create_temp_file > "$HOME/temp_file_path.txt"
|
||||
create_temp_dir > "$HOME/temp_dir_path.txt"
|
||||
cleanup_temp_files
|
||||
@@ -151,20 +151,20 @@ EOF
|
||||
|
||||
@test "should_protect_data protects system and critical apps" {
|
||||
# System apps should be protected
|
||||
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; should_protect_data 'com.apple.Safari' && echo 'protected' || echo 'not-protected'")
|
||||
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_data 'com.apple.Safari' && echo 'protected' || echo 'not-protected'")
|
||||
[ "$result" = "protected" ]
|
||||
|
||||
# Critical network apps should be protected
|
||||
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; should_protect_data 'com.clash.app' && echo 'protected' || echo 'not-protected'")
|
||||
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_data 'com.clash.app' && echo 'protected' || echo 'not-protected'")
|
||||
[ "$result" = "protected" ]
|
||||
|
||||
# Regular apps should not be protected
|
||||
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; should_protect_data 'com.example.RegularApp' && echo 'protected' || echo 'not-protected'")
|
||||
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_data 'com.example.RegularApp' && echo 'protected' || echo 'not-protected'")
|
||||
[ "$result" = "not-protected" ]
|
||||
}
|
||||
|
||||
@test "print_summary_block formats output correctly" {
|
||||
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/common.sh'; print_summary_block 'success' 'Test Summary' 'Detail 1' 'Detail 2'")
|
||||
result=$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; print_summary_block 'success' 'Test Summary' 'Detail 1' 'Detail 2'")
|
||||
[[ "$result" == *"Test Summary"* ]]
|
||||
[[ "$result" == *"Detail 1"* ]]
|
||||
[[ "$result" == *"Detail 2"* ]]
|
||||
@@ -173,7 +173,7 @@ EOF
|
||||
@test "start_inline_spinner and stop_inline_spinner work in non-TTY" {
|
||||
# Should not hang in non-interactive mode
|
||||
result=$(HOME="$HOME" bash --noprofile --norc << 'EOF'
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Testing..."
|
||||
sleep 0.1
|
||||
stop_inline_spinner
|
||||
|
||||
@@ -32,7 +32,7 @@ setup() {
|
||||
run bash --noprofile --norc -c '
|
||||
set -euo pipefail
|
||||
PATH="/usr/bin:/bin"
|
||||
source "'"$PROJECT_ROOT"'/lib/common.sh"
|
||||
source "'"$PROJECT_ROOT"'/lib/core/common.sh"
|
||||
run_with_timeout 1 sleep 0.1
|
||||
'
|
||||
[ "$status" -eq 0 ]
|
||||
@@ -42,7 +42,7 @@ setup() {
|
||||
run bash --noprofile --norc -c '
|
||||
set -euo pipefail
|
||||
PATH="/usr/bin:/bin"
|
||||
source "'"$PROJECT_ROOT"'/lib/common.sh"
|
||||
source "'"$PROJECT_ROOT"'/lib/core/common.sh"
|
||||
run_with_timeout 1 sleep 5
|
||||
'
|
||||
[ "$status" -eq 124 ]
|
||||
@@ -56,8 +56,8 @@ setup() {
|
||||
|
||||
run env HOME="$HOME" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||
# Mock sudo and defaults to avoid system changes
|
||||
sudo() { return 0; }
|
||||
defaults() { return 0; }
|
||||
@@ -74,8 +74,8 @@ EOF
|
||||
|
||||
run env HOME="$HOME" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||
sudo() { return 0; }
|
||||
defaults() { return 0; }
|
||||
export -f sudo defaults
|
||||
@@ -96,8 +96,8 @@ EOF
|
||||
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||
opt_saved_state_cleanup
|
||||
EOF
|
||||
|
||||
@@ -109,8 +109,8 @@ EOF
|
||||
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||
opt_saved_state_cleanup
|
||||
EOF
|
||||
|
||||
@@ -124,8 +124,8 @@ EOF
|
||||
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||
# Mock qlmanage and cleanup_path to avoid system calls
|
||||
qlmanage() { return 0; }
|
||||
cleanup_path() {
|
||||
@@ -148,8 +148,8 @@ EOF
|
||||
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||
# MOLE_MAIL_DOWNLOADS_MIN_KB is readonly, defaults to 5120 KB (~5MB)
|
||||
opt_mail_downloads
|
||||
EOF
|
||||
@@ -170,8 +170,8 @@ EOF
|
||||
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||
# MOLE_MAIL_DOWNLOADS_MIN_KB and MOLE_LOG_AGE_DAYS are readonly constants
|
||||
opt_mail_downloads
|
||||
EOF
|
||||
@@ -182,8 +182,8 @@ EOF
|
||||
@test "_opt_get_dir_size_kb returns zero for missing directory" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||
size=$(_opt_get_dir_size_kb "/nonexistent/path")
|
||||
echo "$size"
|
||||
EOF
|
||||
@@ -198,8 +198,8 @@ EOF
|
||||
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimization_tasks.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||
size=$(_opt_get_dir_size_kb "$HOME/test_size")
|
||||
echo "$size"
|
||||
EOF
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bats
|
||||
# Tests for safe_* functions in lib/common.sh
|
||||
# Tests for safe_* functions in lib/core/common.sh
|
||||
|
||||
setup_file() {
|
||||
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
|
||||
@@ -22,7 +22,7 @@ teardown_file() {
|
||||
}
|
||||
|
||||
setup() {
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
TEST_DIR="$HOME/test_safe_functions"
|
||||
mkdir -p "$TEST_DIR"
|
||||
}
|
||||
@@ -33,39 +33,39 @@ teardown() {
|
||||
|
||||
# Test validate_path_for_deletion
|
||||
@test "validate_path_for_deletion rejects empty path" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion ''"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion ''"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "validate_path_for_deletion rejects relative path" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion 'relative/path'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion 'relative/path'"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "validate_path_for_deletion rejects path traversal" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '/tmp/../etc'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/tmp/../etc'"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "validate_path_for_deletion rejects system directories" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '/System'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/System'"
|
||||
[ "$status" -eq 1 ]
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '/usr/bin'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/usr/bin'"
|
||||
[ "$status" -eq 1 ]
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '/etc'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '/etc'"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "validate_path_for_deletion accepts valid path" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; validate_path_for_deletion '$TEST_DIR/valid'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; validate_path_for_deletion '$TEST_DIR/valid'"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
# Test safe_remove
|
||||
@test "safe_remove validates path before deletion" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '/System/test' 2>&1"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '/System/test' 2>&1"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ teardown() {
|
||||
local test_file="$TEST_DIR/test_file.txt"
|
||||
echo "test" > "$test_file"
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '$test_file' true"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '$test_file' true"
|
||||
[ "$status" -eq 0 ]
|
||||
[ ! -f "$test_file" ]
|
||||
}
|
||||
@@ -83,19 +83,19 @@ teardown() {
|
||||
mkdir -p "$test_subdir"
|
||||
touch "$test_subdir/file.txt"
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '$test_subdir' true"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '$test_subdir' true"
|
||||
[ "$status" -eq 0 ]
|
||||
[ ! -d "$test_subdir" ]
|
||||
}
|
||||
|
||||
@test "safe_remove handles non-existent path gracefully" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '$TEST_DIR/nonexistent' true"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '$TEST_DIR/nonexistent' true"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "safe_remove in silent mode suppresses error output" {
|
||||
# Try to remove system directory in silent mode
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_remove '/System/test' true 2>&1"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '/System/test' true 2>&1"
|
||||
[ "$status" -eq 1 ]
|
||||
# Should not output error in silent mode
|
||||
}
|
||||
@@ -103,7 +103,7 @@ teardown() {
|
||||
|
||||
# Test safe_find_delete
|
||||
@test "safe_find_delete validates base directory" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_find_delete '/nonexistent' '*.tmp' 7 'f' 2>&1"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_find_delete '/nonexistent' '*.tmp' 7 'f' 2>&1"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ teardown() {
|
||||
mkdir -p "$real_dir"
|
||||
ln -s "$real_dir" "$link_dir"
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_find_delete '$link_dir' '*.tmp' 7 'f' 2>&1"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_find_delete '$link_dir' '*.tmp' 7 'f' 2>&1"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"symlink"* ]]
|
||||
|
||||
@@ -121,7 +121,7 @@ teardown() {
|
||||
}
|
||||
|
||||
@test "safe_find_delete validates type filter" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_find_delete '$TEST_DIR' '*.tmp' 7 'x' 2>&1"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_find_delete '$TEST_DIR' '*.tmp' 7 'x' 2>&1"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"Invalid type filter"* ]]
|
||||
}
|
||||
@@ -137,21 +137,21 @@ teardown() {
|
||||
# Make old_file 8 days old (requires touch -t)
|
||||
touch -t "$(date -v-8d '+%Y%m%d%H%M.%S' 2>/dev/null || date -d '8 days ago' '+%Y%m%d%H%M.%S')" "$old_file" 2>/dev/null || true
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; safe_find_delete '$TEST_DIR' '*.tmp' 7 'f'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_find_delete '$TEST_DIR' '*.tmp' 7 'f'"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
# Test MOLE constants are defined
|
||||
@test "MOLE_* constants are defined" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; echo \$MOLE_TEMP_FILE_AGE_DAYS"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; echo \$MOLE_TEMP_FILE_AGE_DAYS"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "7" ]
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; echo \$MOLE_MAX_PARALLEL_JOBS"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; echo \$MOLE_MAX_PARALLEL_JOBS"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "15" ]
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; echo \$MOLE_TM_BACKUP_SAFE_HOURS"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; echo \$MOLE_TM_BACKUP_SAFE_HOURS"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "48" ]
|
||||
}
|
||||
|
||||
@@ -55,12 +55,16 @@ setup() {
|
||||
|
||||
@test "build-analyze.sh detects missing Go toolchain" {
|
||||
if command -v go > /dev/null 2>&1; then
|
||||
skip "Go is installed, cannot test missing toolchain"
|
||||
# Go is installed, verify script doesn't error out
|
||||
# (Don't actually build - too slow)
|
||||
run bash -c "grep -q 'go build' '$PROJECT_ROOT/scripts/build-analyze.sh'"
|
||||
[ "$status" -eq 0 ]
|
||||
else
|
||||
# Go is missing, verify proper error handling
|
||||
run "$PROJECT_ROOT/scripts/build-analyze.sh"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"Go not installed"* ]]
|
||||
fi
|
||||
|
||||
run "$PROJECT_ROOT/scripts/build-analyze.sh"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"Go not installed"* ]]
|
||||
}
|
||||
|
||||
@test "build-analyze.sh has version info support" {
|
||||
|
||||
@@ -7,8 +7,8 @@ setup_file() {
|
||||
|
||||
setup() {
|
||||
# Source common.sh first (required by sudo_manager)
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/sudo_manager.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/core/sudo.sh"
|
||||
}
|
||||
|
||||
# Test sudo session detection
|
||||
@@ -33,7 +33,7 @@ setup() {
|
||||
export -f sudo
|
||||
|
||||
# These should not crash even without real sudo
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; has_sudo_session"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; has_sudo_session"
|
||||
[ "$status" -eq 1 ] # Expected: no sudo session
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ setup() {
|
||||
|
||||
# Start keepalive (will run in background)
|
||||
local pid
|
||||
pid=$(bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; _start_sudo_keepalive")
|
||||
pid=$(bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; _start_sudo_keepalive")
|
||||
|
||||
# Should return a PID (number)
|
||||
[[ "$pid" =~ ^[0-9]+$ ]]
|
||||
@@ -63,10 +63,10 @@ setup() {
|
||||
|
||||
# Test _stop_sudo_keepalive
|
||||
@test "_stop_sudo_keepalive handles invalid PID gracefully" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; _stop_sudo_keepalive ''"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; _stop_sudo_keepalive ''"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; _stop_sudo_keepalive '99999'"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; _stop_sudo_keepalive '99999'"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@@ -77,12 +77,12 @@ setup() {
|
||||
# Set a fake PID
|
||||
export MOLE_SUDO_KEEPALIVE_PID="99999"
|
||||
|
||||
run bash -c "export MOLE_SUDO_KEEPALIVE_PID=99999; source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; stop_sudo_session"
|
||||
run bash -c "export MOLE_SUDO_KEEPALIVE_PID=99999; source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; stop_sudo_session"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
# Test global state management
|
||||
@test "sudo manager initializes global state correctly" {
|
||||
result=$(bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/sudo_manager.sh'; echo \$MOLE_SUDO_ESTABLISHED")
|
||||
result=$(bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/core/sudo.sh'; echo \$MOLE_SUDO_ESTABLISHED")
|
||||
[[ "$result" == "false" ]] || [[ -z "$result" ]]
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ create_app_artifacts() {
|
||||
result="$(
|
||||
HOME="$HOME" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
find_app_files "com.example.TestApp" "TestApp"
|
||||
EOF
|
||||
)"
|
||||
@@ -62,7 +62,7 @@ EOF
|
||||
result="$(
|
||||
HOME="$HOME" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
files="$(printf '%s\n%s\n' "$HOME/sized/file1" "$HOME/sized/file2")"
|
||||
calculate_total_size "$files"
|
||||
EOF
|
||||
@@ -77,8 +77,8 @@ EOF
|
||||
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall.sh"
|
||||
|
||||
# Test stubs
|
||||
request_sudo_access() { return 0; }
|
||||
@@ -119,8 +119,8 @@ EOF
|
||||
@test "decode_file_list validates base64 encoding" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall.sh"
|
||||
|
||||
# Valid base64 encoded path list
|
||||
valid_data=$(printf '/path/one\n/path/two' | base64)
|
||||
@@ -134,8 +134,8 @@ EOF
|
||||
@test "decode_file_list rejects invalid base64" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall.sh"
|
||||
|
||||
# Invalid base64 - function should return empty and fail
|
||||
if result=$(decode_file_list "not-valid-base64!!!" "TestApp" 2>/dev/null); then
|
||||
@@ -153,8 +153,8 @@ EOF
|
||||
@test "decode_file_list handles empty input" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall.sh"
|
||||
|
||||
# Empty base64
|
||||
empty_data=$(printf '' | base64)
|
||||
@@ -169,8 +169,8 @@ EOF
|
||||
@test "decode_file_list rejects non-absolute paths" {
|
||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||
set -euo pipefail
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall_batch.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/uninstall.sh"
|
||||
|
||||
# Relative path - function should reject it
|
||||
bad_data=$(printf 'relative/path' | base64)
|
||||
|
||||
@@ -22,8 +22,8 @@ teardown_file() {
|
||||
}
|
||||
|
||||
setup() {
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/update_manager.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/manage/update.sh"
|
||||
}
|
||||
|
||||
# Test brew_has_outdated function
|
||||
@@ -33,7 +33,7 @@ setup() {
|
||||
}
|
||||
export -f brew
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; brew_has_outdated"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; brew_has_outdated"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ setup() {
|
||||
}
|
||||
export -f brew
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; brew_has_outdated"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; brew_has_outdated"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@@ -64,30 +64,30 @@ setup() {
|
||||
}
|
||||
export -f brew
|
||||
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; brew_has_outdated cask"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; brew_has_outdated cask"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
# Test format_brew_update_label function
|
||||
@test "format_brew_update_label returns empty when no updates" {
|
||||
result=$(BREW_OUTDATED_COUNT=0 bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; format_brew_update_label")
|
||||
result=$(BREW_OUTDATED_COUNT=0 bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; format_brew_update_label")
|
||||
[[ -z "$result" ]]
|
||||
}
|
||||
|
||||
@test "format_brew_update_label formats with formula and cask counts" {
|
||||
result=$(BREW_OUTDATED_COUNT=5 BREW_FORMULA_OUTDATED_COUNT=3 BREW_CASK_OUTDATED_COUNT=2 bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; format_brew_update_label")
|
||||
result=$(BREW_OUTDATED_COUNT=5 BREW_FORMULA_OUTDATED_COUNT=3 BREW_CASK_OUTDATED_COUNT=2 bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; format_brew_update_label")
|
||||
[[ "$result" =~ "3 formula" ]]
|
||||
[[ "$result" =~ "2 cask" ]]
|
||||
}
|
||||
|
||||
@test "format_brew_update_label shows total when breakdown unavailable" {
|
||||
result=$(BREW_OUTDATED_COUNT=5 bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; format_brew_update_label")
|
||||
result=$(BREW_OUTDATED_COUNT=5 bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; format_brew_update_label")
|
||||
[[ "$result" =~ "5 updates" ]]
|
||||
}
|
||||
|
||||
# Test ask_for_updates function
|
||||
@test "ask_for_updates returns 1 when no updates available" {
|
||||
run bash -c "source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates < /dev/null"
|
||||
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates < /dev/null"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ setup() {
|
||||
export BREW_CASK_OUTDATED_COUNT=2
|
||||
|
||||
# Use input redirection to simulate ESC (cancel)
|
||||
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates"
|
||||
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates"
|
||||
# Should show updates and ask for confirmation
|
||||
[ "$status" -eq 1 ] # ESC cancels
|
||||
}
|
||||
@@ -106,21 +106,21 @@ setup() {
|
||||
@test "ask_for_updates detects App Store updates" {
|
||||
export APPSTORE_UPDATE_COUNT=3
|
||||
|
||||
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates"
|
||||
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates"
|
||||
[ "$status" -eq 1 ] # ESC cancels
|
||||
}
|
||||
|
||||
@test "ask_for_updates detects macOS updates" {
|
||||
export MACOS_UPDATE_AVAILABLE=true
|
||||
|
||||
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates"
|
||||
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates"
|
||||
[ "$status" -eq 1 ] # ESC cancels
|
||||
}
|
||||
|
||||
@test "ask_for_updates detects Mole updates" {
|
||||
export MOLE_UPDATE_AVAILABLE=true
|
||||
|
||||
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/common.sh'; source '$PROJECT_ROOT/lib/update_manager.sh'; ask_for_updates"
|
||||
run bash -c "printf '\x1b' | source '$PROJECT_ROOT/lib/core/common.sh'; source '$PROJECT_ROOT/lib/manage/update.sh'; ask_for_updates"
|
||||
[ "$status" -eq 1 ] # ESC cancels
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ brew() {
|
||||
esac
|
||||
}
|
||||
export -f brew start_inline_spinner stop_inline_spinner
|
||||
source "$PROJECT_ROOT/lib/common.sh"
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
update_via_homebrew "1.7.9"
|
||||
EOF
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ setup() {
|
||||
|
||||
@test "patterns_equivalent treats paths with tilde expansion as equal" {
|
||||
local status
|
||||
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; patterns_equivalent '~/.cache/test' \"\$HOME/.cache/test\""; then
|
||||
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; patterns_equivalent '~/.cache/test' \"\$HOME/.cache/test\""; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
@@ -38,7 +38,7 @@ setup() {
|
||||
|
||||
@test "patterns_equivalent distinguishes different paths" {
|
||||
local status
|
||||
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; patterns_equivalent '~/.cache/test' \"\$HOME/.cache/other\""; then
|
||||
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; patterns_equivalent '~/.cache/test' \"\$HOME/.cache/other\""; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
@@ -47,7 +47,7 @@ setup() {
|
||||
}
|
||||
|
||||
@test "save_whitelist_patterns keeps unique entries and preserves header" {
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; save_whitelist_patterns \"\$HOME/.cache/foo\" \"\$HOME/.cache/foo\" \"\$HOME/.cache/bar\""
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; save_whitelist_patterns \"\$HOME/.cache/foo\" \"\$HOME/.cache/foo\" \"\$HOME/.cache/bar\""
|
||||
|
||||
[[ -f "$WHITELIST_PATH" ]]
|
||||
|
||||
@@ -64,8 +64,8 @@ setup() {
|
||||
|
||||
@test "load_whitelist falls back to defaults when config missing" {
|
||||
rm -f "$WHITELIST_PATH"
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; rm -f \"\$HOME/.config/mole/whitelist\"; load_whitelist; printf '%s\n' \"\${CURRENT_WHITELIST_PATTERNS[@]}\"" > "$HOME/current_whitelist.txt"
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; printf '%s\n' \"\${DEFAULT_WHITELIST_PATTERNS[@]}\"" > "$HOME/default_whitelist.txt"
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; rm -f \"\$HOME/.config/mole/whitelist\"; load_whitelist; printf '%s\n' \"\${CURRENT_WHITELIST_PATTERNS[@]}\"" > "$HOME/current_whitelist.txt"
|
||||
HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; printf '%s\n' \"\${DEFAULT_WHITELIST_PATTERNS[@]}\"" > "$HOME/default_whitelist.txt"
|
||||
|
||||
current=()
|
||||
while IFS= read -r line; do
|
||||
@@ -83,14 +83,14 @@ setup() {
|
||||
|
||||
@test "is_whitelisted matches saved patterns exactly" {
|
||||
local status
|
||||
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; save_whitelist_patterns \"\$HOME/.cache/unique-pattern\"; load_whitelist; is_whitelisted \"\$HOME/.cache/unique-pattern\""; then
|
||||
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; save_whitelist_patterns \"\$HOME/.cache/unique-pattern\"; load_whitelist; is_whitelisted \"\$HOME/.cache/unique-pattern\""; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/whitelist_manager.sh'; save_whitelist_patterns \"\$HOME/.cache/unique-pattern\"; load_whitelist; is_whitelisted \"\$HOME/.cache/other-pattern\""; then
|
||||
if HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/manage/whitelist.sh'; save_whitelist_patterns \"\$HOME/.cache/unique-pattern\"; load_whitelist; is_whitelisted \"\$HOME/.cache/other-pattern\""; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
|
||||
Reference in New Issue
Block a user