dependabot-nuget 0.315.0 → 0.316.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +1 -1
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +6 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ClosePullRequest.cs +15 -0
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreatePullRequest.cs +47 -0
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyGroup.cs +60 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +151 -23
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +4 -18
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PullRequestExistsForSecurityUpdate.cs +15 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateDependencyNotFound.cs +9 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateIgnored.cs +10 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateNotFound.cs +11 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateNotPossible.cs +13 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatePullRequest.cs +6 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ModifiedFilesTracker.cs +151 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +78 -32
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +99 -111
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +169 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +271 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/IUpdateHandler.cs +22 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +192 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +187 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +175 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +43 -2
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ILogger.cs +17 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +15 -9
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MarkdownListBuilder.cs +65 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/ApiModel/JobTests.cs +405 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs +92 -82
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +5 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +67 -1
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +445 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestMessageTests.cs +1 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +260 -20
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +30 -2
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +69 -10
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandlerTests.cs +766 -0
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/GroupUpdateAllVersionsHandlerTests.cs +636 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandlerTests.cs +513 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandlerTests.cs +806 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandlerTests.cs +589 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlerSelectionTests.cs +183 -0
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlersTestsBase.cs +43 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +2 -2
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateOperationBaseTests.cs +121 -7
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +6 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +2 -2
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +51 -0
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MarkdownListBuilderTests.cs +42 -0
  51. metadata +26 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 599cf0f919d861ba0185c00daedeacf4c1025ba1b8db76095de6fc1b11112f17
4
- data.tar.gz: 82e97522f5878ee5bf2b7a0542f3eb7be518b2f6d5c82e4f96d61648ec68483c
3
+ metadata.gz: b42ebbf0a6556e3766516b2b59eec508929837779a8f9ca2042a4d4f7d629626
4
+ data.tar.gz: abb2a60e86d368d9d4a0f09c6311fd7b73f2ac708c1ec73c05d92d1b9409b27d
5
5
  SHA512:
6
- metadata.gz: 0deda57ef624bdd66f09b1e86dd8854e5c97380f74b2518be3a846b8893682ecba6b1f346c1eec26f08769c6d61125b1ead35e6bc4abcac1b87ce4857bf9286c
7
- data.tar.gz: 7d29664a09cd947e72a9adf5875509cfc2fc49c220b3d55eac5c12899449316f706c6186037feff106b32edff0a684fe90934d9eb271b99fb25ee863202776f5
6
+ metadata.gz: 5b266cf823214164397b8e9ed67e5f238364ab95074f33f5cacec47adac5a6cecf147a39ccdb11701e29d0de0ca675b3dabd4a330d75d8ff81dbf7b660c26807
7
+ data.tar.gz: fa309cdd55f81b5507329def15d7a7e6c50b7668e8f1f886cc7c0e1498ac8209aab577d6ba617dbe7ecffda79f93be8d696908b692a07415b78c677905bb9fdb
@@ -50,8 +50,8 @@ public partial class EntryPointTests
50
50
  },
51
51
  expectedUrls:
52
52
  [
53
- "POST /update_jobs/TEST-ID/update_dependency_list",
54
53
  "POST /update_jobs/TEST-ID/increment_metric",
54
+ "POST /update_jobs/TEST-ID/update_dependency_list",
55
55
  "POST /update_jobs/TEST-ID/create_pull_request",
56
56
  "PATCH /update_jobs/TEST-ID/mark_as_processed",
57
57
  ]
@@ -9,4 +9,10 @@ public sealed record WorkspaceDiscoveryResult : NativeResult
9
9
  public ImmutableArray<ProjectDiscoveryResult> Projects { get; init; }
10
10
  public GlobalJsonDiscoveryResult? GlobalJson { get; init; }
11
11
  public DotNetToolsJsonDiscoveryResult? DotNetToolsJson { get; init; }
12
+
13
+ public ProjectDiscoveryResult? GetProjectDiscoveryFromPath(string repoPath)
14
+ {
15
+ var projectDiscovery = Projects.FirstOrDefault(p => System.IO.Path.Join(Path, p.FilePath).FullyNormalizedRootedPath().Equals(repoPath, StringComparison.OrdinalIgnoreCase));
16
+ return projectDiscovery;
17
+ }
12
18
  }
@@ -10,6 +10,7 @@ public record ExperimentsManager
10
10
  public bool InstallDotnetSdks { get; init; } = false;
11
11
  public bool NativeUpdater { get; init; } = false;
