dependabot-nuget 0.278.0 → 0.279.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) 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.Test/EntryPointTests.Update.cs +2 -3
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +87 -83
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Requirement.cs +2 -2
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +47 -46
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +6 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +11 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs +6 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +6 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +5 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +1 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +60 -15
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +1 -1
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +1 -1
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +50 -46
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +5 -5
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +2 -4
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/RequirementTests.cs +4 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +10 -1
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +92 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs +10 -4
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +8 -8
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +10 -1
  30. data/helpers/lib/NuGetUpdater/global.json +1 -1
  31. data/lib/dependabot/nuget/file_updater.rb +5 -1
  32. data/lib/dependabot/nuget/native_helpers.rb +4 -1
  33. data/lib/dependabot/nuget/requirement.rb +2 -0
  34. data/lib/dependabot/nuget/update_checker/repository_finder.rb +26 -2
  35. metadata +9 -5
@@ -25,57 +25,19 @@ public class UpdaterWorker
25
25
 
26
26
  public async Task RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive, string? resultOutputPath = null)
27
27
  {
28
- var result = await RunAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
29
- if (resultOutputPath is { })
30
- {
31
- await WriteResultFile(result, resultOutputPath, _logger);
32
- }
33
- }
34
-
35
- public async Task<UpdateOperationResult> RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
36
- {
37
- MSBuildHelper.RegisterMSBuild(Environment.CurrentDirectory, repoRootPath);
38
- UpdateOperationResult result;
39
-
40
- if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
41
- {
42
- workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
43
- }
44
-
28
+ UpdateOperationResult result = new(); // assumed to be ok until proven otherwise
45
29
  try
46
30
  {
47
- if (!isTransitive)
48
- {
49
- await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
50
- await GlobalJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
51
- }
52
-
53
- var extension = Path.GetExtension(workspacePath).ToLowerInvariant();
54
- switch (extension)
55
- {
56
- case ".sln":
57
- await RunForSolutionAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
58
- break;
59
- case ".proj":
60
- await RunForProjFileAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
61
- break;
62
- case ".csproj":
63
- case ".fsproj":
64
- case ".vbproj":
65
- await RunForProjectAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
66
- break;
67
- default:
68
- _logger.Log($"File extension [{extension}] is not supported.");
69
- break;
70
- }
71
-
72
- result = new(); // all ok
73
- _logger.Log("Update complete.");
31
+ result = await RunAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
74
32
  }
75
33
  catch (HttpRequestException ex)
76
34
  when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
77
35
  {
78
- // TODO: consolidate this error handling between AnalyzeWorker, DiscoveryWorker, and UpdateWorker
36
+ if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
37
+ {
38
+ workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
39
+ }
40
+
79
41
  result = new()
80
42
  {
81
43
  ErrorType = ErrorType.AuthenticationFailure,
@@ -91,8 +53,50 @@ public class UpdaterWorker
91
53
  };
92
54
  }
93
55
 
56
+ if (resultOutputPath is { })
57
+ {
58
+ await WriteResultFile(result, resultOutputPath, _logger);
59
+ }
60
+ }
61
+
62
+ public async Task<UpdateOperationResult> RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
63
+ {
64
+ MSBuildHelper.RegisterMSBuild(Environment.CurrentDirectory, repoRootPath);
65
+
66
+ if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
67
+ {
68
+ workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
69
+ }
70
+
71
+ if (!isTransitive)
72
+ {
73
+ await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
74
+ await GlobalJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
75
+ }
76
+
77
+ var extension = Path.GetExtension(workspacePath).ToLowerInvariant();
78
+ switch (extension)
79
+ {
80
+ case ".sln":
81
+ await RunForSolutionAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
82
+ break;
83
+ case ".proj":
84
+ await RunForProjFileAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
85
+ break;
86
+ case ".csproj":
87
+ case ".fsproj":
88
+ case ".vbproj":
89
+ await RunForProjectAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
90
+ break;
91
+ default:
92
+ _logger.Log($"File extension [{extension}] is not supported.");
93
+ break;
94
+ }
95
+
96
+ _logger.Log("Update complete.");
97
+
94
98
  _processedProjectPaths.Clear();
95
- return result;
99
+ return new UpdateOperationResult();
96
100
  }
97
101
 
98
102
  internal static async Task WriteResultFile(UpdateOperationResult result, string resultOutputPath, Logger logger)
