using NetDocsForLLM.Models; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading.Tasks; namespace NetDocsForLLM.Services { public interface IDocFxService { Task GenerateMetadataAsync(IEnumerable assemblies); } public class DocFxService : IDocFxService { private readonly string _workingDirectory; private readonly string _docfxPath; public DocFxService() { // Create a temporary working directory _workingDirectory = Path.Combine(Path.GetTempPath(), "NetDocsForLLM_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_workingDirectory); // Locate DocFX executable in the packages directory var baseDir = AppDomain.CurrentDomain.BaseDirectory; _docfxPath = Path.Combine(baseDir, "docfx", "docfx.exe"); // If not found in the default location, try to locate it in the packages directory if (!File.Exists(_docfxPath)) { var packagesDir = Path.Combine(baseDir, "..", "..", "..", "packages"); var docfxPaths = Directory.GetFiles(packagesDir, "docfx.exe", SearchOption.AllDirectories); if (docfxPaths.Length > 0) { _docfxPath = docfxPaths[0]; } else { throw new FileNotFoundException("No se pudo encontrar docfx.exe. Asegúrese de que el paquete docfx.console esté instalado."); } } } public async Task GenerateMetadataAsync(IEnumerable assemblies) { try { // Create DocFX configuration var configPath = Path.Combine(_workingDirectory, "docfx.json"); var config = CreateDocFxConfig(assemblies); File.WriteAllText(configPath, config); // Run DocFX metadata var result = await RunDocFxMetadataAsync(configPath); // Return path to the generated metadata var apiPath = Path.Combine(_workingDirectory, "obj", "api"); return apiPath; } catch (Exception ex) { throw new InvalidOperationException($"Error al generar metadatos con DocFX: {ex.Message}", ex); } } private string CreateDocFxConfig(IEnumerable assemblies) { var assemblyPaths = new List(); var xmlPaths = new List(); foreach (var assembly in assemblies) { assemblyPaths.Add(assembly.FilePath); if (assembly.HasXmlDocumentation) { xmlPaths.Add(assembly.XmlDocPath); } } return $@" {{ ""metadata"": [ {{ ""src"": [ {{ ""files"": [ ""{string.Join("\",\n \"", assemblyPaths.Select(p => p.Replace("\\", "\\\\")))}"" ], ""src"": ""."" }} ], ""dest"": ""obj/api"", ""properties"": {{ ""TargetFramework"": ""net6.0"" }}, ""disableGitFeatures"": true, ""disableDefaultFilter"": false }} ], ""build"": {{ ""content"": [ {{ ""files"": [""*.yml""], ""src"": ""obj/api"", ""dest"": ""api"" }} ], ""resource"": [ {{ ""files"": [""images/**""], ""exclude"": [""obj/**"", ""_site/**""] }} ], ""dest"": ""_site"", ""globalMetadataFiles"": [], ""fileMetadataFiles"": [], ""template"": [""default""], ""postProcessors"": [], ""markdownEngineName"": ""markdig"", ""noLangKeyword"": false, ""keepFileLink"": false, ""cleanupCacheHistory"": false, ""disableGitFeatures"": false }} }}"; } private async Task RunDocFxMetadataAsync(string configPath) { var startInfo = new ProcessStartInfo { FileName = _docfxPath, Arguments = $"metadata \"{configPath}\"", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, WorkingDirectory = _workingDirectory }; using var process = new Process { StartInfo = startInfo }; var outputBuilder = new System.Text.StringBuilder(); var errorBuilder = new System.Text.StringBuilder(); process.OutputDataReceived += (sender, e) => { if (!string.IsNullOrEmpty(e.Data)) outputBuilder.AppendLine(e.Data); }; process.ErrorDataReceived += (sender, e) => { if (!string.IsNullOrEmpty(e.Data)) errorBuilder.AppendLine(e.Data); }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); await process.WaitForExitAsync(); if (process.ExitCode != 0) { throw new InvalidOperationException( $"DocFX metadata falló con código de salida {process.ExitCode}. Error: {errorBuilder}"); } return outputBuilder.ToString(); } } }