dependabot-nuget 0.277.0 → 0.279.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/build +1 -1
  3. data/helpers/lib/NuGetUpdater/.editorconfig +1 -0
  4. data/helpers/lib/NuGetUpdater/Directory.Build.props +1 -0
  5. data/helpers/lib/NuGetUpdater/Directory.Common.props +1 -1
  6. data/helpers/lib/NuGetUpdater/Directory.Packages.props +6 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +42 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +1 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +132 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +2 -3
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +94 -85
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +2 -2
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Requirement.cs +2 -2
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +47 -41
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +2 -1
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/AllowedUpdate.cs +6 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreatePullRequest.cs +18 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFile.cs +18 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +6 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/IncrementMetric.cs +7 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +49 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +11 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobFile.cs +6 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobSource.cs +11 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/MarkAsProcessed.cs +9 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs +6 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ReportedDependency.cs +16 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ReportedRequirement.cs +9 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/RequirementSource.cs +7 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +6 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatedDependencyList.cs +7 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +64 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +12 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunResult.cs +13 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +328 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +28 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +1 -1
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +53 -37
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +5 -5
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +34 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +2 -4
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/RequirementTests.cs +4 -1
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +10 -1
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +315 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +60 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs +41 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +69 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +8 -8
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +10 -1
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +22 -0
  52. data/helpers/lib/NuGetUpdater/global.json +1 -1
  53. data/lib/dependabot/nuget/file_fetcher.rb +17 -0
  54. data/lib/dependabot/nuget/file_updater.rb +5 -1
  55. data/lib/dependabot/nuget/native_helpers.rb +4 -1
  56. data/lib/dependabot/nuget/requirement.rb +2 -0
  57. data/lib/dependabot/nuget/update_checker/repository_finder.rb +26 -2
  58. metadata +33 -5
