dependabot-common 0.235.0 → 0.237.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/clients/azure.rb +3 -3
  3. data/lib/dependabot/config/file.rb +32 -9
  4. data/lib/dependabot/config/file_fetcher.rb +3 -3
  5. data/lib/dependabot/config/ignore_condition.rb +34 -8
  6. data/lib/dependabot/config/update_config.rb +42 -6
  7. data/lib/dependabot/config.rb +1 -1
  8. data/lib/dependabot/dependency_file.rb +89 -14
  9. data/lib/dependabot/dependency_group.rb +29 -5
  10. data/lib/dependabot/errors.rb +101 -13
  11. data/lib/dependabot/file_fetchers/base.rb +250 -93
  12. data/lib/dependabot/file_updaters/artifact_updater.rb +37 -10
  13. data/lib/dependabot/file_updaters/vendor_updater.rb +13 -3
  14. data/lib/dependabot/logger.rb +7 -2
  15. data/lib/dependabot/metadata_finders/base/changelog_finder.rb +13 -6
  16. data/lib/dependabot/pull_request_creator/commit_signer.rb +33 -7
  17. data/lib/dependabot/pull_request_creator/github.rb +13 -10
  18. data/lib/dependabot/pull_request_creator/message.rb +21 -2
  19. data/lib/dependabot/pull_request_creator/message_builder/link_and_mention_sanitizer.rb +37 -16
  20. data/lib/dependabot/pull_request_creator/message_builder/metadata_presenter.rb +5 -3
  21. data/lib/dependabot/pull_request_creator/message_builder.rb +5 -18
  22. data/lib/dependabot/pull_request_creator/pr_name_prefixer.rb +10 -4
  23. data/lib/dependabot/pull_request_updater/github.rb +2 -2
  24. data/lib/dependabot/shared_helpers.rb +117 -33
  25. data/lib/dependabot/simple_instrumentor.rb +22 -3
  26. data/lib/dependabot/source.rb +65 -17
  27. data/lib/dependabot/update_checkers/version_filters.rb +12 -1
  28. data/lib/dependabot/utils.rb +21 -2
  29. data/lib/dependabot/workspace/base.rb +42 -7
  30. data/lib/dependabot/workspace/change_attempt.rb +31 -3
  31. data/lib/dependabot/workspace/git.rb +34 -4
  32. data/lib/dependabot/workspace.rb +16 -2
  33. data/lib/dependabot.rb +1 -1
  34. metadata +37 -9
@@ -59,20 +59,11 @@ module Dependabot
59
59
  end
60
60
 
61
61
  def pr_message
62
- # TODO: Remove unignore_commands? feature flag once we are confident
63
- # that it is working as expected
64
- msg = if unignore_commands?
65
- "#{suffixed_pr_message_header}" \
66
- "#{commit_message_intro}" \
67
- "#{metadata_cascades}" \
68
- "#{ignore_conditions_table}" \
69
- "#{prefixed_pr_message_footer}"
70
- else
71
- "#{suffixed_pr_message_header}" \
72
- "#{commit_message_intro}" \
73
- "#{metadata_cascades}" \
74
- "#{prefixed_pr_message_footer}"
75
- end
62
+ msg = "#{suffixed_pr_message_header}" \
63
+ "#{commit_message_intro}" \
64
+ "#{metadata_cascades}" \
65
+ "#{ignore_conditions_table}" \
66
+ "#{prefixed_pr_message_footer}"
76
67
 
77
68
  truncate_pr_message(msg)
78
69
  rescue StandardError => e
@@ -80,10 +71,6 @@ module Dependabot
80
71
  suffixed_pr_message_header + prefixed_pr_message_footer
81
72
  end
82
73
 
83
- def unignore_commands?
84
- Experiments.enabled?(:unignore_commands)
85
- end
86
-
87
74
  # Truncate PR message as determined by the pr_message_max_length and pr_message_encoding instance variables
88
75
  # The encoding is used when calculating length, all messages are returned as ruby UTF_8 encoded string
89
76
  def truncate_pr_message(msg)
@@ -132,7 +132,7 @@ module Dependabot
132
132
  case last_dependabot_commit_style
133
133
  when :gitmoji then true
134
134
  when :conventional_prefix, :conventional_prefix_with_scope
