@neurolint/cli
Version:
NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations
462 lines (400 loc) • 14.8 kB
text/typescript
import chalk from "chalk";
import ora from "ora";
import { table } from "table";
import inquirer from "inquirer";
import { PluginManager } from "../../lib/plugin-manager";
interface PluginOptions {
// Common options
verbose?: boolean;
// Install options
force?: boolean;
// Create options
author?: string;
description?: string;
// List options
enabled?: boolean;
disabled?: boolean;
}
const pluginManager = new PluginManager();
export async function pluginCommand(action: string, target?: string, options: PluginOptions = {}) {
try {
// Initialize plugin manager
await pluginManager.initialize();
switch (action) {
case 'install':
await installPlugin(target!, options);
break;
case 'remove':
case 'uninstall':
await removePlugin(target!, options);
break;
case 'list':
await listPlugins(options);
break;
case 'enable':
await togglePlugin(target!, true, options);
break;
case 'disable':
await togglePlugin(target!, false, options);
break;
case 'update':
await updatePlugin(target, options);
break;
case 'search':
await searchPlugins(target || '', options);
break;
case 'create':
await createPlugin(target!, options);
break;
case 'info':
await showPluginInfo(target!, options);
break;
default:
await showPluginHelp();
}
} catch (error) {
console.error(chalk.red(`Plugin command failed: ${error instanceof Error ? error.message : 'Unknown error'}`));
process.exit(1);
}
}
async function installPlugin(source: string, options: PluginOptions) {
if (!source) {
console.error(chalk.red('Plugin source is required'));
process.exit(1);
}
const spinner = ora(`Installing plugin from ${source}...`).start();
try {
const result = await pluginManager.installPlugin(source, options);
spinner.succeed(`Plugin installed: ${result.plugin.name} v${result.plugin.version}`);
if (options.verbose) {
console.log(chalk.gray(`Plugin ID: ${result.pluginId}`));
console.log(chalk.gray(`Description: ${result.plugin.description || 'No description'}`));
}
} catch (error) {
spinner.fail('Plugin installation failed');
throw error;
}
}
async function removePlugin(pluginName: string, options: PluginOptions) {
if (!pluginName) {
console.error(chalk.red('Plugin name is required'));
process.exit(1);
}
// Confirm removal unless forced
if (!options.force) {
const { confirmed } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmed',
message: `Are you sure you want to remove plugin "${pluginName}"?`,
default: false
}
]);
if (!confirmed) {
console.log(chalk.yellow('Plugin removal cancelled'));
return;
}
}
const spinner = ora(`Removing plugin ${pluginName}...`).start();
try {
await pluginManager.removePlugin(pluginName);
spinner.succeed(`Plugin removed: ${pluginName}`);
} catch (error) {
spinner.fail('Plugin removal failed');
throw error;
}
}
async function listPlugins(options: PluginOptions) {
const spinner = ora('Loading plugins...').start();
try {
const plugins = pluginManager.listPlugins();
spinner.stop();
if (plugins.length === 0) {
console.log(chalk.yellow('No plugins installed'));
console.log(chalk.gray('Install a plugin with: neurolint plugin install <source>'));
return;
}
// Filter plugins if needed
let filteredPlugins = plugins;
if (options.enabled !== undefined) {
filteredPlugins = plugins.filter(p => p.enabled === options.enabled);
}
if (filteredPlugins.length === 0) {
console.log(chalk.yellow(`No ${options.enabled ? 'enabled' : 'disabled'} plugins found`));
return;
}
console.log(chalk.white.bold(`\nInstalled Plugins (${filteredPlugins.length})\n`));
// Prepare table data
const tableData = [
['Name', 'Version', 'Status', 'Source', 'Installed']
];
filteredPlugins.forEach(plugin => {
tableData.push([
plugin.name,
plugin.version || 'unknown',
plugin.enabled ? chalk.green('enabled') : chalk.gray('disabled'),
plugin.source || 'unknown',
plugin.installedAt ? new Date(plugin.installedAt).toLocaleDateString() : 'unknown'
]);
});
console.log(table(tableData, {
border: {
topBody: chalk.gray('─'),
topJoin: chalk.gray('┬'),
topLeft: chalk.gray('┌'),
topRight: chalk.gray('┐'),
bottomBody: chalk.gray('─'),
bottomJoin: chalk.gray('┴'),
bottomLeft: chalk.gray('└'),
bottomRight: chalk.gray('┘'),
bodyLeft: chalk.gray('│'),
bodyRight: chalk.gray('│'),
bodyJoin: chalk.gray('│'),
joinBody: chalk.gray('─'),
joinLeft: chalk.gray('├'),
joinRight: chalk.gray('┤'),
joinJoin: chalk.gray('┼')
}
}));
if (options.verbose) {
const stats = pluginManager.pluginSystem.getStatistics();
console.log(chalk.gray(`\nPlugin System Statistics:`));
console.log(chalk.gray(` Total Hooks: ${stats.totalHooks}`));
console.log(chalk.gray(` Total Transformers: ${stats.totalTransformers}`));
console.log(chalk.gray(` Total Validators: ${stats.totalValidators}`));
}
} catch (error) {
spinner.fail('Failed to list plugins');
throw error;
}
}
async function togglePlugin(pluginName: string, enabled: boolean, options: PluginOptions) {
if (!pluginName) {
console.error(chalk.red('Plugin name is required'));
process.exit(1);
}
const spinner = ora(`${enabled ? 'Enabling' : 'Disabling'} plugin ${pluginName}...`).start();
try {
await pluginManager.togglePlugin(pluginName, enabled);
spinner.succeed(`Plugin ${pluginName} ${enabled ? 'enabled' : 'disabled'}`);
} catch (error) {
spinner.fail(`Failed to ${enabled ? 'enable' : 'disable'} plugin`);
throw error;
}
}
async function updatePlugin(pluginName?: string, options: PluginOptions) {
if (!pluginName) {
// Update all plugins
const plugins = pluginManager.listPlugins();
if (plugins.length === 0) {
console.log(chalk.yellow('No plugins to update'));
return;
}
for (const plugin of plugins) {
try {
console.log(chalk.blue(`Updating ${plugin.name}...`));
await pluginManager.updatePlugin(plugin.name);
} catch (error) {
console.warn(chalk.yellow(`Failed to update ${plugin.name}: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
} else {
const spinner = ora(`Updating plugin ${pluginName}...`).start();
try {
await pluginManager.updatePlugin(pluginName);
spinner.succeed(`Plugin updated: ${pluginName}`);
} catch (error) {
spinner.fail('Plugin update failed');
throw error;
}
}
}
async function searchPlugins(query: string, options: PluginOptions) {
const spinner = ora(`Searching for plugins: ${query}...`).start();
try {
const results = await pluginManager.searchPlugins(query);
spinner.stop();
if (results.length === 0) {
console.log(chalk.yellow(`No plugins found matching: ${query}`));
return;
}
console.log(chalk.white.bold(`\nPlugin Search Results (${results.length})\n`));
const tableData = [
['Name', 'Version', 'Description', 'Downloads']
];
results.forEach((plugin: any) => {
tableData.push([
plugin.name,
plugin.version || 'unknown',
(plugin.description || 'No description').substring(0, 50) + (plugin.description?.length > 50 ? '...' : ''),
plugin.downloads?.toLocaleString() || 'unknown'
]);
});
console.log(table(tableData, {
border: {
topBody: chalk.gray('─'),
topJoin: chalk.gray('┬'),
topLeft: chalk.gray('┌'),
topRight: chalk.gray('┐'),
bottomBody: chalk.gray('─'),
bottomJoin: chalk.gray('┴'),
bottomLeft: chalk.gray('└'),
bottomRight: chalk.gray('┘'),
bodyLeft: chalk.gray('│'),
bodyRight: chalk.gray('│'),
bodyJoin: chalk.gray('│'),
joinBody: chalk.gray('─'),
joinLeft: chalk.gray('├'),
joinRight: chalk.gray('┤'),
joinJoin: chalk.gray('┼')
}
}));
console.log(chalk.gray(`\nInstall a plugin with: neurolint plugin install <plugin-name>`));
} catch (error) {
spinner.fail('Search failed');
throw error;
}
}
async function createPlugin(pluginName: string, options: PluginOptions) {
if (!pluginName) {
console.error(chalk.red('Plugin name is required'));
process.exit(1);
}
// Validate plugin name
if (!/^[a-z][a-z0-9-]*$/.test(pluginName)) {
console.error(chalk.red('Plugin name must start with a letter and contain only lowercase letters, numbers, and hyphens'));
process.exit(1);
}
const spinner = ora(`Creating plugin template: ${pluginName}...`).start();
try {
// Prompt for additional information if not provided
const answers = await inquirer.prompt([
{
type: 'input',
name: 'description',
message: 'Plugin description:',
default: options.description || `Custom NeuroLint plugin: ${pluginName}`
},
{
type: 'input',
name: 'author',
message: 'Author name:',
default: options.author || 'Your Name'
},
{
type: 'list',
name: 'targetLayer',
message: 'Primary target layer:',
choices: [
{ name: 'Layer 1: Configuration fixes', value: 1 },
{ name: 'Layer 2: Content cleanup', value: 2 },
{ name: 'Layer 3: Component intelligence', value: 3 },
{ name: 'Layer 4: Hydration safety', value: 4 },
{ name: 'Layer 5: App Router optimization', value: 5 },
{ name: 'Layer 6: Quality enforcement', value: 6 }
],
default: 3
}
]);
await pluginManager.createPluginTemplate(pluginName, {
description: answers.description,
author: answers.author,
targetLayer: answers.targetLayer
});
spinner.succeed(`Plugin template created: ${pluginName}`);
console.log(chalk.green('\nPlugin template created successfully!'));
console.log(chalk.gray('\nNext steps:'));
console.log(chalk.gray(` 1. cd ${pluginName}`));
console.log(chalk.gray(` 2. Edit index.js to implement your plugin logic`));
console.log(chalk.gray(` 3. Test with: npm test`));
console.log(chalk.gray(` 4. Install locally: neurolint plugin install .`));
} catch (error) {
spinner.fail('Plugin template creation failed');
throw error;
}
}
async function showPluginInfo(pluginName: string, options: PluginOptions) {
if (!pluginName) {
console.error(chalk.red('Plugin name is required'));
process.exit(1);
}
const spinner = ora(`Loading plugin information: ${pluginName}...`).start();
try {
const plugins = pluginManager.listPlugins();
const plugin = plugins.find(p => p.name === pluginName);
if (!plugin) {
spinner.fail(`Plugin not found: ${pluginName}`);
return;
}
spinner.succeed(`Plugin information loaded: ${pluginName}`);
console.log(chalk.white.bold(`\nPlugin Information: ${plugin.name}\n`));
const infoData = [
['Property', 'Value'],
['Name', plugin.name],
['Version', plugin.version || 'unknown'],
['Description', plugin.description || 'No description'],
['Status', plugin.enabled ? chalk.green('enabled') : chalk.gray('disabled')],
['Source', plugin.source || 'unknown'],
['Installed', plugin.installedAt ? new Date(plugin.installedAt).toLocaleString() : 'unknown']
];
console.log(table(infoData, {
border: {
topBody: chalk.gray('─'),
topJoin: chalk.gray('┬'),
topLeft: chalk.gray('┌'),
topRight: chalk.gray('┐'),
bottomBody: chalk.gray('─'),
bottomJoin: chalk.gray('┴'),
bottomLeft: chalk.gray('└'),
bottomRight: chalk.gray('┘'),
bodyLeft: chalk.gray('│'),
bodyRight: chalk.gray('│'),
bodyJoin: chalk.gray('│'),
joinBody: chalk.gray('─'),
joinLeft: chalk.gray('├'),
joinRight: chalk.gray('┤'),
joinJoin: chalk.gray('┼')
}
}));
if (options.verbose) {
const pluginInstance = pluginManager.pluginSystem.getPlugin(plugin.id);
if (pluginInstance) {
console.log(chalk.gray('\nPlugin Capabilities:'));
if (pluginInstance.hooks) {
console.log(chalk.gray(` Hooks: ${Object.keys(pluginInstance.hooks).join(', ')}`));
}
if (pluginInstance.transformers) {
console.log(chalk.gray(` Transformers: ${Object.keys(pluginInstance.transformers).join(', ')}`));
}
if (pluginInstance.validators) {
console.log(chalk.gray(` Validators: ${Object.keys(pluginInstance.validators).join(', ')}`));
}
}
}
} catch (error) {
spinner.fail('Failed to load plugin information');
throw error;
}
}
async function showPluginHelp() {
console.log(chalk.white.bold('\nNeuroLint Plugin Management\n'));
console.log(chalk.white('Available Commands:'));
console.log(chalk.gray(' install <source> Install a plugin from various sources'));
console.log(chalk.gray(' remove <name> Remove an installed plugin'));
console.log(chalk.gray(' list List all installed plugins'));
console.log(chalk.gray(' enable <name> Enable a plugin'));
console.log(chalk.gray(' disable <name> Disable a plugin'));
console.log(chalk.gray(' update [name] Update plugin(s)'));
console.log(chalk.gray(' search <query> Search for plugins in registry'));
console.log(chalk.gray(' create <name> Create a new plugin template'));
console.log(chalk.gray(' info <name> Show detailed plugin information'));
console.log(chalk.white('\nPlugin Sources:'));
console.log(chalk.gray(' Local file: ./my-plugin.js'));
console.log(chalk.gray(' URL: https://example.com/plugin.js'));
console.log(chalk.gray(' Registry: my-custom-plugin'));
console.log(chalk.white('\nExamples:'));
console.log(chalk.gray(' neurolint plugin install ./my-plugin.js'));
console.log(chalk.gray(' neurolint plugin create my-awesome-plugin'));
console.log(chalk.gray(' neurolint plugin list --verbose'));
console.log(chalk.gray(' neurolint plugin search react'));
}