dependabot-nuget 0.291.0 → 0.293.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.editorconfig +1 -0
  3. data/helpers/lib/NuGetUpdater/.gitignore +1 -0
  4. data/helpers/lib/NuGetUpdater/Directory.Build.props +1 -0
  5. data/helpers/lib/NuGetUpdater/Directory.Packages.props +2 -1
  6. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Correlator.cs +197 -0
  7. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/DotNetPackageCorrelation.csproj +12 -0
  8. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageMapper.cs +68 -0
  9. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/PackageSet.cs +11 -0
  10. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/Release.cs +25 -0
  11. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/ReleasesFile.cs +9 -0
  12. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/RuntimePackages.cs +11 -0
  13. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/Sdk.cs +13 -0
  14. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/SemVerComparer.cs +16 -0
  15. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation/Model/SemVersionConverter.cs +42 -0
  16. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/DotNetPackageCorrelation.Cli.csproj +16 -0
  17. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Cli/Program.cs +32 -0
  18. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/CorrelatorTests.cs +99 -0
  19. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/DotNetPackageCorrelation.Test.csproj +18 -0
  20. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/EndToEndTests.cs +30 -0
  21. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/RuntimePackagesTests.cs +206 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +6 -4
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +1 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +19 -4
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +5 -5
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +17 -5
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +8 -1
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +128 -4
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +8 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +9 -7
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +4 -4
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Extensions.cs +1 -1
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementConverter.cs +19 -1
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/BadRequirementException.cs +9 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +40 -8
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyDiscovery.targets +2 -2
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +65 -23
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +3 -3
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +99 -2
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/EnsureDotNetPackageCorrelation.targets +25 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +15 -5
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +5 -1
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/MissingFileException.cs +2 -1
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs +3 -3
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +4 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/BadRequirement.cs +10 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CommitOptions.cs +1 -1
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +7 -2
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotParseable.cs +15 -0
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +25 -2
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobRepoNotFound.cs +1 -4
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs +1 -1
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +6 -2
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdateNotPossible.cs +1 -1
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +9 -18
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/UnparseableFileException.cs +12 -0
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +1 -1
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +21 -0
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +6 -30
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +12 -1
  61. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +0 -7
  62. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DotNetPackageCorrelationManager.cs +46 -0
  63. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +59 -30
  64. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  65. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProjectHelper.cs +4 -4
  66. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +15 -4
  67. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +15 -9
  68. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +60 -2
  69. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +20 -3
  70. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +56 -0
  71. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +108 -0
  72. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +16 -12
  73. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -0
  74. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +15 -28
  75. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +5 -4
  76. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +84 -40
  77. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestBase.cs +24 -0
  78. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/ExpectedUpdateOperationResult.cs +1 -1
  79. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +25 -11
  80. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +1 -1
  81. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +2 -2
  82. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.LockFile.cs +251 -0
  83. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +14 -8
  84. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +154 -9
  85. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +71 -15
  86. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +38 -20
  87. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/ProjectHelperTests.cs +65 -0
  88. data/helpers/lib/NuGetUpdater/NuGetUpdater.sln +18 -1
  89. data/helpers/lib/NuGetUpdater/global.json +1 -1
  90. data/lib/dependabot/nuget/language.rb +21 -5
  91. data/lib/dependabot/nuget/native_helpers.rb +41 -14
  92. data/lib/dependabot/nuget/package_manager.rb +4 -4
  93. metadata +30 -7
  94. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +0 -11
