dependabot-nuget 0.321.3 → 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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +22 -22
  3. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/Program.cs +21 -7
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +19 -11
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +19 -9
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +21 -14
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/FrameworkCheckCommand.cs +8 -5
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +29 -16
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +20 -19
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +2 -1
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/GlobalJsonBuildFile.cs +5 -13
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/PrivateSourceTimedOutException.cs +12 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +4 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceTimedOut.cs +10 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +1 -1
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +1 -1
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +2 -2
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +1 -1
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +1 -1
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +1 -1
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/FileWriterWorker.cs +84 -34
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/XmlFileWriter.cs +17 -5
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +18 -7
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +15 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +0 -2
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +0 -2
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/GlobalJsonBuildFileTests.cs +0 -1
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +1 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/JobErrorBaseTests.cs +7 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +11 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +21 -22
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +8 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests.cs +1 -1
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterWorkerTests_MiscellaneousTests.cs +45 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests.cs +26 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +16 -0
  38. data/helpers/lib/NuGetUpdater/global.json +1 -1
  39. metadata +6 -4
@@ -32,13 +32,36 @@ public class FileWriterWorker
32
32
  )
33
33
  {
34
34
  var updateOperations = new List<UpdateOperationBase>();
35
- var projectDirectory = Path.GetDirectoryName(projectPath.FullName)!;
36
- var projectDirectoryRelativeToRepoRoot = Path.GetRelativePath(repoContentsPath.FullName, projectDirectory).FullyNormalizedRootedPath();
35
+ var initialProjectDirectory = new DirectoryInfo(Path.GetDirectoryName(projectPath.FullName)!);
37
36
 
38
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>();
39
62
  var updatedDotNetToolsPath = await DotNetToolsJsonUpdater.UpdateDependencyAsync(
40
63
  repoContentsPath.FullName,
41
- projectDirectory,
64
+ initialProjectDirectory.FullName,
42
65
  dependencyName,
43
66
  oldDependencyVersion.ToString(),
44
67
  newDependencyVersion.ToString(),
@@ -57,7 +80,7 @@ public class FileWriterWorker
57
80
 
58
81
  var updatedGlobalJsonPath = await GlobalJsonUpdater.UpdateDependencyAsync(
59
82
  repoContentsPath.FullName,
60
- projectDirectory,
83
+ initialProjectDirectory.FullName,
61
84
  dependencyName,
62
85
  oldDependencyVersion.ToString(),
63
86
  newDependencyVersion.ToString(),
@@ -74,33 +97,54 @@ public class FileWriterWorker
74
97
  });
75
98
  }
76
99
 
77
- // then try packages.config updates
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
+ {
78
111
  var additionalFiles = ProjectHelper.GetAllAdditionalFilesFromProject(projectPath.FullName, ProjectHelper.PathFormat.Full);
79
112
  var packagesConfigFullPath = additionalFiles.Where(p => Path.GetFileName(p).Equals(ProjectHelper.PackagesConfigFileName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
80
- if (packagesConfigFullPath is not null)
113
+ if (packagesConfigFullPath is null)
81
114
  {
82
- var packagesConfigOperations = await PackagesConfigUpdater.UpdateDependencyAsync(
83
- repoContentsPath.FullName,
84
- projectPath.FullName,
85
- dependencyName,
86
- oldDependencyVersion.ToString(),
87
- newDependencyVersion.ToString(),
88
- packagesConfigFullPath,
89
- _logger
90
- );
91
- var packagesConfigOperationsWithNormalizedPaths = packagesConfigOperations
92
- .Select(op => op with { UpdatedFiles = [.. op.UpdatedFiles.Select(f => Path.GetRelativePath(repoContentsPath.FullName, f).FullyNormalizedRootedPath())] })
93
- .ToArray();
94
- updateOperations.AddRange(packagesConfigOperationsWithNormalizedPaths);
115
+ return [];
95
116
  }
96
117
 
97
- // then try project updates
98
- var initialDiscoveryResult = await _discoveryWorker.RunAsync(repoContentsPath.FullName, projectDirectoryRelativeToRepoRoot);
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);
99
143
  var initialProjectDiscovery = initialDiscoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, projectPath);
100
144
  if (initialProjectDiscovery is null)
101
145
  {
102
146
  _logger.Info($"Unable to find project discovery for project {projectPath}.");
103
- return [.. updateOperations];
147
+ return [];
104
148
  }
105
149
 
106
150
  var initialRequestedDependency = initialProjectDiscovery.Dependencies
@@ -108,14 +152,14 @@ public class FileWriterWorker
108
152
  if (initialRequestedDependency is null || initialRequestedDependency.Version is null)
109
153
  {
110
154
  _logger.Info($"Dependency {dependencyName} not found in initial project discovery.");
111
- return [.. updateOperations];
155
+ return [];
112
156
  }
113
157
 
114
158
  var initialDependencyVersion = NuGetVersion.Parse(initialRequestedDependency.Version);
115
159
  if (initialDependencyVersion >= newDependencyVersion)
116
160
  {
117
161
  _logger.Info($"Dependency {dependencyName} is already at version {initialDependencyVersion}, no update needed.");
118
- return [.. updateOperations];
162
+ return [];
119
163
  }
120
164
 
121
165
  var initialTopLevelDependencies = initialProjectDiscovery.Dependencies
@@ -126,6 +170,7 @@ public class FileWriterWorker
126
170
  ? initialTopLevelDependencies.Select(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase) ? newDependency : d).ToImmutableArray()
