dependabot-nuget 0.321.2 → 0.322.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +22 -22
  3. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageMapper.cs +9 -0
  4. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/Program.cs +21 -7
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +19 -11
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +19 -9
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +21 -14
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/FrameworkCheckCommand.cs +8 -5
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +29 -16
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +20 -19
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +2 -1
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscoveryTargetingPacks.props +2 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencySolver/IDependencySolver.cs +8 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencySolver/MSBuildDependencySolver.cs +32 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +1 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +10 -1
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +6 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +5 -13
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/PrivateSourceTimedOutException.cs +12 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +4 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceTimedOut.cs +10 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +1 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +1 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +2 -2
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +1 -1
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +1 -1
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +1 -1
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +6 -3
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/FileWriterWorker.cs +376 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/IFileWriter.cs +14 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/XmlFileWriter.cs +477 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +9 -5
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +18 -7
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +26 -1
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +15 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/DependencySolver/MSBuildDependencySolverTests.cs +633 -0
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +0 -2
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +0 -2
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +49 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +0 -1
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs +484 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +1 -0
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/JobErrorBaseTests.cs +7 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +11 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +21 -22
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +8 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/DotNetToolsJsonUpdaterTests.cs +181 -0
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterTestsBase.cs +61 -0
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests.cs +917 -0
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests_MiscellaneousTests.cs +154 -0
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/TestFileWriterReturnsConstantResult.cs +20 -0
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests.cs +1620 -0
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests_CreateUpdatedVersionRangeTests.cs +25 -0
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/GlobalJsonUpdaterTests.cs +139 -0
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +1961 -1
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateOperationResultTests.cs +116 -0
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +16 -1043
  60. data/helpers/lib/NuGetUpdater/global.json +1 -1
  61. metadata +21 -10
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +0 -375
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +0 -296
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.LockFile.cs +0 -251
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +0 -201
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +0 -3821
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +0 -2706
@@ -1,3 +1,4 @@
1
+ using System.Collections.Immutable;
1
2
  using System.Text.Json.Nodes;
2
3
 
3
4
  namespace NuGetUpdater.Core;
@@ -12,28 +13,19 @@ internal sealed class GlobalJsonBuildFile : JsonBuildFile
12
13
  {
13
14
  }
14
15
 
15
- public JsonObject? Sdk => Node.Value is JsonObject root ? root["sdk"]?.AsObject() : null;
16
-
17
16
  public JsonObject? MSBuildSdks => Node.Value is JsonObject root ? root["msbuild-sdks"]?.AsObject() : null;
18
17
 
19
18
  public IEnumerable<Dependency> GetDependencies()
20
19
  {
21
- List<Dependency> dependencies = [];
22
- if (Sdk is not null
23
- && Sdk.TryGetPropertyValue("version", out var version))
24
- {
25
- dependencies.Add(GetSdkDependency("Microsoft.NET.Sdk", version));
26
- }
27
-
28
20
  if (MSBuildSdks is null)
29
21
  {
30
- return dependencies;
22
+ return [];
31
23
  }
32
24
 
33
25
  var msBuildDependencies = MSBuildSdks
34
- .Select(t => GetSdkDependency(t.Key, t.Value));
35
- dependencies.AddRange(msBuildDependencies);
36
- return dependencies;
26
+ .Select(t => GetSdkDependency(t.Key, t.Value))
27
+ .ToImmutableArray();
28
+ return msBuildDependencies;
37
29
  }
38
30
 
39
31
  private Dependency GetSdkDependency(string name, JsonNode? version)
@@ -0,0 +1,12 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ internal class PrivateSourceTimedOutException : Exception
4
+ {
5
+ public string Url { get; }
6
+
7
+ public PrivateSourceTimedOutException(string url)
8
+ : base($"The request to source {url} has timed out.")
9
+ {
10
+ Url = url;
11
+ }
12
+ }
@@ -70,10 +70,14 @@ public abstract record JobErrorBase : MessageBase
70
70
 