12
12
  public bool UseLegacyDependencySolver { get; init; } = false;
13
+ public bool UseLegacyUpdateHandler { get; init; } = false;
13
14
  public bool UseDirectDiscovery { get; init; } = false;
14
15
 
15
16
  public Dictionary<string, object> ToDictionary()
@@ -19,6 +20,7 @@ public record ExperimentsManager
19
20
  ["nuget_install_dotnet_sdks"] = InstallDotnetSdks,
20
21
  ["nuget_native_updater"] = NativeUpdater,
21
22
  ["nuget_legacy_dependency_solver"] = UseLegacyDependencySolver,
23
+ ["nuget_use_legacy_update_handler"] = UseLegacyUpdateHandler,
22
24
  ["nuget_use_direct_discovery"] = UseDirectDiscovery,
23
25
  };
24
26
  }
@@ -30,6 +32,7 @@ public record ExperimentsManager
30
32
  InstallDotnetSdks = IsEnabled(experiments, "nuget_install_dotnet_sdks"),
31
33
  NativeUpdater = IsEnabled(experiments, "nuget_native_updater"),
32
34
  UseLegacyDependencySolver = IsEnabled(experiments, "nuget_legacy_dependency_solver"),
35
+ UseLegacyUpdateHandler = IsEnabled(experiments, "nuget_use_legacy_update_handler"),
33
36
  UseDirectDiscovery = IsEnabled(experiments, "nuget_use_direct_discovery"),
34
37
  };
35
38
  }
@@ -22,4 +22,19 @@ public sealed record ClosePullRequest : MessageBase
22
22
 
23
23
  return report.ToString().Trim();
24
24
  }
25
+
26
+ public static ClosePullRequest WithDependenciesChanged(Job job) => CloseWithReason(job, "dependencies_changed");
27
+ public static ClosePullRequest WithDependenciesRemoved(Job job) => CloseWithReason(job, "dependencies_removed");
28
+ public static ClosePullRequest WithDependencyRemoved(Job job) => CloseWithReason(job, "dependency_removed");
29
+ public static ClosePullRequest WithUpdateNoLongerPossible(Job job) => CloseWithReason(job, "update_no_longer_possible");
30
+ public static ClosePullRequest WithUpToDate(Job job) => CloseWithReason(job, "up_to_date");
31
+
32
+ private static ClosePullRequest CloseWithReason(Job job, string reason)
33
+ {
34
+ return new ClosePullRequest()
35
+ {
36
+ DependencyNames = [.. job.Dependencies.Distinct().OrderBy(n => n, StringComparer.OrdinalIgnoreCase)],
37
+ Reason = reason,
38
+ };
39
+ }
25
40
  }
@@ -1,4 +1,5 @@
1
1
  using System.Text;
2
+ using System.Text.Json;
2
3
  using System.Text.Json.Serialization;
3
4
 
4
5
  using NuGet.Versioning;
@@ -8,17 +9,29 @@ namespace NuGetUpdater.Core.Run.ApiModel;
8
9
  public sealed record CreatePullRequest : MessageBase
9
10
  {
10
11
  public required ReportedDependency[] Dependencies { get; init; }
12
+
11
13
  [JsonPropertyName("updated-dependency-files")]
12
14
  public required DependencyFile[] UpdatedDependencyFiles { get; init; }
15
+
13
16
  [JsonPropertyName("base-commit-sha")]
14
17
  public required string BaseCommitSha { get; init; }
18
+
15
19
  [JsonPropertyName("commit-message")]
16
20
  public required string CommitMessage { get; init; }
21
+
17
22
  [JsonPropertyName("pr-title")]
18
23
  public required string PrTitle { get; init; }
24
+
19
25
  [JsonPropertyName("pr-body")]
20
26
  public required string PrBody { get; init; }
21
27
 
28
+ /// <summary>
29
+ /// This is serialized as either `null` or `{"name": "group-name"}`.
30
+ /// </summary>
31
+ [JsonPropertyName("dependency-group")]
32
+ [JsonConverter(typeof(DependencyGroupConverter))]
33
+ public required string? DependencyGroup { get; init; }
34
+
22
35
  public override string GetReport()
23
36
  {
24
37
  var dependencyNames = Dependencies
@@ -35,4 +48,38 @@ public sealed record CreatePullRequest : MessageBase
35
48
 
36
49
  return report.ToString().Trim();
37
50
  }
51
+
52
+ public class DependencyGroupConverter : JsonConverter<string?>
53
+ {
54
+ public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
55
+ {
56
+ if (reader.TokenType == JsonTokenType.Null)
57
+ {
58
+ return null;
59
+ }
60
+
61
+ var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(ref reader, options);
62
+ if (dict is not null &&
63
+ dict.TryGetValue("name", out var name))
64
+ {
65
+ return name;
66
+ }
67
+
68
+ throw new NotSupportedException("Expected an object with a `name` property.");
69
+ }
70
+
71
+ public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
72
+ {
73
+ if (value is null)
74
+ {
75
+ writer.WriteNullValue();
76
+ }
77
+ else
78
+ {
79
+ writer.WriteStartObject();
80
+ writer.WriteString("name", value);
81
+ writer.WriteEndObject();
82
+ }
83
+ }
84
+ }
38
85
  }
