@five-vm/cli
Version:
High-performance CLI for Five VM development with WebAssembly integration
340 lines (338 loc) • 12.5 kB
JavaScript
/**
* 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