dependabot-nuget 0.263.0 → 0.264.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +37 -0
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +3 -3
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +1 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +169 -0
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +79 -67
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.FrameworkCheck.cs +0 -4
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +10 -11
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalysisResult.cs +11 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +441 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +177 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +47 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyInfo.cs +12 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Extensions.cs +36 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +128 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Requirement.cs +105 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementConverter.cs +17 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/SecurityVulnerability.cs +11 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/SecurityVulnerabilityExtensions.cs +36 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +179 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionResult.cs +54 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Dependency.cs +5 -2
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +2 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +2 -2
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +0 -2
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +0 -3
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +0 -3
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +0 -5
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs +0 -4
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +6 -2
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +0 -4
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs +0 -2
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/HashSetExtensions.cs +0 -2
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +0 -4
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/Logger.cs +0 -3
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +7 -8
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +0 -4
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +0 -3
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs +0 -4
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +90 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +304 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/CompatibilityCheckerTests.cs +145 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/ExpectedAnalysisResult.cs +8 -0
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/RequirementTests.cs +69 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/SecurityVulnerabilityExtensionsTests.cs +78 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/VersionFinderTests.cs +193 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +1 -2
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs +2 -2
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +2 -2
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +1 -1
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Proj.cs +1 -1
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +102 -9
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +4 -4
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +2 -2
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +8 -2
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +2 -1
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +8 -7
  58. data/lib/dependabot/nuget/analysis/analysis_json_reader.rb +63 -0
  59. data/lib/dependabot/nuget/analysis/dependency_analysis.rb +63 -0
  60. data/lib/dependabot/nuget/file_fetcher.rb +7 -6
  61. data/lib/dependabot/nuget/file_parser.rb +28 -21
  62. data/lib/dependabot/nuget/file_updater.rb +22 -25
  63. data/lib/dependabot/nuget/metadata_finder.rb +2 -160
  64. data/lib/dependabot/nuget/native_discovery/native_dependency_details.rb +102 -0
  65. data/lib/dependabot/nuget/native_discovery/native_dependency_file_discovery.rb +129 -0
  66. data/lib/dependabot/nuget/native_discovery/native_directory_packages_props_discovery.rb +44 -0
  67. data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +174 -0
  68. data/lib/dependabot/nuget/native_discovery/native_evaluation_details.rb +63 -0
  69. data/lib/dependabot/nuget/native_discovery/native_project_discovery.rb +82 -0
  70. data/lib/dependabot/nuget/native_discovery/native_property_details.rb +43 -0
  71. data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +68 -0
  72. data/lib/dependabot/nuget/native_helpers.rb +59 -0
  73. data/lib/dependabot/nuget/native_update_checker/native_requirements_updater.rb +105 -0
  74. data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +200 -0
  75. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +3 -2
  76. data/lib/dependabot/nuget/update_checker.rb +47 -0
  77. metadata +39 -5
@@ -246,12 +246,13 @@ public abstract class UpdateWorkerTestBase : TestBase
246
246
  }
247
247
 
248
248
  // ensure only the test feed is used
249
+ string relativeLocalFeedPath = Path.GetRelativePath(temporaryDirectory, localFeedPath);
249
250
  await File.WriteAllTextAsync(Path.Join(temporaryDirectory, "NuGet.Config"), $"""
250
251
  <?xml version="1.0" encoding="utf-8"?>
251
252
  <configuration>
252
253
  <packageSources>
253
254
  <clear />
254
- <add key="local-feed" value="{localFeedPath}" />
255
+ <add key="local-feed" value="{relativeLocalFeedPath}" />
255
256
  </packageSources>
256
257
  </configuration>
257
258
  """
@@ -383,7 +383,7 @@ public class MSBuildHelperTests : TestBase
383
383
  }
384
384
 
385
385
  [Fact]