@@ -1,8 +1,68 @@
1
+ using System.Collections.Immutable;
2
+ using System.IO.Enumeration;
3
+
1
4
  namespace NuGetUpdater.Core.Run.ApiModel;
2
5
 
3
6
  public record DependencyGroup
4
7
  {
5
8
  public required string Name { get; init; }
6
9
  public string? AppliesTo { get; init; }
10
+
11
+ // TODO: make more strongly typed, but currently this seems to be:
12
+ // "patterns" => string[] where each element is a wildcard name pattern
13
+ // "exclude-patterns"=> string[] where each element is a wildcard name pattern
14
+ // "dependency-type" => production|development // not used for nuget?
7
15
  public Dictionary<string, object> Rules { get; init; } = new();
16
+
17
+ public GroupMatcher GetGroupMatcher() => GroupMatcher.FromRules(Rules);
18
+ }
19
+
20
+ public class GroupMatcher
21
+ {
22
+ public ImmutableArray<string> Patterns { get; init; } = ImmutableArray<string>.Empty;
23
+ public ImmutableArray<string> ExcludePatterns { get; init; } = ImmutableArray<string>.Empty;
24
+
25
+ public bool IsMatch(string dependencyName)
26
+ {
27
+ var isIncluded = Patterns.Any(p => FileSystemName.MatchesSimpleExpression(p, dependencyName));
28
+ var isExcluded = ExcludePatterns.Any(p => FileSystemName.MatchesSimpleExpression(p, dependencyName));
29
+ var isMatch = isIncluded && !isExcluded;
30
+ return isMatch;
31
+ }
32
+
33
+ public static GroupMatcher FromRules(Dictionary<string, object> rules)
34
+ {
35
+ string[] patterns;
36
+ if (rules.TryGetValue("patterns", out var patternsObject) &&
37
+ patternsObject is string[] patternsArray)
38
+ {
39
+ patterns = patternsArray;
40
+ }
41
+ else
42
+ {
43
+ patterns = ["*"]; // default to matching everything unless excluded below
44
+ }
45
+
46
+ string[] excludePatterns;
47
+ if (rules.TryGetValue("exclude-patterns", out var excludePatternsObject) &&
48
+ excludePatternsObject is string[] excludePatternsArray)
49
+ {
50
+ excludePatterns = excludePatternsArray;
51
+ }
52
+ else
53
+ {
54
+ excludePatterns = [];
55
+ }
56
+
57
+ return new GroupMatcher()
58
+ {
59
+ Patterns = [.. patterns],
60
+ ExcludePatterns = [.. excludePatterns],
61
+ };
62
+ }
63
+ }
64
+
65
+ public static class DependencyGroupExtensions
66
+ {
67
+ public static bool IsSecurity(this DependencyGroup group) => group.AppliesTo == "security-updates";
8
68
  }
@@ -1,9 +1,14 @@
1
1
  using System.Collections.Immutable;
2
+ using System.IO.Enumeration;
2
3
  using System.Text.Json;
3
4
  using System.Text.Json.Serialization;
4
5
 
6
+ using Microsoft.Extensions.FileSystemGlobbing;
7
+
5
8
  using NuGet.Versioning;
6
9
 
10
+ using NuGetUpdater.Core.Analyze;
11
+
7
12
  namespace NuGetUpdater.Core.Run.ApiModel;
8
13
 
9
14
  public sealed record Job
@@ -14,7 +19,9 @@ public sealed record Job
14
19
  [JsonConverter(typeof(NullAsBoolConverter))]
15
20
  public bool Debug { get; init; } = false;
16
21
  public ImmutableArray<DependencyGroup> DependencyGroups { get; init; } = [];
