dependabot-common 0.236.0 → 0.237.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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