UNPKG

@five-vm/cli

Version:

High-performance CLI for Five VM development with WebAssembly integration

282 lines โ€ข 12.2 kB
/** * Five CLI Deploy-and-Execute Command * * Combined command that deploys bytecode to Solana and then executes it. * Perfect for testing workflows where you need real on-chain execution. * Designed for localnet testing and development workflows. */ import { readFile } from 'fs/promises'; import { extname } from 'path'; import chalk from 'chalk'; import ora from 'ora'; import { FiveSDK } from '../sdk/FiveSDK.js'; import { ConfigManager } from '../config/ConfigManager.js'; import { FiveFileManager } from '../utils/FiveFileManager.js'; /** * Five deploy-and-execute command implementation */ export const deployAndExecuteCommand = { name: 'deploy-and-execute', description: 'Deploy bytecode to Solana and execute immediately (perfect for testing)', aliases: ['dae', 'test-onchain'], options: [ { flags: '-f, --function <index>', description: 'Execute specific function by index (default: 0)', defaultValue: 0 }, { flags: '-p, --params <params>', description: 'Function parameters as JSON array (e.g., "[10, 5]")', defaultValue: '[]' }, { flags: '-t, --target <target>', description: 'Override target network (devnet, testnet, mainnet, local)', required: false }, { flags: '-n, --network <url>', description: 'Override network RPC URL', required: false }, { flags: '-k, --keypair <file>', description: 'Override keypair file path', required: false }, { flags: '--max-cu <units>', description: 'Maximum compute units for execution (default: 1400000)', defaultValue: 1400000 }, { flags: '--compute-budget <units>', description: 'Compute budget for deployment transaction', defaultValue: 1400000 }, { flags: '--format <format>', description: 'Output format', choices: ['text', 'json'], defaultValue: 'text' }, { flags: '--debug', description: 'Enable debug output', defaultValue: false }, { flags: '--skip-deployment-verification', description: 'Skip deployment verification', defaultValue: false }, { flags: '--cleanup', description: 'Clean up deployed account after execution (for testing)', defaultValue: false } ], arguments: [ { name: 'bytecode', description: 'Five VM bytecode file (.bin, .five, or .v source)', required: true } ], examples: [ { command: 'five deploy-and-execute script.five', description: 'Deploy and execute function 0 on configured target' }, { command: 'five deploy-and-execute script.five --target local -f 1 -p "[10, 5]"', description: 'Deploy to localnet and execute function 1 with parameters [10, 5]' }, { command: 'five deploy-and-execute src/main.v --target local --debug', description: 'Compile, deploy, and execute source file on localnet with debug output' }, { command: 'five dae script.bin --target devnet --max-cu 2000000', description: 'Deploy and execute on devnet with higher compute limit' } ], handler: async (args, options, context) => { const { logger } = context; try { // Apply config with CLI overrides const configManager = ConfigManager.getInstance(); const overrides = { target: options.target, network: options.network, keypair: options.keypair }; const config = await configManager.applyOverrides(overrides); // Show target context prefix const targetPrefix = ConfigManager.getTargetPrefix(config.target); console.log(`${targetPrefix} Deploy-and-Execute workflow starting`); if (context.options.verbose || options.debug) { logger.info(`Target: ${config.target}`); logger.info(`Network: ${config.networks[config.target].rpcUrl}`); logger.info(`Keypair: ${config.keypairPath}`); } const inputFile = args[0]; // STEP 1: Load/Compile bytecode let bytecode; let abi = null; if (extname(inputFile) === '.v') { // Compile source file console.log(chalk.blue('๐Ÿ“ Compiling Five source...')); const sourceCode = await readFile(inputFile, 'utf8'); const compilationResult = await FiveSDK.compile(sourceCode, { optimize: true, debug: options.debug }); if (!compilationResult.success || !compilationResult.bytecode) { console.log(chalk.red('โŒ Compilation failed')); if (compilationResult.errors) { compilationResult.errors.forEach(error => { console.log(chalk.red(` โ€ข ${error.message}`)); }); } process.exit(1); } bytecode = compilationResult.bytecode; abi = compilationResult.abi; console.log(chalk.green(`โœ… Compilation successful (${bytecode.length} bytes)`)); } else { // Load existing bytecode file console.log(chalk.blue('๐Ÿ“ Loading bytecode...')); const fileManager = FiveFileManager.getInstance(); const loadedFile = await fileManager.loadFile(inputFile, { validateFormat: true }); bytecode = loadedFile.bytecode; abi = loadedFile.abi; console.log(chalk.green(`โœ… Loaded ${loadedFile.format.toUpperCase()} file (${bytecode.length} bytes)`)); } // Setup Solana connection and keypair const { Connection, Keypair } = await import('@solana/web3.js'); const rpcUrl = config.networks[config.target].rpcUrl; const connection = new Connection(rpcUrl, 'confirmed'); // Load deployer keypair const keypairPath = config.keypairPath.startsWith('~/') ? config.keypairPath.replace('~', process.env.HOME || '') : config.keypairPath; const keypairData = JSON.parse(await readFile(keypairPath, 'utf8')); const deployerKeypair = Keypair.fromSecretKey(new Uint8Array(keypairData)); if (options.debug) { console.log(`๐Ÿ”‘ Deployer: ${deployerKeypair.publicKey.toString()}`); } // STEP 2: Deploy bytecode console.log(chalk.blue('๐Ÿš€ Deploying to Solana...')); const deploySpinner = ora('Deploying bytecode...').start(); const deploymentResult = await FiveSDK.deployToSolana(bytecode, connection, deployerKeypair, { debug: options.debug, network: config.target, computeBudget: options.computeBudget, maxRetries: 3 }); if (!deploymentResult.success) { deploySpinner.fail('Deployment failed'); try { console.log(chalk.red(`โŒ Deployment error: ${deploymentResult.error || 'unknown'}`)); console.log(chalk.gray('Deployment result (debug):'), JSON.stringify(deploymentResult, null, 2)); } catch { } process.exit(1); } const scriptAccount = deploymentResult.programId; deploySpinner.succeed(`Deployed successfully: ${scriptAccount}`); if (deploymentResult.logs && (context.options.verbose || options.debug)) { console.log(chalk.gray('๐Ÿ“‹ Deployment logs:')); deploymentResult.logs.forEach(log => { console.log(chalk.gray(` ${log}`)); }); } // STEP 3: Wait for deployment to fully propagate, then execute console.log(chalk.blue('โฑ๏ธ Waiting for deployment to propagate...')); await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay console.log(chalk.blue('โšก Executing deployed script...')); // Parse function index and parameters const functionIndex = parseInt(options.function) || 0; let parameters = []; try { if (options.params && options.params !== '[]') { parameters = JSON.parse(options.params); } } catch (error) { console.log(chalk.red(`โŒ Invalid parameters JSON: ${options.params}`)); process.exit(1); } if (options.debug) { console.log(`๐Ÿ“‹ Executing function ${functionIndex} with parameters:`, parameters); } const executeSpinner = ora('Executing function...').start(); const executionResult = await FiveSDK.executeScriptAccount(scriptAccount, functionIndex, parameters, connection, deployerKeypair, { debug: options.debug, network: config.target, computeBudget: options.maxCu, maxRetries: 3 }); if (executionResult.success) { executeSpinner.succeed('Execution completed successfully'); // Display results displayExecutionResults(executionResult, options, targetPrefix); } else { executeSpinner.fail('Execution failed'); console.log(chalk.red(`โŒ Execution error: ${executionResult.error}`)); if (executionResult.logs && executionResult.logs.length > 0) { console.log(chalk.red('๐Ÿ“‹ Error logs:')); executionResult.logs.forEach(log => { console.log(chalk.red(` ${log}`)); }); } process.exit(1); } // STEP 4: Optional cleanup (for testing) if (options.cleanup) { console.log(chalk.yellow('๐Ÿงน Cleanup requested (not implemented yet)')); console.log(chalk.gray(` Script account ${scriptAccount} will remain on chain`)); } console.log(chalk.green('๐ŸŽ‰ Deploy-and-Execute workflow completed successfully!')); } catch (error) { logger.error('Deploy-and-Execute workflow failed:', error); throw error; } } }; /** * Display execution results in a formatted way */ function displayExecutionResults(result, options, targetPrefix) { if (options.format === 'json') { console.log(JSON.stringify(result, null, 2)); return; } console.log(chalk.bold(`\n${targetPrefix} Execution Results:`)); if (result.transactionId) { console.log(`Transaction: ${chalk.cyan(result.transactionId)}`); } if (result.computeUnitsUsed !== undefined) { console.log(`Compute units used: ${chalk.gray(result.computeUnitsUsed.toLocaleString())}`); } if (result.result !== undefined) { console.log(`Result: ${chalk.cyan(JSON.stringify(result.result))}`); } if (result.logs && result.logs.length > 0) { console.log(chalk.bold('\n๐Ÿ“‹ Transaction logs:')); result.logs.forEach((log) => { // Filter out system logs and show only relevant ones if (log.includes('Five') || log.includes('success') || log.includes('error') || log.includes('Program log:')) { console.log(chalk.gray(` ${log}`)); } }); } } //# sourceMappingURL=deploy-and-execute.js.map