@@ -45,7 +45,7 @@ public async Task<List<FileInfo>> FindAppHostProjectFilesAsync(string searchDire
4545 } ;
4646
4747 interactionService . DisplayMessage ( "magnifying_glass_tilted_left" , InteractionServiceStrings . FindingAppHosts ) ;
48-
48+
4949 // Scan for *.csproj files (existing logic)
5050 var projectFiles = searchDirectory . GetFiles ( "*.csproj" , enumerationOptions ) ;
5151 logger . LogDebug ( "Found {ProjectFileCount} project files in {SearchDirectory}" , projectFiles . Length , searchDirectory . FullName ) ;
@@ -93,7 +93,7 @@ await Parallel.ForEachAsync(projectFiles, parallelOptions, async (projectFile, c
9393 await Parallel . ForEachAsync ( candidateAppHostFiles , parallelOptions , async ( candidateFile , ct ) =>
9494 {
9595 logger . LogDebug ( "Checking single-file apphost candidate {CandidateFile}" , candidateFile . FullName ) ;
96-
96+
9797 if ( await IsValidSingleFileAppHostAsync ( candidateFile , ct ) )
9898 {
9999 logger . LogDebug ( "Found single-file apphost candidate {CandidateFile} in {SearchDirectory}" , candidateFile . FullName , searchDirectory . FullName ) ;
@@ -222,7 +222,7 @@ private static async Task<bool> IsValidSingleFileAppHostAsync(FileInfo candidate
222222 {
223223 logger . LogDebug ( "Provided path {Path} is a directory, searching for project files recursively" , projectFile . FullName ) ;
224224 var directory = new DirectoryInfo ( projectFile . FullName ) ;
225-
225+
226226 // Search recursively for .csproj files and validate they are AppHost projects
227227 // Use ShowStatusAsync and parallel processing similar to FindAppHostProjectFilesAsync
228228 var appHostProjects = await interactionService . ShowStatusAsync ( InteractionServiceStrings . SearchingProjects , async ( ) =>
@@ -232,27 +232,27 @@ private static async Task<bool> IsValidSingleFileAppHostAsync(FileInfo candidate
232232 RecurseSubdirectories = true ,
233233 IgnoreInaccessible = true
234234 } ;
235-
235+
236236 interactionService . DisplayMessage ( "magnifying_glass_tilted_left" , InteractionServiceStrings . FindingAppHosts ) ;
237-
237+
238238 var allProjectFiles = directory . GetFiles ( "*.csproj" , enumerationOptions ) ;
239239 logger . LogDebug ( "Found {ProjectFileCount} project files in {Directory}" , allProjectFiles . Length , directory . FullName ) ;
240-
240+
241241 var foundProjects = new List < FileInfo > ( ) ;
242242 var lockObject = new object ( ) ;
243-
243+
244244 var parallelOptions = new ParallelOptions
245245 {
246246 CancellationToken = cancellationToken ,
247247 MaxDegreeOfParallelism = Environment . ProcessorCount
248248 } ;
249-
249+
250250 // Validate each project to see if it's an AppHost in parallel
251251 await Parallel . ForEachAsync ( allProjectFiles , parallelOptions , async ( candidateProject , ct ) =>
252252 {
253253 logger . LogDebug ( "Checking project file {ProjectFile}" , candidateProject . FullName ) ;
254254 var information = await runner . GetAppHostInformationAsync ( candidateProject , new DotNetCliRunnerInvocationOptions ( ) , ct ) ;
255-
255+
256256 if ( information . ExitCode == 0 && information . IsAspireHost )
257257 {
258258 logger . LogDebug ( "Found AppHost project file {ProjectFile}" , candidateProject . FullName ) ;
@@ -264,17 +264,17 @@ await Parallel.ForEachAsync(allProjectFiles, parallelOptions, async (candidatePr
264264 }
265265 }
266266 } ) ;
267-
267+
268268 // If no .csproj AppHost files found and single-file apphost is enabled, check for apphost.cs
269269 if ( foundProjects . Count == 0 && features . IsFeatureEnabled ( KnownFeatures . SingleFileAppHostEnabled , false ) )
270270 {
271271 var appHostFiles = directory . GetFiles ( "apphost.cs" , enumerationOptions ) ;
272272 logger . LogDebug ( "Found {CandidateFileCount} single-file apphost candidates" , appHostFiles . Length ) ;
273-
273+
274274 await Parallel . ForEachAsync ( appHostFiles , parallelOptions , async ( candidateFile , ct ) =>
275275 {
276276 logger . LogDebug ( "Checking single-file apphost candidate {CandidateFile}" , candidateFile . FullName ) ;
277-
277+
278278 if ( await IsValidSingleFileAppHostAsync ( candidateFile , ct ) )
279279 {
280280 logger . LogDebug ( "Found valid single-file apphost {AppHostFile}" , candidateFile . FullName ) ;
@@ -287,10 +287,10 @@ await Parallel.ForEachAsync(appHostFiles, parallelOptions, async (candidateFile,
287287 }
288288 } ) ;
289289 }
290-
290+
291291 // Sort for deterministic results
292292 foundProjects . Sort ( ( x , y ) => x . FullName . CompareTo ( y . FullName ) ) ;
293-
293+
294294 return foundProjects ;
295295 } ) ;
296296
@@ -351,6 +351,12 @@ await Parallel.ForEachAsync(appHostFiles, parallelOptions, async (candidateFile,
351351 logger . LogDebug ( "Using project file {ProjectFile}" , projectFile . FullName ) ;
352352 return projectFile ;
353353 }
354+ // Handle .fsproj files
355+ else if ( projectFile . Extension . Equals ( ".fsproj" , StringComparison . OrdinalIgnoreCase ) )
356+ {
357+ logger . LogDebug ( "Using project file {ProjectFile}" , projectFile . FullName ) ;
358+ return projectFile ;
359+ }
354360 // Reject other extensions
355361 else
356362 {
@@ -424,7 +430,7 @@ public async Task<IReadOnlyList<FileInfo>> FindExecutableProjectsAsync(string se
424430 var executableProjects = new List < FileInfo > ( ) ;
425431 var lockObject = new object ( ) ;
426432 logger . LogDebug ( "Searching for executable project files in {SearchDirectory}" , searchDirectory ) ;
427-
433+
428434 var enumerationOptions = new EnumerationOptions
429435 {
430436 RecurseSubdirectories = true ,
@@ -444,7 +450,7 @@ public async Task<IReadOnlyList<FileInfo>> FindExecutableProjectsAsync(string se
444450 await Parallel . ForEachAsync ( projectFiles , parallelOptions , async ( projectFile , ct ) =>
445451 {
446452 logger . LogDebug ( "Checking project file {ProjectFile} for OutputType" , projectFile . FullName ) ;
447-
453+
448454 var ( exitCode , jsonDocument ) = await runner . GetProjectItemsAndPropertiesAsync (
449455 projectFile ,
450456 [ ] ,
0 commit comments