dependabot-nuget 0.267.0 → 0.270.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.
@@ -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
+ }