dependabot-nuget 0.268.0 → 0.270.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,689 @@
1
+ using System.Collections.Immutable;
2
+ using System.Text;
3
+ using System.Text.RegularExpressions;
4
+
5
+ using NuGet.Common;
6
+ using NuGet.Configuration;
7
+ using NuGet.Frameworks;
8
+ using NuGet.Packaging.Core;
9
+ using NuGet.Protocol;
10
+ using NuGet.Protocol.Core.Types;
11
+ using NuGet.Versioning;
12
+
13
+ using NuGetUpdater.Core.Analyze;
14
+
15
+ using Task = System.Threading.Tasks.Task;
16
+
17
+ namespace NuGetUpdater.Core;
18
+
19
+ // Data type to store information of a given package
20
+ public class PackageToUpdate
21
+ {
22
+ public string PackageName { get; set; }
23
+ public string CurrentVersion { get; set; }
24
+ public string NewVersion { get; set; }
25
+
26
+ // Second version in case there's a "bounds" on the package version
27
+ public string SecondVersion { get; set; }
28
+
29
+ // Bool to determine if a package has to be a specific version
30
+ public bool IsSpecific { get; set; }
31
+ }
32
+
33
+ public class PackageManager
34
+ {
35
+ // Dictionaries to store the relationships of a package (dependencies and parents)
36
+ private readonly Dictionary<PackageToUpdate, HashSet<PackageToUpdate>> packageDependencies = new Dictionary<PackageToUpdate, HashSet<PackageToUpdate>>();
37
+ private readonly Dictionary<PackageToUpdate, HashSet<PackageToUpdate>> reverseDependencies = new Dictionary<PackageToUpdate, HashSet<PackageToUpdate>>();
38
+
39
+ // Path of the repository
40
+ private readonly string repoRoot;
41
+
42
+ // Path to the project within the repository
43
+ private readonly string projectPath;
44
+
45
+ public PackageManager(string repoRoot, string projectPath)
46
+ {
47
+ this.repoRoot = repoRoot;
48
+ this.projectPath = projectPath;
49
+ }
50
+
51
+ // Method alterted from VersionFinder.cs to find the metadata of a given package
52
+ private async Task<IPackageSearchMetadata?> FindPackageMetadataAsync(PackageIdentity packageIdentity, CancellationToken cancellationToken)
53
+ {
54
+ string? currentDirectory = Path.GetDirectoryName(projectPath);
55
+ string CurrentDirectory = currentDirectory ?? Environment.CurrentDirectory;
56
+ SourceCacheContext SourceCacheContext = new SourceCacheContext();
57
+ PackageDownloadContext PackageDownloadContext = new PackageDownloadContext(SourceCacheContext);
58
+ ILogger Logger = NullLogger.Instance;
59
+
60
+ IMachineWideSettings MachineWideSettings = new NuGet.CommandLine.CommandLineMachineWideSettings();
61
+ ISettings Settings = NuGet.Configuration.Settings.LoadDefaultSettings(
62
+ CurrentDirectory,
63
+ configFileName: null,
64
+ MachineWideSettings);
65
+
66
+ var globalPackagesFolder = SettingsUtility.GetGlobalPackagesFolder(Settings);
67
+ var sourceMapping = PackageSourceMapping.GetPackageSourceMapping(Settings);
68
+ var packageSources = sourceMapping.GetConfiguredPackageSources(packageIdentity.Id).ToHashSet();
69
+ var sourceProvider = new PackageSourceProvider(Settings);
70
+
71
+ ImmutableArray<PackageSource> PackageSources = sourceProvider.LoadPackageSources()
72
+ .Where(p => p.IsEnabled)
73
+ .ToImmutableArray();
74
+
75
+ var sources = packageSources.Count == 0
76
+ ? PackageSources
77
+ : PackageSources
78
+ .Where(p => packageSources.Contains(p.Name))
79
+ .ToImmutableArray();
80
+
81
+ var message = new StringBuilder();
82
+ message.AppendLine($"finding info url for {packageIdentity}, using package sources: {string.Join(", ", sources.Select(s => s.Name))}");
83
+
84
+ foreach (var source in sources)
85
+ {
86
+ message.AppendLine($" checking {source.Name}");
87
+ var sourceRepository = Repository.Factory.GetCoreV3(source);
88
+ var feed = await sourceRepository.GetResourceAsync<MetadataResource>(cancellationToken);
89
+ if (feed is null)
90
+ {
91
+ message.AppendLine($" feed for {source.Name} was null");
92
+ continue;
93
+ }
94
+
95
+ try
96
+ {
97
+ var existsInFeed = await feed.Exists(
98
+ packageIdentity,
99
+ includeUnlisted: false,
100
+ SourceCacheContext,
101
+ NullLogger.Instance,
102
+ cancellationToken);
103
+
104
+ if (!existsInFeed)
105
+ {
106
+ message.AppendLine($" package {packageIdentity} does not exist in {source.Name}");
107
+ continue;
108
+ }
109
+ }
110
+ catch (FatalProtocolException)
111
+ {
112
+ // if anything goes wrong here, the package source obviously doesn't contain the requested package
113
+ continue;
114
+ }
115
+
116
+ var metadataResource = await sourceRepository.GetResourceAsync<PackageMetadataResource>(cancellationToken);
117
+ var metadata = await metadataResource.GetMetadataAsync(packageIdentity, SourceCacheContext, Logger, cancellationToken);
118
+ return metadata;
119
+ }
120
+
121
+ return null;
122
+ }
123
+
124
+ // Method to find the best match framework of a given package's target framework availability
125
+ public static NuGetFramework FindBestMatchFramework(IEnumerable<NuGet.Packaging.PackageDependencyGroup> dependencySet, string targetFrameworkString)
126
+ {
127
+ // Parse the given target framework string into a NuGetFramework object
128
+ var targetFramework = NuGetFramework.ParseFolder(targetFrameworkString);
129
+ var frameworkReducer = new FrameworkReducer();
130
+
131
+ // Collect all target frameworks from the dependency set
132
+ var availableFrameworks = dependencySet.Select(dg => dg.TargetFramework).ToList();
133
+
134
+ // Return bestmatch framework
135
+ return frameworkReducer.GetNearest(targetFramework, availableFrameworks);
136
+ }
137
+
138
+ // Method to get the dependencies of a package
139
+ public async Task<List<PackageToUpdate>> GetDependenciesAsync(PackageToUpdate package, string targetFramework, string projectDirectory)
140
+ {
141
+ if (!NuGetVersion.TryParse(package.NewVersion, out var otherVersion))
142
+ {
143
+ return null;
144
+ }
145
+
146
+ // Create a package identity to use for obtaining the metadata url
147
+ PackageIdentity packageIdentity = new PackageIdentity(package.PackageName, otherVersion);
148
+
149
+ bool specific = false;
150
+
151
+ List<PackageToUpdate> dependencyList = new List<PackageToUpdate>();
152
+
153
+ try
154
+ {
155
+ // Fetch package metadata URL
156
+ var metadataUrl = await FindPackageMetadataAsync(packageIdentity, CancellationToken.None);
157
+ IEnumerable<NuGet.Packaging.PackageDependencyGroup> dependencySet = metadataUrl?.DependencySets ?? [];
158
+
159
+ // Get the bestMatchFramework based off the dependencies
160
+ var bestMatchFramework = FindBestMatchFramework(dependencySet, targetFramework);
161
+
162
+ if (bestMatchFramework != null)
163
+ {
164
+ // Process the best match framework
165
+ var bestMatchGroup = dependencySet.First(dg => dg.TargetFramework == bestMatchFramework);
166
+
167
+ foreach (var packageDependency in bestMatchGroup.Packages)
168
+ {
169
+ string version = packageDependency.VersionRange.OriginalString;
170
+ string firstVersion = null;
171
+ string SecondVersion = null;
172
+
173
+ // Conditions to check if the version has bounds specified
174
+ if (version.StartsWith("[") && version.EndsWith("]"))
175
+ {
176
+ version = version.Trim('[', ']');
177
+ var versions = version.Split(',');
178
+ version = versions.FirstOrDefault().Trim();
179
+ if (versions.Length > 1)
180
+ {
181
+ SecondVersion = versions.LastOrDefault()?.Trim();
182
+ }
183
+ specific = true;
184
+ }
185
+ else if (version.StartsWith("[") && version.EndsWith(")"))
186
+ {
187
+ version = version.Trim('[', ')');
188
+ var versions = version.Split(',');
189
+ version = versions.FirstOrDefault().Trim();
190
+ if (versions.Length > 1)
191
+ {
192
+ SecondVersion = versions.LastOrDefault()?.Trim();
193
+ }
194
+ }
195
+ else if (version.StartsWith("(") && version.EndsWith("]"))
196
+ {
197
+ version = version.Trim('(', ']');
198
+ var versions = version.Split(',');
199
+ version = versions.FirstOrDefault().Trim();
200
+ if (versions.Length > 1)
201
+ {
202
+ SecondVersion = versions.LastOrDefault()?.Trim();
203
+ }
204
+ }
205
+ else if (version.StartsWith("(") && version.EndsWith(")"))
206
+ {
207
+ version = version.Trim('(', ')');
208
+ var versions = version.Split(',');
209
+ version = versions.FirstOrDefault().Trim();
210
+ if (versions.Length > 1)
211
+ {
212
+ SecondVersion = versions.LastOrDefault()?.Trim();
213
+ }
214
+ }
215
+
216
+ // Store the dependency data to later add to the dependencyList
217
+ PackageToUpdate dependencyPackage = new PackageToUpdate
218
+ {
219
+ PackageName = packageDependency.Id,
220
+ CurrentVersion = version,
221
+ };
222
+
223
+ if (specific == true)
224
+ {
225
+ dependencyPackage.IsSpecific = true;
226
+ }
227
+
228
+ if (SecondVersion != null)
229
+ {
230
+ dependencyPackage.SecondVersion = SecondVersion;
231
+ }
232
+
233
+ dependencyList.Add(dependencyPackage);
234
+ }
235
+ }
236
+ else
237
+ {
238
+ Console.WriteLine("No compatible framework found.");
239
+ }
240
+ }
241
+ catch (HttpRequestException ex)
242
+ {
243
+ Console.WriteLine($"HTTP error occurred: {ex.Message}");
244
+ }
245
+ catch (ArgumentNullException ex)
246
+ {
247
+ Console.WriteLine($"Argument is null error: {ex.ParamName}, {ex.Message}");
248
+ }
249
+ catch (InvalidOperationException ex)
250
+ {
251
+ Console.WriteLine($"Invalid operation exception: {ex.Message}");
252
+ }
253
+ catch (Exception ex)
254
+ {
255
+ Console.WriteLine($"An error occurred: {ex.Message}");
256
+ }
257
+
258
+ return dependencyList;
259
+ }
260
+
261
+ // Method AddDependency to create the relationships between a parent and child
262
+ private void AddDependency(PackageToUpdate parent, PackageToUpdate child)
263
+ {
264
+ if (!packageDependencies.ContainsKey(parent))
265
+ {
266
+ packageDependencies[parent] = new HashSet<PackageToUpdate>();
267
+ }
268
+ else if (packageDependencies[parent].Contains(child))
269
+ {
270
+ // Remove the old child dependency if it exists
271
+ packageDependencies[parent].Remove(child);
272
+ }
273
+
274
+ packageDependencies[parent].Add(child);
275
+
276
+ if (!reverseDependencies.ContainsKey(child))
277
+ {
278
+ reverseDependencies[child] = new HashSet<PackageToUpdate>();
279
+ }
280
+ else if (reverseDependencies[child].Contains(parent))
281
+ {
282
+ // Remove the old parent dependency if it exists
283
+ reverseDependencies[child].Remove(parent);
284
+ }
285
+
286
+ reverseDependencies[child].Add(parent);
287
+ }
288
+
289
+ // Method to get the dependencies of a package and add them as a dependency
290
+ public async Task PopulatePackageDependenciesAsync(List<PackageToUpdate> packages, string targetFramework, string projectDirectory)
291
+ {
292
+ // Loop through each package and get their dependencies
293
+ foreach (PackageToUpdate package in packages)
294
+ {
295
+ List<PackageToUpdate> dependencies = await GetDependenciesAsync(package, targetFramework, projectDirectory);
296
+
297
+ if (dependencies == null)
298
+ {
299
+ continue;
300
+ }
301
+
302
+ // Add each dependency based off if it exists or not
303
+ foreach (PackageToUpdate dependency in dependencies)
304
+ {
305
+ PackageToUpdate checkInExisting = packages.FirstOrDefault(p => string.Compare(p.PackageName, dependency.PackageName, StringComparison.OrdinalIgnoreCase) == 0);
306
+ if (checkInExisting != null)
307
+ {
308
+ checkInExisting.IsSpecific = dependency.IsSpecific;
309
+ AddDependency(package, checkInExisting);
310
+ }
311
+ else
312
+ {
313
+ AddDependency(package, dependency);
314
+ }
315
+ }
316
+ }
317
+ }
318
+
319
+ // Method to get the parent packages of a given package
320
+ public HashSet<PackageToUpdate> GetParentPackages(PackageToUpdate package)
321
+ {
322
+ if (reverseDependencies.TryGetValue(package, out var parents))
323
+ {
324
+ return parents;
325
+ }
326
+
327
+ return new HashSet<PackageToUpdate>();
328
+ }
329
+
330
+ // Method to update the version of a desired package based off framework
331
+ public async Task<string> UpdateVersion(List<PackageToUpdate> existingPackages, PackageToUpdate package, string targetFramework, string projectDirectory)
332
+ {
333
+ // Bool to track if the package was in the original existing list
334
+ bool inExisting = true;
335
+
336
+ // If there is no new version to update or if the current version isn't updated
337
+ if (package.NewVersion == null)
338
+ {
339
+ return "No new version";
340
+ }
341
+
342
+ // If the package is already updated or needs to be updated
343
+ if (package.CurrentVersion != null)
344
+ {
345
+ if (package.CurrentVersion == package.NewVersion)
346
+ {
347
+ return "Already updated to new version";
348
+ }
349
+ }
350
+ // Place the current version as the new version for updating purposes
351
+ else
352
+ {
353
+ package.CurrentVersion = package.NewVersion;
354
+ inExisting = false;
355
+ }
356
+
357
+ try
358
+ {
359
+ NuGetVersion CurrentVersion = new NuGetVersion(package.CurrentVersion);
360
+ NuGetVersion newerVersion = new NuGetVersion(package.NewVersion);
361
+
362
+ // If the CurrentVersion is less than or equal to the newerVersion, proceed with the update
363
+ if (CurrentVersion <= newerVersion)
364
+ {
365
+ string currentVersiontemp = package.CurrentVersion;
366
+ package.CurrentVersion = package.NewVersion;
367
+
368
+ // Check if the current package has dependencies
369
+ List<PackageToUpdate> dependencyList = await GetDependenciesAsync(package, targetFramework, projectDirectory);
370
+
371
+ // If there are dependencies
372
+ if (dependencyList != null)
373
+ {
374
+ foreach (PackageToUpdate dependency in dependencyList)
375
+ {
376
+ // Check if the dependency is in the existing packages
377
+ foreach (PackageToUpdate existingPackage in existingPackages)
378
+ {
379
+ // If you find the dependency
380
+ if (string.Equals(dependency.PackageName, existingPackage.PackageName, StringComparison.OrdinalIgnoreCase))
381
+ {
382
+ NuGetVersion existingCurrentVersion = new NuGetVersion(existingPackage.CurrentVersion);
383
+ NuGetVersion dependencyCurrentVersion = new NuGetVersion(dependency.CurrentVersion);
384
+
385
+ // Check if the existing version is less than the dependency's existing version
386
+ if (existingCurrentVersion < dependencyCurrentVersion)
387
+ {
388
+ // Create temporary copy of the current version and of the existing package
389
+ string dependencyOldVersion = existingPackage.CurrentVersion;
390
+
391
+ // Susbtitute the current version of the existingPackage with the dependency current version
392
+ existingPackage.CurrentVersion = dependency.CurrentVersion;
393
+
394
+ // If the family is compatible with the dependency's version, update with the dependency version
395
+ if (await AreAllParentsCompatibleAsync(existingPackages, existingPackage, targetFramework, projectDirectory) == true)
396
+ {
397
+ existingPackage.CurrentVersion = dependencyOldVersion;
398
+ string NewVersion = dependency.CurrentVersion;
399
+ existingPackage.NewVersion = dependency.CurrentVersion;
400
+ await UpdateVersion(existingPackages, existingPackage, targetFramework, projectDirectory);
401
+ }
402
+ // If not, resort to putting version back to normal and remove new version
403
+ else
404
+ {
405
+ existingPackage.CurrentVersion = dependencyOldVersion;
406
+ package.CurrentVersion = currentVersiontemp;
407
+ package.NewVersion = package.CurrentVersion;
408
+ return "Out of scope";
409
+ }
410
+ }
411
+ }
412
+ }
413
+
414
+ // If the dependency has brackets or parenthesis, it's a specific version
415
+ if (dependency.CurrentVersion.Contains('[') || dependency.CurrentVersion.Contains(']') || dependency.CurrentVersion.Contains('{') || dependency.CurrentVersion.Contains('}'))
416
+ {
417
+ dependency.IsSpecific = true;
418
+ }
419
+
420
+ await UpdateVersion(existingPackages, dependency, targetFramework, projectDirectory);
421
+ }
422
+ }
423
+
424
+ // Get the parent packages of the package and check the compatibility between its family
425
+ HashSet<PackageToUpdate> parentPackages = GetParentPackages(package);
426
+
427
+ foreach (PackageToUpdate parent in parentPackages)
428
+ {
429
+ bool isCompatible = await IsCompatibleAsync(parent, package, targetFramework, projectDirectory);
430
+
431
+ // If the parent and package are not compatible
432
+ if (!isCompatible)
433
+ {
434
+ // Attempt to find and update to a compatible version between the two
435
+ NuGetVersion compatibleVersion = await FindCompatibleVersionAsync(existingPackages, parent, package, targetFramework);
436
+ if (compatibleVersion == null)
437
+ {
438
+ return "Failed to update";
439
+ }
440
+
441
+ // If a version is found, update to that version
442
+ parent.NewVersion = compatibleVersion.ToString();
443
+ await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory);
444
+ }
445
+
446
+ // If it's compatible and the package you updated wasn't in the existing package, check if the parent's dependencies version is the same as the current version
447
+ else if (isCompatible == true && inExisting == false)
448
+ {
449
+ List<PackageToUpdate> dependencyListParent = await GetDependenciesAsync(parent, targetFramework, projectDirectory);
450
+
451
+ PackageToUpdate parentDependency = dependencyListParent.FirstOrDefault(p => string.Compare(p.PackageName, package.PackageName, StringComparison.OrdinalIgnoreCase) == 0);
452
+
453
+ // If the parent's dependency current version is not the same as the current version of the package
454
+ if (parentDependency.CurrentVersion != package.CurrentVersion)
455
+ {
456
+ // Create a NugetContext instance to get the latest versions of the parent
457
+ NuGetContext nugetContext = new NuGetContext(Path.GetDirectoryName(projectPath));
458
+ Logger logger = null;
459
+
460
+ string currentVersionString = parent.CurrentVersion;
461
+ NuGetVersion currentVersionParent = NuGetVersion.Parse(currentVersionString);
462
+
463
+ var result = await VersionFinder.GetVersionsAsync(parent.PackageName, currentVersionParent, nugetContext, logger, CancellationToken.None);
464
+ var versions = result.GetVersions();
465
+ NuGetVersion latestVersion = versions.Where(v => !v.IsPrerelease).Max();
466
+
467
+ // Loop from the current version to the latest version, use next patch as a limit (unless there's a limit) so it doesn't look for versions that don't exist
468
+ for (NuGetVersion version = currentVersionParent; version <= latestVersion; version = NextPatch(version, versions))
469
+ {
470
+ NuGetVersion nextPatch = NextPatch(version, versions);
471
+
472
+ // If the next patch is the same as the currentVersioon, then the update is a Success
473
+ if (nextPatch == version)
474
+ {
475
+ return "Success";
476
+ }
477
+
478
+ string parentVersion = version.ToString();
479
+ parent.NewVersion = parentVersion;
480
+
481
+ // Check if the parent needs to be updated since the child isn't in the existing package list and the parent can update to a newer version to remove the dependency
482
+ List<PackageToUpdate> dependencyListParentTemp = await GetDependenciesAsync(parent, targetFramework, projectDirectory);
483
+ PackageToUpdate parentDependencyTemp = dependencyListParentTemp.FirstOrDefault(p => string.Compare(p.PackageName, package.PackageName, StringComparison.OrdinalIgnoreCase) == 0);
484
+
485
+ // If the newer package version of the parent has the same version as the parent's previous dependency, update
486
+ if (parentDependencyTemp.CurrentVersion == package.CurrentVersion)
487
+ {
488
+ parent.NewVersion = parentVersion;
489
+ parent.CurrentVersion = null;
490
+ await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory);
491
+ package.IsSpecific = true;
492
+ return "Success";
493
+ }
494
+ }
495
+ parent.CurrentVersion = currentVersionString;
496
+ }
497
+ }
498
+ }
499
+ }
500
+
501
+ else
502
+ {
503
+ Console.WriteLine("Current version is >= latest version");
504
+ }
505
+ }
506
+ catch
507
+ {
508
+ return "Failed to update";
509
+ }
510
+
511
+ return "Success";
512
+ }
513
+
514
+ // Method to determine if a parent and child are compatible with their versions
515
+ public async Task<bool> IsCompatibleAsync(PackageToUpdate parent, PackageToUpdate child, string targetFramework, string projectDirectory)
516
+ {
517
+ // Get the dependencies of the parent
518
+ List<PackageToUpdate> dependencies = await GetDependenciesAsync(parent, targetFramework, projectDirectory);
519
+
520
+ foreach (PackageToUpdate dependency in dependencies)
521
+ {
522
+
523
+ // If the child is present
524
+ if (string.Equals(dependency.PackageName, child.PackageName, StringComparison.OrdinalIgnoreCase))
525
+ {
526
+ NuGetVersion dependencyVersion = new NuGetVersion(dependency.CurrentVersion);
527
+ NuGetVersion childVersion = new NuGetVersion(child.CurrentVersion);
528
+
529
+ // If the dependency version of the parent and the childversion is the same, or if the child version can be >=
530
+ if (dependencyVersion == childVersion || (childVersion > dependencyVersion && dependency.IsSpecific != true))
531
+ {
532
+ return true;
533
+ }
534
+ else
535
+ {
536
+ return false;
537
+ }
538
+ }
539
+ }
540
+
541
+ return false;
542
+ }
543
+
544
+ // Method to update a version to the next available version for a package
545
+ public NuGetVersion NextPatch(NuGetVersion version, IEnumerable<NuGetVersion> allVersions)
546
+ {
547
+ var versions = allVersions.Where(v => v > version);
548
+
549
+ if (!versions.Any())
550
+ {
551
+ // If there are no greater versions, return current version
552
+ return version;
553
+ }
554
+
555
+ // Find smallest version in the versions
556
+ return versions.Min();
557
+ }
558
+
559
+ // Method to find a compatible version with the child for the parent to update to
560
+ public async Task<NuGetVersion> FindCompatibleVersionAsync(List<PackageToUpdate> existingPackages, PackageToUpdate possibleParent, PackageToUpdate possibleDependency, string targetFramework)
561
+ {
562
+ string packageId = possibleParent.PackageName;
563
+ string currentVersionString = possibleParent.CurrentVersion;
564
+ NuGetVersion CurrentVersion = NuGetVersion.Parse(currentVersionString);
565
+ string currentVersionStringDependency = possibleDependency.CurrentVersion;
566
+ NuGetVersion currentVersionDependency = NuGetVersion.Parse(currentVersionStringDependency);
567
+
568
+ // Create a NugetContext instance to get the latest versions of the parent
569
+ NuGetContext nugetContext = new NuGetContext(Path.GetDirectoryName(projectPath));
570
+ Logger logger = null;
571
+
572
+ var result = await VersionFinder.GetVersionsAsync(possibleParent.PackageName, CurrentVersion, nugetContext, logger, CancellationToken.None);
573
+ var versions = result.GetVersions();
574
+
575
+ // If there are no versions
576
+ if (versions.Length == 0)
577
+ {
578
+ return null;
579
+ }
580
+
581
+ NuGetVersion latestVersion = versions
582
+ .Where(v => !v.IsPrerelease)
583
+ .Max();
584
+
585
+ // If there's a version bounds that the parent has
586
+ if (possibleParent.SecondVersion != null)
587
+ {
588
+ NuGetVersion SecondVersion = NuGetVersion.Parse(possibleParent.SecondVersion);
589
+ latestVersion = SecondVersion;
590
+ }
591
+
592
+ // If there is no later version
593
+ if (CurrentVersion == latestVersion)
594
+ {
595
+ return null;
596
+ }
597
+
598
+ // If the current version of the parent is less than the current version of the dependency
599
+ else if (CurrentVersion < currentVersionDependency)
600
+ {
601
+ return currentVersionDependency;
602
+ }
603
+
604
+ // Loop from the current version to the latest version, use next patch as a limit (unless there's a limit) so it doesn't look for versions that don't exist
605
+ for (NuGetVersion version = CurrentVersion; version <= latestVersion; version = NextPatch(version, versions))
606
+ {
607
+ possibleParent.NewVersion = version.ToString();
608
+
609
+ NuGetVersion nextPatch = NextPatch(version, versions);
610
+
611
+ // If the next patch is the same as the CurrentVersion, then nothing is needed
612
+ if (nextPatch == version)
613
+ {
614
+ return nextPatch;
615
+ }
616
+
617
+ // Check if there's compatibility with parent and dependency
618
+ if (await IsCompatibleAsync(possibleParent, possibleDependency, targetFramework, nugetContext.CurrentDirectory))
619
+ {
620
+ // Check if parents are compatible, recursively
621
+ if (await AreAllParentsCompatibleAsync(existingPackages, possibleParent, targetFramework, nugetContext.CurrentDirectory))
622
+ {
623
+ // If compatible, return the new version
624
+ if (Regex.IsMatch(possibleParent.NewVersion, @"[a-zA-Z]"))
625
+ {
626
+ possibleParent.IsSpecific = true;
627
+ }
628
+ return version;
629
+ }
630
+ }
631
+ }
632
+
633
+ // If no compatible version is found, return null
634
+ return null;
635
+ }
636
+
637
+ // Method to determine if all the parents of a given package are compatible with the parent's desired version
638
+ public async Task<bool> AreAllParentsCompatibleAsync(List<PackageToUpdate> existingPackages, PackageToUpdate possibleParent, string targetFramework, string projectDirectory)
639
+ {
640
+ // Get the possibleParent parentPackages
641
+ HashSet<PackageToUpdate> parentPackages = GetParentPackages(possibleParent);
642
+
643
+ foreach (PackageToUpdate parent in parentPackages)
644
+ {
645
+ // Check compatibility between the possibleParent and current parent
646
+ bool isCompatible = await IsCompatibleAsync(parent, possibleParent, targetFramework, projectDirectory);
647
+
648
+ // If the possibleParent and parent are not compatible
649
+ if (!isCompatible)
650
+ {
651
+ // Find a compatible version if possible
652
+ NuGetVersion compatibleVersion = await FindCompatibleVersionAsync(existingPackages, parent, possibleParent, targetFramework);
653
+ if (compatibleVersion == null)
654
+ {
655
+ return false;
656
+ }
657
+
658
+ parent.NewVersion = compatibleVersion.ToString();
659
+ await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory);
660
+ }
661
+
662
+ // Recursively check if all ancestors are compatible
663
+ if (!await AreAllParentsCompatibleAsync(existingPackages, parent, targetFramework, projectDirectory))
664
+ {
665
+ return false;
666
+ }
667
+ }
668
+
669
+ return true;
670
+ }
671
+
672
+ // Method to update the existing packages with new version of the desired packages to update
673
+ public void UpdateExistingPackagesWithNewVersions(List<PackageToUpdate> existingPackages, List<PackageToUpdate> packagesToUpdate)
674
+ {
675
+ foreach (PackageToUpdate packageToUpdate in packagesToUpdate)
676
+ {
677
+ PackageToUpdate existingPackage = existingPackages.FirstOrDefault(p => string.Compare(p.PackageName, packageToUpdate.PackageName, StringComparison.OrdinalIgnoreCase) == 0);
678
+
679
+ if (existingPackage != null)
680
+ {
681
+ existingPackage.NewVersion = packageToUpdate.NewVersion;
682
+ }
683
+ else
684
+ {
685
+ Console.WriteLine($"Package {packageToUpdate.PackageName} not found in existing packages");
686
+ }
687
+ }
688
+ }
689
+ }