From 9a942a2770908376a278daddc6be6bc2bcda5c9e Mon Sep 17 00:00:00 2001 From: Tw93 Date: Mon, 22 Dec 2025 18:53:48 +0800 Subject: [PATCH] feat: Add configurable project cleanup depth, refine interactive menu display, and sanitize uninstall app names. --- bin/uninstall.sh | 19 ++++++++++++++++++ lib/clean/project.sh | 46 ++++++++++++++++++++++++++++++++------------ mole | 2 +- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/bin/uninstall.sh b/bin/uninstall.sh index 05d62cf..0d35c6f 100755 --- a/bin/uninstall.sh +++ b/bin/uninstall.sh @@ -149,6 +149,17 @@ scan_applications() { local bundle_name bundle_name=$(plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" 2> /dev/null) + # Sanitize metadata values (prevent paths, pipes, and newlines) + if [[ "$md_display_name" == /* ]]; then md_display_name=""; fi + md_display_name="${md_display_name//|/-}" + md_display_name="${md_display_name//[$'\t\r\n']/}" + + bundle_display_name="${bundle_display_name//|/-}" + bundle_display_name="${bundle_display_name//[$'\t\r\n']/}" + + bundle_name="${bundle_name//|/-}" + bundle_name="${bundle_name//[$'\t\r\n']/}" + # Select best available name if [[ -n "$md_display_name" && "$md_display_name" != "(null)" && "$md_display_name" != "$app_name" ]]; then display_name="$md_display_name" @@ -159,6 +170,14 @@ scan_applications() { fi fi + # Final safety check: if display_name looks like a path, revert to app_name + if [[ "$display_name" == /* ]]; then + display_name="$app_name" + fi + # Ensure no pipes or newlines in final display name + display_name="${display_name//|/-}" + display_name="${display_name//[$'\t\r\n']/}" + # Calculate app size (in parallel for performance) local app_size="N/A" local app_size_kb="0" diff --git a/lib/clean/project.sh b/lib/clean/project.sh index bee91b9..d1100f9 100644 --- a/lib/clean/project.sh +++ b/lib/clean/project.sh @@ -26,6 +26,10 @@ readonly PURGE_TARGETS=( # Minimum age in days before considering for cleanup readonly MIN_AGE_DAYS=7 +# Scan depth defaults (relative to search root) +readonly PURGE_MIN_DEPTH_DEFAULT=2 +readonly PURGE_MAX_DEPTH_DEFAULT=8 + # Search paths (only project directories) readonly PURGE_SEARCH_PATHS=( "$HOME/www" @@ -71,6 +75,18 @@ is_safe_project_artifact() { scan_purge_targets() { local search_path="$1" local output_file="$2" + local min_depth="${MOLE_PURGE_MIN_DEPTH:-$PURGE_MIN_DEPTH_DEFAULT}" + local max_depth="${MOLE_PURGE_MAX_DEPTH:-$PURGE_MAX_DEPTH_DEFAULT}" + + if [[ ! "$min_depth" =~ ^[0-9]+$ ]]; then + min_depth="$PURGE_MIN_DEPTH_DEFAULT" + fi + if [[ ! "$max_depth" =~ ^[0-9]+$ ]]; then + max_depth="$PURGE_MAX_DEPTH_DEFAULT" + fi + if [[ "$max_depth" -lt "$min_depth" ]]; then + max_depth="$min_depth" + fi if [[ ! -d "$search_path" ]]; then return @@ -83,8 +99,8 @@ scan_purge_targets() { "--hidden" "--no-ignore" "--type" "d" - "--min-depth" "2" - "--max-depth" "5" + "--min-depth" "$min_depth" + "--max-depth" "$max_depth" "--threads" "4" "--exclude" ".git" "--exclude" "Library" @@ -152,7 +168,7 @@ scan_purge_targets() { ((i++)) done - command find "$search_path" -mindepth 2 -maxdepth 5 -type d \ + command find "$search_path" -mindepth "$min_depth" -maxdepth "$max_depth" -type d \ \( "${find_expr[@]}" \) 2> /dev/null | while IFS= read -r item; do if is_safe_project_artifact "$item" "$search_path"; then @@ -226,6 +242,7 @@ get_dir_size_kb() { select_purge_categories() { local -a categories=("$@") local total_items=${#categories[@]} + local clear_line=$'\r\033[2K' if [[ $total_items -eq 0 ]]; then return 1 @@ -265,7 +282,7 @@ select_purge_categories() { } draw_menu() { - printf "\033[H\033[2J" + printf "\033[H" # Calculate total size of selected items for header local selected_size=0 local selected_count=0 @@ -276,11 +293,12 @@ select_purge_categories() { ((selected_count++)) fi done - local selected_gb=$(echo "scale=1; $selected_size/1024/1024" | bc) + local selected_gb + selected_gb=$(echo "scale=1; $selected_size/1024/1024" | bc) - printf '\n' - echo -e "${PURPLE_BOLD}Select Categories to Clean${NC} ${GRAY}- ${selected_gb}GB ($selected_count selected)${NC}" - echo "" + printf "%s\n" "$clear_line" + printf "%s${PURPLE_BOLD}Select Categories to Clean${NC} ${GRAY}- ${selected_gb}GB ($selected_count selected)${NC}\n" "$clear_line" + printf "%s\n" "$clear_line" IFS=',' read -r -a recent_flags <<< "${PURGE_RECENT_CATEGORIES:-}" for ((i = 0; i < total_items; i++)); do @@ -291,14 +309,14 @@ select_purge_categories() { [[ ${recent_flags[i]:-false} == "true" ]] && recent_marker=" ${GRAY}| Recent${NC}" if [[ $i -eq $cursor_pos ]]; then - printf "\r\033[2K${CYAN}${ICON_ARROW} %s %s%s${NC}\n" "$checkbox" "${categories[i]}" "$recent_marker" + printf "%s${CYAN}${ICON_ARROW} %s %s%s${NC}\n" "$clear_line" "$checkbox" "${categories[i]}" "$recent_marker" else - printf "\r\033[2K %s %s%s\n" "$checkbox" "${categories[i]}" "$recent_marker" + printf "%s %s %s%s\n" "$clear_line" "$checkbox" "${categories[i]}" "$recent_marker" fi done - echo "" - echo -e "${GRAY}↑↓ | Space Select | Enter Confirm | A All | I Invert | Q Quit${NC}" + printf "%s\n" "$clear_line" + printf "%s${GRAY}↑↓ | Space Select | Enter Confirm | A All | I Invert | Q Quit${NC}\n" "$clear_line" } trap restore_terminal EXIT @@ -308,6 +326,10 @@ select_purge_categories() { stty -echo -icanon intr ^C 2> /dev/null || true hide_cursor + if [[ -t 1 ]]; then + clear_screen + fi + # Main loop while true; do draw_menu diff --git a/mole b/mole index 73db700..1c14f32 100755 --- a/mole +++ b/mole @@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/lib/core/common.sh" # Version info -VERSION="1.14.0" +VERSION="1.14.1" MOLE_TAGLINE="Deep clean and optimize your Mac." # Check TouchID configuration