dependabot-nuget 0.293.0 → 0.295.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 833b170b34a1cef53346970cdfc208b39f49899aaeeb1d2da285463eabf2aea3
4
- data.tar.gz: aea74663e1ec787d2496c6d63f97c531d6ccd5415cdd810ce7d8518080b027cc
3
+ metadata.gz: 8ac3a95f5211adff79f8e7954308a4d3656b63c8be20b70a7b6f129f04402881
4
+ data.tar.gz: a79771c0cc6c0a08bfaa2a7cef8253902c7e8b92b868568c231064098ba77a1c
5
5
  SHA512:
6
- metadata.gz: 889ab157302c7dbb0be345ca4e5204fe5518d6aafbd61d7992008404fa84200240e6f88655027ddb4a77611964cb97e2ea7857ad6453d33e498e4d6416b79eb8
7
- data.tar.gz: 17f3003ef471825cf980d00da41c528bb051dc64285d6ca808ca84bea09a89b91a197a388d8f62c4e089427a52e77ba24d13b452ec9bc7231b46d18c9c84d697
6
+ metadata.gz: 800097bc4856927e983da508d16b4e6d0fd75cf40b5ca5ed25901fbb467f0f99e8f8dc724341c0da6cc6126303c6437a5bd8c7521132c3363ed752181402ee1b
7
+ data.tar.gz: c0a1675e7cdf06ec0314d58092de09d633fa46892ae8f01c20ec74553d756265fda431434babb6eb140184b3380b78576f6309c1ec010f41e0e09e6ad6b28adb
@@ -36,7 +36,7 @@
36
36
  <PackageVersion Include="System.Text.Json" Version="8.0.4" />
37
37
  <PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
38
38
  <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.0" />
39
- <PackageVersion Include="xunit" Version="2.9.2" />
39
+ <PackageVersion Include="xunit" Version="2.9.3" />
40
40
  <PackageVersion Include="xunit.runner.visualstudio" Version="3.0.0" />
41
41
  </ItemGroup>
42
42
 
@@ -113,11 +113,22 @@ internal static class VersionFinder
113
113
  ? versionRange.MinVersion
114
114
  : null;
115
115
 
116
- return version => (currentVersion is null || version > currentVersion)
117
- && versionRange.Satisfies(version)
118
- && (currentVersion is null || !currentVersion.IsPrerelease || !version.IsPrerelease || version.Version == currentVersion.Version)
119
- && !dependencyInfo.IgnoredVersions.Any(r => r.IsSatisfiedBy(version))
120
- && !dependencyInfo.Vulnerabilities.Any(v => v.IsVulnerable(version));
116
+ var safeVersions = dependencyInfo.Vulnerabilities.SelectMany(v => v.SafeVersions).ToList();
117
+ return version =>
118
+ {
119
+ var versionGreaterThanCurrent = currentVersion is null || version > currentVersion;
120
+ var rangeSatisfies = versionRange.Satisfies(version);
121
+ var prereleaseTypeMatches = currentVersion is null || !currentVersion.IsPrerelease || !version.IsPrerelease || version.Version == currentVersion.Version;
122
+ var isIgnoredVersion = dependencyInfo.IgnoredVersions.Any(i => i.IsSatisfiedBy(version));
123
+ var isVulnerableVersion = dependencyInfo.Vulnerabilities.Any(v => v.IsVulnerable(version));
124
+ var isSafeVersion = !safeVersions.Any() || safeVersions.Any(s => s.IsSatisfiedBy(version));
125
+ return versionGreaterThanCurrent
126
+ && rangeSatisfies
127
+ && prereleaseTypeMatches
128
+ && !isIgnoredVersion
129
+ && !isVulnerableVersion
130
+ && isSafeVersion;
131
+ };
121
132
  }
122
133
 
123
134
  internal static Func<NuGetVersion, bool> CreateVersionFilter(NuGetVersion currentVersion)
@@ -262,7 +262,8 @@ public partial class DiscoveryWorker : IDiscoveryWorker
262
262
  }
263
263
  }
264
264
 
265
- return expandedProjects.ToImmutableArray();
265
+ var result = expandedProjects.OrderBy(p => p).ToImmutableArray();
266
+ return result;
266
267
  }
267
268
 
268
269
  private static IEnumerable<string> ExpandItemGroupFilesFromProject(string projectPath, params string[] itemTypes)
