UNPKG

@five-vm/cli

Version:

High-performance CLI for Five VM development with WebAssembly integration

340 lines (338 loc) 12.5 kB
#!/usr/bin/env node /** * Five CLI - Main Entry Point * * High-performance command-line interface for Five VM development with WebAssembly integration. * Provides DSL compilation, bytecode analysis, VM execution, and Solana deployment capabilities. */ import { Command } from 'commander'; import chalk from 'chalk'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { createLogger } from './utils/logger.js'; import { commands, getCommand, generateCommandsHelp } from './commands/index.js'; import { styleCommandNotFound, createSectionHeader, styleCommandExample } from './utils/ascii-art.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export class FiveCLI { program; config; logger; context; constructor(config) { this.config = config; this.logger = createLogger({ level: config.verbose ? 'debug' : 'info', enableColors: true }); this.program = new Command(); this.setupProgram(); this.setupContext(); this.registerCommands(); } /** * Initialize the CLI program with metadata and global options */ setupProgram() { this.program .name('five') .description('Five VM CLI - Ultra-fast bytecode VM for Solana') .helpOption(false) // Disable default help to use our custom help .addHelpText('beforeAll', () => { return 'Five VM CLI - Ultra-fast bytecode VM for Solana\n'; }) .addHelpText('after', () => { return ` ${createSectionHeader('Quick Examples', 'green')} ${styleCommandExample('five init my-project', 'Create a new Five project')} ${styleCommandExample('five compile script.v', 'Compile Five source to bytecode')} ${styleCommandExample('five execute script.v --local', 'Local WASM execution')} ${styleCommandExample('five deploy build/script.bin --target mainnet', 'Deploy to Solana mainnet')} ${styleCommandExample('five help <command>', 'Get help for specific command')} ${chalk.bold.cyan('Documentation:')} ${chalk.cyan('https://github.com/five-vm/five-cli#readme')} ${chalk.bold.cyan('Report Issues:')} ${chalk.cyan('https://github.com/five-vm/five-cli/issues')} `; }); // Global options with styling this.program .option('--verbose', 'Verbose output', false) .option('--debug', 'Debug mode', false) .option('--no-color', 'Disable colored output') .option('--config <file>', 'Use custom configuration file') .option('-h, --help', 'Display help information'); // Global error handling this.program.exitOverride(); this.program.configureOutput({ outputError: (str, write) => { // Custom error output with colored formatting write(chalk.red(str)); } }); } /** * Setup command execution context */ setupContext() { this.context = { config: this.config, logger: this.logger, wasmManager: null, // Will be initialized by individual commands options: { verbose: this.config.verbose, debug: this.config.debug } }; } /** * Register all available commands with the CLI */ registerCommands() { for (const commandDef of commands) { const command = this.program .command(commandDef.name) .description(commandDef.description); // Add aliases if (commandDef.aliases) { command.aliases(commandDef.aliases); } // Add options if (commandDef.options) { for (const option of commandDef.options) { command.option(option.flags, option.description, option.defaultValue); } } // Add arguments if (commandDef.arguments) { for (const arg of commandDef.arguments) { if (arg.variadic) { command.argument(`[${arg.name}...]`, arg.description); } else if (arg.required) { command.argument(`<${arg.name}>`, arg.description); } else { command.argument(`[${arg.name}]`, arg.description); } } } // Add examples to help text if (commandDef.examples) { const exampleText = commandDef.examples .map(ex => ` ${chalk.cyan(ex.command)} ${ex.description}`) .join('\n'); command.addHelpText('after', `\n${chalk.bold('Examples:')}\n${exampleText}\n`); } // Set command handler command.action(async (...args) => { try { // Extract options and arguments const options = args[args.length - 2]; // Second to last is options const command = args[args.length - 1]; // Last is command object const commandArgs = args.slice(0, -2); // All others are arguments // Update context with current options this.updateContextFromOptions(options); // Execute command await commandDef.handler(commandArgs, options, this.context); } catch (error) { await this.handleCommandError(error, commandDef.name); } }); } } /** * Update execution context from command-line options */ updateContextFromOptions(options) { this.context.options = { ...this.context.options, verbose: options.verbose || this.config.verbose, debug: options.debug || this.config.debug, output: options.output, format: options.format, optimize: options.optimize, target: options.target }; // Update logger level if verbose mode changed if (options.verbose && !this.config.verbose) { this.logger = createLogger({ level: 'debug', enableColors: !options.noColor }); this.context.logger = this.logger; } } /** * Handle command execution errors with proper formatting and exit codes */ async handleCommandError(error, commandName) { const cliError = error; if (cliError.category === 'user') { // User errors (invalid input, missing files, etc.) this.logger.error(`${commandName}: ${cliError.message}`); if (cliError.details && this.context.options.verbose) { console.error(chalk.gray('Details:'), cliError.details); } } else if (cliError.category === 'wasm') { // WASM-related errors this.logger.error(`WASM Error in ${commandName}: ${cliError.message}`); if (this.context.options.debug && cliError.details) { console.error(chalk.gray('WASM Stack:'), cliError.details.stack); } } else { // System errors this.logger.error(`System error in ${commandName}: ${cliError.message}`); if (this.context.options.debug) { console.error(chalk.gray('Stack trace:')); console.error(chalk.gray(error.stack)); } } // Exit with appropriate code const exitCode = cliError.exitCode || 1; process.exit(exitCode); } /** * Run the CLI with provided arguments */ async run(argv) { try { // Handle special cases for help and version if (argv.includes('--help') || argv.includes('-h')) { this.program.help(); return; } // Show welcome message if no command provided if (argv.length <= 2) { console.log(chalk.bold.cyan('Five VM CLI - Ultra-fast bytecode VM for Solana')); console.log(chalk.gray('Use ') + chalk.cyan('five help') + chalk.gray(' to see available commands.')); console.log(chalk.gray('Use ') + chalk.cyan('five help <command>') + chalk.gray(' for command-specific help.')); return; } // Check if command exists const commandName = argv[2]; if (commandName && !commandName.startsWith('-')) { const command = getCommand(commandName); if (!command) { // Show styled command not found with suggestions const suggestions = commands .map(cmd => cmd.name) .filter(name => name.includes(commandName) || commandName.includes(name)) .slice(0, 3); console.error(styleCommandNotFound(commandName, suggestions)); console.error('\nAvailable commands:'); console.error(generateCommandsHelp()); process.exit(1); } } // Parse and execute await this.program.parseAsync(argv); } catch (error) { // Handle CLI parsing errors if (error instanceof Error) { if (error.name === 'CommanderError') { // Commander.js error - usually help or version display return; } this.logger.error('CLI Error:', error.message); if (this.context.options.debug) { console.error(chalk.gray('Stack trace:'), error.stack); } } process.exit(1); } } /** * Get CLI version from package.json */ getVersion() { try { // Try to read package.json from multiple possible locations const possiblePaths = [ join(this.config.rootDir, 'package.json'), join(__dirname, '../package.json'), join(__dirname, '../../package.json') ]; for (const path of possiblePaths) { try { const packageJson = require(path); return packageJson.version || '1.0.0'; } catch { continue; } } return '1.0.0'; } catch { return '1.0.0'; } } /** * Get CLI program instance for testing */ getProgram() { return this.program; } /** * Get current configuration */ getConfig() { return this.config; } /** * Get current logger */ getLogger() { return this.logger; } } /** * Create and configure a new Five CLI instance */ export function createCLI(config = {}) { const defaultConfig = { rootDir: process.cwd(), verbose: false, debug: false, wasmDir: join(process.cwd(), 'assets', 'vm'), tempDir: join(process.cwd(), '.five-tmp') }; const finalConfig = { ...defaultConfig, ...config }; return new FiveCLI(finalConfig); } /** * Default export for convenience */ export default FiveCLI; /** * Main execution when run as script */ export async function main() { try { const cli = createCLI({ verbose: process.argv.includes('--verbose') || process.argv.includes('-v'), debug: process.argv.includes('--debug') }); await cli.run(process.argv); } catch (error) { console.error(chalk.red('Fatal error:'), error); process.exit(1); } } // Execute main if this file is run directly // Handle various execution contexts (direct, npx, global install) const isMainModule = (import.meta.url === `file://${process.argv[1]}` || process.argv[1].endsWith('dist/index.js') || process.argv[1].endsWith('/five') || process.argv[1].includes('five-cli')); if (isMainModule) { main().catch(error => { console.error('Fatal CLI error:', error); process.exit(1); }); } //# sourceMappingURL=index.js.map