17
- public ImmutableArray<string>? Dependencies { get; init; } = null;
22
+
23
+ [JsonConverter(typeof(NullAsEmptyStringArrayConverter))]
24
+ public ImmutableArray<string> Dependencies { get; init; } = [];
18
25
  public string? DependencyGroupToRefresh { get; init; } = null;
19
26
  public ImmutableArray<PullRequest> ExistingPullRequests { get; init; } = [];
20
27
  public ImmutableArray<GroupPullRequest> ExistingGroupPullRequests { get; init; } = [];
@@ -34,25 +41,29 @@ public sealed record Job
34
41
  public ImmutableArray<Dictionary<string, object>>? CredentialsMetadata { get; init; } = null;
35
42
  public int MaxUpdaterRunTime { get; init; } = 0;
36
43
 
37
- public IEnumerable<string> GetAllDirectories()
44
+ public ImmutableArray<string> GetAllDirectories()
38
45
  {
39
- var returnedADirectory = false;
46
+ var builder = ImmutableArray.CreateBuilder<string>();
40
47
  if (Source.Directory is not null)
41
48
  {
42
- returnedADirectory = true;
43
- yield return Source.Directory;
49
+ builder.Add(Source.Directory);
44
50
  }
45
51
 
46
- foreach (var directory in Source.Directories ?? [])
52
+ builder.AddRange(Source.Directories ?? []);
53
+ if (builder.Count == 0)
47
54
  {
48
- returnedADirectory = true;
49
- yield return directory;
55
+ builder.Add("/");
50
56
  }
51
57
 
52
- if (!returnedADirectory)
53
- {
54
- yield return "/";
55
- }
58
+ return builder.ToImmutable();
59
+ }
60
+
61
+ public ImmutableArray<DependencyGroup> GetRelevantDependencyGroups()
62
+ {
63
+ var appliesToKey = SecurityUpdatesOnly ? "security-updates" : "version-updates";
64
+ var groups = DependencyGroups.Where(g => g.AppliesTo == appliesToKey)
65
+ .ToImmutableArray();
66
+ return groups;
56
67
  }
57
68
 
58
69
  public ImmutableArray<Tuple<string?, ImmutableArray<PullRequestDependency>>> GetAllExistingPullRequests()
@@ -66,27 +77,125 @@ public sealed record Job
66
77
  return existingPullRequests;
67
78
  }
68
79
 