@@ -114,7 +114,7 @@ internal static class SdkProjectDiscovery
114
114
  var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(args, startingProjectDirectory, experimentsManager);
115
115
  return (exitCode, stdOut, stdErr);
116
116
  }, logger, retainMSBuildSdks: true);
117
- MSBuildHelper.ThrowOnUnauthenticatedFeed(stdOut);
117
+ MSBuildHelper.ThrowOnError(stdOut);
118
118
  if (stdOut.Contains("""error MSB4057: The target "GenerateBuildDependencyFile" does not exist in the project."""))
119
119
  {
120
120
  // this can happen if it's a non-SDK-style project; totally normal, not worth examining the binlog
@@ -10,4 +10,6 @@ public record Advisory
10
10
  public ImmutableArray<Requirement>? AffectedVersions { get; init; } = null;
11
11
  public ImmutableArray<Requirement>? PatchedVersions { get; init; } = null;
12
12
  public ImmutableArray<Requirement>? UnaffectedVersions { get; init; } = null;
13
+
14
+ public IEnumerable<Requirement> SafeVersions => (PatchedVersions ?? []).Concat(UnaffectedVersions ?? []);
13
15
  }
@@ -4,6 +4,10 @@ using System.Text;
4
4
  using System.Text.Json;
5
5
  using System.Text.Json.Serialization;
6
6
 
7
+ using Microsoft.Extensions.FileSystemGlobbing;
8
+
9
+ using NuGet.Versioning;
10
+
7
11
  using NuGetUpdater.Core.Analyze;
8
12
  using NuGetUpdater.Core.Discover;
9
13
  using NuGetUpdater.Core.Run.ApiModel;
@@ -113,169 +117,145 @@ public class RunWorker
113
117
  await _apiHandler.UpdateDependencyList(discoveredUpdatedDependencies);
114
118
 
115
119
  // TODO: pull out relevant dependencies, then check each for updates and track the changes
116
- // TODO: for each top-level dependency, _or_ specific dependency (if security, use transitive)
117
120
  var originalDependencyFileContents = new Dictionary<string, string>();
118
121
  var actualUpdatedDependencies = new List<ReportedDependency>();
119
- if (job.AllowedUpdates.Any(a => a.UpdateType == UpdateType.All))
122
+ await _apiHandler.IncrementMetric(new()
120
123
  {
121
- await _apiHandler.IncrementMetric(new()
122
- {
123
- Metric = "updater.started",
124
- Tags = { ["operation"] = "group_update_all_versions" },
125
- });
124
+ Metric = "updater.started",
125
+ Tags = { ["operation"] = "group_update_all_versions" },
126
+ });
127
+
128
+ // track original contents for later handling
129
+ async Task TrackOriginalContentsAsync(string directory, string fileName)
130
+ {
131
+ var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
132
+ var localFullPath = Path.Join(repoContentsPath.FullName, repoFullPath);
133
+ var content = await File.ReadAllTextAsync(localFullPath);
134
+ originalDependencyFileContents[repoFullPath] = content;
135
+ }
126
136
 
127
- // track original contents for later handling
128
- async Task TrackOriginalContentsAsync(string directory, string fileName)
137
+ foreach (var project in discoveryResult.Projects)
138
+ {
139
+ var projectDirectory = Path.GetDirectoryName(project.FilePath);
140
+ await TrackOriginalContentsAsync(discoveryResult.Path, project.FilePath);
141
+ foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
129
142
  {
130
- var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
131
- var localFullPath = Path.Join(repoContentsPath.FullName, repoFullPath);
132
- var content = await File.ReadAllTextAsync(localFullPath);
133
- originalDependencyFileContents[repoFullPath] = content;
143
+ var extraFilePath = Path.Join(projectDirectory, extraFile);
144
+ await TrackOriginalContentsAsync(discoveryResult.Path, extraFilePath);
134
145
  }
146
+ // TODO: include global.json, etc.
147
+ }
135
148
 
