Skip to content

Commit 05b325a

Browse files
committed
extracting common utils
1 parent 29228e6 commit 05b325a

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

CLAUDE.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Essential Commands
6+
7+
```bash
8+
# Development
9+
npm run build # Compile TypeScript to dist/
10+
npm run dev # Watch mode development
11+
./bin/run.js [command] # Run CLI locally during development
12+
13+
# Testing
14+
npm test # Run all tests (unit → integration → e2e)
15+
npm run test:unit # Unit tests only
16+
npm run test:integration # Integration tests only
17+
18+
# Code Quality
19+
npm run lint # ESLint with TypeScript checking
20+
npm run format # Prettier formatting
21+
```
22+
23+
## Architecture Overview
24+
25+
### Command System
26+
- **Base Pattern**: All commands extend `BaseCommand` from `src/commands/base-command.ts`
27+
- **Structure**: Each command has `index.ts` (definition) + `<command>.ts` (implementation)
28+
- **Registration**: Commands registered in `src/commands/main.ts`
29+
- **Framework**: Built on Commander.js with custom help formatting
30+
31+
### Key Architectural Constraints
32+
1. **ESM Module**: Pure ES modules, no CommonJS
33+
2. **Working Directory**: Never use `process.cwd()` - use `command.workingDir`
34+
3. **Chalk Usage**: Import from safe helper, not directly
35+
4. **TypeScript**: Strict checking enabled, compile to `dist/`
36+
37+
### Configuration System
38+
- **Netlify Config**: Uses `@netlify/config` for `netlify.toml` resolution
39+
- **Contexts**: Supports production, dev, deploy-preview contexts
40+
- **Workspace-aware**: Detects and handles JavaScript monorepos (npm/yarn/pnpm)
41+
- **Authentication**: OAuth browser flow with token management
42+
43+
### Directory Structure
44+
```
45+
src/
46+
├── commands/ # CLI commands (api, build, deploy, dev, etc.)
47+
├── lib/ # Core functionality (functions, edge-functions, build)
48+
├── utils/ # Utilities and helpers
49+
└── recipes/ # Code generation recipes
50+
```
51+
52+
## Development Patterns
53+
54+
### Adding New Commands
55+
1. Create `src/commands/<name>/` directory
56+
2. Implement command class extending `BaseCommand`
57+
3. Export from `index.ts` with proper types
58+
4. Register in `src/commands/main.ts`
59+
5. Add tests in `tests/integration/commands/<name>/`
60+
61+
### Testing Strategy
62+
- **Unit**: Fast, isolated logic tests
63+
- **Integration**: CLI command testing with fixtures in `tests/integration/__fixtures__/`
64+
- **E2E**: Full end-to-end scenarios
65+
- **Live Tests**: Some create actual Netlify sites (use `NETLIFY_TEST_DISABLE_LIVE=true` to skip)
66+
67+
### Monorepo Support
68+
Commands automatically detect workspace context and filter packages. Use `base-command.ts` methods for workspace handling rather than implementing custom logic.
69+
70+
## Special Considerations
71+
72+
### Error Handling
73+
All commands include telemetry and comprehensive error reporting. Use `BaseCommand` error handling patterns rather than raw throws.
74+
75+
### Environment Variables
76+
- `DEBUG_TESTS=true` - Show subprocess output in tests
77+
- `NETLIFY_TEST_DISABLE_LIVE=true` - Skip tests requiring live Netlify sites
78+
- Various `NETLIFY_*` vars for authentication and API endpoints
79+
80+
### Code Quality
81+
ESLint enforces strict TypeScript checking with custom rules preventing common issues like incorrect working directory usage. Always run linting before commits.

