From a2c88a5c2906aab135623a8f47b4f5bba4ea0438 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:00:37 +0000 Subject: [PATCH 1/8] Initial plan From ea92b42fb4c2c7ef831acea99b28672e02f8c2bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:07:10 +0000 Subject: [PATCH 2/8] Implement dynamic Swashbuckle CLI version detection Co-authored-by: PrincessMadMath <8277266+PrincessMadMath@users.noreply.github.com> --- .../SwaggerManager.cs | 71 +++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs index 1f7880b..5960abf 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Build.Framework; @@ -5,7 +6,7 @@ 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 +15,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 +23,12 @@ 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); + 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 +55,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 +82,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 +130,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.dll in the assembly directory + var swashbuckleAssemblyPath = Path.Combine(assemblyDirectory, "Swashbuckle.AspNetCore.dll"); + + if (!File.Exists(swashbuckleAssemblyPath)) + { + // Try searching in subdirectories (sometimes dependencies are in different locations) + var searchPattern = "Swashbuckle.AspNetCore.dll"; + var files = Directory.GetFiles(assemblyDirectory, searchPattern, SearchOption.AllDirectories); + + if (files.Length > 0) + { + swashbuckleAssemblyPath = files[0]; + } + else + { + this._loggerWrapper.LogMessage("Could not detect Swashbuckle version: Swashbuckle.AspNetCore.dll not found. 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 version = versionParts[0]; + + this._loggerWrapper.LogMessage($"✓ Detected Swashbuckle.AspNetCore version: {version}", MessageImportance.Normal); + return version; + } + + 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 From e524dfc7692db3ff8ddeef275e9c822121ef4032 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:12:46 +0000 Subject: [PATCH 3/8] Test dynamic version detection with Swashbuckle v9 and v10 Co-authored-by: PrincessMadMath <8277266+PrincessMadMath@users.noreply.github.com> --- src/WebApiDebugger/WebApiDebugger.csproj | 2 +- src/WebApiDebugger/openapi-v1.yaml | 3 ++- src/Workleap.OpenApi.MSBuild/SwaggerManager.cs | 9 +++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/WebApiDebugger/WebApiDebugger.csproj b/src/WebApiDebugger/WebApiDebugger.csproj index f3bb99b..b1fded4 100644 --- a/src/WebApiDebugger/WebApiDebugger.csproj +++ b/src/WebApiDebugger/WebApiDebugger.csproj @@ -12,7 +12,7 @@ - false + true 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 5960abf..d4e528d 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -141,13 +141,14 @@ private void PrependComment(string outputOpenApiSpecPath, string comment) try { - // Look for Swashbuckle.AspNetCore.dll in the assembly directory - var swashbuckleAssemblyPath = Path.Combine(assemblyDirectory, "Swashbuckle.AspNetCore.dll"); + // 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)) { // Try searching in subdirectories (sometimes dependencies are in different locations) - var searchPattern = "Swashbuckle.AspNetCore.dll"; + var searchPattern = "Swashbuckle.AspNetCore.SwaggerGen.dll"; var files = Directory.GetFiles(assemblyDirectory, searchPattern, SearchOption.AllDirectories); if (files.Length > 0) @@ -156,7 +157,7 @@ private void PrependComment(string outputOpenApiSpecPath, string comment) } else { - this._loggerWrapper.LogMessage("Could not detect Swashbuckle version: Swashbuckle.AspNetCore.dll not found. Using default version.", MessageImportance.Low); + this._loggerWrapper.LogMessage("Could not detect Swashbuckle version: Swashbuckle.AspNetCore.SwaggerGen.dll not found. Using default version.", MessageImportance.Low); return null; } } From 9a05fbd5a187db1cafaef83071aef8c572b35194 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:14:19 +0000 Subject: [PATCH 4/8] Revert WebApiDebugger to original configuration Co-authored-by: PrincessMadMath <8277266+PrincessMadMath@users.noreply.github.com> --- src/WebApiDebugger/WebApiDebugger.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WebApiDebugger/WebApiDebugger.csproj b/src/WebApiDebugger/WebApiDebugger.csproj index b1fded4..f3bb99b 100644 --- a/src/WebApiDebugger/WebApiDebugger.csproj +++ b/src/WebApiDebugger/WebApiDebugger.csproj @@ -12,7 +12,7 @@ - true + false true GenerateContract From 3d39e1a16c01d1b7114c6ea385d5718647522e7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:18:15 +0000 Subject: [PATCH 5/8] Address code review feedback: Remove recursive directory search Co-authored-by: PrincessMadMath <8277266+PrincessMadMath@users.noreply.github.com> --- src/WebApiDebugger/WebApiDebugger.csproj | 3 +-- src/Workleap.OpenApi.MSBuild/SwaggerManager.cs | 15 ++------------- 2 files changed, 3 insertions(+), 15 deletions(-) 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/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs index d4e528d..466da92 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -147,19 +147,8 @@ private void PrependComment(string outputOpenApiSpecPath, string comment) if (!File.Exists(swashbuckleAssemblyPath)) { - // Try searching in subdirectories (sometimes dependencies are in different locations) - var searchPattern = "Swashbuckle.AspNetCore.SwaggerGen.dll"; - var files = Directory.GetFiles(assemblyDirectory, searchPattern, SearchOption.AllDirectories); - - if (files.Length > 0) - { - swashbuckleAssemblyPath = files[0]; - } - else - { - this._loggerWrapper.LogMessage("Could not detect Swashbuckle version: Swashbuckle.AspNetCore.SwaggerGen.dll not found. Using default version.", MessageImportance.Low); - return null; - } + 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) From b2c0a193de0362afad3378398924e70474b78636 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:20:59 +0000 Subject: [PATCH 6/8] Add null checks and version validation Co-authored-by: PrincessMadMath <8277266+PrincessMadMath@users.noreply.github.com> --- .../SwaggerManager.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs index 466da92..40ad627 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -26,7 +26,15 @@ public SwaggerManager(ILoggerWrapper loggerWrapper, IProcessWrapper processWrapp // Detect Swashbuckle version from the application's assemblies var assemblyDirectory = Path.GetDirectoryName(openApiWebApiAssemblyPath); - this._swaggerVersion = this.DetectSwashbuckleVersion(assemblyDirectory) ?? DefaultSwaggerVersion; + 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"); @@ -159,10 +167,19 @@ private void PrependComment(string outputOpenApiSpecPath, string comment) { // Product version might include metadata like "9.0.6+commit", extract just the version number var versionParts = productVersion.Split('+', '-'); - var version = versionParts[0]; + var versionString = versionParts[0]; - this._loggerWrapper.LogMessage($"✓ Detected Swashbuckle.AspNetCore version: {version}", MessageImportance.Normal); - return version; + // Validate that the extracted version is a valid version string + // We don't need to parse as System.Version because NuGet versions support more formats + // Just validate it's not empty and looks reasonable + if (!string.IsNullOrWhiteSpace(versionString) && versionString.Contains('.')) + { + this._loggerWrapper.LogMessage($"✓ Detected Swashbuckle.AspNetCore version: {versionString}", MessageImportance.Normal); + return versionString; + } + + this._loggerWrapper.LogMessage($"Detected version '{versionString}' does not appear valid. Using default version.", MessageImportance.Low); + return null; } this._loggerWrapper.LogMessage("Could not detect Swashbuckle version from assembly metadata. Using default version.", MessageImportance.Low); From 6bc85efc6aa87a972f7ef05e4110bf5e1f7cbf60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:23:50 +0000 Subject: [PATCH 7/8] Improve version validation with regex pattern Co-authored-by: PrincessMadMath <8277266+PrincessMadMath@users.noreply.github.com> --- src/Workleap.OpenApi.MSBuild/SwaggerManager.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs index 40ad627..7ed4580 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using Microsoft.Build.Framework; namespace Workleap.OpenApi.MSBuild; @@ -169,16 +170,16 @@ private void PrependComment(string outputOpenApiSpecPath, string comment) var versionParts = productVersion.Split('+', '-'); var versionString = versionParts[0]; - // Validate that the extracted version is a valid version string - // We don't need to parse as System.Version because NuGet versions support more formats - // Just validate it's not empty and looks reasonable - if (!string.IsNullOrWhiteSpace(versionString) && versionString.Contains('.')) + // Validate that the extracted version matches semantic versioning pattern (e.g., 9.0.4, 10.0.1) + // Pattern: 1-4 digits, dot, 1-4 digits, dot, 1-4 digits, optionally followed by more version parts + 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 appear valid. Using default version.", MessageImportance.Low); + this._loggerWrapper.LogMessage($"Detected version '{versionString}' does not match expected format. Using default version.", MessageImportance.Low); return null; } From c11416b8bbb071e2f62080814f26119c614dd0d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:26:07 +0000 Subject: [PATCH 8/8] Fix regex to require 3-part semantic version (major.minor.patch) Co-authored-by: PrincessMadMath <8277266+PrincessMadMath@users.noreply.github.com> --- src/Workleap.OpenApi.MSBuild/SwaggerManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs index 7ed4580..3260c65 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -171,9 +171,10 @@ private void PrependComment(string outputOpenApiSpecPath, string comment) var versionString = versionParts[0]; // Validate that the extracted version matches semantic versioning pattern (e.g., 9.0.4, 10.0.1) - // Pattern: 1-4 digits, dot, 1-4 digits, dot, 1-4 digits, optionally followed by more version parts + // 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})?$")) + 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;