@@ -322,7 +322,7 @@ internal static partial class MSBuildHelper
322
322
  try
323
323
  {
324
324
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
325
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
325
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
326
326
 
327
327
  // NU1608: Detected package version outside of dependency constraint
328
328
 
@@ -359,7 +359,7 @@ internal static partial class MSBuildHelper
359
359
  try
360
360
  {
361
361
  string tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
362
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
362
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
363
363
 
364
364
  // Add Dependency[] packages to List<PackageToUpdate> existingPackages
365
365
  List<PackageToUpdate> existingPackages = packages
@@ -515,7 +515,7 @@ internal static partial class MSBuildHelper
515
515
  try
516
516
  {
517
517
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
518
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
518
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
519
519
  ThrowOnUnauthenticatedFeed(stdOut);
520
520
 
521
521
  // simple cases first
@@ -540,7 +540,7 @@ internal static partial class MSBuildHelper
540
540
  foreach ((string PackageName, NuGetVersion packageVersion) in badPackagesAndVersions)
541
541
  {
542
542
  // this command dumps a JSON object with all versions of the specified package from all package sources
543
- (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"package search {PackageName} --exact-match --format json", workingDirectory: tempDirectory.FullName);
543
+ (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["package", "search", PackageName, "--exact-match", "--format", "json"], workingDirectory: tempDirectory.FullName);
544
544
  if (exitCode != 0)
545
545
  {
546
546
  continue;
@@ -770,7 +770,7 @@ internal static partial class MSBuildHelper
770
770
  var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
771
771
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
772
772
 
773
- var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"build \"{tempProjectPath}\" /t:_ReportDependencies", workingDirectory: tempDirectory.FullName);
773
+ var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", ["build", tempProjectPath, "/t:_ReportDependencies"], workingDirectory: tempDirectory.FullName);
774
774
  ThrowOnUnauthenticatedFeed(stdout);
775
775
 
776
776
  if (exitCode == 0)
@@ -26,7 +26,7 @@ internal static class NuGetHelper
26
26
  try
27
27
  {
28
28
  var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, "netstandard2.0", packages, usePackageDownload: true);
29
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"");
29
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath]);
30
30
 
31
31
  return exitCode == 0;
32
32
  }
@@ -5,17 +5,15 @@ namespace NuGetUpdater.Core;
5
5
 
6
6
  public static class ProcessEx
7
7
  {
8
- public static Task<(int ExitCode, string Output, string Error)> RunAsync(string fileName, string arguments = "", string? workingDirectory = null)
8
+ public static Task<(int ExitCode, string Output, string Error)> RunAsync(string fileName, IEnumerable<string>? arguments = null, string? workingDirectory = null)
9
9
  {
10
10
  var tcs = new TaskCompletionSource<(int, string, string)>();
11
11
 
12
12
  var redirectInitiated = new ManualResetEventSlim();
13
13
  var process = new Process
14
14
  {
15
- StartInfo =
15
+ StartInfo = new ProcessStartInfo(fileName, arguments ?? [])
16
16
  {
17
- FileName = fileName,
18
- Arguments = arguments,
19
17
  UseShellExecute = false, // required to redirect output
20
18
  RedirectStandardOutput = true,
21
19
  RedirectStandardError = true,
@@ -46,11 +46,14 @@ public class RequirementTests
46
46
  }
47
47
 
48
48
  [Theory]
49
+ [InlineData("> *", "> 0")] // standard wildcard, no digits
49
50
  [InlineData("> 1.*", "> 1.0")] // standard wildcard, single digit
50
51
  [InlineData("> 1.2.*", "> 1.2.0")] // standard wildcard, multiple digit
52
+ [InlineData("> a", "> 0")] // alternate wildcard, no digits
51
53
  [InlineData("> 1.a", "> 1.0")] // alternate wildcard, single digit
52
54
  [InlineData("> 1.2.a", "> 1.2.0")] // alternate wildcard, multiple digit
53
- public void Parse_ConvertsWildcardInVersion(string givenRequirementString, string expectedRequirementString)
55
+ [InlineData(">= 1.40.0, ", ">= 1.40.0")] // empty string following comma
56
+ public void Parse_Requirement(string givenRequirementString, string expectedRequirementString)
54
57
  {
55
58
  var parsedRequirement = Requirement.Parse(givenRequirementString);
56
59
  var actualRequirementString = parsedRequirement.ToString();
@@ -315,7 +315,7 @@ namespace NuGetUpdater.Core.Test
315
315
  </Project>
316
316
  """
317
317
  );
318
- var (exitCode, stdout, stderr) = ProcessEx.RunAsync("dotnet", $"msbuild {projectPath} /t:_ReportCurrentSdkVersion").Result;
318
+ var (exitCode, stdout, stderr) = ProcessEx.RunAsync("dotnet", ["msbuild", projectPath, "/t:_ReportCurrentSdkVersion"]).Result;
319
319
  if (exitCode != 0)
320
320
  {
321
321
  throw new Exception($"Failed to report the current SDK version:\n{stdout}\n{stderr}");
@@ -391,6 +391,7 @@ namespace NuGetUpdater.Core.Test
391
391
  WellKnownReferencePackage("Microsoft.AspNetCore.App", "net6.0"),
392
392
  WellKnownReferencePackage("Microsoft.AspNetCore.App", "net7.0"),
393
393
  WellKnownReferencePackage("Microsoft.AspNetCore.App", "net8.0"),
394
+ WellKnownReferencePackage("Microsoft.AspNetCore.App", "net9.0"),
394
395
  WellKnownReferencePackage("Microsoft.NETCore.App", "net6.0",
395
396
  [
396
397
  ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
@@ -412,9 +413,17 @@ namespace NuGetUpdater.Core.Test
412
413
  </FileList>
413
414
  """))
414
415
  ]),