@@ -0,0 +1,18 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ namespace NuGetUpdater.Core.Run.ApiModel;
4
+
5
+ public sealed record CreatePullRequest
6
+ {
7
+ public required ReportedDependency[] Dependencies { get; init; }
8
+ [JsonPropertyName("updated-dependency-files")]
9
+ public required DependencyFile[] UpdatedDependencyFiles { get; init; }
10
+ [JsonPropertyName("base-commit-sha")]
11
+ public required string BaseCommitSha { get; init; }
12
+ [JsonPropertyName("commit-message")]
13
+ public required string CommitMessage { get; init; }
14
+ [JsonPropertyName("pr-title")]
15
+ public required string PrTitle { get; init; }
16
+ [JsonPropertyName("pr-body")]
17
+ public required string PrBody { get; init; }
18
+ }
@@ -0,0 +1,18 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ namespace NuGetUpdater.Core.Run.ApiModel;
4
+
5
+ public sealed record DependencyFile
6
+ {
7
+ public required string Name { get; init; }
8
+ public required string Content { get; init; }
9
+ public required string Directory { get; init; }
10
+ public string Type { get; init; } = "file"; // TODO: enum
11
+ [JsonPropertyName("support_file")]
12
+ public bool SupportFile { get; init; } = false;
13
+ [JsonPropertyName("content_encoding")]
14
+ public string ContentEncoding { get; init; } = "utf-8";
15
+ public bool Deleted { get; init; } = false;
16
+ public string Operation { get; init; } = "update"; // TODO: enum
17
+ public string? Mode { get; init; } = null; // TODO: what is this?
18
+ }
@@ -0,0 +1,6 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record DependencyFileNotFound : JobErrorBase
4
+ {
5
+ public override string Type => "dependency_file_not_found";
6
+ }
@@ -0,0 +1,7 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public sealed record IncrementMetric
4
+ {
5
+ public required string Metric { get; init; }
6
+ public Dictionary<string, string> Tags { get; init; } = new();
7
+ }
@@ -0,0 +1,49 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public sealed record Job
4
+ {
5
+ public required string PackageManager { get; init; }
6
+ public AllowedUpdate[]? AllowedUpdates { get; init; } = null;
7
+ public bool Debug { get; init; } = false;
8
+ public object[]? DependencyGroups { get; init; } = null;
9
+ public object[]? Dependencies { get; init; } = null;
10
+ public string? DependencyGroupToRefresh { get; init; } = null;
11
+ public object[]? ExistingPullRequests { get; init; } = null;
12
+ public object[]? ExistingGroupPullRequests { get; init; } = null;
13
+ public Dictionary<string, object>? Experiments { get; init; } = null;
14
+ public object[]? IgnoreConditions { get; init; } = null;
15
+ public bool LockfileOnly { get; init; } = false;
16
+ public string? RequirementsUpdateStrategy { get; init; } = null;
17
+ public object[]? SecurityAdvisories { get; init; } = null;
18
+ public bool SecurityUpdatesOnly { get; init; } = false;
19
+ public required JobSource Source { get; init; }
20
+ public bool UpdateSubdependencies { get; init; } = false;
21
+ public bool UpdatingAPullRequest { get; init; } = false;
22
+ public bool VendorDependencies { get; init; } = false;
23
+ public bool RejectExternalCode { get; init; } = false;
24
+ public bool RepoPrivate { get; init; } = false;
25
+ public object? CommitMessageOptions { get; init; } = null;
26
+ public object[]? CredentialsMetadata { get; init; } = null;
27
+ public int MaxUpdaterRunTime { get; init; } = 0;
28
+
29
+ public IEnumerable<string> GetAllDirectories()
30
+ {
31
+ var returnedADirectory = false;
32
+ if (Source.Directory is not null)
33
+ {
34
+ returnedADirectory = true;
35
+ yield return Source.Directory;
36
+ }
37
+
38
+ foreach (var directory in Source.Directories ?? [])
39
+ {
40
+ returnedADirectory = true;
41
+ yield return directory;
42
+ }
43
+
44
+ if (!returnedADirectory)
45
+ {
46
+ yield return "/";
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,11 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ namespace NuGetUpdater.Core.Run.ApiModel;
4
+
5
+ public abstract record JobErrorBase
6
+ {
7
+ [JsonPropertyName("error-type")]
8
+ public abstract string Type { get; }
9
+ [JsonPropertyName("error-details")]
10
+ public required string Details { get; init; }
11
+ }
@@ -0,0 +1,6 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public sealed record JobFile
4
+ {
5
+ public required Job Job { get; init; }
6
+ }
@@ -0,0 +1,11 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public sealed class JobSource
4
+ {
5
+ public required string Provider { get; init; }
6
+ public required string Repo { get; init; }
7
+ public string? Directory { get; init; } = null;
8
+ public string[]? Directories { get; init; } = null;
9
+ public string? Hostname { get; init; } = null;
10
+ public string? ApiEndpoint { get; init; } = null;
11
+ }
@@ -0,0 +1,9 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ namespace NuGetUpdater.Core.Run.ApiModel;
4
+
5
+ public sealed record MarkAsProcessed
6
+ {
7
+ [JsonPropertyName("base-commit-sha")]
8
+ public required string BaseCommitSha { get; init; }
9
+ }
@@ -0,0 +1,6 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record PrivateSourceAuthenticationFailure : JobErrorBase
4
+ {
5
+ public override string Type => "private_source_authentication_failure";
6
+ }
@@ -0,0 +1,16 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ namespace NuGetUpdater.Core.Run.ApiModel;
4
+
5
+ public sealed record ReportedDependency
6
+ {
7
+ public required string Name { get; init; }
8
+ public required string? Version { get; init; }
9
+ public required ReportedRequirement[] Requirements { get; init; }
10
+ [JsonPropertyName("previous-version")]
11
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
12
+ public string? PreviousVersion { get; init; } = null;
13
+ [JsonPropertyName("previous-requirements")]
14
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
15
+ public ReportedRequirement[]? PreviousRequirements { get; init; } = null;
16
+ }
@@ -0,0 +1,9 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public sealed record ReportedRequirement
4
+ {
5
+ public required string Requirement { get; init; }
6
+ public required string File { get; init; }
7
+ public string[] Groups { get; init; } = Array.Empty<string>();
8
+ public RequirementSource? Source { get; init; } = null;
9
+ }
@@ -0,0 +1,7 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public sealed record RequirementSource
4
+ {
5
+ public required string? SourceUrl { get; init; }
6
+ public string Type { get; init; } = "nuget_repo";
7
+ }
@@ -0,0 +1,6 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record UnknownError : JobErrorBase
4
+ {
5
+ public override string Type => "unknown_error";
6
+ }
@@ -0,0 +1,7 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public sealed record UpdatedDependencyList
4
+ {
5
+ public required ReportedDependency[] Dependencies { get; init; }
6
+ public required string[] DependencyFiles { get; init; }
7
+ }
@@ -0,0 +1,64 @@
1
+ using System.Text;
2
+ using System.Text.Json;
3
+ using System.Text.Json.Serialization;
4
+
5
+ using NuGetUpdater.Core.Run.ApiModel;
6
+
7
+ namespace NuGetUpdater.Core.Run;
8
+
9
+ public class HttpApiHandler : IApiHandler
10
+ {
11
+ private static readonly HttpClient HttpClient = new();
12
+
13
+ public static readonly JsonSerializerOptions SerializerOptions = new()
14
+ {
15
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
16
+ Converters = { new JsonStringEnumConverter() },
17
+ };
18
+
19
+ private readonly string _apiUrl;
20
+ private readonly string _jobId;
21
+
22
+ public HttpApiHandler(string apiUrl, string jobId)
23
+ {
24
+ _apiUrl = apiUrl.TrimEnd('/');
25
+ _jobId = jobId;
26
+ }
27
+
28
+ public async Task RecordUpdateJobError(JobErrorBase error)
29
+ {
30
+ await PostAsJson("record_update_job_error", error);
31
+ }
32
+
33
+ public async Task UpdateDependencyList(UpdatedDependencyList updatedDependencyList)
34
+ {
35
+ await PostAsJson("update_dependency_list", updatedDependencyList);
36
+ }
37
+
38
+ public async Task IncrementMetric(IncrementMetric incrementMetric)
39
+ {
40
+ await PostAsJson("increment_metric", incrementMetric);
41
+ }
42
+
43
+ public async Task CreatePullRequest(CreatePullRequest createPullRequest)
44
+ {
45
+ await PostAsJson("create_pull_request", createPullRequest);
46
+ }
47
+
48
+ public async Task MarkAsProcessed(MarkAsProcessed markAsProcessed)
49
+ {
50
+ await PostAsJson("mark_as_processed", markAsProcessed);
51
+ }
52
+
53
+ private async Task PostAsJson(string endpoint, object body)
54
+ {
55
+ var wrappedBody = new
56
+ {
57
+ Data = body,
58
+ };
59
+ var payload = JsonSerializer.Serialize(wrappedBody, SerializerOptions);
60
+ var content = new StringContent(payload, Encoding.UTF8, "application/json");
61
+ var response = await HttpClient.PostAsync($"{_apiUrl}/update_jobs/{_jobId}/{endpoint}", content);
62
+ var _ = response.EnsureSuccessStatusCode();
63
+ }
64
+ }
@@ -0,0 +1,12 @@
1
+ using NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ namespace NuGetUpdater.Core.Run;
4
+
5
+ public interface IApiHandler
6
+ {
7
+ Task RecordUpdateJobError(JobErrorBase error);
8
+ Task UpdateDependencyList(UpdatedDependencyList updatedDependencyList);
9
+ Task IncrementMetric(IncrementMetric incrementMetric);
10
+ Task CreatePullRequest(CreatePullRequest createPullRequest);
11
+ Task MarkAsProcessed(MarkAsProcessed markAsProcessed);
12
+ }
@@ -0,0 +1,13 @@
1
+ using System.Text.Json.Serialization;
2
+
3
+ using NuGetUpdater.Core.Run.ApiModel;
4
+
5
+ namespace NuGetUpdater.Core.Run;
6
+
7
+ public sealed record RunResult
8
+ {
9
+ [JsonPropertyName("base64_dependency_files")]
10
+ public required DependencyFile[] Base64DependencyFiles { get; init; }
11
+ [JsonPropertyName("base_commit_sha")]
12
+ public required string BaseCommitSha { get; init; }
13
+ }
@@ -0,0 +1,328 @@
1
+ using System.Net;
2
+ using System.Text;
3
+ using System.Text.Json;
4
+ using System.Text.Json.Serialization;
5
+
6
+ using NuGetUpdater.Core.Analyze;
7
+ using NuGetUpdater.Core.Discover;
8
+ using NuGetUpdater.Core.Run.ApiModel;
9
+
10
+ namespace NuGetUpdater.Core.Run;
11
+
12
+ public class RunWorker
13
+ {
14
+ private readonly IApiHandler _apiHandler;
15
+ private readonly Logger _logger;
16
+
17
+ internal static readonly JsonSerializerOptions SerializerOptions = new()
18
+ {
19
+ PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower,
20
+ WriteIndented = true,
21
+ Converters = { new JsonStringEnumConverter() },
22
+ };
23
+
24
+ public RunWorker(IApiHandler apiHandler, Logger logger)
25
+ {
26
+ _apiHandler = apiHandler;
27
+ _logger = logger;
28
+ }
29
+
30
+ public async Task RunAsync(FileInfo jobFilePath, DirectoryInfo repoContentsPath, string baseCommitSha, FileInfo outputFilePath)
31
+ {
32
+ var jobFileContent = await File.ReadAllTextAsync(jobFilePath.FullName);
33
+ var jobWrapper = Deserialize(jobFileContent);
34
+ var result = await RunAsync(jobWrapper.Job, repoContentsPath, baseCommitSha);
35
+ var resultJson = JsonSerializer.Serialize(result, SerializerOptions);
36
+ await File.WriteAllTextAsync(outputFilePath.FullName, resultJson);
37
+ }
38
+
39
+ public Task<RunResult> RunAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha)
40
+ {
41
+ return RunWithErrorHandlingAsync(job, repoContentsPath, baseCommitSha);
42
+ }
43
+
44
+ private async Task<RunResult> RunWithErrorHandlingAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha)
45
+ {
46
+ JobErrorBase? error = null;
47
+ string[] lastUsedPackageSourceUrls = []; // used for error reporting below
48
+ var runResult = new RunResult()
49
+ {
50
+ Base64DependencyFiles = [],
51
+ BaseCommitSha = baseCommitSha,
52
+ };
53
+
54
+ try
55
+ {
56
+ MSBuildHelper.RegisterMSBuild(repoContentsPath.FullName, repoContentsPath.FullName);
57
+
58
+ var allDependencyFiles = new Dictionary<string, DependencyFile>();
59
+ foreach (var directory in job.GetAllDirectories())
60
+ {
61
+ var localPath = PathHelper.JoinPath(repoContentsPath.FullName, directory);
62
+ lastUsedPackageSourceUrls = NuGetContext.GetPackageSourceUrls(localPath);
63
+ var result = await RunForDirectory(job, repoContentsPath, directory, baseCommitSha);
64
+ foreach (var dependencyFile in result.Base64DependencyFiles)
65
+ {
66
+ allDependencyFiles[dependencyFile.Name] = dependencyFile;
67
+ }
68
+ }
69
+
70
+ runResult = new RunResult()
71
+ {
72
+ Base64DependencyFiles = allDependencyFiles.Values.ToArray(),
73
+ BaseCommitSha = baseCommitSha,
74
+ };
75
+ }
76
+ catch (HttpRequestException ex)
77
+ when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
78
+ {
79
+ error = new PrivateSourceAuthenticationFailure()
80
+ {
81
+ Details = $"({string.Join("|", lastUsedPackageSourceUrls)})",
82
+ };
83
+ }
84
+ catch (MissingFileException ex)
85
+ {
86
+ error = new DependencyFileNotFound()
87
+ {
88
+ Details = ex.FilePath,
89
+ };
90
+ }
91
+ catch (Exception ex)
92
+ {
93
+ error = new UnknownError()
94
+ {
95
+ Details = ex.ToString(),
96
+ };
97
+ }
98
+
99
+ if (error is not null)
100
+ {
101
+ await _apiHandler.RecordUpdateJobError(error);
102
+ }
103
+
104
+ await _apiHandler.MarkAsProcessed(new() { BaseCommitSha = baseCommitSha });
105
+
106
+ return runResult;
107
+ }
108
+
109
+ private async Task<RunResult> RunForDirectory(Job job, DirectoryInfo repoContentsPath, string repoDirectory, string baseCommitSha)
110
+ {
111
+ var discoveryWorker = new DiscoveryWorker(_logger);
112
+ var discoveryResult = await discoveryWorker.RunAsync(repoContentsPath.FullName, repoDirectory);
113
+
114
+ _logger.Log("Discovery JSON content:");
115
+ _logger.Log(JsonSerializer.Serialize(discoveryResult, DiscoveryWorker.SerializerOptions));
116
+
117
+ // report dependencies
118
+ var discoveredUpdatedDependencies = GetUpdatedDependencyListFromDiscovery(discoveryResult);
119
+ await _apiHandler.UpdateDependencyList(discoveredUpdatedDependencies);
120
+
121
+ // TODO: pull out relevant dependencies, then check each for updates and track the changes
122
+ // TODO: for each top-level dependency, _or_ specific dependency (if security, use transitive)
123
+ var originalDependencyFileContents = new Dictionary<string, string>();
124
+ var allowedUpdates = job.AllowedUpdates ?? [];
125
+ var actualUpdatedDependencies = new List<ReportedDependency>();
126
+ if (allowedUpdates.Any(a => a.UpdateType == "all"))
127
+ {
128
+ await _apiHandler.IncrementMetric(new()
129
+ {
130
+ Metric = "updater.started",
131
+ Tags = { ["operation"] = "group_update_all_versions" },
132
+ });
133
+
134
+ // track original contents for later handling
135
+ foreach (var project in discoveryResult.Projects)
136
+ {
137
+ // TODO: include global.json, etc.
138
+ var path = Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix().EnsurePrefix("/");
139
+ var localPath = Path.Join(repoContentsPath.FullName, discoveryResult.Path, project.FilePath);
140
+ var content = await File.ReadAllTextAsync(localPath);
141
+ originalDependencyFileContents[path] = content;
142
+ }
143
+
144
+ // do update
145
+ _logger.Log($"Running update in directory {repoDirectory}");
146
+ foreach (var project in discoveryResult.Projects)
147
+ {
148
+ foreach (var dependency in project.Dependencies.Where(d => !d.IsTransitive))
149
+ {
150
+ if (dependency.Name == "Microsoft.NET.Sdk")
151
+ {
152
+ // this can't be updated
153
+ // TODO: pull this out of discovery?
154
+ continue;
155
+ }
156
+
157
+ if (dependency.Version is null)
158
+ {
159
+ // if we don't know the version, there's nothing we can do
160
+ continue;
161
+ }
162
+
163
+ var analyzeWorker = new AnalyzeWorker(_logger);
164
+ var dependencyInfo = new DependencyInfo()
165
+ {
166
+ Name = dependency.Name,
167
+ Version = dependency.Version!,
168
+ IsVulnerable = false,
169
+ IgnoredVersions = [],
170
+ Vulnerabilities = [],
171
+ };
172
+ var analysisResult = await analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
173
+ // TODO: log analysisResult
174
+ if (analysisResult.CanUpdate)
175
+ {
176
+ // TODO: this is inefficient, but not likely causing a bottleneck
177
+ var previousDependency = discoveredUpdatedDependencies.Dependencies
178
+ .Single(d => d.Name == dependency.Name && d.Requirements.Single().File == Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix().EnsurePrefix("/"));
179
+ var updatedDependency = new ReportedDependency()
180
+ {
181
+ Name = dependency.Name,
182
+ Version = analysisResult.UpdatedVersion,
183
+ Requirements =
184
+ [
185
+ new ReportedRequirement()
186
+ {
187
+ File = Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix().EnsurePrefix("/"),
188
+ Requirement = analysisResult.UpdatedVersion,
189
+ Groups = previousDependency.Requirements.Single().Groups,
190
+ Source = new RequirementSource()
191
+ {
192
+ SourceUrl = analysisResult.UpdatedDependencies.Single(d => d.Name == dependency.Name).InfoUrl,
193
+ },
194
+ }
195
+ ],
196
+ PreviousVersion = dependency.Version,
197
+ PreviousRequirements = previousDependency.Requirements,
198
+ };
199
+
200
+ var updateWorker = new UpdaterWorker(_logger);
201
+ var dependencyFilePath = Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix();
202
+ var updateResult = await updateWorker.RunAsync(repoContentsPath.FullName, dependencyFilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: false);
203
+ // TODO: need to report if anything was actually updated
204
+ if (updateResult.ErrorType is null || updateResult.ErrorType == ErrorType.None)
205
+ {
206
+ actualUpdatedDependencies.Add(updatedDependency);
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ // create PR - we need to manually check file contents; we can't easily use `git status` in tests
213
+ var updatedDependencyFiles = new List<DependencyFile>();
214
+ foreach (var project in discoveryResult.Projects)
215
+ {
216
+ var path = Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix().EnsurePrefix("/");
217
+ var localPath = Path.Join(repoContentsPath.FullName, discoveryResult.Path, project.FilePath);
218
+ var updatedContent = await File.ReadAllTextAsync(localPath);
219
+ var originalContent = originalDependencyFileContents[path];
220
+ if (updatedContent != originalContent)
221
+ {
222
+ updatedDependencyFiles.Add(new DependencyFile()
223
+ {
224
+ Name = project.FilePath,
225
+ Content = updatedContent,
226
+ Directory = discoveryResult.Path,
227
+ });
228
+ }
229
+ }
230
+
231
+ if (updatedDependencyFiles.Count > 0)
232
+ {
233
+ var createPullRequest = new CreatePullRequest()
234
+ {
235
+ Dependencies = actualUpdatedDependencies.ToArray(),
236
+ UpdatedDependencyFiles = updatedDependencyFiles.ToArray(),
237
+ BaseCommitSha = baseCommitSha,
238
+ CommitMessage = "TODO: message",
239
+ PrTitle = "TODO: title",
240
+ PrBody = "TODO: body",
241
+ };
242
+ await _apiHandler.CreatePullRequest(createPullRequest);
243
+ // TODO: log updated dependencies to console
244
+ }
245
+ else
246
+ {
247
+ // TODO: log or throw if nothing was updated, but was expected to be
248
+ }
249
+ }
250
+ else
251
+ {
252
+ // TODO: throw if no updates performed
253
+ }
254
+
255
+ var result = new RunResult()
256
+ {
257
+ Base64DependencyFiles = originalDependencyFileContents.Select(kvp => new DependencyFile()
258
+ {
259
+ Name = Path.GetFileName(kvp.Key),
260
+ Content = Convert.ToBase64String(Encoding.UTF8.GetBytes(kvp.Value)),
261
+ Directory = Path.GetDirectoryName(kvp.Key)!.NormalizePathToUnix(),
262
+ }).ToArray(),
263
+ BaseCommitSha = baseCommitSha,
264
+ };
265
+ return result;
266
+ }
267
+
268
+ internal static UpdatedDependencyList GetUpdatedDependencyListFromDiscovery(WorkspaceDiscoveryResult discoveryResult)
269
+ {
270
+ string GetFullRepoPath(string path)
271
+ {
272
+ // ensures `path\to\file` is `/path/to/file`
273
+ return Path.Join(discoveryResult.Path, path).NormalizePathToUnix().NormalizeUnixPathParts().EnsurePrefix("/");
274
+ }
275
+
276
+ var auxiliaryFiles = new List<string>();
277
+ if (discoveryResult.GlobalJson is not null)
278
+ {
279
+ auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.GlobalJson.FilePath));
280
+ }
281
+ if (discoveryResult.DotNetToolsJson is not null)
282
+ {
283
+ auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.DotNetToolsJson.FilePath));
284
+ }
285
+ if (discoveryResult.DirectoryPackagesProps is not null)
286
+ {
287
+ auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.DirectoryPackagesProps.FilePath));
288
+ }
289
+
290
+ var updatedDependencyList = new UpdatedDependencyList()
291
+ {
292
+ Dependencies = discoveryResult.Projects.SelectMany(p =>
293
+ {
294
+ return p.Dependencies.Where(d => d.Version is not null).Select(d =>
295
+ new ReportedDependency()
296
+ {
297
+ Name = d.Name,
298
+ Requirements = d.IsTransitive ? [] : [new ReportedRequirement()
299
+ {
300
+ File = GetFullRepoPath(p.FilePath),
301
+ Requirement = d.Version!,
302
+ Groups = ["dependencies"],
303
+ }],
304
+ Version = d.Version,
305
+ }
306
+ );
307
+ }).ToArray(),
308
+ DependencyFiles = discoveryResult.Projects.Select(p => GetFullRepoPath(p.FilePath)).Concat(auxiliaryFiles).ToArray(),
309
+ };
310
+ return updatedDependencyList;
311
+ }
312
+
313
+ public static JobFile Deserialize(string json)
314
+ {
315
+ var jobFile = JsonSerializer.Deserialize<JobFile>(json, SerializerOptions);
316
+ if (jobFile is null)
317
+ {
318
+ throw new InvalidOperationException("Unable to deserialize job wrapper.");
319
+ }
320
+
321
+ if (jobFile.Job.PackageManager != "nuget")
322
+ {
323
+ throw new InvalidOperationException("Package manager must be 'nuget'");
324
+ }
325
+
326
+ return jobFile;
327
+ }
328
+ }
@@ -0,0 +1,28 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ internal static class LockFileUpdater
4
+ {
5
+ public static async Task UpdateLockFileAsync(
6
+ string repoRootPath,
7
+ string projectPath,
8
+ Logger logger)
9
+ {
10
+ var projectDirectory = Path.GetDirectoryName(projectPath);
11
+ var lockPath = Path.Combine(projectDirectory, "packages.lock.json");
12
+ logger.Log($" Updating lock file");
13
+ if (!File.Exists(lockPath))
14
+ {
15
+ logger.Log($" File [{Path.GetRelativePath(repoRootPath, lockPath)}] does not exist.");
16
+ return;
17
+ }
18
+
19
+ await MSBuildHelper.SidelineGlobalJsonAsync(projectDirectory, repoRootPath, async () =>
20
+ {
21
+ var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", ["restore", "--force-evaluate", projectPath], workingDirectory: projectDirectory);
22
+ if (exitCode != 0)
23
+ {
24
+ logger.Log($" Lock file update failed.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
25
+ }
26
+ }, retainMSBuildSdks: true);
27
+ }
28
+ }
@@ -231,7 +231,7 @@ internal static class SdkPackageUpdater
231
231
  logger.Log($" Adding [{dependencyName}/{newDependencyVersion}] as a top-level package reference.");
232
232
 
233
233
  // see https://learn.microsoft.com/nuget/consume-packages/install-use-packages-dotnet-cli
234
- var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"add {projectPath} package {dependencyName} --version {newDependencyVersion}", workingDirectory: projectDirectory);
234
+ var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", ["add", projectPath, "package", dependencyName, "--version", newDependencyVersion], workingDirectory: projectDirectory);
235
235
  MSBuildHelper.ThrowOnUnauthenticatedFeed(stdout);
236
236
  if (exitCode != 0)
237
237
  {