mirror of
https://github.com/tw93/Mole.git
synced 2026-02-16 03:46:11 +00:00
feat: add purge command to clean project build artifacts and update clean dry-run message.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -52,3 +52,4 @@ cmd/status/status
|
|||||||
/status
|
/status
|
||||||
mole-analyze
|
mole-analyze
|
||||||
# Note: bin/analyze-go and bin/status-go are released binaries and should be tracked
|
# Note: bin/analyze-go and bin/status-go are released binaries and should be tracked
|
||||||
|
.mole_cleanup_stats
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -47,6 +47,7 @@ mo uninstall # Remove apps + leftovers
|
|||||||
mo optimize # Refresh caches & services
|
mo optimize # Refresh caches & services
|
||||||
mo analyze # Visual disk explorer
|
mo analyze # Visual disk explorer
|
||||||
mo status # Live system health dashboard
|
mo status # Live system health dashboard
|
||||||
|
mo purge # Clean project build artifacts
|
||||||
|
|
||||||
mo touchid # Configure Touch ID for sudo
|
mo touchid # Configure Touch ID for sudo
|
||||||
mo update # Update Mole
|
mo update # Update Mole
|
||||||
@@ -181,6 +182,19 @@ Proxy HTTP · 192.168.1.100 Terminal ▮▯▯▯▯ 12.5%
|
|||||||
|
|
||||||
Health score based on CPU, memory, disk, temperature, and I/O load. Color-coded by range.
|
Health score based on CPU, memory, disk, temperature, and I/O load. Color-coded by range.
|
||||||
|
|
||||||
|
### Project Artifact Purge
|
||||||
|
|
||||||
|
Remove build artifacts from old projects to reclaim disk space. Fast parallel scanning targets `node_modules`, `target`, `build`, `dist`, `.next`, `.gradle`, `venv`, and similar directories.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mo purge --dry-run # Preview cleanup (recommended)
|
||||||
|
mo purge # Clean old project artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Safety:** Only scans common project directories, skips recently modified projects (7 days), and requires artifacts at least 2 levels deep to avoid system files.
|
||||||
|
|
||||||
|
**Performance:** Uses macOS Spotlight index (mdfind) for lightning-fast scanning, with parallel search across multiple directories.
|
||||||
|
|
||||||
## Quick Launchers
|
## Quick Launchers
|
||||||
|
|
||||||
Launch Mole commands instantly from Raycast or Alfred:
|
Launch Mole commands instantly from Raycast or Alfred:
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ start_cleanup() {
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [[ "$DRY_RUN" != "true" && -t 0 ]]; then
|
if [[ "$DRY_RUN" != "true" && -t 0 ]]; then
|
||||||
echo -e "${YELLOW}☻${NC} First time? Run ${GRAY}mo clean --dry-run${NC} first to preview changes"
|
echo -e "${GRAY}${ICON_SOLID} Use --dry-run to preview, --whitelist to manage protected paths${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$DRY_RUN" == "true" ]]; then
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
|||||||
155
bin/purge.sh
Executable file
155
bin/purge.sh
Executable file
@@ -0,0 +1,155 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Mole - Project purge command (mo purge)
|
||||||
|
# Remove old project build artifacts and dependencies
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Fix locale issues (avoid Perl warnings on non-English systems)
|
||||||
|
export LC_ALL=C
|
||||||
|
export LANG=C
|
||||||
|
|
||||||
|
# Get script directory and source common functions
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/../lib/core/common.sh"
|
||||||
|
source "$SCRIPT_DIR/../lib/core/log.sh"
|
||||||
|
source "$SCRIPT_DIR/../lib/clean/project.sh"
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
# Export list configuration
|
||||||
|
EXPORT_LIST_FILE="$HOME/.config/mole/purge-list.txt"
|
||||||
|
CURRENT_SECTION=""
|
||||||
|
|
||||||
|
# Section management
|
||||||
|
start_section() {
|
||||||
|
local section_name="$1"
|
||||||
|
CURRENT_SECTION="$section_name"
|
||||||
|
printf '\n'
|
||||||
|
echo -e "${BLUE}━━━ ${section_name} ━━━${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
end_section() {
|
||||||
|
CURRENT_SECTION=""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note activity for export list
|
||||||
|
note_activity() {
|
||||||
|
if [[ -n "$CURRENT_SECTION" ]]; then
|
||||||
|
printf '%s\n' "$CURRENT_SECTION" >> "$EXPORT_LIST_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main purge function
|
||||||
|
start_purge() {
|
||||||
|
# Clear screen for better UX
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
printf '\033[2J\033[H'
|
||||||
|
fi
|
||||||
|
printf '\n'
|
||||||
|
echo -e "${PURPLE_BOLD}Purge Project Artifacts${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
echo -e "${GRAY}${ICON_SOLID}${NC} Dry run mode - previewing what would be cleaned"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prepare export list
|
||||||
|
if [[ "$DRY_RUN" != "true" ]]; then
|
||||||
|
mkdir -p "$(dirname "$EXPORT_LIST_FILE")"
|
||||||
|
: > "$EXPORT_LIST_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize stats file
|
||||||
|
echo "0" > "$SCRIPT_DIR/../.mole_cleanup_stats"
|
||||||
|
echo "0" > "$SCRIPT_DIR/../.mole_cleanup_count"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Perform the purge
|
||||||
|
perform_purge() {
|
||||||
|
clean_project_artifacts
|
||||||
|
|
||||||
|
# Final summary (matching clean.sh format)
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
local summary_heading=""
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
summary_heading="Purge complete - dry run"
|
||||||
|
else
|
||||||
|
summary_heading="Purge complete"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local -a summary_details=()
|
||||||
|
local total_size_cleaned=0
|
||||||
|
local total_items_cleaned=0
|
||||||
|
|
||||||
|
# Read stats
|
||||||
|
if [[ -f "$SCRIPT_DIR/../.mole_cleanup_stats" ]]; then
|
||||||
|
total_size_cleaned=$(cat "$SCRIPT_DIR/../.mole_cleanup_stats" 2>/dev/null || echo "0")
|
||||||
|
rm -f "$SCRIPT_DIR/../.mole_cleanup_stats"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read count
|
||||||
|
if [[ -f "$SCRIPT_DIR/../.mole_cleanup_count" ]]; then
|
||||||
|
total_items_cleaned=$(cat "$SCRIPT_DIR/../.mole_cleanup_count" 2>/dev/null || echo "0")
|
||||||
|
rm -f "$SCRIPT_DIR/../.mole_cleanup_count"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $total_size_cleaned -gt 0 ]]; then
|
||||||
|
local freed_gb
|
||||||
|
freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}')
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
summary_details+=("Potential space: ${GREEN}${freed_gb}GB${NC}")
|
||||||
|
else
|
||||||
|
summary_details+=("Space freed: ${GREEN}${freed_gb}GB${NC}")
|
||||||
|
|
||||||
|
if [[ $total_items_cleaned -gt 0 ]]; then
|
||||||
|
summary_details+=("Items cleaned: $total_items_cleaned")
|
||||||
|
fi
|
||||||
|
|
||||||
|
summary_details+=("Free space now: $(get_free_space)")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
summary_details+=("No old project artifacts found.")
|
||||||
|
else
|
||||||
|
summary_details+=("No old project artifacts to clean.")
|
||||||
|
fi
|
||||||
|
summary_details+=("Free space now: $(get_free_space)")
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_summary_block "$summary_heading" "${summary_details[@]}"
|
||||||
|
printf '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main entry point
|
||||||
|
main() {
|
||||||
|
# Set up signal handling
|
||||||
|
trap 'show_cursor; exit 130' INT TERM
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
"--debug")
|
||||||
|
export MO_DEBUG=1
|
||||||
|
;;
|
||||||
|
"--dry-run" | "-n")
|
||||||
|
DRY_RUN=true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $arg"
|
||||||
|
echo "Use 'mo --help' for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
start_purge
|
||||||
|
hide_cursor
|
||||||
|
perform_purge
|
||||||
|
show_cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
435
lib/clean/project.sh
Normal file
435
lib/clean/project.sh
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Project Purge Module (mo purge)
|
||||||
|
# Removes heavy project build artifacts and dependencies
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Targets to look for (heavy build artifacts)
|
||||||
|
readonly PURGE_TARGETS=(
|
||||||
|
"node_modules"
|
||||||
|
"target" # Rust, Maven
|
||||||
|
"build" # Gradle, various
|
||||||
|
"dist" # JS builds
|
||||||
|
"venv" # Python
|
||||||
|
".venv" # Python
|
||||||
|
".gradle" # Gradle local
|
||||||
|
"__pycache__" # Python
|
||||||
|
".next" # Next.js
|
||||||
|
".nuxt" # Nuxt.js
|
||||||
|
".output" # Nuxt.js
|
||||||
|
"vendor" # PHP Composer
|
||||||
|
"obj" # C# / Unity
|
||||||
|
".turbo" # Turborepo cache
|
||||||
|
".parcel-cache" # Parcel bundler
|
||||||
|
)
|
||||||
|
|
||||||
|
# Minimum age in days before considering for cleanup
|
||||||
|
readonly MIN_AGE_DAYS=7
|
||||||
|
|
||||||
|
# Search paths (only project directories)
|
||||||
|
readonly PURGE_SEARCH_PATHS=(
|
||||||
|
"$HOME/www"
|
||||||
|
"$HOME/dev"
|
||||||
|
"$HOME/Projects"
|
||||||
|
"$HOME/GitHub"
|
||||||
|
"$HOME/Code"
|
||||||
|
"$HOME/Workspace"
|
||||||
|
"$HOME/Repos"
|
||||||
|
"$HOME/Development"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if path is safe to clean (must be inside a project directory)
|
||||||
|
# Args: $1 - path to check
|
||||||
|
is_safe_project_artifact() {
|
||||||
|
local path="$1"
|
||||||
|
local search_path="$2"
|
||||||
|
|
||||||
|
# Path must be absolute
|
||||||
|
if [[ "$path" != /* ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Must not be a direct child of HOME directory
|
||||||
|
# e.g., ~/.gradle is NOT safe, but ~/Projects/foo/.gradle IS safe
|
||||||
|
local relative_path="${path#$search_path/}"
|
||||||
|
local depth=$(echo "$relative_path" | tr -cd '/' | wc -c)
|
||||||
|
|
||||||
|
# Require at least 1 level deep (inside a project folder)
|
||||||
|
# e.g., ~/www/MyProject/node_modules is OK (depth >= 1)
|
||||||
|
# but ~/www/node_modules is NOT OK (depth = 0)
|
||||||
|
if [[ $depth -lt 1 ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fast scan using fd or optimized find
|
||||||
|
# Args: $1 - search path, $2 - output file
|
||||||
|
# Scan for purge targets using strict project boundary checks
|
||||||
|
# Args: $1 - search path, $2 - output file
|
||||||
|
scan_purge_targets() {
|
||||||
|
local search_path="$1"
|
||||||
|
local output_file="$2"
|
||||||
|
|
||||||
|
if [[ ! -d "$search_path" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use fd for fast parallel search if available
|
||||||
|
if command -v fd > /dev/null 2>&1; then
|
||||||
|
local fd_args=(
|
||||||
|
"--absolute-path"
|
||||||
|
"--hidden"
|
||||||
|
"--no-ignore"
|
||||||
|
"--type" "d"
|
||||||
|
"--min-depth" "2"
|
||||||
|
"--max-depth" "5"
|
||||||
|
"--threads" "4"
|
||||||
|
"--exclude" ".git"
|
||||||
|
"--exclude" "Library"
|
||||||
|
"--exclude" ".Trash"
|
||||||
|
"--exclude" "Applications"
|
||||||
|
)
|
||||||
|
|
||||||
|
for target in "${PURGE_TARGETS[@]}"; do
|
||||||
|
fd_args+=("-g" "$target")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Run fd command
|
||||||
|
fd "${fd_args[@]}" . "$search_path" 2>/dev/null | while IFS= read -r item; do
|
||||||
|
if is_safe_project_artifact "$item" "$search_path"; then
|
||||||
|
echo "$item"
|
||||||
|
fi
|
||||||
|
done | filter_nested_artifacts > "$output_file"
|
||||||
|
else
|
||||||
|
# Fallback to optimized find with pruning
|
||||||
|
# This prevents descending into heavily nested dirs like node_modules once found,
|
||||||
|
# providing a massive speedup (O(project_dirs) vs O(files)).
|
||||||
|
|
||||||
|
local prune_args=()
|
||||||
|
|
||||||
|
# 1. Directories to prune (ignore completely)
|
||||||
|
local prune_dirs=(".git" "Library" ".Trash" "Applications")
|
||||||
|
for dir in "${prune_dirs[@]}"; do
|
||||||
|
# -name "DIR" -prune -o
|
||||||
|
prune_args+=("-name" "$dir" "-prune" "-o")
|
||||||
|
done
|
||||||
|
|
||||||
|
# 2. Targets to find (print AND prune)
|
||||||
|
# If we find node_modules, we print it and STOP looking inside it
|
||||||
|
for target in "${PURGE_TARGETS[@]}"; do
|
||||||
|
# -name "TARGET" -print -prune -o
|
||||||
|
prune_args+=("-name" "$target" "-print" "-prune" "-o")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Run find command
|
||||||
|
# Logic: ( prune_pattern -prune -o target_pattern -print -prune )
|
||||||
|
# Note: We rely on implicit recursion for directories that don't match any pattern.
|
||||||
|
# -print is only called explicitly on targets.
|
||||||
|
|
||||||
|
# Removing the trailing -o from loop construction if necessary?
|
||||||
|
# Actually my loop adds -o at the end. I need to handle that.
|
||||||
|
# Let's verify the array construction.
|
||||||
|
|
||||||
|
# Re-building args cleanly:
|
||||||
|
local find_expr=()
|
||||||
|
|
||||||
|
# Excludes
|
||||||
|
for dir in "${prune_dirs[@]}"; do
|
||||||
|
find_expr+=("-name" "$dir" "-prune" "-o")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
local i=0
|
||||||
|
for target in "${PURGE_TARGETS[@]}"; do
|
||||||
|
find_expr+=("-name" "$target" "-print" "-prune")
|
||||||
|
|
||||||
|
# Add -o unless it's the very last item of targets
|
||||||
|
if [[ $i -lt $((${#PURGE_TARGETS[@]} - 1)) ]]; then
|
||||||
|
find_expr+=("-o")
|
||||||
|
fi
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
|
||||||
|
command find "$search_path" -mindepth 2 -maxdepth 5 -type d \
|
||||||
|
\( "${find_expr[@]}" \) 2>/dev/null | while IFS= read -r item; do
|
||||||
|
|
||||||
|
if is_safe_project_artifact "$item" "$search_path"; then
|
||||||
|
echo "$item"
|
||||||
|
fi
|
||||||
|
done | filter_nested_artifacts > "$output_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Filter out nested artifacts (e.g. node_modules inside node_modules)
|
||||||
|
filter_nested_artifacts() {
|
||||||
|
while IFS= read -r item; do
|
||||||
|
local parent_dir=$(dirname "$item")
|
||||||
|
local is_nested=false
|
||||||
|
|
||||||
|
for target in "${PURGE_TARGETS[@]}"; do
|
||||||
|
# Check if parent directory IS a target or IS INSIDE a target
|
||||||
|
# e.g. .../node_modules/foo/node_modules -> parent has node_modules
|
||||||
|
if [[ "$parent_dir" == *"/$target"* || "$parent_dir" == *"/$target" ]]; then
|
||||||
|
is_nested=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$is_nested" == "false" ]]; then
|
||||||
|
echo "$item"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if a path was modified recently (safety check)
|
||||||
|
# Args: $1 - path
|
||||||
|
is_recently_modified() {
|
||||||
|
local path="$1"
|
||||||
|
local age_days=$MIN_AGE_DAYS
|
||||||
|
|
||||||
|
if [[ ! -e "$path" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check modification time (macOS compatible)
|
||||||
|
local mod_time
|
||||||
|
mod_time=$(stat -f "%m" "$path" 2>/dev/null || stat -c "%Y" "$path" 2>/dev/null || echo "0")
|
||||||
|
local current_time=$(date +%s)
|
||||||
|
local age_seconds=$((current_time - mod_time))
|
||||||
|
local age_in_days=$((age_seconds / 86400))
|
||||||
|
|
||||||
|
if [[ $age_in_days -lt $age_days ]]; then
|
||||||
|
return 0 # Recently modified
|
||||||
|
else
|
||||||
|
return 1 # Old enough to clean
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get human-readable size of directory
|
||||||
|
# Args: $1 - path
|
||||||
|
get_dir_size_kb() {
|
||||||
|
local path="$1"
|
||||||
|
if [[ -d "$path" ]]; then
|
||||||
|
du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0"
|
||||||
|
else
|
||||||
|
echo "0"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Simplified clean function for project artifacts
|
||||||
|
# Args: $1 - path, $2 - description
|
||||||
|
safe_clean() {
|
||||||
|
local path="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
if [[ ! -e "$path" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get size before deletion
|
||||||
|
local size_kb=$(get_dir_size_kb "$path")
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
if [[ $size_kb -gt 0 ]]; then
|
||||||
|
local size_mb=$((size_kb / 1024))
|
||||||
|
echo -e "${GRAY}Would remove:${NC} $description (~${size_mb}MB)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ $size_kb -gt 0 ]]; then
|
||||||
|
local size_mb=$((size_kb / 1024))
|
||||||
|
|
||||||
|
# Show cleaning status (transient) with spinner
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
# Use standard spinner prefix or none as requested?
|
||||||
|
# User asked for "no indentation". MOLE_SPINNER_PREFIX controls indentation in ui.sh.
|
||||||
|
# But ui.sh often adds " |".
|
||||||
|
# Let's use start_inline_spinner which uses MOLE_SPINNER_PREFIX.
|
||||||
|
# We can temporarily clear prefix to avoid indentation if needed,
|
||||||
|
# but standard UI guidelines might suggest some alignment.
|
||||||
|
# The user specifically said "不要缩进".
|
||||||
|
local original_prefix="${MOLE_SPINNER_PREFIX:-}"
|
||||||
|
MOLE_SPINNER_PREFIX="" start_inline_spinner "Cleaning $description (~${size_mb}MB)..."
|
||||||
|
|
||||||
|
rm -rf "$path" 2>/dev/null || true
|
||||||
|
|
||||||
|
stop_inline_spinner
|
||||||
|
MOLE_SPINNER_PREFIX="$original_prefix"
|
||||||
|
else
|
||||||
|
rm -rf "$path" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e "$path" ]]; then
|
||||||
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Removed $description (~${size_mb}MB)"
|
||||||
|
|
||||||
|
# Update stats file
|
||||||
|
if [[ -f "$SCRIPT_DIR/../.mole_cleanup_stats" ]]; then
|
||||||
|
local current_total=$(cat "$SCRIPT_DIR/../.mole_cleanup_stats")
|
||||||
|
local new_total=$((current_total + size_kb))
|
||||||
|
echo "$new_total" > "$SCRIPT_DIR/../.mole_cleanup_stats"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update count file
|
||||||
|
local count_file="$SCRIPT_DIR/../.mole_cleanup_count"
|
||||||
|
local current_count=0
|
||||||
|
if [[ -f "$count_file" ]]; then
|
||||||
|
current_count=$(cat "$count_file")
|
||||||
|
fi
|
||||||
|
echo $((current_count + 1)) > "$count_file"
|
||||||
|
else
|
||||||
|
echo -e "${RED}${ICON_CROSS}${NC} Failed to remove $description"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main cleanup function
|
||||||
|
# Env: DRY_RUN
|
||||||
|
clean_project_artifacts() {
|
||||||
|
local -a all_found_items=()
|
||||||
|
local -a safe_to_clean=()
|
||||||
|
local -a recently_modified=()
|
||||||
|
local total_found_size=0 # in KB
|
||||||
|
|
||||||
|
# Show warning and ask for confirmation (not in dry-run mode)
|
||||||
|
if [[ "$DRY_RUN" != "true" && -t 0 ]]; then
|
||||||
|
echo -e "${GRAY}${ICON_SOLID}${NC} Will remove old project build artifacts, use --dry-run to preview"
|
||||||
|
echo -ne "${PURPLE}${ICON_ARROW}${NC} Press ${GREEN}Enter${NC} to continue, ${GRAY}ESC${NC} to cancel: "
|
||||||
|
|
||||||
|
# Read single key
|
||||||
|
IFS= read -r -s -n1 key || key=""
|
||||||
|
drain_pending_input
|
||||||
|
case "$key" in
|
||||||
|
$'\e')
|
||||||
|
echo ""
|
||||||
|
echo -e "${GRAY}Cancelled${NC}"
|
||||||
|
printf '\n'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
"" | $'\n' | $'\r')
|
||||||
|
printf "\r\033[K"
|
||||||
|
# Continue with scan
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo ""
|
||||||
|
echo -e "${GRAY}Cancelled${NC}"
|
||||||
|
printf '\n'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set up cleanup on interrupt
|
||||||
|
local scan_pids=()
|
||||||
|
local scan_temps=()
|
||||||
|
cleanup_scan() {
|
||||||
|
# Kill all background scans
|
||||||
|
for pid in "${scan_pids[@]}"; do
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
# Clean up temp files
|
||||||
|
for temp in "${scan_temps[@]}"; do
|
||||||
|
rm -f "$temp" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
printf '\n'
|
||||||
|
echo -e "${GRAY}Interrupted${NC}"
|
||||||
|
printf '\n'
|
||||||
|
exit 130
|
||||||
|
}
|
||||||
|
trap cleanup_scan INT TERM
|
||||||
|
|
||||||
|
# Start parallel scanning of all paths at once
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Scanning project directories (please wait)..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Launch all scans in parallel
|
||||||
|
for path in "${PURGE_SEARCH_PATHS[@]}"; do
|
||||||
|
if [[ -d "$path" ]]; then
|
||||||
|
local scan_output
|
||||||
|
scan_output=$(mktemp)
|
||||||
|
scan_temps+=("$scan_output")
|
||||||
|
|
||||||
|
# Launch scan in background for true parallelism
|
||||||
|
scan_purge_targets "$path" "$scan_output" &
|
||||||
|
local scan_pid=$!
|
||||||
|
scan_pids+=("$scan_pid")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait for all scans to complete
|
||||||
|
for pid in "${scan_pids[@]}"; do
|
||||||
|
wait "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all results
|
||||||
|
for scan_output in "${scan_temps[@]}"; do
|
||||||
|
if [[ -f "$scan_output" ]]; then
|
||||||
|
while IFS= read -r item; do
|
||||||
|
if [[ -n "$item" ]]; then
|
||||||
|
all_found_items+=("$item")
|
||||||
|
fi
|
||||||
|
done < "$scan_output"
|
||||||
|
rm -f "$scan_output"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clean up trap
|
||||||
|
trap - INT TERM
|
||||||
|
|
||||||
|
if [[ ${#all_found_items[@]} -eq 0 ]]; then
|
||||||
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} No project artifacts found."
|
||||||
|
note_activity
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Filter items based on modification time
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Analyzing artifacts..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
for item in "${all_found_items[@]}"; do
|
||||||
|
if is_recently_modified "$item"; then
|
||||||
|
recently_modified+=("$item")
|
||||||
|
else
|
||||||
|
safe_to_clean+=("$item")
|
||||||
|
local item_size=$(get_dir_size_kb "$item")
|
||||||
|
total_found_size=$((total_found_size + item_size))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}●${NC} Found ${#all_found_items[@]} artifacts (${#safe_to_clean[@]} older than $MIN_AGE_DAYS days)"
|
||||||
|
|
||||||
|
if [[ ${#recently_modified[@]} -gt 0 ]]; then
|
||||||
|
echo -e "${YELLOW}${ICON_WARNING}${NC} Skipping ${#recently_modified[@]} recently modified items (active projects)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#safe_to_clean[@]} -eq 0 ]]; then
|
||||||
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} No old artifacts to clean."
|
||||||
|
note_activity
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show total size estimate
|
||||||
|
local total_size_mb=$((total_found_size / 1024))
|
||||||
|
if [[ $total_size_mb -gt 0 ]]; then
|
||||||
|
echo -e "${GRAY}Estimated space to reclaim: ~${total_size_mb} MB${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean safe items
|
||||||
|
for item in "${safe_to_clean[@]}"; do
|
||||||
|
safe_clean "$item" "$(basename "$(dirname "$item")")/$(basename "$item")"
|
||||||
|
done
|
||||||
|
}
|
||||||
9
mole
9
mole
@@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
source "$SCRIPT_DIR/lib/core/common.sh"
|
source "$SCRIPT_DIR/lib/core/common.sh"
|
||||||
|
|
||||||
# Version info
|
# Version info
|
||||||
VERSION="1.13.5"
|
VERSION="1.13.6"
|
||||||
MOLE_TAGLINE="Deep clean and optimize your Mac."
|
MOLE_TAGLINE="Deep clean and optimize your Mac."
|
||||||
|
|
||||||
# Check if Touch ID is already configured
|
# Check if Touch ID is already configured
|
||||||
@@ -242,6 +242,10 @@ show_help() {
|
|||||||
printf " %s%-28s%s %s\n" "$GREEN" "mo uninstall --force-rescan" "$NC" "Rescan apps and refresh cache"
|
printf " %s%-28s%s %s\n" "$GREEN" "mo uninstall --force-rescan" "$NC" "Rescan apps and refresh cache"
|
||||||
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --whitelist" "$NC" "Manage protected items"
|
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --whitelist" "$NC" "Manage protected items"
|
||||||
echo
|
echo
|
||||||
|
printf "%s%s%s\n" "$BLUE" "ADVANCED" "$NC"
|
||||||
|
printf " %s%-28s%s %s\n" "$GREEN" "mo purge" "$NC" "Remove old project artifacts"
|
||||||
|
printf " %s%-28s%s %s\n" "$GREEN" "mo purge --dry-run" "$NC" "Preview project cleanup"
|
||||||
|
echo
|
||||||
printf "%s%s%s\n" "$BLUE" "OPTIONS" "$NC"
|
printf "%s%s%s\n" "$BLUE" "OPTIONS" "$NC"
|
||||||
printf " %s%-28s%s %s\n" "$GREEN" "--debug" "$NC" "Show detailed operation logs"
|
printf " %s%-28s%s %s\n" "$GREEN" "--debug" "$NC" "Show detailed operation logs"
|
||||||
echo
|
echo
|
||||||
@@ -772,6 +776,9 @@ main() {
|
|||||||
"status")
|
"status")
|
||||||
exec "$SCRIPT_DIR/bin/status.sh" "${args[@]:1}"
|
exec "$SCRIPT_DIR/bin/status.sh" "${args[@]:1}"
|
||||||
;;
|
;;
|
||||||
|
"purge")
|
||||||
|
exec "$SCRIPT_DIR/bin/purge.sh" "${args[@]:1}"
|
||||||
|
;;
|
||||||
"touchid")
|
"touchid")
|
||||||
exec "$SCRIPT_DIR/bin/touchid.sh" "${args[@]:1}"
|
exec "$SCRIPT_DIR/bin/touchid.sh" "${args[@]:1}"
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user