136
- foreach (var project in discoveryResult.Projects)
149
+ // do update
150
+ _logger.Info($"Running update in directory {repoDirectory}");
151
+ foreach (var project in discoveryResult.Projects)
152
+ {
153
+ foreach (var dependency in project.Dependencies)
137
154
  {
138
- var projectDirectory = Path.GetDirectoryName(project.FilePath);
139
- await TrackOriginalContentsAsync(discoveryResult.Path, project.FilePath);
140
- foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
155
+ if (!IsUpdateAllowed(job, dependency))
141
156
  {
142
- var extraFilePath = Path.Join(projectDirectory, extraFile);
143
- await TrackOriginalContentsAsync(discoveryResult.Path, extraFilePath);
157
+ continue;
144
158
  }
145
- // TODO: include global.json, etc.
146
- }
147
159
 
148
- // do update
149
- _logger.Info($"Running update in directory {repoDirectory}");
150
- foreach (var project in discoveryResult.Projects)
151
- {
152
- foreach (var dependency in project.Dependencies.Where(d => !d.IsTransitive))
160
+ var dependencyInfo = GetDependencyInfo(job, dependency);
161
+ var analysisResult = await _analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
162
+ // TODO: log analysisResult
163
+ if (analysisResult.CanUpdate)
153
164
  {
154
- if (dependency.Name == "Microsoft.NET.Sdk")
155
- {
156
- // this can't be updated
157
- // TODO: pull this out of discovery?
158
- continue;
159
- }
165
+ var dependencyLocation = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
160
166
 
161
- if (dependency.Version is null)
162
- {
163
- // if we don't know the version, there's nothing we can do
164
- continue;
165
- }
166
-
167
- var ignoredVersions = GetIgnoredRequirementsForDependency(job, dependency.Name);
168
- var dependencyInfo = new DependencyInfo()
167
+ // TODO: this is inefficient, but not likely causing a bottleneck
168
+ var previousDependency = discoveredUpdatedDependencies.Dependencies
169
+ .Single(d => d.Name == dependency.Name && d.Requirements.Single().File == dependencyLocation);
170
+ var updatedDependency = new ReportedDependency()
169
171
  {
170
172
  Name = dependency.Name,
171
- Version = dependency.Version!,
172
- IsVulnerable = false,
173
- IgnoredVersions = ignoredVersions,
174
- Vulnerabilities = [],
175
- };
176
- var analysisResult = await _analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
177
- // TODO: log analysisResult
178
- if (analysisResult.CanUpdate)
179
- {
180
- var dependencyLocation = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
181
-
182
- // TODO: this is inefficient, but not likely causing a bottleneck
183
- var previousDependency = discoveredUpdatedDependencies.Dependencies
184
- .Single(d => d.Name == dependency.Name && d.Requirements.Single().File == dependencyLocation);
185
- var updatedDependency = new ReportedDependency()
186
- {
187
- Name = dependency.Name,
188
- Version = analysisResult.UpdatedVersion,
189
- Requirements =
190
- [
191
- new ReportedRequirement()
192
- {
193
- File = dependencyLocation,
194
- Requirement = analysisResult.UpdatedVersion,
195
- Groups = previousDependency.Requirements.Single().Groups,
196
- Source = new RequirementSource()
197
- {
198
- SourceUrl = analysisResult.UpdatedDependencies.FirstOrDefault(d => d.Name == dependency.Name)?.InfoUrl,
199
- },
200
- }
201
- ],
202
- PreviousVersion = dependency.Version,
203
- PreviousRequirements = previousDependency.Requirements,
204
- };
205
-
206
- var dependencyFilePath = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
207
- var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, dependencyFilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: false);
208
- // TODO: need to report if anything was actually updated
209
- if (updateResult.Error is null)
210
- {
211
- if (dependencyLocation != dependencyFilePath)
173
+ Version = analysisResult.UpdatedVersion,
174
+ Requirements =
175
+ [
176
+ new ReportedRequirement()
212
177
  {
213
- updatedDependency.Requirements.All(r => r.File == dependencyFilePath);
178
+ File = dependencyLocation,
179
+ Requirement = analysisResult.UpdatedVersion,
180
+ Groups = previousDependency.Requirements.Single().Groups,
181
+ Source = new RequirementSource()
182
+ {
183
+ SourceUrl = analysisResult.UpdatedDependencies.FirstOrDefault(d => d.Name == dependency.Name)?.InfoUrl,
184
+ },
214
185
  }
186
+ ],
187
+ PreviousVersion = dependency.Version,
188
+ PreviousRequirements = previousDependency.Requirements,
189
+ };
215
190
 
216
- actualUpdatedDependencies.Add(updatedDependency);
191
+ var dependencyFilePath = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
192
+ var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, dependencyFilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: dependency.IsTransitive);
193
+ // TODO: need to report if anything was actually updated
194
+ if (updateResult.Error is null)
195
+ {
196
+ if (dependencyLocation != dependencyFilePath)
197
+ {
198
+ updatedDependency.Requirements.All(r => r.File == dependencyFilePath);
217
199
  }
200
+
201
+ actualUpdatedDependencies.Add(updatedDependency);
218
202
  }
219
203
  }
220
204
  }