src/utils/mcp-utils.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { resolve } from 'node:path'
2+
import { promises as fs } from 'node:fs'
3+
import { homedir } from 'node:os'
4+
import { chalk, log } from './command-helpers.js'
5+
import type { ConsumerConfig } from '../recipes/ai-context/context.js'
6+
7+
/**
8+
* Generate MCP configuration for the detected IDE or development environment
9+
*/
10+
export const generateMcpConfig = (ide: ConsumerConfig): Record<string, unknown> => {
11+
const configs: Record<string, Record<string, unknown>> = {
12+
vscode: {
13+
servers: {
14+
netlify: {
15+
type: 'stdio',
16+
command: 'npx',
17+
args: ['-y', '@netlify/mcp'],
18+
},
19+
},
20+
},
21+
cursor: {
22+
mcpServers: {
23+
netlify: {
24+
command: 'npx',
25+
args: ['-y', '@netlify/mcp'],
26+
},
27+
},
28+
},
29+
windsurf: {
30+
mcpServers: {
31+
netlify: {
32+
command: 'npx',
33+
args: ['-y', '@netlify/mcp'],
34+
},
35+
},
36+
},
37+
}
38+
39+
return (
40+
configs[ide.key] ?? {
41+
mcpServers: {
42+
netlify: {
43+
command: 'npx',
44+
args: ['-y', '@netlify/mcp'],
45+
},
46+
},
47+
}
48+
)
49+
}
50+
51+
/**
52+
* VS Code specific MCP configuration
53+
*/
54+
export const configureMcpForVSCode = async (config: Record<string, unknown>, projectPath: string): Promise<void> => {
55+
const vscodeDirPath = resolve(projectPath, '.vscode')
56+
const configPath = resolve(vscodeDirPath, 'mcp.json')
57+
58+
try {
59+
// Create .vscode directory if it doesn't exist
60+
await fs.mkdir(vscodeDirPath, { recursive: true })
61+
62+
// Write or update mcp.json
63+
let existingConfig: Record<string, unknown> = {}
64+
try {
65+
const existingContent = await fs.readFile(configPath, 'utf-8')
66+
existingConfig = JSON.parse(existingContent) as Record<string, unknown>
67+
} catch {
68+
// File doesn't exist or is invalid JSON
69+
}
70+
71+
const updatedConfig = { ...existingConfig, ...config }
72+
73+
await fs.writeFile(configPath, JSON.stringify(updatedConfig, null, 2), 'utf-8')
74+
log(`${chalk.green('✅')} VS Code MCP configuration saved to ${chalk.cyan('.vscode/mcp.json')}`)
75+
} catch (error) {
76+
throw new Error(`Failed to configure VS Code MCP: ${error instanceof Error ? error.message : 'Unknown error'}`)
77+
}
78+
}
79+
80+
/**
81+
* Cursor specific MCP configuration
82+
*/
83+
export const configureMcpForCursor = async (config: Record<string, unknown>, projectPath: string): Promise<void> => {
84+
const configPath = resolve(projectPath, '.cursor', 'mcp.json')
85+
86+
try {
87+
await fs.mkdir(resolve(projectPath, '.cursor'), { recursive: true })
88+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8')
89+
log(`${chalk.green('✅')} Cursor MCP configuration saved to ${chalk.cyan('.cursor/mcp.json')}`)
90+
} catch (error) {
91+
throw new Error(`Failed to configure Cursor MCP: ${error instanceof Error ? error.message : 'Unknown error'}`)
92+
}
93+
}
94+
95+
/**
96+
* Windsurf specific MCP configuration
97+
*/
98+
export const configureMcpForWindsurf = async (config: Record<string, unknown>, _projectPath: string): Promise<void> => {
99+
const windsurfDirPath = resolve(homedir(), '.codeium', 'windsurf')
100+
const configPath = resolve(windsurfDirPath, 'mcp_config.json')
101+
102+
try {
103+
// Create .codeium/windsurf directory if it doesn't exist
104+
await fs.mkdir(windsurfDirPath, { recursive: true })
105+
106+
// Read existing config or create new one
107+
let existingConfig: Record<string, unknown> = {}
108+
try {
109+
const existingContent = await fs.readFile(configPath, 'utf-8')
110+
existingConfig = JSON.parse(existingContent) as Record<string, unknown>
111+
} catch {
112+
// File doesn't exist or is invalid JSON
113+
}
114+
115+
// Merge mcpServers from both configs
116+
const existingServers = (existingConfig.mcpServers as Record<string, unknown> | undefined) ?? {}
117+
const newServers = (config.mcpServers as Record<string, unknown> | undefined) ?? {}
118+
119+
const updatedConfig = {
120+
...existingConfig,
121+
mcpServers: {
122+
...existingServers,
123+
...newServers,
124+
},
125+
}
126+
127+
await fs.writeFile(configPath, JSON.stringify(updatedConfig, null, 2), 'utf-8')
128+
log(`${chalk.green('✅')} Windsurf MCP configuration saved`)
129+
log(`${chalk.gray('💡')} Restart Windsurf to activate the MCP server`)
130+
} catch (error) {
131+
throw new Error(`Failed to configure Windsurf MCP: ${error instanceof Error ? error.message : 'Unknown error'}`)
132+
}
133+
}
134+
135+
/**
136+
* Generic MCP configuration display
137+
*/
138+
export const showGenericMcpConfig = (config: Record<string, unknown>, ideName: string): void => {
139+
log(`\n${chalk.yellow('📋 Manual configuration required')}`)
140+
log(`Please add the following configuration to your ${ideName} settings:`)
141+
log(`\n${chalk.gray('--- Configuration ---')}`)
142+
log(JSON.stringify(config, null, 2))
143+
log(`${chalk.gray('--- End Configuration ---')}\n`)
144+
}

0 commit comments

Comments
 (0)