135
- last_dependabot_commit_message.match?(/: (\[[Ss]ecurity\] )?(B|U)/)
135
+ last_dependabot_commit_title.match?(/: (\[[Ss]ecurity\] )?(B|U)/)
136
136
  else raise "Unknown commit style #{last_dependabot_commit_style}"
137
137
  end
138
138
  end
@@ -152,7 +152,7 @@ module Dependabot
152
152
  end
153
153
 
154
154
  def last_dependabot_commit_style
155
- return unless (msg = last_dependabot_commit_message)
155
+ return unless (msg = last_dependabot_commit_title)
156
156
 
157
157
  return :gitmoji if msg.start_with?("⬆️")
158
158
  return :conventional_prefix if msg.match?(/\A(chore|build|upgrade):/i)
@@ -162,7 +162,7 @@ module Dependabot
162
162
  end
163
163
 
164
164
  def last_dependabot_commit_prefix
165
- last_dependabot_commit_message&.split(/[:(]/)&.first
165
+ last_dependabot_commit_title&.split(/[:(]/)&.first
166
166
  end
167
167
 
168
168
  # rubocop:disable Metrics/PerceivedComplexity
@@ -245,7 +245,7 @@ module Dependabot
245
245
  ANGULAR_PREFIXES.any? { |pre| message.match?(/#{pre}[:(]/i) }
246
246
  end
247
247
 
248
- return last_dependabot_commit_message&.start_with?(/[A-Z]/) if semantic_messages.none?
248
+ return last_dependabot_commit_title&.start_with?(/[A-Z]/) if semantic_messages.none?
249
249
 
250
250
  capitalized_msgs = semantic_messages
251
251
  .select { |m| m.start_with?(/[A-Z]/) }
@@ -329,6 +329,12 @@ module Dependabot
329
329
  .map(&:strip)
330
330
  end
331
331
 
332
+ def last_dependabot_commit_title
333
+ return @last_dependabot_commit_title if defined?(@last_dependabot_commit_title)
334
+
335
+ @last_dependabot_commit_title = last_dependabot_commit_message&.split("\n")&.first
336
+ end
337
+
332
338
  def last_dependabot_commit_message
333
339
  @last_dependabot_commit_message ||=
334
340
  case source.provider
@@ -128,7 +128,7 @@ module Dependabot
128
128
  if file.type == "submodule"
129
129
  {
130
130
  path: file.path.sub(%r{^/}, ""),
131
- mode: "160000",
131
+ mode: Dependabot::DependencyFile::Mode::SUBMODULE,
132
132
  type: "commit",
133
133
  sha: file.content
134
134
  }
@@ -146,7 +146,7 @@ module Dependabot
146
146
 
147
147
  {
148
148
  path: file.realpath,
149
- mode: "100644",
149
+ mode: Dependabot::DependencyFile::Mode::FILE,
150
150
  type: "blob"
151
151
  }.merge(content)
152
152
  end
@@ -1,4 +1,4 @@
1
- # typed: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "digest"
@@ -8,6 +8,7 @@ require "fileutils"
8
8
  require "json"
9
9
  require "open3"
10
10
  require "shellwords"
11
+ require "sorbet-runtime"
11
12
  require "tmpdir"
12
13
 
13
14
  require "dependabot/simple_instrumentor"
@@ -18,20 +19,34 @@ require "dependabot"
18
19
 
19
20
  module Dependabot
20
21
  module SharedHelpers
21
- GIT_CONFIG_GLOBAL_PATH = File.expand_path(".gitconfig", Utils::BUMP_TMP_DIR_PATH)
22
- USER_AGENT = "dependabot-core/#{Dependabot::VERSION} " \
23
- "#{Excon::USER_AGENT} ruby/#{RUBY_VERSION} " \
24
- "(#{RUBY_PLATFORM}) " \
25
- "(+https://github.com/dependabot/dependabot-core)".freeze
22
+ extend T::Sig
23
+
24
+ GIT_CONFIG_GLOBAL_PATH = T.let(File.expand_path(".gitconfig", Utils::BUMP_TMP_DIR_PATH), String)
25
+ USER_AGENT = T.let(
26
+ "dependabot-core/#{Dependabot::VERSION} " \
27
+ "#{Excon::USER_AGENT} ruby/#{RUBY_VERSION} " \
28
+ "(#{RUBY_PLATFORM}) " \
29
+ "(+https://github.com/dependabot/dependabot-core)".freeze,
30
+ String
31
+ )
26
32
  SIGKILL = 9
27
33
 
34
+ sig do
35
+ type_parameters(:T)
36
+ .params(
37
+ directory: String,
38
+ repo_contents_path: T.nilable(String),
39
+ block: T.proc.params(arg0: T.any(Pathname, String)).returns(T.type_parameter(:T))
40
+ )
41
+ .returns(T.type_parameter(:T))
42
+ end
28
43
  def self.in_a_temporary_repo_directory(directory = "/", repo_contents_path = nil, &block)
29
44
  if repo_contents_path
30
45
  # If a workspace has been defined to allow orcestration of the git repo
31
46
  # by the runtime we should defer to it, otherwise we prepare the folder
32
47
  # for direct use and yield.
33
48
  if Dependabot::Workspace.active_workspace
34
- Dependabot::Workspace.active_workspace.change(&block)
49
+ T.must(Dependabot::Workspace.active_workspace).change(&block)
35
50
  else
36
51
  path = Pathname.new(File.join(repo_contents_path, directory)).expand_path
37
52
  reset_git_repo(repo_contents_path)
@@ -46,9 +61,18 @@ module Dependabot
46
61
  end
47
62
  end
48
63
 
49
- def self.in_a_temporary_directory(directory = "/")
64
+ sig do
65
+ type_parameters(:T)
66
+ .params(
67
+ directory: String,
68
+ _block: T.proc.params(arg0: T.any(Pathname, String)).returns(T.type_parameter(:T))
69
+ )
70
+ .returns(T.type_parameter(:T))
71
+ end
72
+ def self.in_a_temporary_directory(directory = "/", &_block)
50
73
  FileUtils.mkdir_p(Utils::BUMP_TMP_DIR_PATH)
51
74
  tmp_dir = Dir.mktmpdir(Utils::BUMP_TMP_FILE_PREFIX, Utils::BUMP_TMP_DIR_PATH)
75
+ path = Pathname.new(File.join(tmp_dir, directory)).expand_path
52
76
 
53
77
  begin
54
78
  path = Pathname.new(File.join(tmp_dir, directory)).expand_path
@@ -60,28 +84,59 @@ module Dependabot
60
84
  end
61
85
 
62
86
  class HelperSubprocessFailed < Dependabot::DependabotError
63
- attr_reader :error_class, :error_context, :trace
87
+ extend T::Sig
88
+
89
+ sig { returns(String) }
90
+ attr_reader :error_class
91
+
92
+ sig { returns(T::Hash[Symbol, String]) }
93
+ attr_reader :error_context
94
+
95
+ sig { returns(T.nilable(T::Array[String])) }
96
+ attr_reader :trace
64
97
 
98
+ sig do
99
+ params(
100
+ message: String,
101
+ error_context: T::Hash[Symbol, String],
102
+ error_class: T.nilable(String),
103
+ trace: T.nilable(T::Array[String])
104
+ ).void
105
+ end
65
106
  def initialize(message:, error_context:, error_class: nil, trace: nil)
66
107
  super(message)
67
- @error_class = error_class || "HelperSubprocessFailed"
108
+ @error_class = T.let(error_class || "HelperSubprocessFailed", String)
68
109
  @error_context = error_context
69
- @fingerprint = error_context[:fingerprint] || error_context[:command]
110
+ @fingerprint = T.let(error_context[:fingerprint] || error_context[:command], T.nilable(String))
70
111
  @trace = trace
71
112
  end
72
113
 
114
+ sig { returns(T::Hash[Symbol, T.untyped]) }
73
115
  def raven_context
74
116
  { fingerprint: [@fingerprint], extra: @error_context.except(:stderr_output, :fingerprint) }
75
117
  end
76
118
  end
77
119
 
78
120
  # Escapes all special characters, e.g. = & | <>
121
+ sig { params(command: String).returns(String) }
79
122
  def self.escape_command(command)
80
123
  command_parts = command.split.map(&:strip).reject(&:empty?)
81
124
  Shellwords.join(command_parts)
82
125
  end
83
126
 
84
127
  # rubocop:disable Metrics/MethodLength
128
+ # rubocop:disable Metrics/AbcSize
129
+ sig do
130
+ params(
131
+ command: String,
132
+ function: String,
133
+ args: T.any(T::Array[String], T::Hash[Symbol, String]),
134
+ env: T.nilable(T::Hash[String, String]),
135
+ stderr_to_stdout: T::Boolean,
136
+ allow_unsafe_shell_command: T::Boolean
137
+ )
138
+ .returns(T.nilable(T.any(String, T::Hash[String, T.untyped], T::Array[T::Hash[String, T.untyped]])))
139
+ end
85
140
  def self.run_helper_subprocess(command:, function:, args:, env: nil,
86
141
  stderr_to_stdout: false,
87
142
  allow_unsafe_shell_command: false)
@@ -95,11 +150,11 @@ module Dependabot
95
150
  if ENV["DEBUG_FUNCTION"] == function
96
151
  puts helper_subprocess_bash_command(stdin_data: stdin_data, command: cmd, env: env)
97
152
  # Pause execution so we can run helpers inside the temporary directory
98
- debugger # rubocop:disable Lint/Debugger
153
+ T.unsafe(self).debugger
99
154
  end
100
155
 
101
156
  env_cmd = [env, cmd].compact
102
- stdout, stderr, process = Open3.capture3(*env_cmd, stdin_data: stdin_data)
157
+ stdout, stderr, process = T.unsafe(Open3).capture3(*env_cmd, stdin_data: stdin_data)
103
158
  time_taken = Time.now - start
104
159
 
105
160
  if ENV["DEBUG_HELPERS"] == "true"
@@ -126,24 +181,27 @@ module Dependabot
126
181
 
127
182
  check_out_of_memory_error(stderr, error_context)
128
183
 
129
- response = JSON.parse(stdout)
130
- return response["result"] if process.success?
131
-
132
- raise HelperSubprocessFailed.new(
133
- message: response["error"],
134
- error_class: response["error_class"],
135
- error_context: error_context,
136
- trace: response["trace"]
137
- )
138
- rescue JSON::ParserError
139
- raise HelperSubprocessFailed.new(
140
- message: stdout || "No output from command",
141
- error_class: "JSON::ParserError",
142
- error_context: error_context
143
- )
184
+ begin
185
+ response = JSON.parse(stdout)
186
+ return response["result"] if process.success?
187
+
188
+ raise HelperSubprocessFailed.new(
189
+ message: response["error"],
190
+ error_class: response["error_class"],
191
+ error_context: error_context,
192
+ trace: response["trace"]
193
+ )
194
+ rescue JSON::ParserError
195
+ raise HelperSubprocessFailed.new(
196
+ message: stdout || "No output from command",
197
+ error_class: "JSON::ParserError",
198
+ error_context: error_context
199
+ )
200
+ end
144
201
  end
145
202
 
146
203
  # rubocop:enable Metrics/MethodLength
204
+ sig { params(stderr: T.nilable(String), error_context: T::Hash[Symbol, String]).void }
147
205
  def self.check_out_of_memory_error(stderr, error_context)
148
206
  return unless stderr&.include?("JavaScript heap out of memory")
149
207
 
@@ -154,12 +212,14 @@ module Dependabot
154
212
  )
155
213
  end
156
214
 
215
+ sig { returns(T::Array[T.class_of(Excon::Middleware::Base)]) }
157
216
  def self.excon_middleware
158
- Excon.defaults[:middlewares] +
217
+ T.must(T.cast(Excon.defaults, T::Hash[Symbol, T::Array[T.class_of(Excon::Middleware::Base)]])[:middlewares]) +
159
218
  [Excon::Middleware::Decompress] +
160
219
  [Excon::Middleware::RedirectFollower]
161
220
  end
162
221
 
222
+ sig { params(headers: T.nilable(T::Hash[String, String])).returns(T::Hash[String, String]) }
163
223
  def self.excon_headers(headers = nil)
164
224
  headers ||= {}
165
225
  {
@@ -167,9 +227,10 @@ module Dependabot
167
227
  }.merge(headers)
168
228
  end
169
229
 
230
+ sig { params(options: T.nilable(T::Hash[Symbol, T.untyped])).returns(T::Hash[Symbol, T.untyped]) }
170
231
  def self.excon_defaults(options = nil)
171
232
  options ||= {}
172
- headers = options.delete(:headers)
233
+ headers = T.cast(options.delete(:headers), T.nilable(T::Hash[String, String]))
173
234
  {
174
235
  instrumentor: Dependabot::SimpleInstrumentor,
175
236
  connect_timeout: 5,
@@ -182,7 +243,15 @@ module Dependabot
182
243
  }.merge(options)
183
244
  end
184
245
 
185
- def self.with_git_configured(credentials:)
246
+ sig do
247
+ type_parameters(:T)
248
+ .params(
249
+ credentials: T::Array[T::Hash[String, String]],
250
+ _block: T.proc.returns(T.type_parameter(:T))
251
+ )
252
+ .returns(T.type_parameter(:T))
253
+ end
254
+ def self.with_git_configured(credentials:, &_block)
186
255
  safe_directories = find_safe_directories
187
256
 
188
257
  FileUtils.mkdir_p(Utils::BUMP_TMP_DIR_PATH)
@@ -203,18 +272,20 @@ module Dependabot
203
272
  end
204
273
 
205
274
  # Handle SCP-style git URIs
275
+ sig { params(uri: String).returns(String) }
206
276
  def self.scp_to_standard(uri)
207
277
  return uri unless uri.start_with?("git@")
208
278
 
209
- "https://#{uri.split('git@').last.sub(%r{:/?}, '/')}"
279
+ "https://#{T.must(uri.split('git@').last).sub(%r{:/?}, '/')}"
210
280
  end
211
281
 
282
+ sig { returns(String) }
212
283
  def self.credential_helper_path
213
284
  File.join(__dir__, "../../bin/git-credential-store-immutable")
214
285
  end
215
286
 
216
- # rubocop:disable Metrics/AbcSize
217
287
  # rubocop:disable Metrics/PerceivedComplexity
288
+ sig { params(credentials: T::Array[T::Hash[String, String]], safe_directories: T::Array[String]).void }
218
289
  def self.configure_git_to_use_https_with_credentials(credentials, safe_directories)
219
290
  File.open(GIT_CONFIG_GLOBAL_PATH, "w") do |file|
220
291
  file << "# Generated by dependabot/dependabot-core"
@@ -274,6 +345,7 @@ module Dependabot
274
345
  # rubocop:enable Metrics/AbcSize
275
346
  # rubocop:enable Metrics/PerceivedComplexity
276
347
 
348
+ sig { params(host: String).void }
277
349
  def self.configure_git_to_use_https(host)
278
350
  # NOTE: we use --global here (rather than --system) so that Dependabot
279
351
  # can be run without privileged access
@@ -299,6 +371,7 @@ module Dependabot
299
371
  )
300
372
  end
301
373
 
374
+ sig { params(path: String).void }
302
375
  def self.reset_git_repo(path)
303
376
  Dir.chdir(path) do
304
377
  run_shell_command("git reset HEAD --hard")
@@ -306,6 +379,7 @@ module Dependabot
306
379
  end
307
380
  end
308
381
 
382
+ sig { returns(T::Array[String]) }
309
383
  def self.find_safe_directories
310
384
  # to preserve safe directories from global .gitconfig
311
385
  output, process = Open3.capture2("git config --global --get-all safe.directory")
@@ -314,6 +388,15 @@ module Dependabot
314
388
  safe_directories
315
389
  end
316
390
 
391
+ sig do
392
+ params(
393
+ command: String,
394
+ allow_unsafe_shell_command: T::Boolean,
395
+ env: T.nilable(T::Hash[String, String]),
396
+ fingerprint: T.nilable(String),
397
+ stderr_to_stdout: T::Boolean
398
+ ).returns(String)
399
+ end
317
400
  def self.run_shell_command(command,
318
401
  allow_unsafe_shell_command: false,
319
402
  env: {},
@@ -347,6 +430,7 @@ module Dependabot
347
430
  )
348
431
  end
349
432
 
433
+ sig { params(command: String, stdin_data: String, env: T.nilable(T::Hash[String, String])).returns(String) }
350
434
  def self.helper_subprocess_bash_command(command:, stdin_data:, env:)
351
435
  escaped_stdin_data = stdin_data.gsub("\"", "\\\"")
352
436
  env_keys = env ? env.compact.map { |k, v| "#{k}=#{v}" }.join(" ") + " " : ""
@@ -1,16 +1,35 @@
1
- # typed: true
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  module Dependabot
5
7
  module SimpleInstrumentor
6
8
  class << self
7
- attr_accessor :events, :subscribers
9
+ extend T::Sig
10
+ extend T::Generic
11
+
12
+ sig { returns(T.nilable(T::Array[T.proc.params(name: String, params: T::Hash[Symbol, T.untyped]).void])) }
13
+ attr_accessor :subscribers
8
14
 
15
+ sig { params(block: T.proc.params(name: String, params: T::Hash[Symbol, T.untyped]).void).void }
9
16
  def subscribe(&block)
10
- @subscribers ||= []
17
+ @subscribers ||= T.let(
18
+ [],
19
+ T.nilable(T::Array[T.proc.params(name: String, params: T::Hash[Symbol, T.untyped]).void])
20
+ )
11
21
  @subscribers << block
12
22
  end
13
23
 
24
+ sig do
25
+ type_parameters(:T)
26
+ .params(
27
+ name: String,
28
+ params: T::Hash[Symbol, T.untyped],
29
+ block: T.proc.returns(T.type_parameter(:T))
30
+ )
31
+ .returns(T.nilable(T.type_parameter(:T)))
32
+ end
14
33
  def instrument(name, params = {}, &block)
15
34
  @subscribers&.each { |s| s.call(name, params) }
16
35
  yield if block
@@ -1,8 +1,12 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  module Dependabot
5
7
  class Source
8
+ extend T::Sig
9
+
6
10
  GITHUB_SOURCE = %r{
7
11
  (?<provider>github)
8
12
  (?:\.com)[/:]
@@ -59,24 +63,48 @@ module Dependabot
59
63
  (?:#{CODECOMMIT_SOURCE})
60
64
  /x
61
65
 
62
- IGNORED_PROVIDER_HOSTS = %w(gitbox.apache.org svn.apache.org fuchsia.googlesource.com).freeze
66
+ IGNORED_PROVIDER_HOSTS = T.let(%w(gitbox.apache.org svn.apache.org fuchsia.googlesource.com).freeze,
67
+ T::Array[String])
68
+
69
+ sig { returns(String) }
70
+ attr_accessor :provider
71
+
72
+ sig { returns(String) }
73
+ attr_accessor :repo
74
+
75
+ sig { returns(T.nilable(String)) }
76
+ attr_accessor :directory
63
77
 
64
- attr_accessor :provider, :repo, :directory, :branch, :commit,
65
- :hostname, :api_endpoint
78
+ sig { returns(T.nilable(T::Array[String])) }
79
+ attr_accessor :directories
66
80
 
81
+ sig { returns(T.nilable(String)) }
82
+ attr_accessor :branch
83
+
84
+ sig { returns(T.nilable(String)) }
85
+ attr_accessor :commit
86
+
87
+ sig { returns(String) }
88
+ attr_accessor :hostname
89
+
90
+ sig { returns(T.nilable(String)) }
91
+ attr_accessor :api_endpoint
92
+
93
+ sig { params(url_string: T.nilable(String)).returns(T.nilable(Source)) }
67
94
  def self.from_url(url_string)
68
95
  return github_enterprise_from_url(url_string) unless url_string&.match?(SOURCE_REGEX)
69
96
 
70
- captures = url_string.match(SOURCE_REGEX).named_captures
97
+ captures = T.must(url_string.match(SOURCE_REGEX)).named_captures
71
98
 
72
99
  new(
73
- provider: captures.fetch("provider"),
74
- repo: captures.fetch("repo").delete_suffix(".git").delete_suffix("."),
100
+ provider: T.must(captures.fetch("provider")),
101
+ repo: T.must(captures.fetch("repo")).delete_suffix(".git").delete_suffix("."),
75
102
  directory: captures.fetch("directory"),
76
103
  branch: captures.fetch("branch")
77
104
  )
78
105
  end
79
106
 
107
+ sig { params(url_string: T.nilable(String)).returns(T.nilable(Source)) }
80
108
  def self.github_enterprise_from_url(url_string)
81
109
  captures = url_string&.match(GITHUB_ENTERPRISE_SOURCE)&.named_captures
82
110
  return unless captures
@@ -88,7 +116,7 @@ module Dependabot
88
116
 
89
117
  new(
90
118
  provider: "github",
91
- repo: captures.fetch("repo").delete_suffix(".git").delete_suffix("."),
119
+ repo: T.must(captures.fetch("repo")).delete_suffix(".git").delete_suffix("."),
92
120
  directory: captures.fetch("directory"),
93
121
  branch: captures.fetch("branch"),
94
122
  hostname: captures.fetch("host"),
@@ -96,18 +124,30 @@ module Dependabot
96
124
  )
97
125
  end
98
126
 
127
+ sig { params(base_url: String).returns(T::Boolean) }
99
128
  def self.github_enterprise?(base_url)
100
129
  resp = Excon.get(File.join(base_url, "status"))
101
130
  resp.status == 200 &&
102
131
  # Alternatively: resp.headers["Server"] == "GitHub.com", but this
103
132
  # currently doesn't work with development environments
104
- resp.headers["X-GitHub-Request-Id"] &&
105
- !resp.headers["X-GitHub-Request-Id"].empty?
133
+ ((resp.headers["X-GitHub-Request-Id"] && !resp.headers["X-GitHub-Request-Id"]&.empty?) || false)
106
134
  rescue StandardError
107
135
  false
108
136
  end
109
137
 
110
- def initialize(provider:, repo:, directory: nil, branch: nil, commit: nil,
138
+ sig do
139
+ params(
140
+ provider: String,
141
+ repo: String,
142
+ directory: T.nilable(String),
143
+ directories: T.nilable(T::Array[String]),
144
+ branch: T.nilable(String),
145
+ commit: T.nilable(String),
146
+ hostname: T.nilable(String),
147
+ api_endpoint: T.nilable(String)
148
+ ).void
149
+ end
150
+ def initialize(provider:, repo:, directory: nil, directories: nil, branch: nil, commit: nil,
111
151
  hostname: nil, api_endpoint: nil)
112
152
  if (hostname.nil? ^ api_endpoint.nil?) && (provider != "codecommit")
113
153
  msg = "Both hostname and api_endpoint must be specified if either " \
@@ -119,16 +159,19 @@ module Dependabot
119
159
  @provider = provider
120
160
  @repo = repo
121
161
  @directory = directory
162
+ @directories = directories
122
163
  @branch = branch
123
164
  @commit = commit
124
- @hostname = hostname || default_hostname(provider)
125
- @api_endpoint = api_endpoint || default_api_endpoint(provider)
165
+ @hostname = T.let(hostname || default_hostname(provider), String)
166
+ @api_endpoint = T.let(api_endpoint || default_api_endpoint(provider), T.nilable(String))
126
167
  end
127
168
 
169
+ sig { returns(String) }
128
170
  def url
129
171
  "https://" + hostname + "/" + repo
130
172
  end
131
173
 
174
+ sig { returns(String) }
132
175
  def url_with_directory
133
176
  return url if [nil, ".", "/"].include?(directory)
134
177
 
@@ -149,25 +192,29 @@ module Dependabot
149
192
  end
150
193
  end
151
194
 
195
+ sig { returns(String) }
152
196
  def organization
153
- repo.split("/").first
197
+ T.must(repo.split("/").first)
154
198
  end
155
199
 
200
+ sig { returns(String) }
156
201
  def project
157
202
  raise "Project is an Azure DevOps concept only" unless provider == "azure"
158
203
 
159
204
  parts = repo.split("/_git/")
160
- return parts.first.split("/").last if parts.first.split("/").count == 2
205
+ return T.must(T.must(parts.first).split("/").last) if parts.first&.split("/")&.count == 2
161
206
 
162
- parts.last
207
+ T.must(parts.last)
163
208
  end
164
209
 
210
+ sig { returns(String) }
165
211
  def unscoped_repo
166
- repo.split("/").last
212
+ T.must(repo.split("/").last)
167
213
  end
168
214
 
169
215
  private
170
216
 
217
+ sig { params(provider: String).returns(String) }
171
218
  def default_hostname(provider)
172
219
  case provider
173
220
  when "github" then "github.com"
@@ -179,6 +226,7 @@ module Dependabot
179
226
  end
180
227
  end
181
228
 
229
+ sig { params(provider: String).returns(T.nilable(String)) }
182
230
  def default_api_endpoint(provider)
183
231
  case provider
184
232
  when "github" then "https://api.github.com/"
@@ -1,9 +1,20 @@
1
- # typed: true
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  module Dependabot
5
7
  module UpdateCheckers
6
8
  module VersionFilters
9
+ extend T::Sig
10
+
11
+ sig do
12
+ params(
13
+ versions_array: T::Array[T.any(Gem::Version, T::Hash[Symbol, String])],
14
+ security_advisories: T::Array[SecurityAdvisory]
15
+ )
16
+ .returns(T::Array[T.any(Gem::Version, T::Hash[Symbol, String])])
17
+ end
7
18
  def self.filter_vulnerable_versions(versions_array, security_advisories)
8
19
  versions_array.reject do |v|
9
20
  security_advisories.any? do |a|