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
- User-First Design - Always consider the user experience
- Simplicity - Keep things simple and intuitive
- Reliability - Ensure code is robust and handles errors gracefully
- Performance - Optimize for speed and efficiency
- 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
- Lazy Loading - Load resources only when needed
- Caching - Cache expensive operations
- Parallel Processing - Use async/await for concurrent operations
- Memory Management - Avoid memory leaks
- 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