71
71
  return new UnknownError(ex, jobId);
72
72
  }
73
+ case InvalidDataException invalidData when invalidData.Message == "Central Directory corrupt.":
74
+ return new PrivateSourceBadResponse(NuGetContext.GetPackageSourceUrls(currentDirectory));
73
75
  case InvalidProjectFileException invalidProjectFile:
74
76
  return new DependencyFileNotParseable(invalidProjectFile.ProjectFile);
75
77
  case MissingFileException missingFile:
76
78
  return new DependencyFileNotFound(missingFile.FilePath, missingFile.Message);
79
+ case PrivateSourceTimedOutException timeout:
80
+ return new PrivateSourceTimedOut(timeout.Url);
77
81
  case UnparseableFileException unparseableFile:
78
82
  return new DependencyFileNotParseable(unparseableFile.FilePath, unparseableFile.Message);
79
83
  case UpdateNotPossibleException updateNotPossible:
@@ -0,0 +1,10 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record PrivateSourceTimedOut : JobErrorBase
4
+ {
5
+ public PrivateSourceTimedOut(string url)
6
+ : base("private_source_timed_out")
7
+ {
8
+ Details["source"] = url;
9
+ }
10
+ }
@@ -126,7 +126,7 @@ public class PullRequestTextGenerator
126
126
 
127
127
  public static string GetPullRequestBody(Job job, ImmutableArray<UpdateOperationBase> updateOperationsPerformed, string? dependencyGroupName)
128
128
  {
129
- var report = UpdateOperationBase.GenerateUpdateOperationReport(updateOperationsPerformed);
129
+ var report = UpdateOperationBase.GenerateUpdateOperationReport(updateOperationsPerformed, includeFileNames: false);
130
130
  return report;
131
131
  }
132
132
  }
@@ -140,7 +140,7 @@ internal class CreateSecurityUpdatePullRequestHandler : IUpdateHandler
140
140
  updateOperationsPerformed.AddRange(patchedUpdateOperations);
141
141
  foreach (var o in patchedUpdateOperations)
142
142
  {
143
- logger.Info($"Update operation performed: {o.GetReport()}");
143
+ logger.Info($"Update operation performed: {o.GetReport(includeFileNames: true)}");
144
144
  }
145
145
  }
146
146
  }
@@ -143,7 +143,7 @@ internal class GroupUpdateAllVersionsHandler : IUpdateHandler
143
143
  updateOperationsPerformed.AddRange(patchedUpdateOperations);
144
144
  foreach (var o in patchedUpdateOperations)
145
145
  {
146
- logger.Info($"Update operation performed: {o.GetReport()}");
146
+ logger.Info($"Update operation performed: {o.GetReport(includeFileNames: true)}");
147
147
  }
148
148
  }
149
149
 
@@ -242,7 +242,7 @@ internal class GroupUpdateAllVersionsHandler : IUpdateHandler
242
242
  updateOperationsPerformed.AddRange(patchedUpdateOperations);
243
243
  foreach (var o in patchedUpdateOperations)
244
244
  {
245
- logger.Info($"Update operation performed: {o.GetReport()}");
245
+ logger.Info($"Update operation performed: {o.GetReport(includeFileNames: true)}");
246
246
  }
247
247
  }
248
248
 
@@ -137,7 +137,7 @@ internal class RefreshGroupUpdatePullRequestHandler : IUpdateHandler
137
137
  updateOperationsPerformed.AddRange(patchedUpdateOperations);
138
138
  foreach (var o in patchedUpdateOperations)
139
139
  {
140
- logger.Info($"Update operation performed: {o.GetReport()}");
140
+ logger.Info($"Update operation performed: {o.GetReport(includeFileNames: true)}");
141
141
  }
142
142
  }
143
143
  }
