dependabot-nuget 0.298.0 → 0.299.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ddf8333c71d3c76213603301923f6005fc9a35a3a7b1f59dedf21ff2381a5ac
4
- data.tar.gz: b2e38e24b320965f93d502eacdc0622b354e915a0366bc8478a9528c053e4410
3
+ metadata.gz: 47471c96a0fec7881b5454528d6cd07199cd600b1ec4dde66656fc754db318fe
4
+ data.tar.gz: 2364b4e071736115c1cb9503dc40562819e4c376198321761812c70dc309f1bd
5
5
  SHA512:
6
- metadata.gz: 317614f24858deff63cad0dddfcfb80f53c6e8330b4dfd64d9946a34c50afed27f9e63527f3563ebc2678f7c5ad6e43078ddf4feb7944c10fb7947056f31740d
7
- data.tar.gz: 8cb7df04044fc6a1bb421508bbf1bf1fdebab675f81c30d188963e6991f2bd84b4d4f165a315fbfec3257a97e85f232ed824cef7d01f3ab658cd5f9247d44035
6
+ metadata.gz: d31e631d9c1dd411843c4c911ad2db4e8a19f4d1fd15ffb0e5c2d4af1b092520bcc4aea2b31996c765870973e0da47b85910fb7d14f81ca973a7069554dc6733
7
+ data.tar.gz: 48714dea50d6fab935c7ca952e5d992f9aaf0699a54f99f576db55575b36251af0b7503744d229d187245b7e1eab687ce639fad574c8b15bb795f569720a0c99
@@ -62,23 +62,24 @@ internal static class VersionFinder
62
62
 
63
63
  foreach (var source in sources)
