dependabot-nuget 0.245.0 → 0.247.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 +4 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +42 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +164 -90
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +38 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +92 -18
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +27 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +115 -14
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/{UpdateWorker.DirsProj.cs → UpdateWorkerTests.DirsProj.cs} +22 -24
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +66 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +373 -83
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +117 -4
- 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 -45
- data/lib/dependabot/nuget/file_parser/property_value_finder.rb +57 -5
- data/lib/dependabot/nuget/file_parser.rb +18 -4
- 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 +19 -0
- data/lib/dependabot/nuget/metadata_finder.rb +32 -4
- data/lib/dependabot/nuget/nuget_client.rb +31 -13
- 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/dependency_finder.rb +23 -13
- data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +83 -21
- data/lib/dependabot/nuget/update_checker/repository_finder.rb +29 -13
- 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 +6 -7
- data/lib/dependabot/nuget/version.rb +7 -2
- metadata +21 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +0 -317
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strong
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "nokogiri"
|
@@ -30,7 +30,21 @@ module Dependabot
|
|
30
30
|
dependency_set += packages_config_dependencies
|
31
31
|
dependency_set += global_json_dependencies if global_json
|
32
32
|
dependency_set += dotnet_tools_json_dependencies if dotnet_tools_json
|
33
|
-
|
33
|
+
|
34
|
+
(dependencies, deps_with_unresolved_versions) = dependency_set.dependencies.partition do |d|
|
35
|
+
# try to parse the version; don't care about result, just that it succeeded
|
36
|
+
_ = Version.new(d.version)
|
37
|
+
true
|
38
|
+
rescue ArgumentError
|
39
|
+
# version could not be parsed
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
deps_with_unresolved_versions.each do |d|
|
44
|
+
Dependabot.logger.warn "Dependency '#{d.name}' excluded due to unparsable version: #{d.version}"
|
45
|
+
end
|
46
|
+
|
47
|
+
dependencies
|
34
48
|
end
|
35
49
|
|
36
50
|
private
|
@@ -63,14 +77,14 @@ module Dependabot
|
|
63
77
|
def global_json_dependencies
|
64
78
|
return DependencySet.new unless global_json
|
65
79
|
|
66
|
-
GlobalJsonParser.new(global_json: global_json).dependency_set
|
80
|
+
GlobalJsonParser.new(global_json: T.must(global_json)).dependency_set
|
67
81
|
end
|
68
82
|
|
69
83
|
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
70
84
|
def dotnet_tools_json_dependencies
|
71
85
|
return DependencySet.new unless dotnet_tools_json
|
72
86
|
|
73
|
-
DotNetToolsJsonParser.new(dotnet_tools_json: dotnet_tools_json).dependency_set
|
87
|
+
DotNetToolsJsonParser.new(dotnet_tools_json: T.must(dotnet_tools_json)).dependency_set
|
74
88
|
end
|
75
89
|
|
76
90
|
sig { returns(Dependabot::Nuget::FileParser::ProjectFileParser) }
|
@@ -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
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
|
6
|
+
module Dependabot
|
7
|
+
module Nuget
|
8
|
+
module HttpResponseHelpers
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { params(string: String).returns(String) }
|
12
|
+
def self.remove_wrapping_zero_width_chars(string)
|
13
|
+
string.force_encoding("UTF-8").encode
|
14
|
+
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
|
15
|
+
.gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "nokogiri"
|
@@ -12,13 +12,30 @@ 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
|
-
|
21
|
-
|
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
|
22
39
|
|
23
40
|
# Fallback to getting source from the search result's projectUrl or licenseUrl.
|
24
41
|
# GitHub Packages doesn't support getting the `.nuspec`, switch to getting
|
@@ -31,6 +48,7 @@ module Dependabot
|
|
31
48
|
nil
|
32
49
|
end
|
33
50
|
|
51
|
+
sig { returns(T.nilable(Dependabot::Source)) }
|
34
52
|
def src_repo_from_project
|
35
53
|
source = dependency.requirements.find { |r| r.fetch(:source) }&.fetch(:source)
|
36
54
|
return unless source
|
@@ -58,6 +76,7 @@ module Dependabot
|
|
58
76
|
# Ignored, this is expected for some registries that don't handle these request.
|
59
77
|
end
|
60
78
|
|
79
|
+
sig { params(body: String).returns(T.nilable(String)) }
|
61
80
|
def extract_search_url(body)
|
62
81
|
JSON.parse(body)
|
63
82
|
.fetch("resources", [])
|
@@ -65,6 +84,7 @@ module Dependabot
|
|
65
84
|
&.fetch("@id")
|
66
85
|
end
|
67
86
|
|
87
|
+
sig { params(body: String).returns(T.nilable(Dependabot::Source)) }
|
68
88
|
def extract_source_repo(body)
|
69
89
|
JSON.parse(body).fetch("data", []).each do |search_result|
|
70
90
|
next unless search_result["id"].casecmp(dependency.name).zero?
|
@@ -82,6 +102,7 @@ module Dependabot
|
|
82
102
|
nil
|
83
103
|
end
|
84
104
|
|
105
|
+
sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(Dependabot::Source)) }
|
85
106
|
def look_up_source_in_nuspec(nuspec)
|
86
107
|
potential_source_urls = [
|
87
108
|
nuspec.at_css("package > metadata > repository")
|
@@ -97,6 +118,7 @@ module Dependabot
|
|
97
118
|
Source.from_url(source_url)
|
98
119
|
end
|
99
120
|
|
121
|
+
sig { params(nuspec: Nokogiri::XML::Document).returns(T.nilable(String)) }
|
100
122
|
def source_from_anywhere_in_nuspec(nuspec)
|
101
123
|
github_urls = []
|
102
124
|
nuspec.to_s.force_encoding(Encoding::UTF_8)
|
@@ -110,17 +132,21 @@ module Dependabot
|
|
110
132
|
end
|
111
133
|
end
|
112
134
|
|
135
|
+
sig { returns(T.nilable(Nokogiri::XML::Document)) }
|
113
136
|
def dependency_nuspec_file
|
114
137
|
return @dependency_nuspec_file unless @dependency_nuspec_file.nil?
|
115
138
|
|
139
|
+
return if dependency_nuspec_url.nil?
|
140
|
+
|
116
141
|
response = Dependabot::RegistryClient.get(
|
117
|
-
url: dependency_nuspec_url,
|
142
|
+
url: T.must(dependency_nuspec_url),
|
118
143
|
headers: auth_header
|
119
144
|
)
|
120
145
|
|
121
146
|
@dependency_nuspec_file = Nokogiri::XML(response.body)
|
122
147
|
end
|
123
148
|
|
149
|
+
sig { returns(T.nilable(String)) }
|
124
150
|
def dependency_nuspec_url
|
125
151
|
source = dependency.requirements
|
126
152
|
.find { |r| r.fetch(:source) }&.fetch(:source)
|
@@ -128,6 +154,7 @@ module Dependabot
|
|
128
154
|
source.fetch(:nuspec_url) if source&.key?(:nuspec_url)
|
129
155
|
end
|
130
156
|
|
157
|
+
sig { returns(T.nilable(String)) }
|
131
158
|
def dependency_source_url
|
132
159
|
source = dependency.requirements
|
133
160
|
.find { |r| r.fetch(:source) }&.fetch(:source)
|
@@ -139,6 +166,7 @@ module Dependabot
|
|
139
166
|
end
|
140
167
|
|
141
168
|
# rubocop:disable Metrics/PerceivedComplexity
|
169
|
+
sig { returns(T::Hash[String, String]) }
|
142
170
|
def auth_header
|
143
171
|
source = dependency.requirements
|
144
172
|
.find { |r| r.fetch(:source) }&.fetch(:source)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "dependabot/nuget/cache_manager"
|
5
|
+
require "dependabot/nuget/http_response_helpers"
|
5
6
|
require "dependabot/nuget/update_checker/repository_finder"
|
6
7
|
require "sorbet-runtime"
|
7
8
|
|
@@ -20,11 +21,33 @@ module Dependabot
|
|
20
21
|
get_package_versions_v3(dependency_name, repository_details)
|
21
22
|
elsif repository_type == "v2"
|
22
23
|
get_package_versions_v2(dependency_name, repository_details)
|
24
|
+
elsif repository_type == "local"
|
25
|
+
get_package_versions_local(dependency_name, repository_details)
|
23
26
|
else
|
24
27
|
raise "Unknown repository type: #{repository_type}"
|
25
28
|
end
|
26
29
|
end
|
27
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
|
+
|
28
51
|
sig do
|
29
52
|
params(dependency_name: String, repository_details: T::Hash[Symbol, String])
|
30
53
|
.returns(T.nilable(T::Set[String]))
|
@@ -52,14 +75,15 @@ module Dependabot
|
|
52
75
|
doc = execute_xml_nuget_request(repository_details.fetch(:versions_url), repository_details)
|
53
76
|
return unless doc
|
54
77
|
|
55
|
-
|
78
|
+
# v2 APIs can differ, but all tested have this title value set to the name of the package
|
79
|
+
title_nodes = doc.xpath("/feed/entry/title")
|
56
80
|
matching_versions = Set.new
|
57
|
-
|
58
|
-
return nil unless
|
81
|
+
title_nodes.each do |title_node|
|
82
|
+
return nil unless title_node.text
|
59
83
|
|
60
|
-
next unless
|
84
|
+
next unless title_node.text.casecmp?(dependency_name)
|
61
85
|
|
62
|
-
version_node =
|
86
|
+
version_node = title_node.parent.xpath("properties/Version")
|
63
87
|
matching_versions << version_node.text if version_node && version_node.text
|
64
88
|
end
|
65
89
|
|
@@ -131,6 +155,7 @@ module Dependabot
|
|
131
155
|
&.find { |d| d.fetch("id").casecmp(dependency_name.downcase).zero? }
|
132
156
|
&.fetch("versions")
|
133
157
|
&.map { |d| d.fetch("version") }
|
158
|
+
&.to_set
|
134
159
|
end
|
135
160
|
|
136
161
|
sig do
|
@@ -162,7 +187,7 @@ module Dependabot
|
|
162
187
|
)
|
163
188
|
return unless response.status == 200
|
164
189
|
|
165
|
-
body = remove_wrapping_zero_width_chars(response.body)
|
190
|
+
body = HttpResponseHelpers.remove_wrapping_zero_width_chars(response.body)
|
166
191
|
JSON.parse(body)
|
167
192
|
end
|
168
193
|
|
@@ -193,13 +218,6 @@ module Dependabot
|
|
193
218
|
|
194
219
|
raise PrivateSourceTimedOut, repo_url
|
195
220
|
end
|
196
|
-
|
197
|
-
sig { params(string: String).returns(String) }
|
198
|
-
private_class_method def self.remove_wrapping_zero_width_chars(string)
|
199
|
-
string.force_encoding("UTF-8").encode
|
200
|
-
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
|
201
|
-
.gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
|
202
|
-
end
|
203
221
|
end
|
204
222
|
end
|
205
223
|
end
|
@@ -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]
|