UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

289 lines (288 loc) 10.5 kB
import resolve from 'resolve'; import * as path from 'path'; import * as fs from 'fs'; import logger from '../../../logger.js'; class LRUCache { cache; maxSize; constructor(maxSize) { this.cache = new Map(); this.maxSize = maxSize; } get(key) { const value = this.cache.get(key); if (value !== undefined) { this.cache.delete(key); this.cache.set(key, value); } return value; } set(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; if (firstKey !== undefined) { this.cache.delete(firstKey); } } this.cache.set(key, value); } has(key) { return this.cache.has(key); } clear() { this.cache.clear(); } get size() { return this.cache.size; } keys() { return this.cache.keys(); } delete(key) { return this.cache.delete(key); } } const importCache = new LRUCache(1000); const CACHE_MAX_SIZE = 1000; export function resolveImport(importPath, options) { if (isBuiltinModule(importPath)) { return importPath; } const cacheKey = `${options.fromFile}:${importPath}:${options.language}`; if (options.useCache !== false && importCache.has(cacheKey)) { return importCache.get(cacheKey); } try { if (isExternalPackage(importPath)) { const packageName = getPackageName(importPath); try { const basedir = path.dirname(options.fromFile); const resolvedPackage = resolve.sync(packageName, { basedir, preserveSymlinks: false }); logger.debug({ packageName, resolvedPackage }, 'Package exists but specific import cannot be resolved'); return importPath; } catch (packageError) { logger.debug({ err: packageError, packageName }, 'Package not found'); return importPath; } } const basedir = path.dirname(options.fromFile); const extensions = options.extensions || getDefaultExtensions(options.language); if (options.expandSecurityBoundary === true) { const expandedResolvedPath = resolveImportWithExpandedBoundary(importPath, basedir, extensions); if (expandedResolvedPath) { let finalPath = expandedResolvedPath; if (options.projectRoot && typeof options.projectRoot === 'string' && expandedResolvedPath.startsWith(options.projectRoot)) { try { finalPath = path.relative(options.projectRoot, expandedResolvedPath); finalPath = finalPath.replace(/\\/g, '/'); if (!finalPath.startsWith('./') && !finalPath.startsWith('../')) { finalPath = `./${finalPath}`; } } catch (pathError) { logger.warn({ err: pathError, projectRoot: options.projectRoot, resolvedPath: expandedResolvedPath }, 'Error making path relative to project root'); finalPath = expandedResolvedPath; } } logger.debug({ originalPath: importPath, resolvedPath: expandedResolvedPath, finalPath, projectRoot: options.projectRoot, securityExpanded: true }, 'Resolved import path with expanded security boundary'); if (options.useCache !== false) { importCache.set(cacheKey, finalPath); } return finalPath; } else { logger.debug({ importPath, basedir, securityExpanded: true }, 'Failed to resolve import with expanded boundary, falling back to standard resolution'); } } let standardResolvedPath; try { standardResolvedPath = resolve.sync(importPath, { basedir, extensions, preserveSymlinks: false }); } catch (error) { logger.debug({ err: error, importPath, basedir }, 'Error resolving import with standard resolution'); return importPath; } let finalPath = standardResolvedPath; if (options.projectRoot) { if (standardResolvedPath.startsWith(options.projectRoot)) { finalPath = path.relative(options.projectRoot, standardResolvedPath); finalPath = finalPath.replace(/\\/g, '/'); if (!finalPath.startsWith('./') && !finalPath.startsWith('../')) { finalPath = `./${finalPath}`; } logger.debug({ originalPath: importPath, resolvedPath: standardResolvedPath, finalPath, projectRoot: options.projectRoot }, 'Resolved import path relative to project root'); } else { logger.debug({ originalPath: importPath, resolvedPath: standardResolvedPath, projectRoot: options.projectRoot }, 'Resolved import path is outside project root'); } } if (options.useCache !== false) { if (importCache.size >= CACHE_MAX_SIZE) { const firstKey = importCache.keys().next().value; if (firstKey !== undefined) { importCache.delete(firstKey); } } importCache.set(cacheKey, finalPath); } return finalPath; } catch (error) { logger.debug({ err: error, importPath, fromFile: options.fromFile }, 'Error resolving import'); return importPath; } } export function clearImportCache() { importCache.clear(); } export function getImportCacheSize() { return importCache.size; } function isBuiltinModule(moduleName) { const builtinModules = [ 'assert', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'module', 'net', 'os', 'path', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'timers', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'wasi', 'worker_threads', 'zlib' ]; return builtinModules.includes(moduleName); } function isExternalPackage(moduleName) { return !moduleName.startsWith('.') && !moduleName.startsWith('/') && !isBuiltinModule(moduleName); } function getPackageName(moduleName) { if (moduleName.startsWith('@')) { const parts = moduleName.split('/'); if (parts.length >= 2) { return `${parts[0]}/${parts[1]}`; } } const parts = moduleName.split('/'); return parts[0]; } function resolveImportWithExpandedBoundary(importPath, basedir, extensions) { if (!importPath || typeof importPath !== 'string') { logger.warn({ importPath }, 'Invalid import path provided to resolveImportWithExpandedBoundary'); return null; } if (!basedir || typeof basedir !== 'string') { logger.warn({ basedir }, 'Invalid base directory provided to resolveImportWithExpandedBoundary'); return null; } logger.debug({ importPath, basedir, securityExpanded: true }, 'Attempting to resolve import with expanded security boundary'); try { const resolvedPath = resolve.sync(importPath, { basedir, extensions, preserveSymlinks: false }); if (!resolvedPath || typeof resolvedPath !== 'string') { logger.warn({ importPath, resolvedPath }, 'Resolve returned an invalid path'); return null; } logger.debug({ importPath, resolvedPath, securityExpanded: true }, 'Successfully resolved import with expanded security boundary'); return resolvedPath; } catch (error) { logger.debug({ err: error, importPath, basedir, securityExpanded: true }, 'Error resolving import with expanded boundary'); try { const potentialPath = path.resolve(basedir, importPath); for (const ext of extensions) { const fullPath = `${potentialPath}${ext}`; if (fs.existsSync(fullPath)) { logger.debug({ importPath, resolvedPath: fullPath, method: 'direct-fs' }, 'Resolved import path with direct filesystem check'); return fullPath; } } return null; } catch (directError) { logger.debug({ err: directError, importPath, basedir }, 'Error resolving import with direct filesystem check'); return null; } } } function getDefaultExtensions(language) { switch (language) { case 'javascript': return ['.js', '.json', '.node', '.mjs', '.cjs']; case 'typescript': return ['.ts', '.tsx', '.js', '.jsx', '.json', '.node']; case 'python': return ['.py', '.pyw', '.pyc', '.pyo', '.pyd']; case 'java': return ['.java', '.class', '.jar']; case 'csharp': return ['.cs', '.dll']; case 'go': return ['.go']; case 'ruby': return ['.rb', '.rake', '.gemspec']; case 'rust': return ['.rs']; case 'php': return ['.php']; default: return ['.js', '.json', '.node']; } }