205
+ }
221
206
 
222
- // create PR - we need to manually check file contents; we can't easily use `git status` in tests
223
- var updatedDependencyFiles = new Dictionary<string, DependencyFile>();
224
- async Task AddUpdatedFileIfDifferentAsync(string directory, string fileName)
207
+ // create PR - we need to manually check file contents; we can't easily use `git status` in tests
208
+ var updatedDependencyFiles = new Dictionary<string, DependencyFile>();
209
+ async Task AddUpdatedFileIfDifferentAsync(string directory, string fileName)
210
+ {
211
+ var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
212
+ var localFullPath = Path.GetFullPath(Path.Join(repoContentsPath.FullName, repoFullPath));
213
+ var originalContent = originalDependencyFileContents[repoFullPath];
214
+ var updatedContent = await File.ReadAllTextAsync(localFullPath);
215
+ if (updatedContent != originalContent)
225
216
  {
226
- var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
227
- var localFullPath = Path.GetFullPath(Path.Join(repoContentsPath.FullName, repoFullPath));
228
- var originalContent = originalDependencyFileContents[repoFullPath];
229
- var updatedContent = await File.ReadAllTextAsync(localFullPath);
230
- if (updatedContent != originalContent)
217
+ updatedDependencyFiles[localFullPath] = new DependencyFile()
231
218
  {
232
- updatedDependencyFiles[localFullPath] = new DependencyFile()
233
- {
234
- Name = Path.GetFileName(repoFullPath),
235
- Directory = Path.GetDirectoryName(repoFullPath)!.NormalizePathToUnix(),
236
- Content = updatedContent,
237
- };
238
- }
219
+ Name = Path.GetFileName(repoFullPath),
220
+ Directory = Path.GetDirectoryName(repoFullPath)!.NormalizePathToUnix(),
221
+ Content = updatedContent,
222
+ };
239
223
  }
224
+ }
240
225
 
241
- foreach (var project in discoveryResult.Projects)
226
+ foreach (var project in discoveryResult.Projects)
227
+ {
228
+ await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, project.FilePath);
229
+ var projectDirectory = Path.GetDirectoryName(project.FilePath);
230
+ foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
242
231
  {
243
- await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, project.FilePath);
244
- var projectDirectory = Path.GetDirectoryName(project.FilePath);
245
- foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
246
- {
247
- var extraFilePath = Path.Join(projectDirectory, extraFile);
248
- await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, extraFilePath);
249
- }
250
- // TODO: handle global.json, etc.
232
+ var extraFilePath = Path.Join(projectDirectory, extraFile);
233
+ await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, extraFilePath);
251
234
  }
235
+ // TODO: handle global.json, etc.
236
+ }
252
237
 
253
- if (updatedDependencyFiles.Count > 0)
254
- {
255
- var updatedDependencyFileList = updatedDependencyFiles
256
- .OrderBy(kvp => kvp.Key)
257
- .Select(kvp => kvp.Value)
258
- .ToArray();
259
- var createPullRequest = new CreatePullRequest()
260
- {
261
- Dependencies = actualUpdatedDependencies.ToArray(),
262
- UpdatedDependencyFiles = updatedDependencyFileList,
263
- BaseCommitSha = baseCommitSha,
264
- CommitMessage = "TODO: message",
265
- PrTitle = "TODO: title",
266
- PrBody = "TODO: body",
267
- };
268
- await _apiHandler.CreatePullRequest(createPullRequest);
269
- // TODO: log updated dependencies to console
270
- }
271
- else
238
+ if (updatedDependencyFiles.Count > 0)
239
+ {
240
+ var updatedDependencyFileList = updatedDependencyFiles
241
+ .OrderBy(kvp => kvp.Key)
242
+ .Select(kvp => kvp.Value)
243
+ .ToArray();
244
+ var createPullRequest = new CreatePullRequest()
272
245
  {
273
- // TODO: log or throw if nothing was updated, but was expected to be
274
- }
246
+ Dependencies = actualUpdatedDependencies.ToArray(),
247
+ UpdatedDependencyFiles = updatedDependencyFileList,
248
+ BaseCommitSha = baseCommitSha,
249
+ CommitMessage = "TODO: message",
250
+ PrTitle = "TODO: title",
251
+ PrBody = "TODO: body",
252
+ };
253
+ await _apiHandler.CreatePullRequest(createPullRequest);
254
+ // TODO: log updated dependencies to console
275
255
  }
