dependabot-common 0.236.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.
@@ -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|
@@ -5,6 +5,7 @@ require "tmpdir"
5
5
  require "set"
6
6
  require "sorbet-runtime"
7
7
  require "dependabot/version"
8
+ require "dependabot/config/file"
8
9
 
9
10
  # TODO: in due course, these "registries" should live in a wrapper gem, not
10
11
  # dependabot-core.
@@ -22,11 +23,13 @@ module Dependabot
22
23
  version_class = @version_classes[package_manager]
23
24
  return version_class if version_class
24
25
 
25
- raise "Unsupported package_manager #{package_manager}"
26
+ raise "Unregistered package_manager #{package_manager}"
26
27
  end
27
28
 
28
29
  sig { params(package_manager: String, version_class: T.class_of(Dependabot::Version)).void }
29
30
  def self.register_version_class(package_manager, version_class)
31
+ validate_package_manager!(package_manager)
32
+
30
33
  @version_classes[package_manager] = version_class
31
34
  end
32
35
 
@@ -37,11 +40,13 @@ module Dependabot
37
40
  requirement_class = @requirement_classes[package_manager]
38
41
  return requirement_class if requirement_class
39
42
 
40
- raise "Unsupported package_manager #{package_manager}"
43
+ raise "Unregistered package_manager #{package_manager}"
41
44
  end
42
45
 
43
46
  sig { params(package_manager: String, requirement_class: T.class_of(Gem::Requirement)).void }
44
47
  def self.register_requirement_class(package_manager, requirement_class)
48
+ validate_package_manager!(package_manager)
49
+
45
50
  @requirement_classes[package_manager] = requirement_class
46
51
  end
47
52
 
@@ -54,7 +59,21 @@ module Dependabot
54
59
 
55
60
  sig { params(package_manager: String).void }
56
61
  def self.register_always_clone(package_manager)
62
+ validate_package_manager!(package_manager)
63
+
57
64
  @cloning_package_managers << package_manager
58
65
  end
66
+
67
+ sig { params(package_manager: String).void }
68
+ def self.validate_package_manager!(package_manager)
69
+ # Official package manager
70
+ return if Config::File::PACKAGE_MANAGER_LOOKUP.invert.key?(package_manager)
71
+
72
+ # Used by specs
73
+ return if package_manager == "dummy"
74
+
75
+ raise "Unsupported package_manager #{package_manager}"
76
+ end
77
+ private_class_method :validate_package_manager!
59
78
  end
60
79
  end
@@ -1,29 +1,53 @@
1
- # typed: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  module Dependabot
5
7
  module Workspace
6
8
  class Base
7
- attr_reader :change_attempts, :path
9
+ extend T::Sig
10
+ extend T::Helpers
11
+ extend T::Generic
12
+
13
+ abstract!
14
+
15
+ sig { returns(T::Array[Dependabot::Workspace::ChangeAttempt]) }
16
+ attr_reader :change_attempts
8
17
 
18
+ sig { returns(T.any(Pathname, String)) }
19
+ attr_reader :path
20
+
21
+ sig { params(path: T.any(Pathname, String)).void }
9
22
  def initialize(path)
10
23
  @path = path
11
- @change_attempts = []
24
+ @change_attempts = T.let([], T::Array[Dependabot::Workspace::ChangeAttempt])
12
25
  end
13
26
 
27
+ sig { returns(T::Boolean) }
14
28
  def changed?
15
29
  changes.any?
16
30
  end
17
31
 
32
+ sig { returns(T::Array[Dependabot::Workspace::ChangeAttempt]) }
18
33
  def changes
19
34
  change_attempts.select(&:success?)
20
35
  end
21
36
 
37
+ sig { returns(T::Array[Dependabot::Workspace::ChangeAttempt]) }
22
38
  def failed_change_attempts
23
39
  change_attempts.select(&:error?)
24
40
  end
25
41
 
26
- def change(memo = nil)
42
+ sig do
43
+ type_parameters(:T)
44
+ .params(
45
+ memo: T.nilable(String),
46
+ _blk: T.proc.params(arg0: T.any(Pathname, String)).returns(T.type_parameter(:T))
47
+ )
48
+ .returns(T.type_parameter(:T))
49
+ end
50
+ def change(memo = nil, &_blk)
27
51
  Dir.chdir(path) { yield(path) }
28
52
  rescue StandardError => e
29
53
  capture_failed_change_attempt(memo, e)
@@ -31,17 +55,28 @@ module Dependabot
31
55
  raise e
32
56
  end
33
57
 
58
+ sig do
59
+ abstract.params(memo: T.nilable(String)).returns(T.nilable(T::Array[Dependabot::Workspace::ChangeAttempt]))
60
+ end
34
61
  def store_change(memo = nil); end
35
62
 
36
- def to_patch
37
- ""
38
- end
63
+ sig { abstract.returns(String) }
64
+ def to_patch; end
39
65
 
66
+ sig { abstract.returns(NilClass) }
40
67
  def reset!; end
41
68
 
42
69
  protected
43
70
 
71
+ sig do
72
+ abstract
73
+ .params(memo: T.nilable(String), error: T.nilable(StandardError))
74
+ .returns(T.nilable(T::Array[Dependabot::Workspace::ChangeAttempt]))
75
+ end
44
76
  def capture_failed_change_attempt(memo = nil, error = nil); end
77
+
78
+ sig { abstract.returns(String) }
79
+ def clean; end
45
80
  end
46
81
  end
47
82
  end