UNPKG

@aaswe/codebase-ai

Version:

AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs

375 lines 13.9 kB
"use strict"; /** * Installation Detector * * Detects package installations, updates, and dependency changes * to trigger appropriate analysis workflows. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstallationDetector = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const events_1 = require("events"); const logger_1 = __importDefault(require("../../utils/logger")); /** * Installation Detector * * Monitors package installations and dependency changes to trigger * automatic project analysis when significant changes occur. */ class InstallationDetector extends events_1.EventEmitter { config; isActive = false; watchers = []; lastPackageSnapshot; debounceTimer; constructor(config = {}) { super(); this.config = { projectRoot: process.cwd(), watchLockFiles: true, watchPackageJson: true, debounceDelay: 3000, // 3 seconds ignoreDevDependencies: false, ...config }; } /** * Start monitoring for installation events */ async startMonitoring() { if (this.isActive) { return; } try { logger_1.default.info('Starting installation monitoring', { projectRoot: this.config.projectRoot, watchLockFiles: this.config.watchLockFiles, watchPackageJson: this.config.watchPackageJson }); // Take initial snapshot this.lastPackageSnapshot = await this.createPackageSnapshot(); // Setup file watchers if (this.config.watchPackageJson) { await this.setupPackageJsonWatcher(); } if (this.config.watchLockFiles) { await this.setupLockFileWatchers(); } this.isActive = true; logger_1.default.info('Installation monitoring started successfully'); } catch (error) { logger_1.default.error('Failed to start installation monitoring', { error }); throw error; } } /** * Stop monitoring */ async stopMonitoring() { if (!this.isActive) { return; } logger_1.default.info('Stopping installation monitoring'); // Clear debounce timer if (this.debounceTimer) { clearTimeout(this.debounceTimer); this.debounceTimer = undefined; } // Close all watchers for (const watcher of this.watchers) { try { watcher.close(); } catch (error) { logger_1.default.warn('Failed to close watcher', { error }); } } this.watchers = []; this.isActive = false; logger_1.default.info('Installation monitoring stopped'); } /** * Manually detect changes (for testing or one-time checks) */ async detectChanges() { try { const currentSnapshot = await this.createPackageSnapshot(); if (!this.lastPackageSnapshot) { this.lastPackageSnapshot = currentSnapshot; return null; } const changes = this.compareSnapshots(this.lastPackageSnapshot, currentSnapshot); if (changes.length === 0) { return null; } const event = { type: this.determineEventType(changes), timestamp: new Date(), projectRoot: this.config.projectRoot, changes, packageManager: await this.detectPackageManager(), triggerAnalysis: this.shouldTriggerAnalysis(changes) }; this.lastPackageSnapshot = currentSnapshot; logger_1.default.info('Package changes detected', { changeCount: changes.length, triggerAnalysis: event.triggerAnalysis, changes: changes.map(c => `${c.name}: ${c.changeType}`) }); return event; } catch (error) { logger_1.default.error('Failed to detect package changes', { error }); return null; } } /** * Get current monitoring status */ getStatus() { return { isActive: this.isActive, config: { ...this.config }, watcherCount: this.watchers.length, lastSnapshot: this.lastPackageSnapshot ? new Date() : undefined }; } // Private methods async setupPackageJsonWatcher() { try { const chokidar = await Promise.resolve().then(() => __importStar(require('chokidar'))); const packageJsonPath = path.join(this.config.projectRoot, 'package.json'); const watcher = chokidar.watch(packageJsonPath, { persistent: false, ignoreInitial: true }); watcher.on('change', () => { logger_1.default.debug('package.json changed'); this.scheduleChangeDetection(); }); watcher.on('error', (error) => { logger_1.default.warn('Package.json watcher error', { error }); }); this.watchers.push(watcher); logger_1.default.debug('Package.json watcher setup completed'); } catch (error) { logger_1.default.warn('Failed to setup package.json watcher', { error }); } } async setupLockFileWatchers() { try { const chokidar = await Promise.resolve().then(() => __importStar(require('chokidar'))); const lockFiles = [ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml' ]; for (const lockFile of lockFiles) { const lockFilePath = path.join(this.config.projectRoot, lockFile); try { await fs.access(lockFilePath); const watcher = chokidar.watch(lockFilePath, { persistent: false, ignoreInitial: true }); watcher.on('change', () => { logger_1.default.debug(`${lockFile} changed`); this.scheduleChangeDetection(); }); watcher.on('add', () => { logger_1.default.debug(`${lockFile} created`); this.scheduleChangeDetection(); }); watcher.on('error', (error) => { logger_1.default.warn(`${lockFile} watcher error`, { error }); }); this.watchers.push(watcher); logger_1.default.debug(`${lockFile} watcher setup completed`); } catch { // Lock file doesn't exist, skip } } } catch (error) { logger_1.default.warn('Failed to setup lock file watchers', { error }); } } scheduleChangeDetection() { // Clear existing timer if (this.debounceTimer) { clearTimeout(this.debounceTimer); } // Schedule detection with debounce this.debounceTimer = setTimeout(async () => { try { const event = await this.detectChanges(); if (event) { this.emit('installation_detected', event); } } catch (error) { logger_1.default.error('Failed to detect changes in scheduled detection', { error }); } }, this.config.debounceDelay); } async createPackageSnapshot() { const snapshot = new Map(); try { const packageJsonPath = path.join(this.config.projectRoot, 'package.json'); const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); // Add production dependencies if (packageJson.dependencies) { for (const [name, version] of Object.entries(packageJson.dependencies)) { snapshot.set(name, `prod:${version}`); } } // Add dev dependencies if not ignored if (!this.config.ignoreDevDependencies && packageJson.devDependencies) { for (const [name, version] of Object.entries(packageJson.devDependencies)) { snapshot.set(name, `dev:${version}`); } } } catch (error) { logger_1.default.warn('Failed to create package snapshot', { error }); } return snapshot; } compareSnapshots(oldSnapshot, newSnapshot) { const changes = []; // Check for added and updated packages for (const [name, newVersionInfo] of newSnapshot) { const [newType, newVersion] = newVersionInfo.split(':'); const isDev = newType === 'dev'; if (!oldSnapshot.has(name)) { // Package added changes.push({ name, newVersion, changeType: 'added', isDev }); } else { const oldVersionInfo = oldSnapshot.get(name); const [, oldVersion] = oldVersionInfo.split(':'); if (oldVersion !== newVersion) { // Package updated changes.push({ name, oldVersion, newVersion, changeType: 'updated', isDev }); } } } // Check for removed packages for (const [name, oldVersionInfo] of oldSnapshot) { if (!newSnapshot.has(name)) { const [oldType, oldVersion] = oldVersionInfo.split(':'); const isDev = oldType === 'dev'; changes.push({ name, oldVersion, newVersion: '', changeType: 'removed', isDev }); } } return changes; } determineEventType(changes) { const hasAdded = changes.some(c => c.changeType === 'added'); const hasUpdated = changes.some(c => c.changeType === 'updated'); const hasRemoved = changes.some(c => c.changeType === 'removed'); if (hasAdded && !hasUpdated && !hasRemoved) { return 'package_installed'; } else if (hasUpdated && !hasAdded && !hasRemoved) { return 'package_updated'; } else if (hasRemoved && !hasAdded && !hasUpdated) { return 'package_removed'; } else { return 'lockfile_changed'; } } shouldTriggerAnalysis(changes) { // Don't trigger for dev dependencies if ignored if (this.config.ignoreDevDependencies) { const nonDevChanges = changes.filter(c => !c.isDev); if (nonDevChanges.length === 0) { return false; } } // Trigger analysis for any significant changes return changes.some(c => c.changeType === 'added' || c.changeType === 'updated' || (c.changeType === 'removed' && !c.isDev)); } async detectPackageManager() { const lockFiles = [ { file: 'package-lock.json', manager: 'npm' }, { file: 'yarn.lock', manager: 'yarn' }, { file: 'pnpm-lock.yaml', manager: 'pnpm' } ]; for (const { file, manager } of lockFiles) { try { const lockFilePath = path.join(this.config.projectRoot, file); await fs.access(lockFilePath); return manager; } catch { // File doesn't exist, continue } } return 'unknown'; } } exports.InstallationDetector = InstallationDetector; //# sourceMappingURL=InstallationDetector.js.map