276
256
  else
277
257
  {
278
- // TODO: throw if no updates performed
258
+ // TODO: log or throw if nothing was updated, but was expected to be
279
259
  }
280
260
 
281
261
  var result = new RunResult()
@@ -295,6 +275,62 @@ public class RunWorker
295
275
  return result;
296
276
  }
297
277
 
278
+ internal static bool IsUpdateAllowed(Job job, Dependency dependency)
279
+ {
280
+ if (dependency.Name.Equals("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase))
281
+ {
282
+ // this can't be updated
283
+ // TODO: pull this out of discovery?
284
+ return false;
285
+ }
286
+
287
+ if (dependency.Version is null)
288
+ {
289
+ // if we don't know the version, there's nothing we can do
290
+ // TODO: pull this out of discovery?
291
+ return false;
292
+ }
293
+
294
+ var version = NuGetVersion.Parse(dependency.Version);
295
+ var dependencyInfo = GetDependencyInfo(job, dependency);
296
+ var isVulnerable = dependencyInfo.Vulnerabilities.Any(v => v.IsVulnerable(version));
297
+ var allowed = job.AllowedUpdates.Any(allowedUpdate =>
298
+ {
299
+ // check name restriction, if any
300
+ if (allowedUpdate.DependencyName is not null)
301
+ {
302
+ var matcher = new Matcher(StringComparison.OrdinalIgnoreCase)
303
+ .AddInclude(allowedUpdate.DependencyName);
304
+ var result = matcher.Match(dependency.Name);
305
+ if (!result.HasMatches)
306
+ {
307
+ return false;
308
+ }
309
+ }
310
+
311
+ var isSecurityUpdate = allowedUpdate.UpdateType == UpdateType.Security || job.SecurityUpdatesOnly;
312
+ if (isSecurityUpdate)
313
+ {
314
+ // only update if it's vulnerable
315
+ return isVulnerable;
316
+ }
317
+ else
318
+ {
319
+ // not a security update, so only update if...
320
+ // ...we've been explicitly asked to update this
321
+ if ((job.Dependencies ?? []).Any(d => d.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase)))
322
+ {
323
+ return true;
324
+ }
325
+
326
+ // ...no specific update being performed, do it if it's not transitive
327
+ return !dependency.IsTransitive;
328
+ }
329
+ });
330
+
331
+ return allowed;
332
+ }
333
+
298
334
  internal static ImmutableArray<Requirement> GetIgnoredRequirementsForDependency(Job job, string dependencyName)
299
335
  {
300
336
  var ignoreConditions = job.IgnoreConditions
@@ -314,6 +350,30 @@ public class RunWorker
314
350
  return ignoredVersions;
315
351
  }
316
352
 
353
+ internal static DependencyInfo GetDependencyInfo(Job job, Dependency dependency)
354
+ {
355
+ var dependencyVersion = NuGetVersion.Parse(dependency.Version!);
356
+ var securityAdvisories = job.SecurityAdvisories.Where(s => s.DependencyName.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase)).ToArray();
357
+ var isVulnerable = securityAdvisories.Any(s => (s.AffectedVersions ?? []).Any(v => v.IsSatisfiedBy(dependencyVersion)));
358
+ var ignoredVersions = GetIgnoredRequirementsForDependency(job, dependency.Name);
359
+ var vulnerabilities = securityAdvisories.Select(s => new SecurityVulnerability()
360
+ {
361
+ DependencyName = dependency.Name,
362
+ PackageManager = "nuget",
363
+ VulnerableVersions = s.AffectedVersions ?? [],
364
+ SafeVersions = s.SafeVersions.ToImmutableArray(),
365
+ }).ToImmutableArray();
366
+ var dependencyInfo = new DependencyInfo()
367
+ {
368
+ Name = dependency.Name,
369
+ Version = dependencyVersion.ToString(),
370
+ IsVulnerable = isVulnerable,
371
+ IgnoredVersions = ignoredVersions,
372
+ Vulnerabilities = vulnerabilities,
373
+ };
374
+ return dependencyInfo;
375
+ }
376
+
317
377
  internal static UpdatedDependencyList GetUpdatedDependencyListFromDiscovery(WorkspaceDiscoveryResult discoveryResult, string pathToContents)