127
171
  : initialTopLevelDependencies.Concat([newDependency]).ToImmutableArray();
128
172
 
173
+ var updateOperations = new List<UpdateOperationBase>();
129
174
  foreach (var targetFramework in initialProjectDiscovery.TargetFrameworks)
130
175
  {
131
176
  var resolvedDependencies = await _dependencySolver.SolveAsync(initialTopLevelDependencies, desiredDependencies, targetFramework);
@@ -154,13 +199,13 @@ public class FileWriterWorker
154
199
  var orderedProjectDiscovery = GetProjectDiscoveryEvaluationOrder(repoContentsPath, initialDiscoveryResult, projectPath, _logger);
155
200
 
156
201
  // track original contents
157
- var originalFileContents = await GetOriginalFileContentsAsync(repoContentsPath, new DirectoryInfo(projectDirectory), orderedProjectDiscovery);
202
+ var originalFileContents = await GetOriginalFileContentsAsync(repoContentsPath, initialProjectDirectory, orderedProjectDiscovery);
158
203
 
159
204
  var allUpdatedFiles = new List<string>();
160
205
  foreach (var projectDiscovery in orderedProjectDiscovery)
161
206
  {
162
207
  var projectFullPath = Path.Join(repoContentsPath.FullName, initialDiscoveryResult.Path, projectDiscovery.FilePath).FullyNormalizedRootedPath();
163
- var updatedFiles = await TryPerformFileWritesAsync(repoContentsPath, new FileInfo(projectFullPath), projectDiscovery, resolvedDependencies.Value);
208
+ var updatedFiles = await TryPerformFileWritesAsync(_fileWriter, repoContentsPath, initialProjectDirectory, projectDiscovery, resolvedDependencies.Value);
164
209
  allUpdatedFiles.AddRange(updatedFiles);
165
210
  }
166
211
 
@@ -172,7 +217,7 @@ public class FileWriterWorker
172
217
  }
173
218
 
174
219
  // this final call to discover has the benefit of also updating the lock file if it exists
175
- var finalDiscoveryResult = await _discoveryWorker.RunAsync(repoContentsPath.FullName, projectDirectoryRelativeToRepoRoot);
220
+ var finalDiscoveryResult = await _discoveryWorker.RunAsync(repoContentsPath.FullName, initialProjectDirectoryRelativeToRepoRoot);
176
221
  var finalProjectDiscovery = finalDiscoveryResult.GetProjectDiscoveryFromFullPath(repoContentsPath, projectPath);
177
222
  if (finalProjectDiscovery is null)
178
223
  {
@@ -222,8 +267,7 @@ public class FileWriterWorker
222
267
  updateOperations.AddRange(computedOperationsWithUpdatedFiles);
223
268
  }
224
269
 