@@ -0,0 +1,206 @@
1
+ using Semver;
2
+
3
+ using Xunit;
4
+
5
+ namespace DotNetPackageCorrelation;
6
+
7
+ public class RuntimePackagesTests
8
+ {
9
+ [Theory]
10
+ [MemberData(nameof(CorrelatedPackageCanBeFoundData))]
11
+ public void CorrelatedPackageCanBeFound(RuntimePackages runtimePackages, string runtimePackageName, string runtimePackageVersion, string candidatePackageName, string? expectedPackageVersion)
12
+ {
13
+ var packageMapper = PackageMapper.Load(runtimePackages);
14
+ var actualPackageVersion = packageMapper.GetPackageVersionThatShippedWithOtherPackage(runtimePackageName, SemVersion.Parse(runtimePackageVersion), candidatePackageName);
15
+ if (expectedPackageVersion is null)
16
+ {
17
+ Assert.Null(actualPackageVersion);
18
+ }
19
+ else
20
+ {
21
+ Assert.NotNull(actualPackageVersion);
22
+ Assert.Equal(expectedPackageVersion, actualPackageVersion.ToString());
23
+ }
24
+ }
25
+
26
+ public static IEnumerable<object?[]> CorrelatedPackageCanBeFoundData()
27
+ {
28
+ // package not found in specified runtime, but it is in earlier runtime; more recent runtime has that package, but that's not returned
29
+ yield return
30
+ [
31
+ // runtimePackages
32
+ new RuntimePackages()
33
+ {
34
+ Runtimes = new SortedDictionary<SemVersion, PackageSet>(SemVerComparer.Instance)
35
+ {
36
+ {
37
+ SemVersion.Parse("1.0.100"),
38
+ new PackageSet()
39
+ {
40
+ Packages = new SortedDictionary<string, SemVersion>(StringComparer.OrdinalIgnoreCase)
41
+ {
42
+ { "Runtime.Package", SemVersion.Parse("1.0.0") },
43
+ { "Some.Package", SemVersion.Parse("1.0.1") }
44
+ }
45
+ }
46
+ },
47
+ {
48
+ SemVersion.Parse("1.0.101"),
49
+ new PackageSet()
50
+ {
51
+ Packages = new SortedDictionary<string, SemVersion>(StringComparer.OrdinalIgnoreCase)
52
+ {
53
+ // this runtime didn't ship with a new version of "Some.Package", but the earlier release did
54
+ { "Runtime.Package", SemVersion.Parse("1.0.1") }
55
+ }
56
+ }
57
+ },
58
+ {
59
+ SemVersion.Parse("1.0.200"),
60
+ new PackageSet()
61
+ {
62
+ Packages = new SortedDictionary<string, SemVersion>(StringComparer.OrdinalIgnoreCase)
63
+ {
64
+ // the requested package shipped with this runtime, but this runtime isn't the correct version so it's not returned
65
+ { "Runtime.Package", SemVersion.Parse("1.0.2") },
66
+ { "Some.Package", SemVersion.Parse("1.0.2") }
67
+ }
68
+ }
69
+ },
70
+ }
71
+ },
72
+ // runtimePackageName
73
+ "Runtime.Package",
74
+ // runtimePackageVersion
75
+ "1.0.1",
76
+ // candidatePackageName
77
+ "Some.Package",
78
+ // expectedPackageVersion
79
+ "1.0.1"
80
+ ];
81
+
82
+ // package differing in case is found
83
+ yield return
84
+ [
85
+ // runtimePackages
86
+ new RuntimePackages()
87
+ {
88
+ Runtimes = new SortedDictionary<SemVersion, PackageSet>(SemVerComparer.Instance)
89
+ {
90
+ {
91
+ SemVersion.Parse("1.0.100"),
92
+ new PackageSet()
93
+ {
94
+ Packages = new SortedDictionary<string, SemVersion>(StringComparer.OrdinalIgnoreCase)
95
+ {
96
+ { "runtime.package", SemVersion.Parse("1.0.0") },
97
+ { "some.package", SemVersion.Parse("1.0.1") }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ },
103
+ // runtimePackageName
104
+ "Runtime.Package",
105
+ // runtimePackageVersion
106
+ "1.0.0",
107
+ // candidatePackageName
108
+ "Some.Package",
109
+ // expectedPackageVersion
110
+ "1.0.1"
111
+ ];
112
+
113
+ // runtime package not found by name
114
+ yield return
115
+ [
116
+ // runtimePackages
117
+ new RuntimePackages()
118
+ {
119
+ Runtimes = new SortedDictionary<SemVersion, PackageSet>(SemVerComparer.Instance)
120
+ {
121
+ {
122
+ SemVersion.Parse("1.0.100"),
123
+ new PackageSet()
124
+ {
125
+ Packages = new SortedDictionary<string, SemVersion>(StringComparer.OrdinalIgnoreCase)
126
+ {
127
+ { "runtime.package", SemVersion.Parse("1.0.0") },
128
+ { "some.package", SemVersion.Parse("1.0.1") }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ },
134
+ // runtimePackageName
135
+ "Different.Runtime.Package",
136
+ // runtimePackageVersion
137
+ "1.0.0",
138
+ // candidatePackageName
139
+ "Some.Package",
140
+ // expectedPackageVersion
141
+ null
142
+ ];
143
+
144
+ // runtime package not found by version
145
+ yield return
146
+ [
147
+ // runtimePackages
148
+ new RuntimePackages()
149
+ {
150
+ Runtimes = new SortedDictionary<SemVersion, PackageSet>(SemVerComparer.Instance)
151
+ {
152
+ {
153
+ SemVersion.Parse("1.0.100"),
154
+ new PackageSet()
155
+ {
156
+ Packages = new SortedDictionary<string, SemVersion>(StringComparer.OrdinalIgnoreCase)
157
+ {
158
+ { "runtime.package", SemVersion.Parse("1.0.0") },
159
+ { "some.package", SemVersion.Parse("1.0.1") }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ },
165
+ // runtimePackageName
166
+ "Runtime.Package",
167
+ // runtimePackageVersion
168
+ "9.9.9",
169
+ // candidatePackageName
170
+ "Some.Package",
171
+ // expectedPackageVersion
172
+ null
173
+ ];
174
+
175
+ // candidate package not found
176
+ yield return
177
+ [
178
+ // runtimePackages
179
+ new RuntimePackages()
180
+ {
181
+ Runtimes = new SortedDictionary<SemVersion, PackageSet>(SemVerComparer.Instance)
182
+ {
183
+ {
184
+ SemVersion.Parse("1.0.100"),
185
+ new PackageSet()
186
+ {
187
+ Packages = new SortedDictionary<string, SemVersion>(StringComparer.OrdinalIgnoreCase)
188
+ {
189
+ { "runtime.package", SemVersion.Parse("1.0.0") },
190
+ { "some.package", SemVersion.Parse("1.0.1") }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ },
196
+ // runtimePackageName
197
+ "Runtime.Package",
198
+ // runtimePackageVersion
199
+ "1.0.0",
200
+ // candidatePackageName
201
+ "Package.Not.In.This.Runtime",
202
+ // expectedPackageVersion
203
+ null
204
+ ];
205
+ }
206
+ }
@@ -7,6 +7,7 @@ namespace NuGetUpdater.Cli.Commands;
7
7
 
8
8
  internal static class AnalyzeCommand
9
9
  {
10
+ internal static readonly Option<string> JobIdOption = new("--job-id") { IsRequired = true };
10
11
  internal static readonly Option<FileInfo> JobPathOption = new("--job-path") { IsRequired = true };
11
12
  internal static readonly Option<DirectoryInfo> RepoRootOption = new("--repo-root") { IsRequired = true };
12
13
  internal static readonly Option<FileInfo> DependencyFilePathOption = new("--dependency-file-path") { IsRequired = true };
@@ -17,6 +18,7 @@ internal static class AnalyzeCommand
17
18
  {
18
19
  Command command = new("analyze", "Determines how to update a dependency based on the workspace discovery information.")
19
20
  {
21
+ JobIdOption,
20
22
  JobPathOption,
21
23
  RepoRootOption,
22
24
  DependencyFilePathOption,
@@ -26,13 +28,13 @@ internal static class AnalyzeCommand
26
28
 
27
29
  command.TreatUnmatchedTokensAsErrors = true;
28
30
 
29
- command.SetHandler(async (jobPath, repoRoot, discoveryPath, dependencyPath, analysisDirectory) =>
31
+ command.SetHandler(async (jobId, jobPath, repoRoot, discoveryPath, dependencyPath, analysisDirectory) =>
30
32
  {
31
33
  var logger = new ConsoleLogger();
32
- var experimentsManager = await ExperimentsManager.FromJobFileAsync(jobPath.FullName, logger);
33
- var worker = new AnalyzeWorker(experimentsManager, logger);
34
+ var (experimentsManager, _errorResult) = await ExperimentsManager.FromJobFileAsync(jobId, jobPath.FullName);
35
+ var worker = new AnalyzeWorker(jobId, experimentsManager, logger);
34
36
  await worker.RunAsync(repoRoot.FullName, discoveryPath.FullName, dependencyPath.FullName, analysisDirectory.FullName);
35
- }, JobPathOption, RepoRootOption, DiscoveryFilePathOption, DependencyFilePathOption, AnalysisFolderOption);
37
+ }, JobIdOption, JobPathOption, RepoRootOption, DiscoveryFilePathOption, DependencyFilePathOption, AnalysisFolderOption);
36
38
 
37
39
  return command;
38
40
  }
@@ -30,7 +30,7 @@ internal static class CloneCommand
30
30
  var apiHandler = new HttpApiHandler(apiUrl.ToString(), jobId);
31
31
  var logger = new ConsoleLogger();
32
32
  var gitCommandHandler = new ShellGitCommandHandler(logger);
33
- var worker = new CloneWorker(apiHandler, gitCommandHandler, logger);
33
+ var worker = new CloneWorker(jobId, apiHandler, gitCommandHandler);
34
34
  var exitCode = await worker.RunAsync(jobPath, repoContentsPath);
35
35
  setExitCode(exitCode);
36
36
  }, JobPathOption, RepoContentsPathOption, ApiUrlOption, JobIdOption);
@@ -7,6 +7,7 @@ namespace NuGetUpdater.Cli.Commands;
7
7
 
8
8
  internal static class DiscoverCommand
9
9
  {
10
+ internal static readonly Option<string> JobIdOption = new("--job-id") { IsRequired = true };
10
11
  internal static readonly Option<FileInfo> JobPathOption = new("--job-path") { IsRequired = true };
11
12
  internal static readonly Option<DirectoryInfo> RepoRootOption = new("--repo-root") { IsRequired = true };
12
13
  internal static readonly Option<string> WorkspaceOption = new("--workspace") { IsRequired = true };
@@ -16,6 +17,7 @@ internal static class DiscoverCommand
16
17
  {
17
18
  Command command = new("discover", "Generates a report of the workspace dependencies and where they are located.")
18
19
  {
20
+ JobIdOption,
19
21
  JobPathOption,
20
22
  RepoRootOption,
21
23
  WorkspaceOption,
@@ -24,13 +26,26 @@ internal static class DiscoverCommand
24
26
 
25
27
  command.TreatUnmatchedTokensAsErrors = true;
26
28
 
27
- command.SetHandler(async (jobPath, repoRoot, workspace, outputPath) =>
29
+ command.SetHandler(async (jobId, jobPath, repoRoot, workspace, outputPath) =>
28
30
  {
31
+ var (experimentsManager, error) = await ExperimentsManager.FromJobFileAsync(jobId, jobPath.FullName);
32
+ if (error is not null)
33
+ {
34
+ // to make testing easier, this should be a `WorkspaceDiscoveryResult` object
35
+ var discoveryErrorResult = new WorkspaceDiscoveryResult
36
+ {
37
+ Error = error,
38
+ Path = workspace,
39
+ Projects = [],
40
+ };
41
+ await DiscoveryWorker.WriteResultsAsync(repoRoot.FullName, outputPath.FullName, discoveryErrorResult);
42
+ return;
43
+ }
44
+
29
45
  var logger = new ConsoleLogger();
30
- var experimentsManager = await ExperimentsManager.FromJobFileAsync(jobPath.FullName, logger);
31
- var worker = new DiscoveryWorker(experimentsManager, logger);
46
+ var worker = new DiscoveryWorker(jobId, experimentsManager, logger);
32
47
  await worker.RunAsync(repoRoot.FullName, workspace, outputPath.FullName);
33
- }, JobPathOption, RepoRootOption, WorkspaceOption, OutputOption);
48
+ }, JobIdOption, JobPathOption, RepoRootOption, WorkspaceOption, OutputOption);
34
49
 
35
50
  return command;
36
51
  }
@@ -33,12 +33,12 @@ internal static class RunCommand
33
33
  command.SetHandler(async (jobPath, repoContentsPath, apiUrl, jobId, outputPath, baseCommitSha) =>
34
34
  {
35
35
  var apiHandler = new HttpApiHandler(apiUrl.ToString(), jobId);
36
+ var (experimentsManager, _errorResult) = await ExperimentsManager.FromJobFileAsync(jobId, jobPath.FullName);
36
37
  var logger = new ConsoleLogger();
37
- var experimentsManager = await ExperimentsManager.FromJobFileAsync(jobPath.FullName, logger);
38
- var discoverWorker = new DiscoveryWorker(experimentsManager, logger);
39
- var analyzeWorker = new AnalyzeWorker(experimentsManager, logger);
40
- var updateWorker = new UpdaterWorker(experimentsManager, logger);
41
- var worker = new RunWorker(apiHandler, discoverWorker, analyzeWorker, updateWorker, logger);
38
+ var discoverWorker = new DiscoveryWorker(jobId, experimentsManager, logger);
39
+ var analyzeWorker = new AnalyzeWorker(jobId, experimentsManager, logger);
40
+ var updateWorker = new UpdaterWorker(jobId, experimentsManager, logger);
41
+ var worker = new RunWorker(jobId, apiHandler, discoverWorker, analyzeWorker, updateWorker, logger);
42
42
  await worker.RunAsync(jobPath, repoContentsPath, baseCommitSha, outputPath);
43
43
  }, JobPathOption, RepoContentsPathOption, ApiUrlOption, JobIdOption, OutputPathOption, BaseCommitShaOption);
44
44
 
@@ -6,6 +6,7 @@ namespace NuGetUpdater.Cli.Commands;
6
6
 
7
7
  internal static class UpdateCommand
8
8
  {
9
+ internal static readonly Option<string> JobIdOption = new("--job-id") { IsRequired = true };
9
10
  internal static readonly Option<FileInfo> JobPathOption = new("--job-path") { IsRequired = true };
10
11
  internal static readonly Option<DirectoryInfo> RepoRootOption = new("--repo-root", () => new DirectoryInfo(Environment.CurrentDirectory)) { IsRequired = false };
11
12
  internal static readonly Option<FileInfo> SolutionOrProjectFileOption = new("--solution-or-project") { IsRequired = true };
@@ -19,6 +20,7 @@ internal static class UpdateCommand
19
20
  {
20
21
  Command command = new("update", "Applies the changes from an analysis report to update a dependency.")
21
22
  {
23
+ JobIdOption,
22
24
  JobPathOption,
23
25
  RepoRootOption,
24
26
  SolutionOrProjectFileOption,
@@ -30,15 +32,25 @@ internal static class UpdateCommand
30
32
  };
31
33
 
32
34
  command.TreatUnmatchedTokensAsErrors = true;
33
-
34
- command.SetHandler(async (jobPath, repoRoot, solutionOrProjectFile, dependencyName, newVersion, previousVersion, isTransitive, resultOutputPath) =>
35
+ command.SetHandler(async (context) =>
35
36
  {
37
+ // since we have more than 8 arguments, we have to pull them out manually
38
+ var jobId = context.ParseResult.GetValueForOption(JobIdOption)!;
39
+ var jobPath = context.ParseResult.GetValueForOption(JobPathOption)!;
40
+ var repoRoot = context.ParseResult.GetValueForOption(RepoRootOption)!;
41
+ var solutionOrProjectFile = context.ParseResult.GetValueForOption(SolutionOrProjectFileOption)!;
42
+ var dependencyName = context.ParseResult.GetValueForOption(DependencyNameOption)!;
43
+ var newVersion = context.ParseResult.GetValueForOption(NewVersionOption)!;
44
+ var previousVersion = context.ParseResult.GetValueForOption(PreviousVersionOption)!;
45
+ var isTransitive = context.ParseResult.GetValueForOption(IsTransitiveOption);
46
+ var resultOutputPath = context.ParseResult.GetValueForOption(ResultOutputPathOption);
47
+
48
+ var (experimentsManager, _error) = await ExperimentsManager.FromJobFileAsync(jobId, jobPath.FullName);
36
49
  var logger = new ConsoleLogger();
37
- var experimentsManager = await ExperimentsManager.FromJobFileAsync(jobPath.FullName, logger);
38
- var worker = new UpdaterWorker(experimentsManager, logger);
50
+ var worker = new UpdaterWorker(jobId, experimentsManager, logger);
39
51
  await worker.RunAsync(repoRoot.FullName, solutionOrProjectFile.FullName, dependencyName, previousVersion, newVersion, isTransitive, resultOutputPath);
40
52
  setExitCode(0);
41
- }, JobPathOption, RepoRootOption, SolutionOrProjectFileOption, DependencyNameOption, NewVersionOption, PreviousVersionOption, IsTransitiveOption, ResultOutputPathOption);
53
+ });
42
54
 
43
55
  return command;
44
56
  }
@@ -26,6 +26,8 @@ public partial class EntryPointTests
26
26
  await RunAsync(path =>
27
27
  [
28
28
  "analyze",
29
+ "--job-id",
30
+ "TEST-JOB-ID",
29
31
  "--job-path",
30
32
  Path.Combine(path, "job.json"),
31
33
  "--repo-root",
@@ -146,6 +148,8 @@ public partial class EntryPointTests
146
148
  await RunAsync(path =>
147
149
  [
148
150
  "analyze",
151
+ "--job-id",
152
+ "TEST-JOB-ID",
149
153
  "--job-path",
150
154
  Path.Combine(path, "job.json"),
151
155
  "--repo-root",
@@ -235,6 +239,8 @@ public partial class EntryPointTests
235
239
  await RunAsync(path =>
236
240
  [
237
241
  "analyze",
242
+ "--job-id",
243
+ "TEST-JOB-ID",
238
244
  "--job-path",
239
245
  Path.Combine(path, "job.json"),
240
246
  "--repo-root",
@@ -345,7 +351,8 @@ public partial class EntryPointTests
345
351
  {
346
352
  if (args[i] == "--job-path")
347
353
  {
348
- experimentsManager = await ExperimentsManager.FromJobFileAsync(args[i + 1], new TestLogger());
354
+ var experimentsResult = await ExperimentsManager.FromJobFileAsync("TEST-JOB-ID", args[i + 1]);
355
+ experimentsManager = experimentsResult.ExperimentsManager;
349
356
  }
350
357
  }
351
358
 
@@ -1,5 +1,6 @@
1
1
  using System.Text;
2
2
  using System.Text.Json;
3
+ using System.Text.Json.Serialization;
3
4
 
4
5
  using NuGetUpdater.Core;
5
6
  using NuGetUpdater.Core.Discover;
@@ -25,6 +26,8 @@ public partial class EntryPointTests
25
26
  await RunAsync(path =>
26
27
  [
27
28
  "discover",
29
+ "--job-id",
30
+ "TEST-JOB-ID",
28
31
  "--job-path",
29
32
  Path.Combine(path, "job.json"),
30
33
  "--repo-root",
@@ -83,6 +86,8 @@ public partial class EntryPointTests
83
86
  await RunAsync(path =>
84
87
  [
85
88
  "discover",
89
+ "--job-id",
90
+ "TEST-JOB-ID",
86
91
  "--job-path",
87
92
  Path.Combine(path, "job.json"),
88
93
  "--repo-root",
@@ -178,6 +183,8 @@ public partial class EntryPointTests
178
183
  await RunAsync(path =>
179
184
  [
180
185
  "discover",
186
+ "--job-id",
187
+ "TEST-JOB-ID",
181
188
  "--job-path",
182
189
  Path.Combine(path, "job.json"),
183
190
  "--repo-root",
@@ -251,6 +258,8 @@ public partial class EntryPointTests
251
258
  await RunAsync(path =>
252
259
  [
253
260
  "discover",
261
+ "--job-id",
262
+ "TEST-JOB-ID",
254
263
  "--job-path",
255
264
  Path.Combine(path, "job.json"),
256
265
  "--repo-root",
@@ -321,6 +330,8 @@ public partial class EntryPointTests
321
330
  await RunAsync(path =>
322
331
  [
323
332
  "discover",
333
+ "--job-id",
334
+ "TEST-JOB-ID",
324
335
  "--job-path",
325
336
  Path.Combine(path, "job.json"),
326
337
  "--repo-root",
@@ -388,6 +399,85 @@ public partial class EntryPointTests
388
399
  );
389
400
  }
390
401
 
402
+ [Fact]
403
+ public async Task JobFileParseErrorIsReported_InvalidJson()
404
+ {
405
+ using var testDirectory = new TemporaryDirectory();
406
+ var jobFilePath = Path.Combine(testDirectory.DirectoryPath, "job.json");
407
+ var resultFilePath = Path.Combine(testDirectory.DirectoryPath, DiscoveryWorker.DiscoveryResultFileName);
408
+ await File.WriteAllTextAsync(jobFilePath, "not json");
409
+ await RunAsync(path =>
410
+ [
411
+ "discover",
412
+ "--job-id",
413
+ "TEST-JOB-ID",
414
+ "--job-path",
415
+ jobFilePath,
416
+ "--repo-root",
417
+ path,
418
+ "--workspace",
419
+ "/",
420
+ "--output",
421
+ resultFilePath
422
+ ],
423
+ initialFiles: [],
424
+ expectedResult: new()
425
+ {
426
+ Path = "/",
427
+ Projects = [],
428
+ ErrorRegex = "Error deserializing job file contents",
429
+ }
430
+ );
431
+ }
432
+
433
+ [Fact]
434
+ public async Task JobFileParseErrorIsReported_BadRequirement()
435
+ {
436
+ using var testDirectory = new TemporaryDirectory();
437
+ var jobFilePath = Path.Combine(testDirectory.DirectoryPath, "job.json");
438
+ var resultFilePath = Path.Combine(testDirectory.DirectoryPath, DiscoveryWorker.DiscoveryResultFileName);
439
+
440
+ // write a job file with a valid shape, but invalid requirement
441
+ await File.WriteAllTextAsync(jobFilePath, """
442
+ {
443
+ "job": {
444
+ "source": {
445
+ "provider": "github",
446
+ "repo": "test/repo"
447
+ },
448
+ "security-advisories": [
449
+ {
450
+ "dependency-name": "Some.Dependency",
451
+ "affected-versions": ["not a valid requirement"]
452
+ }
453
+ ]
454
+ }
455
+ }
456
+ """);
457
+ await RunAsync(path =>
458
+ [
459
+ "discover",
460
+ "--job-id",
461
+ "TEST-JOB-ID",
462
+ "--job-path",
463
+ jobFilePath,
464
+ "--repo-root",
465
+ path,
466
+ "--workspace",
467
+ "/",
468
+ "--output",
469
+ resultFilePath
470
+ ],
471
+ initialFiles: [],
472
+ expectedResult: new()
473
+ {
474
+ Path = "/",
475
+ Projects = [],
476
+ Error = new Core.Run.ApiModel.BadRequirement("not a valid requirement"),
477
+ }
478
+ );
479
+ }
480
+
391
481
  private static async Task RunAsync(
392
482
  Func<string, string[]> getArgs,
393
483
  TestFile[] initialFiles,
@@ -406,6 +496,7 @@ public partial class EntryPointTests
406
496
  var originalErr = Console.Error;
407
497
  Console.SetOut(writer);
408
498
  Console.SetError(writer);
499
+ string? resultPath = null;
409
500
 
410
501
  try
411
502
  {
@@ -416,9 +507,15 @@ public partial class EntryPointTests
416
507
  // manually pull out the experiments manager for the validate step below
417
508
  for (int i = 0; i < args.Length - 1; i++)
418
509
  {
419
- if (args[i] == "--job-path")
510
+ switch (args[i])
420
511
  {
421
- experimentsManager = await ExperimentsManager.FromJobFileAsync(args[i + 1], new TestLogger());
512
+ case "--job-path":
513
+ var experimentsResult = await ExperimentsManager.FromJobFileAsync("TEST-JOB-ID", args[i + 1]);
514
+ experimentsManager = experimentsResult.ExperimentsManager;
515
+ break;
516
+ case "--output":
517
+ resultPath = args[i + 1];
518
+ break;
422
519
  }
423
520
  }
424
521
 
@@ -434,13 +531,40 @@ public partial class EntryPointTests
434
531
  Console.SetError(originalErr);
435
532
  }
436
533
 
437
- var resultPath = Path.Join(path, DiscoveryWorker.DiscoveryResultFileName);
534
+ resultPath ??= Path.Join(path, DiscoveryWorker.DiscoveryResultFileName);
438
535
  var resultJson = await File.ReadAllTextAsync(resultPath);
439
- var resultObject = JsonSerializer.Deserialize<WorkspaceDiscoveryResult>(resultJson, DiscoveryWorker.SerializerOptions);
536
+ var serializerOptions = new JsonSerializerOptions()
537
+ {
538
+ Converters = { new TestJobErrorBaseConverter() }
539
+ };
540
+ foreach (var converter in DiscoveryWorker.SerializerOptions.Converters)
541
+ {
542
+ serializerOptions.Converters.Add(converter);
543
+ }
544
+ var resultObject = JsonSerializer.Deserialize<WorkspaceDiscoveryResult>(resultJson, serializerOptions);
440
545
  return resultObject!;
441
546
  });
442
547
 
443
548
  ValidateWorkspaceResult(expectedResult, actualResult, experimentsManager);
444
549
  }
550
+
551
+ private class TestJobErrorBaseConverter : JsonConverter<Core.Run.ApiModel.JobErrorBase>
552
+ {
553
+ public override Core.Run.ApiModel.JobErrorBase? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
554
+ {
555
+ var dict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(ref reader, options)!;
556
+ return dict["error-type"].GetString() switch
557
+ {
558
+ "illformed_requirement" => new Core.Run.ApiModel.BadRequirement(dict["error-details"].GetProperty("message").GetString()!),
559
+ "unknown_error" => new Core.Run.ApiModel.UnknownError(new Exception("Error deserializing job file contents"), "TEST-JOB-ID"),
560
+ _ => throw new NotImplementedException($"Unknown error type: {dict["error-type"]}"),
561
+ };
562
+ }
563
+
564
+ public override void Write(Utf8JsonWriter writer, Core.Run.ApiModel.JobErrorBase value, JsonSerializerOptions options)
565
+ {
566
+ throw new NotImplementedException();
567
+ }
568
+ }
445
569
  }
446
570
  }
@@ -19,6 +19,8 @@ public partial class EntryPointTests
19
19
  await Run(path =>
20
20
  [
21
21
  "update",
22
+ "--job-id",
23
+ "TEST-JOB-ID",
22
24
  "--job-path",
23
25
  Path.Combine(path, "job.json"),
24
26
  "--repo-root",
@@ -122,6 +124,8 @@ public partial class EntryPointTests
122
124
  await Run(path =>
123
125
  [
124
126
  "update",
127
+ "--job-id",
128
+ "TEST-JOB-ID",
125
129
  "--job-path",
126
130
  Path.Combine(path, "job.json"),
127
131
  "--repo-root",
@@ -202,6 +206,8 @@ public partial class EntryPointTests
202
206
  await Run(path =>
203
207
  [
204
208
  "update",
209
+ "--job-id",
210
+ "TEST-JOB-ID",
205
211
  "--job-path",
206
212
  Path.Combine(path, "job.json"),
207
213
  "--repo-root",
@@ -361,6 +367,8 @@ public partial class EntryPointTests
361
367
  IEnumerable<string> executableArgs = [
362
368
  executableName,
363
369
  "update",
370
+ "--job-id",
371
+ "TEST-JOB-ID",
364
372
  "--job-path",
365
373
  Path.Combine(tempDir.DirectoryPath, "job.json"),
366
374
  "--repo-root",