318
378
  {
319
379
  string GetFullRepoPath(string path)
@@ -357,7 +417,7 @@ public class RunWorker
357
417
  new ReportedDependency()
358
418
  {
359
419
  Name = d.Name,
360
- Requirements = d.IsTransitive ? [] : [new ReportedRequirement()
420
+ Requirements = [new ReportedRequirement()
361
421
  {
362
422
  File = GetFullRepoPath(p.FilePath),
363
423
  Requirement = d.Version!,
@@ -347,7 +347,7 @@ internal static class PackageReferenceUpdater
347
347
  projectDirectory,
348
348
  experimentsManager
349
349
  );
350
- MSBuildHelper.ThrowOnUnauthenticatedFeed(stdout);
350
+ MSBuildHelper.ThrowOnError(stdout);
351
351
  if (exitCode != 0)
352
352
  {
353
353
  logger.Warn($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
@@ -148,18 +148,15 @@ internal static partial class PackagesConfigUpdater
148
148
 
149
149
  if (exitCodeAgain != 0)
150
150
  {
151
- MSBuildHelper.ThrowOnMissingFile(fullOutput);
152
- MSBuildHelper.ThrowOnMissingFile(restoreOutput);
153
- MSBuildHelper.ThrowOnMissingPackages(restoreOutput);
151
+ MSBuildHelper.ThrowOnError(fullOutput);
152
+ MSBuildHelper.ThrowOnError(restoreOutput);
154
153
  throw new Exception($"Unable to restore.\nOutput:\n${restoreOutput}\n");
155
154
  }
156
155
 
157
156
  goto doRestore;
158
157
  }
159
158
 
160
- MSBuildHelper.ThrowOnUnauthenticatedFeed(fullOutput);
161
- MSBuildHelper.ThrowOnMissingFile(fullOutput);
162
- MSBuildHelper.ThrowOnMissingPackages(fullOutput);
159
+ MSBuildHelper.ThrowOnError(fullOutput);
163
160
  throw new Exception(fullOutput);
164
161
  }
165
162
  }
@@ -903,21 +903,6 @@ internal static partial class MSBuildHelper
903
903
  }
904
904
  }
905
905
 
906
- internal static void ThrowOnUnauthenticatedFeed(string stdout)
907
- {
908
- var unauthorizedMessageSnippets = new string[]
909
- {
910
- "The plugin credential provider could not acquire credentials",
911
- "401 (Unauthorized)",
912
- "error NU1301: Unable to load the service index for source",
913
- "Response status code does not indicate success: 403",
914
- };
915
- if (unauthorizedMessageSnippets.Any(stdout.Contains))
916
- {
917
- throw new HttpRequestException(message: stdout, inner: null, statusCode: System.Net.HttpStatusCode.Unauthorized);
918
- }
919
- }
920
-
921
906
  internal static string? GetMissingFile(string output)
922
907
  {
923
908
  var missingFilePatterns = new[]
@@ -934,7 +919,30 @@ internal static partial class MSBuildHelper
934
919
  return null;
935
920
  }
936
921
 
937
- internal static void ThrowOnMissingFile(string output)
922
+ internal static void ThrowOnError(string output)
923
+ {
924
+ ThrowOnUnauthenticatedFeed(output);
925
+ ThrowOnMissingFile(output);
926
+ ThrowOnMissingPackages(output);
927
+ ThrowOnUnresolvableDependencies(output);
928
+ }
929
+
930
+ private static void ThrowOnUnauthenticatedFeed(string stdout)
931
+ {
932
+ var unauthorizedMessageSnippets = new string[]
933
+ {
934
+ "The plugin credential provider could not acquire credentials",
935
+ "401 (Unauthorized)",
936
+ "error NU1301: Unable to load the service index for source",
937
+ "Response status code does not indicate success: 403",
938
+ };
939
+ if (unauthorizedMessageSnippets.Any(stdout.Contains))
940
+ {
941
+ throw new HttpRequestException(message: stdout, inner: null, statusCode: System.Net.HttpStatusCode.Unauthorized);
942
+ }
943
+ }
944
+
945
+ private static void ThrowOnMissingFile(string output)
938
946
  {
939
947
  var missingFile = GetMissingFile(output);
940
948
  if (missingFile is not null)
@@ -943,9 +951,9 @@ internal static partial class MSBuildHelper
943
951
  }
944
952
  }
