diff --git a/lib/clean/project.sh b/lib/clean/project.sh index db5bf8f..f91d2b8 100644 --- a/lib/clean/project.sh +++ b/lib/clean/project.sh @@ -226,6 +226,29 @@ load_purge_config() { # Initialize paths on script load. load_purge_config +# Args: $1 - directory path +# Determine whether a directory is a project root. +# This is used to safely allow cleaning direct-child artifacts when +# users configure a single project directory as a purge search path. +is_purge_project_root() { + local dir="$1" + local indicator + + for indicator in "${MONOREPO_INDICATORS[@]}"; do + if [[ -e "$dir/$indicator" ]]; then + return 0 + fi + done + + for indicator in "${PROJECT_INDICATORS[@]}"; do + if [[ -e "$dir/$indicator" ]]; then + return 0 + fi + done + + return 1 +} + # Args: $1 - path to check # Safe cleanup requires the path be inside a project directory. is_safe_project_artifact() { @@ -234,10 +257,20 @@ is_safe_project_artifact() { if [[ "$path" != /* ]]; then return 1 fi + + if [[ "$path" != "$search_path/"* ]]; then + return 1 + fi + # Must not be a direct child of the search root. local relative_path="${path#"$search_path"/}" local depth=$(echo "$relative_path" | LC_ALL=C tr -cd '/' | wc -c) if [[ $depth -lt 1 ]]; then + # Allow direct-child artifacts only when the search path is itself + # a project root (single-project mode). + if is_purge_project_root "$search_path"; then + return 0 + fi return 1 fi return 0 diff --git a/tests/purge.bats b/tests/purge.bats index 31cf904..e1e6931 100644 --- a/tests/purge.bats +++ b/tests/purge.bats @@ -76,6 +76,22 @@ setup() { [[ "$result" == "ALLOWED" ]] } +@test "is_safe_project_artifact: allows direct child when search path is project root" { + mkdir -p "$HOME/single-project/node_modules" + touch "$HOME/single-project/package.json" + + result=$(bash -c " + source '$PROJECT_ROOT/lib/clean/project.sh' + if is_safe_project_artifact '$HOME/single-project/node_modules' '$HOME/single-project'; then + echo 'ALLOWED' + else + echo 'BLOCKED' + fi + ") + + [[ "$result" == "ALLOWED" ]] +} + @test "filter_nested_artifacts: removes nested node_modules" { mkdir -p "$HOME/www/project/node_modules/package/node_modules" @@ -434,6 +450,28 @@ EOF [[ "$result" == "SKIPPED" ]] } +@test "scan_purge_targets: finds direct-child artifacts in project root with find mode" { + mkdir -p "$HOME/single-project/node_modules" + touch "$HOME/single-project/package.json" + + local scan_output + scan_output="$(mktemp)" + + result=$(bash -c " + source '$PROJECT_ROOT/lib/clean/project.sh' + MO_USE_FIND=1 scan_purge_targets '$HOME/single-project' '$scan_output' + if grep -q '$HOME/single-project/node_modules' '$scan_output'; then + echo 'FOUND' + else + echo 'MISSING' + fi + ") + + rm -f "$scan_output" + + [[ "$result" == "FOUND" ]] +} + @test "is_recently_modified: detects recent projects" { mkdir -p "$HOME/www/project/node_modules" touch "$HOME/www/project/package.json"