UNPKG

@neurolint/cli

Version:

NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations

476 lines (405 loc) 11.8 kB
const fs = require('fs-extra'); const path = require('path'); const { EventEmitter } = require('events'); /** * NeuroLint Plugin System * Provides extensible architecture for custom rules and transformations */ class PluginSystem extends EventEmitter { constructor() { super(); this.plugins = new Map(); this.hooks = new Map(); this.middlewares = []; this.validators = new Map(); this.transformers = new Map(); this.config = { pluginDir: path.join(process.cwd(), '.neurolint', 'plugins'), autoload: true, maxExecutionTime: 30000, // 30 seconds sandbox: true }; } /** * Initialize plugin system */ async initialize(config = {}) { this.config = { ...this.config, ...config }; if (this.config.autoload) { await this.loadPluginsFromDirectory(); } this.emit('initialized'); } /** * Register a plugin */ registerPlugin(plugin) { if (!this.validatePlugin(plugin)) { throw new Error(`Invalid plugin: ${plugin.name || 'unknown'}`); } const pluginId = plugin.name || `plugin_${Date.now()}`; // Wrap plugin in sandbox if enabled const wrappedPlugin = this.config.sandbox ? this.sandboxPlugin(plugin) : plugin; this.plugins.set(pluginId, wrappedPlugin); // Register plugin hooks if (plugin.hooks) { Object.entries(plugin.hooks).forEach(([hookName, handler]) => { this.registerHook(hookName, handler, pluginId); }); } // Register plugin transformers if (plugin.transformers) { Object.entries(plugin.transformers).forEach(([name, transformer]) => { this.registerTransformer(name, transformer, pluginId); }); } // Register plugin validators if (plugin.validators) { Object.entries(plugin.validators).forEach(([name, validator]) => { this.registerValidator(name, validator, pluginId); }); } // Call plugin initialization if (plugin.initialize) { plugin.initialize(this.createPluginContext(pluginId)); } this.emit('plugin-registered', { pluginId, plugin: wrappedPlugin }); return pluginId; } /** * Load plugins from directory */ async loadPluginsFromDirectory() { const pluginDir = this.config.pluginDir; if (!(await fs.pathExists(pluginDir))) { return; } const pluginFiles = await fs.readdir(pluginDir); for (const file of pluginFiles) { if (file.endsWith('.js') || file.endsWith('.json')) { try { const pluginPath = path.join(pluginDir, file); const plugin = require(pluginPath); await this.registerPlugin(plugin); } catch (error) { console.warn(`Failed to load plugin ${file}:`, error.message); } } } } /** * Register a hook */ registerHook(hookName, handler, pluginId) { if (!this.hooks.has(hookName)) { this.hooks.set(hookName, []); } this.hooks.get(hookName).push({ handler, pluginId, priority: handler.priority || 0 }); // Sort by priority (higher priority first) this.hooks.get(hookName).sort((a, b) => b.priority - a.priority); } /** * Execute hooks */ async executeHook(hookName, context = {}) { const hooks = this.hooks.get(hookName) || []; const results = []; for (const hook of hooks) { try { const startTime = Date.now(); const result = await this.executeWithTimeout( hook.handler, [context], this.config.maxExecutionTime ); const executionTime = Date.now() - startTime; results.push({ pluginId: hook.pluginId, result, executionTime }); this.emit('hook-executed', { hookName, pluginId: hook.pluginId, executionTime, success: true }); } catch (error) { console.warn(`Hook ${hookName} failed for plugin ${hook.pluginId}:`, error.message); this.emit('hook-failed', { hookName, pluginId: hook.pluginId, error: error.message }); } } return results; } /** * Register a transformer */ registerTransformer(name, transformer, pluginId) { this.transformers.set(name, { transformer, pluginId, priority: transformer.priority || 0 }); } /** * Execute transformer */ async executeTransformer(name, ast, context = {}) { const transformerInfo = this.transformers.get(name); if (!transformerInfo) { throw new Error(`Transformer '${name}' not found`); } try { const startTime = Date.now(); const result = await this.executeWithTimeout( transformerInfo.transformer, [ast, context], this.config.maxExecutionTime ); const executionTime = Date.now() - startTime; this.emit('transformer-executed', { name, pluginId: transformerInfo.pluginId, executionTime, success: true }); return result; } catch (error) { this.emit('transformer-failed', { name, pluginId: transformerInfo.pluginId, error: error.message }); throw error; } } /** * Register a validator */ registerValidator(name, validator, pluginId) { this.validators.set(name, { validator, pluginId, priority: validator.priority || 0 }); } /** * Execute validator */ async executeValidator(name, code, context = {}) { const validatorInfo = this.validators.get(name); if (!validatorInfo) { throw new Error(`Validator '${name}' not found`); } try { const startTime = Date.now(); const result = await this.executeWithTimeout( validatorInfo.validator, [code, context], this.config.maxExecutionTime ); const executionTime = Date.now() - startTime; this.emit('validator-executed', { name, pluginId: validatorInfo.pluginId, executionTime, success: true }); return result; } catch (error) { this.emit('validator-failed', { name, pluginId: validatorInfo.pluginId, error: error.message }); throw error; } } /** * Execute all transformers for a given layer */ async executeLayerTransformers(layerId, ast, context = {}) { const layerTransformers = Array.from(this.transformers.entries()) .filter(([name, info]) => info.transformer.layer === layerId) .sort((a, b) => b[1].priority - a[1].priority); const results = []; for (const [name, info] of layerTransformers) { try { const result = await this.executeTransformer(name, ast, context); results.push({ name, pluginId: info.pluginId, result }); } catch (error) { console.warn(`Layer ${layerId} transformer ${name} failed:`, error.message); } } return results; } /** * Validate plugin structure */ validatePlugin(plugin) { if (!plugin || typeof plugin !== 'object') { return false; } // Required fields if (!plugin.name || typeof plugin.name !== 'string') { return false; } if (!plugin.version || typeof plugin.version !== 'string') { return false; } // Optional but validated fields if (plugin.hooks && typeof plugin.hooks !== 'object') { return false; } if (plugin.transformers && typeof plugin.transformers !== 'object') { return false; } if (plugin.validators && typeof plugin.validators !== 'object') { return false; } return true; } /** * Sandbox plugin execution */ sandboxPlugin(plugin) { // Create a sandboxed version of the plugin const sandboxedPlugin = { ...plugin }; // Wrap all functions to add error handling and timeouts Object.keys(sandboxedPlugin).forEach(key => { if (typeof sandboxedPlugin[key] === 'function') { const originalFn = sandboxedPlugin[key]; sandboxedPlugin[key] = async (...args) => { return this.executeWithTimeout(originalFn, args, this.config.maxExecutionTime); }; } }); return sandboxedPlugin; } /** * Execute function with timeout */ executeWithTimeout(fn, args, timeout) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(`Plugin execution timeout (${timeout}ms)`)); }, timeout); try { const result = fn.apply(null, args); if (result && typeof result.then === 'function') { // Handle promises result .then(value => { clearTimeout(timer); resolve(value); }) .catch(error => { clearTimeout(timer); reject(error); }); } else { // Handle synchronous functions clearTimeout(timer); resolve(result); } } catch (error) { clearTimeout(timer); reject(error); } }); } /** * Create plugin context */ createPluginContext(pluginId) { return { pluginId, emit: (event, data) => this.emit(`plugin:${pluginId}:${event}`, data), log: (message) => console.log(`[Plugin:${pluginId}] ${message}`), warn: (message) => console.warn(`[Plugin:${pluginId}] ${message}`), config: this.config, registerHook: (hookName, handler) => this.registerHook(hookName, handler, pluginId), registerTransformer: (name, transformer) => this.registerTransformer(name, transformer, pluginId), registerValidator: (name, validator) => this.registerValidator(name, validator, pluginId) }; } /** * Get plugin information */ getPlugin(pluginId) { return this.plugins.get(pluginId); } /** * List all plugins */ listPlugins() { return Array.from(this.plugins.entries()).map(([id, plugin]) => ({ id, name: plugin.name, version: plugin.version, description: plugin.description, enabled: true })); } /** * Remove plugin */ removePlugin(pluginId) { const plugin = this.plugins.get(pluginId); if (!plugin) { return false; } // Remove hooks this.hooks.forEach((hooks, hookName) => { this.hooks.set(hookName, hooks.filter(h => h.pluginId !== pluginId)); }); // Remove transformers Array.from(this.transformers.entries()).forEach(([name, info]) => { if (info.pluginId === pluginId) { this.transformers.delete(name); } }); // Remove validators Array.from(this.validators.entries()).forEach(([name, info]) => { if (info.pluginId === pluginId) { this.validators.delete(name); } }); // Call plugin cleanup if (plugin.cleanup) { try { plugin.cleanup(); } catch (error) { console.warn(`Plugin ${pluginId} cleanup failed:`, error.message); } } this.plugins.delete(pluginId); this.emit('plugin-removed', { pluginId }); return true; } /** * Get plugin statistics */ getStatistics() { return { totalPlugins: this.plugins.size, totalHooks: Array.from(this.hooks.values()).reduce((sum, hooks) => sum + hooks.length, 0), totalTransformers: this.transformers.size, totalValidators: this.validators.size, pluginList: this.listPlugins() }; } } module.exports = { PluginSystem };