diff --git a/bin/clean.sh b/bin/clean.sh index 663e120..af1e544 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -117,7 +117,6 @@ if [[ ${#WHITELIST_PATTERNS[@]} -gt 0 ]]; then fi # Section tracking and summary counters. -readonly MOLE_ONE_GIB_KB=$((1024 * 1024)) total_items=0 TRACK_SECTION=0 SECTION_ACTIVITY=0 @@ -651,13 +650,8 @@ safe_clean() { label+=" ${#targets[@]} items" fi - local line_color=$GREEN - if ((total_size_kb < MOLE_ONE_GIB_KB)); then - line_color=$YELLOW - fi - if [[ "$DRY_RUN" == "true" ]]; then - echo -e " ${line_color}${ICON_DRY_RUN}${NC} $label${NC}, ${line_color}$size_human dry${NC}" + echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} $label${NC}, ${YELLOW}$size_human dry${NC}" local paths_temp paths_temp=$(create_temp_file) @@ -717,6 +711,8 @@ safe_clean() { done fi else + local line_color + line_color=$(cleanup_result_color_kb "$total_size_kb") echo -e " ${line_color}${ICON_SUCCESS}${NC} $label${NC}, ${line_color}$size_human${NC}" fi files_cleaned=$((files_cleaned + total_count)) diff --git a/lib/clean/apps.sh b/lib/clean/apps.sh index a848880..5f45d4c 100644 --- a/lib/clean/apps.sh +++ b/lib/clean/apps.sh @@ -48,12 +48,14 @@ clean_ds_store_tree() { if [[ $file_count -gt 0 ]]; then local size_human size_human=$(bytes_to_human "$total_bytes") + local size_kb=$(((total_bytes + 1023) / 1024)) if [[ "$DRY_RUN" == "true" ]]; then echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} $label${NC}, ${YELLOW}$file_count files, $size_human dry${NC}" else - echo -e " ${GREEN}${ICON_SUCCESS}${NC} $label${NC}, ${GREEN}$file_count files, $size_human${NC}" + local line_color + line_color=$(cleanup_result_color_kb "$size_kb") + echo -e " ${line_color}${ICON_SUCCESS}${NC} $label${NC}, ${line_color}$file_count files, $size_human${NC}" fi - local size_kb=$(((total_bytes + 1023) / 1024)) files_cleaned=$((files_cleaned + file_count)) total_size_cleaned=$((total_size_cleaned + size_kb)) total_items=$((total_items + 1)) diff --git a/lib/clean/dev.sh b/lib/clean/dev.sh index 04aff0d..5465371 100644 --- a/lib/clean/dev.sh +++ b/lib/clean/dev.sh @@ -439,7 +439,9 @@ clean_xcode_device_support() { done if [[ $removed_count -gt 0 ]]; then - echo -e " ${GREEN}${ICON_SUCCESS}${NC} ${display_name} · removed ${removed_count} old versions, ${stale_size_human}" + local line_color + line_color=$(cleanup_result_color_kb "$stale_size_kb") + echo -e " ${line_color}${ICON_SUCCESS}${NC} ${display_name} · removed ${removed_count} old versions, ${line_color}${stale_size_human}${NC}" note_activity fi fi @@ -655,10 +657,12 @@ clean_xcode_simulator_runtime_volumes() { if [[ $removed_count -gt 0 ]]; then local removed_human removed_human=$(bytes_to_human "$((removed_size_kb * 1024))") + local line_color + line_color=$(cleanup_result_color_kb "$removed_size_kb") if [[ $skipped_protected -gt 0 ]]; then - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode runtime volumes · removed ${removed_count} (${removed_human}), skipped ${skipped_protected} protected" + echo -e " ${line_color}${ICON_SUCCESS}${NC} Xcode runtime volumes · removed ${removed_count} (${line_color}${removed_human}${NC}), skipped ${skipped_protected} protected" else - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode runtime volumes · removed ${removed_count} (${removed_human})" + echo -e " ${line_color}${ICON_SUCCESS}${NC} Xcode runtime volumes · removed ${removed_count} (${line_color}${removed_human}${NC})" fi note_activity else @@ -745,9 +749,13 @@ clean_dev_mobile() { fi if ((removed_unavailable > 0)); then - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${removed_unavailable}, ${unavailable_size_human}" + local line_color + line_color=$(cleanup_result_color_kb "$unavailable_size_kb") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${removed_unavailable}, ${line_color}${unavailable_size_human}${NC}" else - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · cleanup completed, ${unavailable_size_human}" + local line_color + line_color=$(cleanup_result_color_kb "$unavailable_size_kb") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Xcode unavailable simulators · cleanup completed, ${line_color}${unavailable_size_human}${NC}" fi else stop_section_spinner @@ -793,7 +801,9 @@ clean_dev_mobile() { if ((manually_removed > 0)); then if ((manual_failed == 0)); then - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${manually_removed} (fallback), ${unavailable_size_human}" + local line_color + line_color=$(cleanup_result_color_kb "$unavailable_size_kb") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Xcode unavailable simulators · removed ${manually_removed} (fallback), ${line_color}${unavailable_size_human}${NC}" else echo -e " ${YELLOW}${ICON_WARNING}${NC} Xcode unavailable simulators · partially cleaned ${manually_removed}/${#unavailable_udids[@]}, ${unavailable_size_human}" fi diff --git a/lib/clean/system.sh b/lib/clean/system.sh index 27a3065..c3403b8 100644 --- a/lib/clean/system.sh +++ b/lib/clean/system.sh @@ -309,7 +309,9 @@ clean_time_machine_failed_backups() { continue fi if tmutil delete "$inprogress_file" 2> /dev/null; then - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete backup: $backup_name${NC}, ${GREEN}$size_human${NC}" + local line_color + line_color=$(cleanup_result_color_kb "$size_kb") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Incomplete backup: $backup_name${NC}, ${line_color}$size_human${NC}" tm_cleaned=$((tm_cleaned + 1)) files_cleaned=$((files_cleaned + 1)) total_size_cleaned=$((total_size_cleaned + size_kb)) @@ -360,7 +362,9 @@ clean_time_machine_failed_backups() { continue fi if tmutil delete "$inprogress_file" 2> /dev/null; then - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Incomplete APFS backup in $bundle_name: $backup_name${NC}, ${GREEN}$size_human${NC}" + local line_color + line_color=$(cleanup_result_color_kb "$size_kb") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Incomplete APFS backup in $bundle_name: $backup_name${NC}, ${line_color}$size_human${NC}" tm_cleaned=$((tm_cleaned + 1)) files_cleaned=$((files_cleaned + 1)) total_size_cleaned=$((total_size_cleaned + size_kb)) diff --git a/lib/clean/user.sh b/lib/clean/user.sh index f5f0012..eadaa3b 100644 --- a/lib/clean/user.sh +++ b/lib/clean/user.sh @@ -233,7 +233,9 @@ clean_chrome_old_versions() { if [[ "$DRY_RUN" == "true" ]]; then echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Chrome old versions${NC}, ${YELLOW}${cleaned_count} dirs, $size_human dry${NC}" else - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Chrome old versions${NC}, ${GREEN}${cleaned_count} dirs, $size_human${NC}" + local line_color + line_color=$(cleanup_result_color_kb "$total_size") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Chrome old versions${NC}, ${line_color}${cleaned_count} dirs, $size_human${NC}" fi files_cleaned=$((files_cleaned + cleaned_count)) total_size_cleaned=$((total_size_cleaned + total_size)) @@ -319,7 +321,9 @@ clean_edge_old_versions() { if [[ "$DRY_RUN" == "true" ]]; then echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Edge old versions${NC}, ${YELLOW}${cleaned_count} dirs, $size_human dry${NC}" else - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Edge old versions${NC}, ${GREEN}${cleaned_count} dirs, $size_human${NC}" + local line_color + line_color=$(cleanup_result_color_kb "$total_size") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Edge old versions${NC}, ${line_color}${cleaned_count} dirs, $size_human${NC}" fi files_cleaned=$((files_cleaned + cleaned_count)) total_size_cleaned=$((total_size_cleaned + total_size)) @@ -381,7 +385,9 @@ clean_edge_updater_old_versions() { if [[ "$DRY_RUN" == "true" ]]; then echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Edge updater old versions${NC}, ${YELLOW}${cleaned_count} dirs, $size_human dry${NC}" else - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Edge updater old versions${NC}, ${GREEN}${cleaned_count} dirs, $size_human${NC}" + local line_color + line_color=$(cleanup_result_color_kb "$total_size") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Edge updater old versions${NC}, ${line_color}${cleaned_count} dirs, $size_human${NC}" fi files_cleaned=$((files_cleaned + cleaned_count)) total_size_cleaned=$((total_size_cleaned + total_size)) @@ -597,7 +603,9 @@ clean_app_caches() { else local size_human size_human=$(bytes_to_human "$((total_size * 1024))") - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Sandboxed app caches${NC}, ${GREEN}$size_human${NC}" + local line_color + line_color=$(cleanup_result_color_kb "$total_size") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Sandboxed app caches${NC}, ${line_color}$size_human${NC}" fi fi files_cleaned=$((files_cleaned + cleaned_count)) @@ -793,7 +801,9 @@ clean_group_container_caches() { else local size_human size_human=$(bytes_to_human "$((total_size * 1024))") - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Group Containers logs/caches${NC}, ${GREEN}$size_human${NC}" + local line_color + line_color=$(cleanup_result_color_kb "$total_size") + echo -e " ${line_color}${ICON_SUCCESS}${NC} Group Containers logs/caches${NC}, ${line_color}$size_human${NC}" fi fi files_cleaned=$((files_cleaned + cleaned_count)) @@ -960,7 +970,9 @@ clean_external_volume_target() { if [[ "$DRY_RUN" == "true" ]]; then echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} External volume cleanup${NC}, ${YELLOW}${volume_name}, $size_human dry${NC}" else - echo -e " ${GREEN}${ICON_SUCCESS}${NC} External volume cleanup${NC}, ${GREEN}${volume_name}, $size_human${NC}" + local line_color + line_color=$(cleanup_result_color_kb "$total_size") + echo -e " ${line_color}${ICON_SUCCESS}${NC} External volume cleanup${NC}, ${line_color}${volume_name}, $size_human${NC}" fi files_cleaned=$((files_cleaned + cleaned_count)) total_size_cleaned=$((total_size_cleaned + total_size)) @@ -1340,10 +1352,12 @@ clean_application_support_logs() { echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Application Support logs/caches${NC}, ${YELLOW}$size_human dry${NC}" fi else + local line_color + line_color=$(cleanup_result_color_kb "$total_size_kb") if [[ "$total_size_partial" == "true" ]]; then - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Application Support logs/caches${NC}, ${GREEN}at least $size_human${NC}" + echo -e " ${line_color}${ICON_SUCCESS}${NC} Application Support logs/caches${NC}, ${line_color}at least $size_human${NC}" else - echo -e " ${GREEN}${ICON_SUCCESS}${NC} Application Support logs/caches${NC}, ${GREEN}$size_human${NC}" + echo -e " ${line_color}${ICON_SUCCESS}${NC} Application Support logs/caches${NC}, ${line_color}$size_human${NC}" fi fi files_cleaned=$((files_cleaned + cleaned_count)) diff --git a/lib/core/base.sh b/lib/core/base.sh index 22e541b..4c559a9 100644 --- a/lib/core/base.sh +++ b/lib/core/base.sh @@ -78,6 +78,7 @@ readonly MOLE_SAVED_STATE_AGE_DAYS=30 # Saved state retention (days) - increa readonly MOLE_TM_BACKUP_SAFE_HOURS=48 # TM backup safety window (hours) readonly MOLE_MAX_DS_STORE_FILES=500 # Max .DS_Store files to clean per scan readonly MOLE_MAX_ORPHAN_ITERATIONS=100 # Max iterations for orphaned app data scan +readonly MOLE_ONE_GIB_KB=$((1024 * 1024)) # ============================================================================ # Whitelist Configuration @@ -548,6 +549,18 @@ bytes_to_human_kb() { bytes_to_human "$((${1:-0} * 1024))" } +# Pick a cleanup result color using the shared 1 GiB threshold. +cleanup_result_color_kb() { + local size_kb="${1:-0}" + [[ "$size_kb" =~ ^[0-9]+$ ]] || size_kb=0 + + if ((size_kb >= MOLE_ONE_GIB_KB)); then + printf '%s' "$GREEN" + else + printf '%s' "$YELLOW" + fi +} + # ============================================================================ # Temporary File Management # ============================================================================ diff --git a/tests/clean_apps.bats b/tests/clean_apps.bats index 3ab13db..24a35ac 100644 --- a/tests/clean_apps.bats +++ b/tests/clean_apps.bats @@ -32,8 +32,8 @@ source "$PROJECT_ROOT/lib/clean/apps.sh" start_inline_spinner() { :; } stop_section_spinner() { :; } note_activity() { :; } -get_file_size() { echo 10; } -bytes_to_human() { echo "0B"; } +get_file_size() { echo $((2 * 1024 * 1024 * 1024)); } +bytes_to_human() { echo "2.15GB"; } files_cleaned=0 total_size_cleaned=0 total_items=0 @@ -44,6 +44,30 @@ EOF [ "$status" -eq 0 ] [[ "$output" == *"DS test"* ]] + [[ "$output" == *$'\033[0;33m→\033[0m'* ]] +} + +@test "clean_ds_store_tree uses yellow for small successful cleanups" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" DRY_RUN=false /bin/bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/clean/apps.sh" +start_inline_spinner() { :; } +stop_section_spinner() { :; } +note_activity() { :; } +get_file_size() { echo 512; } +bytes_to_human() { echo "512B"; } +files_cleaned=0 +total_size_cleaned=0 +total_items=0 +mkdir -p "$HOME/test_ds" +touch "$HOME/test_ds/.DS_Store" +clean_ds_store_tree "$HOME/test_ds" "DS test" +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"DS test"* ]] + [[ "$output" == *$'\033[0;33m✓\033[0m'* ]] } @test "scan_installed_apps uses cache when fresh" { diff --git a/tests/core_common.bats b/tests/core_common.bats index 85cd9f2..b56fa52 100644 --- a/tests/core_common.bats +++ b/tests/core_common.bats @@ -44,6 +44,21 @@ setup() { [[ -n "$result" ]] } +@test "cleanup_result_color_kb switches from yellow to green at 1 GiB" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" + +if [[ "$(cleanup_result_color_kb $((MOLE_ONE_GIB_KB - 1)))" == "$YELLOW" ]] && + [[ "$(cleanup_result_color_kb "$MOLE_ONE_GIB_KB")" == "$GREEN" ]]; then + echo "ok" +fi +EOF + + [ "$status" -eq 0 ] + [ "$output" = "ok" ] +} + @test "log_info prints message and appends to log file" { local message="Informational message from test" local stdout_output