UNPKG

@neurolint/cli

Version:

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

545 lines (439 loc) 14 kB
const fs = require('fs-extra'); const path = require('path'); const { PluginSystem } = require('./plugin-system'); const chalk = require('chalk'); /** * Plugin Manager for NeuroLint CLI * Handles plugin installation, removal, and management */ class PluginManager { constructor(config = {}) { this.pluginSystem = new PluginSystem(); this.registryUrl = config.registryUrl || 'https://registry.neurolint.dev/plugins'; this.pluginDir = config.pluginDir || path.join(process.cwd(), '.neurolint', 'plugins'); this.configFile = path.join(process.cwd(), '.neurolint', 'plugins.json'); this.installedPlugins = new Map(); } /** * Initialize plugin manager */ async initialize() { await fs.ensureDir(this.pluginDir); await this.loadInstalledPlugins(); await this.pluginSystem.initialize({ pluginDir: this.pluginDir }); } /** * Install plugin from various sources */ async installPlugin(source, options = {}) { console.log(chalk.blue(`Installing plugin: ${source}`)); let plugin; let pluginId; try { if (source.startsWith('http') || source.startsWith('https')) { // Install from URL plugin = await this.installFromUrl(source); } else if (source.includes('/') || source.includes('\\')) { // Install from local path plugin = await this.installFromPath(source); } else if (source.startsWith('@') || source.includes('/')) { // Install from npm package plugin = await this.installFromNpm(source); } else { // Install from NeuroLint registry plugin = await this.installFromRegistry(source); } // Register plugin with the system pluginId = await this.pluginSystem.registerPlugin(plugin); // Save plugin metadata await this.savePluginMetadata(pluginId, { source, installedAt: new Date().toISOString(), version: plugin.version, enabled: true, ...options }); console.log(chalk.green(`Plugin installed successfully: ${plugin.name} v${plugin.version}`)); return { pluginId, plugin }; } catch (error) { console.error(chalk.red(`Plugin installation failed: ${error.message}`)); throw error; } } /** * Install plugin from URL */ async installFromUrl(url) { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch plugin from ${url}: ${response.statusText}`); } const pluginCode = await response.text(); // Create temporary file and require it const tempFile = path.join(this.pluginDir, `temp_${Date.now()}.js`); await fs.writeFile(tempFile, pluginCode); try { const plugin = require(tempFile); // Move to permanent location const pluginFile = path.join(this.pluginDir, `${plugin.name}.js`); await fs.move(tempFile, pluginFile); return plugin; } catch (error) { await fs.remove(tempFile); throw error; } } /** * Install plugin from local path */ async installFromPath(localPath) { const absolutePath = path.resolve(localPath); if (!(await fs.pathExists(absolutePath))) { throw new Error(`Plugin file not found: ${absolutePath}`); } const plugin = require(absolutePath); // Copy to plugin directory const pluginFile = path.join(this.pluginDir, `${plugin.name}.js`); await fs.copy(absolutePath, pluginFile); return plugin; } /** * Install plugin from npm package */ async installFromNpm(packageName) { // This would require npm installation - simplified for now throw new Error('NPM package installation not yet implemented. Use local path or URL.'); } /** * Install plugin from NeuroLint registry */ async installFromRegistry(pluginName) { const registryUrl = `${this.registryUrl}/${pluginName}`; try { const response = await fetch(registryUrl); if (!response.ok) { throw new Error(`Plugin not found in registry: ${pluginName}`); } const pluginInfo = await response.json(); // Download plugin from registry const downloadUrl = pluginInfo.downloadUrl; return await this.installFromUrl(downloadUrl); } catch (error) { throw new Error(`Failed to install from registry: ${error.message}`); } } /** * Remove plugin */ async removePlugin(pluginName) { console.log(chalk.blue(`Removing plugin: ${pluginName}`)); try { // Find plugin by name const pluginId = this.findPluginByName(pluginName); if (!pluginId) { throw new Error(`Plugin not found: ${pluginName}`); } // Remove from plugin system this.pluginSystem.removePlugin(pluginId); // Remove plugin file const pluginFile = path.join(this.pluginDir, `${pluginName}.js`); if (await fs.pathExists(pluginFile)) { await fs.remove(pluginFile); } // Remove metadata this.installedPlugins.delete(pluginId); await this.saveInstalledPlugins(); console.log(chalk.green(`Plugin removed successfully: ${pluginName}`)); } catch (error) { console.error(chalk.red(`Plugin removal failed: ${error.message}`)); throw error; } } /** * List installed plugins */ listPlugins() { const plugins = this.pluginSystem.listPlugins(); return plugins.map(plugin => { const metadata = this.installedPlugins.get(plugin.id) || {}; return { ...plugin, source: metadata.source || 'unknown', installedAt: metadata.installedAt || 'unknown', enabled: metadata.enabled !== false }; }); } /** * Enable/disable plugin */ async togglePlugin(pluginName, enabled) { const pluginId = this.findPluginByName(pluginName); if (!pluginId) { throw new Error(`Plugin not found: ${pluginName}`); } const metadata = this.installedPlugins.get(pluginId) || {}; metadata.enabled = enabled; this.installedPlugins.set(pluginId, metadata); await this.saveInstalledPlugins(); console.log(chalk.green(`Plugin ${pluginName} ${enabled ? 'enabled' : 'disabled'}`)); } /** * Update plugin */ async updatePlugin(pluginName) { console.log(chalk.blue(`Updating plugin: ${pluginName}`)); try { const pluginId = this.findPluginByName(pluginName); if (!pluginId) { throw new Error(`Plugin not found: ${pluginName}`); } const metadata = this.installedPlugins.get(pluginId); if (!metadata || !metadata.source) { throw new Error(`Cannot update plugin: missing source information`); } // Remove old version await this.removePlugin(pluginName); // Install new version await this.installPlugin(metadata.source, { update: true }); console.log(chalk.green(`Plugin updated successfully: ${pluginName}`)); } catch (error) { console.error(chalk.red(`Plugin update failed: ${error.message}`)); throw error; } } /** * Search registry for plugins */ async searchPlugins(query) { try { const searchUrl = `${this.registryUrl}/search?q=${encodeURIComponent(query)}`; const response = await fetch(searchUrl); if (!response.ok) { throw new Error(`Search failed: ${response.statusText}`); } const results = await response.json(); return results.plugins || []; } catch (error) { console.warn(chalk.yellow(`Search failed: ${error.message}`)); return []; } } /** * Create plugin template */ async createPluginTemplate(pluginName, options = {}) { const pluginDir = path.join(process.cwd(), pluginName); if (await fs.pathExists(pluginDir)) { throw new Error(`Directory already exists: ${pluginDir}`); } await fs.ensureDir(pluginDir); const template = this.generatePluginTemplate(pluginName, options); // Write main plugin file await fs.writeFile(path.join(pluginDir, 'index.js'), template.main); // Write package.json await fs.writeFile(path.join(pluginDir, 'package.json'), JSON.stringify(template.packageJson, null, 2)); // Write README await fs.writeFile(path.join(pluginDir, 'README.md'), template.readme); // Write test file await fs.writeFile(path.join(pluginDir, 'test.js'), template.test); console.log(chalk.green(`Plugin template created: ${pluginDir}`)); console.log(chalk.gray(`Next steps:`)); console.log(chalk.gray(` cd ${pluginName}`)); console.log(chalk.gray(` # Edit index.js to implement your plugin`)); console.log(chalk.gray(` neurolint plugin install .`)); } /** * Generate plugin template */ generatePluginTemplate(pluginName, options) { const className = pluginName.charAt(0).toUpperCase() + pluginName.slice(1).replace(/-/g, ''); const main = `/** * ${pluginName} - NeuroLint Plugin * ${options.description || 'Custom NeuroLint plugin'} */ module.exports = { name: '${pluginName}', version: '1.0.0', description: '${options.description || 'Custom NeuroLint plugin'}', author: '${options.author || 'Your Name'}', // Plugin initialization initialize(context) { context.log('${className} plugin initialized'); }, // Plugin cleanup cleanup() { // Cleanup resources if needed }, // Hook implementations hooks: { 'before-analysis': { priority: 0, handler: async (context) => { // Called before code analysis starts return { success: true }; } }, 'after-analysis': { priority: 0, handler: async (context) => { // Called after code analysis completes return { success: true }; } } }, // Custom transformers transformers: { '${pluginName}-transform': { layer: 3, // Target layer (1-6) priority: 0, handler: async (ast, context) => { // Implement your AST transformation here const transformations = []; // Example transformation // traverse(ast, { // FunctionDeclaration(path) { // // Your transformation logic // } // }); return transformations; } } }, // Custom validators validators: { '${pluginName}-validator': { priority: 0, handler: async (code, context) => { // Implement your code validation here const issues = []; // Example validation if (code.includes('// TODO')) { issues.push({ type: 'todo-comment', severity: 'info', message: 'TODO comment found', line: 1 // Calculate actual line number }); } return issues; } } } }; `; const packageJson = { name: pluginName, version: '1.0.0', description: options.description || 'Custom NeuroLint plugin', main: 'index.js', scripts: { test: 'node test.js' }, keywords: ['neurolint', 'plugin', 'code-analysis'], author: options.author || 'Your Name', license: 'MIT', peerDependencies: { '@babel/parser': '^7.0.0', '@babel/traverse': '^7.0.0', '@babel/types': '^7.0.0' } }; const readme = `# ${pluginName} ${options.description || 'Custom NeuroLint plugin'} ## Installation \`\`\`bash neurolint plugin install . \`\`\` ## Usage This plugin will be automatically loaded by NeuroLint when installed. ## Configuration Add plugin configuration to your \`.neurolintrc.json\`: \`\`\`json { "plugins": { "${pluginName}": { "enabled": true, "options": { // Plugin-specific options } } } } \`\`\` ## Development 1. Clone this repository 2. Make your changes to \`index.js\` 3. Test with \`npm test\` 4. Install locally with \`neurolint plugin install .\` ## License MIT `; const test = `/** * Basic test for ${pluginName} plugin */ const plugin = require('./index.js'); // Test plugin structure console.log('Testing plugin structure...'); if (!plugin.name) { throw new Error('Plugin must have a name'); } if (!plugin.version) { throw new Error('Plugin must have a version'); } console.log(\`Plugin \${plugin.name} v\${plugin.version} structure is valid\`); // Test plugin initialization if (plugin.initialize) { const mockContext = { log: (msg) => console.log(\`[Mock] \${msg}\`), warn: (msg) => console.warn(\`[Mock] \${msg}\`), pluginId: 'test-plugin' }; plugin.initialize(mockContext); console.log('Plugin initialization test passed'); } console.log('All tests passed!'); `; return { main, packageJson, readme, test }; } /** * Helper methods */ findPluginByName(name) { const plugins = this.pluginSystem.listPlugins(); const plugin = plugins.find(p => p.name === name); return plugin ? plugin.id : null; } async loadInstalledPlugins() { try { if (await fs.pathExists(this.configFile)) { const config = await fs.readJson(this.configFile); this.installedPlugins = new Map(Object.entries(config.plugins || {})); } } catch (error) { console.warn('Failed to load plugin configuration:', error.message); } } async saveInstalledPlugins() { try { const config = { plugins: Object.fromEntries(this.installedPlugins) }; await fs.writeJson(this.configFile, config, { spaces: 2 }); } catch (error) { console.warn('Failed to save plugin configuration:', error.message); } } async savePluginMetadata(pluginId, metadata) { this.installedPlugins.set(pluginId, metadata); await this.saveInstalledPlugins(); } } module.exports = { PluginManager };