@@ -135,7 +135,7 @@ internal class RefreshSecurityUpdatePullRequestHandler : IUpdateHandler
135
135
  updateOperationsPerformed.AddRange(patchedUpdateOperations);
136
136
  foreach (var o in patchedUpdateOperations)
137
137
  {
138
- logger.Info($"Update operation performed: {o.GetReport()}");
138
+ logger.Info($"Update operation performed: {o.GetReport(includeFileNames: true)}");
139
139
  }
140
140
  }
141
141
  }
@@ -125,7 +125,7 @@ internal class RefreshVersionUpdatePullRequestHandler : IUpdateHandler
125
125
  updateOperationsPerformed.AddRange(patchedUpdateOperations);
126
126
  foreach (var o in patchedUpdateOperations)
127
127
  {
128
- logger.Info($"Update operation performed: {o.GetReport()}");
128
+ logger.Info($"Update operation performed: {o.GetReport(includeFileNames: true)}");
129
129
  }
130
130
  }
131
131
  }
@@ -2,7 +2,7 @@ namespace NuGetUpdater.Core;
2
2
 
3
3
  internal static class DotNetToolsJsonUpdater
4
4
  {
5
- public static async Task UpdateDependencyAsync(
5
+ public static async Task<string?> UpdateDependencyAsync(
6
6
  string repoRootPath,
7
7
  string workspacePath,
8
8
  string dependencyName,
@@ -13,7 +13,7 @@ internal static class DotNetToolsJsonUpdater
13
13
  if (!MSBuildHelper.TryGetDotNetToolsJsonPath(repoRootPath, workspacePath, out var dotnetToolsJsonPath))
14
14
  {
15
15
  logger.Info(" No dotnet-tools.json file found.");
16
- return;
16
+ return null;
17
17
  }
18
18
 
19
19
  var dotnetToolsJsonFile = DotNetToolsJsonBuildFile.Open(repoRootPath, dotnetToolsJsonPath, logger);
@@ -24,7 +24,7 @@ internal static class DotNetToolsJsonUpdater
24
24
  if (!containsDependency)
25
25
  {
26
26
  logger.Info($" Dependency [{dependencyName}] not found.");
27
- return;
27
+ return null;
28
28
  }
29
29
 
30
30
  var tool = dotnetToolsJsonFile.Tools
@@ -40,7 +40,10 @@ internal static class DotNetToolsJsonUpdater
40
40
  if (await dotnetToolsJsonFile.SaveAsync())
41
41
  {
42
42
  logger.Info($" Saved [{dotnetToolsJsonFile.RelativePath}].");
43
+ return dotnetToolsJsonFile.Path;
43
44
  }
44
45
  }
46
+
47
+ return null;
45
48
  }
46
49
  }
