Public/Find-InProject.ps1
|
function Find-InProject { <# .SYNOPSIS Search across project files (respects common ignore patterns). .DESCRIPTION Searches for patterns in project files, automatically excluding node_modules, vendor, .git, and other common directories. .PARAMETER Pattern The search pattern (regex supported). .PARAMETER Type File type filter: php, js, ts, css, html, json, perl, py, etc. .PARAMETER Path Directory to search in (defaults to current directory). .PARAMETER CaseSensitive Make search case-sensitive. .PARAMETER AsJson Output as JSON for MCP tools. .EXAMPLE Find-InProject "function login" search "TODO" -Type php search "import" -Type js,ts #> [CmdletBinding()] param( [Parameter(Position = 0, Mandatory = $true)] [string]$Pattern, [Parameter(Position = 1)] [string]$Type, [string]$Path = '.', [switch]$CaseSensitive, [switch]$AsJson ) $searchPath = Resolve-Path $Path -ErrorAction SilentlyContinue if (-not $searchPath) { Write-Host "Path not found: $Path" -ForegroundColor Red return } $excludeDirs = @( 'node_modules', 'vendor', '.git', '__pycache__', '.idea', '.vscode', 'venv', '.venv', 'dist', 'build', 'coverage', '.next', '.nuxt', 'storage/framework', 'bootstrap/cache', 'target', 'bin', 'obj' ) $typeExtensions = @{ 'php' = @('*.php') 'js' = @('*.js', '*.jsx', '*.mjs') 'ts' = @('*.ts', '*.tsx') 'css' = @('*.css', '*.scss', '*.sass', '*.less') 'html' = @('*.html', '*.htm', '*.blade.php', '*.twig') 'json' = @('*.json') 'perl' = @('*.pl', '*.pm', '*.t') 'py' = @('*.py') 'python' = @('*.py') 'md' = @('*.md', '*.markdown') 'sql' = @('*.sql') 'yaml' = @('*.yaml', '*.yml') 'xml' = @('*.xml') 'config' = @('*.json', '*.yaml', '*.yml', '*.xml', '*.ini', '*.env*') } $includePatterns = @() if ($Type) { $types = $Type.ToLower() -split '[,\s]+' foreach ($t in $types) { if ($typeExtensions.ContainsKey($t)) { $includePatterns += $typeExtensions[$t] } else { $includePatterns += "*.$t" } } } $files = Get-ChildItem -Path $searchPath -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $filePath = $_.FullName foreach ($excludeDir in $excludeDirs) { if ($filePath -match "[\\/]$excludeDir[\\/]") { return $false } } if ($includePatterns.Count -gt 0) { $matched = $false foreach ($incPattern in $includePatterns) { if ($_.Name -like $incPattern) { $matched = $true break } } return $matched } $excludeExtensions = @('.exe', '.dll', '.zip', '.tar', '.gz', '.png', '.jpg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot', '.mp3', '.mp4', '.pdf') if ($excludeExtensions -contains $_.Extension.ToLower()) { return $false } return $true } $results = @() $totalMatches = 0 foreach ($file in $files) { try { $content = Get-Content $file.FullName -ErrorAction Stop $lineNum = 0 $fileMatches = @() foreach ($line in $content) { $lineNum++ $isMatch = if ($CaseSensitive) { $line -cmatch $Pattern } else { $line -match $Pattern } if ($isMatch) { $fileMatches += [ordered]@{ line = $lineNum content = $line.Trim() } $totalMatches++ } } if ($fileMatches.Count -gt 0) { $relativePath = $file.FullName.Replace($searchPath.Path, '').TrimStart('\', '/') $results += [ordered]@{ file = $relativePath matches = $fileMatches count = $fileMatches.Count } } } catch { # Skip files that can't be read } } if ($AsJson) { @{ pattern = $Pattern totalMatches = $totalMatches fileCount = $results.Count results = $results } | ConvertTo-Json -Depth 10 return } Write-Host "" Write-Host "Search: " -NoNewline -ForegroundColor Cyan Write-Host $Pattern -ForegroundColor Yellow if ($Type) { Write-Host "Type: " -NoNewline -ForegroundColor Cyan Write-Host $Type -ForegroundColor Yellow } Write-Host "" if ($results.Count -eq 0) { Write-Host "No matches found." -ForegroundColor DarkGray Write-Host "" return } foreach ($result in $results) { Write-Host $result.file -ForegroundColor Green foreach ($match in $result.matches | Select-Object -First 5) { Write-Host " " -NoNewline Write-Host "$($match.line.ToString().PadLeft(4)):" -NoNewline -ForegroundColor DarkGray $line = $match.content if ($line -match "($Pattern)") { $parts = $line -split "($Pattern)" foreach ($part in $parts) { if ($part -match "^$Pattern$") { Write-Host $part -NoNewline -ForegroundColor Black -BackgroundColor Yellow } else { Write-Host $part -NoNewline -ForegroundColor White } } Write-Host "" } else { Write-Host " $line" -ForegroundColor White } } if ($result.matches.Count -gt 5) { Write-Host " ... $($result.matches.Count - 5) more matches" -ForegroundColor DarkGray } Write-Host "" } Write-Host "Found " -NoNewline -ForegroundColor Cyan Write-Host $totalMatches -NoNewline -ForegroundColor Yellow Write-Host " matches in " -NoNewline -ForegroundColor Cyan Write-Host $results.Count -NoNewline -ForegroundColor Yellow Write-Host " files" -ForegroundColor Cyan Write-Host "" } |