@five-vm/cli
Version:
High-performance CLI for Five VM development with WebAssembly integration
800 lines (712 loc) • 21.4 kB
JavaScript
/**
* Five CLI Init Command
*
* Initialize new Five VM projects with templates, configuration,
* and development setup.
*/
import { writeFile, mkdir, access } from 'fs/promises';
import { join } from 'path';
import chalk from 'chalk';
import ora from 'ora';
/**
* Five init command implementation
*/
export const initCommand = {
name: 'init',
description: 'Initialize new project',
aliases: ['new', 'create'],
options: [
{
flags: '-t, --template <template>',
description: 'Project template',
choices: ['basic', 'defi', 'nft', 'game', 'dao'],
defaultValue: 'basic'
},
{
flags: '--target <target>',
description: 'Default compilation target',
choices: ['vm', 'solana', 'debug', 'test'],
defaultValue: 'vm'
},
{
flags: '--name <name>',
description: 'Project name (default: directory name)',
required: false
},
{
flags: '--description <desc>',
description: 'Project description',
required: false
},
{
flags: '--author <author>',
description: 'Project author',
required: false
},
{
flags: '--license <license>',
description: 'Project license',
defaultValue: 'MIT'
},
{
flags: '--no-git',
description: 'Skip git repository initialization',
defaultValue: false
},
{
flags: '--no-examples',
description: 'Skip example files',
defaultValue: false
}
],
arguments: [
{
name: 'directory',
description: 'Project directory (default: current directory)',
required: false
}
],
examples: [
{
command: 'five init',
description: 'Initialize project in current directory'
},
{
command: 'five init my-project',
description: 'Create new project in my-project directory'
},
{
command: 'five init my-defi --template defi --target solana',
description: 'Create DeFi project targeting Solana'
},
{
command: 'five init game --template game --no-git',
description: 'Create game project without git initialization'
}
],
handler: async (args, options, context) => {
const { logger } = context;
try {
// Determine project directory
const projectDir = args[0] || process.cwd();
const projectName = options.name || (args[0] ? args[0] : 'five-project');
logger.info(`Initializing Five VM project: ${projectName}`);
// Check if directory exists and is empty
await checkProjectDirectory(projectDir, logger);
// Create project structure
const spinner = ora('Creating project structure...').start();
await createProjectStructure(projectDir, options.template);
spinner.succeed('Project structure created');
// Generate project configuration
spinner.start('Generating configuration files...');
await generateProjectConfig(projectDir, projectName, options);
await generatePackageJson(projectDir, projectName, options);
spinner.succeed('Configuration files generated');
// Generate source files
if (!options.noExamples) {
spinner.start('Generating example files...');
await generateExampleFiles(projectDir, options.template);
spinner.succeed('Example files generated');
}
// Initialize git repository
if (!options.noGit) {
spinner.start('Initializing git repository...');
await initializeGitRepository(projectDir);
spinner.succeed('Git repository initialized');
}
// Display success message
displaySuccessMessage(projectDir, projectName, options);
}
catch (error) {
logger.error('Project initialization failed:', error);
throw error;
}
}
};
/**
* Check if project directory is valid for initialization
*/
async function checkProjectDirectory(projectDir, logger) {
try {
await access(projectDir);
// Directory exists, check if it's empty
const { readdir } = await import('fs/promises');
const files = await readdir(projectDir);
if (files.length > 0) {
logger.warn(`Directory ${projectDir} is not empty`);
// Check for existing Five project
const hasConfig = files.includes('five.toml') || files.includes('package.json');
if (hasConfig) {
throw new Error('Directory already contains a project configuration');
}
}
}
catch (error) {
if (error.code === 'ENOENT') {
// Directory doesn't exist, create it
await mkdir(projectDir, { recursive: true });
}
else {
throw error;
}
}
}
/**
* Create project directory structure
*/
async function createProjectStructure(projectDir, template) {
const dirs = [
'src',
'tests',
'examples',
'build',
'docs',
'.five'
];
// Add template-specific directories
switch (template) {
case 'defi':
dirs.push('src/protocols', 'src/tokens', 'src/pools');
break;
case 'nft':
dirs.push('src/collections', 'src/metadata', 'assets');
break;
case 'game':
dirs.push('src/entities', 'src/systems', 'src/components', 'assets');
break;
case 'dao':
dirs.push('src/governance', 'src/treasury', 'src/proposals');
break;
}
for (const dir of dirs) {
await mkdir(join(projectDir, dir), { recursive: true });
}
}
/**
* Generate project configuration file
*/
async function generateProjectConfig(projectDir, projectName, options) {
const config = {
name: projectName,
version: '0.1.0',
description: options.description || `A Five VM project`,
sourceDir: 'src',
buildDir: 'build',
target: options.target,
optimizations: {
enableVLE: true,
enableCompression: true,
enableRegisterAllocation: true,
enableConstraintOptimization: true,
optimizationLevel: 2
},
dependencies: []
};
const configContent = generateTomlConfig(config);
await writeFile(join(projectDir, 'five.toml'), configContent);
}
/**
* Generate package.json for Node.js tooling
*/
async function generatePackageJson(projectDir, projectName, options) {
const packageJson = {
name: projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
version: '0.1.0',
description: options.description || 'A Five VM project',
author: options.author || '',
license: options.license,
scripts: {
build: 'five compile src/**/*.v',
test: 'five test',
deploy: 'five deploy',
'build:release': 'five compile src/**/*.v -O 3',
'build:debug': 'five compile src/**/*.v --debug',
'watch': 'five compile src/**/*.v --watch'
},
devDependencies: {
'five-cli': '^1.0.0'
},
keywords: [
'five-vm',
'blockchain',
'solana',
'smart-contracts'
]
};
await writeFile(join(projectDir, 'package.json'), JSON.stringify(packageJson, null, 2));
}
/**
* Generate example files based on template
*/
async function generateExampleFiles(projectDir, template) {
// Generate main source file
const mainFile = getTemplateMainFile(template);
await writeFile(join(projectDir, 'src/main.v'), mainFile);
// Generate test file
const testFile = getTemplateTestFile(template);
await writeFile(join(projectDir, 'tests/main.test.v'), testFile);
// Generate README
const readme = generateReadme(template);
await writeFile(join(projectDir, 'README.md'), readme);
// Generate .gitignore
const gitignore = generateGitignore();
await writeFile(join(projectDir, '.gitignore'), gitignore);
}
/**
* Initialize git repository
*/
async function initializeGitRepository(projectDir) {
const { execSync } = await import('child_process');
try {
execSync('git init', { cwd: projectDir, stdio: 'ignore' });
execSync('git add .', { cwd: projectDir, stdio: 'ignore' });
execSync('git commit -m "Initial commit"', { cwd: projectDir, stdio: 'ignore' });
}
catch (error) {
// Git initialization is optional, don't fail the entire process
console.warn(chalk.yellow('Warning: Git initialization failed'));
}
}
/**
* Generate TOML configuration
*/
function generateTomlConfig(config) {
return `
[]
name = "${config.name}"
version = "${config.version}"
description = "${config.description}"
source_dir = "${config.sourceDir}"
build_dir = "${config.buildDir}"
target = "${config.target}"
[]
enable_vle = ${config.optimizations.enableVLE}
enable_compression = ${config.optimizations.enableCompression}
enable_register_allocation = ${config.optimizations.enableRegisterAllocation}
enable_constraint_optimization = ${config.optimizations.enableConstraintOptimization}
optimization_level = ${config.optimizations.optimizationLevel}
[]
[]
max_bytecode_size = 1048576
target_compute_units = 200000
[]
network = "devnet"
`;
}
/**
* Get template main file content
*/
function getTemplateMainFile(template) {
const templates = {
basic: `// Basic Five VM Program
script BasicProgram {
// Program initialization
init() {
log("BasicProgram initialized");
}
// Main program constraints
constraints {
// Add your business logic here
require(true, "Always passes");
}
}
// Main entry point
instruction main() {
log("Hello, Five VM!");
42 // Return value
}
// Example function with parameters
instruction add(a: u64, b: u64) -> u64 {
a + b
}
instruction test_add() {
let result = add(2, 3);
assert_eq(result, 5, "Addition should work");
}
`,
defi: `// DeFi Protocol on Five VM
script DefiProtocol {
init() {
log("DeFi Protocol initialized");
}
constraints {
// Ensure minimum liquidity
require(get_balance() >= 1000, "Insufficient liquidity");
// Validate price oracle
let price = get_price_oracle();
require(price > 0, "Invalid price");
}
}
account LiquidityPool {
token_a_amount: u64,
token_b_amount: u64,
total_shares: u64,
fee_rate: u64
}
instruction swap(amount_in: u64, token_in: string) -> u64 {
let pool = load_account<LiquidityPool>(0);
if token_in == "A" {
let amount_out = (amount_in * pool.token_b_amount) / (pool.token_a_amount + amount_in);
pool.token_a_amount += amount_in;
pool.token_b_amount -= amount_out;
amount_out
} else {
let amount_out = (amount_in * pool.token_a_amount) / (pool.token_b_amount + amount_in);
pool.token_b_amount += amount_in;
pool.token_a_amount -= amount_out;
amount_out
}
}
instruction add_liquidity(amount_a: u64, amount_b: u64) -> u64 {
let pool = load_account<LiquidityPool>(0);
let shares = if pool.total_shares == 0 {
(amount_a * amount_b).sqrt()
} else {
min(
(amount_a * pool.total_shares) / pool.token_a_amount,
(amount_b * pool.total_shares) / pool.token_b_amount
)
};
pool.token_a_amount += amount_a;
pool.token_b_amount += amount_b;
pool.total_shares += shares;
shares
}
`,
nft: `// NFT Collection on Five VM
script NFTCollection {
init() {
log("NFT Collection initialized");
}
constraints {
// Ensure valid mint authority
require(is_mint_authority(), "Invalid mint authority");
// Check collection size limits
let current_supply = get_current_supply();
require(current_supply < 10000, "Max supply reached");
}
}
account NFTMetadata {
name: string,
symbol: string,
uri: string,
creator: pubkey,
collection: pubkey,
is_mutable: bool
}
instruction mint_nft(to: pubkey, metadata_uri: string) -> pubkey {
let nft_id = derive_pda("nft", [to, get_clock().slot]);
let metadata = NFTMetadata {
name: "Five VM NFT",
symbol: "FVM",
uri: metadata_uri,
creator: get_signer(),
collection: get_program_id(),
is_mutable: true
};
create_account(nft_id, metadata);
log("NFT minted successfully");
nft_id
}
instruction transfer_nft(nft_id: pubkey, from: pubkey, to: pubkey) {
require(is_signer(from), "Invalid signature");
let metadata = load_account<NFTMetadata>(nft_id);
require(metadata.creator == from, "Not owner");
// Update ownership (simplified)
metadata.creator = to;
save_account(nft_id, metadata);
emit TransferEvent { from, to, nft_id };
}
event TransferEvent {
from: pubkey,
to: pubkey,
nft_id: pubkey
}
`,
game: `// Game Logic on Five VM
script GameEngine {
init() {
log("Game Engine initialized");
}
constraints {
// Validate player actions
let player = get_player();
require(player.is_active, "Player not active");
// Check game state
let game_state = get_game_state();
require(game_state == "active", "Game not active");
}
}
account Player {
id: pubkey,
level: u64,
experience: u64,
health: u64,
position_x: u64,
position_y: u64,
inventory: [u64; 10],
is_active: bool
}
account GameWorld {
width: u64,
height: u64,
players_count: u64,
started_at: u64
}
instruction move_player(direction: string, distance: u64) {
let player = load_account<Player>(get_signer());
match direction {
"north" => player.position_y += distance,
"south" => player.position_y -= distance,
"east" => player.position_x += distance,
"west" => player.position_x -= distance,
_ => require(false, "Invalid direction")
}
// Validate bounds
let world = load_account<GameWorld>(0);
require(player.position_x < world.width, "Out of bounds");
require(player.position_y < world.height, "Out of bounds");
save_account(get_signer(), player);
emit PlayerMoved { player: get_signer(), x: player.position_x, y: player.position_y };
}
instruction level_up() {
let player = load_account<Player>(get_signer());
let required_exp = player.level * 100;
require(player.experience >= required_exp, "Insufficient experience");
player.level += 1;
player.experience -= required_exp;
player.health = 100; // Full heal on level up
save_account(get_signer(), player);
emit LevelUp { player: get_signer(), new_level: player.level };
}
event PlayerMoved {
player: pubkey,
x: u64,
y: u64
}
event LevelUp {
player: pubkey,
new_level: u64
}
`,
dao: `// DAO Governance on Five VM
script DAOGovernance {
init() {
log("DAO Governance initialized");
}
constraints {
// Validate governance token
let token_balance = get_token_balance();
require(token_balance > 0, "No governance tokens");
// Check proposal validity
let proposal_id = get_current_proposal();
if proposal_id > 0 {
let proposal = get_proposal(proposal_id);
require(proposal.is_active, "Proposal not active");
}
}
}
account Proposal {
id: u64,
title: string,
description: string,
proposer: pubkey,
votes_for: u64,
votes_against: u64,
start_time: u64,
end_time: u64,
is_active: bool,
is_executed: bool
}
account Vote {
proposal_id: u64,
voter: pubkey,
amount: u64,
is_for: bool
}
instruction create_proposal(title: string, description: string, duration: u64) -> u64 {
let proposer_balance = get_token_balance();
require(proposer_balance >= 1000, "Insufficient tokens to propose");
let proposal_id = get_next_proposal_id();
let current_time = get_clock().unix_timestamp;
let proposal = Proposal {
id: proposal_id,
title,
description,
proposer: get_signer(),
votes_for: 0,
votes_against: 0,
start_time: current_time,
end_time: current_time + duration,
is_active: true,
is_executed: false
};
create_account(derive_pda("proposal", [proposal_id]), proposal);
emit ProposalCreated { id: proposal_id, proposer: get_signer() };
proposal_id
}
instruction vote(proposal_id: u64, amount: u64, is_for: bool) {
let voter_balance = get_token_balance();
require(voter_balance >= amount, "Insufficient token balance");
let proposal = load_account<Proposal>(derive_pda("proposal", [proposal_id]));
require(proposal.is_active, "Proposal not active");
require(get_clock().unix_timestamp <= proposal.end_time, "Voting period ended");
// Check if already voted
let vote_account = derive_pda("vote", [proposal_id, get_signer()]);
require(!account_exists(vote_account), "Already voted");
// Record vote
let vote = Vote {
proposal_id,
voter: get_signer(),
amount,
is_for
};
create_account(vote_account, vote);
// Update proposal vote counts
if is_for {
proposal.votes_for += amount;
} else {
proposal.votes_against += amount;
}
save_account(derive_pda("proposal", [proposal_id]), proposal);
emit VoteCast { proposal_id, voter: get_signer(), amount, is_for };
}
event ProposalCreated {
id: u64,
proposer: pubkey
}
event VoteCast {
proposal_id: u64,
voter: pubkey,
amount: u64,
is_for: bool
}
`
};
return templates[template] || templates.basic;
}
/**
* Get template test file content
*/
function getTemplateTestFile(template) {
return `// Tests for ${template} template
instruction test_basic_functionality() {
// Test basic program functionality
let result = main();
assert_eq(result, 42, "Main should return 42");
}
instruction test_initialization() {
// Test program initialization
// Add your test logic here
assert_true(true, "Initialization test");
}
instruction test_failure_case() {
// Test expected failure scenarios
require(false, "This should fail");
}
`;
}
/**
* Generate README content
*/
function generateReadme(template) {
return `
A ${template} project built with Five VM.
- Node.js 18+
- Five CLI: \`npm install -g five-cli\`
\`\`\`bash
npm run build
npm run build:release
npm run build:debug
\`\`\`
\`\`\`bash
npm test
\`\`\`
\`\`\`bash
npm run watch
\`\`\`
\`\`\`bash
npm run deploy
\`\`\`
- \`src/\` - Five VM source files (.v)
- \`tests/\` - Test files
- \`build/\` - Compiled bytecode
- \`docs/\` - Documentation
- \`five.toml\` - Project configuration
## Learn More
- [Five VM Documentation](https://five-vm.dev)
- [Five VM GitHub](https://github.com/five-vm)
- [Examples](./examples)
## License
MIT
`;
}
/**
* Generate .gitignore content
*/
function generateGitignore() {
return `
build/
*.bin
*.so
*.wasm
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.env
.env.local
.vscode/
.idea/
*.swp
*.swo
.DS_Store
Thumbs.db
.five/cache/
*.debug.bin
`;
}
/**
* Display success message
*/
function displaySuccessMessage(projectDir, projectName, options) {
console.log('\n' + chalk.green('✓ Project initialized successfully!'));
console.log('\n' + chalk.bold('Next steps:'));
if (projectDir !== process.cwd()) {
console.log(` ${chalk.cyan('cd')} ${projectDir}`);
}
console.log(` ${chalk.cyan('npm install')} - Install dependencies`);
console.log(` ${chalk.cyan('npm run build')} - Compile the project`);
console.log(` ${chalk.cyan('npm test')} - Run tests`);
console.log(` ${chalk.cyan('npm run watch')} - Start development mode`);
console.log('\n' + chalk.gray('Happy coding with Five VM! 🚀'));
}
//# sourceMappingURL=init.js.map