416
+ WellKnownReferencePackage("Microsoft.NETCore.App", "net9.0",
417
+ [
418
+ ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
419
+ <FileList TargetFrameworkIdentifier=".NETCoreApp" TargetFrameworkVersion="9.0" FrameworkName="Microsoft.NETCore.App" Name=".NET Runtime">
420
+ </FileList>
421
+ """))
422
+ ]),
415
423
  WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net6.0"),
416
424
  WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net7.0"),
417
425
  WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net8.0"),
426
+ WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net9.0"),
418
427
  ];
419
428
  }
420
429
  }
@@ -169,6 +169,98 @@ public class RunWorkerTests
169
169
  );
170
170
  }
171
171
 
172
+ [Fact]
173
+ public async Task PrivateSourceAuthenticationFailureIsForwaredToApiHandler()
174
+ {
175
+ static (int, string) TestHttpHandler(string uriString)
176
+ {
177
+ var uri = new Uri(uriString, UriKind.Absolute);
178
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
179
+ return uri.PathAndQuery switch
180
+ {
181
+ // initial request is good
182
+ "/index.json" => (200, $$"""
183
+ {
184
+ "version": "3.0.0",
185
+ "resources": [
186
+ {
187
+ "@id": "{{baseUrl}}/download",
188
+ "@type": "PackageBaseAddress/3.0.0"
189
+ },
190
+ {
191
+ "@id": "{{baseUrl}}/query",
192
+ "@type": "SearchQueryService"
193
+ },
194
+ {
195
+ "@id": "{{baseUrl}}/registrations",
196
+ "@type": "RegistrationsBaseUrl"
197
+ }
198
+ ]
199
+ }
200
+ """),
201
+ // all other requests are unauthorized
202
+ _ => (401, "{}"),
203
+ };
204
+ }
205
+ using var http = TestHttpServer.CreateTestStringServer(TestHttpHandler);
206
+ await RunAsync(
207
+ packages:
208
+ [
209
+ ],
210
+ job: new Job()
211
+ {
212
+ PackageManager = "nuget",
213
+ Source = new()
214
+ {
215
+ Provider = "github",
216
+ Repo = "test/repo",
217
+ Directory = "/",
218
+ },
219
+ AllowedUpdates =
220
+ [
221
+ new() { UpdateType = "all" }
222
+ ]
223
+ },
224
+ files:
225
+ [
226
+ ("NuGet.Config", $"""
227
+ <configuration>
228
+ <packageSources>
229
+ <clear />
230
+ <add key="private_feed" value="{http.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
231
+ </packageSources>
232
+ </configuration>
233
+ """),
234
+ ("project.csproj", """
235
+ <Project Sdk="Microsoft.NET.Sdk">
236
+ <PropertyGroup>
237
+ <TargetFramework>net8.0</TargetFramework>
238
+ </PropertyGroup>
239
+ <ItemGroup>
240
+ <PackageReference Include="Some.Package" Version="1.0.0" />
241
+ </ItemGroup>
242
+ </Project>
243
+ """)
244
+ ],
245
+ expectedResult: new RunResult()
246
+ {
247
+ Base64DependencyFiles = [],
248
+ BaseCommitSha = "TEST-COMMIT-SHA",
249
+ },
250
+ expectedApiMessages:
251
+ [
252
+ new PrivateSourceAuthenticationFailure()
253
+ {
254
+ Details = $"({http.BaseUrl.TrimEnd('/')}/index.json)"
255
+ },
256
+ new MarkAsProcessed()
257
+ {
258
+ BaseCommitSha = "TEST-COMMIT-SHA",
259
+ }
260
+ ]
261
+ );
262
+ }
263
+
172
264
  private static async Task RunAsync(Job job, TestFile[] files, RunResult expectedResult, object[] expectedApiMessages, MockNuGetPackage[]? packages = null)
173
265
  {
174
266
  // arrange
@@ -9,27 +9,33 @@ internal class TestApiHandler : IApiHandler
9
9
 
10
10
  public IEnumerable<(Type Type, object Object)> ReceivedMessages => _receivedMessages;
11
11
 
12
+ public Task RecordUpdateJobError(JobErrorBase error)
13
+ {
14
+ _receivedMessages.Add((error.GetType(), error));
15
+ return Task.CompletedTask;
16
+ }
17
+
12
18
  public Task UpdateDependencyList(UpdatedDependencyList updatedDependencyList)
13
19
  {
14
- _receivedMessages.Add((typeof(UpdatedDependencyList), updatedDependencyList));
20
+ _receivedMessages.Add((updatedDependencyList.GetType(), updatedDependencyList));
15
21
  return Task.CompletedTask;
16
22
  }
17
23
 
18
24
  public Task IncrementMetric(IncrementMetric incrementMetric)
19
25
  {
20
- _receivedMessages.Add((typeof(IncrementMetric), incrementMetric));
26
+ _receivedMessages.Add((incrementMetric.GetType(), incrementMetric));
21
27
  return Task.CompletedTask;
22
28
  }
23
29
 
24
30
  public Task CreatePullRequest(CreatePullRequest createPullRequest)
25
31
  {
26
- _receivedMessages.Add((typeof(CreatePullRequest), createPullRequest));
32
+ _receivedMessages.Add((createPullRequest.GetType(), createPullRequest));
27
33
  return Task.CompletedTask;
28
34
  }
29
35
 
30
36
  public Task MarkAsProcessed(MarkAsProcessed markAsProcessed)
31
37
  {
32
- _receivedMessages.Add((typeof(MarkAsProcessed), markAsProcessed));
38
+ _receivedMessages.Add((markAsProcessed.GetType(), markAsProcessed));
33
39
  return Task.CompletedTask;
34
40
  }
35
41
  }
@@ -257,14 +257,6 @@ public abstract class UpdateWorkerTestBase : TestBase
257
257
  package.WriteToDirectory(localFeedPath);
258
258
  }
259
259
 
260
- // override various nuget locations
261
- foreach (var envName in new[] { "NUGET_PACKAGES", "NUGET_HTTP_CACHE_PATH", "NUGET_SCRATCH", "NUGET_PLUGINS_CACHE_PATH" })
262
- {
263
- string dir = Path.Join(temporaryDirectory, envName);
264
- Directory.CreateDirectory(dir);
265
- Environment.SetEnvironmentVariable(envName, dir);
266
- }
267
-
268
260
  // ensure only the test feed is used
269
261
  string relativeLocalFeedPath = Path.GetRelativePath(temporaryDirectory, localFeedPath);
270
262
  await File.WriteAllTextAsync(Path.Join(temporaryDirectory, "NuGet.Config"), $"""
@@ -278,6 +270,14 @@ public abstract class UpdateWorkerTestBase : TestBase
278
270
  """
279
271
  );
280
272
  }
273
+
274
+ // override various nuget locations
275
+ foreach (var envName in new[] { "NUGET_PACKAGES", "NUGET_HTTP_CACHE_PATH", "NUGET_SCRATCH", "NUGET_PLUGINS_CACHE_PATH" })
276
+ {
277
+ string dir = Path.Join(temporaryDirectory, envName);
278
+ Directory.CreateDirectory(dir);
279
+ Environment.SetEnvironmentVariable(envName, dir);
280
+ }
281
281
  }
282
282
 
283
283
  protected static async Task<TestFile[]> RunUpdate(TestFile[] files, Func<string, Task> action)
@@ -16,6 +16,7 @@ public partial class UpdateWorkerTests
16
16
  [InlineData("net472")]
17
17
  [InlineData("net7.0")]
18
18
  [InlineData("net8.0")]
19
+ [InlineData("net9.0")]
19
20
  public async Task UpdateVersionAttribute_InProjectFile_ForPackageReferenceInclude(string tfm)
20
21
  {
21
22
  // update Some.Package from 9.0.1 to 13.0.1
@@ -186,6 +187,7 @@ public partial class UpdateWorkerTests
186
187
  projectContents: $"""
187
188
  <Project Sdk="Microsoft.NET.Sdk">
188
189
  <PropertyGroup>
190
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
189
191
  <TargetFramework>net8.0</TargetFramework>
190
192
  </PropertyGroup>
191
193
  <ItemGroup>
@@ -199,6 +201,7 @@ public partial class UpdateWorkerTests
199
201
  (Path: "src/Project/Project.csproj", Content: """
200
202
  <Project Sdk="Microsoft.NET.Sdk">
201
203
  <PropertyGroup>
204
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
202
205
  <TargetFramework>net8.0</TargetFramework>
203
206
  </PropertyGroup>
204
207
  <ItemGroup>
@@ -211,6 +214,7 @@ public partial class UpdateWorkerTests
211
214
  expectedProjectContents: $"""
212
215
  <Project Sdk="Microsoft.NET.Sdk">
213
216
  <PropertyGroup>
217
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
214
218
  <TargetFramework>net8.0</TargetFramework>
215
219
  </PropertyGroup>
216
220
  <ItemGroup>
@@ -224,6 +228,7 @@ public partial class UpdateWorkerTests
224
228
  (Path: "src/Project/Project.csproj", Content: """
225
229
  <Project Sdk="Microsoft.NET.Sdk">
226
230
  <PropertyGroup>
231
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
227
232
  <TargetFramework>net8.0</TargetFramework>
228
233
  </PropertyGroup>
229
234
  <ItemGroup>
@@ -244,7 +249,7 @@ public partial class UpdateWorkerTests
244
249
  MockNuGetPackage.CreateSimplePackage("Some.Package", "9.0.1", "net8.0"),
245
250
  MockNuGetPackage.CreateSimplePackage("Some.Package", "13.0.1", "net8.0"),
246
251
  // necessary for the `net8.0-windows10.0.19041.0` TFM
247
- new("Microsoft.Windows.SDK.NET.Ref", "10.0.19041.31", Files:
252
+ new("Microsoft.Windows.SDK.NET.Ref", "10.0.19041.34", Files:
248
253
  [
249
254
  ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
250
255
  <FileList Name="Windows SDK .NET 6.0">
@@ -548,6 +553,7 @@ public partial class UpdateWorkerTests
548
553
  <Project Sdk="Microsoft.NET.Sdk">
549
554
 
550
555
  <PropertyGroup>
556
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
551
557
  <TargetFramework>net8.0</TargetFramework>
552
558
  </PropertyGroup>
553
559
 
@@ -562,6 +568,7 @@ public partial class UpdateWorkerTests
562
568
  <Project Sdk="Microsoft.NET.Sdk">
563
569
 
564
570
  <PropertyGroup>
571
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
565
572
  <TargetFramework>net8.0</TargetFramework>
566
573
  </PropertyGroup>
567
574
 
@@ -588,6 +595,7 @@ public partial class UpdateWorkerTests
588
595
  projectContents: """
589
596
  <Project Sdk="Microsoft.NET.Sdk">
590
597
  <PropertyGroup>
598
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
591
599
  <TargetFramework>net8.0</TargetFramework>
592
600
  </PropertyGroup>
593
601
  <ItemGroup>
@@ -608,6 +616,7 @@ public partial class UpdateWorkerTests
608
616
  expectedProjectContents: """
609
617
  <Project Sdk="Microsoft.NET.Sdk">
610
618
  <PropertyGroup>
619
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
611
620
  <TargetFramework>net8.0</TargetFramework>
612
621
  </PropertyGroup>
613
622
  <ItemGroup>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "sdk": {
3
- "version": "8.0.300",
3
+ "version": "9.0.100-rc.1.24452.12",
4
4
  "rollForward": "latestMinor"
5
5
  }
6
6
  }
@@ -51,7 +51,7 @@ module Dependabot
51
51
  sig { override.returns(T::Array[Dependabot::DependencyFile]) }
52
52
  def updated_dependency_files
53
53
  base_dir = "/"
54
- SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
54
+ all_updated_files = SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
55
55
  dependencies.each do |dependency|
56
56
  try_update_projects(dependency) || try_update_json(dependency)
57
57
  end
@@ -70,6 +70,10 @@ module Dependabot
70
70
  end
71
71
  updated_files
72
72
  end
73
+
74
+ raise UpdateNotPossible, dependencies.map(&:name) if all_updated_files.empty?
75
+
76
+ all_updated_files
73
77
  end
74
78
 
75
79
  private
@@ -242,8 +242,11 @@ module Dependabot
242
242
  puts "running NuGet updater:\n" + command
243
243
 
244
244
  NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials) do
245
+ # default to UseNewNugetPackageResolved _unless_ nuget_legacy_dependency_solver is enabled
245
246
  env = {}
246
- env["UseNewNugetPackageResolver"] = "true" if Dependabot::Experiments.enabled?(:nuget_dependency_solver)
247
+ unless Dependabot::Experiments.enabled?(:nuget_legacy_dependency_solver)
248
+ env["UseNewNugetPackageResolver"] = "true"
249
+ end
247
250
  output = SharedHelpers.run_shell_command(command,
248
251
  allow_unsafe_shell_command: true,
249
252
  fingerprint: fingerprint,
@@ -42,6 +42,8 @@ module Dependabot
42
42
  convert_dotnet_constraint_to_ruby_constraint(req_string)
43
43
  end
44
44
 
45
+ requirements = requirements.compact.reject(&:empty?)
46
+
45
47
  super(requirements)
46
48
  end
47
49
 
@@ -13,6 +13,7 @@ require "dependabot/nuget/http_response_helpers"
13
13
 
14
14
  module Dependabot
15
15
  module Nuget
16
+ # rubocop:disable Metrics/ClassLength
16
17
  class RepositoryFinder
17
18
  extend T::Sig
18
19
 
@@ -48,7 +49,15 @@ module Dependabot
48
49
  @known_repositories << { url: DEFAULT_REPOSITORY_URL, token: nil } if @known_repositories.empty?
49
50
 
50
51
  @known_repositories = @known_repositories.map do |repo|
51
- { url: URI::DEFAULT_PARSER.escape(repo[:url]), token: repo[:token] }
52
+ url = repo[:url]
53
+ begin
54
+ url = URI::DEFAULT_PARSER.parse(url).to_s
55
+ rescue URI::InvalidURIError
56
+ # e.g., the url has spaces or unacceptable symbols
57
+ url = URI::DEFAULT_PARSER.escape(url)
58
+ end
59
+
60
+ { url: url, token: repo[:token] }
52
61
  end
53
62
  @known_repositories.uniq
54
63
  end
@@ -68,6 +77,20 @@ module Dependabot
68
77
  }
69
78
  end
70
79
 
80
+ sig { params(source_name: String).returns(String) }
81
+ def self.escape_source_name_to_element_name(source_name)
82
+ source_name.chars.map do |c|
83
+ case c
84
+ when /[A-Za-z0-9\-_.]/
85
+ # letters, digits, hyphens, underscores, and periods are all directly allowed
86
+ c
87
+ else
88
+ # otherwise it needs to be escaped as a 4 digit hex value
89
+ "_x#{c.ord.to_s(16).rjust(4, '0')}_"
90
+ end
91
+ end.join
92
+ end
93
+
71
94
  private
72
95
 
73
96
  sig { returns(Dependabot::Dependency) }
@@ -376,7 +399,7 @@ module Dependabot
376
399
  next source_details[:token] = nil unless key
377
400
  next source_details[:token] = nil if key.match?(/^\d/)
378
401
 
379
- tag = key.gsub(" ", "_x0020_")
402
+ tag = RepositoryFinder.escape_source_name_to_element_name(key)
380
403
  creds_nodes = doc.css("configuration > packageSourceCredentials " \
381
404
  "> #{tag} > add")
382
405
 
@@ -438,5 +461,6 @@ module Dependabot
438
461
  end
439
462
  end
440
463
  end
464
+ # rubocop:enable Metrics/ClassLength
441
465
  end
442
466
  end