dependabot-nuget 0.293.0 → 0.295.0

Sign up to get free protection for your applications and to get access to all the features.
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
  {