69
- public Tuple<string?, ImmutableArray<PullRequestDependency>>? GetExistingPullRequestForDependency(Dependency dependency)
80
+ public Tuple<string?, ImmutableArray<PullRequestDependency>>? GetExistingPullRequestForDependencies(IEnumerable<Dependency> dependencies, bool considerVersions)
70
81
  {
71
- if (dependency.Version is null)
82
+ if (dependencies.Any(d => d.Version is null))
72
83
  {
73
84
  return null;
74
85
  }
75
86
 
76
- var dependencyVersion = NuGetVersion.Parse(dependency.Version);
87
+ string CreateIdentifier(string dependencyName, string dependencyVersion)
88
+ {
89
+ return $"{dependencyName}/{(considerVersions ? dependencyVersion : null)}";
90
+ }
91
+
92
+ var desiredDependencySet = dependencies.Select(d => CreateIdentifier(d.Name, d.Version!)).ToHashSet(StringComparer.OrdinalIgnoreCase);
77
93
  var existingPullRequests = GetAllExistingPullRequests();
78
- var existingPullRequest = existingPullRequests.FirstOrDefault(pr =>
94
+ var existingPullRequest = existingPullRequests
95
+ .FirstOrDefault(pr =>
96
+ {
97
+ var prDependencySet = pr.Item2.Select(d => CreateIdentifier(d.DependencyName, d.DependencyVersion.ToString())).ToHashSet(StringComparer.OrdinalIgnoreCase);
98
+ return prDependencySet.SetEquals(desiredDependencySet);
99
+ });
100
+ return existingPullRequest;
101
+ }
102
+
103
+ public bool IsDependencyIgnored(string dependencyName, string dependencyVersion)
104
+ {
105
+ var versionsToIgnore = IgnoreConditions
106
+ .Where(c => FileSystemName.MatchesSimpleExpression(c.DependencyName, dependencyName))
107
+ .Select(c => c.VersionRequirement ?? Requirement.Parse(">= 0.0.0")) // no range means ignore everything
108
+ .ToArray();
109
+ var parsedDependencyVersion = NuGetVersion.Parse(dependencyVersion);
110
+ var isIgnored = versionsToIgnore
111
+ .Any(r => r.IsSatisfiedBy(parsedDependencyVersion));
112
+ return isIgnored;
113
+ }
114
+
115
+ public bool IsUpdatePermitted(Dependency dependency)
116
+ {
117
+ if (dependency.Version is null)
79
118
  {
80
- if (pr.Item2.Length == 1 &&
81
- pr.Item2[0].DependencyName.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase) &&
82
- pr.Item2[0].DependencyVersion == dependencyVersion)
119
+ // if we don't know the version, there's nothing we can do
120
+ return false;
121
+ }
122
+
123
+ var version = NuGetVersion.Parse(dependency.Version);
124
+ var dependencyInfo = RunWorker.GetDependencyInfo(this, dependency);
125
+ var isVulnerable = dependencyInfo.Vulnerabilities.Any(v => v.IsVulnerable(version));
126
+
127
+ bool IsAllowed(AllowedUpdate allowedUpdate)
128
+ {
129
+ // check name restriction, if any
130
+ if (allowedUpdate.DependencyName is not null)
83
131
  {
84
- return true;
132
+ var matcher = new Matcher(StringComparison.OrdinalIgnoreCase)
133
+ .AddInclude(allowedUpdate.DependencyName);
134
+ var result = matcher.Match(dependency.Name);
135
+ if (!result.HasMatches)
136
+ {
137
+ return false;
138
+ }
85
139
  }
86
140
 
87
- return false;
88
- });
89
- return existingPullRequest;
141
+ var isSecurityUpdate = allowedUpdate.UpdateType == UpdateType.Security || SecurityUpdatesOnly;
142
+ if (isSecurityUpdate)
143
+ {
144
+ if (isVulnerable)
145
+ {
146
+ // try to match to existing PR
147
+ var dependencyVersion = NuGetVersion.Parse(dependency.Version);
148
+ var existingPullRequests = GetAllExistingPullRequests()
149
+ .Where(pr => pr.Item2.Any(d => d.DependencyName.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase) && d.DependencyVersion >= dependencyVersion))
150
+ .ToArray();
151
+ if (existingPullRequests.Length > 0)
152
+ {
153
+ // found a matching pr...
154
+ if (UpdatingAPullRequest)
155
+ {
156
+ // ...and we've been asked to update it
157
+ return true;
158
+ }
159
+ else
160
+ {
161
+ // ...but no update requested => don't perform any update
162
+ return false;
163
+ }
164
+ }
165
+ else
166
+ {
167
+ // no matching pr...
168
+ if (UpdatingAPullRequest)
169
+ {
170
+ // ...but we've been asked to perform an update => no update possible
171
+ return false;
172
+ }
173
+ else
174
+ {
175
+ // ...and no update specifically requested => create new
176
+ return true;
177
+ }
178
+ }
179
+ }
180
+
181
+ return false;
182
+ }
183
+ else
184
+ {
185
+ // not a security update, so only update if...
186
+ // ...we've been explicitly asked to update this
187
+ if (Dependencies.Any(d => d.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase)))
188
+ {
189
+ return true;
190
+ }
191
+
192
+ // ...no specific update being performed, do it if it's not transitive
193
+ return !dependency.IsTransitive;
194
+ }
195
+ }
196
+
197
+ var allowed = AllowedUpdates.Any(IsAllowed);
198
+ return allowed;
90
199
  }
91
200
  }
92
201
 
@@ -107,3 +216,22 @@ public class NullAsBoolConverter : JsonConverter<bool>
107
216
  writer.WriteBooleanValue(value);
108
217
  }
109
218
  }
219
+
220
+ public class NullAsEmptyStringArrayConverter : JsonConverter<ImmutableArray<string>>
221
+ {
222
+ public override ImmutableArray<string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
223
+ {
224
+ if (reader.TokenType == JsonTokenType.Null)
225
+ {
226
+ return [];
227
+ }
228
+
229
+ return JsonSerializer.Deserialize<ImmutableArray<string>>(ref reader, options);
230
+ }
231
+
232
+ public override void Write(Utf8JsonWriter writer, ImmutableArray<string> value, JsonSerializerOptions options)
233
+ {
234
+ writer.WriteStartArray();
235
+ writer.WriteEndArray();
236
+ }
237
+ }
@@ -5,6 +5,7 @@ using System.Text.Json.Serialization;
5
5
  using Microsoft.Build.Exceptions;
6
6
 