64
64
  {
65
- var sourceRepository = Repository.Factory.GetCoreV3(source);
66
- var feed = await sourceRepository.GetResourceAsync<MetadataResource>();
67
- if (feed is null)
65
+ MetadataResource? feed = null;
66
+ try
68
67
  {
69
- logger.Warn($"Failed to get {nameof(MetadataResource)} for [{source.Source}]");
70
- continue;
71
- }
68
+ var sourceRepository = Repository.Factory.GetCoreV3(source);
69
+ feed = await sourceRepository.GetResourceAsync<MetadataResource>();
70
+ if (feed is null)
71
+ {
72
+ logger.Warn($"Failed to get {nameof(MetadataResource)} for [{source.Source}]");
73
+ continue;
74
+ }
72
75
 
73
- var packageFinder = await sourceRepository.GetResourceAsync<FindPackageByIdResource>();
74
- if (packageFinder is null)
75
- {
76
- logger.Warn($"Failed to get {nameof(FindPackageByIdResource)} for [{source.Source}]");
77
- continue;
78
- }
76
+ var packageFinder = await sourceRepository.GetResourceAsync<FindPackageByIdResource>();
77
+ if (packageFinder is null)
78
+ {
79
+ logger.Warn($"Failed to get {nameof(FindPackageByIdResource)} for [{source.Source}]");
80
+ continue;
81
+ }
79
82
 
80
- try
81
- {
82
83
  // a non-compliant v2 API returning 404 can cause this to throw
83
84
  var existsInFeed = await feed.Exists(
84
85
  packageId,
@@ -147,20 +147,43 @@ public class RunWorker
147
147
  var extraFilePath = Path.Join(projectDirectory, extraFile);
148
148
  await TrackOriginalContentsAsync(discoveryResult.Path, extraFilePath);
149
149
  }
150
- // TODO: include global.json, etc.
150
+ }
151
+
152
+ var nonProjectFiles = new[]
153
+ {
154
+ discoveryResult.GlobalJson?.FilePath,
155
+ discoveryResult.DotNetToolsJson?.FilePath,
156
+ }.Where(f => f is not null).Cast<string>().ToArray();
157
+ foreach (var nonProjectFile in nonProjectFiles)
158
+ {
159
+ await TrackOriginalContentsAsync(discoveryResult.Path, nonProjectFile);
151
160
  }
152
161
 
153
162
  // do update
154
163
  var updateOperations = GetUpdateOperations(discoveryResult).ToArray();
155
- foreach (var updateOperation in updateOperations)
164
+ var allowedUpdateOperations = updateOperations.Where(u => IsUpdateAllowed(job, u.Dependency)).ToArray();
165
+
166
+ // requested update isn't listed => SecurityUpdateNotNeeded
167
+ var expectedSecurityUpdateDependencyNames = job.SecurityAdvisories
168
+ .Select(s => s.DependencyName)
169
+ .ToHashSet(StringComparer.OrdinalIgnoreCase);
170
+ var actualUpdateDependencyNames = allowedUpdateOperations
171
+ .Select(u => u.Dependency.Name)
172
+ .ToHashSet(StringComparer.OrdinalIgnoreCase);
173
+ var expectedDependencyUpdateMissingInActual = expectedSecurityUpdateDependencyNames
174
+ .Except(actualUpdateDependencyNames, StringComparer.OrdinalIgnoreCase)
175
+ .OrderBy(d => d, StringComparer.OrdinalIgnoreCase)
176
+ .ToArray();
177
+
178
+ foreach (var missingSecurityUpdate in expectedDependencyUpdateMissingInActual)
156
179
  {
157
- var dependency = updateOperation.Dependency;
158
- if (!IsUpdateAllowed(job, dependency))
159
- {
160
- continue;
161
- }
180
+ await _apiHandler.RecordUpdateJobError(new SecurityUpdateNotNeeded(missingSecurityUpdate));
181
+ }
162
182
 
163
- _logger.Info($"Updating [{dependency.Name}] in [{updateOperation.ProjectPath}]");
183
+ foreach (var updateOperation in allowedUpdateOperations)
184
+ {
185
+ var dependency = updateOperation.Dependency;
186
+ _logger.Info($"Updating [{dependency.Name}] in [{updateOperation.FilePath}]");
164
187
 
165
188
  var dependencyInfo = GetDependencyInfo(job, dependency);
166
189
  var analysisResult = await _analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
@@ -169,7 +192,7 @@ public class RunWorker
169
192
  {
170
193
  // TODO: this is inefficient, but not likely causing a bottleneck
171
194
  var previousDependency = discoveredUpdatedDependencies.Dependencies
172
- .Single(d => d.Name == dependency.Name && d.Requirements.Single().File == updateOperation.ProjectPath);
195
+ .Single(d => d.Name == dependency.Name && d.Requirements.Single().File == updateOperation.FilePath);
173
196
  var updatedDependency = new ReportedDependency()
174
197
  {
175
198
  Name = dependency.Name,
@@ -178,7 +201,7 @@ public class RunWorker
178
201
  [
179
202
  new ReportedRequirement()
180
203
  {
181
- File = updateOperation.ProjectPath,
204
+ File = updateOperation.FilePath,
182
205
  Requirement = analysisResult.UpdatedVersion,
183
206
  Groups = previousDependency.Requirements.Single().Groups,
184
207
  Source = new RequirementSource()
@@ -191,7 +214,7 @@ public class RunWorker
191
214
  PreviousRequirements = previousDependency.Requirements,
192
215
  };
193
216
 
194
- var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, updateOperation.ProjectPath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: dependency.IsTransitive);
217
+ var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, updateOperation.FilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: dependency.IsTransitive);
195
218
  // TODO: need to report if anything was actually updated
196
219
  if (updateResult.Error is null)
197
220
  {
@@ -214,7 +237,6 @@ public class RunWorker
214
237
 
215
238
  if (updatedContent != originalContent)
216
239
  {
217
-
218
240
  updatedDependencyFiles[localFullPath] = new DependencyFile()
219
241
  {
220
242
  Name = Path.GetFileName(repoFullPath),
@@ -233,7 +255,11 @@ public class RunWorker
233
255
  var extraFilePath = Path.Join(projectDirectory, extraFile);
234
256
  await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, extraFilePath);
235
257
  }
236
- // TODO: handle global.json, etc.
258
+ }
259
+
260
+ foreach (var nonProjectFile in nonProjectFiles)
261
+ {
262
+ await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, nonProjectFile);
237
263
  }
238
264
 
239
265
  if (updatedDependencyFiles.Count > 0)
@@ -276,34 +302,50 @@ public class RunWorker
276
302
  return result;
277
303
  }
278
304
 
279
- internal static IEnumerable<(string ProjectPath, Dependency Dependency)> GetUpdateOperations(WorkspaceDiscoveryResult discovery)
305
+ internal static IEnumerable<(string FilePath, Dependency Dependency)> GetUpdateOperations(WorkspaceDiscoveryResult discovery)
280
306
  {
281
- // discovery is grouped by project then dependency, but we want to pivot and return a list of update operations sorted by dependency name then project path
307
+ // discovery is grouped by project/file then dependency, but we want to pivot and return a list of update operations sorted by dependency name then file path
282
308
 
283
309
  var updateOrder = new Dictionary<string, Dictionary<string, Dictionary<string, Dependency>>>(StringComparer.OrdinalIgnoreCase);
284
- // <dependency name, <project path, specific dependencies>>
310
+ // <dependency name, <file path, specific dependencies>>
285
311
 
286
312
  // collect
287
- foreach (var project in discovery.Projects)
313
+ void CollectDependenciesForFile(string filePath, IEnumerable<Dependency> dependencies)
288
314
  {
289
- var projectPath = Path.Join(discovery.Path, project.FilePath).FullyNormalizedRootedPath();
290
- foreach (var dependency in project.Dependencies)
315
+ foreach (var dependency in dependencies)
291
316
  {
292
317
  var dependencyGroup = updateOrder.GetOrAdd(dependency.Name, () => new Dictionary<string, Dictionary<string, Dependency>>(PathComparer.Instance));
293
- var dependenciesForProject = dependencyGroup.GetOrAdd(projectPath, () => new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase));
294
- dependenciesForProject[dependency.Name] = dependency;
318
+ var dependenciesForFile = dependencyGroup.GetOrAdd(filePath, () => new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase));
319
+ dependenciesForFile[dependency.Name] = dependency;
295
320
  }
296
321
  }
322
+ foreach (var project in discovery.Projects)
323
+ {
324
+ var projectPath = Path.Join(discovery.Path, project.FilePath).FullyNormalizedRootedPath();
325
+ CollectDependenciesForFile(projectPath, project.Dependencies);
326
+ }
327
+
328
+ if (discovery.GlobalJson is not null)
329
+ {
330
+ var globalJsonPath = Path.Join(discovery.Path, discovery.GlobalJson.FilePath).FullyNormalizedRootedPath();
331
+ CollectDependenciesForFile(globalJsonPath, discovery.GlobalJson.Dependencies);
332
+ }
333
+
334
+ if (discovery.DotNetToolsJson is not null)
335
+ {
336
+ var dotnetToolsJsonPath = Path.Join(discovery.Path, discovery.DotNetToolsJson.FilePath).FullyNormalizedRootedPath();
337
+ CollectDependenciesForFile(dotnetToolsJsonPath, discovery.DotNetToolsJson.Dependencies);
338
+ }
297
339
 