386
- public async Task AllPackageDependenciesCanBeFoundWithNuGetConfig()
386
+ public async Task LocalPackageSourcesAreHonored()
387
387
  {
388
388
  var nugetPackagesDirectory = Environment.GetEnvironmentVariable("NUGET_PACKAGES");
389
389
  var nugetHttpCacheDirectory = Environment.GetEnvironmentVariable("NUGET_HTTP_CACHE_PATH");
@@ -399,20 +399,21 @@ public class MSBuildHelperTests : TestBase
399
399
  Environment.SetEnvironmentVariable("NUGET_HTTP_CACHE_PATH", tempNuGetHttpCacheDirectory);
400
400
 
401
401
  // create two local package sources with different packages available in each
402
- string localSource1 = Path.Combine(temp.DirectoryPath, "localSource1");
402
+ string localSource1 = Path.Combine(temp.DirectoryPath, "local", "source1");
403
403
  Directory.CreateDirectory(localSource1);
404
- string localSource2 = Path.Combine(temp.DirectoryPath, "localSource2");
404
+ string localSource2 = Path.Combine(temp.DirectoryPath, "local", "source2");
405
405
  Directory.CreateDirectory(localSource2);
406
406
 
407
- // `Package.A` will only live in `localSource1` and will have a dependency on `Package.B` which is only
408
- // available in `localSource2`
407
+ // `Package.A` will only live in `local\source1` and uses Windows-style directory separators and will have
408
+ // a dependency on `Package.B` which is only available in `local/source2` and uses Unix-style directory
409
+ // separators.
409
410
  MockNuGetPackage.CreateSimplePackage("Package.A", "1.0.0", "net8.0", [(null, [("Package.B", "2.0.0")])]).WriteToDirectory(localSource1);
410
411
  MockNuGetPackage.CreateSimplePackage("Package.B", "2.0.0", "net8.0").WriteToDirectory(localSource2);
411
412
  await File.WriteAllTextAsync(Path.Join(temp.DirectoryPath, "NuGet.Config"), """
412
413
  <configuration>
413
414
  <packageSources>
414
- <add key="localSource1" value="./localSource1" />
415
- <add key="localSource2" value="./localSource2" />
415
+ <add key="localSource1" value="local\source1" />
416
+ <add key="localSource2" value="local/source2" />
416
417
  </packageSources>
417
418
  </configuration>
418
419
  """);
@@ -0,0 +1,63 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/dependency"
5
+ require "dependabot/nuget/analysis/dependency_analysis"
6
+ require "dependabot/nuget/native_discovery/native_discovery_json_reader"
7
+ require "json"
8
+ require "sorbet-runtime"
9
+
10
+ module Dependabot
11
+ module Nuget
12
+ class AnalysisJsonReader
13
+ extend T::Sig
14
+
15
+ sig { returns(String) }
16
+ def self.temp_directory
17
+ File.join(NativeDiscoveryJsonReader.temp_directory, "analysis")
18
+ end
19
+
20
+ sig { params(dependency_name: String).returns(String) }
21
+ def self.analysis_file_path(dependency_name:)
22
+ File.join(temp_directory, "#{dependency_name}.json")
23
+ end
24
+
25
+ sig { params(dependency_name: String).returns(T.nilable(DependencyFile)) }
26
+ def self.analysis_json(dependency_name:)
27
+ file_path = analysis_file_path(dependency_name: dependency_name)
28
+ return unless File.exist?(file_path)
29
+
30
+ DependencyFile.new(
31
+ name: Pathname.new(file_path).cleanpath.to_path,
32
+ directory: temp_directory,
33
+ type: "file",
34
+ content: File.read(file_path)
35
+ )
36
+ end
37
+
38
+ sig { params(analysis_json: DependencyFile).void }
39
+ def initialize(analysis_json:)
40
+ @analysis_json = analysis_json
41
+ end
42
+
43
+ sig { returns(DependencyAnalysis) }
44
+ def dependency_analysis
45
+ @dependency_analysis ||= T.let(begin
46
+ raise Dependabot::DependencyFileNotParseable, analysis_json.path unless analysis_json.content
47
+
48
+ Dependabot.logger.info("#{File.basename(analysis_json.path)} analysis content: #{analysis_json.content}")
49
+
50
+ parsed_json = T.let(JSON.parse(T.must(analysis_json.content)), T::Hash[String, T.untyped])
51
+ DependencyAnalysis.from_json(parsed_json)
52
+ end, T.nilable(DependencyAnalysis))
53
+ rescue JSON::ParserError
54
+ raise Dependabot::DependencyFileNotParseable, analysis_json.path
55
+ end
56
+
57
+ private
58
+
59
+ sig { returns(DependencyFile) }
60
+ attr_reader :analysis_json
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,63 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/nuget/version"
5
+ require "sorbet-runtime"
6
+
7
+ module Dependabot
8
+ module Nuget
9
+ class DependencyAnalysis
10
+ extend T::Sig
11
+
12
+ sig { params(json: T::Hash[String, T.untyped]).returns(DependencyAnalysis) }
13
+ def self.from_json(json)
14
+ updated_version = T.let(json.fetch("UpdatedVersion"), String)
15
+ can_update = T.let(json.fetch("CanUpdate"), T::Boolean)
16
+ version_comes_from_multi_dependency_property = T.let(json.fetch("VersionComesFromMultiDependencyProperty"),
17
+ T::Boolean)
18
+ updated_dependencies = T.let(json.fetch("UpdatedDependencies"),
19
+ T::Array[T::Hash[String, T.untyped]]).map do |dep|
20
+ NativeDependencyDetails.from_json(dep)
21
+ end
22
+
23
+ DependencyAnalysis.new(
24
+ updated_version: updated_version,
25
+ can_update: can_update,
26
+ version_comes_from_multi_dependency_property: version_comes_from_multi_dependency_property,
27
+ updated_dependencies: updated_dependencies
28
+ )
29
+ end
30
+
31
+ sig do
32
+ params(updated_version: String,
33
+ can_update: T::Boolean,
34
+ version_comes_from_multi_dependency_property: T::Boolean,
35
+ updated_dependencies: T::Array[NativeDependencyDetails]).void
36
+ end
37
+ def initialize(updated_version:, can_update:, version_comes_from_multi_dependency_property:,
38
+ updated_dependencies:)
39
+ @updated_version = updated_version
40
+ @can_update = can_update
41
+ @version_comes_from_multi_dependency_property = version_comes_from_multi_dependency_property
42
+ @updated_dependencies = updated_dependencies
43
+ end
44
+
45
+ sig { returns(String) }
46
+ attr_reader :updated_version
47
+
48
+ sig { returns(T::Boolean) }
49
+ attr_reader :can_update
50
+
51
+ sig { returns(T::Boolean) }
52
+ attr_reader :version_comes_from_multi_dependency_property
53
+
54
+ sig { returns(T::Array[NativeDependencyDetails]) }
55
+ attr_reader :updated_dependencies
56
+
57
+ sig { returns(Dependabot::Nuget::Version) }
58
+ def numeric_updated_version
59
+ @numeric_updated_version ||= T.let(Version.new(updated_version), T.nilable(Dependabot::Nuget::Version))
60
+ end
61
+ end
62
+ end
63
+ end
@@ -34,12 +34,13 @@ module Dependabot
34
34
  end
35
35
 
36
36
  sig do
37
- params(
38
- source: Dependabot::Source,
39
- credentials: T::Array[Credential],
40
- repo_contents_path: T.nilable(String),
41
- options: T::Hash[String, String]
42
- ).void
37
+ override
38
+ .params(
39
+ source: Dependabot::Source,
40
+ credentials: T::Array[Credential],
41
+ repo_contents_path: T.nilable(String),
42
+ options: T::Hash[String, String]
43
+ ).void
43
44
  end
44
45
  def initialize(source:, credentials:, repo_contents_path: nil, options: {})
45
46
  super(source: source, credentials: credentials, repo_contents_path: repo_contents_path, options: options)
@@ -4,7 +4,7 @@
4
4
  require "dependabot/dependency"
5
5
  require "dependabot/file_parsers"
6
6
  require "dependabot/file_parsers/base"
7
- require "dependabot/nuget/discovery/discovery_json_reader"
7
+ require "dependabot/nuget/native_discovery/native_discovery_json_reader"
8
8
  require "dependabot/nuget/native_helpers"
9
9
  require "sorbet-runtime"
10
10
 
@@ -18,39 +18,46 @@ module Dependabot
18
18
  require "dependabot/file_parsers/base/dependency_set"
19
19
  require_relative "cache_manager"
20
20
 
21
+ sig { returns(T::Hash[String, T::Array[Dependabot::Dependency]]) }
22
+ def self.file_dependency_cache
23
+ T.let(CacheManager.cache("file_parser.parse"), T::Hash[String, T::Array[Dependabot::Dependency]])
24
+ end
25
+
21
26
  sig { override.returns(T::Array[Dependabot::Dependency]) }
22
27
  def parse
23
28
  return [] unless repo_contents_path
24
29
 
25
- cache = T.let(CacheManager.cache("file_parser.parse"), T::Hash[String, T::Array[Dependabot::Dependency]])
26
- # key the cache on the dependency files, excluding the content
27
- key = dependency_files.map { |d| d.to_h.except("content") }.to_s
28
- cache[key] ||= begin
30
+ key = NativeDiscoveryJsonReader.create_cache_key(dependency_files)
31
+ workspace_path = source&.directory || "/"
32
+ self.class.file_dependency_cache[key] ||= begin
29
33
  # run discovery for the repo
34
+ discovery_json_path = NativeDiscoveryJsonReader.create_discovery_file_path_from_dependency_files(
35
+ dependency_files
36
+ )
30
37
  NativeHelpers.run_nuget_discover_tool(repo_root: T.must(repo_contents_path),
31
- workspace_path: source&.directory || "/",
32
- output_path: DiscoveryJsonReader.discovery_file_path,
38
+ workspace_path: workspace_path,
39
+ output_path: discovery_json_path,
33
40
  credentials: credentials)
34
- discovered_dependencies.dependencies
35
- end
36
41
 
37
- T.must(cache[key])
38
- end
42
+ discovery_json = NativeDiscoveryJsonReader.discovery_json_from_path(discovery_json_path)
43
+ return [] unless discovery_json
39
44
 
40
- private
45
+ Dependabot.logger.info("Discovery JSON content: #{discovery_json.content}")
46
+ discovery_json_reader = NativeDiscoveryJsonReader.new(
47
+ discovery_json: discovery_json
48
+ )
41
49
 
42
- sig { returns(Dependabot::FileParsers::Base::DependencySet) }
43
- def discovered_dependencies
44
- discovery_json = DiscoveryJsonReader.discovery_json
45
- return DependencySet.new unless discovery_json
46
-
47
- Dependabot.logger.info("Discovery JSON content: #{discovery_json.content}")
50
+ # cache discovery results
51
+ NativeDiscoveryJsonReader.set_discovery_from_dependency_files(dependency_files: dependency_files,
52
+ discovery: discovery_json_reader)
53
+ discovery_json_reader.dependency_set.dependencies
54
+ end
48
55
 
49
- DiscoveryJsonReader.new(
50
- discovery_json: discovery_json
51
- ).dependency_set
56
+ T.must(self.class.file_dependency_cache[key])
52
57
  end
53
58
 
59
+ private
60
+
54
61
  sig { returns(T::Array[Dependabot::DependencyFile]) }
55
62
  def proj_files
56
63
  projfile = /\.proj$/
@@ -4,9 +4,9 @@
4
4
  require "dependabot/dependency_file"
5
5
  require "dependabot/file_updaters"
6
6
  require "dependabot/file_updaters/base"
7
- require "dependabot/nuget/discovery/dependency_details"
8
- require "dependabot/nuget/discovery/discovery_json_reader"
9
- require "dependabot/nuget/discovery/workspace_discovery"
7
+ require "dependabot/nuget/native_discovery/native_dependency_details"
8
+ require "dependabot/nuget/native_discovery/native_discovery_json_reader"
9
+ require "dependabot/nuget/native_discovery/native_workspace_discovery"
10
10
  require "dependabot/nuget/native_helpers"
11
11
  require "dependabot/shared_helpers"
12
12
  require "sorbet-runtime"
@@ -31,7 +31,7 @@ module Dependabot
31
31
 
32
32
  sig { override.returns(T::Array[Dependabot::DependencyFile]) }
33
33
  def updated_dependency_files
34
- base_dir = T.must(dependency_files.first).directory
34
+ base_dir = "/"
35
35
  SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
36
36
  dependencies.each do |dependency|
37
37
  try_update_projects(dependency) || try_update_json(dependency)
@@ -111,7 +111,7 @@ module Dependabot
111
111
  # Ideally we should find a way to not run this code in prod
112
112
  # (or a better way to track calls made to NativeHelpers)
113
113
  @update_tooling_calls ||= T.let({}, T.nilable(T::Hash[String, Integer]))
114
- key = proj_path + dependency.name
114
+ key = "#{proj_path.delete_prefix(T.must(repo_contents_path))}+#{dependency.name}"
115
115
  @update_tooling_calls[key] =
116
116
  if @update_tooling_calls[key]
117
117
  T.must(@update_tooling_calls[key]) + 1
@@ -126,18 +126,10 @@ module Dependabot
126
126
  @update_tooling_calls
127
127
  end
128
128
 
129
- sig { returns(T.nilable(WorkspaceDiscovery)) }
129
+ sig { returns(T.nilable(NativeWorkspaceDiscovery)) }
130
130
  def workspace
131
- @workspace ||= T.let(begin
132
- discovery_json = DiscoveryJsonReader.discovery_json
133
- if discovery_json
134
- workspace = DiscoveryJsonReader.new(
135
- discovery_json: discovery_json
136
- ).workspace_discovery
137
- end
138
-
139
- workspace
140
- end, T.nilable(WorkspaceDiscovery))
131
+ discovery_json_reader = NativeDiscoveryJsonReader.get_discovery_from_dependency_files(dependency_files)
132
+ discovery_json_reader.workspace_discovery
141
133
  end
142
134
 
143
135
  sig { params(project_file: Dependabot::DependencyFile).returns(T::Array[String]) }
@@ -145,17 +137,20 @@ module Dependabot
145
137
  workspace&.projects&.find { |p| p.file_path == project_file.name }&.referenced_project_paths || []
146
138
  end
147
139
 
148
- sig { params(project_file: Dependabot::DependencyFile).returns(T::Array[DependencyDetails]) }
140
+ sig { params(project_file: Dependabot::DependencyFile).returns(T::Array[NativeDependencyDetails]) }
149
141
  def project_dependencies(project_file)
150
- workspace&.projects&.find { |p| p.file_path == project_file.name }&.dependencies || []
142
+ workspace&.projects&.find do |p|
143
+ full_project_file_path = File.join(project_file.directory, project_file.name)
144
+ p.file_path == full_project_file_path
145
+ end&.dependencies || []
151
146
  end
152
147
 
153
- sig { returns(T::Array[DependencyDetails]) }
148
+ sig { returns(T::Array[NativeDependencyDetails]) }
154
149
  def global_json_dependencies
155
150
  workspace&.global_json&.dependencies || []
156
151
  end
157
152
 
158
- sig { returns(T::Array[DependencyDetails]) }
153
+ sig { returns(T::Array[NativeDependencyDetails]) }
159
154
  def dotnet_tools_json_dependencies
160
155
  workspace&.dotnet_tools_json&.dependencies || []
161
156
  end
@@ -164,13 +159,15 @@ module Dependabot
164
159
  sig { params(dependency_file: Dependabot::DependencyFile, updated_content: String).returns(String) }
165
160
  def normalize_content(dependency_file, updated_content)
166
161
  # Fix up line endings
167
- if dependency_file.content&.include?("\r\n") && updated_content.match?(/(?<!\r)\n/)
162
+ if dependency_file.content&.include?("\r\n")
168
163
  # The original content contain windows style newlines.
169
- # Ensure the updated content also uses windows style newlines.
170
- updated_content = updated_content.gsub(/(?<!\r)\n/, "\r\n")
171
- puts "Fixing mismatched Windows line endings for [#{dependency_file.name}]."
164
+ if updated_content.match?(/(?<!\r)\n/)
165
+ # Ensure the updated content also uses windows style newlines.
166
+ updated_content = updated_content.gsub(/(?<!\r)\n/, "\r\n")
167
+ puts "Fixing mismatched Windows line endings for [#{dependency_file.name}]."
168
+ end
172
169
  elsif updated_content.include?("\r\n")
173
- # The original content does not contain windows style newlines.
170
+ # The original content does not contain windows style newlines, but the updated content does.
174
171
  # Ensure the updated content uses unix style newlines.
175
172
  updated_content = updated_content.gsub("\r\n", "\n")
176
173
  puts "Fixing mismatched Unix line endings for [#{dependency_file.name}]."
@@ -12,148 +12,16 @@ module Dependabot
12
12
  class MetadataFinder < Dependabot::MetadataFinders::Base
13
13
  extend T::Sig
14
14
 
15
- sig do
16
- override
17
- .params(
18
- dependency: Dependabot::Dependency,
19
- credentials: T::Array[Dependabot::Credential]
20
- )
21
- .void
22
- end
23
- def initialize(dependency:, credentials:)
24
- @dependency_nuspec_file = T.let(nil, T.nilable(Nokogiri::XML::Document))
25
-
26
- super
27
- end
28
-
29
15
  private
30
16
 
31
17
  sig { override.returns(T.nilable(Dependabot::Source)) }
32
18
  def look_up_source
33
- return Source.from_url(dependency_source_url) if dependency_source_url
34
-
35
- if dependency_nuspec_file
36
- src_repo = look_up_source_in_nuspec(T.must(dependency_nuspec_file))
37
- return src_repo if src_repo
38
- end
39
-
40
- # Fallback to getting source from the search result's projectUrl or licenseUrl.
41
- # GitHub Packages doesn't support getting the `.nuspec`, switch to getting
42
- # that instead once it is supported.
43
- src_repo_from_project
44
- rescue StandardError
45
- # At this point in the process the PR is ready to be posted, we tried to gather commit
46
- # and release notes, but have encountered an exception. So let's eat it since it's
47
- # better to have a PR with no info than error out.
48
- nil
49
- end
50
-
51
- sig { returns(T.nilable(Dependabot::Source)) }
52
- def src_repo_from_project
53
- source = dependency.requirements.find { |r| r.fetch(:source) }&.fetch(:source)
54
- return unless source
55
-
56
- # Query the service index e.g. https://nuget.pkg.github.com/ORG/index.json
57
- response = Dependabot::RegistryClient.get(
58
- url: source.fetch(:url),
59
- headers: { **auth_header, "Accept" => "application/json" }
60
- )
61
- return unless response.status == 200
19
+ source_url = dependency_source_url
20
+ return Source.from_url(source_url) if source_url
62
21
 
63
- # Extract the query url e.g. https://nuget.pkg.github.com/ORG/query
64
- search_base = extract_search_url(response.body)
65
- return unless search_base
66
-
67
- response = Dependabot::RegistryClient.get(
68
- url: search_base + "?q=#{dependency.name.downcase}&prerelease=true&semVerLevel=2.0.0",
69
- headers: { **auth_header, "Accept" => "application/json" }
70
- )
71
- return unless response.status == 200
72
-
73
- # Find a projectUrl or licenseUrl that look like a source URL
74
- extract_source_repo(response.body)
75
- rescue JSON::ParserError
76
- # Ignored, this is expected for some registries that don't handle these request.
77
- end
78
-
79
- sig { params(body: String).returns(T.nilable(String)) }
80
- def extract_search_url(body)
81
- JSON.parse(body)
82
- .fetch("resources", [])
83
- .find { |r| r.fetch("@type") == "SearchQueryService" }
84
- &.fetch("@id")
85
- end
86
-
87
- sig { params(body: String).returns(T.nilable(Dependabot::Source)) }
88
- def extract_source_repo(body)
89
- JSON.parse(body).fetch("data", []).each do |search_result|
90
- next unless search_result["id"].casecmp(dependency.name).zero?
91
-
92
- if search_result.key?("projectUrl")
93
- source = Source.from_url(search_result.fetch("projectUrl"))
94
- return source if source
95
- end
96
- if search_result.key?("licenseUrl")
97
- source = Source.from_url(search_result.fetch("licenseUrl"))
98
- return source if source
99
- end
100
- end
101
- # failed to find a source URL
102
22
  nil
103
23
  end
104
24
 
105
- sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(Dependabot::Source)) }
106
- def look_up_source_in_nuspec(nuspec)
107
- potential_source_urls = [
108
- nuspec.at_css("package > metadata > repository")
109
- &.attribute("url")&.value,
110
- nuspec.at_css("package > metadata > repository > url")&.content,
111
- nuspec.at_css("package > metadata > projectUrl")&.content,
112
- nuspec.at_css("package > metadata > licenseUrl")&.content
113
- ].compact
114
-
115
- source_url = potential_source_urls.find { |url| Source.from_url(url) }
116
- source_url ||= source_from_anywhere_in_nuspec(nuspec)
117
-
118
- Source.from_url(source_url)
119
- end
120
-
121
- sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(String)) }
122
- def source_from_anywhere_in_nuspec(nuspec)
123
- github_urls = []
124
- nuspec.to_s.force_encoding(Encoding::UTF_8)
125
- .scan(Source::SOURCE_REGEX) do
126
- github_urls << Regexp.last_match.to_s
127
- end
128
-
129
- github_urls.find do |url|
130
- repo = T.must(Source.from_url(url)).repo
131
- repo.downcase.end_with?(dependency.name.downcase)
132
- end
133
- end
134
-
135
- sig { returns(T.nilable(Nokogiri::XML::Document)) }
136
- def dependency_nuspec_file
137
- return @dependency_nuspec_file unless @dependency_nuspec_file.nil?
138
-
139
- return if dependency_nuspec_url.nil?
140
-
141
- response = Dependabot::RegistryClient.get(
142
- url: T.must(dependency_nuspec_url),
143
- headers: auth_header
144
- )
145
-
146
- @dependency_nuspec_file = Nokogiri::XML(response.body)
147
- end
148
-
149
- sig { returns(T.nilable(String)) }
150
- def dependency_nuspec_url
151
- source = dependency.requirements
152
- .find { |r| r.fetch(:source) }&.fetch(:source)
153
-
154
- source.fetch(:nuspec_url) if source&.key?(:nuspec_url)
155
- end
156
-
157
25
  sig { returns(T.nilable(String)) }
