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.

142 lines (141 loc) 5.85 kB
import { exec } from 'child_process'; import { promisify } from 'util'; import * as fs from 'fs'; import * as path from 'path'; import logger from '../../../logger.js'; import { UnifiedSecurityConfigManager } from '../../vibe-task-manager/security/unified-security-config.js'; const execAsync = promisify(exec); export class DependencyCruiserAdapter { allowedDir; outputDir; securityValidator; cache = new Map(); tempFiles = []; constructor(allowedDir, outputDir) { this.allowedDir = allowedDir; this.outputDir = outputDir; this.securityValidator = UnifiedSecurityConfigManager.getInstance(); } async analyzeImports(filePath, options) { try { const cruiserOptions = { baseDir: options.baseDir || path.dirname(filePath), includeOnly: options.includeOnly, exclude: options.exclude, maxDepth: options.maxDepth, outputFormat: options.outputFormat || 'json', tsConfig: options.tsConfig }; const cacheKey = `${filePath}:${JSON.stringify(cruiserOptions)}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } if (!this.securityValidator.validatePathSecurity(filePath, { operation: 'read' })) { logger.warn({ filePath }, 'File path is outside allowed directory'); return []; } const tempOutputFile = path.join(this.outputDir, `dependency-cruiser-${Date.now()}.json`); this.tempFiles.push(tempOutputFile); const command = this.buildDependencyCruiserCommand(filePath, tempOutputFile, cruiserOptions); await execAsync(command); const resultJson = await fs.promises.readFile(tempOutputFile, 'utf8'); const result = JSON.parse(resultJson); const imports = this.convertToImportInfo(result, filePath); await fs.promises.unlink(tempOutputFile); const fileIndex = this.tempFiles.indexOf(tempOutputFile); if (fileIndex !== -1) { this.tempFiles.splice(fileIndex, 1); } this.cache.set(cacheKey, imports); return imports; } catch (error) { logger.error({ err: error, filePath }, 'Error analyzing imports with Dependency-Cruiser'); return []; } } buildDependencyCruiserCommand(filePath, outputFile, options) { const baseCommand = 'npx depcruise'; const outputFormat = options.outputFormat || 'json'; let command = `${baseCommand} --output-type ${outputFormat} --output-to ${outputFile}`; if (options.includeOnly && options.includeOnly.length > 0) { command += ` --include-only "${options.includeOnly.join(',')}"`; } if (options.exclude && options.exclude.length > 0) { command += ` --exclude "${options.exclude.join(',')}"`; } if (options.maxDepth) { command += ` --max-depth ${options.maxDepth}`; } if (options.tsConfig) { command += ` --ts-config ${options.tsConfig}`; } command += ` "${filePath}"`; return command; } convertToImportInfo(result, filePath) { const imports = []; const normalizedFilePath = path.normalize(filePath); const module = result.modules.find(m => path.normalize(m.source) === normalizedFilePath); if (!module) { return imports; } for (const dependency of module.dependencies) { const importedItems = []; importedItems.push({ name: this.extractNameFromPath(dependency.module), path: dependency.resolved, isDefault: false, isNamespace: false, nodeText: dependency.module }); const importInfo = { path: dependency.resolved, importedItems, isDynamic: dependency.dynamic, isRelative: this.isRelativePath(dependency.module), isCore: dependency.coreModule, moduleSystem: dependency.moduleSystem, metadata: { dependencyTypes: dependency.dependencyTypes, exoticallyRequired: dependency.exoticallyRequired } }; imports.push(importInfo); } return imports; } extractNameFromPath(importPath) { if (this.isRelativePath(importPath)) { const basename = path.basename(importPath); const extname = path.extname(basename); return extname ? basename.slice(0, -extname.length) : basename; } const parts = importPath.split('/'); if (parts[0].startsWith('@') && parts.length > 1) { return `${parts[0]}/${parts[1]}`; } return parts[0]; } isRelativePath(importPath) { return importPath.startsWith('./') || importPath.startsWith('../'); } dispose() { this.cache.clear(); if (this.tempFiles && this.tempFiles.length > 0) { this.tempFiles.forEach(file => { try { if (fs.existsSync(file)) { fs.unlinkSync(file); logger.debug({ file }, 'Deleted temporary file during DependencyCruiserAdapter disposal'); } } catch (error) { logger.warn({ file, error }, 'Failed to delete temporary file during DependencyCruiserAdapter disposal'); } }); this.tempFiles = []; } logger.debug('DependencyCruiserAdapter disposed'); } }