298
340
  // return
299
341
  foreach (var dependencyName in updateOrder.Keys.OrderBy(k => k, StringComparer.OrdinalIgnoreCase))
300
342
  {
301
- var projectDependencies = updateOrder[dependencyName];
302
- foreach (var projectPath in projectDependencies.Keys.OrderBy(p => p, PathComparer.Instance))
343
+ var fileDependencies = updateOrder[dependencyName];
344
+ foreach (var filePath in fileDependencies.Keys.OrderBy(p => p, PathComparer.Instance))
303
345
  {
304
- var dependencies = projectDependencies[projectPath];
346
+ var dependencies = fileDependencies[filePath];
305
347
  var dependency = dependencies[dependencyName];
306
- yield return (projectPath, dependency);
348
+ yield return (filePath, dependency);
307
349
  }
308
350
  }
309
351
  }
@@ -450,35 +492,69 @@ public class RunWorker
450
492
  }
451
493
  }
452
494
 
495
+ var allDependenciesWithFilePath = discoveryResult.Projects.SelectMany(p =>
496
+ {
497
+ return p.Dependencies
498
+ .Where(d => d.Version is not null)
499
+ .Select(d =>
500
+ (p.FilePath, new ReportedDependency()
501
+ {
502
+ Name = d.Name,
503
+ Requirements = [new ReportedRequirement()
504
+ {
505
+ File = GetFullRepoPath(p.FilePath),
506
+ Requirement = d.Version!,
507
+ Groups = ["dependencies"],
508
+ }],
509
+ Version = d.Version,
510
+ }));
511
+ }).ToList();
512
+
513
+ var nonProjectDependencySet = new (string?, IEnumerable<Dependency>)[]
514
+ {
515
+ (discoveryResult.GlobalJson?.FilePath, discoveryResult.GlobalJson?.Dependencies ?? []),
516
+ (discoveryResult.DotNetToolsJson?.FilePath, discoveryResult.DotNetToolsJson?.Dependencies ?? []),
517
+ };
518
+
519
+ foreach (var (filePath, dependencies) in nonProjectDependencySet)
520
+ {
521
+ if (filePath is null)
522
+ {
523
+ continue;
524
+ }
525
+
526
+ allDependenciesWithFilePath.AddRange(dependencies
527
+ .Where(d => d.Version is not null)
528
+ .Select(d =>
529
+ (filePath, new ReportedDependency()
530
+ {
531
+ Name = d.Name,
532
+ Requirements = [new ReportedRequirement()
533
+ {
534
+ File = GetFullRepoPath(filePath),
535
+ Requirement = d.Version!,
536
+ Groups = ["dependencies"],
537
+ }],
538
+ Version = d.Version,
539
+ })));
540
+ }
541
+
542
+ var sortedDependencies = allDependenciesWithFilePath
543
+ .OrderBy(pair => Path.Join(discoveryResult.Path, pair.FilePath).FullyNormalizedRootedPath(), PathComparer.Instance)
544
+ .ThenBy(pair => pair.Item2.Name, StringComparer.OrdinalIgnoreCase)
545
+ .Select(pair => pair.Item2)
546
+ .ToArray();
547
+
453
548
  var dependencyFiles = discoveryResult.Projects
454
549
  .Select(p => GetFullRepoPath(p.FilePath))
455
550
  .Concat(auxiliaryFiles)
456
551
  .Distinct()
457
552
  .OrderBy(p => p)
458
553
  .ToArray();
459
- var orderedProjects = discoveryResult.Projects
460
- .OrderBy(p => Path.Join(discoveryResult.Path, p.FilePath).FullyNormalizedRootedPath(), PathComparer.Instance)
461
- .ToArray();
554
+
462
555
  var updatedDependencyList = new UpdatedDependencyList()
463
556
  {
464
- Dependencies = orderedProjects.SelectMany(p =>
465
- {
466
- return p.Dependencies
467
- .Where(d => d.Version is not null)
468
- .OrderBy(d => d.Name, StringComparer.OrdinalIgnoreCase)
469
- .Select(d =>
470
- new ReportedDependency()
471
- {
472
- Name = d.Name,
473
- Requirements = [new ReportedRequirement()
474
- {
475
- File = GetFullRepoPath(p.FilePath),
476
- Requirement = d.Version!,
477
- Groups = ["dependencies"],
478
- }],
479
- Version = d.Version,
480
- });
481
- }).ToArray(),
557
+ Dependencies = sortedDependencies,
482
558
  DependencyFiles = dependencyFiles,
483
559
  };
484
560
  return updatedDependencyList;
@@ -795,7 +795,7 @@ internal static partial class MSBuildHelper
795
795
  );
796
796
  return (exitCode, stdOut, stdErr);
797
797
  });
798
- ThrowOnUnauthenticatedFeed(stdOut);
798
+ ThrowOnError(stdOut);
799
799
  if (exitCode != 0)