158
26
  def dependency_source_url
159
27
  source = dependency.requirements
@@ -164,32 +32,6 @@ module Dependabot
164
32
 
165
33
  source.fetch("source_url")
166
34
  end
167
-
168
- # rubocop:disable Metrics/PerceivedComplexity
169
- sig { returns(T::Hash[String, String]) }
170
- def auth_header
171
- source = dependency.requirements
172
- .find { |r| r.fetch(:source) }&.fetch(:source)
173
- url = source&.fetch(:url, nil) || source&.fetch("url")
174
-
175
- token = credentials
176
- .select { |cred| cred["type"] == "nuget_feed" }
177
- .find { |cred| cred["url"] == url }
178
- &.fetch("token", nil)
179
-
180
- return {} unless token
181
-
182
- if token.include?(":")
183
- encoded_token = Base64.encode64(token).delete("\n")
184
- { "Authorization" => "Basic #{encoded_token}" }
185
- elsif Base64.decode64(token).ascii_only? &&
186
- Base64.decode64(token).include?(":")
187
- { "Authorization" => "Basic #{token.delete("\n")}" }
188
- else
189
- { "Authorization" => "Bearer #{token}" }
190
- end
191
- end
192
- # rubocop:enable Metrics/PerceivedComplexity
193
35
  end
