dependabot-nuget 0.80.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 +7 -0
- data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +49 -0
- data/lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb +53 -0
- data/lib/dependabot/nuget/file_fetcher.rb +216 -0
- data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +63 -0
- data/lib/dependabot/nuget/file_parser/project_file_parser.rb +154 -0
- data/lib/dependabot/nuget/file_parser/property_value_finder.rb +129 -0
- data/lib/dependabot/nuget/file_parser.rb +86 -0
- data/lib/dependabot/nuget/file_updater/packages_config_declaration_finder.rb +67 -0
- data/lib/dependabot/nuget/file_updater/project_file_declaration_finder.rb +76 -0
- data/lib/dependabot/nuget/file_updater/property_value_updater.rb +62 -0
- data/lib/dependabot/nuget/file_updater.rb +152 -0
- data/lib/dependabot/nuget/metadata_finder.rb +117 -0
- data/lib/dependabot/nuget/requirement.rb +90 -0
- data/lib/dependabot/nuget/update_checker/property_updater.rb +95 -0
- data/lib/dependabot/nuget/update_checker/repository_finder.rb +230 -0
- data/lib/dependabot/nuget/update_checker/requirements_updater.rb +79 -0
- data/lib/dependabot/nuget/update_checker/version_finder.rb +229 -0
- data/lib/dependabot/nuget/update_checker.rb +128 -0
- data/lib/dependabot/nuget/version.rb +23 -0
- data/lib/dependabot/nuget.rb +10 -0
- metadata +190 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require "dependabot/dependency"
|
6
|
+
require "dependabot/file_parsers"
|
7
|
+
require "dependabot/file_parsers/base"
|
8
|
+
|
9
|
+
# For details on how dotnet handles version constraints, see:
|
10
|
+
# https://docs.microsoft.com/en-us/nuget/reference/package-versioning
|
11
|
+
module Dependabot
|
12
|
+
module Nuget
|
13
|
+
class FileParser < Dependabot::FileParsers::Base
|
14
|
+
require "dependabot/file_parsers/base/dependency_set"
|
15
|
+
require_relative "file_parser/project_file_parser"
|
16
|
+
require_relative "file_parser/packages_config_parser"
|
17
|
+
|
18
|
+
PACKAGE_CONF_DEPENDENCY_SELECTOR = "packages > packages"
|
19
|
+
|
20
|
+
def parse
|
21
|
+
dependency_set = DependencySet.new
|
22
|
+
dependency_set += project_file_dependencies
|
23
|
+
dependency_set += packages_config_dependencies
|
24
|
+
dependency_set.dependencies
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def project_file_dependencies
|
30
|
+
dependency_set = DependencySet.new
|
31
|
+
|
32
|
+
(project_files + project_import_files).each do |file|
|
33
|
+
parser = project_file_parser
|
34
|
+
dependency_set += parser.dependency_set(project_file: file)
|
35
|
+
end
|
36
|
+
|
37
|
+
dependency_set
|
38
|
+
end
|
39
|
+
|
40
|
+
def packages_config_dependencies
|
41
|
+
dependency_set = DependencySet.new
|
42
|
+
|
43
|
+
packages_config_files.each do |file|
|
44
|
+
parser = PackagesConfigParser.new(packages_config: file)
|
45
|
+
dependency_set += parser.dependency_set
|
46
|
+
end
|
47
|
+
|
48
|
+
dependency_set
|
49
|
+
end
|
50
|
+
|
51
|
+
def project_file_parser
|
52
|
+
@project_file_parser ||=
|
53
|
+
ProjectFileParser.new(dependency_files: dependency_files)
|
54
|
+
end
|
55
|
+
|
56
|
+
def project_files
|
57
|
+
dependency_files.select { |df| df.name.match?(/\.[a-z]{2}proj$/) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def packages_config_files
|
61
|
+
dependency_files.select do |f|
|
62
|
+
f.name.split("/").last.casecmp("packages.config").zero?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def project_import_files
|
67
|
+
dependency_files -
|
68
|
+
project_files -
|
69
|
+
packages_config_files -
|
70
|
+
[nuget_config]
|
71
|
+
end
|
72
|
+
|
73
|
+
def nuget_config
|
74
|
+
dependency_files.find { |f| f.name.casecmp("nuget.config").zero? }
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_required_files
|
78
|
+
return if project_files.any? || packages_config_files.any?
|
79
|
+
|
80
|
+
raise "No project file or packages.config!"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Dependabot::FileParsers.register("nuget", Dependabot::Nuget::FileParser)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "dependabot/nuget/file_updater"
|
5
|
+
|
6
|
+
module Dependabot
|
7
|
+
module Nuget
|
8
|
+
class FileUpdater
|
9
|
+
class PackagesConfigDeclarationFinder
|
10
|
+
DECLARATION_REGEX =
|
11
|
+
%r{<package [^>]*?/>|
|
12
|
+
<package [^>]*?[^/]>.*?</package>}mx.freeze
|
13
|
+
|
14
|
+
attr_reader :dependency_name, :declaring_requirement,
|
15
|
+
:packages_config
|
16
|
+
|
17
|
+
def initialize(dependency_name:, packages_config:,
|
18
|
+
declaring_requirement:)
|
19
|
+
@dependency_name = dependency_name
|
20
|
+
@packages_config = packages_config
|
21
|
+
@declaring_requirement = declaring_requirement
|
22
|
+
|
23
|
+
if declaring_requirement[:file].split("/").last.
|
24
|
+
casecmp("packages.config").zero?
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
raise "Requirement not from packages.config!"
|
29
|
+
end
|
30
|
+
|
31
|
+
def declaration_strings
|
32
|
+
@declaration_strings ||= fetch_declaration_strings
|
33
|
+
end
|
34
|
+
|
35
|
+
def declaration_nodes
|
36
|
+
declaration_strings.map do |declaration_string|
|
37
|
+
Nokogiri::XML(declaration_string)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def fetch_declaration_strings
|
44
|
+
deep_find_declarations(packages_config.content).select do |nd|
|
45
|
+
node = Nokogiri::XML(nd)
|
46
|
+
node.remove_namespaces!
|
47
|
+
node = node.at_xpath("/package")
|
48
|
+
|
49
|
+
node_name = node.attribute("id")&.value&.strip ||
|
50
|
+
node.at_xpath("./id")&.content&.strip
|
51
|
+
next false unless node_name == dependency_name
|
52
|
+
|
53
|
+
node_requirement = node.attribute("version")&.value&.strip ||
|
54
|
+
node.at_xpath("./version")&.content&.strip
|
55
|
+
node_requirement == declaring_requirement.fetch(:requirement)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def deep_find_declarations(string)
|
60
|
+
string.scan(DECLARATION_REGEX).flat_map do |matching_node|
|
61
|
+
[matching_node, *deep_find_declarations(matching_node[0..-2])]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "dependabot/nuget/file_updater"
|
5
|
+
|
6
|
+
module Dependabot
|
7
|
+
module Nuget
|
8
|
+
class FileUpdater
|
9
|
+
class ProjectFileDeclarationFinder
|
10
|
+
DECLARATION_REGEX =
|
11
|
+
%r{
|
12
|
+
<PackageReference [^>]*?/>|
|
13
|
+
<PackageReference [^>]*?[^/]>.*?</PackageReference>|
|
14
|
+
<Dependency [^>]*?/>|
|
15
|
+
<Dependency [^>]*?[^/]>.*?</Dependency>|
|
16
|
+
<DevelopmentDependency [^>]*?/>|
|
17
|
+
<DevelopmentDependency [^>]*?[^/]>.*?</DevelopmentDependency>
|
18
|
+
}mx.freeze
|
19
|
+
|
20
|
+
attr_reader :dependency_name, :declaring_requirement,
|
21
|
+
:dependency_files
|
22
|
+
|
23
|
+
def initialize(dependency_name:, dependency_files:,
|
24
|
+
declaring_requirement:)
|
25
|
+
@dependency_name = dependency_name
|
26
|
+
@dependency_files = dependency_files
|
27
|
+
@declaring_requirement = declaring_requirement
|
28
|
+
end
|
29
|
+
|
30
|
+
def declaration_strings
|
31
|
+
@declaration_strings ||= fetch_declaration_strings
|
32
|
+
end
|
33
|
+
|
34
|
+
def declaration_nodes
|
35
|
+
declaration_strings.map do |declaration_string|
|
36
|
+
Nokogiri::XML(declaration_string)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def fetch_declaration_strings
|
43
|
+
deep_find_declarations(declaring_file.content).select do |nd|
|
44
|
+
node = Nokogiri::XML(nd)
|
45
|
+
node.remove_namespaces!
|
46
|
+
node = node.at_xpath("/PackageReference") ||
|
47
|
+
node.at_xpath("/Dependency") ||
|
48
|
+
node.at_xpath("/DevelopmentDependency")
|
49
|
+
|
50
|
+
node_name = node.attribute("Include")&.value&.strip ||
|
51
|
+
node.at_xpath("./Include")&.content&.strip
|
52
|
+
next false unless node_name == dependency_name
|
53
|
+
|
54
|
+
node_requirement = node.attribute("Version")&.value&.strip ||
|
55
|
+
node.at_xpath("./Version")&.content&.strip
|
56
|
+
node_requirement == declaring_requirement.fetch(:requirement)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def deep_find_declarations(string)
|
61
|
+
string.scan(DECLARATION_REGEX).flat_map do |matching_node|
|
62
|
+
[matching_node, *deep_find_declarations(matching_node[0..-2])]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def declaring_file
|
67
|
+
filename = declaring_requirement.fetch(:file)
|
68
|
+
declaring_file = dependency_files.find { |f| f.name == filename }
|
69
|
+
return declaring_file if declaring_file
|
70
|
+
|
71
|
+
raise "No file found with name #{filename}!"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require "dependabot/dependency_file"
|
6
|
+
require "dependabot/nuget/file_updater"
|
7
|
+
require "dependabot/nuget/file_parser/property_value_finder"
|
8
|
+
|
9
|
+
module Dependabot
|
10
|
+
module Nuget
|
11
|
+
class FileUpdater
|
12
|
+
class PropertyValueUpdater
|
13
|
+
def initialize(dependency_files:)
|
14
|
+
@dependency_files = dependency_files
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_files_for_property_change(property_name:, updated_value:,
|
18
|
+
callsite_file:)
|
19
|
+
declaration_details =
|
20
|
+
property_value_finder.
|
21
|
+
property_details(
|
22
|
+
property_name: property_name,
|
23
|
+
callsite_file: callsite_file
|
24
|
+
)
|
25
|
+
|
26
|
+
declaration_file = dependency_files.find do |f|
|
27
|
+
declaration_details.fetch(:file) == f.name
|
28
|
+
end
|
29
|
+
node = declaration_details.fetch(:node)
|
30
|
+
|
31
|
+
updated_content = declaration_file.content.sub(
|
32
|
+
%r{<#{Regexp.quote(node.name)}>
|
33
|
+
\s*#{Regexp.quote(node.content)}\s*
|
34
|
+
</#{Regexp.quote(node.name)}>}xm,
|
35
|
+
"<#{node.name}>#{updated_value}</#{node.name}>"
|
36
|
+
)
|
37
|
+
|
38
|
+
files = dependency_files.dup
|
39
|
+
files[files.index(declaration_file)] =
|
40
|
+
update_file(file: declaration_file, content: updated_content)
|
41
|
+
files
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :dependency_files
|
47
|
+
|
48
|
+
def property_value_finder
|
49
|
+
@property_value_finder ||=
|
50
|
+
Nuget::FileParser::PropertyValueFinder.
|
51
|
+
new(dependency_files: dependency_files)
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_file(file:, content:)
|
55
|
+
updated_file = file.dup
|
56
|
+
updated_file.content = content
|
57
|
+
updated_file
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/file_updaters"
|
4
|
+
require "dependabot/file_updaters/base"
|
5
|
+
|
6
|
+
module Dependabot
|
7
|
+
module Nuget
|
8
|
+
class FileUpdater < Dependabot::FileUpdaters::Base
|
9
|
+
require_relative "file_updater/packages_config_declaration_finder"
|
10
|
+
require_relative "file_updater/project_file_declaration_finder"
|
11
|
+
require_relative "file_updater/property_value_updater"
|
12
|
+
|
13
|
+
def self.updated_files_regex
|
14
|
+
[
|
15
|
+
%r{^[^/]*\.[a-z]{2}proj$},
|
16
|
+
/^packages\.config$/i
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
def updated_dependency_files
|
21
|
+
updated_files = dependency_files.dup
|
22
|
+
|
23
|
+
# Loop through each of the changed requirements, applying changes to
|
24
|
+
# all files for that change. Note that the logic is different here
|
25
|
+
# to other languages because donet has property inheritance across
|
26
|
+
# files
|
27
|
+
dependencies.each do |dependency|
|
28
|
+
updated_files = update_files_for_dependency(
|
29
|
+
files: updated_files,
|
30
|
+
dependency: dependency
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
updated_files.reject! { |f| dependency_files.include?(f) }
|
35
|
+
|
36
|
+
raise "No files changed!" if updated_files.none?
|
37
|
+
|
38
|
+
updated_files
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def project_files
|
44
|
+
dependency_files.select { |df| df.name.match?(/\.[a-z]{2}proj$/) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def packages_config_files
|
48
|
+
dependency_files.select do |f|
|
49
|
+
f.name.split("/").last.casecmp("packages.config").zero?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_required_files
|
54
|
+
return if project_files.any? || packages_config_files.any?
|
55
|
+
|
56
|
+
raise "No project file or packages.config!"
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_files_for_dependency(files:, dependency:)
|
60
|
+
# The UpdateChecker ensures the order of requirements is preserved
|
61
|
+
# when updating, so we can zip them together in new/old pairs.
|
62
|
+
reqs = dependency.requirements.zip(dependency.previous_requirements).
|
63
|
+
reject { |new_req, old_req| new_req == old_req }
|
64
|
+
|
65
|
+
# Loop through each changed requirement and update the files
|
66
|
+
reqs.each do |new_req, old_req|
|
67
|
+
raise "Bad req match" unless new_req[:file] == old_req[:file]
|
68
|
+
next if new_req[:requirement] == old_req[:requirement]
|
69
|
+
|
70
|
+
file = files.find { |f| f.name == new_req.fetch(:file) }
|
71
|
+
|
72
|
+
files =
|
73
|
+
if new_req.dig(:metadata, :property_name)
|
74
|
+
update_property_value(files, file, new_req)
|
75
|
+
else
|
76
|
+
update_declaration(files, dependency, file, old_req, new_req)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
files
|
81
|
+
end
|
82
|
+
|
83
|
+
def update_property_value(files, file, req)
|
84
|
+
files = files.dup
|
85
|
+
property_name = req.fetch(:metadata).fetch(:property_name)
|
86
|
+
|
87
|
+
PropertyValueUpdater.
|
88
|
+
new(dependency_files: files).
|
89
|
+
update_files_for_property_change(
|
90
|
+
property_name: property_name,
|
91
|
+
updated_value: req.fetch(:requirement),
|
92
|
+
callsite_file: file
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def update_declaration(files, dependency, file, old_req, new_req)
|
97
|
+
files = files.dup
|
98
|
+
|
99
|
+
updated_content = file.content
|
100
|
+
|
101
|
+
original_declarations(dependency, old_req).each do |old_dec|
|
102
|
+
updated_content = updated_content.gsub(
|
103
|
+
old_dec,
|
104
|
+
updated_declaration(old_dec, old_req, new_req)
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
raise "Expected content to change!" if updated_content == file.content
|
109
|
+
|
110
|
+
files[files.index(file)] =
|
111
|
+
updated_file(file: file, content: updated_content)
|
112
|
+
files
|
113
|
+
end
|
114
|
+
|
115
|
+
def original_declarations(dependency, requirement)
|
116
|
+
declaration_finder(dependency, requirement).declaration_strings
|
117
|
+
end
|
118
|
+
|
119
|
+
def declaration_finder(dependency, requirement)
|
120
|
+
@declaration_finders ||= {}
|
121
|
+
|
122
|
+
requirement_fn = requirement.fetch(:file)
|
123
|
+
@declaration_finders[dependency.hash + requirement.hash] ||=
|
124
|
+
if requirement_fn.split("/").last.casecmp("packages.config").zero?
|
125
|
+
PackagesConfigDeclarationFinder.new(
|
126
|
+
dependency_name: dependency.name,
|
127
|
+
declaring_requirement: requirement,
|
128
|
+
packages_config:
|
129
|
+
packages_config_files.find { |f| f.name == requirement_fn }
|
130
|
+
)
|
131
|
+
else
|
132
|
+
ProjectFileDeclarationFinder.new(
|
133
|
+
dependency_name: dependency.name,
|
134
|
+
declaring_requirement: requirement,
|
135
|
+
dependency_files: dependency_files
|
136
|
+
)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def updated_declaration(old_declaration, previous_req, requirement)
|
141
|
+
original_req_string = previous_req.fetch(:requirement)
|
142
|
+
|
143
|
+
old_declaration.gsub(
|
144
|
+
original_req_string,
|
145
|
+
requirement.fetch(:requirement)
|
146
|
+
)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
Dependabot::FileUpdaters.register("nuget", Dependabot::Nuget::FileUpdater)
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "dependabot/metadata_finders"
|
5
|
+
require "dependabot/metadata_finders/base"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module Nuget
|
9
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
10
|
+
private
|
11
|
+
|
12
|
+
def look_up_source
|
13
|
+
return Source.from_url(dependency_source_url) if dependency_source_url
|
14
|
+
|
15
|
+
look_up_source_in_nuspec(dependency_nuspec_file)
|
16
|
+
end
|
17
|
+
|
18
|
+
def look_up_source_in_nuspec(nuspec)
|
19
|
+
potential_source_urls = [
|
20
|
+
nuspec.at_css("package > metadata > repository")&.
|
21
|
+
attribute("url")&.value,
|
22
|
+
nuspec.at_css("package > metadata > repository > url")&.content,
|
23
|
+
nuspec.at_css("package > metadata > projectUrl")&.content,
|
24
|
+
nuspec.at_css("package > metadata > licenseUrl")&.content
|
25
|
+
].compact
|
26
|
+
|
27
|
+
source_url = potential_source_urls.find { |url| Source.from_url(url) }
|
28
|
+
source_url ||= source_from_anywhere_in_nuspec(nuspec)
|
29
|
+
|
30
|
+
Source.from_url(source_url)
|
31
|
+
end
|
32
|
+
|
33
|
+
def source_from_anywhere_in_nuspec(nuspec)
|
34
|
+
github_urls = []
|
35
|
+
nuspec.to_s.scan(Source::SOURCE_REGEX) do
|
36
|
+
github_urls << Regexp.last_match.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
github_urls.find do |url|
|
40
|
+
repo = Source.from_url(url).repo
|
41
|
+
repo.downcase.end_with?(dependency.name.downcase)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def dependency_nuspec_file
|
46
|
+
return @dependency_nuspec_file unless @dependency_nuspec_file.nil?
|
47
|
+
|
48
|
+
response = Excon.get(
|
49
|
+
dependency_nuspec_url,
|
50
|
+
headers: auth_header,
|
51
|
+
idempotent: true,
|
52
|
+
**SharedHelpers.excon_defaults
|
53
|
+
)
|
54
|
+
|
55
|
+
@dependency_nuspec_file = Nokogiri::XML(response.body)
|
56
|
+
end
|
57
|
+
|
58
|
+
# rubocop:disable Metrics/AbcSize
|
59
|
+
def dependency_nuspec_url
|
60
|
+
source = dependency.requirements.
|
61
|
+
find { |r| r&.fetch(:source) }&.fetch(:source)
|
62
|
+
|
63
|
+
if source&.key?(:nuspec_url)
|
64
|
+
source.fetch(:nuspec_url) ||
|
65
|
+
"https://api.nuget.org/v3-flatcontainer/"\
|
66
|
+
"#{dependency.name.downcase}/#{dependency.version}/"\
|
67
|
+
"#{dependency.name.downcase}.nuspec"
|
68
|
+
elsif source&.key?(:nuspec_url)
|
69
|
+
source.fetch("nuspec_url") ||
|
70
|
+
"https://api.nuget.org/v3-flatcontainer/"\
|
71
|
+
"#{dependency.name.downcase}/#{dependency.version}/"\
|
72
|
+
"#{dependency.name.downcase}.nuspec"
|
73
|
+
else
|
74
|
+
"https://api.nuget.org/v3-flatcontainer/"\
|
75
|
+
"#{dependency.name.downcase}/#{dependency.version}/"\
|
76
|
+
"#{dependency.name.downcase}.nuspec"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
# rubocop:enable Metrics/AbcSize
|
80
|
+
|
81
|
+
def dependency_source_url
|
82
|
+
source = dependency.requirements.
|
83
|
+
find { |r| r&.fetch(:source) }&.fetch(:source)
|
84
|
+
|
85
|
+
return unless source
|
86
|
+
return source.fetch(:source_url) if source.key?(:source_url)
|
87
|
+
|
88
|
+
source.fetch("source_url")
|
89
|
+
end
|
90
|
+
|
91
|
+
def auth_header
|
92
|
+
source = dependency.requirements.
|
93
|
+
find { |r| r&.fetch(:source) }&.fetch(:source)
|
94
|
+
url = source&.fetch(:url, nil) || source&.fetch("url")
|
95
|
+
|
96
|
+
token = credentials.
|
97
|
+
select { |cred| cred["type"] == "nuget_feed" }.
|
98
|
+
find { |cred| cred["url"] == url }&.
|
99
|
+
fetch("token", nil)
|
100
|
+
|
101
|
+
return {} unless token
|
102
|
+
|
103
|
+
if token.include?(":")
|
104
|
+
encoded_token = Base64.encode64(token).delete("\n")
|
105
|
+
{ "Authorization" => "Basic #{encoded_token}" }
|
106
|
+
elsif Base64.decode64(token).ascii_only? &&
|
107
|
+
Base64.decode64(token).include?(":")
|
108
|
+
{ "Authorization" => "Basic #{token.delete("\n")}" }
|
109
|
+
else
|
110
|
+
{ "Authorization" => "Bearer #{token}" }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
Dependabot::MetadataFinders.register("nuget", Dependabot::Nuget::MetadataFinder)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/utils"
|
4
|
+
require "dependabot/nuget/version"
|
5
|
+
|
6
|
+
# For details on .NET version constraints see:
|
7
|
+
# https://docs.microsoft.com/en-us/nuget/reference/package-versioning
|
8
|
+
module Dependabot
|
9
|
+
module Nuget
|
10
|
+
class Requirement < Gem::Requirement
|
11
|
+
def self.parse(obj)
|
12
|
+
return ["=", Nuget::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
13
|
+
|
14
|
+
unless (matches = PATTERN.match(obj.to_s))
|
15
|
+
msg = "Illformed requirement [#{obj.inspect}]"
|
16
|
+
raise BadRequirementError, msg
|
17
|
+
end
|
18
|
+
|
19
|
+
return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
|
20
|
+
|
21
|
+
[matches[1] || "=", Nuget::Version.new(matches[2])]
|
22
|
+
end
|
23
|
+
|
24
|
+
# For consistency with other langauges, we define a requirements array.
|
25
|
+
# Dotnet doesn't have an `OR` separator for requirements, so it always
|
26
|
+
# contains a single element.
|
27
|
+
def self.requirements_array(requirement_string)
|
28
|
+
[new(requirement_string)]
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(*requirements)
|
32
|
+
requirements = requirements.flatten.flat_map do |req_string|
|
33
|
+
convert_dotnet_constraint_to_ruby_constraint(req_string)
|
34
|
+
end
|
35
|
+
|
36
|
+
super(requirements)
|
37
|
+
end
|
38
|
+
|
39
|
+
def satisfied_by?(version)
|
40
|
+
version = Nuget::Version.new(version.to_s)
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def convert_dotnet_constraint_to_ruby_constraint(req_string)
|
47
|
+
return unless req_string
|
48
|
+
|
49
|
+
if req_string&.start_with?("(", "[")
|
50
|
+
return convert_dotnet_range_to_ruby_range(req_string)
|
51
|
+
end
|
52
|
+
|
53
|
+
return req_string.split(",").map(&:strip) if req_string.include?(",")
|
54
|
+
return req_string unless req_string.include?("*")
|
55
|
+
|
56
|
+
convert_wildcard_req(req_string)
|
57
|
+
end
|
58
|
+
|
59
|
+
def convert_dotnet_range_to_ruby_range(req_string)
|
60
|
+
lower_b, upper_b = req_string.split(",").map(&:strip)
|
61
|
+
|
62
|
+
lower_b =
|
63
|
+
if ["(", "["].include?(lower_b) then nil
|
64
|
+
elsif lower_b.start_with?("(") then "> #{lower_b.sub(/\(\s*/, '')}"
|
65
|
+
else ">= #{lower_b.sub(/\[\s*/, '').strip}"
|
66
|
+
end
|
67
|
+
|
68
|
+
upper_b =
|
69
|
+
if [")", "]"].include?(upper_b) then nil
|
70
|
+
elsif upper_b.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}"
|
71
|
+
else "<= #{upper_b.sub(/\s*\]/, '').strip}"
|
72
|
+
end
|
73
|
+
|
74
|
+
[lower_b, upper_b].compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def convert_wildcard_req(req_string)
|
78
|
+
return ">= 0" if req_string.start_with?("*")
|
79
|
+
|
80
|
+
defined_part = req_string.split("*").first
|
81
|
+
suffix = defined_part.end_with?(".") ? "0" : "a"
|
82
|
+
version = defined_part + suffix
|
83
|
+
"~> #{version}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Dependabot::Utils.
|
90
|
+
register_requirement_class("nuget", Dependabot::Nuget::Requirement)
|