@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
JavaScript
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 };