1
0
mirror of https://github.com/tw93/Mole.git synced 2026-03-22 17:55:08 +00:00

Add dry-run support across destructive commands (#516)

* chore: update contributors [skip ci]

* Add dry-run support across destructive commands

Implement dry-run for uninstall, purge, installer, touchid, completion, and remove flows.\nGuard side effects in uninstall path (launchctl, defaults writes, kill/brew actions), update help/README, and add coverage in CLI/Bats tests.\n\nValidation: ./scripts/check.sh and ./scripts/test.sh (452 tests, 0 failures, 8 skipped).

* test(purge): keep dev-compatible purge coverage

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tw93 <hitw93@gmail.com>
This commit is contained in:
陳德生
2026-03-01 20:03:22 +08:00
committed by GitHub
parent adcd98096a
commit 05446e0847
18 changed files with 1021 additions and 684 deletions

View File

@@ -32,8 +32,33 @@ emit_fish_completions() {
printf 'complete -c %s -n "not __fish_mole_no_subcommand" -a fish -d "generate fish completion" -n "__fish_see_subcommand_path completion"\n' "$cmd"
}
DRY_RUN_MODE=false
if [[ $# -gt 0 ]]; then
normalized_args=()
for arg in "$@"; do
case "$arg" in
"--dry-run" | "-n")
DRY_RUN_MODE=true
;;
*)
normalized_args+=("$arg")
;;
esac
done
if [[ ${#normalized_args[@]} -gt 0 ]]; then
set -- "${normalized_args[@]}"
else
set --
fi
fi
# Auto-install mode when run without arguments
if [[ $# -eq 0 ]]; then
if [[ "$DRY_RUN_MODE" == "true" ]]; then
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, shell config files will not be modified"
echo ""
fi
# Detect current shell
current_shell="${SHELL##*/}"
if [[ -z "$current_shell" ]]; then
@@ -73,16 +98,21 @@ if [[ $# -eq 0 ]]; then
if [[ -z "$completion_name" ]]; then
if [[ -f "$config_file" ]] && grep -Eq "(^# Mole shell completion$|(mole|mo)[[:space:]]+completion)" "$config_file" 2> /dev/null; then
original_mode=""
original_mode="$(stat -f '%Mp%Lp' "$config_file" 2> /dev/null || true)"
temp_file="$(mktemp)"
grep -Ev "(^# Mole shell completion$|(mole|mo)[[:space:]]+completion)" "$config_file" > "$temp_file" || true
mv "$temp_file" "$config_file"
if [[ -n "$original_mode" ]]; then
chmod "$original_mode" "$config_file" 2> /dev/null || true
if [[ "$DRY_RUN_MODE" == "true" ]]; then
echo -e "${GRAY}${ICON_REVIEW} [DRY RUN] Would remove stale completion entries from $config_file${NC}"
echo ""
else
original_mode=""
original_mode="$(stat -f '%Mp%Lp' "$config_file" 2> /dev/null || true)"
temp_file="$(mktemp)"
grep -Ev "(^# Mole shell completion$|(mole|mo)[[:space:]]+completion)" "$config_file" > "$temp_file" || true
mv "$temp_file" "$config_file"
if [[ -n "$original_mode" ]]; then
chmod "$original_mode" "$config_file" 2> /dev/null || true
fi
echo -e "${GREEN}${ICON_SUCCESS}${NC} Removed stale completion entries from $config_file"
echo ""
fi
echo -e "${GREEN}${ICON_SUCCESS}${NC} Removed stale completion entries from $config_file"
echo ""
fi
log_error "mole not found in PATH, install Mole before enabling completion"
exit 1
@@ -90,6 +120,12 @@ if [[ $# -eq 0 ]]; then
# Check if already installed and normalize to latest line
if [[ -f "$config_file" ]] && grep -Eq "(mole|mo)[[:space:]]+completion" "$config_file" 2> /dev/null; then
if [[ "$DRY_RUN_MODE" == "true" ]]; then
echo -e "${GRAY}${ICON_REVIEW} [DRY RUN] Would normalize completion entry in $config_file${NC}"
echo ""
exit 0
fi
original_mode=""
original_mode="$(stat -f '%Mp%Lp' "$config_file" 2> /dev/null || true)"
temp_file="$(mktemp)"
@@ -114,6 +150,11 @@ if [[ $# -eq 0 ]]; then
echo -e "${GRAY}Will add to ${config_file}:${NC}"
echo " $completion_line"
echo ""
if [[ "$DRY_RUN_MODE" == "true" ]]; then
echo -e "${GREEN}${ICON_SUCCESS}${NC} Dry run complete, no changes made"
exit 0
fi
echo -ne "${PURPLE}${ICON_ARROW}${NC} Enable completion for ${GREEN}${current_shell}${NC}? ${GRAY}Enter confirm / Q cancel${NC}: "
IFS= read -r -s -n1 key || key=""
drain_pending_input
@@ -227,6 +268,7 @@ Setup shell tab completion for mole and mo commands.
Auto-install:
mole completion # Auto-detect shell and install
mole completion --dry-run # Preview config changes without writing files
Manual install:
mole completion bash # Generate bash completion script

View File

@@ -650,13 +650,22 @@ perform_installers() {
show_summary() {
local summary_heading="Installers cleaned"
local -a summary_details=()
local dry_run_mode="${MOLE_DRY_RUN:-0}"
if [[ "$dry_run_mode" == "1" ]]; then
summary_heading="Dry run complete - no changes made"
fi
if [[ $total_deleted -gt 0 ]]; then
local freed_mb
freed_mb=$(echo "$total_size_freed_kb" | awk '{printf "%.2f", $1/1024}')
summary_details+=("Removed ${GREEN}$total_deleted${NC} installers, freed ${GREEN}${freed_mb}MB${NC}")
summary_details+=("Your Mac is cleaner now!")
if [[ "$dry_run_mode" == "1" ]]; then
summary_details+=("Would remove ${GREEN}$total_deleted${NC} installers, free ${GREEN}${freed_mb}MB${NC}")
else
summary_details+=("Removed ${GREEN}$total_deleted${NC} installers, freed ${GREEN}${freed_mb}MB${NC}")
summary_details+=("Your Mac is cleaner now!")
fi
else
summary_details+=("No installers were removed")
fi
@@ -675,6 +684,9 @@ main() {
"--debug")
export MO_DEBUG=1
;;
"--dry-run" | "-n")
export MOLE_DRY_RUN=1
;;
*)
echo "Unknown option: $arg"
exit 1
@@ -682,6 +694,11 @@ main() {
esac
done
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, No installer files will be removed"
printf '\n'
fi
hide_cursor
perform_installers
local exit_code=$?

View File

@@ -205,11 +205,18 @@ perform_purge() {
rm -f "$stats_dir/purge_count"
fi
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
summary_heading="Dry run complete - no changes made"
fi
if [[ $total_size_cleaned -gt 0 ]]; then
local freed_gb
freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}')
local summary_line="Space freed: ${GREEN}${freed_gb}GB${NC}"
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
summary_line="Would free: ${GREEN}${freed_gb}GB${NC}"
fi
[[ $total_items_cleaned -gt 0 ]] && summary_line+=" | Items: $total_items_cleaned"
summary_line+=" | Free: $(get_free_space)"
summary_details+=("$summary_line")
@@ -233,6 +240,7 @@ show_help() {
echo ""
echo -e "${YELLOW}Options:${NC}"
echo " --paths Edit custom scan directories"
echo " --dry-run Preview purge actions without making changes"
echo " --debug Enable debug logging"
echo " --help Show this help message"
echo ""
@@ -262,6 +270,9 @@ main() {
"--debug")
export MO_DEBUG=1
;;
"--dry-run" | "-n")
export MOLE_DRY_RUN=1
;;
*)
echo "Unknown option: $arg"
echo "Use 'mo purge --help' for usage information"
@@ -271,6 +282,10 @@ main() {
done
start_purge
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, No project artifacts will be removed"
printf '\n'
fi
hide_cursor
perform_purge
show_cursor

View File

@@ -60,6 +60,10 @@ supports_touchid() {
return 1
}
touchid_dry_run_enabled() {
[[ "${MOLE_DRY_RUN:-0}" == "1" ]]
}
# Show current Touch ID status
show_status() {
if is_touchid_configured; then
@@ -74,6 +78,16 @@ enable_touchid() {
# Cleanup trap handled by global EXIT trap
local temp_file=""
if touchid_dry_run_enabled; then
if is_touchid_configured; then
echo -e "${GREEN}${ICON_SUCCESS} Touch ID is already enabled, no changes needed${NC}"
else
echo -e "${GREEN}${ICON_SUCCESS} [DRY RUN] Would enable Touch ID for sudo${NC}"
echo -e "${GRAY}${ICON_REVIEW} Target files: ${PAM_SUDO_FILE} and/or ${PAM_SUDO_LOCAL_FILE}${NC}"
fi
return 0
fi
# First check if system supports Touch ID
if ! supports_touchid; then
log_warning "This Mac may not support Touch ID"
@@ -201,6 +215,16 @@ disable_touchid() {
# Cleanup trap handled by global EXIT trap
local temp_file=""
if touchid_dry_run_enabled; then
if ! is_touchid_configured; then
echo -e "${YELLOW}Touch ID is not currently enabled${NC}"
else
echo -e "${GREEN}${ICON_SUCCESS} [DRY RUN] Would disable Touch ID for sudo${NC}"
echo -e "${GRAY}${ICON_REVIEW} Target files: ${PAM_SUDO_FILE} and/or ${PAM_SUDO_LOCAL_FILE}${NC}"
fi
return 0
fi
if ! is_touchid_configured; then
echo -e "${YELLOW}Touch ID is not currently enabled${NC}"
return 0
@@ -303,12 +327,39 @@ show_menu() {
# Main
main() {
local command="${1:-}"
local command=""
local arg
for arg in "$@"; do
case "$arg" in
"--dry-run" | "-n")
export MOLE_DRY_RUN=1
;;
"--help" | "-h")
show_touchid_help
return 0
;;
enable | disable | status)
if [[ -z "$command" ]]; then
command="$arg"
else
log_error "Only one touchid command is supported per run"
return 1
fi
;;
*)
log_error "Unknown command: $arg"
return 1
;;
esac
done
if touchid_dry_run_enabled; then
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, No sudo authentication files will be modified"
echo ""
fi
case "$command" in
"--help" | "-h")
show_touchid_help
;;
enable)
enable_touchid
;;

View File

@@ -822,10 +822,17 @@ main() {
"--debug")
export MO_DEBUG=1
;;
"--dry-run" | "-n")
export MOLE_DRY_RUN=1
;;
esac
done
hide_cursor
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, No app files or settings will be modified"
printf '\n'
fi
local first_scan=true
while true; do