Contributing โ
Help improve Go Bananas! for everyone.
Welcome Contributors! โ
Go Bananas! is an open-source project and we welcome contributions of all kinds:
- ๐ Bug reports and fixes
- โจ New features
- ๐ Documentation improvements
- ๐งช Test coverage
- ๐ก Ideas and feedback
Getting Started โ
Prerequisites โ
- Node.js 24
- npm or pnpm
- Cloudflare account (for testing deployments)
- Git
Development Setup โ
bash
# Clone the repository
git clone https://github.com/davendra/go-bananas-app.git
cd go-bananas-app
# Install dependencies
npm install
# Copy environment template
cp .env.example .env
# Start development server
npm run devRunning Tests โ
bash
# Type checking
npm run typecheck
# Unit tests
npm test
# All tests
npm run test:allContribution Workflow โ
1. Find or Create an Issue โ
- Check existing issues
- For new features, open a discussion first
- For bugs, include steps to reproduce
2. Fork and Branch โ
bash
# Fork on GitHub, then clone
git clone https://github.com/your-username/go-bananas.git
# Create feature branch
git checkout -b feature/your-feature-name3. Make Changes โ
- Follow the code style guide
- Add tests for new functionality
- Update documentation as needed
4. Test Locally โ
bash
npm run typecheck
npm test
npm run dev # Manual testing5. Commit and Push โ
bash
# Commit with conventional commit format
git commit -m "feat: add new feature description"
# Push to your fork
git push origin feature/your-feature-name6. Open Pull Request โ
- Use the PR template
- Link related issues
- Wait for review
Code Style โ
TypeScript โ
- Use TypeScript strict mode
- Prefer
interfaceovertypefor objects - Use explicit return types for functions
- Avoid
any- useunknownif type is uncertain
typescript
// Good
interface ImageMetadata {
id: number;
width: number;
height: number;
}
async function processImage(data: unknown): Promise<ImageMetadata> {
// Implementation
}
// Avoid
async function processImage(data: any) {
// Implementation
}Formatting โ
We use Prettier with default settings:
bash
# Format all files
npx prettier --write .
# Check formatting
npx prettier --check .Naming Conventions โ
| Type | Convention | Example |
|---|---|---|
| Files | kebab-case | image-storage.ts |
| Classes | PascalCase | ImageStorageService |
| Functions | camelCase | processImage |
| Constants | UPPER_SNAKE | MAX_IMAGE_SIZE |
| Interfaces | PascalCase | ImageMetadata |
Project Structure โ
go-bananas/
โโโ src/
โ โโโ index.ts # Worker entry point
โ โโโ mcp/
โ โ โโโ agent.ts # MCP Durable Object
โ โโโ tools/ # MCP tool implementations
โ โโโ api/ # REST API endpoints
โ โโโ auth/ # Authentication
โ โโโ services/ # Business logic
โ โโโ types/ # TypeScript definitions
โโโ frontend/ # React console
โโโ migrations/ # Database migrations
โโโ tests/ # Test files
โโโ doc-site/ # This documentation
โโโ scripts/ # Utility scriptsAdding a New MCP Tool โ
1. Create Tool File โ
typescript
// src/tools/my-new-tool.ts
import { z } from 'zod';
import { trackUsage } from '../services/usage';
import { Env } from '../types';
// 1. Define input schema
export const MyNewToolInputSchema = z.object({
param1: z.string().min(1).describe('First parameter'),
param2: z.number().optional().describe('Optional parameter'),
});
export type MyNewToolInput = z.infer<typeof MyNewToolInputSchema>;
// 2. Define result type
export interface MyNewToolResult {
success: boolean;
data: any;
}
// 3. Implement handler
export async function myNewTool(
env: Env,
tenantId: string,
sessionId: string,
geminiApiKey: string,
params: MyNewToolInput
): Promise<MyNewToolResult> {
const startTime = Date.now();
try {
// Validate input
const input = MyNewToolInputSchema.parse(params);
// Implement logic
const result = await doSomething(input);
// Track usage
await trackUsage(env.DB, {
tenantId,
sessionId,
operation: 'my_new_tool',
imagesGenerated: 0,
durationMs: Date.now() - startTime,
});
return { success: true, data: result };
} catch (error) {
await trackUsage(env.DB, {
tenantId,
sessionId,
operation: 'my_new_tool_failed',
durationMs: Date.now() - startTime,
});
throw error;
}
}
// 4. Export tool definition
export function getMyNewToolDefinition() {
return {
name: 'my_new_tool',
description: 'What this tool does',
inputSchema: MyNewToolInputSchema,
};
}2. Register in Agent โ
typescript
// src/mcp/agent.ts
import { myNewTool, MyNewToolInputSchema } from '../tools/my-new-tool';
// In constructor
this.registerMyNewTool();
// Add registration method
private registerMyNewTool() {
this.server.tool(
'my_new_tool',
'What this tool does',
MyNewToolInputSchema.shape,
async (params) => this.handleMyNewTool(params)
);
}
// Add handler
private async handleMyNewTool(params: unknown) {
const result = await myNewTool(
this.env,
this.tenant.tenantId,
this.sessionId,
this.tenant.geminiApiKey,
params as MyNewToolInput
);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
};
}3. Add Tests โ
typescript
// tests/my-new-tool.test.ts
import { describe, it, expect } from 'vitest';
import { MyNewToolInputSchema, myNewTool } from '../src/tools/my-new-tool';
describe('myNewTool', () => {
it('validates input correctly', () => {
const valid = MyNewToolInputSchema.parse({ param1: 'test' });
expect(valid.param1).toBe('test');
});
it('rejects invalid input', () => {
expect(() => MyNewToolInputSchema.parse({})).toThrow();
});
});4. Update Documentation โ
- Add to MCP tools documentation
- Update tool count in CLAUDE.md
- Add examples in README
Testing Guidelines โ
Unit Tests โ
Test individual functions in isolation:
typescript
import { describe, it, expect, vi } from 'vitest';
describe('functionName', () => {
it('should handle normal case', () => {
const result = functionName(input);
expect(result).toBe(expected);
});
it('should handle edge case', () => {
// Test edge cases
});
it('should throw on invalid input', () => {
expect(() => functionName(invalid)).toThrow();
});
});Integration Tests โ
Test API endpoints and database interactions:
typescript
import { unstable_dev } from 'wrangler';
describe('API Integration', () => {
let worker: UnstableDevWorker;
beforeAll(async () => {
worker = await unstable_dev('src/index.ts', {
experimental: { disableExperimentalWarning: true },
});
});
afterAll(async () => {
await worker.stop();
});
it('returns images list', async () => {
const resp = await worker.fetch('/api/images', {
headers: { 'X-API-Key': 'sk_test_xxx' },
});
expect(resp.status).toBe(200);
});
});Documentation โ
Writing Documentation โ
- Use VitePress markdown
- Include code examples
- Add Mermaid diagrams for complex flows
- Use admonitions for important notes
markdown
::: tip
This is a helpful tip.
:::
::: warning
This is a warning message.
:::
::: danger
This is a dangerous operation.
:::Testing Documentation โ
bash
cd doc-site
npm install
npm run devPull Request Guidelines โ
PR Title Format โ
Use conventional commits:
feat:- New featurefix:- Bug fixdocs:- Documentation onlytest:- Adding testsrefactor:- Code refactoringchore:- Maintenance tasks
PR Checklist โ
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] TypeScript types checked
- [ ] No console.log statements
- [ ] Follows code style
Review Process โ
- Automated checks must pass
- At least one maintainer review
- All comments addressed
- Squash and merge
Community โ
Communication โ
- GitHub Issues - Bug reports and features
- Discussions - Questions and ideas
- Pull Requests - Code contributions
Code of Conduct โ
Be respectful and inclusive. We follow the Contributor Covenant.