Skip to content

Commit dfcc1bd

Browse files
committed
feat: add C#/.NET support with comprehensive tooling
- Add C# tool detector with .NET SDK and tool detection - Add C# config wizard for project configuration - Add C# command runner following JavaScript pattern - Add 8 C# commands: setup, test, build, lint, format, clean, dev, restore - Add C# test infrastructure - Complete C#/.NET language support
1 parent fc1ab42 commit dfcc1bd

22 files changed

Lines changed: 5960 additions & 0 deletions

languages/csharp/config-wizard.js

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
/**
2+
* C#/.NET Configuration Wizard
3+
*
4+
* Interactive configuration for C#/.NET projects
5+
* Following TypeScript/JavaScript pattern exactly
6+
*/
7+
8+
const fs = require('fs');
9+
const path = require('path');
10+
const CSharpToolDetector = require('./tool-detector');
11+
12+
class CSharpConfigWizard {
13+
constructor(projectPath = process.cwd()) {
14+
this.projectPath = projectPath;
15+
this.toolDetector = new CSharpToolDetector();
16+
this.detectedTools = null;
17+
}
18+
19+
/**
20+
* Run interactive configuration wizard for C#/.NET projects
21+
*/
22+
async runWizard(options = {}) {
23+
console.log('🚀 C#/.NET Project Configuration Wizard\n');
24+
25+
// Detect tools first
26+
this.detectedTools = await this.toolDetector.detectTools();
27+
const report = this.toolDetector.generateEnvironmentReport(this.detectedTools);
28+
29+
// Show environment report
30+
this.showEnvironmentReport(report);
31+
32+
// Check if .NET SDK is installed
33+
if (!report.summary.dotnetInstalled) {
34+
console.log('❌ .NET SDK is not installed. Please install .NET SDK first.');
35+
this.showInstallationGuide('dotnet');
36+
return null;
37+
}
38+
39+
// Detect project type (Console, Web API, Blazor, etc.)
40+
const projectType = await this.detectProjectType(options);
41+
42+
// Configure project based on type
43+
const config = await this.configureProject(projectType, options);
44+
45+
// Generate configuration
46+
const fullConfig = this.generateConfiguration(config, report);
47+
48+
// Save configuration
49+
const saved = await this.saveConfiguration(fullConfig);
50+
51+
if (saved) {
52+
this.showSuccessMessage(fullConfig);
53+
}
54+
55+
return fullConfig;
56+
}
57+
58+
/**
59+
* Show environment report
60+
*/
61+
showEnvironmentReport(report) {
62+
console.log('📊 C#/.NET Environment Report:');
63+
console.log('='.repeat(50));
64+
65+
// .NET SDK information
66+
if (report.dotnet) {
67+
console.log(`✅ .NET SDK: ${report.dotnet.version}`);
68+
} else {
69+
console.log('❌ .NET SDK: Not installed');
70+
}
71+
72+
// MSBuild information
73+
if (report.msbuild) {
74+
console.log(`✅ MSBuild: ${report.msbuild.version || 'Available'}`);
75+
} else {
76+
console.log('ℹ️ MSBuild: Not installed (optional for .NET Core)');
77+
}
78+
79+
// NuGet information
80+
if (report.nuget) {
81+
console.log(`✅ NuGet: ${report.nuget.version || 'Available'}`);
82+
} else {
83+
console.log('ℹ️ NuGet: Not installed globally (dotnet CLI has built-in NuGet)');
84+
}
85+
86+
// Testing framework detection
87+
if (report.summary.hasTesting) {
88+
const testingFrameworks = [];
89+
if (report.xunit?.installed) testingFrameworks.push(`xUnit ${report.xunit.version || ''}`);
90+
if (report.nunit?.installed) testingFrameworks.push(`NUnit ${report.nunit.version || ''}`);
91+
if (report.mstest?.installed) testingFrameworks.push(`MSTest ${report.mstest.version || ''}`);
92+
console.log(`🧪 Testing: ${testingFrameworks.join(', ') || 'No testing framework detected'}`);
93+
} else {
94+
console.log('ℹ️ Testing: No testing framework detected (xUnit recommended)');
95+
}
96+
97+
// Formatter detection
98+
if (report.summary.hasFormatter) {
99+
console.log(`🎨 Formatter: dotnet-format ${report['dotnet-format']?.version || 'Available'}`);
100+
} else {
101+
console.log('ℹ️ Formatter: dotnet-format not installed (recommended)');
102+
}
103+
104+
// Coverage tool detection
105+
if (report.summary.hasCoverage) {
106+
console.log(`📊 Coverage: Coverlet ${report.coverlet?.version || 'Available'}`);
107+
} else {
108+
console.log('ℹ️ Coverage: Coverlet not installed (optional)');
109+
}
110+
111+
// Framework detection
112+
if (report.frameworks && report.frameworks.length > 0) {
113+
console.log(`🏗️ Frameworks: ${report.frameworks.join(', ')}`);
114+
}
115+
116+
// Build tool detection
117+
if (report.buildTools && report.buildTools.length > 0) {
118+
console.log(`🔧 Build Tools: ${report.buildTools.join(', ')}`);
119+
}
120+
121+
// .NET Workloads
122+
if (report.workloads && report.workloads.length > 0) {
123+
console.log(`📦 Installed Workloads: ${report.workloads.map((w) => w.id).join(', ')}`);
124+
}
125+
126+
console.log('');
127+
}
128+
129+
/**
130+
* Show installation guide for a tool
131+
*/
132+
showInstallationGuide(toolName) {
133+
const command = this.toolDetector.getInstallationCommand(toolName);
134+
console.log(`💡 Installation command: ${command}`);
135+
console.log('');
136+
}
137+
138+
/**
139+
* Detect project type
140+
*/
141+
async detectProjectType(options = {}) {
142+
const fs = require('fs');
143+
const path = require('path');
144+
145+
// Check for existing .csproj files
146+
const csprojFiles = this.findFiles('**/*.csproj');
147+
148+
if (csprojFiles.length === 0) {
149+
// No existing project, ask user
150+
return this.askForProjectType();
151+
}
152+
153+
// Analyze existing project
154+
const projectTypes = new Set();
155+
156+
for (const csprojFile of csprojFiles) {
157+
try {
158+
const content = fs.readFileSync(path.join(this.projectPath, csprojFile), 'utf8');
159+
160+
if (content.includes('Microsoft.NET.Sdk.Web') || content.includes('Microsoft.AspNetCore')) {
161+
if (content.includes('Microsoft.AspNetCore.Components.Web')) {
162+
projectTypes.add('blazor');
163+
} else {
164+
projectTypes.add('aspnetcore');
165+
}
166+
} else if (content.includes('Microsoft.NET.Sdk.WindowsDesktop')) {
167+
if (content.includes('UseWPF')) {
168+
projectTypes.add('wpf');
169+
} else if (content.includes('UseWindowsForms')) {
170+
projectTypes.add('winforms');
171+
}
172+
} else if (content.includes('Microsoft.NET.Sdk.Maui')) {
173+
projectTypes.add('maui');
174+
} else if (content.includes('OutputType') && content.includes('Library')) {
175+
projectTypes.add('library');
176+
} else {
177+
projectTypes.add('console');
178+
}
179+
} catch (error) {
180+
// Could not read or parse csproj file
181+
projectTypes.add('unknown');
182+
}
183+
}
184+
185+
// If multiple project types, use the first one
186+
const detectedType = Array.from(projectTypes)[0] || 'console';
187+
188+
console.log(`🔍 Detected project type: ${detectedType}`);
189+
return detectedType;
190+
}
191+
192+
/**
193+
* Ask user for project type
194+
*/
195+
askForProjectType() {
196+
// For now, default to console app
197+
// In a full implementation, this would show interactive prompts
198+
console.log('📝 No existing C# project detected.');
199+
console.log('📝 Defaulting to Console Application.');
200+
return 'console';
201+
}
202+
203+
/**
204+
* Configure project based on type
205+
*/
206+
async configureProject(projectType, options = {}) {
207+
const config = {
208+
projectType: projectType,
209+
framework: 'net8.0', // Default to .NET 8
210+
testingFramework: 'xunit', // Default to xUnit
211+
includeFormatter: true,
212+
includeCoverage: false,
213+
includeDevServer: projectType === 'aspnetcore' || projectType === 'blazor',
214+
};
215+
216+
// Set framework based on project type
217+
if (projectType === 'aspnetcore' || projectType === 'blazor') {
218+
config.framework = 'net8.0';
219+
} else if (projectType === 'maui') {
220+
config.framework = 'net8.0';
221+
}
222+
223+
// Check for existing testing framework
224+
if (this.detectedTools?.xunit?.installed) {
225+
config.testingFramework = 'xunit';
226+
} else if (this.detectedTools?.nunit?.installed) {
227+
config.testingFramework = 'nunit';
228+
} else if (this.detectedTools?.mstest?.installed) {
229+
config.testingFramework = 'mstest';
230+
}
231+
232+
// Check for existing formatter
233+
if (this.detectedTools?.['dotnet-format']?.installed) {
234+
config.includeFormatter = true;
235+
}
236+
237+
console.log(`⚙️ Configuration:`);
238+
console.log(` • Project Type: ${config.projectType}`);
239+
console.log(` • Target Framework: ${config.framework}`);
240+
console.log(` • Testing Framework: ${config.testingFramework}`);
241+
console.log(` • Include Formatter: ${config.includeFormatter ? 'Yes' : 'No'}`);
242+
console.log(` • Include Dev Server: ${config.includeDevServer ? 'Yes' : 'No'}`);
243+
console.log('');
244+
245+
return config;
246+
}
247+
248+
/**
249+
* Generate configuration object
250+
*/
251+
generateConfiguration(config, report) {
252+
const fullConfig = {
253+
csharp: {
254+
projectType: config.projectType,
255+
framework: config.framework,
256+
testingFramework: config.testingFramework,
257+
includeFormatter: config.includeFormatter,
258+
includeCoverage: config.includeCoverage,
259+
includeDevServer: config.includeDevServer,
260+
tools: {},
261+
frameworks: report.frameworks || [],
262+
buildTools: report.buildTools || [],
263+
},
264+
};
265+
266+
// Add tool information
267+
if (report.dotnet) {
268+
fullConfig.csharp.tools.dotnet = {
269+
installed: report.dotnet.installed,
270+
version: report.dotnet.version,
271+
path: report.dotnet.path,
272+
};
273+
}
274+
275+
if (report.nuget) {
276+
fullConfig.csharp.tools.nuget = {
277+
installed: report.nuget.installed,
278+
version: report.nuget.version,
279+
path: report.nuget.path,
280+
};
281+
}
282+
283+
if (report.xunit?.installed) {
284+
fullConfig.csharp.tools.xunit = {
285+
installed: report.xunit.installed,
286+
version: report.xunit.version,
287+
};
288+
}
289+
290+
if (report['dotnet-format']?.installed) {
291+
fullConfig.csharp.tools['dotnet-format'] = {
292+
installed: report['dotnet-format'].installed,
293+
version: report['dotnet-format'].version,
294+
};
295+
}
296+
297+
return fullConfig;
298+
}
299+
300+
/**
301+
* Save configuration to .opencode/config.json
302+
*/
303+
async saveConfiguration(config) {
304+
const fs = require('fs');
305+
const path = require('path');
306+
307+
const opencodeDir = path.join(this.projectPath, '.opencode');
308+
const configPath = path.join(opencodeDir, 'config.json');
309+
310+
try {
311+
// Create .opencode directory if it doesn't exist
312+
if (!fs.existsSync(opencodeDir)) {
313+
fs.mkdirSync(opencodeDir, { recursive: true });
314+
}
315+
316+
// Read existing config if it exists
317+
let existingConfig = {};
318+
if (fs.existsSync(configPath)) {
319+
try {
320+
existingConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
321+
} catch (error) {
322+
console.warn('⚠️ Could not parse existing config.json, creating new one');
323+
}
324+
}
325+
326+
// Merge new C# config with existing config
327+
const mergedConfig = {
328+
...existingConfig,
329+
...config,
330+
};
331+
332+
// Write config
333+
fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), 'utf8');
334+
335+
return true;
336+
} catch (error) {
337+
console.error('❌ Failed to save configuration:', error.message);
338+
return false;
339+
}
340+
}
341+
342+
/**
343+
* Show success message
344+
*/
345+
showSuccessMessage(config) {
346+
console.log('✅ C#/.NET project configuration saved!');
347+
console.log('');
348+
console.log('📋 Available commands:');
349+
console.log(' • /csharp-setup - Re-run configuration wizard');
350+
console.log(' • /csharp-build - Build project');
351+
console.log(' • /csharp-test - Run tests');
352+
353+
if (config.csharp.includeDevServer) {
354+
console.log(' • /csharp-dev - Start development server');
355+
}
356+
357+
console.log(' • /csharp-lint - Run code analysis');
358+
console.log(' • /csharp-format - Format code');
359+
console.log(' • /csharp-deps - Manage NuGet packages');
360+
console.log('');
361+
console.log('📁 Configuration saved to: .opencode/config.json');
362+
console.log('');
363+
}
364+
365+
/**
366+
* Find files using glob pattern
367+
*/
368+
findFiles(pattern) {
369+
try {
370+
const { globSync } = require('../../scripts/lib/utils');
371+
return globSync(pattern, { cwd: this.projectPath, nodir: true });
372+
} catch (error) {
373+
return [];
374+
}
375+
}
376+
}
377+
378+
module.exports = CSharpConfigWizard;

0 commit comments

Comments
 (0)