Development

Learn about development practices and guidelines for contributing to ao-forge.

Overview

This guide covers development practices, coding standards, and best practices for contributing to ao-forge. It's essential reading for anyone who wants to contribute to the project.

Development Principles

Core Principles

  1. User-First Design - Always consider the user experience
  2. Simplicity - Keep things simple and intuitive
  3. Reliability - Ensure code is robust and handles errors gracefully
  4. Performance - Optimize for speed and efficiency
  5. Maintainability - Write code that's easy to understand and modify

Code Quality

  • Type Safety - Use TypeScript for all code
  • Testing - Write tests for all functionality
  • Documentation - Document all public APIs
  • Error Handling - Handle errors appropriately
  • Performance - Consider performance implications

Architecture

System Architecture

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   CLI Layer     │    │  Manager Layer  │    │  Utility Layer  │
│                 │    │                 │    │                 │
│ - Commands      │    │ - ProjectMgr    │    │ - FileSystem    │
│ - Options       │    │ - ProcessMgr    │    │ - Logger        │
│ - Validation    │    │ - BuildMgr      │    │ - Validator     │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         └───────────────────────┼───────────────────────┘
                                 │
                    ┌─────────────────┐
                    │   AO Process    │
                    │                 │
                    │ - Lua Scripts   │
                    │ - AOS CLI       │
                    │ - Arweave       │
                    └─────────────────┘

Layer Responsibilities

CLI Layer

  • Command parsing and validation
  • User interface and feedback
  • Error handling and reporting
  • Help and documentation

Manager Layer

  • Business logic implementation
  • Process orchestration
  • Configuration management
  • External service integration

Utility Layer

  • Common functionality
  • File system operations
  • Logging and debugging
  • Validation and formatting

Coding Standards

TypeScript Guidelines

Type Definitions

// Use interfaces for object shapes
interface ProjectConfig {
  name: string
  framework: Framework
  packageManager: PackageManager
}

// Use type aliases for unions
type Framework = 'nextjs' | 'nuxtjs'
type PackageManager = 'npm' | 'pnpm' | 'yarn'

// Use enums for constants
enum ErrorCode {
  PROJECT_NOT_FOUND = 'PROJECT_NOT_FOUND',
  INVALID_CONFIG = 'INVALID_CONFIG'
}

Function Signatures

// Use explicit return types
async function createProject(options: CreateProjectOptions): Promise<void> {
  // Implementation
}

// Use optional parameters appropriately
function validateConfig(config: Config, strict: boolean = false): ValidationResult {
  // Implementation
}

// Use generics for reusable functions
function mapArray<T, U>(array: T[], mapper: (item: T) => U): U[] {
  return array.map(mapper)
}

Error Handling

// Use custom error classes
class AOForgeError extends Error {
  constructor(
    public code: string,
    message: string,
    public details?: any
  ) {
    super(message)
    this.name = 'AOForgeError'
  }
}

// Handle errors appropriately
async function riskyOperation(): Promise<void> {
  try {
    await performOperation()
  } catch (error) {
    if (error instanceof AOForgeError) {
      throw error
    }
    throw new AOForgeError('OPERATION_FAILED', 'Operation failed', error)
  }
}

Naming Conventions

Files and Directories

src/
├── cli/                    # CLI commands
│   ├── init.ts            # init command
│   ├── dev.ts             # dev command
│   └── build.ts           # build command
├── managers/              # Manager classes
│   ├── project-manager.ts # Project management
│   ├── process-manager.ts # Process management
│   └── build-manager.ts   # Build management
├── utils/                 # Utility functions
│   ├── file-system.ts     # File operations
│   ├── logger.ts          # Logging utilities
│   └── validator.ts       # Validation utilities
└── types/                 # Type definitions
    ├── project.ts         # Project types
    ├── process.ts         # Process types
    └── build.ts           # Build types

Classes and Interfaces

// Use PascalCase for classes
class ProjectManager {}
class ProcessManager {}
class BuildManager {}

