Skip to main content
Glama
search-utils.ts5.2 kB
import { promises as fs } from 'fs'; import path from 'path'; import { FileSystemItem } from './types.js'; interface SearchResult { path: string; matches?: Array<{ line: number; content: string; }>; } /** * Search for files by path pattern (glob-like) */ export async function searchByPath( rootDir: string, pattern: string, items?: FileSystemItem[] ): Promise<string[]> { // If items not provided, get them if (!items) { const { listDirectory } = await import('./fs-utils.js'); items = await listDirectory(rootDir, '.', true, 10); } const results: string[] = []; // Convert glob pattern to regex // Order matters: we process ? first to avoid breaking regex syntax added later let regexPattern = pattern .replace(/\./g, '\\.') // Escape dots: . → \. .replace(/\?/g, '[^/]') // ? → match single non-slash char .replace(/\*\*/g, '__DOUBLE_STAR__') // ** → temporary placeholder .replace(/\*/g, '[^/]*') // * → match any non-slash chars .replace(/__DOUBLE_STAR__\//g, '(?:.*/)?') // **/ → optional path (0+ dirs) .replace(/__DOUBLE_STAR__/g, '.*') // ** → match anything .replace(/\{([^}]+)\}/g, (match, group) => { // {a,b,c} → (a|b|c) for alternation const options = group.split(','); return '(' + options.join('|') + ')'; }); // Handle different pattern types if (pattern.startsWith('**/')) { // Pattern like **/*.js - match anywhere regexPattern = regexPattern; } else if (pattern.includes('/')) { // Pattern like src/**/*.js - must match from start regexPattern = '^/' + regexPattern; } else { // Pattern like *.js or ?.ts - match filename in any directory regexPattern = '^.*/' + regexPattern; } const regex = new RegExp(regexPattern + '$', 'i'); const searchItems = (items: FileSystemItem[]) => { for (const item of items) { // Skip node_modules and common build directories if (item.path.includes('/node_modules/') || item.path.includes('/dist/') || item.path.includes('/.git/')) { continue; } if (regex.test(item.path)) { results.push(item.path); } if (item.children) { searchItems(item.children); } } }; searchItems(items); return results; } /** * Search for content within files */ export async function searchContent( rootDir: string, query: string, options: { include?: string; ignoreCase?: boolean; maxResults?: number; maxFilesToSearch?: number; } = {} ): Promise<SearchResult[]> { const { listDirectory } = await import('./fs-utils.js'); const items = await listDirectory(rootDir, '.', true, 10); const results: SearchResult[] = []; const regex = new RegExp(query, options.ignoreCase ? 'gi' : 'g'); let resultCount = 0; let filesSearched = 0; const maxFilesToSearch = options.maxFilesToSearch || 1000; const searchItems = async (items: FileSystemItem[]) => { for (const item of items) { if (options.maxResults && resultCount >= options.maxResults) { break; } if (item.type === 'file') { // Check file search limit if (filesSearched >= maxFilesToSearch) { break; } // Check if file matches include pattern if (options.include) { const includeRegex = new RegExp( options.include .replace(/\./g, '\\.') .replace(/\*/g, '.*') .replace(/\?/g, '.') .replace(/\{([^}]+)\}/g, (match, group) => { // Handle {js,ts} style patterns const options = group.split(','); return '(' + options.join('|') + ')'; }), 'i' ); if (!includeRegex.test(item.path)) { continue; } } try { const fullPath = path.join(rootDir, item.path.slice(1)); // Remove leading / // Check file size before reading const stats = await fs.stat(fullPath); if (stats.size > 1024 * 1024) { // Skip files > 1MB continue; } filesSearched++; const content = await fs.readFile(fullPath, 'utf-8'); const lines = content.split('\n'); const matches: Array<{ line: number; content: string }> = []; lines.forEach((line, index) => { if (regex.test(line)) { matches.push({ line: index + 1, content: line.trim() }); } }); if (matches.length > 0) { results.push({ path: item.path, matches: matches.slice(0, 5) // Limit to 5 matches per file }); resultCount++; } } catch (error) { // Skip files that can't be read } } if (item.children) { await searchItems(item.children); } } }; await searchItems(items); return results; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/davstr1/peekabooMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server