@@ -57,7 +57,8 @@ const PACKAGE_MANAGERS: Array<{ file: string; name: string }> = [
5757 { file : "pnpm-lock.yaml" , name : "pnpm" } ,
5858 { file : "yarn.lock" , name : "yarn" } ,
5959 { file : "package-lock.json" , name : "npm" } ,
60- { file : "bun.lockb" , name : "bun" }
60+ { file : "bun.lockb" , name : "bun" } ,
61+ { file : "packages.lock.json" , name : "nuget" }
6162] ;
6263
6364export async function analyzeRepo ( repoPath : string ) : Promise < RepoAnalysis > {
@@ -82,9 +83,17 @@ export async function analyzeRepo(repoPath: string): Promise<RepoAnalysis> {
8283 const hasRequirements = files . includes ( "requirements.txt" ) ;
8384 const hasGoMod = files . includes ( "go.mod" ) ;
8485 const hasCargo = files . includes ( "Cargo.toml" ) ;
85- const hasCsproj = files . some (
86- ( f ) => f . endsWith ( ".csproj" ) || f . endsWith ( ".sln" ) || f . endsWith ( ".slnx" )
86+ const hasDotnet = files . some (
87+ ( f ) =>
88+ f . endsWith ( ".csproj" ) ||
89+ f . endsWith ( ".fsproj" ) ||
90+ f . endsWith ( ".sln" ) ||
91+ f . endsWith ( ".slnx" ) ||
92+ f === "global.json" ||
93+ f === "Directory.Build.props"
8794 ) ;
95+ const hasCsproj = hasDotnet && files . some ( ( f ) => f . endsWith ( ".csproj" ) ) ;
96+ const hasFsproj = files . some ( ( f ) => f . endsWith ( ".fsproj" ) ) ;
8897 const hasPomXml = files . includes ( "pom.xml" ) ;
8998 const hasBuildGradle = files . includes ( "build.gradle" ) || files . includes ( "build.gradle.kts" ) ;
9099 const hasGemfile = files . includes ( "Gemfile" ) ;
@@ -100,7 +109,8 @@ export async function analyzeRepo(repoPath: string): Promise<RepoAnalysis> {
100109 if ( hasPyProject || hasRequirements ) analysis . languages . push ( "Python" ) ;
101110 if ( hasGoMod ) analysis . languages . push ( "Go" ) ;
102111 if ( hasCargo ) analysis . languages . push ( "Rust" ) ;
103- if ( hasCsproj ) analysis . languages . push ( "C#" ) ;
112+ if ( hasCsproj || ( hasDotnet && ! hasFsproj ) ) analysis . languages . push ( "C#" ) ;
113+ if ( hasFsproj ) analysis . languages . push ( "F#" ) ;
104114 if ( hasPomXml || hasBuildGradle ) analysis . languages . push ( "Java" ) ;
105115 if ( hasGemfile ) analysis . languages . push ( "Ruby" ) ;
106116 if ( hasComposerJson ) analysis . languages . push ( "PHP" ) ;
@@ -120,6 +130,11 @@ export async function analyzeRepo(repoPath: string): Promise<RepoAnalysis> {
120130 analysis . frameworks . push ( ...detectFrameworks ( deps , files ) ) ;
121131 }
122132
133+ if ( hasDotnet ) {
134+ const dotnetFrameworks = await detectDotnetFrameworks ( repoPath ) ;
135+ analysis . frameworks . push ( ...dotnetFrameworks ) ;
136+ }
137+
123138 const workspace = await detectWorkspace ( repoPath , files , rootPackageJson ) ;
124139 if ( workspace ) {
125140 analysis . workspaceType = workspace . type ;
@@ -203,6 +218,63 @@ function detectFrameworks(deps: string[], files: string[]): string[] {
203218 return frameworks ;
204219}
205220
221+ async function detectDotnetFrameworks ( repoPath : string ) : Promise < string [ ] > {
222+ const projectFiles = await fg ( "**/*.{csproj,fsproj}" , {
223+ cwd : repoPath ,
224+ onlyFiles : true ,
225+ ignore : [ "**/node_modules/**" , "**/bin/**" , "**/obj/**" ]
226+ } ) ;
227+
228+ const frameworks : string [ ] = [ ] ;
229+ for ( const projFile of projectFiles ) {
230+ try {
231+ const content = await fs . readFile ( path . join ( repoPath , projFile ) , "utf8" ) ;
232+ frameworks . push ( ...parseDotnetProject ( content ) ) ;
233+ } catch {
234+ // ignore read errors
235+ }
236+ }
237+ return frameworks ;
238+ }
239+
240+ function parseDotnetProject ( content : string ) : string [ ] {
241+ const frameworks : string [ ] = [ ] ;
242+ const hasPackage = ( pkg : string ) : boolean => content . includes ( `Include="${ pkg } "` ) ;
243+
244+ // SDK-based detection
245+ if ( content . includes ( 'Sdk="Microsoft.NET.Sdk.Web"' ) ) frameworks . push ( "ASP.NET Core" ) ;
246+ if ( content . includes ( 'Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"' ) )
247+ frameworks . push ( "Blazor WebAssembly" ) ;
248+
249+ // Package reference detection
250+ if ( hasPackage ( "Microsoft.AspNetCore" ) || hasPackage ( "Microsoft.AspNetCore.App" ) )
251+ frameworks . push ( "ASP.NET Core" ) ;
252+ if ( hasPackage ( "Microsoft.AspNetCore.Components" ) ) frameworks . push ( "Blazor" ) ;
253+ if ( hasPackage ( "Microsoft.EntityFrameworkCore" ) ) frameworks . push ( "Entity Framework" ) ;
254+ if ( hasPackage ( "Microsoft.Maui.Controls" ) ) frameworks . push ( ".NET MAUI" ) ;
255+ if ( hasPackage ( "Xamarin.Forms" ) || hasPackage ( "Xamarin.Essentials" ) ) frameworks . push ( "Xamarin" ) ;
256+
257+ // Project property detection
258+ if ( content . includes ( "<UseWPF>true</UseWPF>" ) ) frameworks . push ( "WPF" ) ;
259+ if ( content . includes ( "<UseWindowsForms>true</UseWindowsForms>" ) ) frameworks . push ( "Windows Forms" ) ;
260+
261+ // Test framework detection
262+ if ( hasPackage ( "xunit" ) || hasPackage ( "xunit.core" ) ) frameworks . push ( "xUnit" ) ;
263+ if ( hasPackage ( "NUnit" ) || hasPackage ( "nunit.framework" ) ) frameworks . push ( "NUnit" ) ;
264+ if ( hasPackage ( "MSTest.TestFramework" ) ) frameworks . push ( "MSTest" ) ;
265+
266+ // Console app fallback
267+ if (
268+ frameworks . length === 0 &&
269+ content . includes ( "<OutputType>Exe</OutputType>" ) &&
270+ content . includes ( 'Sdk="Microsoft.NET.Sdk"' )
271+ ) {
272+ frameworks . push ( "Console" ) ;
273+ }
274+
275+ return frameworks ;
276+ }
277+
206278async function safeReadFile ( filePath : string ) : Promise < string | undefined > {
207279 try {
208280 return await fs . readFile ( filePath , "utf8" ) ;
0 commit comments