225
- var normalizedUpdateOperations = UpdateOperationBase.NormalizeUpdateOperationCollection(repoContentsPath.FullName, updateOperations);
226
- return normalizedUpdateOperations;
270
+ return [.. updateOperations];
227
271
  }
228
272
 
229
273
  internal static async Task<Dictionary<string, string>> GetOriginalFileContentsAsync(DirectoryInfo repoContentsPath, DirectoryInfo initialStartingDirectory, IEnumerable<ProjectDiscoveryResult> projectDiscoveryResults)
@@ -292,16 +336,22 @@ public class FileWriterWorker
292
336
  return projectDiscoveryOrder;
293
337
  }
294
338
 
295
- private async Task<ImmutableArray<string>> TryPerformFileWritesAsync(DirectoryInfo repoContentsPath, FileInfo projectPath, ProjectDiscoveryResult projectDiscovery, ImmutableArray<Dependency> requiredPackageVersions)
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
+ )
296
346
  {
297
- var originalFileContents = await GetOriginalFileContentsAsync(repoContentsPath, projectPath.Directory!, [projectDiscovery]);
347
+ var originalFileContents = await GetOriginalFileContentsAsync(repoContentsPath, originalDiscoveryDirectory, [projectDiscovery]);
298
348
  var relativeFilePaths = originalFileContents.Keys
299
349
  .Select(p => Path.GetRelativePath(repoContentsPath.FullName, p).FullyNormalizedRootedPath())
300
350
  .ToImmutableArray();
301
351
 
302
352
  // try update
303
353
  var addPackageReferenceElementForPinnedPackages = !projectDiscovery.CentralPackageTransitivePinningEnabled;
304
- var success = await _fileWriter.UpdatePackageVersionsAsync(repoContentsPath, [.. relativeFilePaths], projectDiscovery.Dependencies, requiredPackageVersions, addPackageReferenceElementForPinnedPackages);
354
+ var success = await fileWriter.UpdatePackageVersionsAsync(repoContentsPath, relativeFilePaths, projectDiscovery.Dependencies, requiredPackageVersions, addPackageReferenceElementForPinnedPackages);
305
355
  var updatedFiles = new List<string>();
306
356
  foreach (var (filePath, originalContents) in originalFileContents)
307
357
  {
@@ -320,7 +370,7 @@ public class FileWriterWorker
320
370
  await RestoreOriginalFileContentsAsync(originalFileContents);
321
371
  }
322
372
 
323
- var sortedUpdatedFiles = updatedFiles.OrderBy(p => p, StringComparer.Ordinal);
324
- return [.. sortedUpdatedFiles];
373
+ var sortedUpdatedFiles = updatedFiles.OrderBy(p => p, StringComparer.Ordinal).ToImmutableArray();
374
+ return sortedUpdatedFiles;
325
375
  }
326
376
  }
@@ -23,15 +23,20 @@ public class XmlFileWriter : IFileWriter
23
23
 
24
24
  private readonly ILogger _logger;
25
25
 
26
- // these file extensions are XML and can be updated; everything else is ignored
27
- private static readonly HashSet<string> SupportedFileExtensions =
28
- [
26
+ // these file extensions are valid project entrypoints; everything else is ignored
27
+ private static readonly HashSet<string> SupportedProjectFileExtensions = new(StringComparer.OrdinalIgnoreCase)
28
+ {
29
29
  ".csproj",
30
30
  ".vbproj",
31
31
  ".fsproj",
32
+ };
33
+
34
+ // these file extensions are valid additional files and can be updated; everything else is ignored
35
+ private static readonly HashSet<string> SupportedAdditionalFileExtensions = new(StringComparer.OrdinalIgnoreCase)
36
+ {
32
37
  ".props",
33
38
  ".targets",
34
- ];
39
+ };
35
40
 
36
41
  public XmlFileWriter(ILogger logger)