@@ -0,0 +1,376 @@
1
+ using System.Collections.Immutable;
2
+
3
+ using NuGet.Versioning;
4
+
5
+ using NuGetUpdater.Core.DependencySolver;
6
+ using NuGetUpdater.Core.Discover;
7
+ using NuGetUpdater.Core.Utilities;
8
+
9
+ namespace NuGetUpdater.Core.Updater.FileWriters;
10
+
11
+ public class FileWriterWorker
12
+ {
13
+ private readonly IDiscoveryWorker _discoveryWorker;
14
+ private readonly IDependencySolver _dependencySolver;
15
+ private readonly IFileWriter _fileWriter;
16
+ private readonly ILogger _logger;
17
+
18
+ public FileWriterWorker(IDiscoveryWorker discoveryWorker, IDependencySolver dependencySolver, IFileWriter fileWriter, ILogger logger)
19
+ {
20
+ _discoveryWorker = discoveryWorker;
21
+ _dependencySolver = dependencySolver;
22
+ _fileWriter = fileWriter;
23
+ _logger = logger;
24
+ }
25
+
26
+ public async Task<ImmutableArray<UpdateOperationBase>> RunAsync(
27
+ DirectoryInfo repoContentsPath,
28
+ FileInfo projectPath,
29
+ string dependencyName,
30
+ NuGetVersion oldDependencyVersion,
31
+ NuGetVersion newDependencyVersion
32
+ )
33
+ {
34
+ var updateOperations = new List<UpdateOperationBase>();
35
+ var initialProjectDirectory = new DirectoryInfo(Path.GetDirectoryName(projectPath.FullName)!);
36
+
37
+ // first try non-project updates
38
+ var nonProjectUpdates = await ProcessNonProjectUpdatesAsync(repoContentsPath, initialProjectDirectory, dependencyName, oldDependencyVersion, newDependencyVersion);
39
+ updateOperations.AddRange(nonProjectUpdates);
40
+
41
+ // then try packages.config updates
42
+ var packagesConfigUpdates = await ProcessPackagesConfigUpdatesAsync(repoContentsPath, projectPath, dependencyName, oldDependencyVersion, newDependencyVersion);
43
+ updateOperations.AddRange(packagesConfigUpdates);
44
+
45
+ // then try project updates
46
+ var packageReferenceUpdates = await ProcessPackageReferenceUpdatesAsync(repoContentsPath, initialProjectDirectory, projectPath, dependencyName, newDependencyVersion);
47
+ updateOperations.AddRange(packageReferenceUpdates);
48
+
49
+ var normalizedUpdateOperations = UpdateOperationBase.NormalizeUpdateOperationCollection(repoContentsPath.FullName, updateOperations);
50
+ return normalizedUpdateOperations;
51
+ }
52
+
53
+ private async Task<ImmutableArray<UpdateOperationBase>> ProcessNonProjectUpdatesAsync(
54
+ DirectoryInfo repoContentsPath,
55
+ DirectoryInfo initialProjectDirectory,
56
+ string dependencyName,
57
+ NuGetVersion oldDependencyVersion,
58
+ NuGetVersion newDependencyVersion
59
+ )
60
+ {
61
+ var updateOperations = new List<UpdateOperationBase>();
62
+ var updatedDotNetToolsPath = await DotNetToolsJsonUpdater.UpdateDependencyAsync(
63
+ repoContentsPath.FullName,
64
+ initialProjectDirectory.FullName,
65
+ dependencyName,
66
+ oldDependencyVersion.ToString(),
67
+ newDependencyVersion.ToString(),
68
+ _logger
69
+ );
70
+ if (updatedDotNetToolsPath is not null)
71
+ {
72
+ updateOperations.Add(new DirectUpdate()
73
+ {
74
+ DependencyName = dependencyName,
75
+ OldVersion = oldDependencyVersion,
76
+ NewVersion = newDependencyVersion,
77
+ UpdatedFiles = [Path.GetRelativePath(repoContentsPath.FullName, updatedDotNetToolsPath).FullyNormalizedRootedPath()]
78
+ });
79
+ }
80
+
81
+ var updatedGlobalJsonPath = await GlobalJsonUpdater.UpdateDependencyAsync(
82
+ repoContentsPath.FullName,
83
+ initialProjectDirectory.FullName,
84
+ dependencyName,
85
+ oldDependencyVersion.ToString(),
86
+ newDependencyVersion.ToString(),
87
+ _logger
88
+ );
89
+ if (updatedGlobalJsonPath is not null)
90
+ {
91
+ updateOperations.Add(new DirectUpdate()
92
+ {
93
+ DependencyName = dependencyName,
94
+ OldVersion = oldDependencyVersion,
95
+ NewVersion = newDependencyVersion,
96
+ UpdatedFiles = [Path.GetRelativePath(repoContentsPath.FullName, updatedGlobalJsonPath).FullyNormalizedRootedPath()]
97
+ });
98
+ }
99
+
100
+ return [.. updateOperations];
101
+ }
102
+
103
+ private async Task<ImmutableArray<UpdateOperationBase>> ProcessPackagesConfigUpdatesAsync(
104
+ DirectoryInfo repoContentsPath,
105
+ FileInfo projectPath,
106
+ string dependencyName,
107
+ NuGetVersion oldDependencyVersion,
108
+ NuGetVersion newDependencyVersion
109
+ )
110
+ {
111
+ var additionalFiles = ProjectHelper.GetAllAdditionalFilesFromProject(projectPath.FullName, ProjectHelper.PathFormat.Full);
112
+ var packagesConfigFullPath = additionalFiles.Where(p => Path.GetFileName(p).Equals(ProjectHelper.PackagesConfigFileName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
113
+ if (packagesConfigFullPath is null)
114
+ {
115
+ return [];
116
+ }
117
+
118
+ var packagesConfigOperations = await PackagesConfigUpdater.UpdateDependencyAsync(
119
+ repoContentsPath.FullName,
120
+ projectPath.FullName,
121
+ dependencyName,
122
+ oldDependencyVersion.ToString(),
123
+ newDependencyVersion.ToString(),
124
+ packagesConfigFullPath,
125
+ _logger
126
+ );
127
+ var packagesConfigOperationsWithNormalizedPaths = packagesConfigOperations
128
+ .Select(op => op with { UpdatedFiles = [.. op.UpdatedFiles.Select(f => Path.GetRelativePath(repoContentsPath.FullName, f).FullyNormalizedRootedPath())] })
129
+ .ToImmutableArray();
130
+ return packagesConfigOperationsWithNormalizedPaths;
131
+ }
132
+
133
+ private async Task<ImmutableArray<UpdateOperationBase>> ProcessPackageReferenceUpdatesAsync(
134
+ DirectoryInfo repoContentsPath,
135
+ DirectoryInfo initialProjectDirectory,
136
+ FileInfo projectPath,
137
+ string dependencyName,
138
+ NuGetVersion newDependencyVersion
139
+ )
140
+ {
141
+ var initialProjectDirectoryRelativeToRepoRoot = Path.GetRelativePath(repoContentsPath.FullName, initialProjectDirectory.FullName).FullyNormalizedRootedPath();
142
+ var initialDiscoveryResult = await _discoveryWorker.RunAsync(repoContentsPath.FullName, initialProjectDirectoryRelativeToRepoRoot);
143
+ var initialProjectDiscovery = initialDiscoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, projectPath);
144
+ if (initialProjectDiscovery is null)
145
+ {
146
+ _logger.Info($"Unable to find project discovery for project {projectPath}.");
147
+ return [];
148
+ }
149
+
150
+ var initialRequestedDependency = initialProjectDiscovery.Dependencies
151
+ .FirstOrDefault(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
152
+ if (initialRequestedDependency is null || initialRequestedDependency.Version is null)
153
+ {
154
+ _logger.Info($"Dependency {dependencyName} not found in initial project discovery.");
155
+ return [];
156
+ }
157
+
158
+ var initialDependencyVersion = NuGetVersion.Parse(initialRequestedDependency.Version);
159
+ if (initialDependencyVersion >= newDependencyVersion)
160
+ {
161
+ _logger.Info($"Dependency {dependencyName} is already at version {initialDependencyVersion}, no update needed.");
162
+ return [];
163
+ }
164
+
165
+ var initialTopLevelDependencies = initialProjectDiscovery.Dependencies
166
+ .Where(d => !d.IsTransitive)
167
+ .ToImmutableArray();
168
+ var newDependency = new Dependency(dependencyName, newDependencyVersion.ToString(), DependencyType.Unknown);
169
+ var desiredDependencies = initialTopLevelDependencies.Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))
170
+ ? initialTopLevelDependencies.Select(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase) ? newDependency : d).ToImmutableArray()
171
+ : initialTopLevelDependencies.Concat([newDependency]).ToImmutableArray();
172
+
173
+ var updateOperations = new List<UpdateOperationBase>();
174
+ foreach (var targetFramework in initialProjectDiscovery.TargetFrameworks)
175
+ {
176
+ var resolvedDependencies = await _dependencySolver.SolveAsync(initialTopLevelDependencies, desiredDependencies, targetFramework);
177
+ if (resolvedDependencies is null)
178
+ {
179
+ _logger.Warn($"Unable to solve dependency conflicts for target framework {targetFramework}.");
180
+ continue;
181
+ }
182
+
183
+ var resolvedRequestedDependency = resolvedDependencies.Value
184
+ .SingleOrDefault(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
185
+ if (resolvedRequestedDependency is null || resolvedRequestedDependency.Version is null)
186
+ {
187
+ _logger.Warn($"Dependency resolution failed to include {dependencyName}.");
188
+ continue;
189
+ }
190
+
191
+ var resolvedRequestedDependencyVersion = NuGetVersion.Parse(resolvedRequestedDependency.Version);
192
+ if (resolvedRequestedDependencyVersion != newDependencyVersion)
193
+ {
194
+ _logger.Warn($"Requested dependency resolution to include {dependencyName}/{newDependencyVersion} but it was instead resolved to {resolvedRequestedDependencyVersion}.");
195
+ continue;
196
+ }
197
+
198
+ // process all projects bottom up
199
+ var orderedProjectDiscovery = GetProjectDiscoveryEvaluationOrder(repoContentsPath, initialDiscoveryResult, projectPath, _logger);
200
+
201
+ // track original contents
202
+ var originalFileContents = await GetOriginalFileContentsAsync(repoContentsPath, initialProjectDirectory, orderedProjectDiscovery);
203
+
204
+ var allUpdatedFiles = new List<string>();
205
+ foreach (var projectDiscovery in orderedProjectDiscovery)
206
+ {
207
+ var projectFullPath = Path.Join(repoContentsPath.FullName, initialDiscoveryResult.Path, projectDiscovery.FilePath).FullyNormalizedRootedPath();
208
+ var updatedFiles = await TryPerformFileWritesAsync(_fileWriter, repoContentsPath, initialProjectDirectory, projectDiscovery, resolvedDependencies.Value);
209
+ allUpdatedFiles.AddRange(updatedFiles);
210
+ }
211
+
212
+ if (allUpdatedFiles.Count == 0)
213
+ {
214
+ _logger.Warn("Failed to write new dependency versions.");
215
+ await RestoreOriginalFileContentsAsync(originalFileContents);
216
+ continue;
217
+ }
218
+
219
+ // this final call to discover has the benefit of also updating the lock file if it exists
220
+ var finalDiscoveryResult = await _discoveryWorker.RunAsync(repoContentsPath.FullName, initialProjectDirectoryRelativeToRepoRoot);
221
+ var finalProjectDiscovery = finalDiscoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, projectPath);
222
+ if (finalProjectDiscovery is null)
223
+ {
224
+ _logger.Warn($"Unable to find final project discovery for project {projectPath}.");
225
+ await RestoreOriginalFileContentsAsync(originalFileContents);
226
+ continue;
227
+ }
228
+
229
+ var finalRequestedDependency = finalProjectDiscovery.Dependencies
230
+ .FirstOrDefault(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase));
231
+ if (finalRequestedDependency is null || finalRequestedDependency.Version is null)
232
+ {
233
+ _logger.Warn($"Dependency {dependencyName} not found in final project discovery.");
234
+ await RestoreOriginalFileContentsAsync(originalFileContents);
235
+ continue;
236
+ }
237
+
238
+ var resolvedVersion = NuGetVersion.Parse(finalRequestedDependency.Version);
239
+ if (resolvedVersion != newDependencyVersion)
240
+ {
241
+ _logger.Warn($"Final dependency version for {dependencyName} is {resolvedVersion}, expected {newDependencyVersion}.");
242
+ await RestoreOriginalFileContentsAsync(originalFileContents);
243
+ continue;
244
+ }
245
+
246
+ var computedUpdateOperations = await PackageReferenceUpdater.ComputeUpdateOperations(
247
+ repoContentsPath.FullName,
248
+ projectPath.FullName,
249
+ targetFramework,
250
+ initialTopLevelDependencies,
251
+ desiredDependencies,
252
+ resolvedDependencies.Value,
253
+ new ExperimentsManager() { UseDirectDiscovery = true },
254
+ _logger);
255
+ var filteredUpdateOperations = computedUpdateOperations
256
+ .Where(op =>
257
+ {
258
+ var initialDependency = initialProjectDiscovery.Dependencies.FirstOrDefault(d => d.Name.Equals(op.DependencyName, StringComparison.OrdinalIgnoreCase));
259
+ return initialDependency is not null
260
+ && initialDependency.Version is not null
261
+ && NuGetVersion.Parse(initialDependency.Version) < op.NewVersion;
262
+ })
263
+ .ToImmutableArray();
264
+ var computedOperationsWithUpdatedFiles = filteredUpdateOperations
265
+ .Select(op => op with { UpdatedFiles = [.. allUpdatedFiles] })
266
+ .ToImmutableArray();
267
+ updateOperations.AddRange(computedOperationsWithUpdatedFiles);
268
+ }
269
+
270
+ return [.. updateOperations];
271
+ }
272
+
273
+ internal static async Task<Dictionary<string, string>> GetOriginalFileContentsAsync(DirectoryInfo repoContentsPath, DirectoryInfo initialStartingDirectory, IEnumerable<ProjectDiscoveryResult> projectDiscoveryResults)
274
+ {
275
+ var filesAndContents = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
276
+ foreach (var projectDiscoveryResult in projectDiscoveryResults)
277
+ {
278
+ var fullProjectPath = Path.Join(initialStartingDirectory.FullName, projectDiscoveryResult.FilePath).FullyNormalizedRootedPath();
279
+ var projectContents = await File.ReadAllTextAsync(fullProjectPath);
280
+ filesAndContents[fullProjectPath] = projectContents;
281
+
282
+ foreach (var file in projectDiscoveryResult.ImportedFiles.Concat(projectDiscoveryResult.AdditionalFiles))
283
+ {
284
+ var filePath = Path.Join(Path.GetDirectoryName(fullProjectPath)!, file).FullyNormalizedRootedPath();
285
+ var fileContents = await File.ReadAllTextAsync(filePath);
286
+ filesAndContents[filePath] = fileContents;
287
+ }
288
+ }
289
+
290
+ return filesAndContents;
291
+ }
292
+
293
+ internal static async Task RestoreOriginalFileContentsAsync(Dictionary<string, string> originalFilesAndContents)
294
+ {
295
+ foreach (var (path, contents) in originalFilesAndContents)
296
+ {
297
+ await File.WriteAllTextAsync(path, contents);
298
+ }
299
+ }
300
+
301
+ internal static ImmutableArray<ProjectDiscoveryResult> GetProjectDiscoveryEvaluationOrder(DirectoryInfo repoContentsPath, WorkspaceDiscoveryResult discoveryResult, FileInfo projectPath, ILogger logger)
302
+ {
303
+ var visitedProjectPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
304
+ var projectsToProcess = new Queue<ProjectDiscoveryResult>();
305
+ var startingProjectDiscovery = discoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, projectPath);
306
+ if (startingProjectDiscovery is null)
307
+ {
308
+ logger.Warn($"Unable to find project discovery for project {projectPath.FullName} in discovery result.");
309
+ return [];
310
+ }
311
+
312
+ projectsToProcess.Enqueue(startingProjectDiscovery);
313
+
314
+ var reversedProjectDiscoveryOrder = new List<ProjectDiscoveryResult>();
315
+ while (projectsToProcess.TryDequeue(out var projectDiscovery))
316
+ {
317
+ var projectFullPath = Path.Join(repoContentsPath.FullName, discoveryResult.Path, projectDiscovery.FilePath).FullyNormalizedRootedPath();
318
+ if (visitedProjectPaths.Add(projectFullPath))
319
+ {
320
+ reversedProjectDiscoveryOrder.Add(projectDiscovery);
321
+ foreach (var referencedProjectPath in projectDiscovery.ReferencedProjectPaths)
322
+ {
323
+ var referencedProjectFullPath = Path.Join(repoContentsPath.FullName, discoveryResult.Path, referencedProjectPath).FullyNormalizedRootedPath();
324
+ var referencedProjectDiscovery = discoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, new FileInfo(referencedProjectFullPath));
325
+ if (referencedProjectDiscovery is not null)
326
+ {
327
+ projectsToProcess.Enqueue(referencedProjectDiscovery);
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ var projectDiscoveryOrder = ((IEnumerable<ProjectDiscoveryResult>)reversedProjectDiscoveryOrder)
334
+ .Reverse()
335
+ .ToImmutableArray();
336
+ return projectDiscoveryOrder;
337
+ }
338
+
339
+ internal static async Task<ImmutableArray<string>> TryPerformFileWritesAsync(
340
+ IFileWriter fileWriter,
341
+ DirectoryInfo repoContentsPath,
342
+ DirectoryInfo originalDiscoveryDirectory,
343
+ ProjectDiscoveryResult projectDiscovery,
344
+ ImmutableArray<Dependency> requiredPackageVersions
345
+ )
346
+ {
347
+ var originalFileContents = await GetOriginalFileContentsAsync(repoContentsPath, originalDiscoveryDirectory, [projectDiscovery]);
348
+ var relativeFilePaths = originalFileContents.Keys
349
+ .Select(p => Path.GetRelativePath(repoContentsPath.FullName, p).FullyNormalizedRootedPath())
350
+ .ToImmutableArray();
351
+
352
+ // try update
353
+ var addPackageReferenceElementForPinnedPackages = !projectDiscovery.CentralPackageTransitivePinningEnabled;
354
+ var success = await fileWriter.UpdatePackageVersionsAsync(repoContentsPath, relativeFilePaths, projectDiscovery.Dependencies, requiredPackageVersions, addPackageReferenceElementForPinnedPackages);
355
+ var updatedFiles = new List<string>();
356
+ foreach (var (filePath, originalContents) in originalFileContents)
357
+ {
358
+ var currentContents = await File.ReadAllTextAsync(filePath);
359
+ var currentContentsNormalized = currentContents.Replace("\r", "");
360
+ var originalContentsNormalized = originalContents.Replace("\r", "");
361
+ if (currentContentsNormalized != originalContentsNormalized)
362
+ {
363
+ var relativeUpdatedPath = Path.GetRelativePath(repoContentsPath.FullName, filePath).FullyNormalizedRootedPath();
364
+ updatedFiles.Add(relativeUpdatedPath);
365
+ }
366
+ }
367
+
368
+ if (!success)
369
+ {
370
+ await RestoreOriginalFileContentsAsync(originalFileContents);
371
+ }
372
+
373
+ var sortedUpdatedFiles = updatedFiles.OrderBy(p => p, StringComparer.Ordinal).ToImmutableArray();
374
+ return sortedUpdatedFiles;
375
+ }
376
+ }
@@ -0,0 +1,14 @@
1
+ using System.Collections.Immutable;
2
+
3
+ namespace NuGetUpdater.Core.Updater.FileWriters;
4
+
5
+ public interface IFileWriter
6
+ {
7
+ Task<bool> UpdatePackageVersionsAsync(
8
+ DirectoryInfo repoContentsPath,
9
+ ImmutableArray<string> relativeFilePaths,
10
+ ImmutableArray<Dependency> originalDependencies,
11
+ ImmutableArray<Dependency> requiredPackageVersions,
12
+ bool addPackageReferenceElementForPinnedPackages
13
+ );
14
+ }