800
800
  {
801
801
  logger.Warn($"Error determining target frameworks.\nSTDOUT:\n{stdOut}\nSTDERR:\n{stdErr}");
@@ -975,6 +975,7 @@ internal static partial class MSBuildHelper
975
975
  new Regex(@"Package '(?<PackageName>[^']*)' is not found on source '(?<PackageSource>[^$\r\n]*)'\."),
976
976
  new Regex(@"Unable to find package (?<PackageName>[^ ]+)\. No packages exist with this id in source\(s\): (?<PackageSource>.*)$", RegexOptions.Multiline),
977
977
  new Regex(@"Unable to find package (?<PackageName>[^ ]+) with version \((?<PackageVersion>[^)]+)\)"),
978
+ new Regex(@"Could not resolve SDK ""(?<PackageName>[^ ]+)""\."),
978
979
  };
979
980
  var matches = patterns.Select(p => p.Match(output)).Where(m => m.Success);
980
981
  if (matches.Any())
@@ -1300,6 +1300,56 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
1300
1300
  );
1301
1301
  }
1302
1302
 
1303
+ [Fact]
1304
+ public async Task NuGetSourceDoesNotExist()
1305
+ {
1306
+ // this test simulates a `NuGet.Config` that points to an internal-only domain that dependabot doesn't have access to
1307
+ await TestAnalyzeAsync(
1308
+ extraFiles: [
1309
+ ("NuGet.Config", """
1310
+ <configuration>
1311
+ <packageSources>
1312
+ <clear />
1313
+ <add key="feed_that_does_not_exist" value="https://this-domain-does-not-exist/nuget/v2" />
1314
+ </packageSources>
1315
+ </configuration>
1316
+ """)
1317
+ ],
1318
+ discovery: new()
1319
+ {
1320
+ Path = "/",
1321
+ Projects = [
1322
+ new()
1323
+ {
1324
+ FilePath = "./project.csproj",
1325
+ TargetFrameworks = ["net9.0"],
1326
+ Dependencies = [
1327
+ new("Some.Package", "1.0.0", DependencyType.PackageReference),
1328
+ ],
1329
+ ReferencedProjectPaths = [],
1330
+ ImportedFiles = [],
1331
+ AdditionalFiles = [],
1332
+ }
1333
+ ]
1334
+ },
1335
+ dependencyInfo: new()
1336
+ {
1337
+ Name = "Some.Package",
1338
+ Version = "1.0.0",
1339
+ IgnoredVersions = [],
1340
+ IsVulnerable = false,
1341
+ Vulnerabilities = [],
1342
+ },
1343
+ expectedResult: new()
1344
+ {
1345
+ CanUpdate = false,
1346
+ UpdatedVersion = "1.0.0",
1347
+ VersionComesFromMultiDependencyProperty = false,
1348
+ UpdatedDependencies = [],
1349
+ }
1350
+ );
1351
+ }
1352
+
1303
1353
  [Fact]
1304
1354
  public void DeserializeDependencyInfo_UnsupportedIgnoredVersionsAreIgnored()
1305
1355
  {
@@ -1355,4 +1355,30 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
1355
1355
  }
1356
1356
  );
1357
1357
  }
1358
+
1359
+ [Fact]
1360
+ public async Task MissingFileIsReported()
1361
+ {
1362
+ await TestDiscoveryAsync(
1363
+ packages: [],
1364
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = true, InstallDotnetSdks = true },
1365
+ workspacePath: "",
1366
+ files: [
1367
+ ("project.csproj", """
1368
+ <Project Sdk="Microsoft.NET.Sdk">
1369
+ <Import Project="file-that-does-not-exist.props" />
1370
+ <PropertyGroup>
1371
+ <TargetFramework>net8.0</TargetFramework>
1372
+ </PropertyGroup>
1373
+ </Project>
1374
+ """)
1375
+ ],
1376
+ expectedResult: new()
1377
+ {
1378
+ Path = "",
1379
+ Projects = [],
1380
+ ErrorRegex = @"file-that-does-not-exist\.props",
1381
+ }
1382
+ );
1383
+ }
1358
1384
  }
@@ -58,7 +58,7 @@ public class MiscellaneousTests
58
58
  public void GetUpdateOperations(WorkspaceDiscoveryResult discovery, (string ProjectPath, string DependencyName)[] expectedUpdateOperations)
59
59
  {
60
60
  var updateOperations = RunWorker.GetUpdateOperations(discovery).ToArray();
61
- var actualUpdateOperations = updateOperations.Select(uo => (uo.ProjectPath, uo.Dependency.Name)).ToArray();
61
+ var actualUpdateOperations = updateOperations.Select(uo => (uo.FilePath, uo.Dependency.Name)).ToArray();
62
62
  Assert.Equal(expectedUpdateOperations, actualUpdateOperations);
63
63
  }
64
64
 
@@ -94,6 +94,34 @@ public class MiscellaneousTests
94
94
  ("/src/Common.csproj", "Package.D"),
95
95
  },
96
96
  ];
97
+
98
+ yield return
99
+ [
100
+ new WorkspaceDiscoveryResult()
101
+ {
102
+ Path = "",
103
+ Projects = [],
104
+ GlobalJson = new()
105
+ {
106
+ FilePath = "global.json",
107
+ Dependencies = [
108
+ new("Some.MSBuild.Sdk", "1.0.0", DependencyType.MSBuildSdk)
109
+ ]
110
+ },
111
+ DotNetToolsJson = new()
112
+ {
113
+ FilePath = ".config/dotnet-tools.json",
114
+ Dependencies = [
115
+ new("some-tool", "2.0.0", DependencyType.DotNetTool)
116
+ ]
117
+ }
118
+ },
119
+ new (string, string)[]
120
+ {
121
+ ("/.config/dotnet-tools.json", "some-tool"),
122
+ ("/global.json", "Some.MSBuild.Sdk"),
123
+ }
124
+ ];
97
125
  }
