UNPKG

@vibe-dev-kit/cli

Version:

Advanced Command-line toolkit that analyzes your codebase and deploys project-aware rules, memories, commands and agents to any AI coding assistant - VDK is the world's first Vibe Development Kit

372 lines (335 loc) 10.3 kB
/** * Base Integration Module * --------------------- * Abstract base class for all VDK integrations (IDEs, AI tools, platforms) * Provides common interface and functionality for integration detection, * configuration, and management. */ import { execSync } from 'child_process'; import fs from 'fs'; import os from 'os'; import path from 'path'; /** * Base class for all VDK integrations * All integration modules should extend this class */ export class BaseIntegration { constructor(name, projectPath = process.cwd()) { this.name = name; this.projectPath = projectPath; this.configPath = null; this.globalConfigPath = null; // Detection result cache this._detectionCache = null; this._detectionCacheTime = null; this._cacheValidityMs = 30000; // 30 seconds } /** * Abstract method - must be implemented by subclasses * Detect if this integration is being used in the project * @returns {Object} Detection result with isUsed, confidence, indicators, recommendations */ detectUsage() { throw new Error(`detectUsage() must be implemented by ${this.name} integration`); } /** * Abstract method - must be implemented by subclasses * Get integration-specific configuration paths * @returns {Object} Configuration paths relevant to this integration */ getConfigPaths() { throw new Error(`getConfigPaths() must be implemented by ${this.name} integration`); } /** * Abstract method - must be implemented by subclasses * Initialize integration configuration for VDK * @param {Object} options - Configuration options * @returns {boolean} Success status */ async initialize(options = {}) { throw new Error(`initialize() must be implemented by ${this.name} integration`); } /** * Get cached detection result or run fresh detection * @param {boolean} force - Force fresh detection ignoring cache * @returns {Object} Detection result */ getCachedDetection(force = false) { const now = Date.now(); const cacheExpired = !this._detectionCacheTime || now - this._detectionCacheTime > this._cacheValidityMs; if (force || !this._detectionCache || cacheExpired) { this._detectionCache = this.detectUsage(); this._detectionCacheTime = now; } return this._detectionCache; } /** * Check if integration is currently being used * @returns {boolean} True if integration is active */ isActive() { const detection = this.getCachedDetection(); return detection.isUsed && detection.confidence !== 'none'; } /** * Get confidence level of integration detection * @returns {string} Confidence level: none, low, medium, high */ getConfidence() { const detection = this.getCachedDetection(); return detection.confidence; } /** * Get recommendations for this integration * @returns {Array<string>} List of recommendations */ getRecommendations() { const detection = this.getCachedDetection(); return detection.recommendations || []; } /** * Get detection indicators for this integration * @returns {Array<string>} List of indicators found */ getIndicators() { const detection = this.getCachedDetection(); return detection.indicators || []; } /** * Common helper: Check if directory exists (synchronous) * @param {string} dirPath - Directory path to check * @returns {boolean} True if directory exists */ directoryExists(dirPath) { try { return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory(); } catch (error) { return false; } } /** * Common helper: Check if directory exists (asynchronous) * @param {string} dirPath - Directory path to check * @returns {Promise<boolean>} True if directory exists */ async directoryExistsAsync(dirPath) { try { const stats = await fs.promises.stat(dirPath); return stats.isDirectory(); } catch (error) { return false; } } /** * Common helper: Check if file exists (synchronous) * @param {string} filePath - File path to check * @returns {boolean} True if file exists */ fileExists(filePath) { try { return fs.existsSync(filePath) && fs.statSync(filePath).isFile(); } catch (error) { return false; } } /** * Common helper: Check if file exists (asynchronous) * @param {string} filePath - File path to check * @returns {Promise<boolean>} True if file exists */ async fileExistsAsync(filePath) { try { const stats = await fs.promises.stat(filePath); return stats.isFile(); } catch (error) { return false; } } /** * Common helper: Check if command is available in PATH * @param {string} command - Command to check * @returns {boolean} True if command is available */ commandExists(command) { try { execSync(`which ${command}`, { stdio: 'ignore' }); return true; } catch (error) { return false; } } /** * Common helper: Get command version * @param {string} command - Command to get version for * @param {string} versionFlag - Flag to get version (default: --version) * @returns {string|null} Version string or null if failed */ getCommandVersion(command, versionFlag = '--version') { try { const output = execSync(`${command} ${versionFlag}`, { encoding: 'utf8', stdio: 'pipe', }); return output.trim(); } catch (error) { return null; } } /** * Common helper: Check for recent file activity * @param {string} dirPath - Directory to check * @param {number} daysBack - How many days back to check (default: 7) * @returns {Array<string>} List of recently modified files */ getRecentActivity(dirPath, daysBack = 7) { if (!this.directoryExists(dirPath)) { return []; } try { const files = fs.readdirSync(dirPath); const cutoffTime = Date.now() - daysBack * 24 * 60 * 60 * 1000; return files.filter((file) => { const filePath = path.join(dirPath, file); try { const stats = fs.statSync(filePath); return stats.mtime.getTime() > cutoffTime; } catch (error) { return false; } }); } catch (error) { return []; } } /** * Common helper: Ensure directory exists, create if not * @param {string} dirPath - Directory path to ensure * @returns {boolean} True if directory exists or was created */ async ensureDirectory(dirPath) { try { if (!fs.existsSync(dirPath)) { await fs.promises.mkdir(dirPath, { recursive: true }); } return true; } catch (error) { return false; } } /** * Common helper: Read JSON file safely * @param {string} filePath - Path to JSON file * @returns {Object|null} Parsed JSON or null if failed */ readJsonFile(filePath) { try { const content = fs.readFileSync(filePath, 'utf8'); return JSON.parse(content); } catch (error) { return null; } } /** * Common helper: Write JSON file safely * @param {string} filePath - Path to write JSON file * @param {Object} data - Data to write * @returns {boolean} True if successful */ async writeJsonFile(filePath, data) { try { await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'); return true; } catch (error) { return false; } } /** * Get standard platform-specific paths * @returns {Object} Object containing common platform paths */ getPlatformPaths() { const home = os.homedir(); const platform = os.platform(); return { home, platform, config: platform === 'win32' ? path.join(home, 'AppData', 'Roaming') : platform === 'darwin' ? path.join(home, 'Library', 'Application Support') : path.join(home, '.config'), logs: platform === 'win32' ? path.join(home, 'AppData', 'Local', 'Logs') : platform === 'darwin' ? path.join(home, 'Library', 'Logs') : path.join(home, '.local', 'share', 'logs'), cache: platform === 'win32' ? path.join(home, 'AppData', 'Local', 'Cache') : platform === 'darwin' ? path.join(home, 'Library', 'Caches') : path.join(home, '.cache'), }; } /** * Common helper: Check gitignore for patterns * @param {Array<string>} patterns - Patterns to check for * @returns {Array<string>} Found patterns in gitignore */ checkGitignore(patterns) { const gitignorePath = path.join(this.projectPath, '.gitignore'); if (!this.fileExists(gitignorePath)) { return []; } try { const content = fs.readFileSync(gitignorePath, 'utf8'); return patterns.filter((pattern) => content.includes(pattern)); } catch (error) { return []; } } /** * Common helper: Ensure gitignore entry exists * @param {string} entry - Entry to add to .gitignore * @returns {Promise<boolean>} True if successful */ async ensureGitignoreEntry(entry) { const gitignorePath = path.join(this.projectPath, '.gitignore'); try { let content = ''; if (this.fileExists(gitignorePath)) { content = await fs.promises.readFile(gitignorePath, 'utf8'); } // Check if entry already exists if (content.includes(entry)) { return true; } // Add entry with proper spacing const newContent = content + (content && !content.endsWith('\n') ? '\n' : '') + entry + '\n'; await fs.promises.writeFile(gitignorePath, newContent, 'utf8'); return true; } catch (error) { return false; } } /** * Get integration summary for reporting * @returns {Object} Summary of integration status */ getSummary() { const detection = this.getCachedDetection(); return { name: this.name, isActive: this.isActive(), confidence: this.getConfidence(), indicatorCount: this.getIndicators().length, recommendationCount: this.getRecommendations().length, lastChecked: this._detectionCacheTime ? new Date(this._detectionCacheTime).toISOString() : null, }; } }