945
953
 
946
- internal static void ThrowOnMissingPackages(string output)
954
+ private static void ThrowOnMissingPackages(string output)
947
955
  {
948
- var missingPackagesPattern = new Regex(@"Package '(?<PackageName>[^'].*)' is not found on source");
956
+ var missingPackagesPattern = new Regex(@"Package '(?<PackageName>[^']*)' is not found on source");
949
957
  var matchCollection = missingPackagesPattern.Matches(output);
950
958
  var missingPackages = matchCollection.Select(m => m.Groups["PackageName"].Value).Distinct().ToArray();
951
959
  if (missingPackages.Length > 0)
@@ -954,6 +962,16 @@ internal static partial class MSBuildHelper
954
962
  }
955
963
  }
956
964
 
965
+ private static void ThrowOnUnresolvableDependencies(string output)
966
+ {
967
+ var unresolvablePackagePattern = new Regex(@"Unable to resolve dependencies\. '(?<PackageName>[^ ]+) (?<PackageVersion>[^']+)'");
968
+ var match = unresolvablePackagePattern.Match(output);
969
+ if (match.Success)
970
+ {
971
+ throw new UpdateNotPossibleException([$"{match.Groups["PackageName"].Value}.{match.Groups["PackageVersion"].Value}"]);
972
+ }
973
+ }
974
+
957
975
  internal static bool TryGetGlobalJsonPath(string repoRootPath, string workspacePath, [NotNullWhen(returnValue: true)] out string? globalJsonPath)
958
976
  {
959
977
  globalJsonPath = PathHelper.GetFileInDirectoryOrParent(workspacePath, repoRootPath, "global.json", caseSensitive: false);
@@ -478,6 +478,61 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
478
478
  );
479
479
  }
480
480
 
481
+ [Fact]
482
+ public async Task SafeVersionsPropertyIsHonored()
483
+ {
484
+ await TestAnalyzeAsync(
485
+ packages:
486
+ [
487
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"), // initially this
488
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.1.0", "net8.0"), // should update to this due to `SafeVersions`
489
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.2.0", "net8.0"), // this should not be considered
490
+ ],
491
+ discovery: new()
492
+ {
493
+ Path = "/",
494
+ Projects = [
495
+ new()
496
+ {
497
+ FilePath = "./project.csproj",
498
+ TargetFrameworks = ["net8.0"],
499
+ Dependencies = [
500
+ new("Some.Package", "1.0.0", DependencyType.PackageReference),
501
+ ],
502
+ ReferencedProjectPaths = [],
503
+ ImportedFiles = [],
504
+ AdditionalFiles = [],
505
+ },
506
+ ],
507
+ },
508
+ dependencyInfo: new()
509
+ {
510
+ Name = "Some.Package",
511
+ Version = "1.0.0",
512
+ IgnoredVersions = [],
513
+ IsVulnerable = false,
514
+ Vulnerabilities = [
515
+ new()
516
+ {
517
+ DependencyName = "Some.Package",
518
+ PackageManager = "nuget",
519
+ VulnerableVersions = [Requirement.Parse(">= 1.0.0, < 1.1.0")],
520
+ SafeVersions = [Requirement.Parse("= 1.1.0")]
521
+ }
522
+ ],
523
+ },
524
+ expectedResult: new()
525
+ {
526
+ UpdatedVersion = "1.1.0",
527
+ CanUpdate = true,
528
+ VersionComesFromMultiDependencyProperty = false,
529
+ UpdatedDependencies = [
530
+ new("Some.Package", "1.1.0", DependencyType.Unknown, TargetFrameworks: ["net8.0"]),
531
+ ],
532
+ }
533
+ );
534
+ }
535
+
481
536
  [Fact]
482
537
  public async Task VersionFinderCanHandle404FromPackageSource_V2()
483
538
  {