// Use PascalCase for interfaces
interface ProjectConfig {}
interface ProcessOptions {}
interface BuildResult {}

// Use camelCase for functions and variables
const projectName = 'my-project'
function createProject() {}
function validateConfig() {}

Constants

// Use UPPER_CASE for constants
const DEFAULT_PORT = 3000
const MAX_RETRIES = 3
const SUPPORTED_FRAMEWORKS = ['nextjs', 'nuxtjs'] as const

// Use camelCase for configuration objects
const defaultConfig = {
  port: 3000,
  framework: 'nextjs',
  packageManager: 'pnpm'
}

Development Practices

Command Development

Command Structure

class ExampleCommand extends Command {
  name = 'example'
  description = 'Example command description'
  
  options: CommandOption[] = [
    {
      name: 'option1',
      alias: 'o',
      description: 'Option description',
      type: 'string',
      required: true
    }
  ]
  
  async execute(args: string[], options: Record<string, any>): Promise<void> {
    // Validate inputs
    this.validateArgs(args)
    this.validateOptions(options)
    
    try {
      // Perform operation
      await this.performOperation(args, options)
    } catch (error) {
      this.handleError(error)
    }
  }
  
  private async performOperation(args: string[], options: Record<string, any>): Promise<void> {
    // Implementation
  }
}

Input Validation

// Validate command arguments
private validateArgs(args: string[]): void {
  if (args.length === 0) {
    throw new AOForgeError('MISSING_ARGUMENTS', 'Command requires arguments')
  }
}

// Validate command options
private validateOptions(options: Record<string, any>): void {
  if (options.required && !options.value) {
    throw new AOForgeError('MISSING_OPTION', 'Required option is missing')
  }
}

Manager Development

Manager Structure

class ExampleManager {
  private logger: Logger
  private config: Config
  
  constructor(logger: Logger, config: Config) {
    this.logger = logger
    this.config = config
  }
  
  async performOperation(options: OperationOptions): Promise<OperationResult> {
    this.logger.info('Starting operation', { options })
    
    try {
      const result = await this.executeOperation(options)
      this.logger.info('Operation completed', { result })
      return result
    } catch (error) {
      this.logger.error('Operation failed', { error })
      throw error
    }
  }
  
  private async executeOperation(options: OperationOptions): Promise<OperationResult> {
    // Implementation
  }
}

Error Handling

// Use try-catch for error handling
async function riskyOperation(): Promise<void> {
  try {
    await performOperation()
  } catch (error) {
    if (error instanceof AOForgeError) {
      throw error
    }
    throw new AOForgeError('OPERATION_FAILED', 'Operation failed', error)
  }
}

// Use Result pattern for operations that can fail
type Result<T, E = Error> = 
  | { success: true; data: T }
  | { success: false; error: E }

async function safeOperation(): Promise<Result<string>> {
  try {
    const result = await performOperation()
    return { success: true, data: result }
  } catch (error) {
    return { success: false, error: error as Error }
  }
}

Testing

Test Structure

describe('ExampleManager', () => {
  let manager: ExampleManager
  let mockLogger: jest.Mocked<Logger>
  let mockConfig: jest.Mocked<Config>
  
  beforeEach(() => {
    mockLogger = createMockLogger()
    mockConfig = createMockConfig()
    manager = new ExampleManager(mockLogger, mockConfig)
  })
  
  describe('performOperation', () => {
    it('should perform operation successfully', async () => {
      // Arrange
      const options = createTestOptions()
      const expectedResult = createExpectedResult()
      
      // Act
      const result = await manager.performOperation(options)
      
      // Assert
      expect(result).toEqual(expectedResult)
      expect(mockLogger.info).toHaveBeenCalledWith('Starting operation', { options })
    })
    
    it('should handle errors gracefully', async () => {
      // Arrange
      const options = createTestOptions()
      const error = new Error('Operation failed')
      jest.spyOn(manager, 'executeOperation').mockRejectedValue(error)
      
      // Act & Assert
      await expect(manager.performOperation(options)).rejects.toThrow(error)
      expect(mockLogger.error).toHaveBeenCalledWith('Operation failed', { error })
    })
  })
})

