From 19938bbab94484560128a0155306fb3536ea81aa Mon Sep 17 00:00:00 2001 From: Taras Kovalenko Date: Sun, 4 May 2025 14:55:13 +0300 Subject: [PATCH 1/4] feat: enhance package version management with NuGet.Versioning support and improved conflict resolution --- .../Analyzers/PackageAnalyzerTests.cs | 164 +++++++++++- .../Analyzers/EnhancedPackageAnalyzer.cs | 173 +++++++++++++ .../Analyzers/PackageAnalyzer.cs | 31 ++- .../CentralConfigGenerator.Core.csproj | 16 +- .../Services/VersionCompatibilityChecker.cs | 92 +++++++ .../Services/VersionConflictResolver.cs | 133 ++++++++++ .../Services/VersionConflictVisualizer.cs | 113 +++++++++ .../CentralConfigGenerator.csproj | 80 +++--- .../Commands/EnhancedPackagesPropsCommand.cs | 125 ++++++++++ CentralConfigGenerator/Program.cs | 29 ++- .../docs/QUICK_START_ENHANCED.md | 170 +++++++++++++ CentralConfigGenerator/docs/README.md | 111 +++++++- .../docs/VERSION_MANAGEMENT.md | 236 ++++++++++++++++++ Directory.Packages.props | 2 + QUICK_START_ENHANCED.md | 170 +++++++++++++ README.md | 111 +++++++- VERSION_MANAGEMENT.md | 236 ++++++++++++++++++ 17 files changed, 1915 insertions(+), 77 deletions(-) create mode 100644 CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs create mode 100644 CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs create mode 100644 CentralConfigGenerator.Core/Services/VersionConflictResolver.cs create mode 100644 CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs create mode 100644 CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs create mode 100644 CentralConfigGenerator/docs/QUICK_START_ENHANCED.md create mode 100644 CentralConfigGenerator/docs/VERSION_MANAGEMENT.md create mode 100644 QUICK_START_ENHANCED.md create mode 100644 VERSION_MANAGEMENT.md diff --git a/CentralConfigGenerator.Core.Tests/Analyzers/PackageAnalyzerTests.cs b/CentralConfigGenerator.Core.Tests/Analyzers/PackageAnalyzerTests.cs index 9355501..01cdeaa 100644 --- a/CentralConfigGenerator.Core.Tests/Analyzers/PackageAnalyzerTests.cs +++ b/CentralConfigGenerator.Core.Tests/Analyzers/PackageAnalyzerTests.cs @@ -242,4 +242,166 @@ public void ExtractPackageVersions_ShouldHandleComplexVersionStrings() result.ShouldContainKey("Package2"); result["Package2"].ShouldBe("2.0.0-beta+metadata"); } -} \ No newline at end of file + + [Fact] + public void ExtractPackageVersions_ShouldHandleSemanticVersioning_PrereleaseComparison() + { + // Arrange + var projectFiles = new List + { + new() + { + Path = "Project1.csproj", + Content = @" + + + + " + }, + new() + { + Path = "Project2.csproj", + Content = @" + + + + " + } + }; + + // Act + var result = _analyzer.ExtractPackageVersions(projectFiles); + + // Assert + result.ShouldNotBeNull(); + result.Count.ShouldBe(1); + result.ShouldContainKey("Package1"); + // beta > alpha in SemVer + result["Package1"].ShouldBe("1.0.0-beta.1"); + } + + [Fact] + public void ExtractPackageVersions_ShouldPreferStableOverPrerelease() + { + // Arrange + var projectFiles = new List + { + new() + { + Path = "Project1.csproj", + Content = @" + + + + " + }, + new() + { + Path = "Project2.csproj", + Content = @" + + + + " + } + }; + + // Act + var result = _analyzer.ExtractPackageVersions(projectFiles); + + // Assert + result.ShouldNotBeNull(); + result.Count.ShouldBe(1); + result.ShouldContainKey("Package1"); + // Stable version > pre-release + result["Package1"].ShouldBe("1.0.0"); + } + + [Fact] + public void ExtractPackageVersions_ShouldHandleVersionRanges() + { + // Arrange + var projectFiles = new List + { + new() + { + Path = "Project1.csproj", + Content = @" + + + + + " + } + }; + + // Act + var result = _analyzer.ExtractPackageVersions(projectFiles); + + // Assert + result.ShouldNotBeNull(); + result.Count.ShouldBe(2); + result.ShouldContainKey("Package1"); + // Original range preserved + result["Package1"].ShouldBe("[1.0.0,2.0.0)"); + result.ShouldContainKey("Package2"); + result["Package2"].ShouldBe("[2.0.0,)"); + } + + [Fact] + public void ExtractPackageVersions_ShouldHandleFloatingVersions() + { + // Arrange + var projectFiles = new List + { + new() + { + Path = "Project1.csproj", + Content = @" + + + + + " + } + }; + + // Act + var result = _analyzer.ExtractPackageVersions(projectFiles); + + // Assert + result.ShouldNotBeNull(); + result.Count.ShouldBe(2); + result.ShouldContainKey("Package1"); + result["Package1"].ShouldBe("1.0.*"); + result.ShouldContainKey("Package2"); + result["Package2"].ShouldBe("1.*"); + } + + [Fact] + public void ExtractPackageVersions_ShouldHandleCommitHashVersions() + { + // Arrange + var projectFiles = new List + { + new() + { + Path = "Project1.csproj", + Content = @" + + + + " + } + }; + + // Act + var result = _analyzer.ExtractPackageVersions(projectFiles); + + // Assert + result.ShouldNotBeNull(); + result.Count.ShouldBe(1); + result.ShouldContainKey("Package1"); + result["Package1"].ShouldBe("1.0.0+abcdef123456"); + } +} diff --git a/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs new file mode 100644 index 0000000..3fd65c9 --- /dev/null +++ b/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs @@ -0,0 +1,173 @@ +using System.Xml.Linq; +using CentralConfigGenerator.Core.Models; +using CentralConfigGenerator.Core.Services; +using NuGet.Versioning; + +namespace CentralConfigGenerator.Core.Analyzers; + +public interface IEnhancedPackageAnalyzer +{ + PackageAnalysisResult AnalyzePackages(IEnumerable projectFiles); +} + +public class PackageAnalysisResult +{ + public Dictionary ResolvedVersions { get; set; } = new(); + public Dictionary> Conflicts { get; set; } = new(); + public List Warnings { get; set; } = new(); +} + +public class VersionConflict +{ + public string ProjectFile { get; set; } = string.Empty; + public string Version { get; set; } = string.Empty; + public bool IsPreRelease { get; set; } + public bool IsRange { get; set; } +} + +public class VersionWarning +{ + public string PackageName { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; + public WarningLevel Level { get; set; } +} + +public enum WarningLevel +{ + Info, + Warning, + Error +} + +public class EnhancedPackageAnalyzer(IVersionConflictResolver conflictResolver) : IEnhancedPackageAnalyzer +{ + public PackageAnalysisResult AnalyzePackages(IEnumerable projectFiles) + { + var result = new PackageAnalysisResult(); + var packageVersionsByPackage = new Dictionary>(); + + // Collect all versions + foreach (var projectFile in projectFiles) + { + try + { + var xDoc = XDocument.Parse(projectFile.Content); + var packageReferences = xDoc.Descendants("PackageReference"); + + foreach (var packageRef in packageReferences) + { + var packageName = packageRef.Attribute("Include")?.Value; + var versionAttr = packageRef.Attribute("Version"); + + if (string.IsNullOrWhiteSpace(packageName) || versionAttr == null) + continue; + + var version = versionAttr.Value; + + if (!packageVersionsByPackage.ContainsKey(packageName)) + packageVersionsByPackage[packageName] = new List<(string, string)>(); + + packageVersionsByPackage[packageName].Add((projectFile.Path, version)); + } + } + catch (Exception ex) + { + result.Warnings.Add(new VersionWarning + { + PackageName = projectFile.Path, + Message = $"Failed to parse project file: {ex.Message}", + Level = WarningLevel.Error + }); + } + } + + // Resolve conflicts and create final versions + foreach (var package in packageVersionsByPackage) + { + var packageName = package.Key; + var versions = package.Value; + var uniqueVersions = versions.Select(v => v.Version).Distinct().ToList(); + + if (uniqueVersions.Count == 1) + { + // No conflict + result.ResolvedVersions[packageName] = uniqueVersions[0]; + } + else + { + // Conflict detected + result.Conflicts[packageName] = versions + .Select(v => CreateVersionConflict(v.ProjectPath, v.Version)) + .ToList(); + + // Try to resolve with the highest version strategy + try + { + var resolvedVersion = conflictResolver.Resolve( + packageName, + uniqueVersions, + VersionResolutionStrategy.Highest); + + result.ResolvedVersions[packageName] = resolvedVersion; + + result.Warnings.Add(new VersionWarning + { + PackageName = packageName, + Message = $"Multiple versions found. Resolved to: {resolvedVersion}", + Level = WarningLevel.Warning + }); + } + catch (Exception ex) + { + result.Warnings.Add(new VersionWarning + { + PackageName = packageName, + Message = $"Failed to resolve version conflict: {ex.Message}", + Level = WarningLevel.Error + }); + + // Fallback: use most recent version + result.ResolvedVersions[packageName] = uniqueVersions.OrderDescending().First(); + } + } + + // Check for pre-release usage + CheckForPrereleaseUsage(packageName, result.ResolvedVersions[packageName], result.Warnings); + } + + return result; + } + + private VersionConflict CreateVersionConflict(string projectPath, string version) + { + var conflict = new VersionConflict + { + ProjectFile = projectPath, + Version = version + }; + + if (NuGetVersion.TryParse(version, out var nugetVersion)) + { + conflict.IsPreRelease = nugetVersion.IsPrerelease; + } + else if (VersionRange.TryParse(version, out _)) + { + conflict.IsRange = true; + } + + return conflict; + } + + private void CheckForPrereleaseUsage(string packageName, string version, List warnings) + { + if (NuGetVersion.TryParse(version, out var nugetVersion) && nugetVersion.IsPrerelease) + { + warnings.Add(new VersionWarning + { + PackageName = packageName, + Message = $"Using pre-release version: {version}", + Level = WarningLevel.Info + }); + } + } +} diff --git a/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs index f6bfb55..f5eb6e3 100644 --- a/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs @@ -1,5 +1,6 @@ using System.Xml.Linq; using CentralConfigGenerator.Core.Models; +using NuGet.Versioning; using Spectre.Console; namespace CentralConfigGenerator.Core.Analyzers; @@ -8,7 +9,7 @@ public class PackageAnalyzer : IPackageAnalyzer { public Dictionary ExtractPackageVersions(IEnumerable projectFiles) { - var packageVersions = new Dictionary(); + var packageVersions = new Dictionary(); var stringVersions = new Dictionary(); foreach (var projectFile in projectFiles) @@ -31,18 +32,30 @@ public Dictionary ExtractPackageVersions(IEnumerable packageVersions[packageName]) + { + packageVersions[packageName] = nugetVersion; + stringVersions[packageName] = versionStr; + } + } + else if (NuGetVersion.TryParse(versionStr, out var nugetVersion)) { - if (!packageVersions.ContainsKey(packageName) || version > packageVersions[packageName]) + if (!packageVersions.ContainsKey(packageName) || nugetVersion > packageVersions[packageName]) { - packageVersions[packageName] = version; + packageVersions[packageName] = nugetVersion; stringVersions[packageName] = versionStr; } } + else + { + // For non-parseable versions (like variables), just store them + stringVersions.TryAdd(packageName, versionStr); + } } } catch (Exception ex) @@ -55,4 +68,4 @@ public Dictionary ExtractPackageVersions(IEnumerable - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs b/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs new file mode 100644 index 0000000..8c00e9e --- /dev/null +++ b/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs @@ -0,0 +1,92 @@ +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; + +namespace CentralConfigGenerator.Core.Services; + +public interface IVersionCompatibilityChecker +{ + Task CheckCompatibilityAsync(string packageId, string version); +} + +public class CompatibilityCheckResult +{ + public bool IsCompatible { get; set; } + public List Issues { get; set; } = new(); + public string? SuggestedVersion { get; set; } +} + +public class VersionCompatibilityChecker : IVersionCompatibilityChecker +{ + private readonly SourceRepository _repository; + + public VersionCompatibilityChecker() + { + var providers = new List>(Repository.Provider.GetCoreV3()); + _repository = new SourceRepository(new PackageSource("https://api.nuget.org/v3/index.json"), providers); + } + + public async Task CheckCompatibilityAsync(string packageId, string version) + { + var result = new CompatibilityCheckResult(); + if (!NuGetVersion.TryParse(version, out var nugetVersion)) + { + result.IsCompatible = false; + result.Issues.Add($"Invalid version format: {version}"); + return result; + } + + // NuGet API lookup + try + { + var metadataResource = await _repository.GetResourceAsync(); + var searchMetadata = await metadataResource.GetMetadataAsync( + packageId, + includePrerelease: true, + includeUnlisted: false, + new SourceCacheContext(), + NullLogger.Instance, + CancellationToken.None); + + var allVersions = searchMetadata + .Select(m => m.Identity.Version) + .OrderByDescending(v => v) + .ToList(); + + if (allVersions.Count == 0) + { + result.Issues.Add("Package not found in NuGet repository."); + result.IsCompatible = false; + return result; + } + + // Check for pre-release + if (nugetVersion.IsPrerelease) + { + result.Issues.Add("Pre-release version detected. Consider using a stable release for production."); + } + + // Check outdated major version + var latestStable = allVersions.FirstOrDefault(v => !v.IsPrerelease); + if (latestStable != null && nugetVersion.Major < latestStable.Major - 1) + { + result.Issues.Add($"This version is significantly outdated. Latest stable is {latestStable}."); + result.SuggestedVersion ??= latestStable.ToNormalizedString(); + } + } + catch (Exception ex) + { + result.Issues.Add($"Failed to fetch package metadata: {ex.Message}"); + } + + return result; + } + + private class VersionIssue + { + public VersionRange ProblematicVersionRange { get; set; } = VersionRange.None; + public string Issue { get; set; } = string.Empty; + public string? SuggestedVersion { get; set; } + } +} diff --git a/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs b/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs new file mode 100644 index 0000000..d4385fc --- /dev/null +++ b/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs @@ -0,0 +1,133 @@ +using NuGet.Versioning; + +namespace CentralConfigGenerator.Core.Services; + +public interface IVersionConflictResolver +{ + string Resolve(string packageName, IEnumerable versions, VersionResolutionStrategy strategy); +} + +public enum VersionResolutionStrategy +{ + Highest, + Lowest, + MostCommon, + Manual +} + +public record ParsedVersion +{ + public required string Original { get; init; } + public NuGetVersion? Parsed { get; init; } + public VersionRange? Range { get; init; } +} + +public class VersionConflictResolver : IVersionConflictResolver +{ + public string Resolve(string packageName, IEnumerable versions, VersionResolutionStrategy strategy) + { + var versionList = versions.ToList(); + + if (versionList.Count == 0) + { + throw new ArgumentException("No versions provided for resolution"); + } + + if (versionList.Count == 1) + { + return versionList[0]; + } + + // Parse all versions into NuGetVersion objects where possible + var parsedVersions = versionList + .Select(v => new ParsedVersion + { + Original = v, + Parsed = NuGetVersion.TryParse(v, out var parsed) ? parsed : null, + Range = VersionRange.TryParse(v, out var range) ? range : null + }) + .ToList(); + + return strategy switch + { + VersionResolutionStrategy.Highest => ResolveHighest(parsedVersions), + VersionResolutionStrategy.Lowest => ResolveLowest(parsedVersions), + VersionResolutionStrategy.MostCommon => ResolveMostCommon(versionList), + VersionResolutionStrategy.Manual => throw new InvalidOperationException( + $"Manual resolution required for package '{packageName}'. Versions found: {string.Join(", ", versionList)}"), + _ => throw new ArgumentOutOfRangeException(nameof(strategy)) + }; + } + + private static string ResolveHighest(List parsedVersions) + { + // First, try to find the highest parsed version + var highestParsed = parsedVersions + .Where(v => v.Parsed != null) + .OrderByDescending(v => v.Parsed) + .FirstOrDefault(); + + if (highestParsed != null) + { + return highestParsed.Original; + } + + // If no parsed versions, try version ranges + var versionRanges = parsedVersions + .Where(v => v.Range is { HasLowerBound: true }) + .OrderByDescending(v => v.Range!.MinVersion) + .FirstOrDefault(); + + if (versionRanges != null) + { + return versionRanges.Original; + } + + // Fallback to string comparison + return parsedVersions + .OrderByDescending(v => v.Original) + .First() + .Original; + } + + private static string ResolveLowest(List parsedVersions) + { + // Try to find the lowest parsed version + var lowestParsed = parsedVersions + .Where(v => v.Parsed != null) + .OrderBy(v => v.Parsed) + .FirstOrDefault(); + + if (lowestParsed != null) + { + return lowestParsed.Original; + } + + // If no parsed versions, try version ranges + var versionRanges = parsedVersions + .Where(v => v.Range is { HasLowerBound: true }) + .OrderBy(v => v.Range!.MinVersion) + .FirstOrDefault(); + + if (versionRanges != null) + { + return versionRanges.Original; + } + + // Fallback to string comparison + return parsedVersions + .OrderBy(v => v.Original) + .First() + .Original; + } + + private static string ResolveMostCommon(List versions) + { + return versions + .GroupBy(v => v) + .OrderByDescending(g => g.Count()) + .ThenBy(g => g.Key) + .First() + .Key; + } +} \ No newline at end of file diff --git a/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs b/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs new file mode 100644 index 0000000..d4492fe --- /dev/null +++ b/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs @@ -0,0 +1,113 @@ +using CentralConfigGenerator.Core.Analyzers; +using Spectre.Console; + +namespace CentralConfigGenerator.Core.Services; + +public interface IVersionConflictVisualizer +{ + void DisplayResults(PackageAnalysisResult result); +} + +public class VersionConflictVisualizer : IVersionConflictVisualizer +{ + public void DisplayResults(PackageAnalysisResult result) + { + // Display summary + var summaryTable = new Table(); + summaryTable.AddColumn("Metric"); + summaryTable.AddColumn(new TableColumn("Value").Centered()); + + summaryTable.AddRow("Total Packages", result.ResolvedVersions.Count.ToString()); + summaryTable.AddRow("Packages with Conflicts", result.Conflicts.Count.ToString()); + summaryTable.AddRow("Warnings", result.Warnings.Count.ToString()); + + AnsiConsole.Write(new Panel(summaryTable) + .Header("[bold green]Package Analysis Summary[/]") + .Border(BoxBorder.Rounded)); + + AnsiConsole.WriteLine(); + + // Display conflicts + if (result.Conflicts.Count != 0) + { + AnsiConsole.MarkupLine("[bold red]Version Conflicts Detected:[/]"); + + var conflictTable = new Table(); + conflictTable.AddColumn("Package"); + conflictTable.AddColumn("Project"); + conflictTable.AddColumn("Version"); + conflictTable.AddColumn("Type"); + + foreach (var conflict in result.Conflicts) + { + foreach (var detail in conflict.Value) + { + var versionType = detail.IsRange ? "Range" : + detail.IsPreRelease ? "Pre-release" : + "Release"; + + conflictTable.AddRow( + conflict.Key, + Markup.Escape(Path.GetFileName(detail.ProjectFile)), + detail.Version, + versionType + ); + } + + // Add separator row + if (conflict.Key != result.Conflicts.Keys.Last()) + { + conflictTable.AddEmptyRow(); + } + } + + AnsiConsole.Write(conflictTable); + AnsiConsole.WriteLine(); + } + + // Display warnings + if (result.Warnings.Count != 0) + { + AnsiConsole.MarkupLine("[bold yellow]Warnings:[/]"); + + var warningsTable = new Table(); + warningsTable.AddColumn("Level"); + warningsTable.AddColumn("Package"); + warningsTable.AddColumn("Message"); + + foreach (var warning in result.Warnings.OrderBy(w => w.Level)) + { + var levelMarkup = warning.Level switch + { + WarningLevel.Info => "[blue]Info[/]", + WarningLevel.Warning => "[yellow]Warning[/]", + WarningLevel.Error => "[red]Error[/]", + _ => "[white]Unknown[/]" + }; + + warningsTable.AddRow( + levelMarkup, + warning.PackageName, + Markup.Escape(warning.Message) + ); + } + + AnsiConsole.Write(warningsTable); + AnsiConsole.WriteLine(); + } + + // Display resolved versions + AnsiConsole.MarkupLine("[bold green]Resolved Package Versions:[/]"); + + var versionTable = new Table(); + versionTable.AddColumn("Package"); + versionTable.AddColumn("Version"); + + foreach (var package in result.ResolvedVersions.OrderBy(p => p.Key)) + { + versionTable.AddRow(package.Key, package.Value); + } + + AnsiConsole.Write(versionTable); + } +} \ No newline at end of file diff --git a/CentralConfigGenerator/CentralConfigGenerator.csproj b/CentralConfigGenerator/CentralConfigGenerator.csproj index b3404fc..e18f056 100644 --- a/CentralConfigGenerator/CentralConfigGenerator.csproj +++ b/CentralConfigGenerator/CentralConfigGenerator.csproj @@ -1,44 +1,44 @@ - - Exe - true - true - true - CentralConfigGenerator - true - central-config - ./nupkg - - - - README.md - + + Exe + true + true + true + CentralConfigGenerator + true + central-config + ./nupkg + + + + README.md + A modern .NET tool for automatically generating centralized configuration files for .NET projects. CentralConfig analyzes your solution structure and creates properly configured `Directory.Build.props` and `Directory.Packages.props` files to standardize settings across your projects. - 1.0.1 - Taras Kovalenko - Copyright Taras Kovalenko - dotnet;msbuild;build;props;packages;centralized;configuration;directory-build-props;sdk;cli;tool;nuget - CentralConfigGenerator - MIT - https://github.com/TarasKovalenko/CentralConfigGenerator - https://github.com/TarasKovalenko/CentralConfigGenerator.git - git - false - - - - - - - - - - - - - - - - + 1.1.0 + Taras Kovalenko + Copyright Taras Kovalenko + dotnet;msbuild;build;props;packages;centralized;configuration;directory-build-props;sdk;cli;tool;nuget + CentralConfigGenerator + MIT + https://github.com/TarasKovalenko/CentralConfigGenerator + https://github.com/TarasKovalenko/CentralConfigGenerator.git + git + false + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs b/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs new file mode 100644 index 0000000..305c69e --- /dev/null +++ b/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs @@ -0,0 +1,125 @@ +using System.Xml.Linq; +using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Generators; +using CentralConfigGenerator.Core.Services; +using CentralConfigGenerator.Services.Abstractions; +using Spectre.Console; + +namespace CentralConfigGenerator.Commands; + +public class EnhancedPackagesPropsCommand( + IEnhancedPackageAnalyzer packageAnalyzer, + IProjectFileService projectFileService, + IPackagesPropsGenerator packagesPropsGenerator, + IFileService fileService, + IVersionConflictVisualizer conflictVisualizer +) +{ + public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite, bool verbose = false) + { + AnsiConsole.Status() + .Start("Scanning for project files...", ctx => + { + ctx.Spinner(Spinner.Known.Star); + ctx.SpinnerStyle(Style.Parse("green")); + }); + + var targetPath = Path.Combine(directory.FullName, "Directory.Packages.props"); + + if (fileService.Exists(targetPath) && !overwrite) + { + AnsiConsole.MarkupLine("[yellow]Warning:[/] File Directory.Packages.props already exists. Use --overwrite to replace it."); + return; + } + + var projectFiles = await projectFileService.ScanDirectoryForProjectsAsync(directory); + + if (projectFiles.Count == 0) + { + AnsiConsole.MarkupLine("[yellow]Warning:[/] No .csproj files found in the directory tree."); + return; + } + + AnsiConsole.MarkupLine($"[green]Found {projectFiles.Count} project files[/]"); + + // Analyze packages with enhanced analyzer + var analysisResult = await AnsiConsole.Status() + .StartAsync("Analyzing package versions...", ctx => + { + ctx.Spinner(Spinner.Known.Star); + ctx.SpinnerStyle(Style.Parse("green")); + return Task.FromResult(packageAnalyzer.AnalyzePackages(projectFiles)); + }); + + // Display analysis results + conflictVisualizer.DisplayResults(analysisResult); + + // Ask for confirmation if conflicts exist + if (analysisResult.Conflicts.Any()) + { + if (!AnsiConsole.Confirm("Version conflicts were detected. Continue with resolved versions?")) + { + AnsiConsole.MarkupLine("[red]Operation cancelled by user.[/]"); + return; + } + } + + // Generate Directory.Packages.props + var packagesPropsContent = packagesPropsGenerator.GeneratePackagesPropsContent(analysisResult.ResolvedVersions); + await fileService.WriteAllTextAsync(targetPath, packagesPropsContent); + + AnsiConsole.MarkupLine($"[green]Created Directory.Packages.props at {targetPath}[/]"); + + // Update project files + var updateConfirmed = AnsiConsole.Confirm("Remove version attributes from project files?"); + if (!updateConfirmed) + { + AnsiConsole.MarkupLine("[yellow]Skipping project file updates. You'll need to manually remove Version attributes.[/]"); + return; + } + + await AnsiConsole.Progress() + .StartAsync(async ctx => + { + var task = ctx.AddTask("[green]Updating project files[/]", maxValue: projectFiles.Count); + + foreach (var projectFile in projectFiles) + { + try + { + var xDoc = XDocument.Parse(projectFile.Content); + var changed = false; + + var packageReferences = xDoc.Descendants("PackageReference").ToList(); + + foreach (var packageRef in packageReferences) + { + var versionAttr = packageRef.Attribute("Version"); + if (versionAttr != null) + { + versionAttr.Remove(); + changed = true; + } + } + + if (changed) + { + await fileService.WriteAllTextAsync(projectFile.Path, xDoc.ToString()); + if (verbose) + { + AnsiConsole.MarkupLine($"[dim]Updated: {projectFile.Path}[/]"); + } + } + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error updating {projectFile.Path}: {ex.Message}[/]"); + } + + task.Increment(1); + } + }); + + AnsiConsole.MarkupLine("[green]Successfully updated all project files![/]"); + } +} diff --git a/CentralConfigGenerator/Program.cs b/CentralConfigGenerator/Program.cs index c224504..7bcbfcf 100644 --- a/CentralConfigGenerator/Program.cs +++ b/CentralConfigGenerator/Program.cs @@ -1,7 +1,8 @@ -using System.CommandLine; +using System.CommandLine; using CentralConfigGenerator.Commands; using CentralConfigGenerator.Core.Analyzers; using CentralConfigGenerator.Core.Generators; +using CentralConfigGenerator.Core.Services; using CentralConfigGenerator.Services; using CentralConfigGenerator.Services.Abstractions; using Microsoft.Extensions.DependencyInjection; @@ -21,6 +22,7 @@ static async Task Main(string[] args) var buildCommand = new Command("build", "Generate Directory.Build.props file"); var packagesCommand = new Command("packages", "Generate Directory.Packages.props file"); + var packagesEnhancedCommand = new Command("packages-enhanced", "Generate Directory.Packages.props file with enhanced version analysis"); var allCommand = new Command("all", "Generate both Directory.Build.props and Directory.Packages.props files"); var directoryOption = new Option( @@ -49,6 +51,10 @@ static async Task Main(string[] args) packagesCommand.AddOption(overwriteOption); packagesCommand.AddOption(verboseOption); + packagesEnhancedCommand.AddOption(directoryOption); + packagesEnhancedCommand.AddOption(overwriteOption); + packagesEnhancedCommand.AddOption(verboseOption); + allCommand.AddOption(directoryOption); allCommand.AddOption(overwriteOption); allCommand.AddOption(verboseOption); @@ -69,6 +75,14 @@ static async Task Main(string[] args) await command.ExecuteAsync(directory, overwrite); }, directoryOption, overwriteOption, verboseOption); + packagesEnhancedCommand.SetHandler(async (directory, overwrite, verbose) => + { + var command = services.GetRequiredService(); + ArgumentNullException.ThrowIfNull(command); + + await command.ExecuteAsync(directory, overwrite, verbose); + }, directoryOption, overwriteOption, verboseOption); + allCommand.SetHandler(async (directory, overwrite, _) => { var buildPropsCommand = services.GetRequiredService(); @@ -83,6 +97,7 @@ static async Task Main(string[] args) rootCommand.AddCommand(buildCommand); rootCommand.AddCommand(packagesCommand); + rootCommand.AddCommand(packagesEnhancedCommand); rootCommand.AddCommand(allCommand); return await rootCommand.InvokeAsync(args); @@ -92,17 +107,27 @@ public static ServiceProvider ConfigureServices() { var services = new ServiceCollection(); + // Original services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + // Enhanced services + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + // Commands services.AddTransient(); services.AddTransient(); + services.AddTransient(); + // Common services services.AddSingleton(); services.AddSingleton(); return services.BuildServiceProvider(); } -} \ No newline at end of file +} diff --git a/CentralConfigGenerator/docs/QUICK_START_ENHANCED.md b/CentralConfigGenerator/docs/QUICK_START_ENHANCED.md new file mode 100644 index 0000000..6a334a9 --- /dev/null +++ b/CentralConfigGenerator/docs/QUICK_START_ENHANCED.md @@ -0,0 +1,170 @@ +# Quick Start Guide: Enhanced Package Analysis + +This guide will help you get started with the enhanced package analysis features of CentralConfigGenerator. + +## Prerequisites + +- .NET SDK 9.0 or later +- CentralConfigGenerator installed globally + +## Basic Usage + +### 1. Analyze Your Solution + +Navigate to your solution directory and run: + +```bash +central-config packages-enhanced -v +``` + +This will: +- Scan all project files +- Detect version conflicts +- Show a visual analysis report +- Ask for confirmation before making changes + +### 2. Understanding the Output + +You'll see a comprehensive report like this: + +``` +Package Analysis Summary +┌─────────────────────────┬─────────┐ +│ Metric │ Value │ +├─────────────────────────┼─────────┤ +│ Total Packages │ 15 │ +│ Packages with Conflicts │ 3 │ +│ Warnings │ 4 │ +└─────────────────────────┴─────────┘ + +Version Conflicts Detected: +┌─────────────────────┬──────────────────┬─────────────┬─────────────┐ +│ Package │ Project │ Version │ Type │ +├─────────────────────┼──────────────────┼─────────────┼─────────────┤ +│ Newtonsoft.Json │ Web.csproj │ 11.0.2 │ Release │ +│ Newtonsoft.Json │ Core.csproj │ 13.0.3 │ Release │ +│ │ │ │ │ +│ Serilog │ Web.csproj │ 2.10.0 │ Release │ +│ Serilog │ Tests.csproj │ 3.0.1 │ Release │ +└─────────────────────┴──────────────────┴─────────────┴─────────────┘ +``` + +### 3. Conflict Resolution + +The tool will automatically resolve conflicts using the highest version strategy. You'll be asked to confirm: + +``` +Version conflicts were detected. Continue with resolved versions? [y/n] (y): +``` + +### 4. Review Changes + +After confirmation, the tool will: +1. Create a `Directory.Packages.props` file +2. Remove version attributes from project files +3. Show a summary of changes + +## Advanced Features + +### Check Specific Directory + +```bash +central-config packages-enhanced -d ./src/MyProjects -v +``` + +### Force Overwrite + +```bash +central-config packages-enhanced --overwrite +``` + +### Dry Run (Coming Soon) + +```bash +central-config packages-enhanced --dry-run +``` + +## Common Scenarios + +### Scenario 1: Mixed Pre-release and Stable Versions + +If you have both pre-release and stable versions: + +```xml + + + + + +``` + +Result: The stable version `1.0.0` will be selected. + +### Scenario 2: Version Ranges + +When projects use version ranges: + +```xml + + + + + +``` + +Result: The range `[1.0.0,2.0.0)` will be preserved as it encompasses the specific version. + +### Scenario 3: Outdated Packages + +The tool will warn about significantly outdated packages: + +``` +Warnings: +┌─────────┬─────────────────────┬─────────────────────────────────────────────┐ +│ Level │ Package │ Message │ +├─────────┼─────────────────────┼─────────────────────────────────────────────┤ +│ Warning │ EntityFramework │ This version is significantly outdated │ +└─────────┴─────────────────────┴─────────────────────────────────────────────┘ +``` + +## Troubleshooting + +### Issue: Variable Versions + +If you have versions like `$(MyVersion)`: + +```xml + +``` + +These will be preserved as-is in the centralized configuration. + +### Issue: Conflicting Pre-release Versions + +When multiple pre-release versions conflict: + +``` +MyPackage 1.0.0-alpha.1 +MyPackage 1.0.0-beta.1 +``` + +The tool correctly identifies that `beta.1` is newer than `alpha.1`. + +## Best Practices + +1. **Review Before Confirming**: Always review the conflict resolution before accepting +2. **Test After Migration**: Run your tests after centralizing package versions +3. **Use Stable Versions**: Prefer stable versions over pre-release in production +4. **Regular Updates**: Run the analysis periodically to keep versions consistent + +## Next Steps + +- Learn about [version management details](VERSION_MANAGEMENT.md) +- Read the [full documentation](README.md) + +## Getting Help + +If you encounter issues: + +1. Run with verbose logging: `central-config packages-enhanced -v` +2. Open an issue on [GitHub](https://github.com/TarasKovalenko/CentralConfigGenerator/issues) diff --git a/CentralConfigGenerator/docs/README.md b/CentralConfigGenerator/docs/README.md index 4d8256f..b38c5ab 100644 --- a/CentralConfigGenerator/docs/README.md +++ b/CentralConfigGenerator/docs/README.md @@ -25,7 +25,9 @@ CentralConfigGenerator helps you maintain consistent configuration across multip 1. Automatically generating `Directory.Build.props` files with common project properties 2. Automatically generating `Directory.Packages.props` files with centralized package versions -3. Updating your project files to use these centralized configurations +3. Providing advanced version conflict resolution using NuGet.Versioning +4. Offering compatibility checking and visual analysis of version conflicts +5. Updating your project files to use these centralized configurations ## Installation @@ -37,7 +39,7 @@ dotnet tool install --global CentralConfigGenerator ### Basic Commands -CentralConfigGenerator provides three main commands: +CentralConfigGenerator provides four main commands: ```bash # Generate Directory.Build.props file with common project properties @@ -46,6 +48,9 @@ central-config build [options] # Generate Directory.Packages.props file for centralized package versions central-config packages [options] +# Generate Directory.Packages.props with enhanced version analysis +central-config packages-enhanced [options] + # Generate both files in one command central-config all [options] ``` @@ -80,14 +85,18 @@ central-config packages central-config packages -d C:\Projects\MySolution -v ``` -#### Generate Both Files +#### Enhanced Package Analysis ```bash -# Generate both files in one command -central-config all - -# Generate both files with all options -central-config all -d C:\Projects\MySolution -o -v +# Use enhanced package analysis with visual conflict resolution +central-config packages-enhanced -d C:\Projects\MySolution -v + +# This will: +# - Detect version conflicts across projects +# - Show visual analysis of conflicts +# - Suggest resolutions based on semantic versioning +# - Check for compatibility issues +# - Ask for confirmation before proceeding ``` ## How It Works @@ -116,6 +125,22 @@ The `packages` command: 4. Generates a `Directory.Packages.props` file with these package versions 5. Removes version attributes from `PackageReference` elements in project files +### Enhanced Package Analysis + +The `packages-enhanced` command provides advanced features: + +1. **Semantic Version Analysis**: Uses NuGet.Versioning for accurate version comparisons +2. **Conflict Detection**: Identifies and visualizes version conflicts across projects +3. **Version Range Support**: Handles version ranges (e.g., `[1.0.0,2.0.0)`) +4. **Pre-release Detection**: Warns about pre-release packages in production code +5. **Compatibility Checking**: Identifies known issues with specific package versions +6. **Visual Reports**: Provides clear, color-coded reports of analysis results +7. **Multiple Resolution Strategies**: Offers different approaches to resolve conflicts: + - Highest version (default) + - Lowest version + - Most common version + - Manual resolution + ### Understanding the Generated Files #### Directory.Build.props @@ -146,12 +171,64 @@ The `packages` command: ``` +## Enhanced Features + +### Version Conflict Resolution + +When multiple projects reference the same package with different versions, the enhanced analyzer: + +1. Detects all version conflicts +2. Shows a detailed conflict report +3. Applies resolution strategy (highest version by default) +4. Asks for confirmation before proceeding + +Example output: +``` +Package Analysis Summary +┌─────────────────────────┬─────────┐ +│ Metric │ Value │ +├─────────────────────────┼─────────┤ +│ Total Packages │ 12 │ +│ Packages with Conflicts │ 3 │ +│ Warnings │ 5 │ +└─────────────────────────┴─────────┘ + +Version Conflicts Detected: +┌─────────────────────┬──────────────────┬─────────────┬─────────────┐ +│ Package │ Project │ Version │ Type │ +├─────────────────────┼──────────────────┼─────────────┼─────────────┤ +│ Newtonsoft.Json │ Project1.csproj │ 11.0.2 │ Release │ +│ Newtonsoft.Json │ Project2.csproj │ 13.0.3 │ Release │ +└─────────────────────┴──────────────────┴─────────────┴─────────────┘ +``` + +### Semantic Versioning Support + +The enhanced analyzer properly handles: + +- Pre-release versions (e.g., `1.0.0-beta.1`) +- Build metadata (e.g., `1.0.0+build.123`) +- Version ranges (e.g., `[1.0.0,2.0.0)`) +- Floating versions (e.g., `1.0.*`) + +### Compatibility Warnings + +The tool can warn about: + +- Known security vulnerabilities in specific versions +- Performance issues in certain package versions +- Significantly outdated packages +- Pre-release packages in production code + ## Benefits - **Consistent Configuration**: Ensure all projects use the same framework versions, language features, and code quality settings - **Simplified Updates**: Update package versions or project settings in a single location - **Reduced Duplication**: Remove redundant configuration from individual project files - **Improved Maintainability**: Make your solution more maintainable by centralizing common settings +- **Version Conflict Resolution**: Automatically detect and resolve package version conflicts +- **Better Version Management**: Use semantic versioning for accurate version comparisons +- **Visual Analysis**: See clear, color-coded reports of package analysis results ## Common Scenarios @@ -167,11 +244,27 @@ cd MySolution central-config all -v ``` +### Resolving Version Conflicts + +When you have multiple projects with conflicting package versions: + +```bash +# Use enhanced package analysis to detect and resolve conflicts +central-config packages-enhanced -v + +# The tool will: +# 1. Show all version conflicts +# 2. Propose resolutions +# 3. Ask for confirmation +# 4. Update all project files +``` + ## Limitations - Projects with highly customized or conflicting settings may require manual adjustment after running CentralConfigGenerator - For properties to be included in Directory.Build.props, they must appear with identical values in most projects -- Version conflicts in packages will be resolved by selecting the highest version found +- Version conflicts in packages will be resolved by selecting the highest version found (configurable in enhanced mode) +- Some version formats (like variables) cannot be automatically resolved ## Local installation diff --git a/CentralConfigGenerator/docs/VERSION_MANAGEMENT.md b/CentralConfigGenerator/docs/VERSION_MANAGEMENT.md new file mode 100644 index 0000000..3f440df --- /dev/null +++ b/CentralConfigGenerator/docs/VERSION_MANAGEMENT.md @@ -0,0 +1,236 @@ +# Advanced Version Management with NuGet.Versioning + +This document explains how CentralConfigGenerator uses NuGet.Versioning to provide advanced version management capabilities. + +## Overview + +CentralConfigGenerator uses the `NuGet.Versioning` package to provide sophisticated version handling that goes beyond basic .NET version comparison. This integration enables proper semantic versioning support, version range handling, and accurate package version conflict resolution. + +## Key Features + +### 1. Semantic Versioning Support + +The tool properly handles semantic versioning (SemVer) including: + +- Major.Minor.Patch versions (e.g., `1.2.3`) +- Pre-release tags (e.g., `1.0.0-beta.1`, `2.0.0-rc.2`) +- Build metadata (e.g., `1.0.0+build.123`) + +#### Example: +```xml + + + + +``` + +### 2. Version Range Handling + +Supports NuGet's version range syntax: + +- Exact version: `1.0.0` +- Minimum version: `1.0.0` +- Exact range: `[1.0.0]` +- Minimum inclusive: `[1.0.0,)` +- Maximum inclusive: `(,1.0.0]` +- Range: `[1.0.0,2.0.0)` +- Floating version: `1.0.*` + +#### Example: +```xml + + + + +``` + +### 3. Conflict Resolution Strategies + +The enhanced package analyzer provides multiple strategies for resolving version conflicts: + +1. **Highest Version (Default)**: Selects the highest version among all referenced versions +2. **Lowest Version**: Selects the lowest version (useful for maximum compatibility) +3. **Most Common**: Selects the version used by most projects +4. **Manual**: Prompts for manual intervention when automatic resolution isn't suitable + +#### Example: +```csharp +// Using the version conflict resolver +var resolver = new VersionConflictResolver(); +var resolvedVersion = resolver.Resolve( + "Newtonsoft.Json", + new[] { "11.0.2", "12.0.3", "13.0.1" }, + VersionResolutionStrategy.Highest +); +// Result: "13.0.1" +``` + +### 4. Compatibility Checking + +The tool can check for known issues with specific package versions: + +- Security vulnerabilities +- Performance regressions +- Breaking changes +- Deprecated versions + +#### Example output: +``` +Warnings: +┌─────────┬─────────────────────┬─────────────────────────────────────────────┐ +│ Level │ Package │ Message │ +├─────────┼─────────────────────┼─────────────────────────────────────────────┤ +│ Warning │ Newtonsoft.Json │ Known security vulnerability in version 9.x │ +│ Info │ System.Text.Json │ Pre-release version detected │ +│ Warning │ EntityFramework │ This version is significantly outdated │ +└─────────┴─────────────────────┴─────────────────────────────────────────────┘ +``` + +## Implementation Details + +### Version Parsing + +The tool uses `NuGetVersion.TryParse()` instead of the basic `Version.TryParse()`: + +```csharp +// Old approach (limited) +if (Version.TryParse(versionStr, out var version)) +{ + // Only handles numeric versions like "1.0.0" +} + +// New approach (comprehensive) +if (NuGetVersion.TryParse(versionStr, out var nugetVersion)) +{ + // Handles "1.0.0-beta.1+build.123" and more +} +``` + +### Version Comparison + +NuGet.Versioning provides accurate version comparison following SemVer rules: + +```csharp +var v1 = NuGetVersion.Parse("1.0.0-alpha"); +var v2 = NuGetVersion.Parse("1.0.0-beta"); +var v3 = NuGetVersion.Parse("1.0.0"); + +// Correct ordering: alpha < beta < release +Console.WriteLine(v1 < v2); // True +Console.WriteLine(v2 < v3); // True +``` + +### Handling Special Cases + +The tool handles various special version formats: + +1. **Variables**: `$(VersionPrefix)$(VersionSuffix)` +2. **Properties**: `$(MyPackageVersion)` +3. **Wildcards**: `1.0.*` +4. **Floating**: `1.*` + +These are preserved in the output when they cannot be resolved to specific versions. + +## Visual Reporting + +The enhanced package analyzer provides detailed visual reports using Spectre.Console: + +``` +Package Analysis Summary +┌─────────────────────────┬─────────┐ +│ Metric │ Value │ +├─────────────────────────┼─────────┤ +│ Total Packages │ 25 │ +│ Packages with Conflicts │ 5 │ +│ Warnings │ 8 │ +└─────────────────────────┴─────────┘ + +Resolved Package Versions: +┌─────────────────────────────┬─────────────────┐ +│ Package │ Version │ +├─────────────────────────────┼─────────────────┤ +│ Microsoft.Extensions.Logging│ 8.0.0 │ +│ Newtonsoft.Json │ 13.0.3 │ +│ NuGet.Versioning │ 6.7.0 │ +└─────────────────────────────┴─────────────────┘ +``` + +## Best Practices + +1. **Use Semantic Versioning**: Follow SemVer conventions for your package versions +2. **Avoid Floating Versions**: Use specific versions in production code +3. **Review Conflicts**: Always review detected conflicts before accepting resolutions +4. **Check Compatibility**: Pay attention to compatibility warnings +5. **Test After Migration**: Test your solution after centralizing package versions + +## Troubleshooting + +### Common Issues + +1. **Unparseable Versions**: Some version formats (like variables) cannot be parsed + - Solution: These are preserved as-is in the output + +2. **Complex Version Ranges**: Overlapping ranges might cause unexpected resolutions + - Solution: Review the resolution and adjust manually if needed + +3. **Pre-release Dependencies**: Pre-release packages in production code + - Solution: The tool warns about these; consider using stable versions + +### Debug Information + +Run with verbose logging to see detailed version analysis: + +```bash +central-config packages-enhanced -v +``` + +This will show: +- Each version comparison +- Resolution decisions +- Parsing failures +- Compatibility check results + +## API Reference + +### Key Classes + +1. `EnhancedPackageAnalyzer`: Main analyzer with conflict detection +2. `VersionConflictResolver`: Resolves version conflicts using various strategies +3. `VersionCompatibilityChecker`: Checks for known version issues +4. `VersionConflictVisualizer`: Creates visual reports of analysis results + +### Extension Points + +The system is designed to be extensible: + +```csharp +// Custom resolution strategy +public class CustomVersionResolver : IVersionConflictResolver +{ + public string Resolve(string packageName, IEnumerable versions, + VersionResolutionStrategy strategy) + { + // Custom resolution logic + } +} + +// Custom compatibility checker +public class CustomCompatibilityChecker : IVersionCompatibilityChecker +{ + public Task CheckCompatibilityAsync( + string packageId, string version) + { + // Custom compatibility checks + } +} +``` + +## Future Enhancements + +Planned improvements include: + +1. NuGet API integration for real-time version information +2. Custom resolution rules configuration +3. Integration with vulnerability databases +4. Support for dependency graph analysis +5. Automated upgrade path suggestions diff --git a/Directory.Packages.props b/Directory.Packages.props index 8a1c14e..37182f2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,6 +7,8 @@ + + diff --git a/QUICK_START_ENHANCED.md b/QUICK_START_ENHANCED.md new file mode 100644 index 0000000..a5a1764 --- /dev/null +++ b/QUICK_START_ENHANCED.md @@ -0,0 +1,170 @@ +# Quick Start Guide: Enhanced Package Analysis + +This guide will help you get started with the enhanced package analysis features of CentralConfigGenerator. + +## Prerequisites + +- .NET SDK 9.0 or later +- CentralConfigGenerator installed globally + +## Basic Usage + +### 1. Analyze Your Solution + +Navigate to your solution directory and run: + +```bash +central-config packages-enhanced -v +``` + +This will: +- Scan all project files +- Detect version conflicts +- Show a visual analysis report +- Ask for confirmation before making changes + +### 2. Understanding the Output + +You'll see a comprehensive report like this: + +``` +Package Analysis Summary +┌─────────────────────────┬─────────┐ +│ Metric │ Value │ +├─────────────────────────┼─────────┤ +│ Total Packages │ 15 │ +│ Packages with Conflicts │ 3 │ +│ Warnings │ 4 │ +└─────────────────────────┴─────────┘ + +Version Conflicts Detected: +┌─────────────────────┬──────────────────┬─────────────┬─────────────┐ +│ Package │ Project │ Version │ Type │ +├─────────────────────┼──────────────────┼─────────────┼─────────────┤ +│ Newtonsoft.Json │ Web.csproj │ 11.0.2 │ Release │ +│ Newtonsoft.Json │ Core.csproj │ 13.0.3 │ Release │ +│ │ │ │ │ +│ Serilog │ Web.csproj │ 2.10.0 │ Release │ +│ Serilog │ Tests.csproj │ 3.0.1 │ Release │ +└─────────────────────┴──────────────────┴─────────────┴─────────────┘ +``` + +### 3. Conflict Resolution + +The tool will automatically resolve conflicts using the highest version strategy. You'll be asked to confirm: + +``` +Version conflicts were detected. Continue with resolved versions? [y/n] (y): +``` + +### 4. Review Changes + +After confirmation, the tool will: +1. Create a `Directory.Packages.props` file +2. Remove version attributes from project files +3. Show a summary of changes + +## Advanced Features + +### Check Specific Directory + +```bash +central-config packages-enhanced -d ./src/MyProjects -v +``` + +### Force Overwrite + +```bash +central-config packages-enhanced --overwrite +``` + +### Dry Run (Coming Soon) + +```bash +central-config packages-enhanced --dry-run +``` + +## Common Scenarios + +### Scenario 1: Mixed Pre-release and Stable Versions + +If you have both pre-release and stable versions: + +```xml + + + + + +``` + +Result: The stable version `1.0.0` will be selected. + +### Scenario 2: Version Ranges + +When projects use version ranges: + +```xml + + + + + +``` + +Result: The range `[1.0.0,2.0.0)` will be preserved as it encompasses the specific version. + +### Scenario 3: Outdated Packages + +The tool will warn about significantly outdated packages: + +``` +Warnings: +┌─────────┬─────────────────────┬─────────────────────────────────────────────┐ +│ Level │ Package │ Message │ +├─────────┼─────────────────────┼─────────────────────────────────────────────┤ +│ Warning │ EntityFramework │ This version is significantly outdated │ +└─────────┴─────────────────────┴─────────────────────────────────────────────┘ +``` + +## Troubleshooting + +### Issue: Variable Versions + +If you have versions like `$(MyVersion)`: + +```xml + +``` + +These will be preserved as-is in the centralized configuration. + +### Issue: Conflicting Pre-release Versions + +When multiple pre-release versions conflict: + +``` +MyPackage 1.0.0-alpha.1 +MyPackage 1.0.0-beta.1 +``` + +The tool correctly identifies that `beta.1` is newer than `alpha.1`. + +## Best Practices + +1. **Review Before Confirming**: Always review the conflict resolution before accepting +2. **Test After Migration**: Run your tests after centralizing package versions +3. **Use Stable Versions**: Prefer stable versions over pre-release in production +4. **Regular Updates**: Run the analysis periodically to keep versions consistent + +## Next Steps + +- Learn about [version management details](VERSION_MANAGEMENT.md) +- Read the [full documentation](README.md) + +## Getting Help + +If you encounter issues: + +1. Run with verbose logging: `central-config packages-enhanced -v` +2. Open an issue on [GitHub](https://github.com/TarasKovalenko/CentralConfigGenerator/issues) diff --git a/README.md b/README.md index 4d8256f..d749389 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,9 @@ CentralConfigGenerator helps you maintain consistent configuration across multip 1. Automatically generating `Directory.Build.props` files with common project properties 2. Automatically generating `Directory.Packages.props` files with centralized package versions -3. Updating your project files to use these centralized configurations +3. Providing advanced version conflict resolution using NuGet.Versioning +4. Offering compatibility checking and visual analysis of version conflicts +5. Updating your project files to use these centralized configurations ## Installation @@ -37,7 +39,7 @@ dotnet tool install --global CentralConfigGenerator ### Basic Commands -CentralConfigGenerator provides three main commands: +CentralConfigGenerator provides four main commands: ```bash # Generate Directory.Build.props file with common project properties @@ -46,6 +48,9 @@ central-config build [options] # Generate Directory.Packages.props file for centralized package versions central-config packages [options] +# Generate Directory.Packages.props with enhanced version analysis +central-config packages-enhanced [options] + # Generate both files in one command central-config all [options] ``` @@ -80,14 +85,18 @@ central-config packages central-config packages -d C:\Projects\MySolution -v ``` -#### Generate Both Files +#### Enhanced Package Analysis ```bash -# Generate both files in one command -central-config all - -# Generate both files with all options -central-config all -d C:\Projects\MySolution -o -v +# Use enhanced package analysis with visual conflict resolution +central-config packages-enhanced -d C:\Projects\MySolution -v + +# This will: +# - Detect version conflicts across projects +# - Show visual analysis of conflicts +# - Suggest resolutions based on semantic versioning +# - Check for compatibility issues +# - Ask for confirmation before proceeding ``` ## How It Works @@ -116,6 +125,22 @@ The `packages` command: 4. Generates a `Directory.Packages.props` file with these package versions 5. Removes version attributes from `PackageReference` elements in project files +### Enhanced Package Analysis + +The `packages-enhanced` command provides advanced features: + +1. **Semantic Version Analysis**: Uses NuGet.Versioning for accurate version comparisons +2. **Conflict Detection**: Identifies and visualizes version conflicts across projects +3. **Version Range Support**: Handles version ranges (e.g., `[1.0.0,2.0.0)`) +4. **Pre-release Detection**: Warns about pre-release packages in production code +5. **Compatibility Checking**: Identifies known issues with specific package versions +6. **Visual Reports**: Provides clear, color-coded reports of analysis results +7. **Multiple Resolution Strategies**: Offers different approaches to resolve conflicts: + - Highest version (default) + - Lowest version + - Most common version + - Manual resolution + ### Understanding the Generated Files #### Directory.Build.props @@ -146,12 +171,64 @@ The `packages` command: ``` +## Enhanced Features + +### Version Conflict Resolution + +When multiple projects reference the same package with different versions, the enhanced analyzer: + +1. Detects all version conflicts +2. Shows a detailed conflict report +3. Applies resolution strategy (highest version by default) +4. Asks for confirmation before proceeding + +Example output: +``` +Package Analysis Summary +┌─────────────────────────┬─────────┐ +│ Metric │ Value │ +├─────────────────────────┼─────────┤ +│ Total Packages │ 12 │ +│ Packages with Conflicts │ 3 │ +│ Warnings │ 5 │ +└─────────────────────────┴─────────┘ + +Version Conflicts Detected: +┌─────────────────────┬──────────────────┬─────────────┬─────────────┐ +│ Package │ Project │ Version │ Type │ +├─────────────────────┼──────────────────┼─────────────┼─────────────┤ +│ Newtonsoft.Json │ Project1.csproj │ 11.0.2 │ Release │ +│ Newtonsoft.Json │ Project2.csproj │ 13.0.3 │ Release │ +└─────────────────────┴──────────────────┴─────────────┴─────────────┘ +``` + +### Semantic Versioning Support + +The enhanced analyzer properly handles: + +- Pre-release versions (e.g., `1.0.0-beta.1`) +- Build metadata (e.g., `1.0.0+build.123`) +- Version ranges (e.g., `[1.0.0,2.0.0)`) +- Floating versions (e.g., `1.0.*`) + +### Compatibility Warnings + +The tool can warn about: + +- Known security vulnerabilities in specific versions +- Performance issues in certain package versions +- Significantly outdated packages +- Pre-release packages in production code + ## Benefits - **Consistent Configuration**: Ensure all projects use the same framework versions, language features, and code quality settings - **Simplified Updates**: Update package versions or project settings in a single location - **Reduced Duplication**: Remove redundant configuration from individual project files - **Improved Maintainability**: Make your solution more maintainable by centralizing common settings +- **Version Conflict Resolution**: Automatically detect and resolve package version conflicts +- **Better Version Management**: Use semantic versioning for accurate version comparisons +- **Visual Analysis**: See clear, color-coded reports of package analysis results ## Common Scenarios @@ -167,11 +244,27 @@ cd MySolution central-config all -v ``` +### Resolving Version Conflicts + +When you have multiple projects with conflicting package versions: + +```bash +# Use enhanced package analysis to detect and resolve conflicts +central-config packages-enhanced -v + +# The tool will: +# 1. Show all version conflicts +# 2. Propose resolutions +# 3. Ask for confirmation +# 4. Update all project files +``` + ## Limitations - Projects with highly customized or conflicting settings may require manual adjustment after running CentralConfigGenerator - For properties to be included in Directory.Build.props, they must appear with identical values in most projects -- Version conflicts in packages will be resolved by selecting the highest version found +- Version conflicts in packages will be resolved by selecting the highest version found (configurable in enhanced mode) +- Some version formats (like variables) cannot be automatically resolved ## Local installation diff --git a/VERSION_MANAGEMENT.md b/VERSION_MANAGEMENT.md new file mode 100644 index 0000000..8423200 --- /dev/null +++ b/VERSION_MANAGEMENT.md @@ -0,0 +1,236 @@ +# Advanced Version Management with NuGet.Versioning + +This document explains how CentralConfigGenerator uses NuGet.Versioning to provide advanced version management capabilities. + +## Overview + +CentralConfigGenerator uses the `NuGet.Versioning` package to provide sophisticated version handling that goes beyond basic .NET version comparison. This integration enables proper semantic versioning support, version range handling, and accurate package version conflict resolution. + +## Key Features + +### 1. Semantic Versioning Support + +The tool properly handles semantic versioning (SemVer) including: + +- Major.Minor.Patch versions (e.g., `1.2.3`) +- Pre-release tags (e.g., `1.0.0-beta.1`, `2.0.0-rc.2`) +- Build metadata (e.g., `1.0.0+build.123`) + +#### Example: +```xml + + + + +``` + +### 2. Version Range Handling + +Supports NuGet's version range syntax: + +- Exact version: `1.0.0` +- Minimum version: `1.0.0` +- Exact range: `[1.0.0]` +- Minimum inclusive: `[1.0.0,)` +- Maximum inclusive: `(,1.0.0]` +- Range: `[1.0.0,2.0.0)` +- Floating version: `1.0.*` + +#### Example: +```xml + + + + +``` + +### 3. Conflict Resolution Strategies + +The enhanced package analyzer provides multiple strategies for resolving version conflicts: + +1. **Highest Version (Default)**: Selects the highest version among all referenced versions +2. **Lowest Version**: Selects the lowest version (useful for maximum compatibility) +3. **Most Common**: Selects the version used by most projects +4. **Manual**: Prompts for manual intervention when automatic resolution isn't suitable + +#### Example: +```csharp +// Using the version conflict resolver +var resolver = new VersionConflictResolver(); +var resolvedVersion = resolver.Resolve( + "Newtonsoft.Json", + new[] { "11.0.2", "12.0.3", "13.0.1" }, + VersionResolutionStrategy.Highest +); +// Result: "13.0.1" +``` + +### 4. Compatibility Checking + +The tool can check for known issues with specific package versions: + +- Security vulnerabilities +- Performance regressions +- Breaking changes +- Deprecated versions + +#### Example output: +``` +Warnings: +┌─────────┬─────────────────────┬─────────────────────────────────────────────┐ +│ Level │ Package │ Message │ +├─────────┼─────────────────────┼─────────────────────────────────────────────┤ +│ Warning │ Newtonsoft.Json │ Known security vulnerability in version 9.x │ +│ Info │ System.Text.Json │ Pre-release version detected │ +│ Warning │ EntityFramework │ This version is significantly outdated │ +└─────────┴─────────────────────┴─────────────────────────────────────────────┘ +``` + +## Implementation Details + +### Version Parsing + +The tool uses `NuGetVersion.TryParse()` instead of the basic `Version.TryParse()`: + +```csharp +// Old approach (limited) +if (Version.TryParse(versionStr, out var version)) +{ + // Only handles numeric versions like "1.0.0" +} + +// New approach (comprehensive) +if (NuGetVersion.TryParse(versionStr, out var nugetVersion)) +{ + // Handles "1.0.0-beta.1+build.123" and more +} +``` + +### Version Comparison + +NuGet.Versioning provides accurate version comparison following SemVer rules: + +```csharp +var v1 = NuGetVersion.Parse("1.0.0-alpha"); +var v2 = NuGetVersion.Parse("1.0.0-beta"); +var v3 = NuGetVersion.Parse("1.0.0"); + +// Correct ordering: alpha < beta < release +Console.WriteLine(v1 < v2); // True +Console.WriteLine(v2 < v3); // True +``` + +### Handling Special Cases + +The tool handles various special version formats: + +1. **Variables**: `$(VersionPrefix)$(VersionSuffix)` +2. **Properties**: `$(MyPackageVersion)` +3. **Wildcards**: `1.0.*` +4. **Floating**: `1.*` + +These are preserved in the output when they cannot be resolved to specific versions. + +## Visual Reporting + +The enhanced package analyzer provides detailed visual reports using Spectre.Console: + +``` +Package Analysis Summary +┌─────────────────────────┬─────────┐ +│ Metric │ Value │ +├─────────────────────────┼─────────┤ +│ Total Packages │ 25 │ +│ Packages with Conflicts │ 5 │ +│ Warnings │ 8 │ +└─────────────────────────┴─────────┘ + +Resolved Package Versions: +┌─────────────────────────────┬─────────────────┐ +│ Package │ Version │ +├─────────────────────────────┼─────────────────┤ +│ Microsoft.Extensions.Logging│ 8.0.0 │ +│ Newtonsoft.Json │ 13.0.3 │ +│ NuGet.Versioning │ 6.7.0 │ +└─────────────────────────────┴─────────────────┘ +``` + +## Best Practices + +1. **Use Semantic Versioning**: Follow SemVer conventions for your package versions +2. **Avoid Floating Versions**: Use specific versions in production code +3. **Review Conflicts**: Always review detected conflicts before accepting resolutions +4. **Check Compatibility**: Pay attention to compatibility warnings +5. **Test After Migration**: Test your solution after centralizing package versions + +## Troubleshooting + +### Common Issues + +1. **Unparseable Versions**: Some version formats (like variables) cannot be parsed + - Solution: These are preserved as-is in the output + +2. **Complex Version Ranges**: Overlapping ranges might cause unexpected resolutions + - Solution: Review the resolution and adjust manually if needed + +3. **Pre-release Dependencies**: Pre-release packages in production code + - Solution: The tool warns about these; consider using stable versions + +### Debug Information + +Run with verbose logging to see detailed version analysis: + +```bash +central-config packages-enhanced -v +``` + +This will show: +- Each version comparison +- Resolution decisions +- Parsing failures +- Compatibility check results + +## API Reference + +### Key Classes + +1. `EnhancedPackageAnalyzer`: Main analyzer with conflict detection +2. `VersionConflictResolver`: Resolves version conflicts using various strategies +3. `VersionCompatibilityChecker`: Checks for known version issues +4. `VersionConflictVisualizer`: Creates visual reports of analysis results + +### Extension Points + +The system is designed to be extensible: + +```csharp +// Custom resolution strategy +public class CustomVersionResolver : IVersionConflictResolver +{ + public string Resolve(string packageName, IEnumerable versions, + VersionResolutionStrategy strategy) + { + // Custom resolution logic + } +} + +// Custom compatibility checker +public class CustomCompatibilityChecker : IVersionCompatibilityChecker +{ + public Task CheckCompatibilityAsync( + string packageId, string version) + { + // Custom compatibility checks + } +} +``` + +## Future Enhancements + +Planned improvements include: + +1. NuGet API integration for real-time version information +2. Custom resolution rules configuration +3. Integration with vulnerability databases +4. Support for dependency graph analysis +5. Automated upgrade path suggestions From ebee46be64da98450c73e6f347787c6ad45f7c4d Mon Sep 17 00:00:00 2001 From: Taras Kovalenko Date: Sun, 4 May 2025 15:42:33 +0300 Subject: [PATCH 2/4] feat: introduce abstractions for analyzers and generators, enhance version compatibility checking --- .../Analyzers/PackageAnalyzerTests.cs | 1 + .../Analyzers/ProjectAnalyzerTests.cs | 1 + .../Generators/BuildPropsGeneratorTests.cs | 1 + .../Generators/PackagesPropsGeneratorTests.cs | 1 + .../Abstractions/IEnhancedPackageAnalyzer.cs | 8 + .../{ => Abstractions}/IPackageAnalyzer.cs | 2 +- .../{ => Abstractions}/IProjectAnalyzer.cs | 2 +- .../Analyzers/EnhancedPackageAnalyzer.cs | 11 +- .../Analyzers/PackageAnalyzer.cs | 1 + .../Analyzers/ProjectAnalyzer.cs | 1 + .../IBuildPropsGenerator.cs | 2 +- .../IPackagesPropsGenerator.cs | 2 +- .../Generators/BuildPropsGenerator.cs | 1 + .../Generators/PackagesPropsGenerator.cs | 1 + .../Models/CompatibilityCheckResult.cs | 8 + .../Models/ParsedVersion.cs | 10 + .../IVersionCompatibilityChecker.cs | 8 + .../Abstractions/IVersionConflictResolver.cs | 6 + .../IVersionConflictVisualizer.cs | 8 + .../Services/VersionCompatibilityChecker.cs | 23 +- .../Services/VersionConflictResolver.cs | 14 +- .../Services/VersionConflictVisualizer.cs | 22 +- .../Commands/BuildPropsCommandTests.cs | 2 + .../Commands/PackagesPropsCommandTests.cs | 2 + .../CentralConfigGenerator.csproj | 7 +- .../Commands/BuildPropsCommand.cs | 2 + .../Commands/EnhancedPackagesPropsCommand.cs | 3 + .../Commands/PackagesPropsCommand.cs | 2 + CentralConfigGenerator/Program.cs | 3 + .../Services/Abstractions/IFileService.cs | 2 + .../docs/QUICK_START_ENHANCED.md | 170 ----------- CentralConfigGenerator/docs/README.md | 283 ------------------ .../docs/VERSION_MANAGEMENT.md | 236 --------------- Directory.Packages.props | 5 +- 34 files changed, 107 insertions(+), 744 deletions(-) create mode 100644 CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs rename CentralConfigGenerator.Core/Analyzers/{ => Abstractions}/IPackageAnalyzer.cs (74%) rename CentralConfigGenerator.Core/Analyzers/{ => Abstractions}/IProjectAnalyzer.cs (75%) rename CentralConfigGenerator.Core/Generators/{ => Abstractions}/IBuildPropsGenerator.cs (65%) rename CentralConfigGenerator.Core/Generators/{ => Abstractions}/IPackagesPropsGenerator.cs (66%) create mode 100644 CentralConfigGenerator.Core/Models/CompatibilityCheckResult.cs create mode 100644 CentralConfigGenerator.Core/Models/ParsedVersion.cs create mode 100644 CentralConfigGenerator.Core/Services/Abstractions/IVersionCompatibilityChecker.cs create mode 100644 CentralConfigGenerator.Core/Services/Abstractions/IVersionConflictResolver.cs create mode 100644 CentralConfigGenerator.Core/Services/Abstractions/IVersionConflictVisualizer.cs delete mode 100644 CentralConfigGenerator/docs/QUICK_START_ENHANCED.md delete mode 100644 CentralConfigGenerator/docs/README.md delete mode 100644 CentralConfigGenerator/docs/VERSION_MANAGEMENT.md diff --git a/CentralConfigGenerator.Core.Tests/Analyzers/PackageAnalyzerTests.cs b/CentralConfigGenerator.Core.Tests/Analyzers/PackageAnalyzerTests.cs index 01cdeaa..aab343a 100644 --- a/CentralConfigGenerator.Core.Tests/Analyzers/PackageAnalyzerTests.cs +++ b/CentralConfigGenerator.Core.Tests/Analyzers/PackageAnalyzerTests.cs @@ -1,4 +1,5 @@ using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Models; namespace CentralConfigGenerator.Core.Tests.Analyzers; diff --git a/CentralConfigGenerator.Core.Tests/Analyzers/ProjectAnalyzerTests.cs b/CentralConfigGenerator.Core.Tests/Analyzers/ProjectAnalyzerTests.cs index 01fae76..17fd208 100644 --- a/CentralConfigGenerator.Core.Tests/Analyzers/ProjectAnalyzerTests.cs +++ b/CentralConfigGenerator.Core.Tests/Analyzers/ProjectAnalyzerTests.cs @@ -1,4 +1,5 @@ using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Models; namespace CentralConfigGenerator.Core.Tests.Analyzers; diff --git a/CentralConfigGenerator.Core.Tests/Generators/BuildPropsGeneratorTests.cs b/CentralConfigGenerator.Core.Tests/Generators/BuildPropsGeneratorTests.cs index ea1dad2..55e1714 100644 --- a/CentralConfigGenerator.Core.Tests/Generators/BuildPropsGeneratorTests.cs +++ b/CentralConfigGenerator.Core.Tests/Generators/BuildPropsGeneratorTests.cs @@ -1,5 +1,6 @@ using CentralConfigGenerator.Core.Generators; using System.Xml.Linq; +using CentralConfigGenerator.Core.Generators.Abstractions; namespace CentralConfigGenerator.Core.Tests.Generators; diff --git a/CentralConfigGenerator.Core.Tests/Generators/PackagesPropsGeneratorTests.cs b/CentralConfigGenerator.Core.Tests/Generators/PackagesPropsGeneratorTests.cs index eba4800..ba55132 100644 --- a/CentralConfigGenerator.Core.Tests/Generators/PackagesPropsGeneratorTests.cs +++ b/CentralConfigGenerator.Core.Tests/Generators/PackagesPropsGeneratorTests.cs @@ -1,5 +1,6 @@ using CentralConfigGenerator.Core.Generators; using System.Xml.Linq; +using CentralConfigGenerator.Core.Generators.Abstractions; namespace CentralConfigGenerator.Core.Tests.Generators; diff --git a/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs new file mode 100644 index 0000000..ff5c749 --- /dev/null +++ b/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs @@ -0,0 +1,8 @@ +using CentralConfigGenerator.Core.Models; + +namespace CentralConfigGenerator.Core.Analyzers.Abstractions; + +public interface IEnhancedPackageAnalyzer +{ + PackageAnalysisResult AnalyzePackages(IEnumerable projectFiles); +} \ No newline at end of file diff --git a/CentralConfigGenerator.Core/Analyzers/IPackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/Abstractions/IPackageAnalyzer.cs similarity index 74% rename from CentralConfigGenerator.Core/Analyzers/IPackageAnalyzer.cs rename to CentralConfigGenerator.Core/Analyzers/Abstractions/IPackageAnalyzer.cs index 0e90c97..56907c6 100644 --- a/CentralConfigGenerator.Core/Analyzers/IPackageAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/Abstractions/IPackageAnalyzer.cs @@ -1,6 +1,6 @@ using CentralConfigGenerator.Core.Models; -namespace CentralConfigGenerator.Core.Analyzers; +namespace CentralConfigGenerator.Core.Analyzers.Abstractions; public interface IPackageAnalyzer { diff --git a/CentralConfigGenerator.Core/Analyzers/IProjectAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/Abstractions/IProjectAnalyzer.cs similarity index 75% rename from CentralConfigGenerator.Core/Analyzers/IProjectAnalyzer.cs rename to CentralConfigGenerator.Core/Analyzers/Abstractions/IProjectAnalyzer.cs index ebb9094..165346f 100644 --- a/CentralConfigGenerator.Core/Analyzers/IProjectAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/Abstractions/IProjectAnalyzer.cs @@ -1,6 +1,6 @@ using CentralConfigGenerator.Core.Models; -namespace CentralConfigGenerator.Core.Analyzers; +namespace CentralConfigGenerator.Core.Analyzers.Abstractions; public interface IProjectAnalyzer { diff --git a/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs index 3fd65c9..bc9234d 100644 --- a/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs @@ -1,15 +1,12 @@ using System.Xml.Linq; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Models; using CentralConfigGenerator.Core.Services; +using CentralConfigGenerator.Core.Services.Abstractions; using NuGet.Versioning; namespace CentralConfigGenerator.Core.Analyzers; -public interface IEnhancedPackageAnalyzer -{ - PackageAnalysisResult AnalyzePackages(IEnumerable projectFiles); -} - public class PackageAnalysisResult { public Dictionary ResolvedVersions { get; set; } = new(); @@ -138,7 +135,7 @@ public PackageAnalysisResult AnalyzePackages(IEnumerable projectFil return result; } - private VersionConflict CreateVersionConflict(string projectPath, string version) + private static VersionConflict CreateVersionConflict(string projectPath, string version) { var conflict = new VersionConflict { @@ -158,7 +155,7 @@ private VersionConflict CreateVersionConflict(string projectPath, string version return conflict; } - private void CheckForPrereleaseUsage(string packageName, string version, List warnings) + private static void CheckForPrereleaseUsage(string packageName, string version, List warnings) { if (NuGetVersion.TryParse(version, out var nugetVersion) && nugetVersion.IsPrerelease) { diff --git a/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs index f5eb6e3..035b6ca 100644 --- a/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs @@ -1,4 +1,5 @@ using System.Xml.Linq; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Models; using NuGet.Versioning; using Spectre.Console; diff --git a/CentralConfigGenerator.Core/Analyzers/ProjectAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/ProjectAnalyzer.cs index 2777a7f..74a7e4e 100644 --- a/CentralConfigGenerator.Core/Analyzers/ProjectAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/ProjectAnalyzer.cs @@ -1,4 +1,5 @@ using System.Xml.Linq; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Models; using Spectre.Console; diff --git a/CentralConfigGenerator.Core/Generators/IBuildPropsGenerator.cs b/CentralConfigGenerator.Core/Generators/Abstractions/IBuildPropsGenerator.cs similarity index 65% rename from CentralConfigGenerator.Core/Generators/IBuildPropsGenerator.cs rename to CentralConfigGenerator.Core/Generators/Abstractions/IBuildPropsGenerator.cs index be040dd..71ae427 100644 --- a/CentralConfigGenerator.Core/Generators/IBuildPropsGenerator.cs +++ b/CentralConfigGenerator.Core/Generators/Abstractions/IBuildPropsGenerator.cs @@ -1,4 +1,4 @@ -namespace CentralConfigGenerator.Core.Generators; +namespace CentralConfigGenerator.Core.Generators.Abstractions; public interface IBuildPropsGenerator { diff --git a/CentralConfigGenerator.Core/Generators/IPackagesPropsGenerator.cs b/CentralConfigGenerator.Core/Generators/Abstractions/IPackagesPropsGenerator.cs similarity index 66% rename from CentralConfigGenerator.Core/Generators/IPackagesPropsGenerator.cs rename to CentralConfigGenerator.Core/Generators/Abstractions/IPackagesPropsGenerator.cs index 0cb32a3..3509ee0 100644 --- a/CentralConfigGenerator.Core/Generators/IPackagesPropsGenerator.cs +++ b/CentralConfigGenerator.Core/Generators/Abstractions/IPackagesPropsGenerator.cs @@ -1,4 +1,4 @@ -namespace CentralConfigGenerator.Core.Generators; +namespace CentralConfigGenerator.Core.Generators.Abstractions; public interface IPackagesPropsGenerator { diff --git a/CentralConfigGenerator.Core/Generators/BuildPropsGenerator.cs b/CentralConfigGenerator.Core/Generators/BuildPropsGenerator.cs index af720b9..25d8259 100644 --- a/CentralConfigGenerator.Core/Generators/BuildPropsGenerator.cs +++ b/CentralConfigGenerator.Core/Generators/BuildPropsGenerator.cs @@ -1,4 +1,5 @@ using System.Xml.Linq; +using CentralConfigGenerator.Core.Generators.Abstractions; namespace CentralConfigGenerator.Core.Generators; diff --git a/CentralConfigGenerator.Core/Generators/PackagesPropsGenerator.cs b/CentralConfigGenerator.Core/Generators/PackagesPropsGenerator.cs index 1c6fdca..eaa58f1 100644 --- a/CentralConfigGenerator.Core/Generators/PackagesPropsGenerator.cs +++ b/CentralConfigGenerator.Core/Generators/PackagesPropsGenerator.cs @@ -1,4 +1,5 @@ using System.Xml.Linq; +using CentralConfigGenerator.Core.Generators.Abstractions; namespace CentralConfigGenerator.Core.Generators; diff --git a/CentralConfigGenerator.Core/Models/CompatibilityCheckResult.cs b/CentralConfigGenerator.Core/Models/CompatibilityCheckResult.cs new file mode 100644 index 0000000..baafc8e --- /dev/null +++ b/CentralConfigGenerator.Core/Models/CompatibilityCheckResult.cs @@ -0,0 +1,8 @@ +namespace CentralConfigGenerator.Core.Models; + +public class CompatibilityCheckResult +{ + public bool IsCompatible { get; set; } + public List Issues { get; set; } = []; + public string? SuggestedVersion { get; set; } +} \ No newline at end of file diff --git a/CentralConfigGenerator.Core/Models/ParsedVersion.cs b/CentralConfigGenerator.Core/Models/ParsedVersion.cs new file mode 100644 index 0000000..3995596 --- /dev/null +++ b/CentralConfigGenerator.Core/Models/ParsedVersion.cs @@ -0,0 +1,10 @@ +using NuGet.Versioning; + +namespace CentralConfigGenerator.Core.Models; + +public record ParsedVersion +{ + public required string Original { get; init; } + public NuGetVersion? Parsed { get; init; } + public VersionRange? Range { get; init; } +} \ No newline at end of file diff --git a/CentralConfigGenerator.Core/Services/Abstractions/IVersionCompatibilityChecker.cs b/CentralConfigGenerator.Core/Services/Abstractions/IVersionCompatibilityChecker.cs new file mode 100644 index 0000000..e0a19dd --- /dev/null +++ b/CentralConfigGenerator.Core/Services/Abstractions/IVersionCompatibilityChecker.cs @@ -0,0 +1,8 @@ +using CentralConfigGenerator.Core.Models; + +namespace CentralConfigGenerator.Core.Services.Abstractions; + +public interface IVersionCompatibilityChecker +{ + Task CheckCompatibilityAsync(string packageId, string version); +} \ No newline at end of file diff --git a/CentralConfigGenerator.Core/Services/Abstractions/IVersionConflictResolver.cs b/CentralConfigGenerator.Core/Services/Abstractions/IVersionConflictResolver.cs new file mode 100644 index 0000000..8131cbd --- /dev/null +++ b/CentralConfigGenerator.Core/Services/Abstractions/IVersionConflictResolver.cs @@ -0,0 +1,6 @@ +namespace CentralConfigGenerator.Core.Services.Abstractions; + +public interface IVersionConflictResolver +{ + string Resolve(string packageName, IEnumerable versions, VersionResolutionStrategy strategy); +} \ No newline at end of file diff --git a/CentralConfigGenerator.Core/Services/Abstractions/IVersionConflictVisualizer.cs b/CentralConfigGenerator.Core/Services/Abstractions/IVersionConflictVisualizer.cs new file mode 100644 index 0000000..d261337 --- /dev/null +++ b/CentralConfigGenerator.Core/Services/Abstractions/IVersionConflictVisualizer.cs @@ -0,0 +1,8 @@ +using CentralConfigGenerator.Core.Analyzers; + +namespace CentralConfigGenerator.Core.Services.Abstractions; + +public interface IVersionConflictVisualizer +{ + void DisplayResults(PackageAnalysisResult result); +} \ No newline at end of file diff --git a/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs b/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs index 8c00e9e..d45157b 100644 --- a/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs +++ b/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs @@ -1,3 +1,5 @@ +using CentralConfigGenerator.Core.Models; +using CentralConfigGenerator.Core.Services.Abstractions; using NuGet.Common; using NuGet.Configuration; using NuGet.Protocol.Core.Types; @@ -5,18 +7,6 @@ namespace CentralConfigGenerator.Core.Services; -public interface IVersionCompatibilityChecker -{ - Task CheckCompatibilityAsync(string packageId, string version); -} - -public class CompatibilityCheckResult -{ - public bool IsCompatible { get; set; } - public List Issues { get; set; } = new(); - public string? SuggestedVersion { get; set; } -} - public class VersionCompatibilityChecker : IVersionCompatibilityChecker { private readonly SourceRepository _repository; @@ -82,11 +72,4 @@ public async Task CheckCompatibilityAsync(string packa return result; } - - private class VersionIssue - { - public VersionRange ProblematicVersionRange { get; set; } = VersionRange.None; - public string Issue { get; set; } = string.Empty; - public string? SuggestedVersion { get; set; } - } -} +} \ No newline at end of file diff --git a/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs b/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs index d4385fc..3a1a6c8 100644 --- a/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs +++ b/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs @@ -1,12 +1,9 @@ +using CentralConfigGenerator.Core.Models; +using CentralConfigGenerator.Core.Services.Abstractions; using NuGet.Versioning; namespace CentralConfigGenerator.Core.Services; -public interface IVersionConflictResolver -{ - string Resolve(string packageName, IEnumerable versions, VersionResolutionStrategy strategy); -} - public enum VersionResolutionStrategy { Highest, @@ -15,13 +12,6 @@ public enum VersionResolutionStrategy Manual } -public record ParsedVersion -{ - public required string Original { get; init; } - public NuGetVersion? Parsed { get; init; } - public VersionRange? Range { get; init; } -} - public class VersionConflictResolver : IVersionConflictResolver { public string Resolve(string packageName, IEnumerable versions, VersionResolutionStrategy strategy) diff --git a/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs b/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs index d4492fe..3dba8cd 100644 --- a/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs +++ b/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs @@ -1,13 +1,9 @@ using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Services.Abstractions; using Spectre.Console; namespace CentralConfigGenerator.Core.Services; -public interface IVersionConflictVisualizer -{ - void DisplayResults(PackageAnalysisResult result); -} - public class VersionConflictVisualizer : IVersionConflictVisualizer { public void DisplayResults(PackageAnalysisResult result) @@ -42,9 +38,19 @@ public void DisplayResults(PackageAnalysisResult result) { foreach (var detail in conflict.Value) { - var versionType = detail.IsRange ? "Range" : - detail.IsPreRelease ? "Pre-release" : - "Release"; + string versionType; + if (detail.IsRange) + { + versionType = "Range"; + } + else if (detail.IsPreRelease) + { + versionType = "Pre-release"; + } + else + { + versionType = "Release"; + } conflictTable.AddRow( conflict.Key, diff --git a/CentralConfigGenerator.Tests/Commands/BuildPropsCommandTests.cs b/CentralConfigGenerator.Tests/Commands/BuildPropsCommandTests.cs index 155b70b..44c0bed 100644 --- a/CentralConfigGenerator.Tests/Commands/BuildPropsCommandTests.cs +++ b/CentralConfigGenerator.Tests/Commands/BuildPropsCommandTests.cs @@ -1,6 +1,8 @@ using CentralConfigGenerator.Commands; using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Generators; +using CentralConfigGenerator.Core.Generators.Abstractions; using CentralConfigGenerator.Core.Models; using CentralConfigGenerator.Services.Abstractions; diff --git a/CentralConfigGenerator.Tests/Commands/PackagesPropsCommandTests.cs b/CentralConfigGenerator.Tests/Commands/PackagesPropsCommandTests.cs index 208a513..88b2e8c 100644 --- a/CentralConfigGenerator.Tests/Commands/PackagesPropsCommandTests.cs +++ b/CentralConfigGenerator.Tests/Commands/PackagesPropsCommandTests.cs @@ -1,6 +1,8 @@ using CentralConfigGenerator.Commands; using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Generators; +using CentralConfigGenerator.Core.Generators.Abstractions; using CentralConfigGenerator.Core.Models; using CentralConfigGenerator.Services.Abstractions; diff --git a/CentralConfigGenerator/CentralConfigGenerator.csproj b/CentralConfigGenerator/CentralConfigGenerator.csproj index e18f056..94137ab 100644 --- a/CentralConfigGenerator/CentralConfigGenerator.csproj +++ b/CentralConfigGenerator/CentralConfigGenerator.csproj @@ -13,12 +13,13 @@ README.md - A modern .NET tool for automatically generating centralized configuration files for .NET projects. CentralConfig analyzes your solution structure and creates properly configured `Directory.Build.props` and `Directory.Packages.props` files to standardize settings across your projects. + A modern .NET tool for automatically generating centralized configuration files for .NET projects. + CentralConfig analyzes your solution structure and creates properly configured `Directory.Build.props` and `Directory.Packages.props` files to standardize settings across your projects. 1.1.0 Taras Kovalenko Copyright Taras Kovalenko - dotnet;msbuild;build;props;packages;centralized;configuration;directory-build-props;sdk;cli;tool;nuget + dotnet;msbuild;build;props;packages;centralized;configuration;directory-build-props;sdk;cli;tool;nuget;CPM;centralised CentralConfigGenerator MIT https://github.com/TarasKovalenko/CentralConfigGenerator @@ -39,6 +40,6 @@ - + \ No newline at end of file diff --git a/CentralConfigGenerator/Commands/BuildPropsCommand.cs b/CentralConfigGenerator/Commands/BuildPropsCommand.cs index 34cbd2a..36f5b10 100644 --- a/CentralConfigGenerator/Commands/BuildPropsCommand.cs +++ b/CentralConfigGenerator/Commands/BuildPropsCommand.cs @@ -1,6 +1,8 @@ using System.Xml.Linq; using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Generators; +using CentralConfigGenerator.Core.Generators.Abstractions; using CentralConfigGenerator.Extensions; using CentralConfigGenerator.Services.Abstractions; diff --git a/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs b/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs index 305c69e..3744d89 100644 --- a/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs +++ b/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs @@ -1,7 +1,10 @@ using System.Xml.Linq; using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Generators; +using CentralConfigGenerator.Core.Generators.Abstractions; using CentralConfigGenerator.Core.Services; +using CentralConfigGenerator.Core.Services.Abstractions; using CentralConfigGenerator.Services.Abstractions; using Spectre.Console; diff --git a/CentralConfigGenerator/Commands/PackagesPropsCommand.cs b/CentralConfigGenerator/Commands/PackagesPropsCommand.cs index 8a17313..156c8e5 100644 --- a/CentralConfigGenerator/Commands/PackagesPropsCommand.cs +++ b/CentralConfigGenerator/Commands/PackagesPropsCommand.cs @@ -1,6 +1,8 @@ using System.Xml.Linq; using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Generators; +using CentralConfigGenerator.Core.Generators.Abstractions; using CentralConfigGenerator.Extensions; using CentralConfigGenerator.Services.Abstractions; diff --git a/CentralConfigGenerator/Program.cs b/CentralConfigGenerator/Program.cs index 7bcbfcf..ff94529 100644 --- a/CentralConfigGenerator/Program.cs +++ b/CentralConfigGenerator/Program.cs @@ -1,8 +1,11 @@ using System.CommandLine; using CentralConfigGenerator.Commands; using CentralConfigGenerator.Core.Analyzers; +using CentralConfigGenerator.Core.Analyzers.Abstractions; using CentralConfigGenerator.Core.Generators; +using CentralConfigGenerator.Core.Generators.Abstractions; using CentralConfigGenerator.Core.Services; +using CentralConfigGenerator.Core.Services.Abstractions; using CentralConfigGenerator.Services; using CentralConfigGenerator.Services.Abstractions; using Microsoft.Extensions.DependencyInjection; diff --git a/CentralConfigGenerator/Services/Abstractions/IFileService.cs b/CentralConfigGenerator/Services/Abstractions/IFileService.cs index 5c72e29..8163441 100644 --- a/CentralConfigGenerator/Services/Abstractions/IFileService.cs +++ b/CentralConfigGenerator/Services/Abstractions/IFileService.cs @@ -3,6 +3,8 @@ public interface IFileService { bool Exists(string path); + Task ReadAllTextAsync(string path); + Task WriteAllTextAsync(string path, string contents); } \ No newline at end of file diff --git a/CentralConfigGenerator/docs/QUICK_START_ENHANCED.md b/CentralConfigGenerator/docs/QUICK_START_ENHANCED.md deleted file mode 100644 index 6a334a9..0000000 --- a/CentralConfigGenerator/docs/QUICK_START_ENHANCED.md +++ /dev/null @@ -1,170 +0,0 @@ -# Quick Start Guide: Enhanced Package Analysis - -This guide will help you get started with the enhanced package analysis features of CentralConfigGenerator. - -## Prerequisites - -- .NET SDK 9.0 or later -- CentralConfigGenerator installed globally - -## Basic Usage - -### 1. Analyze Your Solution - -Navigate to your solution directory and run: - -```bash -central-config packages-enhanced -v -``` - -This will: -- Scan all project files -- Detect version conflicts -- Show a visual analysis report -- Ask for confirmation before making changes - -### 2. Understanding the Output - -You'll see a comprehensive report like this: - -``` -Package Analysis Summary -┌─────────────────────────┬─────────┐ -│ Metric │ Value │ -├─────────────────────────┼─────────┤ -│ Total Packages │ 15 │ -│ Packages with Conflicts │ 3 │ -│ Warnings │ 4 │ -└─────────────────────────┴─────────┘ - -Version Conflicts Detected: -┌─────────────────────┬──────────────────┬─────────────┬─────────────┐ -│ Package │ Project │ Version │ Type │ -├─────────────────────┼──────────────────┼─────────────┼─────────────┤ -│ Newtonsoft.Json │ Web.csproj │ 11.0.2 │ Release │ -│ Newtonsoft.Json │ Core.csproj │ 13.0.3 │ Release │ -│ │ │ │ │ -│ Serilog │ Web.csproj │ 2.10.0 │ Release │ -│ Serilog │ Tests.csproj │ 3.0.1 │ Release │ -└─────────────────────┴──────────────────┴─────────────┴─────────────┘ -``` - -### 3. Conflict Resolution - -The tool will automatically resolve conflicts using the highest version strategy. You'll be asked to confirm: - -``` -Version conflicts were detected. Continue with resolved versions? [y/n] (y): -``` - -### 4. Review Changes - -After confirmation, the tool will: -1. Create a `Directory.Packages.props` file -2. Remove version attributes from project files -3. Show a summary of changes - -## Advanced Features - -### Check Specific Directory - -```bash -central-config packages-enhanced -d ./src/MyProjects -v -``` - -### Force Overwrite - -```bash -central-config packages-enhanced --overwrite -``` - -### Dry Run (Coming Soon) - -```bash -central-config packages-enhanced --dry-run -``` - -## Common Scenarios - -### Scenario 1: Mixed Pre-release and Stable Versions - -If you have both pre-release and stable versions: - -```xml - - - - - -``` - -Result: The stable version `1.0.0` will be selected. - -### Scenario 2: Version Ranges - -When projects use version ranges: - -```xml - - - - - -``` - -Result: The range `[1.0.0,2.0.0)` will be preserved as it encompasses the specific version. - -### Scenario 3: Outdated Packages - -The tool will warn about significantly outdated packages: - -``` -Warnings: -┌─────────┬─────────────────────┬─────────────────────────────────────────────┐ -│ Level │ Package │ Message │ -├─────────┼─────────────────────┼─────────────────────────────────────────────┤ -│ Warning │ EntityFramework │ This version is significantly outdated │ -└─────────┴─────────────────────┴─────────────────────────────────────────────┘ -``` - -## Troubleshooting - -### Issue: Variable Versions - -If you have versions like `$(MyVersion)`: - -```xml - -``` - -These will be preserved as-is in the centralized configuration. - -### Issue: Conflicting Pre-release Versions - -When multiple pre-release versions conflict: - -``` -MyPackage 1.0.0-alpha.1 -MyPackage 1.0.0-beta.1 -``` - -The tool correctly identifies that `beta.1` is newer than `alpha.1`. - -## Best Practices - -1. **Review Before Confirming**: Always review the conflict resolution before accepting -2. **Test After Migration**: Run your tests after centralizing package versions -3. **Use Stable Versions**: Prefer stable versions over pre-release in production -4. **Regular Updates**: Run the analysis periodically to keep versions consistent - -## Next Steps - -- Learn about [version management details](VERSION_MANAGEMENT.md) -- Read the [full documentation](README.md) - -## Getting Help - -If you encounter issues: - -1. Run with verbose logging: `central-config packages-enhanced -v` -2. Open an issue on [GitHub](https://github.com/TarasKovalenko/CentralConfigGenerator/issues) diff --git a/CentralConfigGenerator/docs/README.md b/CentralConfigGenerator/docs/README.md deleted file mode 100644 index b38c5ab..0000000 --- a/CentralConfigGenerator/docs/README.md +++ /dev/null @@ -1,283 +0,0 @@ -# CentralConfigGenerator - -[![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://taraskovalenko.github.io/) -[![build](https://github.com/TarasKovalenko/CentralConfigGenerator/actions/workflows/dotnet.yml/badge.svg)](https://github.com/TarasKovalenko/CentralConfigGenerator/actions) -[![CentralConfigGenerator NuGet current](https://img.shields.io/nuget/v/CentralConfigGenerator?label=CentralConfigGenerator)](https://www.nuget.org/packages/CentralConfigGenerator/) - -## Goals -A modern .NET tool for automatically generating centralized configuration files for .NET projects. CentralConfig analyzes your solution structure and creates properly configured `Directory.Build.props` and `Directory.Packages.props` files to standardize settings across your projects. - -## Terms of use - -By using this project or its source code, for any purpose and in any shape or form, you grant your **implicit agreement** to all of the following statements: - -- You unequivocally condemn Russia and its military aggression against Ukraine -- You recognize that Russia is an occupant that unlawfully invaded a sovereign state -- You agree that [Russia is a terrorist state](https://www.europarl.europa.eu/doceo/document/RC-9-2022-0482_EN.html) -- You fully support Ukraine's territorial integrity, including its claims over [temporarily occupied territories](https://en.wikipedia.org/wiki/Russian-occupied_territories_of_Ukraine) -- You reject false narratives perpetuated by Russian state propaganda - -To learn more about the war and how you can help, [click here](https://war.ukraine.ua/). Glory to Ukraine! 🇺🇦 - -## Overview - -CentralConfigGenerator helps you maintain consistent configuration across multiple .NET projects by: - -1. Automatically generating `Directory.Build.props` files with common project properties -2. Automatically generating `Directory.Packages.props` files with centralized package versions -3. Providing advanced version conflict resolution using NuGet.Versioning -4. Offering compatibility checking and visual analysis of version conflicts -5. Updating your project files to use these centralized configurations - -## Installation - -```bash -dotnet tool install --global CentralConfigGenerator -``` - -## Usage - -### Basic Commands - -CentralConfigGenerator provides four main commands: - -```bash -# Generate Directory.Build.props file with common project properties -central-config build [options] - -# Generate Directory.Packages.props file for centralized package versions -central-config packages [options] - -# Generate Directory.Packages.props with enhanced version analysis -central-config packages-enhanced [options] - -# Generate both files in one command -central-config all [options] -``` - -### Command Options - -All commands support the following options: - -- `-d, --directory `: Specify the directory to scan (defaults to current directory) -- `-o, --overwrite`: Overwrite existing files (off by default) -- `-v, --verbose`: Enable verbose logging - -### Examples - -#### Generate Directory.Build.props - -```bash -# Generate Directory.Build.props in the current directory -central-config build - -# Generate in a specific directory and overwrite if exists -central-config build -d C:\Projects\MySolution -o -``` - -#### Generate Directory.Packages.props - -```bash -# Generate Directory.Packages.props in the current directory -central-config packages - -# Generate in a specific directory with verbose logging -central-config packages -d C:\Projects\MySolution -v -``` - -#### Enhanced Package Analysis - -```bash -# Use enhanced package analysis with visual conflict resolution -central-config packages-enhanced -d C:\Projects\MySolution -v - -# This will: -# - Detect version conflicts across projects -# - Show visual analysis of conflicts -# - Suggest resolutions based on semantic versioning -# - Check for compatibility issues -# - Ask for confirmation before proceeding -``` - -## How It Works - -### Directory.Build.props Generation - -The `build` command: - -1. Scans all `.csproj` files in the specified directory and subdirectories -2. Identifies common properties that appear in multiple projects -3. Extracts these properties into a `Directory.Build.props` file -4. Removes the extracted properties from individual project files - -By default, CentralConfigGenerator will focus on the following key properties: -- `TargetFramework` -- `ImplicitUsings` -- `Nullable` - -### Directory.Packages.props Generation - -The `packages` command: - -1. Scans all `.csproj` files in the specified directory and subdirectories -2. Extracts all package references and their versions -3. For each package, uses the highest version found across all projects -4. Generates a `Directory.Packages.props` file with these package versions -5. Removes version attributes from `PackageReference` elements in project files - -### Enhanced Package Analysis - -The `packages-enhanced` command provides advanced features: - -1. **Semantic Version Analysis**: Uses NuGet.Versioning for accurate version comparisons -2. **Conflict Detection**: Identifies and visualizes version conflicts across projects -3. **Version Range Support**: Handles version ranges (e.g., `[1.0.0,2.0.0)`) -4. **Pre-release Detection**: Warns about pre-release packages in production code -5. **Compatibility Checking**: Identifies known issues with specific package versions -6. **Visual Reports**: Provides clear, color-coded reports of analysis results -7. **Multiple Resolution Strategies**: Offers different approaches to resolve conflicts: - - Highest version (default) - - Lowest version - - Most common version - - Manual resolution - -### Understanding the Generated Files - -#### Directory.Build.props - -```xml - - - net9.0 - enable - enable - - -``` - -#### Directory.Packages.props - -```xml - - - true - - - - - - - - -``` - -## Enhanced Features - -### Version Conflict Resolution - -When multiple projects reference the same package with different versions, the enhanced analyzer: - -1. Detects all version conflicts -2. Shows a detailed conflict report -3. Applies resolution strategy (highest version by default) -4. Asks for confirmation before proceeding - -Example output: -``` -Package Analysis Summary -┌─────────────────────────┬─────────┐ -│ Metric │ Value │ -├─────────────────────────┼─────────┤ -│ Total Packages │ 12 │ -│ Packages with Conflicts │ 3 │ -│ Warnings │ 5 │ -└─────────────────────────┴─────────┘ - -Version Conflicts Detected: -┌─────────────────────┬──────────────────┬─────────────┬─────────────┐ -│ Package │ Project │ Version │ Type │ -├─────────────────────┼──────────────────┼─────────────┼─────────────┤ -│ Newtonsoft.Json │ Project1.csproj │ 11.0.2 │ Release │ -│ Newtonsoft.Json │ Project2.csproj │ 13.0.3 │ Release │ -└─────────────────────┴──────────────────┴─────────────┴─────────────┘ -``` - -### Semantic Versioning Support - -The enhanced analyzer properly handles: - -- Pre-release versions (e.g., `1.0.0-beta.1`) -- Build metadata (e.g., `1.0.0+build.123`) -- Version ranges (e.g., `[1.0.0,2.0.0)`) -- Floating versions (e.g., `1.0.*`) - -### Compatibility Warnings - -The tool can warn about: - -- Known security vulnerabilities in specific versions -- Performance issues in certain package versions -- Significantly outdated packages -- Pre-release packages in production code - -## Benefits - -- **Consistent Configuration**: Ensure all projects use the same framework versions, language features, and code quality settings -- **Simplified Updates**: Update package versions or project settings in a single location -- **Reduced Duplication**: Remove redundant configuration from individual project files -- **Improved Maintainability**: Make your solution more maintainable by centralizing common settings -- **Version Conflict Resolution**: Automatically detect and resolve package version conflicts -- **Better Version Management**: Use semantic versioning for accurate version comparisons -- **Visual Analysis**: See clear, color-coded reports of package analysis results - -## Common Scenarios - -### Migrating Existing Solutions - -For existing solutions with many projects, use CentralConfigGenerator to centralize configuration: - -```bash -# Navigate to the solution root -cd MySolution - -# Generate both configuration files with verbose output -central-config all -v -``` - -### Resolving Version Conflicts - -When you have multiple projects with conflicting package versions: - -```bash -# Use enhanced package analysis to detect and resolve conflicts -central-config packages-enhanced -v - -# The tool will: -# 1. Show all version conflicts -# 2. Propose resolutions -# 3. Ask for confirmation -# 4. Update all project files -``` - -## Limitations - -- Projects with highly customized or conflicting settings may require manual adjustment after running CentralConfigGenerator -- For properties to be included in Directory.Build.props, they must appear with identical values in most projects -- Version conflicts in packages will be resolved by selecting the highest version found (configurable in enhanced mode) -- Some version formats (like variables) cannot be automatically resolved - -## Local installation - -To install the tool locally for development or testing, clone the repository and run: - -```bash -dotnet tool install --global --add-source .\CentralConfigGenerator\nupkg\ CentralConfigGenerator -``` - -## Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/CentralConfigGenerator/docs/VERSION_MANAGEMENT.md b/CentralConfigGenerator/docs/VERSION_MANAGEMENT.md deleted file mode 100644 index 3f440df..0000000 --- a/CentralConfigGenerator/docs/VERSION_MANAGEMENT.md +++ /dev/null @@ -1,236 +0,0 @@ -# Advanced Version Management with NuGet.Versioning - -This document explains how CentralConfigGenerator uses NuGet.Versioning to provide advanced version management capabilities. - -## Overview - -CentralConfigGenerator uses the `NuGet.Versioning` package to provide sophisticated version handling that goes beyond basic .NET version comparison. This integration enables proper semantic versioning support, version range handling, and accurate package version conflict resolution. - -## Key Features - -### 1. Semantic Versioning Support - -The tool properly handles semantic versioning (SemVer) including: - -- Major.Minor.Patch versions (e.g., `1.2.3`) -- Pre-release tags (e.g., `1.0.0-beta.1`, `2.0.0-rc.2`) -- Build metadata (e.g., `1.0.0+build.123`) - -#### Example: -```xml - - - - -``` - -### 2. Version Range Handling - -Supports NuGet's version range syntax: - -- Exact version: `1.0.0` -- Minimum version: `1.0.0` -- Exact range: `[1.0.0]` -- Minimum inclusive: `[1.0.0,)` -- Maximum inclusive: `(,1.0.0]` -- Range: `[1.0.0,2.0.0)` -- Floating version: `1.0.*` - -#### Example: -```xml - - - - -``` - -### 3. Conflict Resolution Strategies - -The enhanced package analyzer provides multiple strategies for resolving version conflicts: - -1. **Highest Version (Default)**: Selects the highest version among all referenced versions -2. **Lowest Version**: Selects the lowest version (useful for maximum compatibility) -3. **Most Common**: Selects the version used by most projects -4. **Manual**: Prompts for manual intervention when automatic resolution isn't suitable - -#### Example: -```csharp -// Using the version conflict resolver -var resolver = new VersionConflictResolver(); -var resolvedVersion = resolver.Resolve( - "Newtonsoft.Json", - new[] { "11.0.2", "12.0.3", "13.0.1" }, - VersionResolutionStrategy.Highest -); -// Result: "13.0.1" -``` - -### 4. Compatibility Checking - -The tool can check for known issues with specific package versions: - -- Security vulnerabilities -- Performance regressions -- Breaking changes -- Deprecated versions - -#### Example output: -``` -Warnings: -┌─────────┬─────────────────────┬─────────────────────────────────────────────┐ -│ Level │ Package │ Message │ -├─────────┼─────────────────────┼─────────────────────────────────────────────┤ -│ Warning │ Newtonsoft.Json │ Known security vulnerability in version 9.x │ -│ Info │ System.Text.Json │ Pre-release version detected │ -│ Warning │ EntityFramework │ This version is significantly outdated │ -└─────────┴─────────────────────┴─────────────────────────────────────────────┘ -``` - -## Implementation Details - -### Version Parsing - -The tool uses `NuGetVersion.TryParse()` instead of the basic `Version.TryParse()`: - -```csharp -// Old approach (limited) -if (Version.TryParse(versionStr, out var version)) -{ - // Only handles numeric versions like "1.0.0" -} - -// New approach (comprehensive) -if (NuGetVersion.TryParse(versionStr, out var nugetVersion)) -{ - // Handles "1.0.0-beta.1+build.123" and more -} -``` - -### Version Comparison - -NuGet.Versioning provides accurate version comparison following SemVer rules: - -```csharp -var v1 = NuGetVersion.Parse("1.0.0-alpha"); -var v2 = NuGetVersion.Parse("1.0.0-beta"); -var v3 = NuGetVersion.Parse("1.0.0"); - -// Correct ordering: alpha < beta < release -Console.WriteLine(v1 < v2); // True -Console.WriteLine(v2 < v3); // True -``` - -### Handling Special Cases - -The tool handles various special version formats: - -1. **Variables**: `$(VersionPrefix)$(VersionSuffix)` -2. **Properties**: `$(MyPackageVersion)` -3. **Wildcards**: `1.0.*` -4. **Floating**: `1.*` - -These are preserved in the output when they cannot be resolved to specific versions. - -## Visual Reporting - -The enhanced package analyzer provides detailed visual reports using Spectre.Console: - -``` -Package Analysis Summary -┌─────────────────────────┬─────────┐ -│ Metric │ Value │ -├─────────────────────────┼─────────┤ -│ Total Packages │ 25 │ -│ Packages with Conflicts │ 5 │ -│ Warnings │ 8 │ -└─────────────────────────┴─────────┘ - -Resolved Package Versions: -┌─────────────────────────────┬─────────────────┐ -│ Package │ Version │ -├─────────────────────────────┼─────────────────┤ -│ Microsoft.Extensions.Logging│ 8.0.0 │ -│ Newtonsoft.Json │ 13.0.3 │ -│ NuGet.Versioning │ 6.7.0 │ -└─────────────────────────────┴─────────────────┘ -``` - -## Best Practices - -1. **Use Semantic Versioning**: Follow SemVer conventions for your package versions -2. **Avoid Floating Versions**: Use specific versions in production code -3. **Review Conflicts**: Always review detected conflicts before accepting resolutions -4. **Check Compatibility**: Pay attention to compatibility warnings -5. **Test After Migration**: Test your solution after centralizing package versions - -## Troubleshooting - -### Common Issues - -1. **Unparseable Versions**: Some version formats (like variables) cannot be parsed - - Solution: These are preserved as-is in the output - -2. **Complex Version Ranges**: Overlapping ranges might cause unexpected resolutions - - Solution: Review the resolution and adjust manually if needed - -3. **Pre-release Dependencies**: Pre-release packages in production code - - Solution: The tool warns about these; consider using stable versions - -### Debug Information - -Run with verbose logging to see detailed version analysis: - -```bash -central-config packages-enhanced -v -``` - -This will show: -- Each version comparison -- Resolution decisions -- Parsing failures -- Compatibility check results - -## API Reference - -### Key Classes - -1. `EnhancedPackageAnalyzer`: Main analyzer with conflict detection -2. `VersionConflictResolver`: Resolves version conflicts using various strategies -3. `VersionCompatibilityChecker`: Checks for known version issues -4. `VersionConflictVisualizer`: Creates visual reports of analysis results - -### Extension Points - -The system is designed to be extensible: - -```csharp -// Custom resolution strategy -public class CustomVersionResolver : IVersionConflictResolver -{ - public string Resolve(string packageName, IEnumerable versions, - VersionResolutionStrategy strategy) - { - // Custom resolution logic - } -} - -// Custom compatibility checker -public class CustomCompatibilityChecker : IVersionCompatibilityChecker -{ - public Task CheckCompatibilityAsync( - string packageId, string version) - { - // Custom compatibility checks - } -} -``` - -## Future Enhancements - -Planned improvements include: - -1. NuGet API integration for real-time version information -2. Custom resolution rules configuration -3. Integration with vulnerability databases -4. Support for dependency graph analysis -5. Automated upgrade path suggestions diff --git a/Directory.Packages.props b/Directory.Packages.props index 37182f2..0a689d9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,6 +15,9 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file From 7f1ec458d524cd9f301fbee5258a84e3b56c8dfa Mon Sep 17 00:00:00 2001 From: Taras Kovalenko Date: Sun, 4 May 2025 16:22:50 +0300 Subject: [PATCH 3/4] refactor: improve code formatting and consistency across multiple files --- .../Abstractions/IEnhancedPackageAnalyzer.cs | 4 +- .../Analyzers/EnhancedPackageAnalyzer.cs | 124 ++++++++++++------ .../Analyzers/PackageAnalyzer.cs | 17 ++- .../Generators/BuildPropsGenerator.cs | 11 +- .../Generators/PackagesPropsGenerator.cs | 11 +- .../Services/VersionCompatibilityChecker.cs | 23 +++- .../Services/VersionConflictResolver.cs | 27 ++-- .../Services/VersionConflictVisualizer.cs | 12 +- .../Commands/BuildPropsCommand.cs | 13 +- .../Commands/EnhancedPackagesPropsCommand.cs | 70 ++++++---- .../Commands/PackagesPropsCommand.cs | 29 ++-- .../Extensions/MsgLogger.cs | 2 +- CentralConfigGenerator/Program.cs | 96 +++++++++----- 13 files changed, 283 insertions(+), 156 deletions(-) diff --git a/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs index ff5c749..2e582d5 100644 --- a/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs @@ -4,5 +4,5 @@ namespace CentralConfigGenerator.Core.Analyzers.Abstractions; public interface IEnhancedPackageAnalyzer { - PackageAnalysisResult AnalyzePackages(IEnumerable projectFiles); -} \ No newline at end of file + Task AnalyzePackagesAsync(IEnumerable projectFiles); +} diff --git a/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs index bc9234d..b53eb41 100644 --- a/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs @@ -33,15 +33,21 @@ public enum WarningLevel { Info, Warning, - Error + Error, } -public class EnhancedPackageAnalyzer(IVersionConflictResolver conflictResolver) : IEnhancedPackageAnalyzer +public class EnhancedPackageAnalyzer( + IVersionConflictResolver conflictResolver, + IVersionCompatibilityChecker compatibilityChecker +) : IEnhancedPackageAnalyzer { - public PackageAnalysisResult AnalyzePackages(IEnumerable projectFiles) + public async Task AnalyzePackagesAsync( + IEnumerable projectFiles + ) { var result = new PackageAnalysisResult(); - var packageVersionsByPackage = new Dictionary>(); + var packageVersionsByPackage = + new Dictionary>(); // Collect all versions foreach (var projectFile in projectFiles) @@ -60,21 +66,23 @@ public PackageAnalysisResult AnalyzePackages(IEnumerable projectFil continue; var version = versionAttr.Value; - + if (!packageVersionsByPackage.ContainsKey(packageName)) packageVersionsByPackage[packageName] = new List<(string, string)>(); - + packageVersionsByPackage[packageName].Add((projectFile.Path, version)); } } catch (Exception ex) { - result.Warnings.Add(new VersionWarning - { - PackageName = projectFile.Path, - Message = $"Failed to parse project file: {ex.Message}", - Level = WarningLevel.Error - }); + result.Warnings.Add( + new VersionWarning + { + PackageName = projectFile.Path, + Message = $"Failed to parse project file: {ex.Message}", + Level = WarningLevel.Error, + } + ); } } @@ -101,35 +109,63 @@ public PackageAnalysisResult AnalyzePackages(IEnumerable projectFil try { var resolvedVersion = conflictResolver.Resolve( - packageName, - uniqueVersions, - VersionResolutionStrategy.Highest); - + packageName, + uniqueVersions, + VersionResolutionStrategy.Highest + ); + result.ResolvedVersions[packageName] = resolvedVersion; - - result.Warnings.Add(new VersionWarning - { - PackageName = packageName, - Message = $"Multiple versions found. Resolved to: {resolvedVersion}", - Level = WarningLevel.Warning - }); + + result.Warnings.Add( + new VersionWarning + { + PackageName = packageName, + Message = $"Multiple versions found. Resolved to: {resolvedVersion}", + Level = WarningLevel.Warning, + } + ); } catch (Exception ex) { - result.Warnings.Add(new VersionWarning - { - PackageName = packageName, - Message = $"Failed to resolve version conflict: {ex.Message}", - Level = WarningLevel.Error - }); - + result.Warnings.Add( + new VersionWarning + { + PackageName = packageName, + Message = $"Failed to resolve version conflict: {ex.Message}", + Level = WarningLevel.Error, + } + ); + // Fallback: use most recent version result.ResolvedVersions[packageName] = uniqueVersions.OrderDescending().First(); } } // Check for pre-release usage - CheckForPrereleaseUsage(packageName, result.ResolvedVersions[packageName], result.Warnings); + CheckForPrereleaseUsage( + packageName, + result.ResolvedVersions[packageName], + result.Warnings + ); + + // Check compatibility + var compatibilityResult = await compatibilityChecker.CheckCompatibilityAsync( + packageName, + result.ResolvedVersions[packageName] + ); + + if (compatibilityResult.SuggestedVersion != null) + { + result.Warnings.Add( + new VersionWarning + { + PackageName = packageName, + Message = + $"Consider upgrading to version {compatibilityResult.SuggestedVersion} for better compatibility.", + Level = WarningLevel.Warning, + } + ); + } } return result; @@ -137,11 +173,7 @@ public PackageAnalysisResult AnalyzePackages(IEnumerable projectFil private static VersionConflict CreateVersionConflict(string projectPath, string version) { - var conflict = new VersionConflict - { - ProjectFile = projectPath, - Version = version - }; + var conflict = new VersionConflict { ProjectFile = projectPath, Version = version }; if (NuGetVersion.TryParse(version, out var nugetVersion)) { @@ -155,16 +187,22 @@ private static VersionConflict CreateVersionConflict(string projectPath, string return conflict; } - private static void CheckForPrereleaseUsage(string packageName, string version, List warnings) + private static void CheckForPrereleaseUsage( + string packageName, + string version, + List warnings + ) { if (NuGetVersion.TryParse(version, out var nugetVersion) && nugetVersion.IsPrerelease) { - warnings.Add(new VersionWarning - { - PackageName = packageName, - Message = $"Using pre-release version: {version}", - Level = WarningLevel.Info - }); + warnings.Add( + new VersionWarning + { + PackageName = packageName, + Message = $"Using pre-release version: {version}", + Level = WarningLevel.Info, + } + ); } } } diff --git a/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs index 035b6ca..f834cae 100644 --- a/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/PackageAnalyzer.cs @@ -34,11 +34,17 @@ public Dictionary ExtractPackageVersions(IEnumerable packageVersions[packageName]) + + if ( + !packageVersions.ContainsKey(packageName) + || nugetVersion > packageVersions[packageName] + ) { packageVersions[packageName] = nugetVersion; stringVersions[packageName] = versionStr; @@ -46,7 +52,10 @@ public Dictionary ExtractPackageVersions(IEnumerable packageVersions[packageName]) + if ( + !packageVersions.ContainsKey(packageName) + || nugetVersion > packageVersions[packageName] + ) { packageVersions[packageName] = nugetVersion; stringVersions[packageName] = versionStr; diff --git a/CentralConfigGenerator.Core/Generators/BuildPropsGenerator.cs b/CentralConfigGenerator.Core/Generators/BuildPropsGenerator.cs index 25d8259..48d5ad7 100644 --- a/CentralConfigGenerator.Core/Generators/BuildPropsGenerator.cs +++ b/CentralConfigGenerator.Core/Generators/BuildPropsGenerator.cs @@ -7,17 +7,14 @@ public class BuildPropsGenerator : IBuildPropsGenerator { public string GenerateBuildPropsContent(Dictionary commonProperties) { - var xDoc = new XDocument( - new XElement("Project", - new XElement("PropertyGroup") - ) - ); + var xDoc = new XDocument(new XElement("Project", new XElement("PropertyGroup"))); var propertyGroup = xDoc.Root!.Element("PropertyGroup")!; // Define the required properties we want to include + var requiredPropertyNames = new[] { "TargetFramework", "ImplicitUsings", "Nullable" }; - + // Add the properties that match our required list foreach (var propertyName in requiredPropertyNames) { @@ -40,4 +37,4 @@ public string GenerateBuildPropsContent(Dictionary commonPropert return xDoc.ToString(); } -} \ No newline at end of file +} diff --git a/CentralConfigGenerator.Core/Generators/PackagesPropsGenerator.cs b/CentralConfigGenerator.Core/Generators/PackagesPropsGenerator.cs index eaa58f1..5fdc6bf 100644 --- a/CentralConfigGenerator.Core/Generators/PackagesPropsGenerator.cs +++ b/CentralConfigGenerator.Core/Generators/PackagesPropsGenerator.cs @@ -8,8 +8,10 @@ public class PackagesPropsGenerator : IPackagesPropsGenerator public string GeneratePackagesPropsContent(Dictionary packageVersions) { var xDoc = new XDocument( - new XElement("Project", - new XElement("PropertyGroup", + new XElement( + "Project", + new XElement( + "PropertyGroup", new XElement("ManagePackageVersionsCentrally", "true") ), new XElement("ItemGroup") @@ -21,7 +23,8 @@ public string GeneratePackagesPropsContent(Dictionary packageVer foreach (var package in packageVersions.OrderBy(p => p.Key)) { itemGroup.Add( - new XElement("PackageVersion", + new XElement( + "PackageVersion", new XAttribute("Include", package.Key), new XAttribute("Version", package.Value) ) @@ -30,4 +33,4 @@ public string GeneratePackagesPropsContent(Dictionary packageVer return xDoc.ToString(); } -} \ No newline at end of file +} diff --git a/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs b/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs index d45157b..801b8be 100644 --- a/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs +++ b/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs @@ -14,10 +14,16 @@ public class VersionCompatibilityChecker : IVersionCompatibilityChecker public VersionCompatibilityChecker() { var providers = new List>(Repository.Provider.GetCoreV3()); - _repository = new SourceRepository(new PackageSource("https://api.nuget.org/v3/index.json"), providers); + _repository = new SourceRepository( + new PackageSource("https://api.nuget.org/v3/index.json"), + providers + ); } - public async Task CheckCompatibilityAsync(string packageId, string version) + public async Task CheckCompatibilityAsync( + string packageId, + string version + ) { var result = new CompatibilityCheckResult(); if (!NuGetVersion.TryParse(version, out var nugetVersion)) @@ -37,7 +43,8 @@ public async Task CheckCompatibilityAsync(string packa includeUnlisted: false, new SourceCacheContext(), NullLogger.Instance, - CancellationToken.None); + CancellationToken.None + ); var allVersions = searchMetadata .Select(m => m.Identity.Version) @@ -54,14 +61,18 @@ public async Task CheckCompatibilityAsync(string packa // Check for pre-release if (nugetVersion.IsPrerelease) { - result.Issues.Add("Pre-release version detected. Consider using a stable release for production."); + result.Issues.Add( + "Pre-release version detected. Consider using a stable release for production." + ); } // Check outdated major version var latestStable = allVersions.FirstOrDefault(v => !v.IsPrerelease); if (latestStable != null && nugetVersion.Major < latestStable.Major - 1) { - result.Issues.Add($"This version is significantly outdated. Latest stable is {latestStable}."); + result.Issues.Add( + $"This version is significantly outdated. Latest stable is {latestStable}." + ); result.SuggestedVersion ??= latestStable.ToNormalizedString(); } } @@ -72,4 +83,4 @@ public async Task CheckCompatibilityAsync(string packa return result; } -} \ No newline at end of file +} diff --git a/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs b/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs index 3a1a6c8..9ad5eb8 100644 --- a/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs +++ b/CentralConfigGenerator.Core/Services/VersionConflictResolver.cs @@ -9,12 +9,16 @@ public enum VersionResolutionStrategy Highest, Lowest, MostCommon, - Manual + Manual, } public class VersionConflictResolver : IVersionConflictResolver { - public string Resolve(string packageName, IEnumerable versions, VersionResolutionStrategy strategy) + public string Resolve( + string packageName, + IEnumerable versions, + VersionResolutionStrategy strategy + ) { var versionList = versions.ToList(); @@ -34,7 +38,7 @@ public string Resolve(string packageName, IEnumerable versions, VersionR { Original = v, Parsed = NuGetVersion.TryParse(v, out var parsed) ? parsed : null, - Range = VersionRange.TryParse(v, out var range) ? range : null + Range = VersionRange.TryParse(v, out var range) ? range : null, }) .ToList(); @@ -44,8 +48,9 @@ public string Resolve(string packageName, IEnumerable versions, VersionR VersionResolutionStrategy.Lowest => ResolveLowest(parsedVersions), VersionResolutionStrategy.MostCommon => ResolveMostCommon(versionList), VersionResolutionStrategy.Manual => throw new InvalidOperationException( - $"Manual resolution required for package '{packageName}'. Versions found: {string.Join(", ", versionList)}"), - _ => throw new ArgumentOutOfRangeException(nameof(strategy)) + $"Manual resolution required for package '{packageName}'. Versions found: {string.Join(", ", versionList)}" + ), + _ => throw new ArgumentOutOfRangeException(nameof(strategy)), }; } @@ -74,10 +79,7 @@ private static string ResolveHighest(List parsedVersions) } // Fallback to string comparison - return parsedVersions - .OrderByDescending(v => v.Original) - .First() - .Original; + return parsedVersions.OrderByDescending(v => v.Original).First().Original; } private static string ResolveLowest(List parsedVersions) @@ -105,10 +107,7 @@ private static string ResolveLowest(List parsedVersions) } // Fallback to string comparison - return parsedVersions - .OrderBy(v => v.Original) - .First() - .Original; + return parsedVersions.OrderBy(v => v.Original).First().Original; } private static string ResolveMostCommon(List versions) @@ -120,4 +119,4 @@ private static string ResolveMostCommon(List versions) .First() .Key; } -} \ No newline at end of file +} diff --git a/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs b/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs index 3dba8cd..5059d92 100644 --- a/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs +++ b/CentralConfigGenerator.Core/Services/VersionConflictVisualizer.cs @@ -17,9 +17,11 @@ public void DisplayResults(PackageAnalysisResult result) summaryTable.AddRow("Packages with Conflicts", result.Conflicts.Count.ToString()); summaryTable.AddRow("Warnings", result.Warnings.Count.ToString()); - AnsiConsole.Write(new Panel(summaryTable) - .Header("[bold green]Package Analysis Summary[/]") - .Border(BoxBorder.Rounded)); + AnsiConsole.Write( + new Panel(summaryTable) + .Header("[bold green]Package Analysis Summary[/]") + .Border(BoxBorder.Rounded) + ); AnsiConsole.WriteLine(); @@ -88,7 +90,7 @@ public void DisplayResults(PackageAnalysisResult result) WarningLevel.Info => "[blue]Info[/]", WarningLevel.Warning => "[yellow]Warning[/]", WarningLevel.Error => "[red]Error[/]", - _ => "[white]Unknown[/]" + _ => "[white]Unknown[/]", }; warningsTable.AddRow( @@ -116,4 +118,4 @@ public void DisplayResults(PackageAnalysisResult result) AnsiConsole.Write(versionTable); } -} \ No newline at end of file +} diff --git a/CentralConfigGenerator/Commands/BuildPropsCommand.cs b/CentralConfigGenerator/Commands/BuildPropsCommand.cs index 36f5b10..115c1bd 100644 --- a/CentralConfigGenerator/Commands/BuildPropsCommand.cs +++ b/CentralConfigGenerator/Commands/BuildPropsCommand.cs @@ -17,13 +17,18 @@ IFileService fileService { public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite) { - MsgLogger.LogInformation("Generating Directory.Build.props for directory: {0}", directory.FullName); + MsgLogger.LogInformation( + "Generating Directory.Build.props for directory: {0}", + directory.FullName + ); var targetPath = Path.Combine(directory.FullName, "Directory.Build.props"); if (fileService.Exists(targetPath) && !overwrite) { - MsgLogger.LogWarning("File Directory.Build.props already exists. Use --overwrite to replace it."); + MsgLogger.LogWarning( + "File Directory.Build.props already exists. Use --overwrite to replace it." + ); return; } @@ -67,7 +72,7 @@ public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite) { // Find all property elements with the specified name var propertyElements = xDoc.Descendants(property).ToList(); - + foreach (var element in propertyElements) { element.Remove(); @@ -87,4 +92,4 @@ public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite) } } } -} \ No newline at end of file +} diff --git a/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs b/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs index 3744d89..044758e 100644 --- a/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs +++ b/CentralConfigGenerator/Commands/EnhancedPackagesPropsCommand.cs @@ -20,18 +20,24 @@ IVersionConflictVisualizer conflictVisualizer { public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite, bool verbose = false) { - AnsiConsole.Status() - .Start("Scanning for project files...", ctx => - { - ctx.Spinner(Spinner.Known.Star); - ctx.SpinnerStyle(Style.Parse("green")); - }); + AnsiConsole + .Status() + .Start( + "Scanning for project files...", + ctx => + { + ctx.Spinner(Spinner.Known.Star); + ctx.SpinnerStyle(Style.Parse("green")); + } + ); var targetPath = Path.Combine(directory.FullName, "Directory.Packages.props"); if (fileService.Exists(targetPath) && !overwrite) { - AnsiConsole.MarkupLine("[yellow]Warning:[/] File Directory.Packages.props already exists. Use --overwrite to replace it."); + AnsiConsole.MarkupLine( + "[yellow]Warning:[/] File Directory.Packages.props already exists. Use --overwrite to replace it." + ); return; } @@ -39,20 +45,26 @@ public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite, bool ver if (projectFiles.Count == 0) { - AnsiConsole.MarkupLine("[yellow]Warning:[/] No .csproj files found in the directory tree."); + AnsiConsole.MarkupLine( + "[yellow]Warning:[/] No .csproj files found in the directory tree." + ); return; } AnsiConsole.MarkupLine($"[green]Found {projectFiles.Count} project files[/]"); // Analyze packages with enhanced analyzer - var analysisResult = await AnsiConsole.Status() - .StartAsync("Analyzing package versions...", ctx => - { - ctx.Spinner(Spinner.Known.Star); - ctx.SpinnerStyle(Style.Parse("green")); - return Task.FromResult(packageAnalyzer.AnalyzePackages(projectFiles)); - }); + var analysisResult = await AnsiConsole + .Status() + .StartAsync( + "Analyzing package versions...", + async ctx => + { + ctx.Spinner(Spinner.Known.Star); + ctx.SpinnerStyle(Style.Parse("green")); + return await packageAnalyzer.AnalyzePackagesAsync(projectFiles); + } + ); // Display analysis results conflictVisualizer.DisplayResults(analysisResult); @@ -60,7 +72,11 @@ public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite, bool ver // Ask for confirmation if conflicts exist if (analysisResult.Conflicts.Any()) { - if (!AnsiConsole.Confirm("Version conflicts were detected. Continue with resolved versions?")) + if ( + !AnsiConsole.Confirm( + "Version conflicts were detected. Continue with resolved versions?" + ) + ) { AnsiConsole.MarkupLine("[red]Operation cancelled by user.[/]"); return; @@ -68,7 +84,9 @@ public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite, bool ver } // Generate Directory.Packages.props - var packagesPropsContent = packagesPropsGenerator.GeneratePackagesPropsContent(analysisResult.ResolvedVersions); + var packagesPropsContent = packagesPropsGenerator.GeneratePackagesPropsContent( + analysisResult.ResolvedVersions + ); await fileService.WriteAllTextAsync(targetPath, packagesPropsContent); AnsiConsole.MarkupLine($"[green]Created Directory.Packages.props at {targetPath}[/]"); @@ -77,14 +95,20 @@ public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite, bool ver var updateConfirmed = AnsiConsole.Confirm("Remove version attributes from project files?"); if (!updateConfirmed) { - AnsiConsole.MarkupLine("[yellow]Skipping project file updates. You'll need to manually remove Version attributes.[/]"); + AnsiConsole.MarkupLine( + "[yellow]Skipping project file updates. You'll need to manually remove Version attributes.[/]" + ); return; } - await AnsiConsole.Progress() + await AnsiConsole + .Progress() .StartAsync(async ctx => { - var task = ctx.AddTask("[green]Updating project files[/]", maxValue: projectFiles.Count); + var task = ctx.AddTask( + "[green]Updating project files[/]", + maxValue: projectFiles.Count + ); foreach (var projectFile in projectFiles) { @@ -116,9 +140,11 @@ await AnsiConsole.Progress() } catch (Exception ex) { - AnsiConsole.MarkupLine($"[red]Error updating {projectFile.Path}: {ex.Message}[/]"); + AnsiConsole.MarkupLine( + $"[red]Error updating {projectFile.Path}: {ex.Message}[/]" + ); } - + task.Increment(1); } }); diff --git a/CentralConfigGenerator/Commands/PackagesPropsCommand.cs b/CentralConfigGenerator/Commands/PackagesPropsCommand.cs index 156c8e5..65d7970 100644 --- a/CentralConfigGenerator/Commands/PackagesPropsCommand.cs +++ b/CentralConfigGenerator/Commands/PackagesPropsCommand.cs @@ -17,14 +17,18 @@ IFileService fileService { public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite) { - MsgLogger.LogInformation("Generating Directory.Packages.props for directory: {0}", - directory.FullName); + MsgLogger.LogInformation( + "Generating Directory.Packages.props for directory: {0}", + directory.FullName + ); var targetPath = Path.Combine(directory.FullName, "Directory.Packages.props"); if (fileService.Exists(targetPath) && !overwrite) { - MsgLogger.LogWarning("File Directory.Packages.props already exists. Use --overwrite to replace it."); + MsgLogger.LogWarning( + "File Directory.Packages.props already exists. Use --overwrite to replace it." + ); return; } @@ -46,7 +50,9 @@ public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite) MsgLogger.LogDebug("Package: {0} = {1}", package.Key, package.Value); } - var packagesPropsContent = packagesPropsGenerator.GeneratePackagesPropsContent(packageVersions); + var packagesPropsContent = packagesPropsGenerator.GeneratePackagesPropsContent( + packageVersions + ); await fileService.WriteAllTextAsync(targetPath, packagesPropsContent); @@ -76,15 +82,20 @@ public async Task ExecuteAsync(DirectoryInfo directory, bool overwrite) if (changed) { await fileService.WriteAllTextAsync(projectFile.Path, xDoc.ToString()); - MsgLogger.LogInformation("Updated package references in project file: {0}", - projectFile.Path); + MsgLogger.LogInformation( + "Updated package references in project file: {0}", + projectFile.Path + ); } } catch (Exception ex) { - MsgLogger.LogError(ex, "Error updating package references in project file: {0}", - projectFile.Path); + MsgLogger.LogError( + ex, + "Error updating package references in project file: {0}", + projectFile.Path + ); } } } -} \ No newline at end of file +} diff --git a/CentralConfigGenerator/Extensions/MsgLogger.cs b/CentralConfigGenerator/Extensions/MsgLogger.cs index 283aa32..c334a5b 100644 --- a/CentralConfigGenerator/Extensions/MsgLogger.cs +++ b/CentralConfigGenerator/Extensions/MsgLogger.cs @@ -25,4 +25,4 @@ public static void LogError(Exception exception, string message, params object[] LogError(message, args); AnsiConsole.WriteException(exception, ExceptionFormats.ShortenEverything); } -} \ No newline at end of file +} diff --git a/CentralConfigGenerator/Program.cs b/CentralConfigGenerator/Program.cs index ff94529..1e7e9b4 100644 --- a/CentralConfigGenerator/Program.cs +++ b/CentralConfigGenerator/Program.cs @@ -20,13 +20,19 @@ static async Task Main(string[] args) var rootCommand = new RootCommand { - Description = "A tool to generate centralized configuration files for .NET projects" + Description = "A tool to generate centralized configuration files for .NET projects", }; var buildCommand = new Command("build", "Generate Directory.Build.props file"); var packagesCommand = new Command("packages", "Generate Directory.Packages.props file"); - var packagesEnhancedCommand = new Command("packages-enhanced", "Generate Directory.Packages.props file with enhanced version analysis"); - var allCommand = new Command("all", "Generate both Directory.Build.props and Directory.Packages.props files"); + var packagesEnhancedCommand = new Command( + "packages-enhanced", + "Generate Directory.Packages.props file with enhanced version analysis" + ); + var allCommand = new Command( + "all", + "Generate both Directory.Build.props and Directory.Packages.props files" + ); var directoryOption = new Option( ["--directory", "-d"], @@ -62,41 +68,61 @@ static async Task Main(string[] args) allCommand.AddOption(overwriteOption); allCommand.AddOption(verboseOption); - buildCommand.SetHandler(async (directory, overwrite, _) => - { - var command = services.GetRequiredService(); - ArgumentNullException.ThrowIfNull(command); - - await command.ExecuteAsync(directory, overwrite); - }, directoryOption, overwriteOption, verboseOption); - - packagesCommand.SetHandler(async (directory, overwrite, _) => - { - var command = services.GetRequiredService(); - ArgumentNullException.ThrowIfNull(command); - - await command.ExecuteAsync(directory, overwrite); - }, directoryOption, overwriteOption, verboseOption); - - packagesEnhancedCommand.SetHandler(async (directory, overwrite, verbose) => - { - var command = services.GetRequiredService(); - ArgumentNullException.ThrowIfNull(command); - - await command.ExecuteAsync(directory, overwrite, verbose); - }, directoryOption, overwriteOption, verboseOption); + buildCommand.SetHandler( + async (directory, overwrite, _) => + { + var command = services.GetRequiredService(); + ArgumentNullException.ThrowIfNull(command); + + await command.ExecuteAsync(directory, overwrite); + }, + directoryOption, + overwriteOption, + verboseOption + ); - allCommand.SetHandler(async (directory, overwrite, _) => - { - var buildPropsCommand = services.GetRequiredService(); - ArgumentNullException.ThrowIfNull(buildPropsCommand); + packagesCommand.SetHandler( + async (directory, overwrite, _) => + { + var command = services.GetRequiredService(); + ArgumentNullException.ThrowIfNull(command); + + await command.ExecuteAsync(directory, overwrite); + }, + directoryOption, + overwriteOption, + verboseOption + ); - var packagesPropsCommand = services.GetRequiredService(); - ArgumentNullException.ThrowIfNull(packagesPropsCommand); + packagesEnhancedCommand.SetHandler( + async (directory, overwrite, verbose) => + { + var command = services.GetRequiredService(); + ArgumentNullException.ThrowIfNull(command); + + await command.ExecuteAsync(directory, overwrite, verbose); + }, + directoryOption, + overwriteOption, + verboseOption + ); - await buildPropsCommand.ExecuteAsync(directory, overwrite); - await packagesPropsCommand.ExecuteAsync(directory, overwrite); - }, directoryOption, overwriteOption, verboseOption); + allCommand.SetHandler( + async (directory, overwrite, _) => + { + var buildPropsCommand = services.GetRequiredService(); + ArgumentNullException.ThrowIfNull(buildPropsCommand); + + var packagesPropsCommand = services.GetRequiredService(); + ArgumentNullException.ThrowIfNull(packagesPropsCommand); + + await buildPropsCommand.ExecuteAsync(directory, overwrite); + await packagesPropsCommand.ExecuteAsync(directory, overwrite); + }, + directoryOption, + overwriteOption, + verboseOption + ); rootCommand.AddCommand(buildCommand); rootCommand.AddCommand(packagesCommand); From 8db8ee679b61bc6191517675da7de1a360df5d11 Mon Sep 17 00:00:00 2001 From: Taras Kovalenko Date: Sun, 4 May 2025 19:58:53 +0300 Subject: [PATCH 4/4] feat: add cancellation token support to package analysis and compatibility checks, include NSubstitute for testing --- .../CentralConfigGenerator.Core.Tests.csproj | 1 + .../Abstractions/IEnhancedPackageAnalyzer.cs | 5 ++- .../Analyzers/EnhancedPackageAnalyzer.cs | 5 +-- .../IVersionCompatibilityChecker.cs | 8 +++-- .../Services/VersionCompatibilityChecker.cs | 7 ++-- VERSION_MANAGEMENT.md | 36 ------------------- 6 files changed, 19 insertions(+), 43 deletions(-) diff --git a/CentralConfigGenerator.Core.Tests/CentralConfigGenerator.Core.Tests.csproj b/CentralConfigGenerator.Core.Tests/CentralConfigGenerator.Core.Tests.csproj index 4c7aa0f..0ebbfec 100644 --- a/CentralConfigGenerator.Core.Tests/CentralConfigGenerator.Core.Tests.csproj +++ b/CentralConfigGenerator.Core.Tests/CentralConfigGenerator.Core.Tests.csproj @@ -10,6 +10,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs index 2e582d5..fd1ffc5 100644 --- a/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/Abstractions/IEnhancedPackageAnalyzer.cs @@ -4,5 +4,8 @@ namespace CentralConfigGenerator.Core.Analyzers.Abstractions; public interface IEnhancedPackageAnalyzer { - Task AnalyzePackagesAsync(IEnumerable projectFiles); + Task AnalyzePackagesAsync( + IEnumerable projectFiles, + CancellationToken cancellationToken = default + ); } diff --git a/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs b/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs index b53eb41..4acf9fd 100644 --- a/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs +++ b/CentralConfigGenerator.Core/Analyzers/EnhancedPackageAnalyzer.cs @@ -42,7 +42,8 @@ IVersionCompatibilityChecker compatibilityChecker ) : IEnhancedPackageAnalyzer { public async Task AnalyzePackagesAsync( - IEnumerable projectFiles + IEnumerable projectFiles, + CancellationToken cancellationToken = default ) { var result = new PackageAnalysisResult(); @@ -162,7 +163,7 @@ IEnumerable projectFiles PackageName = packageName, Message = $"Consider upgrading to version {compatibilityResult.SuggestedVersion} for better compatibility.", - Level = WarningLevel.Warning, + Level = WarningLevel.Info, } ); } diff --git a/CentralConfigGenerator.Core/Services/Abstractions/IVersionCompatibilityChecker.cs b/CentralConfigGenerator.Core/Services/Abstractions/IVersionCompatibilityChecker.cs index e0a19dd..883a012 100644 --- a/CentralConfigGenerator.Core/Services/Abstractions/IVersionCompatibilityChecker.cs +++ b/CentralConfigGenerator.Core/Services/Abstractions/IVersionCompatibilityChecker.cs @@ -4,5 +4,9 @@ namespace CentralConfigGenerator.Core.Services.Abstractions; public interface IVersionCompatibilityChecker { - Task CheckCompatibilityAsync(string packageId, string version); -} \ No newline at end of file + Task CheckCompatibilityAsync( + string packageId, + string version, + CancellationToken cancellationToken = default + ); +} diff --git a/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs b/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs index 801b8be..ac696df 100644 --- a/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs +++ b/CentralConfigGenerator.Core/Services/VersionCompatibilityChecker.cs @@ -22,7 +22,8 @@ public VersionCompatibilityChecker() public async Task CheckCompatibilityAsync( string packageId, - string version + string version, + CancellationToken cancellationToken = default ) { var result = new CompatibilityCheckResult(); @@ -36,7 +37,9 @@ string version // NuGet API lookup try { - var metadataResource = await _repository.GetResourceAsync(); + var metadataResource = await _repository.GetResourceAsync( + cancellationToken + ); var searchMetadata = await metadataResource.GetMetadataAsync( packageId, includePrerelease: true, diff --git a/VERSION_MANAGEMENT.md b/VERSION_MANAGEMENT.md index 8423200..8ba89c6 100644 --- a/VERSION_MANAGEMENT.md +++ b/VERSION_MANAGEMENT.md @@ -198,39 +198,3 @@ This will show: 2. `VersionConflictResolver`: Resolves version conflicts using various strategies 3. `VersionCompatibilityChecker`: Checks for known version issues 4. `VersionConflictVisualizer`: Creates visual reports of analysis results - -### Extension Points - -The system is designed to be extensible: - -```csharp -// Custom resolution strategy -public class CustomVersionResolver : IVersionConflictResolver -{ - public string Resolve(string packageName, IEnumerable versions, - VersionResolutionStrategy strategy) - { - // Custom resolution logic - } -} - -// Custom compatibility checker -public class CustomCompatibilityChecker : IVersionCompatibilityChecker -{ - public Task CheckCompatibilityAsync( - string packageId, string version) - { - // Custom compatibility checks - } -} -``` - -## Future Enhancements - -Planned improvements include: - -1. NuGet API integration for real-time version information -2. Custom resolution rules configuration -3. Integration with vulnerability databases -4. Support for dependency graph analysis -5. Automated upgrade path suggestions