194
36
  end
195
37
  end
@@ -0,0 +1,102 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/nuget/native_discovery/native_evaluation_details"
5
+ require "sorbet-runtime"
6
+
7
+ module Dependabot
8
+ module Nuget
9
+ class NativeDependencyDetails
10
+ extend T::Sig
11
+
12
+ sig { params(json: T::Hash[String, T.untyped]).returns(NativeDependencyDetails) }
13
+ def self.from_json(json)
14
+ name = T.let(json.fetch("Name"), String)
15
+ version = T.let(json.fetch("Version"), T.nilable(String))
16
+ type = T.let(json.fetch("Type"), String)
17
+ evaluation = NativeEvaluationDetails
18
+ .from_json(T.let(json.fetch("EvaluationResult"), T.nilable(T::Hash[String, T.untyped])))
19
+ target_frameworks = T.let(json.fetch("TargetFrameworks"), T.nilable(T::Array[String]))
20
+ is_dev_dependency = T.let(json.fetch("IsDevDependency"), T::Boolean)
21
+ is_direct = T.let(json.fetch("IsDirect"), T::Boolean)
22
+ is_transitive = T.let(json.fetch("IsTransitive"), T::Boolean)
23
+ is_override = T.let(json.fetch("IsOverride"), T::Boolean)
24
+ is_update = T.let(json.fetch("IsUpdate"), T::Boolean)
25
+ info_url = T.let(json.fetch("InfoUrl"), T.nilable(String))
26
+
27
+ NativeDependencyDetails.new(name: name,
28
+ version: version,
29
+ type: type,
30
+ evaluation: evaluation,
31
+ target_frameworks: target_frameworks,
32
+ is_dev_dependency: is_dev_dependency,
33
+ is_direct: is_direct,
34
+ is_transitive: is_transitive,
35
+ is_override: is_override,
36
+ is_update: is_update,
37
+ info_url: info_url)
38
+ end
39
+
40
+ sig do
41
+ params(name: String,
42
+ version: T.nilable(String),
43
+ type: String,
44
+ evaluation: T.nilable(NativeEvaluationDetails),
45
+ target_frameworks: T.nilable(T::Array[String]),
46
+ is_dev_dependency: T::Boolean,
47
+ is_direct: T::Boolean,
48
+ is_transitive: T::Boolean,
49
+ is_override: T::Boolean,
50
+ is_update: T::Boolean,
51
+ info_url: T.nilable(String)).void
52
+ end
53
+ def initialize(name:, version:, type:, evaluation:, target_frameworks:, is_dev_dependency:, is_direct:,
54
+ is_transitive:, is_override:, is_update:, info_url:)
55
+ @name = name
56
+ @version = version
57
+ @type = type
58
+ @evaluation = evaluation
59
+ @target_frameworks = target_frameworks
60
+ @is_dev_dependency = is_dev_dependency
61
+ @is_direct = is_direct
62
+ @is_transitive = is_transitive
63
+ @is_override = is_override
64
+ @is_update = is_update
65
+ @info_url = info_url
66
+ end
67
+
68
+ sig { returns(String) }
69
+ attr_reader :name
70
+
71
+ sig { returns(T.nilable(String)) }
72
+ attr_reader :version
73
+
74
+ sig { returns(String) }
75
+ attr_reader :type
76
+
77
+ sig { returns(T.nilable(NativeEvaluationDetails)) }
78
+ attr_reader :evaluation
79
+
80
+ sig { returns(T.nilable(T::Array[String])) }
81
+ attr_reader :target_frameworks
82
+
83
+ sig { returns(T::Boolean) }
84
+ attr_reader :is_dev_dependency
85
+
86
+ sig { returns(T::Boolean) }
87
+ attr_reader :is_direct
88
+
89
+ sig { returns(T::Boolean) }
90
+ attr_reader :is_transitive
91
+
92
+ sig { returns(T::Boolean) }
93
+ attr_reader :is_override
94
+
95
+ sig { returns(T::Boolean) }
96
+ attr_reader :is_update
97
+
98
+ sig { returns(T.nilable(String)) }
99
+ attr_reader :info_url
100
+ end
101
+ end
102
+ end