Mocking

// Create mock objects
const createMockLogger = (): jest.Mocked<Logger> => ({
  info: jest.fn(),
  warn: jest.fn(),
  error: jest.fn(),
  debug: jest.fn()
})

const createMockConfig = (): jest.Mocked<Config> => ({
  get: jest.fn(),
  set: jest.fn(),
  validate: jest.fn()
})

// Use mocks in tests
beforeEach(() => {
  mockLogger.info.mockClear()
  mockConfig.get.mockClear()
})

Performance Considerations

Optimization Strategies

  1. Lazy Loading - Load resources only when needed
  2. Caching - Cache expensive operations
  3. Parallel Processing - Use async/await for concurrent operations
  4. Memory Management - Avoid memory leaks
  5. Bundle Size - Keep bundle size minimal

Performance Monitoring

// Measure execution time
const startTime = Date.now()
await performOperation()
const duration = Date.now() - startTime
logger.info('Operation completed', { duration })

// Monitor memory usage
const memoryUsage = process.memoryUsage()
logger.debug('Memory usage', memoryUsage)

Security Considerations

Input Validation

// Validate all inputs
function validateInput(input: unknown): string {
  if (typeof input !== 'string') {
    throw new AOForgeError('INVALID_INPUT', 'Input must be a string')
  }
  
  if (input.length === 0) {
    throw new AOForgeError('EMPTY_INPUT', 'Input cannot be empty')
  }
  
  return input
}

File System Security

// Validate file paths
function validateFilePath(path: string): string {
  const resolvedPath = path.resolve(path)
  const allowedDir = path.resolve(process.cwd())
  
  if (!resolvedPath.startsWith(allowedDir)) {
    throw new AOForgeError('INVALID_PATH', 'Path outside allowed directory')
  }
  
  return resolvedPath
}

Documentation

Code Documentation

/**
 * Creates a new project with the specified options
 * @param options - Project creation options
 * @returns Promise that resolves when project is created
 * @throws {AOForgeError} When project creation fails
 * @example
 * ```typescript
 * await createProject({
 *   name: 'my-project',
 *   framework: 'nextjs',
 *   packageManager: 'pnpm'
 * })
 * ```
 */
async function createProject(options: CreateProjectOptions): Promise<void> {
  // Implementation
}

README Documentation

## Example Command

The `example` command demonstrates how to create a new command.

### Usage

```bash
ao-forge example <name> [options]

Options

  • --option1, -o - Description of option1 (required)
  • --option2 - Description of option2 (optional)

Examples

# Basic usage
ao-forge example my-project

# With options
ao-forge example my-project --option1 value1 --option2 value2

## Best Practices

### General Practices
1. **Write tests first** - Use TDD when possible
2. **Keep functions small** - Single responsibility principle
3. **Use meaningful names** - Code should be self-documenting
4. **Handle errors gracefully** - Provide helpful error messages
5. **Document public APIs** - Include JSDoc comments

### Code Organization
1. **Group related functionality** - Keep related code together
2. **Use consistent patterns** - Follow established patterns
3. **Minimize dependencies** - Only import what you need
4. **Use interfaces** - Define clear contracts
5. **Keep modules focused** - Single responsibility

### Performance
1. **Profile before optimizing** - Measure first
2. **Use appropriate data structures** - Choose the right tool
3. **Avoid premature optimization** - Focus on correctness first
4. **Consider memory usage** - Be mindful of memory consumption
5. **Use async/await** - For non-blocking operations

## Next Steps

- [Testing Guide](/contributing/testing) - Learn about testing practices
- [Code Review Process](/contributing/code-review) - Learn about code review
- [Release Process](/contributing/release) - Learn about releases
- [Setup Guide](/contributing/setup) - Development environment setup