diff --git a/src/WebApiDebugger/WebApiDebugger.csproj b/src/WebApiDebugger/WebApiDebugger.csproj index f3bb99b..827948e 100644 --- a/src/WebApiDebugger/WebApiDebugger.csproj +++ b/src/WebApiDebugger/WebApiDebugger.csproj @@ -5,14 +5,13 @@ enable false false - true + false - false true GenerateContract diff --git a/src/WebApiDebugger/openapi-v1.yaml b/src/WebApiDebugger/openapi-v1.yaml index 2dc5786..5b8b523 100644 --- a/src/WebApiDebugger/openapi-v1.yaml +++ b/src/WebApiDebugger/openapi-v1.yaml @@ -1,4 +1,5 @@ -openapi: 3.0.1 +# DO NOT EDIT. This is a generated file +openapi: 3.0.4 info: title: WebApiDebugger version: '1.0' diff --git a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs index 1f7880b..3260c65 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -1,11 +1,13 @@ +using System.Diagnostics; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using Microsoft.Build.Framework; namespace Workleap.OpenApi.MSBuild; internal sealed class SwaggerManager : ISwaggerManager { - private const string SwaggerVersion = "9.0.4"; + private const string DefaultSwaggerVersion = "9.0.4"; private const string DoNotEditComment = "# DO NOT EDIT. This is a generated file\n"; private const int MaxRetryCount = 3; private readonly IProcessWrapper _processWrapper; @@ -14,6 +16,7 @@ internal sealed class SwaggerManager : ISwaggerManager private readonly string _swaggerDirectory; private readonly string _openApiToolsDirectoryPath; private readonly string _swaggerExecutablePath; + private readonly string _swaggerVersion; public SwaggerManager(ILoggerWrapper loggerWrapper, IProcessWrapper processWrapper, string openApiToolsDirectoryPath, string openApiWebApiAssemblyPath) { @@ -21,7 +24,20 @@ public SwaggerManager(ILoggerWrapper loggerWrapper, IProcessWrapper processWrapp this._loggerWrapper = loggerWrapper; this._openApiWebApiAssemblyPath = openApiWebApiAssemblyPath; this._openApiToolsDirectoryPath = openApiToolsDirectoryPath; - this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, "swagger", SwaggerVersion); + + // Detect Swashbuckle version from the application's assemblies + var assemblyDirectory = Path.GetDirectoryName(openApiWebApiAssemblyPath); + if (string.IsNullOrEmpty(assemblyDirectory)) + { + this._loggerWrapper.LogMessage("Could not determine assembly directory from path. Using default Swashbuckle CLI version.", MessageImportance.Low); + this._swaggerVersion = DefaultSwaggerVersion; + } + else + { + this._swaggerVersion = this.DetectSwashbuckleVersion(assemblyDirectory) ?? DefaultSwaggerVersion; + } + + this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, "swagger", this._swaggerVersion); this._swaggerExecutablePath = Path.Combine(this._swaggerDirectory, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "swagger.exe" : "swagger"); } @@ -48,13 +64,13 @@ public async Task InstallSwaggerCliAsync(CancellationToken cancellationToken) return; } - this._loggerWrapper.LogMessage($"🔧 Installing Swashbuckle.AspNetCore.Cli {SwaggerVersion}..."); + this._loggerWrapper.LogMessage($"🔧 Installing Swashbuckle.AspNetCore.Cli {this._swaggerVersion}..."); for (var retryCount = 0; retryCount < MaxRetryCount; retryCount++) { var result = await this._processWrapper.RunProcessAsync( "dotnet", - ["tool", "update", "Swashbuckle.AspNetCore.Cli", "--ignore-failed-sources", "--tool-path", this._swaggerDirectory, "--configfile", Path.Combine(this._openApiToolsDirectoryPath, "nuget.config"), "--version", SwaggerVersion], + ["tool", "update", "Swashbuckle.AspNetCore.Cli", "--ignore-failed-sources", "--tool-path", this._swaggerDirectory, "--configfile", Path.Combine(this._openApiToolsDirectoryPath, "nuget.config"), "--version", this._swaggerVersion], cancellationToken); var isLastRetry = retryCount == MaxRetryCount - 1; @@ -75,7 +91,7 @@ public async Task InstallSwaggerCliAsync(CancellationToken cancellationToken) break; } - this._loggerWrapper.LogMessage($"✅ Swashbuckle.AspNetCore.Cli {SwaggerVersion} installed successfully."); + this._loggerWrapper.LogMessage($"✅ Swashbuckle.AspNetCore.Cli {this._swaggerVersion} installed successfully."); } public async Task GenerateOpenApiSpecAsync(string swaggerExePath, string outputOpenApiSpecPath, string documentName, CancellationToken cancellationToken) @@ -123,4 +139,58 @@ private void PrependComment(string outputOpenApiSpecPath, string comment) this._loggerWrapper.LogWarning($"Failed to add comment to generated spec for {outputOpenApiSpecPath}."); } } + + private string? DetectSwashbuckleVersion(string? assemblyDirectory) + { + if (string.IsNullOrEmpty(assemblyDirectory) || !Directory.Exists(assemblyDirectory)) + { + this._loggerWrapper.LogMessage("Could not detect Swashbuckle version: assembly directory not found. Using default version.", MessageImportance.Low); + return null; + } + + try + { + // Look for Swashbuckle.AspNetCore.SwaggerGen.dll in the assembly directory + // (Swashbuckle.AspNetCore is a metapackage, so we check for one of its actual assemblies) + var swashbuckleAssemblyPath = Path.Combine(assemblyDirectory, "Swashbuckle.AspNetCore.SwaggerGen.dll"); + + if (!File.Exists(swashbuckleAssemblyPath)) + { + this._loggerWrapper.LogMessage("Could not detect Swashbuckle version: Swashbuckle.AspNetCore.SwaggerGen.dll not found in assembly directory. Using default version.", MessageImportance.Low); + return null; + } + + // Get the assembly version using FileVersionInfo (doesn't load the assembly) + var versionInfo = FileVersionInfo.GetVersionInfo(swashbuckleAssemblyPath); + var productVersion = versionInfo.ProductVersion; + + if (!string.IsNullOrEmpty(productVersion)) + { + // Product version might include metadata like "9.0.6+commit", extract just the version number + var versionParts = productVersion.Split('+', '-'); + var versionString = versionParts[0]; + + // Validate that the extracted version matches semantic versioning pattern (e.g., 9.0.4, 10.0.1) + // Pattern: major.minor.patch format, optionally followed by build number + // Requires at least 3 parts (major.minor.patch) as per semantic versioning + if (!string.IsNullOrWhiteSpace(versionString) && + Regex.IsMatch(versionString, @"^\d{1,4}\.\d{1,4}\.\d{1,4}(\.\d{1,4})?$")) + { + this._loggerWrapper.LogMessage($"✓ Detected Swashbuckle.AspNetCore version: {versionString}", MessageImportance.Normal); + return versionString; + } + + this._loggerWrapper.LogMessage($"Detected version '{versionString}' does not match expected format. Using default version.", MessageImportance.Low); + return null; + } + + this._loggerWrapper.LogMessage("Could not detect Swashbuckle version from assembly metadata. Using default version.", MessageImportance.Low); + return null; + } + catch (Exception ex) + { + this._loggerWrapper.LogMessage($"Error detecting Swashbuckle version: {ex.Message}. Using default version.", MessageImportance.Low); + return null; + } + } } \ No newline at end of file