diff --git a/src/cli/utils/safe-delete-install.ts b/src/cli/utils/safe-delete-install.ts index 5330cc1..c752ccb 100644 --- a/src/cli/utils/safe-delete-install.ts +++ b/src/cli/utils/safe-delete-install.ts @@ -25,8 +25,12 @@ export function generateSafeDeleteBlock( ` for arg in "$@"; do`, ` [[ "$arg" =~ ^- ]] || files+=("$arg")`, ` done`, - ` if (( \${#files[@]} > 0 )); then`, - ` ${cmd} "\${files[@]}"`, + ` local existing=()`, + ` for f in "\${files[@]}"; do`, + ` [ -e "$f" ] || [ -L "$f" ] && existing+=("$f")`, + ` done`, + ` if (( \${#existing[@]} > 0 )); then`, + ` ${cmd} "\${existing[@]}"`, ` fi`, `}`, `command() {`, @@ -51,8 +55,14 @@ export function generateSafeDeleteBlock( ` set files $files $arg`, ` end`, ` end`, - ` if test (count $files) -gt 0`, - ` ${cmd} $files`, + ` set -l existing`, + ` for f in $files`, + ` if test -e $f; or test -L $f`, + ` set existing $existing $f`, + ` end`, + ` end`, + ` if test (count $existing) -gt 0`, + ` ${cmd} $existing`, ` end`, `end`, END_MARKER, @@ -96,7 +106,8 @@ export function generateSafeDeleteBlock( `}`, `function rm {`, ` $files = $args | Where-Object { $_ -notlike '-*' }`, - ` if ($files) { & ${cmd} @files }`, + ` $existing = $files | Where-Object { Test-Path $_ }`, + ` if ($existing) { & ${cmd} @existing }`, `}`, END_MARKER, ].join('\n'); diff --git a/src/templates/managed-settings.json b/src/templates/managed-settings.json index 70b683d..8971982 100644 --- a/src/templates/managed-settings.json +++ b/src/templates/managed-settings.json @@ -5,6 +5,15 @@ "Bash(rm -rf ~*)", "Bash(rm -rf .*)", "Bash(* rm -rf /*)", + "Bash(rm -r /*)", + "Bash(rm -r ~*)", + "Bash(rm -r .*)", + "Bash(rm -fr /*)", + "Bash(rm -fr ~*)", + "Bash(rm -fr .*)", + "Bash(rm -f /*)", + "Bash(rm -f ~*)", + "Bash(rm -f .*)", "Bash(dd if=*)", "Bash(dd*of=/dev/*)", "Bash(mkfs*)", @@ -85,12 +94,17 @@ "Bash(crontab*)", "Bash(rm /var/log*)", "Bash(rm -rf /var/log*)", + "Bash(rm -r /var/log*)", + "Bash(rm -f /var/log*)", + "Bash(rm -fr /var/log*)", "Bash(> /var/log*)", "Bash(truncate /var/log*)", "Bash(history -c*)", "Bash(history -w*)", "Bash(rm ~/.bash_history*)", + "Bash(rm -f ~/.bash_history*)", "Bash(rm ~/.zsh_history*)", + "Bash(rm -f ~/.zsh_history*)", "Bash(unset HISTFILE*)", "Bash(curl 169.254.169.254*)", "Bash(wget 169.254.169.254*)", diff --git a/tests/safe-delete-install.test.ts b/tests/safe-delete-install.test.ts index 61d9a3a..38d6c5d 100644 --- a/tests/safe-delete-install.test.ts +++ b/tests/safe-delete-install.test.ts @@ -10,27 +10,31 @@ import { } from '../src/cli/utils/safe-delete-install.js'; describe('generateSafeDeleteBlock', () => { - it('generates bash/zsh block with markers and both functions', () => { + it('generates bash/zsh block with markers, existence check, and both functions', () => { const block = generateSafeDeleteBlock('zsh', 'darwin', 'trash'); expect(block).not.toBeNull(); expect(block).toContain('# >>> DevFlow safe-delete >>>'); expect(block).toContain('# <<< DevFlow safe-delete <<<'); expect(block).toContain('rm() {'); expect(block).toContain('command() {'); - expect(block).toContain('trash "${files[@]}"'); + expect(block).toContain('[ -e "$f" ] || [ -L "$f" ]'); + expect(block).toContain('existing+=("$f")'); + expect(block).toContain('trash "${existing[@]}"'); }); it('generates bash block with trash-put command', () => { const block = generateSafeDeleteBlock('bash', 'linux', 'trash-put'); - expect(block).toContain('trash-put "${files[@]}"'); + expect(block).toContain('trash-put "${existing[@]}"'); }); - it('generates fish block with fish syntax', () => { + it('generates fish block with fish syntax and existence check', () => { const block = generateSafeDeleteBlock('fish', 'darwin', 'trash'); expect(block).not.toBeNull(); expect(block).toContain('# >>> DevFlow safe-delete >>>'); expect(block).toContain('function rm --description "Safe delete via trash"'); - expect(block).toContain('trash $files'); + expect(block).toContain('test -e $f; or test -L $f'); + expect(block).toContain('set existing $existing $f'); + expect(block).toContain('trash $existing'); expect(block).not.toContain('command()'); }); @@ -42,13 +46,21 @@ describe('generateSafeDeleteBlock', () => { expect(block).toContain('Remove-Alias rm'); }); - it('generates PowerShell macOS/Linux block with trash command', () => { + it('generates PowerShell macOS/Linux block with trash command and existence check', () => { const block = generateSafeDeleteBlock('powershell', 'darwin', 'trash'); expect(block).not.toBeNull(); - expect(block).toContain('& trash @files'); + expect(block).toContain('Test-Path $_'); + expect(block).toContain('& trash @existing'); expect(block).not.toContain('Microsoft.VisualBasic'); }); + it('generates PowerShell Windows block with Resolve-Path existence check', () => { + const block = generateSafeDeleteBlock('powershell', 'win32', null); + expect(block).not.toBeNull(); + expect(block).toContain('Resolve-Path $f -ErrorAction SilentlyContinue'); + expect(block).toContain('Test-Path $p -PathType Container'); + }); + it('returns null for unknown shell', () => { expect(generateSafeDeleteBlock('unknown', 'darwin', 'trash')).toBeNull(); });