98
126
 
99
127
  public static IEnumerable<object?[]> RequirementsFromIgnoredVersionsData()
@@ -2072,6 +2072,391 @@ public class RunWorkerTests
2072
2072
  );
2073
2073
  }
2074
2074
 
2075
+ [Fact]
2076
+ public async Task PackageListedInSecurityAdvisoriesSectionIsNotVulnerable()
2077
+ {
2078
+ await RunAsync(
2079
+ job: new()
2080
+ {
2081
+ Source = new()
2082
+ {
2083
+ Provider = "github",
2084
+ Repo = "test/repo",
2085
+ },
2086
+ SecurityUpdatesOnly = true,
2087
+ SecurityAdvisories = [
2088
+ new()
2089
+ {
2090
+ DependencyName = "Package.Is.Not.Vulnerable",
2091
+ AffectedVersions = [Requirement.Parse("< 1.0.0")]
2092
+ }
2093
+ ]
2094
+ },
2095
+ files: [
2096
+ ("project.csproj", "contents irrelevant")
2097
+ ],
2098
+ discoveryWorker: new TestDiscoveryWorker(_input =>
2099
+ {
2100
+ return Task.FromResult(new WorkspaceDiscoveryResult()
2101
+ {
2102
+ Path = "",
2103
+ Projects = [
2104
+ new()
2105
+ {
2106
+ FilePath = "project.csproj",
2107
+ Dependencies = [
2108
+ new("Package.Is.Not.Vulnerable", "1.0.1", DependencyType.PackageReference)
2109
+ ],
2110
+ ImportedFiles = [],
2111
+ AdditionalFiles = [],
2112
+ }
2113
+ ]
2114
+ });
2115
+ }),
2116
+ analyzeWorker: new TestAnalyzeWorker(_input => throw new NotImplementedException("test shouldn't get this far")),
2117
+ updaterWorker: new TestUpdaterWorker(_input => throw new NotImplementedException("test shouldn't get this far")),
2118
+ expectedResult: new()
2119
+ {
2120
+ Base64DependencyFiles = [
2121
+ new()
2122
+ {
2123
+ Directory = "/",
2124
+ Name = "project.csproj",
2125
+ Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("contents irrelevant"))
2126
+ }
2127
+ ],
2128
+ BaseCommitSha = "TEST-COMMIT-SHA",
2129
+ },
2130
+ expectedApiMessages: [
2131
+ new UpdatedDependencyList()
2132
+ {
2133
+ Dependencies = [
2134
+ new()
2135
+ {
2136
+ Name = "Package.Is.Not.Vulnerable",
2137
+ Version = "1.0.1",
2138
+ Requirements = [
2139
+ new()
2140
+ {
2141
+ Requirement = "1.0.1",
2142
+ File = "/project.csproj",
2143
+ Groups = ["dependencies"],
2144
+ }
2145
+ ]
2146
+ }
2147
+ ],
2148
+ DependencyFiles = ["/project.csproj"]
2149
+ },
2150
+ new IncrementMetric()
2151
+ {
2152
+ Metric = "updater.started",
2153
+ Tags = new()
2154
+ {
2155
+ ["operation"] = "create_security_pr"
2156
+ }
2157
+ },
2158
+ new SecurityUpdateNotNeeded("Package.Is.Not.Vulnerable"),
2159
+ new MarkAsProcessed("TEST-COMMIT-SHA"),
2160
+ ]
2161
+ );
2162
+ }
2163
+
2164
+ [Fact]
2165
+ public async Task PackageListedInSecurityAdvisoriesSectionIsNotPresent()
2166
+ {
2167
+ await RunAsync(
2168
+ job: new()
2169
+ {
2170
+ Source = new()
2171
+ {
2172
+ Provider = "github",
2173
+ Repo = "test/repo",
2174
+ },
2175
+ SecurityUpdatesOnly = true,
2176
+ SecurityAdvisories = [
2177
+ new()
2178
+ {
2179
+ DependencyName = "Package.Is.Not.Vulnerable",
2180
+ AffectedVersions = [Requirement.Parse("< 1.0.0")]
2181
+ }
2182
+ ]
2183
+ },
2184
+ files: [
2185
+ ("project.csproj", "contents irrelevant")
2186
+ ],
2187
+ discoveryWorker: new TestDiscoveryWorker(_input =>
2188
+ {
2189
+ return Task.FromResult(new WorkspaceDiscoveryResult()
2190
+ {
2191
+ Path = "",
2192
+ Projects = [
2193
+ new()
2194
+ {
2195
+ FilePath = "project.csproj",
2196
+ Dependencies = [
2197
+ new("Unrelated.Package", "0.1.0", DependencyType.PackageReference)
2198
+ ],
2199
+ ImportedFiles = [],
2200
+ AdditionalFiles = [],
2201
+ }
2202
+ ]
2203
+ });
2204
+ }),
2205
+ analyzeWorker: new TestAnalyzeWorker(_input => throw new NotImplementedException("test shouldn't get this far")),
2206
+ updaterWorker: new TestUpdaterWorker(_input => throw new NotImplementedException("test shouldn't get this far")),
2207
+ expectedResult: new()
2208
+ {
2209
+ Base64DependencyFiles = [
2210
+ new()
2211
+ {
2212
+ Directory = "/",
2213
+ Name = "project.csproj",
2214
+ Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("contents irrelevant"))
2215
+ }
2216
+ ],
2217
+ BaseCommitSha = "TEST-COMMIT-SHA",
2218
+ },
2219
+ expectedApiMessages: [
2220
+ new UpdatedDependencyList()
2221
+ {
2222
+ Dependencies = [
2223
+ new()
2224
+ {
2225
+ Name = "Unrelated.Package",
2226
+ Version = "0.1.0",
2227
+ Requirements = [
2228
+ new()
2229
+ {
2230
+ Requirement = "0.1.0",
2231
+ File = "/project.csproj",
2232
+ Groups = ["dependencies"],
2233
+ }
2234
+ ]
2235
+ }
2236
+ ],
2237
+ DependencyFiles = ["/project.csproj"]
2238
+ },
2239
+ new IncrementMetric()
2240
+ {
2241
+ Metric = "updater.started",
2242
+ Tags = new()
2243
+ {
2244
+ ["operation"] = "create_security_pr"
2245
+ }
2246
+ },
2247
+ new SecurityUpdateNotNeeded("Package.Is.Not.Vulnerable"),
2248
+ new MarkAsProcessed("TEST-COMMIT-SHA"),
2249
+ ]
2250
+ );
2251
+ }
2252
+
2253
+ [Fact]
2254
+ public async Task NonProjectFilesAreIncludedInPullRequest()
2255
+ {
2256
+ await RunAsync(
2257
+ job: new()
2258
+ {
2259
+ Source = new()
2260
+ {
2261
+ Provider = "github",
2262
+ Repo = "test/repo",
2263
+ },
2264
+ },
2265
+ files: [
2266
+ (".config/dotnet-tools.json", "dotnet-tools.json content old"),
2267
+ ("global.json", "global.json content old")
2268
+ ],
2269
+ discoveryWorker: new TestDiscoveryWorker(input =>
2270
+ {
2271
+ return Task.FromResult(new WorkspaceDiscoveryResult()
2272
+ {
2273
+ Path = "",
2274
+ Projects = [],
2275
+ DotNetToolsJson = new()
2276
+ {
2277
+ FilePath = ".config/dotnet-tools.json",
2278
+ Dependencies = [
2279
+ new("some-tool", "2.0.0", DependencyType.DotNetTool),
2280
+ ]
2281
+ },
2282
+ GlobalJson = new()
2283
+ {
2284
+ FilePath = "global.json",
2285
+ Dependencies = [
2286
+ new("Some.MSBuild.Sdk", "1.0.0", DependencyType.MSBuildSdk),
2287
+ ],
2288
+ },
2289
+ });
2290
+ }),
2291
+ analyzeWorker: new TestAnalyzeWorker(input =>
2292
+ {
2293
+ var (_repoRoot, _discoveryResult, dependencyInfo) = input;
2294
+ var result = dependencyInfo.Name switch
2295
+ {
2296
+ "some-tool" => new AnalysisResult() { CanUpdate = true, UpdatedVersion = "2.0.1", UpdatedDependencies = [new("some-tool", "2.0.1", DependencyType.DotNetTool)] },
2297
+ "Some.MSBuild.Sdk" => new AnalysisResult() { CanUpdate = true, UpdatedVersion = "1.0.1", UpdatedDependencies = [new("Some.MSBuild.Sdk", "1.0.1", DependencyType.MSBuildSdk)] },
2298
+ _ => throw new NotImplementedException("unreachable")
2299
+ };
2300
+ return Task.FromResult(result);
2301
+ }),
2302
+ updaterWorker: new TestUpdaterWorker(async input =>
2303
+ {
2304
+ var (repoRoot, filePath, dependencyName, _previousVersion, _newVersion, _isTransitive) = input;
2305
+ var dependencyFilePath = Path.Join(repoRoot, filePath);
2306
+ var updatedContent = dependencyName switch
2307
+ {
2308
+ "some-tool" => "dotnet-tools.json content UPDATED",
2309
+ "Some.MSBuild.Sdk" => "global.json content UPDATED",
2310
+ _ => throw new NotImplementedException("unreachable")
2311
+ };
2312
+ await File.WriteAllTextAsync(dependencyFilePath, updatedContent);
2313
+ return new UpdateOperationResult();
2314
+ }),
2315
+ expectedResult: new()
2316
+ {
2317
+ Base64DependencyFiles = [
2318
+ new()
2319
+ {
2320
+ Directory = "/.config",
2321
+ Name = "dotnet-tools.json",
2322
+ Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("dotnet-tools.json content old"))
2323
+ },
2324
+ new()
2325
+ {
2326
+ Directory = "/",
2327
+ Name = "global.json",
2328
+ Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("global.json content old"))
2329
+ },
2330
+ ],
2331
+ BaseCommitSha = "TEST-COMMIT-SHA",
2332
+ },
2333
+ expectedApiMessages: [
2334
+ new UpdatedDependencyList()
2335
+ {
2336
+ Dependencies = [
2337
+ new()
2338
+ {
2339
+ Name = "some-tool",
2340
+ Version = "2.0.0",
2341
+ Requirements = [
2342
+ new()
2343
+ {
2344
+ Requirement = "2.0.0",
2345
+ File = "/.config/dotnet-tools.json",
2346
+ Groups = ["dependencies"],
2347
+ }
2348
+ ]
2349
+ },
2350
+ new()
2351
+ {
2352
+ Name = "Some.MSBuild.Sdk",
2353
+ Version = "1.0.0",
2354
+ Requirements = [
2355
+ new()
2356
+ {
2357
+ Requirement = "1.0.0",
2358
+ File = "/global.json",
2359
+ Groups = ["dependencies"],
2360
+ }
2361
+ ]
2362
+ },
2363
+ ],
2364
+ DependencyFiles = ["/.config/dotnet-tools.json", "/global.json"]
2365
+ },
2366
+ new IncrementMetric()
2367
+ {
2368
+ Metric = "updater.started",
2369
+ Tags = new()
2370
+ {
2371
+ ["operation"] = "group_update_all_versions"
2372
+ }
2373
+ },
2374
+ new CreatePullRequest()
2375
+ {
2376
+ Dependencies =
2377
+ [
2378
+ new ReportedDependency()
2379
+ {
2380
+ Name = "some-tool",
2381
+ Version = "2.0.1",
2382
+ Requirements =
2383
+ [
2384
+ new ReportedRequirement()
2385
+ {
2386
+ Requirement = "2.0.1",
2387
+ File = "/.config/dotnet-tools.json",
2388
+ Groups = ["dependencies"],
2389
+ Source = new()
2390
+ {
2391
+ SourceUrl = null,
2392
+ }
2393
+ }
2394
+ ],
2395
+ PreviousVersion = "2.0.0",
2396
+ PreviousRequirements =
2397
+ [
2398
+ new ReportedRequirement()
2399
+ {
2400
+ Requirement = "2.0.0",
2401
+ File = "/.config/dotnet-tools.json",
2402
+ Groups = ["dependencies"],
2403
+ }
2404
+ ],
2405
+ },
2406
+ new ReportedDependency()
2407
+ {
2408
+ Name = "Some.MSBuild.Sdk",
2409
+ Version = "1.0.1",
2410
+ Requirements =
2411
+ [
2412
+ new ReportedRequirement()
2413
+ {
2414
+ Requirement = "1.0.1",
2415
+ File = "/global.json",
2416
+ Groups = ["dependencies"],
2417
+ Source = new()
2418
+ {
2419
+ SourceUrl = null,
2420
+ }
2421
+ }
2422
+ ],
2423
+ PreviousVersion = "1.0.0",
2424
+ PreviousRequirements =
2425
+ [
2426
+ new ReportedRequirement()
2427
+ {
2428
+ Requirement = "1.0.0",
2429
+ File = "/global.json",
2430
+ Groups = ["dependencies"],
2431
+ }
2432
+ ],
2433
+ },
2434
+ ],
2435
+ UpdatedDependencyFiles =
2436
+ [
2437
+ new DependencyFile()
2438
+ {
2439
+ Name = "dotnet-tools.json",
2440
+ Directory = "/.config",
2441
+ Content = "dotnet-tools.json content UPDATED",
2442
+ },
2443
+ new DependencyFile()
2444
+ {
2445
+ Name = "global.json",
2446
+ Directory = "/",
2447
+ Content = "global.json content UPDATED",
2448
+ },
2449
+ ],
2450
+ BaseCommitSha = "TEST-COMMIT-SHA",
2451
+ CommitMessage = "TODO: message",
2452
+ PrTitle = "TODO: title",
2453
+ PrBody = "TODO: body",
2454
+ },
2455
+ new MarkAsProcessed("TEST-COMMIT-SHA"),
2456
+ ]
2457
+ );
2458
+ }
2459
+
2075
2460
  private static async Task RunAsync(Job job, TestFile[] files, IDiscoveryWorker? discoveryWorker, IAnalyzeWorker? analyzeWorker, IUpdaterWorker? updaterWorker, RunResult expectedResult, object[] expectedApiMessages, MockNuGetPackage[]? packages = null, ExperimentsManager? experimentsManager = null, string? repoContentsPath = null)