37
42
  {
@@ -54,8 +59,15 @@ public class XmlFileWriter : IFileWriter
54
59
 
55
60
  var updatesPerformed = requiredPackageVersions.ToDictionary(d => d.Name, _ => false, StringComparer.OrdinalIgnoreCase);
56
61
  var projectRelativePath = relativeFilePaths[0];
62
+ var projectExtension = Path.GetExtension(projectRelativePath);
63
+ if (!SupportedProjectFileExtensions.Contains(projectExtension))
64
+ {
65
+ _logger.Warn($"Project extension '{projectExtension}' not supported; skipping XML update.");
66
+ return false;
67
+ }
68
+
57
69
  var filesAndContentsTasks = relativeFilePaths
58
- .Where(path => SupportedFileExtensions.Contains(Path.GetExtension(path)))
70
+ .Where(path => SupportedProjectFileExtensions.Contains(Path.GetExtension(path)) || SupportedAdditionalFileExtensions.Contains(Path.GetExtension(path)))
59
71
  .Select(async path =>
60
72
  {
61
73
  var content = await ReadFileContentsAsync(repoContentsPath, path);
@@ -21,7 +21,18 @@ public abstract record UpdateOperationBase
21
21
  public required NuGetVersion NewVersion { get; init; }
22
22
  public required ImmutableArray<string> UpdatedFiles { get; init; }
23
23
 
24
- public abstract string GetReport();
24
+ protected abstract string GetReportText();
25
+
26
+ public string GetReport(bool includeFileNames)
27
+ {
28
+ var report = GetReportText();
29
+ if (includeFileNames)
30
+ {
31
+ report += $" in {string.Join(", ", UpdatedFiles)}";
32
+ }
33
+
34
+ return report;
35
+ }
25
36
 
26
37
  public ReportedDependency ToReportedDependency(string projectPath, IEnumerable<ReportedDependency> previouslyReportedDependencies, IEnumerable<Dependency> updatedDependencies)
27
38
  {
@@ -49,9 +60,9 @@ public abstract record UpdateOperationBase
49
60
  };
50
61
  }
51
62
 
52
- internal static string GenerateUpdateOperationReport(IEnumerable<UpdateOperationBase> updateOperations)
63
+ internal static string GenerateUpdateOperationReport(IEnumerable<UpdateOperationBase> updateOperations, bool includeFileNames = true)
53
64
  {
54
- var updateMessages = updateOperations.Select(u => u.GetReport()).ToImmutableArray();
65
+ var updateMessages = updateOperations.Select(u => u.GetReport(includeFileNames)).Distinct(StringComparer.OrdinalIgnoreCase).ToImmutableArray();
55
66
  if (updateMessages.Length == 0)
56
67
  {
57
68
  return string.Empty;
@@ -144,12 +155,12 @@ public abstract record UpdateOperationBase
144
155
  public record DirectUpdate : UpdateOperationBase
145
156
  {
146
157
  public override string Type => nameof(DirectUpdate);
147
- public override string GetReport()
158
+ protected override string GetReportText()
148
159
  {
149
160
  var fromText = OldVersion is null
150
161
  ? string.Empty
151
162
  : $"from {OldVersion} ";
152
- return $"Updated {DependencyName} {fromText}to {NewVersion} in {string.Join(", ", UpdatedFiles)}";
163
+ return $"Updated {DependencyName} {fromText}to {NewVersion}";
153
164
  }
154
165
 
155
166
  public sealed override string ToString() => GetString();
@@ -158,7 +169,7 @@ public record DirectUpdate : UpdateOperationBase
158
169
  public record PinnedUpdate : UpdateOperationBase
159
170
  {
160
171
  public override string Type => nameof(PinnedUpdate);
161
- public override string GetReport() => $"Pinned {DependencyName} at {NewVersion} in {string.Join(", ", UpdatedFiles)}";
172
+ protected override string GetReportText() => $"Pinned {DependencyName} at {NewVersion}";
162
173
  public sealed override string ToString() => GetString();
163
174
  }
164
175
 
@@ -168,7 +179,7 @@ public record ParentUpdate : UpdateOperationBase, IEquatable<UpdateOperationBase
168
179
  public required string ParentDependencyName { get; init; }
169
180
  public required NuGetVersion ParentNewVersion { get; init; }
170
181
 
171
- public override string GetReport() => $"Updated {DependencyName} to {NewVersion} indirectly via {ParentDependencyName}/{ParentNewVersion} in {string.Join(", ", UpdatedFiles)}";
182
+ protected override string GetReportText() => $"Updated {DependencyName} to {NewVersion} indirectly via {ParentDependencyName}/{ParentNewVersion}";
172
183
 
173
184
  bool IEquatable<UpdateOperationBase>.Equals(UpdateOperationBase? other)
174
185
  {
@@ -976,6 +976,7 @@ internal static partial class MSBuildHelper
976
976
  ThrowOnMissingPackages(output);
977
977
  ThrowOnUpdateNotPossible(output);
978
978
  ThrowOnRateLimitExceeded(output);
979
+ ThrowOnTimeout(output);
979
980
  ThrowOnBadResponse(output);
980
981
  ThrowOnUnparseableFile(output);
981
982
  }
@@ -1009,6 +1010,19 @@ internal static partial class MSBuildHelper
1009
1010
  }
1010
1011
  }
1011
1012
 
1013
+ private static void ThrowOnTimeout(string stdout)
1014
+ {
1015
+ var patterns = new[]
1016
+ {
1017
+ new Regex(@"The HTTP request to 'GET (?<Source>[^']+)' has timed out after \d+ms"),
1018
+ };
1019
+ var match = patterns.Select(p => p.Match(stdout)).Where(m => m.Success).FirstOrDefault();
1020
+ if (match is not null)
1021
+ {
1022
+ throw new PrivateSourceTimedOutException(match.Groups["Source"].Value);
1023
+ }
1024
+ }
1025
+
1012
1026
  private static void ThrowOnBadResponse(string stdout)
1013
1027
  {
1014
1028
  var patterns = new[]
@@ -1043,6 +1057,7 @@ internal static partial class MSBuildHelper
1043
1057
  new Regex(@"Unable to find package (?<PackageName>[^ ]+)\. No packages exist with this id in source\(s\): (?<PackageSource>.*)$", RegexOptions.Multiline),
1044
1058
  new Regex(@"Unable to find package (?<PackageName>[^ ]+) with version \((?<PackageVersion>[^)]+)\)"),
1045
1059
  new Regex(@"Unable to find package '(?<PackageName>[^ ]+)'\."),
1060
+ new Regex(@"Unable to resolve dependency '(?<PackageName>[^']+)'\. Source\(s\) used"),
1046
1061
  new Regex(@"Could not resolve SDK ""(?<PackageName>[^ ]+)""\."),
1047
1062
  new Regex(@"Failed to fetch results from V2 feed at '.*FindPackagesById\(\)\?id='(?<PackageName>[^']+)'&semVerLevel=2\.0\.0' with following message : Response status code does not indicate success: 404\."),
1048
1063
  };
@@ -31,7 +31,6 @@ public partial class DiscoveryWorkerTests
31
31
  {
32
32
  FilePath = "global.json",
33
33
  Dependencies = [
34
- new("Microsoft.NET.Sdk", "2.2.104", DependencyType.MSBuildSdk),
35
34
  new("Microsoft.Build.Traversal", "1.0.45", DependencyType.MSBuildSdk),
36
35
  ]
37
36
  },
@@ -65,7 +64,6 @@ public partial class DiscoveryWorkerTests
65
64
  {
66
65
  FilePath = "global.json",
67
66
  Dependencies = [
68
- new("Microsoft.NET.Sdk", "2.2.104", DependencyType.MSBuildSdk),
69
67
  new("Microsoft.Build.Traversal", "1.0.45", DependencyType.MSBuildSdk),
70
68
  ]
71
69
  },
@@ -690,7 +690,6 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
690
690
  {
691
691
  FilePath = "global.json",
692
692
  Dependencies = [
693
- new("Microsoft.NET.Sdk", "6.0.405", DependencyType.MSBuildSdk),
694
693
  new("My.Custom.Sdk", "5.0.0", DependencyType.MSBuildSdk),
695
694
  new("My.Other.Sdk", "1.0.0-beta", DependencyType.MSBuildSdk),
696
695
  ]
@@ -1044,7 +1043,6 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
1044
1043
  {
1045
1044
  FilePath = "global.json",
1046
1045
  Dependencies = [
1047
- new("Microsoft.NET.Sdk", "6.0.405", DependencyType.MSBuildSdk),
1048
1046
  new("My.Custom.Sdk", "5.0.0", DependencyType.MSBuildSdk),
1049
1047
  new("My.Other.Sdk", "1.0.0-beta", DependencyType.MSBuildSdk),
1050
1048
  ]
@@ -53,7 +53,6 @@ public class GlobalJsonBuildFileTests
53
53
  {
54
54
  var expectedDependencies = new List<Dependency>
55
55
  {
56
- new("Microsoft.NET.Sdk", "6.0.405", DependencyType.MSBuildSdk),
57
56
  new("My.Custom.Sdk", "5.0.0", DependencyType.MSBuildSdk),
58
57
  new("My.Other.Sdk", "1.0.0-beta", DependencyType.MSBuildSdk)
59
58
  };
@@ -149,6 +149,7 @@ public class HttpApiHandlerTests
149
149
  yield return [new JobRepoNotFound("unused"), "record_update_job_error"];
150
150
  yield return [new PrivateSourceAuthenticationFailure(["unused"]), "record_update_job_error"];
151
151
  yield return [new PrivateSourceBadResponse(["unused"]), "record_update_job_error"];
152
+ yield return [new PrivateSourceTimedOut("unused"), "record_update_job_error"];
152
153
  yield return [new PullRequestExistsForLatestVersion("unused", "unused"), "record_update_job_error"];
153
154
  yield return [new PullRequestExistsForSecurityUpdate([]), "record_update_job_error"];
154
155
  yield return [new SecurityUpdateDependencyNotFound(), "record_update_job_error"];
@@ -61,6 +61,13 @@ public class JobErrorBaseTests : TestBase
61
61
  new PrivateSourceBadResponse(["http://nuget.example.com/v3/index.json"]),
62
62
  ];
63
63
 
64
+ // service returned corrupt package
65
+ yield return
66
+ [
67
+ new InvalidDataException("Central Directory corrupt."),
68
+ new PrivateSourceBadResponse(["http://nuget.example.com/v3/index.json"]),
69
+ ];
70
+
64
71
  // top-level exception turns into private_source_authentication_failure
65
72
  yield return
66
73
  [
@@ -160,6 +160,17 @@ public class MessageReportTests
160
160
  """
161
161
  ];
162
162
 
163
+ yield return
164
+ [
165
+ // message
166
+ new PrivateSourceTimedOut("url"),
167
+ // expected
168
+ """
169
+ Error type: private_source_timed_out
170
+ - source: url
171
+ """
172
+ ];
173
+
163
174
  yield return
164
175
  [
165
176
  // message
@@ -79,7 +79,7 @@ public class PullRequestTextTests
79
79
  // expectedBody
80
80
  """
81
81
  Performed the following updates:
82
- - Updated Some.Package from 1.0.0 to 1.2.3 in a.txt
82
+ - Updated Some.Package from 1.0.0 to 1.2.3
83
83
  """
84
84
  ];
85
85
 
@@ -108,7 +108,7 @@ public class PullRequestTextTests
108
108
  // expectedBody
109
109
  """
110
110
  Performed the following updates:
111
- - Updated Some.Package from 1.0.0 to 1.2.3 in a.txt
111
+ - Updated Some.Package from 1.0.0 to 1.2.3
112
112
  """
113
113
  ];
114
114
 
@@ -137,7 +137,7 @@ public class PullRequestTextTests
137
137
  // expectedBody
138
138
  """
139
139
  Performed the following updates:
140
- - Updated Some.Package from 1.0.0 to 1.2.3 in a.txt
140
+ - Updated Some.Package from 1.0.0 to 1.2.3
141
141
  """
142
142
  ];
143
143
 
@@ -173,8 +173,8 @@ public class PullRequestTextTests
173
173
  // expectedBody
174
174
  """
175
175
  Performed the following updates:
176
- - Updated Some.Package from 1.0.0 to 1.2.3 in a.txt
177
- - Updated Some.Package from 4.0.0 to 4.5.6 in b.txt
176
+ - Updated Some.Package from 1.0.0 to 1.2.3
177
+ - Updated Some.Package from 4.0.0 to 4.5.6
178
178
  """
179
179
  ];
180
180
 
@@ -229,10 +229,10 @@ public class PullRequestTextTests
229
229
  // expectedBody
230
230
  """
231
231
  Performed the following updates:
232
- - Updated Package.A from 0.1.0 to 1.0.0 in a1.txt
233
- - Updated Package.A from 0.2.0 to 2.0.0 in a2.txt
234
- - Updated Package.B from 0.3.0 to 3.0.0 in b1.txt
235
- - Updated Package.B from 0.4.0 to 4.0.0 in b2.txt
232
+ - Updated Package.A from 0.1.0 to 1.0.0
233
+ - Updated Package.A from 0.2.0 to 2.0.0
234
+ - Updated Package.B from 0.3.0 to 3.0.0
235
+ - Updated Package.B from 0.4.0 to 4.0.0
236
236
  """
237
237
  ];
238
238
 
@@ -317,14 +317,14 @@ public class PullRequestTextTests
317
317
  // expectedBody
318
318
  """
319
319
  Performed the following updates:
320
- - Updated Package.A from 0.1.0 to 1.0.0 in a1.txt
321
- - Updated Package.A from 0.2.0 to 2.0.0 in a2.txt
322
- - Updated Package.B from 0.3.0 to 3.0.0 in b1.txt
323
- - Updated Package.B from 0.4.0 to 4.0.0 in b2.txt
324
- - Updated Package.C from 0.5.0 to 5.0.0 in c1.txt
325
- - Updated Package.C from 0.6.0 to 6.0.0 in c2.txt
326
- - Updated Package.D from 0.7.0 to 7.0.0 in d1.txt
327
- - Updated Package.D from 0.8.0 to 8.0.0 in d2.txt
320
+ - Updated Package.A from 0.1.0 to 1.0.0
321
+ - Updated Package.A from 0.2.0 to 2.0.0
322
+ - Updated Package.B from 0.3.0 to 3.0.0
323
+ - Updated Package.B from 0.4.0 to 4.0.0
324
+ - Updated Package.C from 0.5.0 to 5.0.0
325
+ - Updated Package.C from 0.6.0 to 6.0.0
326
+ - Updated Package.D from 0.7.0 to 7.0.0
327
+ - Updated Package.D from 0.8.0 to 8.0.0
328
328
  """
329
329
  ];
330
330
 
@@ -357,7 +357,7 @@ public class PullRequestTextTests
357
357
  // expectedBody
358
358
  """
359
359
  Performed the following updates:
360
- - Updated Some.Package from 1.0.0 to 1.2.3 in a.txt
360
+ - Updated Some.Package from 1.0.0 to 1.2.3
361
361
  """
362
362
  ];
363
363
 
@@ -398,8 +398,8 @@ public class PullRequestTextTests
398
398
  // expectedBody
399
399
  """
400
400
  Performed the following updates:
401
- - Updated Package.A from 1.0.0 to 1.2.3 in a.txt
402
- - Updated Package.B from 4.0.0 to 4.5.6 in a.txt
401
+ - Updated Package.A from 1.0.0 to 1.2.3
402
+ - Updated Package.B from 4.0.0 to 4.5.6
403
403
  """
404
404
  ];
405
405
 
@@ -437,8 +437,7 @@ public class PullRequestTextTests
437
437
  // expectedBody
438
438
  """
439
439
  Performed the following updates:
440
- - Updated Some.Package from 1.0.0 to 1.2.3 in a.txt
441
- - Updated Some.Package from 1.0.0 to 1.2.3 in b.txt
440
+ - Updated Some.Package from 1.0.0 to 1.2.3
442
441
  """
443
442
  ];
444
443
  }
@@ -3522,7 +3522,7 @@ public class RunWorkerTests
3522
3522
  updaterWorker ??= new UpdaterWorker(jobId, experimentsManager, logger);
3523
3523
 
3524
3524
  var worker = new RunWorker(jobId, testApiHandler, discoveryWorker, analyzeWorker, updaterWorker, logger);
3525
- var repoContentsPathDirectoryInfo = new DirectoryInfo(tempDirectory.DirectoryPath);
3525
+ var repoContentsPathDirectoryInfo = new DirectoryInfo(repoContentsPath);
3526
3526
  var actualResult = await worker.RunAsync(job, repoContentsPathDirectoryInfo, repoContentsPathDirectoryInfo, "TEST-COMMIT-SHA", experimentsManager);
3527
3527
  var actualApiMessages = testApiHandler.ReceivedMessages
3528
3528
  .Select(m =>
@@ -707,6 +707,14 @@ public class SerializationTests : TestBase
707
707
  """
708
708
  ];
709
709
 
710
+ yield return
711
+ [
712
+ new PrivateSourceTimedOut("url"),
713
+ """
714
+ {"data":{"error-type":"private_source_timed_out","error-details":{"source":"url"}}}
715
+ """
716
+ ];
717
+
710
718
  yield return
711
719
  [
712
720
  new PullRequestExistsForLatestVersion("dep", "ver"),
@@ -415,7 +415,7 @@ public class FileWriterWorkerTests : TestBase
415
415
  """)
416
416
  ],
417
417
  expectedOperations: [
418
- new DirectUpdate() { DependencyName = "Some.Dependency", NewVersion = NuGetVersion.Parse("2.0.0"), UpdatedFiles = ["/project.csproj", "/packages.config"] }
418
+ new DirectUpdate() { DependencyName = "Some.Dependency", NewVersion = NuGetVersion.Parse("2.0.0"), UpdatedFiles = ["/packages.config", "/project.csproj"] }
419
419
  ]
420
420
  );
421
421
  }
@@ -106,4 +106,49 @@ public class FileWriterWorkerTests_MiscellaneousTests
106
106
  Assert.Equal("initial client content", await File.ReadAllTextAsync(Path.Join(repoContentsPath.FullName, "client/client.csproj"), TestContext.Current.CancellationToken));
107
107
  Assert.Equal("initial packages config content", await File.ReadAllTextAsync(Path.Join(repoContentsPath.FullName, "client/packages.config"), TestContext.Current.CancellationToken));
108
108
  }
109
+
110
+ [Fact]
111
+ public async Task TryPerformFileWrites_ReportsAppropriateUpdatedFilePaths_WhenStartingFromDifferentProjectDirectory()
112
+ {
113
+ // discovery was initiated from the "tests" directory, but we're now evaluating the "client" project for file writes
114
+ // arrange
115
+ using var tempDir = await TemporaryDirectory.CreateWithContentsAsync(
116
+ ("tests/tests.csproj", """
117
+ <Project>
118
+ <ItemGroup>
119
+ <PackageReference Include="Some.Package" Version="1.0.0" />
120
+ </ItemGroup>
121
+ </Project>
122
+ """),
123
+ ("client/client.csproj", """
124
+ <Project>
125
+ <ItemGroup>
126
+ <PackageReference Include="Some.Package" Version="1.0.0" />
127
+ </ItemGroup>
128
+ </Project>
129
+ """)
130
+ );
131
+ var logger = new TestLogger();
132
+ var fileWriter = new XmlFileWriter(logger);
133
+ var originalDiscoveryDirectory = new DirectoryInfo(Path.Join(tempDir.DirectoryPath, "tests"));
134
+ var projectDiscovery = new ProjectDiscoveryResult()
135
+ {
136
+ FilePath = "../client/client.csproj",
137
+ Dependencies = [new("Some.Package", "1.0.0", DependencyType.PackageReference)],
138
+ ImportedFiles = [],
139
+ AdditionalFiles = [],
140
+ };
141
+
142
+ // act
143
+ var updatedFilePaths = await FileWriterWorker.TryPerformFileWritesAsync(
144
+ fileWriter,
145
+ new DirectoryInfo(tempDir.DirectoryPath),
146
+ originalDiscoveryDirectory,
147
+ projectDiscovery,
148
+ [new("Some.Package", "1.0.1", DependencyType.PackageReference)]
149
+ );
150
+
151
+ // assert
152
+ AssertEx.Equal(["/client/client.csproj"], updatedFilePaths);
153
+ }
109
154
  }