7
7
  using NuGetUpdater.Core.Analyze;
8
+ using NuGetUpdater.Core.Utilities;
8
9
 
9
10
  namespace NuGetUpdater.Core.Run.ApiModel;
10
11
 
@@ -25,24 +26,9 @@ public abstract record JobErrorBase : MessageBase
25
26
  {
26
27
  var report = new StringBuilder();
27
28
  report.AppendLine($"Error type: {Type}");
28
- foreach (var (key, value) in Details)
29
- {
30
- if (this is UnknownError && key == "error-backtrace")
31
- {
32
- // there's nothing meaningful in this field
33
- continue;
34
- }
35
-
36
- var valueString = value.ToString();
37
- if (value is IEnumerable<string> strings)
38
- {
39
- valueString = string.Join(", ", strings);
40
- }
41
-
42
- report.AppendLine($"- {key}: {valueString}");
43
- }
44
-
45
- return report.ToString().Trim();
29
+ report.Append(MarkdownListBuilder.FromObject(Details));
30
+ var fullReport = report.ToString().TrimEnd();
31
+ return fullReport;
46
32
  }
47
33
 
48
34
  public static JobErrorBase ErrorFromException(Exception ex, string jobId, string currentDirectory)
@@ -0,0 +1,15 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record PullRequestExistsForSecurityUpdate : JobErrorBase
4
+ {
5
+ public PullRequestExistsForSecurityUpdate(Dependency[] dependencies)
6
+ : base("pull_request_exists_for_security_update")
7
+ {
8
+ Details["updated-dependencies"] = dependencies.Select(d => new Dictionary<string, object>()
9
+ {
10
+ ["dependency-name"] = d.Name,
11
+ ["dependency-version"] = d.Version!,
12
+ ["dependency-removed"] = false,
13
+ }).ToArray();
14
+ }
15
+ }
@@ -0,0 +1,9 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record SecurityUpdateDependencyNotFound : JobErrorBase
4
+ {
5
+ public SecurityUpdateDependencyNotFound()
6
+ : base("security_update_dependency_not_found")
7
+ {
8
+ }
9
+ }
@@ -0,0 +1,10 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record SecurityUpdateIgnored : JobErrorBase
4
+ {
5
+ public SecurityUpdateIgnored(string dependencyName)
6
+ : base("all_versions_ignored")
7
+ {
8
+ Details["dependency-name"] = dependencyName;
9
+ }
10
+ }
@@ -0,0 +1,11 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record SecurityUpdateNotFound : JobErrorBase
4
+ {
5
+ public SecurityUpdateNotFound(string dependencyName, string dependencyVersion)
6
+ : base("security_update_not_found")
7
+ {
8
+ Details["dependency-name"] = dependencyName;
9
+ Details["dependency-version"] = dependencyVersion;
10
+ }
11
+ }
@@ -0,0 +1,13 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record SecurityUpdateNotPossible : JobErrorBase
4
+ {
5
+ public SecurityUpdateNotPossible(string dependencyName, string latestResolvableVersion, string lowestNonVulnerableVersion, string[] conflictingDependencies)
6
+ : base("security_update_not_possible")
7
+ {
8
+ Details["dependency-name"] = dependencyName;
9
+ Details["latest-resolvable-version"] = latestResolvableVersion;
10
+ Details["lowest-non-vulnerable-version"] = lowestNonVulnerableVersion;
11
+ Details["conflicting-dependencies"] = conflictingDependencies;
12
+ }
13
+ }
@@ -2,6 +2,8 @@ using System.Collections.Immutable;
2
2
  using System.Text;
3
3
  using System.Text.Json.Serialization;
4
4
 
5
+ using static NuGetUpdater.Core.Run.ApiModel.CreatePullRequest;
6
+
5
7
  namespace NuGetUpdater.Core.Run.ApiModel;
6
8
 
7
9
  public sealed record UpdatePullRequest : MessageBase
@@ -24,7 +26,11 @@ public sealed record UpdatePullRequest : MessageBase
24
26
  [JsonPropertyName("commit-message")]
25
27
  public required string CommitMessage { get; init; }
26
28
 
29
+ /// <summary>
30
+ /// This is serialized as either `null` or `{"name": "group-name"}`.
31
+ /// </summary>
27
32
  [JsonPropertyName("dependency-group")]
33
+ [JsonConverter(typeof(DependencyGroupConverter))]
28
34
  public required string? DependencyGroup { get; init; }
29
35
 
30
36
  public override string GetReport()