2076
2461
  {
2077
2462
  // arrange
@@ -17,6 +17,7 @@ public class UpdatedDependencyListTests
17
17
  Directory.CreateDirectory(Path.Combine(temp.DirectoryPath, "src", "a"));
18
18
  Directory.CreateDirectory(Path.Combine(temp.DirectoryPath, "src", "b"));
19
19
  Directory.CreateDirectory(Path.Combine(temp.DirectoryPath, "src", "c"));
20
+ Directory.CreateDirectory(Path.Combine(temp.DirectoryPath, ".config"));
20
21
 
21
22
  File.WriteAllText(Path.Combine(temp.DirectoryPath, "src", "a", "packages.config"), "");
22
23
  File.WriteAllText(Path.Combine(temp.DirectoryPath, "src", "b", "packages.config"), "");
@@ -25,6 +26,9 @@ public class UpdatedDependencyListTests
25
26
  File.WriteAllText(Path.Combine(temp.DirectoryPath, "src", "b", "project.csproj"), "");
26
27
  File.WriteAllText(Path.Combine(temp.DirectoryPath, "src", "c", "project.csproj"), "");
27
28
 
29
+ File.WriteAllText(Path.Combine(temp.DirectoryPath, "global.json"), "");
30
+ File.WriteAllText(Path.Combine(temp.DirectoryPath, ".config/dotnet-tools.json"), "");
31
+
28
32
  var discovery = new WorkspaceDiscoveryResult()
29
33
  {
30
34
  Path = "src",
@@ -69,13 +73,55 @@ public class UpdatedDependencyListTests
69
73
  ImportedFiles = [],
70
74
  AdditionalFiles = ["packages.config"],
71
75
  }
72
- ]
76
+ ],
77
+ GlobalJson = new()
78
+ {
79
+ FilePath = "../global.json",
80
+ Dependencies = [
81
+ new("Some.MSBuild.Sdk", "1.0.0", DependencyType.MSBuildSdk)
82
+ ]
83
+ },
84
+ DotNetToolsJson = new()
85
+ {
86
+ FilePath = "../.config/dotnet-tools.json",
87
+ Dependencies = [
88
+ new("some-tool", "2.0.0", DependencyType.DotNetTool)
89
+ ]
90
+ }
73
91
  };
