dependabot-nuget 0.246.0 → 0.247.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +40 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +27 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +18 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +110 -0
- data/lib/dependabot/nuget/cache_manager.rb +9 -3
- data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +15 -12
- data/lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb +13 -3
- data/lib/dependabot/nuget/file_fetcher.rb +79 -31
- data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +10 -2
- data/lib/dependabot/nuget/file_parser/global_json_parser.rb +10 -2
- data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +11 -2
- data/lib/dependabot/nuget/file_parser/project_file_parser.rb +140 -41
- data/lib/dependabot/nuget/file_parser/property_value_finder.rb +57 -5
- data/lib/dependabot/nuget/file_parser.rb +3 -3
- data/lib/dependabot/nuget/file_updater/property_value_updater.rb +25 -8
- data/lib/dependabot/nuget/file_updater.rb +74 -38
- data/lib/dependabot/nuget/http_response_helpers.rb +6 -1
- data/lib/dependabot/nuget/metadata_finder.rb +27 -3
- data/lib/dependabot/nuget/nuget_client.rb +23 -0
- data/lib/dependabot/nuget/requirement.rb +4 -1
- data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +26 -15
- data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +11 -13
- data/lib/dependabot/nuget/update_checker/repository_finder.rb +25 -3
- data/lib/dependabot/nuget/update_checker/tfm_finder.rb +2 -2
- data/lib/dependabot/nuget/update_checker/version_finder.rb +15 -6
- data/lib/dependabot/nuget/update_checker.rb +4 -4
- data/lib/dependabot/nuget/version.rb +7 -2
- metadata +19 -5
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "nokogiri"
|
@@ -11,25 +11,38 @@ module Dependabot
|
|
11
11
|
module Nuget
|
12
12
|
class FileUpdater
|
13
13
|
class PropertyValueUpdater
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
|
14
17
|
def initialize(dependency_files:)
|
15
18
|
@dependency_files = dependency_files
|
16
19
|
end
|
17
20
|
|
21
|
+
sig do
|
22
|
+
params(property_name: String,
|
23
|
+
updated_value: String,
|
24
|
+
callsite_file: Dependabot::DependencyFile)
|
25
|
+
.returns(T::Array[Dependabot::DependencyFile])
|
26
|
+
end
|
18
27
|
def update_files_for_property_change(property_name:, updated_value:,
|
19
28
|
callsite_file:)
|
20
29
|
declaration_details =
|
21
|
-
property_value_finder
|
22
|
-
.property_details(
|
30
|
+
property_value_finder.property_details(
|
23
31
|
property_name: property_name,
|
24
32
|
callsite_file: callsite_file
|
25
33
|
)
|
34
|
+
throw "Unable to locate property details" unless declaration_details
|
26
35
|
|
36
|
+
declaration_filename = declaration_details.fetch(:file)
|
27
37
|
declaration_file = dependency_files.find do |f|
|
28
|
-
|
38
|
+
declaration_filename == f.name
|
29
39
|
end
|
40
|
+
throw "Unable to locate declaration file" unless declaration_file
|
41
|
+
|
42
|
+
content = T.must(declaration_file.content)
|
30
43
|
node = declaration_details.fetch(:node)
|
31
44
|
|
32
|
-
updated_content =
|
45
|
+
updated_content = content.sub(
|
33
46
|
%r{(<#{Regexp.quote(node.name)}(?:\s[^>]*)?>)
|
34
47
|
\s*#{Regexp.quote(node.content)}\s*
|
35
48
|
</#{Regexp.quote(node.name)}>}xm,
|
@@ -37,21 +50,25 @@ module Dependabot
|
|
37
50
|
)
|
38
51
|
|
39
52
|
files = dependency_files.dup
|
40
|
-
files
|
53
|
+
file_index = T.must(files.index(declaration_file))
|
54
|
+
files[file_index] =
|
41
55
|
update_file(file: declaration_file, content: updated_content)
|
42
56
|
files
|
43
57
|
end
|
44
58
|
|
45
59
|
private
|
46
60
|
|
61
|
+
sig { returns(T::Array[DependencyFile]) }
|
47
62
|
attr_reader :dependency_files
|
48
63
|
|
64
|
+
sig { returns(FileParser::PropertyValueFinder) }
|
49
65
|
def property_value_finder
|
50
66
|
@property_value_finder ||=
|
51
|
-
|
52
|
-
.new(dependency_files: dependency_files)
|
67
|
+
T.let(FileParser::PropertyValueFinder
|
68
|
+
.new(dependency_files: dependency_files), T.nilable(FileParser::PropertyValueFinder))
|
53
69
|
end
|
54
70
|
|
71
|
+
sig { params(file: DependencyFile, content: String).returns(DependencyFile) }
|
55
72
|
def update_file(file:, content:)
|
56
73
|
updated_file = file.dup
|
57
74
|
updated_file.content = content
|
@@ -1,10 +1,11 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strong
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "dependabot/dependency_file"
|
5
5
|
require "dependabot/file_updaters"
|
6
6
|
require "dependabot/file_updaters/base"
|
7
7
|
require "dependabot/nuget/native_helpers"
|
8
|
+
require "dependabot/shared_helpers"
|
8
9
|
require "sorbet-runtime"
|
9
10
|
|
10
11
|
module Dependabot
|
@@ -17,6 +18,7 @@ module Dependabot
|
|
17
18
|
require_relative "file_parser/dotnet_tools_json_parser"
|
18
19
|
require_relative "file_parser/packages_config_parser"
|
19
20
|
|
21
|
+
sig { override.returns(T::Array[Regexp]) }
|
20
22
|
def self.updated_files_regex
|
21
23
|
[
|
22
24
|
%r{^[^/]*\.([a-z]{2})?proj$},
|
@@ -29,32 +31,33 @@ module Dependabot
|
|
29
31
|
]
|
30
32
|
end
|
31
33
|
|
34
|
+
sig { override.returns(T::Array[Dependabot::DependencyFile]) }
|
32
35
|
def updated_dependency_files
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
+
base_dir = T.must(dependency_files.first).directory
|
37
|
+
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
38
|
+
dependencies.each do |dependency|
|
39
|
+
try_update_projects(dependency) || try_update_json(dependency)
|
40
|
+
end
|
41
|
+
updated_files = dependency_files.filter_map do |f|
|
42
|
+
updated_content = File.read(dependency_file_path(f))
|
43
|
+
next if updated_content == f.content
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
updated_content = File.read(dependency_file_path(f))
|
40
|
-
next if updated_content == f.content
|
45
|
+
normalized_content = normalize_content(f, updated_content)
|
46
|
+
next if normalized_content == f.content
|
41
47
|
|
42
|
-
|
43
|
-
next if normalized_content == f.content
|
48
|
+
next if only_deleted_lines?(f.content, normalized_content)
|
44
49
|
|
45
|
-
|
50
|
+
puts "The contents of file [#{f.name}] were updated."
|
46
51
|
|
47
|
-
|
52
|
+
updated_file(file: f, content: normalized_content)
|
53
|
+
end
|
54
|
+
updated_files
|
48
55
|
end
|
49
|
-
|
50
|
-
# reset repo files
|
51
|
-
SharedHelpers.reset_git_repo(T.cast(repo_contents_path, String)) if repo_contents_path
|
52
|
-
|
53
|
-
updated_files
|
54
56
|
end
|
55
57
|
|
56
58
|
private
|
57
59
|
|
60
|
+
sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
|
58
61
|
def try_update_projects(dependency)
|
59
62
|
update_ran = T.let(false, T::Boolean)
|
60
63
|
checked_files = Set.new
|
@@ -64,7 +67,7 @@ module Dependabot
|
|
64
67
|
project_dependencies = project_dependencies(project_file)
|
65
68
|
proj_path = dependency_file_path(project_file)
|
66
69
|
|
67
|
-
next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name)
|
70
|
+
next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name)&.zero? }
|
68
71
|
|
69
72
|
next unless repo_contents_path
|
70
73
|
|
@@ -79,16 +82,16 @@ module Dependabot
|
|
79
82
|
end
|
80
83
|
update_ran = true
|
81
84
|
end
|
82
|
-
|
83
85
|
update_ran
|
84
86
|
end
|
85
87
|
|
88
|
+
sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
|
86
89
|
def try_update_json(dependency)
|
87
|
-
if dotnet_tools_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name)
|
88
|
-
global_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name)
|
90
|
+
if dotnet_tools_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name)&.zero? } ||
|
91
|
+
global_json_dependencies.any? { |dep| dep.name.casecmp(dependency.name)&.zero? }
|
89
92
|
|
90
93
|
# We just need to feed the updater a project file, grab the first
|
91
|
-
project_file = project_files.first
|
94
|
+
project_file = T.must(project_files.first)
|
92
95
|
proj_path = dependency_file_path(project_file)
|
93
96
|
|
94
97
|
return false unless repo_contents_path
|
@@ -109,13 +112,14 @@ module Dependabot
|
|
109
112
|
# Tests need to track how many times we call the tooling updater to ensure we don't recurse needlessly
|
110
113
|
# Ideally we should find a way to not run this code in prod
|
111
114
|
# (or a better way to track calls made to NativeHelpers)
|
112
|
-
@update_tooling_calls ||= {}
|
115
|
+
@update_tooling_calls ||= T.let({}, T.nilable(T::Hash[String, Integer]))
|
113
116
|
key = proj_path + dependency.name
|
114
|
-
|
115
|
-
@update_tooling_calls[key]
|
116
|
-
|
117
|
-
|
118
|
-
|
117
|
+
@update_tooling_calls[key] =
|
118
|
+
if @update_tooling_calls[key]
|
119
|
+
T.must(@update_tooling_calls[key]) + 1
|
120
|
+
else
|
121
|
+
1
|
122
|
+
end
|
119
123
|
end
|
120
124
|
|
121
125
|
# Don't call this from outside tests, we're only checking that we aren't recursing needlessly
|
@@ -124,6 +128,7 @@ module Dependabot
|
|
124
128
|
@update_tooling_calls
|
125
129
|
end
|
126
130
|
|
131
|
+
sig { params(project_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::Dependency]) }
|
127
132
|
def project_dependencies(project_file)
|
128
133
|
# Collect all dependencies from the project file and associated packages.config
|
129
134
|
dependencies = project_file_parser.dependency_set(project_file: project_file).dependencies
|
@@ -134,38 +139,54 @@ module Dependabot
|
|
134
139
|
.dependency_set.dependencies
|
135
140
|
end
|
136
141
|
|
142
|
+
sig { params(project_file: Dependabot::DependencyFile).returns(T.nilable(Dependabot::DependencyFile)) }
|
137
143
|
def find_packages_config(project_file)
|
138
144
|
project_file_name = File.basename(project_file.name)
|
139
145
|
packages_config_path = project_file.name.gsub(project_file_name, "packages.config")
|
140
146
|
packages_config_files.find { |f| f.name == packages_config_path }
|
141
147
|
end
|
142
148
|
|
149
|
+
sig { returns(Dependabot::Nuget::FileParser::ProjectFileParser) }
|
143
150
|
def project_file_parser
|
144
151
|
@project_file_parser ||=
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
152
|
+
T.let(
|
153
|
+
FileParser::ProjectFileParser.new(
|
154
|
+
dependency_files: dependency_files,
|
155
|
+
credentials: credentials,
|
156
|
+
repo_contents_path: repo_contents_path
|
157
|
+
),
|
158
|
+
T.nilable(Dependabot::Nuget::FileParser::ProjectFileParser)
|
149
159
|
)
|
150
160
|
end
|
151
161
|
|
162
|
+
sig { returns(T::Array[Dependabot::Dependency]) }
|
152
163
|
def global_json_dependencies
|
153
164
|
return [] unless global_json
|
154
165
|
|
155
166
|
@global_json_dependencies ||=
|
156
|
-
|
167
|
+
T.let(
|
168
|
+
FileParser::GlobalJsonParser.new(global_json: T.must(global_json)).dependency_set.dependencies,
|
169
|
+
T.nilable(T::Array[Dependabot::Dependency])
|
170
|
+
)
|
157
171
|
end
|
158
172
|
|
173
|
+
sig { returns(T::Array[Dependabot::Dependency]) }
|
159
174
|
def dotnet_tools_json_dependencies
|
160
175
|
return [] unless dotnet_tools_json
|
161
176
|
|
162
177
|
@dotnet_tools_json_dependencies ||=
|
163
|
-
|
178
|
+
T.let(
|
179
|
+
FileParser::DotNetToolsJsonParser.new(dotnet_tools_json: T.must(dotnet_tools_json))
|
180
|
+
.dependency_set.dependencies,
|
181
|
+
T.nilable(T::Array[Dependabot::Dependency])
|
182
|
+
)
|
164
183
|
end
|
165
184
|
|
185
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
186
|
+
sig { params(dependency_file: Dependabot::DependencyFile, updated_content: String).returns(String) }
|
166
187
|
def normalize_content(dependency_file, updated_content)
|
167
188
|
# Fix up line endings
|
168
|
-
if dependency_file.content
|
189
|
+
if dependency_file.content&.include?("\r\n") && updated_content.match?(/(?<!\r)\n/)
|
169
190
|
# The original content contain windows style newlines.
|
170
191
|
# Ensure the updated content also uses windows style newlines.
|
171
192
|
updated_content = updated_content.gsub(/(?<!\r)\n/, "\r\n")
|
@@ -178,19 +199,21 @@ module Dependabot
|
|
178
199
|
end
|
179
200
|
|
180
201
|
# Fix up BOM
|
181
|
-
if !dependency_file.content
|
202
|
+
if !dependency_file.content&.start_with?("\uFEFF") && updated_content.start_with?("\uFEFF")
|
182
203
|
updated_content = updated_content.delete_prefix("\uFEFF")
|
183
204
|
puts "Removing BOM from [#{dependency_file.name}]."
|
184
|
-
elsif dependency_file.content
|
205
|
+
elsif dependency_file.content&.start_with?("\uFEFF") && !updated_content.start_with?("\uFEFF")
|
185
206
|
updated_content = "\uFEFF" + updated_content
|
186
207
|
puts "Adding BOM to [#{dependency_file.name}]."
|
187
208
|
end
|
188
209
|
|
189
210
|
updated_content
|
190
211
|
end
|
212
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
191
213
|
|
214
|
+
sig { params(dependency_file: Dependabot::DependencyFile).returns(String) }
|
192
215
|
def dependency_file_path(dependency_file)
|
193
|
-
if dependency_file.directory.start_with?(repo_contents_path)
|
216
|
+
if dependency_file.directory.start_with?(T.must(repo_contents_path))
|
194
217
|
File.join(dependency_file.directory, dependency_file.name)
|
195
218
|
else
|
196
219
|
file_directory = dependency_file.directory
|
@@ -199,29 +222,42 @@ module Dependabot
|
|
199
222
|
end
|
200
223
|
end
|
201
224
|
|
225
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
202
226
|
def project_files
|
203
227
|
dependency_files.select { |df| df.name.match?(/\.([a-z]{2})?proj$/) }
|
204
228
|
end
|
205
229
|
|
230
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
206
231
|
def packages_config_files
|
207
232
|
dependency_files.select do |f|
|
208
233
|
T.must(T.must(f.name.split("/").last).casecmp("packages.config")).zero?
|
209
234
|
end
|
210
235
|
end
|
211
236
|
|
237
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
212
238
|
def global_json
|
213
239
|
dependency_files.find { |f| T.must(f.name.casecmp("global.json")).zero? }
|
214
240
|
end
|
215
241
|
|
242
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
216
243
|
def dotnet_tools_json
|
217
244
|
dependency_files.find { |f| T.must(f.name.casecmp(".config/dotnet-tools.json")).zero? }
|
218
245
|
end
|
219
246
|
|
247
|
+
sig { override.void }
|
220
248
|
def check_required_files
|
221
249
|
return if project_files.any? || packages_config_files.any?
|
222
250
|
|
223
251
|
raise "No project file or packages.config!"
|
224
252
|
end
|
253
|
+
|
254
|
+
sig { params(original_content: T.nilable(String), updated_content: String).returns(T::Boolean) }
|
255
|
+
def only_deleted_lines?(original_content, updated_content)
|
256
|
+
original_lines = original_content&.lines || []
|
257
|
+
updated_lines = updated_content.lines
|
258
|
+
|
259
|
+
original_lines.count > updated_lines.count
|
260
|
+
end
|
225
261
|
end
|
226
262
|
end
|
227
263
|
end
|
@@ -1,9 +1,14 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "sorbet-runtime"
|
5
|
+
|
4
6
|
module Dependabot
|
5
7
|
module Nuget
|
6
8
|
module HttpResponseHelpers
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { params(string: String).returns(String) }
|
7
12
|
def self.remove_wrapping_zero_width_chars(string)
|
8
13
|
string.force_encoding("UTF-8").encode
|
9
14
|
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "nokogiri"
|
@@ -12,13 +12,28 @@ 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
|
+
|
15
29
|
private
|
16
30
|
|
31
|
+
sig { override.returns(T.nilable(Dependabot::Source)) }
|
17
32
|
def look_up_source
|
18
33
|
return Source.from_url(dependency_source_url) if dependency_source_url
|
19
34
|
|
20
35
|
if dependency_nuspec_file
|
21
|
-
src_repo = look_up_source_in_nuspec(dependency_nuspec_file)
|
36
|
+
src_repo = look_up_source_in_nuspec(T.must(dependency_nuspec_file))
|
22
37
|
return src_repo if src_repo
|
23
38
|
end
|
24
39
|
|
@@ -33,6 +48,7 @@ module Dependabot
|
|
33
48
|
nil
|
34
49
|
end
|
35
50
|
|
51
|
+
sig { returns(T.nilable(Dependabot::Source)) }
|
36
52
|
def src_repo_from_project
|
37
53
|
source = dependency.requirements.find { |r| r.fetch(:source) }&.fetch(:source)
|
38
54
|
return unless source
|
@@ -60,6 +76,7 @@ module Dependabot
|
|
60
76
|
# Ignored, this is expected for some registries that don't handle these request.
|
61
77
|
end
|
62
78
|
|
79
|
+
sig { params(body: String).returns(T.nilable(String)) }
|
63
80
|
def extract_search_url(body)
|
64
81
|
JSON.parse(body)
|
65
82
|
.fetch("resources", [])
|
@@ -67,6 +84,7 @@ module Dependabot
|
|
67
84
|
&.fetch("@id")
|
68
85
|
end
|
69
86
|
|
87
|
+
sig { params(body: String).returns(T.nilable(Dependabot::Source)) }
|
70
88
|
def extract_source_repo(body)
|
71
89
|
JSON.parse(body).fetch("data", []).each do |search_result|
|
72
90
|
next unless search_result["id"].casecmp(dependency.name).zero?
|
@@ -84,6 +102,7 @@ module Dependabot
|
|
84
102
|
nil
|
85
103
|
end
|
86
104
|
|
105
|
+
sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(Dependabot::Source)) }
|
87
106
|
def look_up_source_in_nuspec(nuspec)
|
88
107
|
potential_source_urls = [
|
89
108
|
nuspec.at_css("package > metadata > repository")
|
@@ -99,6 +118,7 @@ module Dependabot
|
|
99
118
|
Source.from_url(source_url)
|
100
119
|
end
|
101
120
|
|
121
|
+
sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(String)) }
|
102
122
|
def source_from_anywhere_in_nuspec(nuspec)
|
103
123
|
github_urls = []
|
104
124
|
nuspec.to_s.force_encoding(Encoding::UTF_8)
|
@@ -112,19 +132,21 @@ module Dependabot
|
|
112
132
|
end
|
113
133
|
end
|
114
134
|
|
135
|
+
sig { returns(T.nilable(Nokogiri::XML::Document)) }
|
115
136
|
def dependency_nuspec_file
|
116
137
|
return @dependency_nuspec_file unless @dependency_nuspec_file.nil?
|
117
138
|
|
118
139
|
return if dependency_nuspec_url.nil?
|
119
140
|
|
120
141
|
response = Dependabot::RegistryClient.get(
|
121
|
-
url: dependency_nuspec_url,
|
142
|
+
url: T.must(dependency_nuspec_url),
|
122
143
|
headers: auth_header
|
123
144
|
)
|
124
145
|
|
125
146
|
@dependency_nuspec_file = Nokogiri::XML(response.body)
|
126
147
|
end
|
127
148
|
|
149
|
+
sig { returns(T.nilable(String)) }
|
128
150
|
def dependency_nuspec_url
|
129
151
|
source = dependency.requirements
|
130
152
|
.find { |r| r.fetch(:source) }&.fetch(:source)
|
@@ -132,6 +154,7 @@ module Dependabot
|
|
132
154
|
source.fetch(:nuspec_url) if source&.key?(:nuspec_url)
|
133
155
|
end
|
134
156
|
|
157
|
+
sig { returns(T.nilable(String)) }
|
135
158
|
def dependency_source_url
|
136
159
|
source = dependency.requirements
|
137
160
|
.find { |r| r.fetch(:source) }&.fetch(:source)
|
@@ -143,6 +166,7 @@ module Dependabot
|
|
143
166
|
end
|
144
167
|
|
145
168
|
# rubocop:disable Metrics/PerceivedComplexity
|
169
|
+
sig { returns(T::Hash[String, String]) }
|
146
170
|
def auth_header
|
147
171
|
source = dependency.requirements
|
148
172
|
.find { |r| r.fetch(:source) }&.fetch(:source)
|
@@ -21,11 +21,33 @@ module Dependabot
|
|
21
21
|
get_package_versions_v3(dependency_name, repository_details)
|
22
22
|
elsif repository_type == "v2"
|
23
23
|
get_package_versions_v2(dependency_name, repository_details)
|
24
|
+
elsif repository_type == "local"
|
25
|
+
get_package_versions_local(dependency_name, repository_details)
|
24
26
|
else
|
25
27
|
raise "Unknown repository type: #{repository_type}"
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
31
|
+
sig do
|
32
|
+
params(dependency_name: String, repository_details: T::Hash[Symbol, String])
|
33
|
+
.returns(T.nilable(T::Set[String]))
|
34
|
+
end
|
35
|
+
private_class_method def self.get_package_versions_local(dependency_name, repository_details)
|
36
|
+
url = repository_details.fetch(:base_url)
|
37
|
+
raise "Local repo #{url} doesn't exist or isn't a directory" unless File.exist?(url) && File.directory?(url)
|
38
|
+
|
39
|
+
package_dir = File.join(url, dependency_name)
|
40
|
+
|
41
|
+
versions = Set.new
|
42
|
+
return versions unless File.exist?(package_dir) && File.directory?(package_dir)
|
43
|
+
|
44
|
+
Dir.each_child(package_dir) do |child|
|
45
|
+
versions.add(child) if File.directory?(File.join(package_dir, child))
|
46
|
+
end
|
47
|
+
|
48
|
+
versions
|
49
|
+
end
|
50
|
+
|
29
51
|
sig do
|
30
52
|
params(dependency_name: String, repository_details: T::Hash[Symbol, String])
|
31
53
|
.returns(T.nilable(T::Set[String]))
|
@@ -133,6 +155,7 @@ module Dependabot
|
|
133
155
|
&.find { |d| d.fetch("id").casecmp(dependency_name.downcase).zero? }
|
134
156
|
&.fetch("versions")
|
135
157
|
&.map { |d| d.fetch("version") }
|
158
|
+
&.to_set
|
136
159
|
end
|
137
160
|
|
138
161
|
sig do
|
@@ -60,6 +60,7 @@ module Dependabot
|
|
60
60
|
convert_wildcard_req(req_string)
|
61
61
|
end
|
62
62
|
|
63
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
63
64
|
def convert_dotnet_range_to_ruby_range(req_string)
|
64
65
|
lower_b, upper_b = req_string.split(",").map(&:strip).map do |bound|
|
65
66
|
next convert_range_wildcard_req(bound) if bound.include?("*")
|
@@ -75,7 +76,8 @@ module Dependabot
|
|
75
76
|
end
|
76
77
|
|
77
78
|
upper_b =
|
78
|
-
if
|
79
|
+
if !upper_b then nil
|
80
|
+
elsif [")", "]"].include?(upper_b) then nil
|
79
81
|
elsif upper_b.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}"
|
80
82
|
else
|
81
83
|
"<= #{upper_b.sub(/\s*\]/, '').strip}"
|
@@ -83,6 +85,7 @@ module Dependabot
|
|
83
85
|
|
84
86
|
[lower_b, upper_b].compact
|
85
87
|
end
|
88
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
86
89
|
|
87
90
|
def convert_range_wildcard_req(req_string)
|
88
91
|
range_end = req_string[-1]
|
@@ -66,23 +66,34 @@ module Dependabot
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def fetch_package_tfms(dependency_version)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
69
|
+
cache = CacheManager.cache("compatibility_checker_tfms_cache")
|
70
|
+
key = "#{dependency.name}::#{dependency_version}"
|
71
|
+
|
72
|
+
cache[key] ||= begin
|
73
|
+
nupkg_buffer = NupkgFetcher.fetch_nupkg_buffer(dependency_urls, dependency.name, dependency_version)
|
74
|
+
return [] unless nupkg_buffer
|
75
|
+
|
76
|
+
# Parse tfms from the folders beneath the lib folder
|
77
|
+
folder_name = "lib/"
|
78
|
+
tfms = Set.new
|
79
|
+
Zip::File.open_buffer(nupkg_buffer) do |zip|
|
80
|
+
lib_file_entries = zip.select { |entry| entry.name.start_with?(folder_name) }
|
81
|
+
# If there is no lib folder in this package, assume it is a development dependency
|
82
|
+
return nil if lib_file_entries.empty?
|
83
|
+
|
84
|
+
lib_file_entries.each do |entry|
|
85
|
+
_, tfm = entry.name.split("/").first(2)
|
86
|
+
|
87
|
+
# some zip compressors create empty directory entries (in this case `lib/`) which can cause the string
|
88
|
+
# split to return `nil`, so we have to explicitly guard against that
|
89
|
+
tfms << tfm if tfm
|
90
|
+
end
|
83
91
|
end
|
92
|
+
|
93
|
+
tfms.to_a
|
84
94
|
end
|
85
|
-
|
95
|
+
|
96
|
+
cache[key]
|
86
97
|
end
|
87
98
|
end
|
88
99
|
end
|
@@ -111,25 +111,19 @@ module Dependabot
|
|
111
111
|
current_redirects = 0
|
112
112
|
|
113
113
|
loop do
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
package_data.write(chunk)
|
119
|
-
end
|
120
|
-
|
121
|
-
response = connection.request(
|
122
|
-
method: :get,
|
114
|
+
# Directly download the stream without any additional settings _except_ for `omit_default_port: true` which
|
115
|
+
# is necessary to not break the URL signing that some NuGet feeds use.
|
116
|
+
response = Excon.get(
|
117
|
+
current_url,
|
123
118
|
headers: auth_header,
|
124
|
-
|
119
|
+
omit_default_port: true
|
125
120
|
)
|
126
121
|
|
127
122
|
# redirect the HTTP response as appropriate based on documentation here:
|
128
123
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
129
124
|
case response.status
|
130
125
|
when 200
|
131
|
-
|
132
|
-
return package_data
|
126
|
+
return response.body
|
133
127
|
when 301, 302, 303, 307, 308
|
134
128
|
current_redirects += 1
|
135
129
|
return nil if current_redirects > max_redirects
|
@@ -142,10 +136,14 @@ module Dependabot
|
|
142
136
|
end
|
143
137
|
|
144
138
|
def self.fetch_url(url, repository_details)
|
139
|
+
fetch_url_with_auth(url, repository_details.fetch(:auth_header))
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.fetch_url_with_auth(url, auth_header)
|
145
143
|
cache = CacheManager.cache("nupkg_fetcher_cache")
|
146
144
|
cache[url] ||= Dependabot::RegistryClient.get(
|
147
145
|
url: url,
|
148
|
-
headers:
|
146
|
+
headers: auth_header
|
149
147
|
)
|
150
148
|
|
151
149
|
cache[url]
|