74
92
  var updatedDependencyList = RunWorker.GetUpdatedDependencyListFromDiscovery(discovery, pathToContents: temp.DirectoryPath);
75
93
  var expectedDependencyList = new UpdatedDependencyList()
76
94
  {
77
95
  Dependencies =
78
96
  [
97
+ new ReportedDependency()
98
+ {
99
+ Name = "some-tool",
100
+ Version = "2.0.0",
101
+ Requirements =
102
+ [
103
+ new ReportedRequirement()
104
+ {
105
+ Requirement = "2.0.0",
106
+ File = "/.config/dotnet-tools.json",
107
+ Groups = ["dependencies"],
108
+ }
109
+ ]
110
+ },
111
+ new ReportedDependency()
112
+ {
113
+ Name = "Some.MSBuild.Sdk",
114
+ Version = "1.0.0",
115
+ Requirements =
116
+ [
117
+ new ReportedRequirement()
118
+ {
119
+ Requirement = "1.0.0",
120
+ File = "/global.json",
121
+ Groups = ["dependencies"],
122
+ }
123
+ ]
124
+ },
79
125
  new ReportedDependency()
80
126
  {
81
127
  Name = "Microsoft.Extensions.DependencyModel",
@@ -119,7 +165,7 @@ public class UpdatedDependencyListTests
119
165
  ],
120
166
  },
121
167
  ],
122
- DependencyFiles = ["/src/a/packages.config", "/src/a/project.csproj", "/src/b/packages.config", "/src/b/project.csproj", "/src/c/packages.config", "/src/c/project.csproj"],
168
+ DependencyFiles = ["/.config/dotnet-tools.json", "/global.json", "/src/a/packages.config", "/src/a/project.csproj", "/src/b/packages.config", "/src/b/project.csproj", "/src/c/packages.config", "/src/c/project.csproj"],
123
169
  };
124
170
 
125
171
  // doing JSON comparison makes this easier; we don't have to define custom record equality and we get an easy diff
@@ -1542,6 +1542,14 @@ public class MSBuildHelperTests : TestBase
1542
1542
  new DependencyNotFound("Some.Package"),
1543
1543
  ];
1544
1544
 
1545
+ yield return
1546
+ [
1547
+ // output
1548
+ """error : Could not resolve SDK "missing-sdk".""",
1549
+ // expectedError
1550
+ new DependencyNotFound("missing-sdk"),
1551
+ ];
1552
+
1545
1553
  yield return
1546
1554
  [
1547
1555
  // output
@@ -26,6 +26,7 @@ module Dependabot
26
26
 
27
27
  sig { override.returns(T::Array[DependencyFile]) }
28
28
  def fetch_files
29
+ NativeHelpers.normalize_file_names
29
30
  NativeHelpers.install_dotnet_sdks
30
31
  discovery_json_reader = DiscoveryJsonReader.run_discovery_in_directory(
31
32
  repo_contents_path: T.must(repo_contents_path),
@@ -286,6 +286,22 @@ module Dependabot
286
286
  end
287
287
  end
288
288
 
289
+ sig { void }
290
+ def self.normalize_file_names
291
+ # environment variables are required and the following will generate an actionable error message if they're not
292
+ _dependabot_repo_contents_path = ENV.fetch("DEPENDABOT_REPO_CONTENTS_PATH")
293
+
294
+ # this environment variable is directly used
295
+ dependabot_home = ENV.fetch("DEPENDABOT_HOME")
296
+
297
+ command = [
298
+ "pwsh",
299
+ "#{dependabot_home}/dependabot-updater/bin/normalize-file-names.ps1"
300
+ ].join(" ")
301
+ output = SharedHelpers.run_shell_command(command)
302
+ puts output
303
+ end
304
+
289
305
  sig { void }
290
306
  def self.install_dotnet_sdks
291
307
  return unless Dependabot::Experiments.enabled?(:nuget_install_dotnet_sdks)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.298.0
4
+ version: 0.299.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-20 00:00:00.000000000 Z
11
+ date: 2025-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.298.0
19
+ version: 0.299.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.298.0
26
+ version: 0.299.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubyzip
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -537,7 +537,7 @@ licenses:
537
537
  - MIT
538
538
  metadata:
539
539
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
540
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.298.0
540
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.299.0
541
541
  post_install_message:
542
542
  rdoc_options: []
543
543
  require_paths: