dependabot-common 0.237.0 → 0.238.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e29557fff3ec856a4ebcb95a2b53dd70f91b5d7f2ea66cd6230e90dbcbbc2dc0
4
- data.tar.gz: f6b2ce2a845422872e58b37681909fb4357bb1ebcac22c1085db113dc2429426
3
+ metadata.gz: 77312fe42bc6241de9c474fa2a1bab0dd3955bff4c2846bc057e52684f1b48bf
4
+ data.tar.gz: 72fbe948d041e0d1e2fd717fa98a2358e32413408ed2d147d6cbd58107d8d5ba
5
5
  SHA512:
6
- metadata.gz: 367d715ab3cb1b0c2c498555419e2212e9986c8e824128ce56695c00cdc190371fa0b7b9115258146ff0573f2e4a8461e1c66651b9d84bb2fdb7b0fe00b901ff
7
- data.tar.gz: 4e5b3e040476f17cb7cf268ccc0cae90f17178da6a5954b14ca2a2ce280cf0f067b1ce8b88ecc8da5c04ae919cc34b670258b1555f9bfb707fb061cc8439acb3
6
+ metadata.gz: f108dbeb6f04a42d5b5b4e30baecad3d376bd82615e067b3ca2696bbdcefd875c55c4dd18da926b597641691e47edb034244d4d170030cbf8055d3c29e9de3cf
7
+ data.tar.gz: e14a429b7dadcd27eccd9e049fb6ebd4a405e8e86229eb683c47d1cb72e911a0da4ae3cab50a3a883000f49ef19e33716cfc2a1fd17181d89858da9644ff80e2
@@ -1,6 +1,7 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "aws-sdk-codecommit"
4
5
  require "dependabot/shared_helpers"
5
6
 
6
7
  module Dependabot
@@ -1,14 +1,23 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
4
5
  require "dependabot/version"
5
6
 
6
7
  module Dependabot
7
8
  class Dependency
8
- @production_checks = {}
9
- @display_name_builders = {}
10
- @name_normalisers = {}
9
+ extend T::Sig
11
10
 
11
+ @production_checks = T.let(
12
+ {},
13
+ T::Hash[String, T.proc.params(arg0: T::Array[T.untyped]).returns(T::Boolean)]
14
+ )
15
+ @display_name_builders = T.let({}, T::Hash[String, T.proc.params(arg0: String).returns(String)])
16
+ @name_normalisers = T.let({}, T::Hash[String, T.proc.params(arg0: String).returns(String)])
17
+
18
+ sig do
19
+ params(package_manager: String).returns(T.proc.params(arg0: T::Array[T.untyped]).returns(T::Boolean))
20
+ end
12
21
  def self.production_check_for_package_manager(package_manager)
13
22
  production_check = @production_checks[package_manager]
14
23
  return production_check if production_check
@@ -16,62 +25,128 @@ module Dependabot
16
25
  raise "Unsupported package_manager #{package_manager}"
17
26
  end
18
27
 
28
+ sig do
29
+ params(
30
+ package_manager: String,
31
+ production_check: T.proc.params(arg0: T::Array[T.untyped]).returns(T::Boolean)
32
+ )
33
+ .returns(T.proc.params(arg0: T::Array[T.untyped]).returns(T::Boolean))
34
+ end
19
35
  def self.register_production_check(package_manager, production_check)
20
36
  @production_checks[package_manager] = production_check
21
37
  end
22
38
 
39
+ sig { params(package_manager: String).returns(T.nilable(T.proc.params(arg0: String).returns(String))) }
23
40
  def self.display_name_builder_for_package_manager(package_manager)
24
41
  @display_name_builders[package_manager]
25
42
  end
26
43
 
44
+ sig { params(package_manager: String, name_builder: T.proc.params(arg0: String).returns(String)).void }
27
45
  def self.register_display_name_builder(package_manager, name_builder)
28
46
  @display_name_builders[package_manager] = name_builder
29
47
  end
30
48
 
49
+ sig { params(package_manager: String).returns(T.nilable(T.proc.params(arg0: String).returns(String))) }
31
50
  def self.name_normaliser_for_package_manager(package_manager)
32
51
  @name_normalisers[package_manager] || ->(name) { name }
33
52
  end
34
53
 
54
+ sig do
55
+ params(
56
+ package_manager: String,
57
+ name_builder: T.proc.params(arg0: String).returns(String)
58
+ ).void
59
+ end
35
60
  def self.register_name_normaliser(package_manager, name_builder)
36
61
  @name_normalisers[package_manager] = name_builder
37
62
  end
38
63
 
39
- attr_reader :name, :version, :requirements, :package_manager,
40
- :previous_version, :previous_requirements,
41
- :subdependency_metadata, :metadata
64
+ sig { returns(String) }
65
+ attr_reader :name
66
+
67
+ sig { returns(T.nilable(String)) }
68
+ attr_reader :version
69
+
70
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
71
+ attr_reader :requirements
72
+
73
+ sig { returns(String) }
74
+ attr_reader :package_manager
75
+
76
+ sig { returns(T.nilable(String)) }
77
+ attr_reader :previous_version
42
78
 
79
+ sig { returns(T.nilable(T::Array[T::Hash[Symbol, T.untyped]])) }
80
+ attr_reader :previous_requirements
81
+
82
+ sig { returns(T.nilable(T::Array[T::Hash[Symbol, T.untyped]])) }
83
+ attr_reader :subdependency_metadata
84
+
85
+ sig { returns(T::Hash[Symbol, T.untyped]) }
86
+ attr_reader :metadata
87
+
88
+ sig do
89
+ params(
90
+ name: String,
91
+ requirements: T::Array[T::Hash[String, String]],
92
+ package_manager: String,
93
+ # TODO: Make version a Dependabot::Version everywhere
94
+ version: T.nilable(T.any(String, Dependabot::Version)),
95
+ previous_version: T.nilable(String),
96
+ previous_requirements: T.nilable(T::Array[T::Hash[String, String]]),
97
+ subdependency_metadata: T.nilable(T::Array[T::Hash[String, String]]),
98
+ removed: T::Boolean,
99
+ metadata: T.nilable(T::Hash[String, String])
100
+ ).void
101
+ end
43
102
  def initialize(name:, requirements:, package_manager:, version: nil,
44
103
  previous_version: nil, previous_requirements: nil,
45
104
  subdependency_metadata: [], removed: false, metadata: {})
46
105
  @name = name
47
- @version = version
48
- @requirements = requirements.map { |req| symbolize_keys(req) }
106
+ @version = T.let(
107
+ case version
108
+ when Dependabot::Version then version.to_s
109
+ when String then version
110
+ end,
111
+ T.nilable(String)
112
+ )
113
+ @requirements = T.let(requirements.map { |req| symbolize_keys(req) }, T::Array[T::Hash[Symbol, String]])
49
114
  @previous_version = previous_version
50
- @previous_requirements =
51
- previous_requirements&.map { |req| symbolize_keys(req) }
115
+ @previous_requirements = T.let(
116
+ previous_requirements&.map { |req| symbolize_keys(req) },
117
+ T.nilable(T::Array[T::Hash[Symbol, T.untyped]])
118
+ )
52
119
  @package_manager = package_manager
53
120
  unless top_level? || subdependency_metadata == []
54
- @subdependency_metadata = subdependency_metadata
55
- &.map { |h| symbolize_keys(h) }
121
+ @subdependency_metadata = T.let(
122
+ subdependency_metadata&.map { |h| symbolize_keys(h) },
123
+ T.nilable(T::Array[T::Hash[Symbol, T.untyped]])
124
+ )
56
125
  end
57
126
  @removed = removed
58
- @metadata = symbolize_keys(metadata || {})
127
+ @metadata = T.let(symbolize_keys(metadata || {}), T::Hash[Symbol, T.untyped])
59
128
 
60
129
  check_values
61
130
  end
62
131
 
132
+ sig { returns(T::Boolean) }
63
133
  def top_level?
64
134
  requirements.any?
65
135
  end
66
136
 
137
+ sig { returns(T::Boolean) }
67
138
  def removed?
68
139
  @removed
69
140
  end
70
141
 
142
+ sig { returns(T.nilable(Dependabot::Version)) }
71
143
  def numeric_version
72
- @numeric_version ||= version_class.new(version) if version && version_class.correct?(version)
144
+ return unless version && version_class.correct?(version)
145
+
146
+ @numeric_version ||= T.let(version_class.new(version), T.nilable(Dependabot::Version))
73
147
  end
74
148
 
149
+ sig { returns(T::Hash[String, T.untyped]) }
75
150
  def to_h
76
151
  {
77
152
  "name" => name,
@@ -85,10 +160,12 @@ module Dependabot
85
160
  }.compact
86
161
  end
87
162
 
163
+ sig { returns(T::Boolean) }
88
164
  def appears_in_lockfile?
89
- previous_version || (version && previous_requirements.nil?)
165
+ !!(previous_version || (version && previous_requirements.nil?))
90
166
  end
91
167
 
168
+ sig { returns(T::Boolean) }
92
169
  def production?
93
170
  return subdependency_production_check unless top_level?
94
171
 
@@ -99,10 +176,12 @@ module Dependabot
99
176
  .call(groups)
100
177
  end
101
178
 
179
+ sig { returns(T::Boolean) }
102
180
  def subdependency_production_check
103
181
  !subdependency_metadata&.all? { |h| h[:production] == false }
104
182
  end
105
183
 
184
+ sig { returns(String) }
106
185
  def display_name
107
186
  display_name_builder =
108
187
  self.class.display_name_builder_for_package_manager(package_manager)
@@ -111,6 +190,7 @@ module Dependabot
111
190
  display_name_builder.call(name)
112
191
  end
113
192
 
193
+ sig { returns(T.nilable(String)) }
114
194
  def humanized_previous_version
115
195
  # If we don't have a previous version, we *may* still be able to figure
116
196
  # one out if a ref was provided and has been changed (in which case the
@@ -119,48 +199,52 @@ module Dependabot
119
199
  return ref_changed? ? previous_ref : nil
120
200
  end
121
201
 
122
- if previous_version.match?(/^[0-9a-f]{40}/)
202
+ if T.must(previous_version).match?(/^[0-9a-f]{40}/)
123
203
  return previous_ref if ref_changed? && previous_ref
124
204
 
125
- "`#{previous_version[0..6]}`"
205
+ "`#{T.must(previous_version)[0..6]}`"
126
206
  elsif version == previous_version &&
127
207
  package_manager == "docker"
128
- digest = docker_digest_from_reqs(previous_requirements)
129
- "`#{digest.split(':').last[0..6]}`"
208
+ digest = docker_digest_from_reqs(T.must(previous_requirements))
209
+ "`#{T.must(T.must(digest).split(':').last)[0..6]}`"
130
210
  else
131
211
  previous_version
132
212
  end
133
213
  end
134
214
 
215
+ sig { returns(T.nilable(String)) }
135
216
  def humanized_version
136
217
  return if removed?
137
218
 
138
- if version.match?(/^[0-9a-f]{40}/)
219
+ if T.must(version).match?(/^[0-9a-f]{40}/)
139
220
  return new_ref if ref_changed? && new_ref
140
221
 
141
- "`#{version[0..6]}`"
222
+ "`#{T.must(version)[0..6]}`"
142
223
  elsif version == previous_version &&
143
224
  package_manager == "docker"
144
225
  digest = docker_digest_from_reqs(requirements)
145
- "`#{digest.split(':').last[0..6]}`"
226
+ "`#{T.must(T.must(digest).split(':').last)[0..6]}`"
146
227
  else
147
228
  version
148
229
  end
149
230
  end
150
231
 
232
+ sig { params(requirements: T::Array[T::Hash[Symbol, T.untyped]]).returns(T.nilable(String)) }
151
233
  def docker_digest_from_reqs(requirements)
152
234
  requirements
153
235
  .filter_map { |r| r.dig(:source, "digest") || r.dig(:source, :digest) }
154
236
  .first
155
237
  end
156
238
 
239
+ sig { returns(T.nilable(String)) }
157
240
  def previous_ref
158
- previous_refs = previous_requirements.filter_map do |r|
241
+ previous_refs = T.must(previous_requirements).filter_map do |r|
159
242
  r.dig(:source, "ref") || r.dig(:source, :ref)
160
243
  end.uniq
161
244
  previous_refs.first if previous_refs.count == 1
162
245
  end
163
246
 
247
+ sig { returns(T.nilable(String)) }
164
248
  def new_ref
165
249
  new_refs = requirements.filter_map do |r|
166
250
  r.dig(:source, "ref") || r.dig(:source, :ref)
@@ -168,12 +252,14 @@ module Dependabot
168
252
  new_refs.first if new_refs.count == 1
169
253
  end
170
254
 
255
+ sig { returns(T::Boolean) }
171
256
  def ref_changed?
172
257
  previous_ref != new_ref
173
258
  end
174
259
 
175
260
  # Returns all detected versions of the dependency. Only ecosystems that
176
261
  # support this feature will return more than the current version.
262
+ sig { returns(T::Array[T.nilable(String)]) }
177
263
  def all_versions
178
264
  all_versions = metadata[:all_versions]
179
265
  return [version].compact unless all_versions
@@ -184,34 +270,52 @@ module Dependabot
184
270
  # This dependency is being indirectly updated by an update to another
185
271
  # dependency. We don't need to try and update it ourselves but want to
186
272
  # surface it to the user in the PR.
273
+ sig { returns(T.nilable(T::Boolean)) }
187
274
  def informational_only?
188
275
  metadata[:information_only]
189
276
  end
190
277
 
278
+ sig { params(other: T.anything).returns(T::Boolean) }
191
279
  def ==(other)
192
- other.instance_of?(self.class) && to_h == other.to_h
280
+ case other
281
+ when Dependency
282
+ to_h == other.to_h
283
+ else
284
+ false
285
+ end
193
286
  end
194
287
 
288
+ sig { returns(Integer) }
195
289
  def hash
196
290
  to_h.hash
197
291
  end
198
292
 
293
+ sig { params(other: T.anything).returns(T::Boolean) }
199
294
  def eql?(other)
200
295
  self == other
201
296
  end
202
297
 
298
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
203
299
  def specific_requirements
204
300
  requirements.select { |r| requirement_class.new(r[:requirement]).specific? }
205
301
  end
206
302
 
303
+ sig { returns(T.class_of(Gem::Requirement)) }
207
304
  def requirement_class
208
305
  Utils.requirement_class_for_package_manager(package_manager)
209
306
  end
210
307
 
308
+ sig { returns(T.class_of(Dependabot::Version)) }
211
309
  def version_class
212
310
  Utils.version_class_for_package_manager(package_manager)
213
311
  end
214
312
 
313
+ sig do
314
+ params(
315
+ allowed_types: T.nilable(T::Array[String])
316
+ )
317
+ .returns(T.nilable(T::Hash[T.any(String, Symbol), T.untyped]))
318
+ end
215
319
  def source_details(allowed_types: nil)
216
320
  sources = all_sources.uniq.compact
217
321
  sources.select! { |source| allowed_types.include?(source[:type].to_s) } if allowed_types
@@ -225,6 +329,7 @@ module Dependabot
225
329
  sources.first
226
330
  end
227
331
 
332
+ sig { returns(T.nilable(String)) }
228
333
  def source_type
229
334
  details = source_details
230
335
  return "default" if details.nil?
@@ -232,11 +337,12 @@ module Dependabot
232
337
  details[:type] || details.fetch("type")
233
338
  end
234
339
 
340
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
235
341
  def all_sources
236
342
  if top_level?
237
343
  requirements.map { |requirement| requirement.fetch(:source) }
238
344
  elsif subdependency_metadata
239
- subdependency_metadata.filter_map { |data| data[:source] }
345
+ T.must(subdependency_metadata).filter_map { |data| data[:source] }
240
346
  else
241
347
  []
242
348
  end
@@ -244,6 +350,7 @@ module Dependabot
244
350
 
245
351
  private
246
352
 
353
+ sig { void }
247
354
  def check_values
248
355
  raise ArgumentError, "blank strings must not be provided as versions" if [version, previous_version].any?("")
249
356
 
@@ -251,6 +358,7 @@ module Dependabot
251
358
  check_subdependency_metadata
252
359
  end
253
360
 
361
+ sig { void }
254
362
  def check_requirement_fields
255
363
  requirement_fields = [requirements, previous_requirements].compact
256
364
  unless requirement_fields.all?(Array) &&
@@ -273,15 +381,17 @@ module Dependabot
273
381
  raise ArgumentError, "blank strings must not be provided as requirements"
274
382
  end
275
383
 
384
+ sig { void }
276
385
  def check_subdependency_metadata
277
386
  return unless subdependency_metadata
278
387
 
279
388
  unless subdependency_metadata.is_a?(Array) &&
280
- subdependency_metadata.all?(Hash)
389
+ T.must(subdependency_metadata).all?(Hash)
281
390
  raise ArgumentError, "subdependency_metadata must be an array of hashes"
282
391
  end
283
392
  end
284
393
 
394
+ sig { params(hash: T::Hash[String, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
285
395
  def symbolize_keys(hash)
286
396
  hash.keys.to_h { |k| [k.to_sym, hash[k]] }
287
397
  end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "pathname"
@@ -5,6 +5,215 @@ require "sorbet-runtime"
5
5
  require "dependabot/utils"
6
6
 
7
7
  module Dependabot
8
+ # rubocop:disable Metrics/MethodLength
9
+ def self.fetcher_error_details(error)
10
+ case error
11
+ when Dependabot::ToolVersionNotSupported
12
+ {
13
+ "error-type": "tool_version_not_supported",
14
+ "error-detail": {
15
+ "tool-name": error.tool_name,
16
+ "detected-version": error.detected_version,
17
+ "supported-versions": error.supported_versions
18
+ }
19
+ }
20
+ when Dependabot::BranchNotFound
21
+ {
22
+ "error-type": "branch_not_found",
23
+ "error-detail": { "branch-name": error.branch_name }
24
+ }
25
+ when Dependabot::DirectoryNotFound
26
+ {
27
+ "error-type": "directory_not_found",
28
+ "error-detail": { "directory-name": error.directory_name }
29
+ }
30
+ when Dependabot::RepoNotFound
31
+ # This happens if the repo gets removed after a job gets kicked off.
32
+ # This also happens when a configured personal access token is not authz'd to fetch files from the job repo.
33
+ {
34
+ "error-type": "job_repo_not_found",
35
+ "error-detail": { message: error.message }
36
+ }
37
+ when Dependabot::DependencyFileNotParseable
38
+ {
39
+ "error-type": "dependency_file_not_parseable",
40
+ "error-detail": {
41
+ message: error.message,
42
+ "file-path": error.file_path
43
+ }
44
+ }
45
+ when Dependabot::DependencyFileNotFound
46
+ {
47
+ "error-type": "dependency_file_not_found",
48
+ "error-detail": { "file-path": error.file_path }
49
+ }
50
+ when Dependabot::OutOfDisk
51
+ {
52
+ "error-type": "out_of_disk",
53
+ "error-detail": {}
54
+ }
55
+ when Dependabot::PathDependenciesNotReachable
56
+ {
57
+ "error-type": "path_dependencies_not_reachable",
58
+ "error-detail": { dependencies: error.dependencies }
59
+ }
60
+ when Octokit::Unauthorized
61
+ { "error-type": "octokit_unauthorized" }
62
+ when Octokit::ServerError
63
+ # If we get a 500 from GitHub there's very little we can do about it,
64
+ # and responsibility for fixing it is on them, not us. As a result we
65
+ # quietly log these as errors
66
+ { "error-type": "server_error" }
67
+ when *Octokit::RATE_LIMITED_ERRORS
68
+ # If we get a rate-limited error we let dependabot-api handle the
69
+ # retry by re-enqueing the update job after the reset
70
+ {
71
+ "error-type": "octokit_rate_limited",
72
+ "error-detail": {
73
+ "rate-limit-reset": error.response_headers["X-RateLimit-Reset"]
74
+ }
75
+ }
76
+ end
77
+ end
78
+
79
+ def self.parser_error_details(error)
80
+ case error
81
+ when Dependabot::DependencyFileNotEvaluatable
82
+ {
83
+ "error-type": "dependency_file_not_evaluatable",
84
+ "error-detail": { message: error.message }
85
+ }
86
+ when Dependabot::DependencyFileNotResolvable
87
+ {
88
+ "error-type": "dependency_file_not_resolvable",
89
+ "error-detail": { message: error.message }
90
+ }
91
+ when Dependabot::BranchNotFound
92
+ {
93
+ "error-type": "branch_not_found",
94
+ "error-detail": { "branch-name": error.branch_name }
95
+ }
96
+ when Dependabot::DependencyFileNotParseable
97
+ {
98
+ "error-type": "dependency_file_not_parseable",
99
+ "error-detail": {
100
+ message: error.message,
101
+ "file-path": error.file_path
102
+ }
103
+ }
104
+ when Dependabot::DependencyFileNotFound
105
+ {
106
+ "error-type": "dependency_file_not_found",
107
+ "error-detail": { "file-path": error.file_path }
108
+ }
109
+ when Dependabot::PathDependenciesNotReachable
110
+ {
111
+ "error-type": "path_dependencies_not_reachable",
112
+ "error-detail": { dependencies: error.dependencies }
113
+ }
114
+ when Dependabot::PrivateSourceAuthenticationFailure
115
+ {
116
+ "error-type": "private_source_authentication_failure",
117
+ "error-detail": { source: error.source }
118
+ }
119
+ when Dependabot::GitDependenciesNotReachable
120
+ {
121
+ "error-type": "git_dependencies_not_reachable",
122
+ "error-detail": { "dependency-urls": error.dependency_urls }
123
+ }
124
+ when Dependabot::NotImplemented
125
+ {
126
+ "error-type": "not_implemented",
127
+ "error-detail": {
128
+ message: error.message
129
+ }
130
+ }
131
+ when Octokit::ServerError
132
+ # If we get a 500 from GitHub there's very little we can do about it,
133
+ # and responsibility for fixing it is on them, not us. As a result we
134
+ # quietly log these as errors
135
+ { "error-type": "server_error" }
136
+ end
137
+ end
138
+
139
+ def self.updater_error_details(error)
140
+ case error
141
+ when Dependabot::DependencyFileNotResolvable
142
+ {
143
+ "error-type": "dependency_file_not_resolvable",
144
+ "error-detail": { message: error.message }
145
+ }
146
+ when Dependabot::DependencyFileNotEvaluatable
147
+ {
148
+ "error-type": "dependency_file_not_evaluatable",
149
+ "error-detail": { message: error.message }
150
+ }
151
+ when Dependabot::GitDependenciesNotReachable
152
+ {
153
+ "error-type": "git_dependencies_not_reachable",
154
+ "error-detail": { "dependency-urls": error.dependency_urls }
155
+ }
156
+ when Dependabot::MisconfiguredTooling
157
+ {
158
+ "error-type": "misconfigured_tooling",
159
+ "error-detail": { "tool-name": error.tool_name, message: error.tool_message }
160
+ }
161
+ when Dependabot::GitDependencyReferenceNotFound
162
+ {
163
+ "error-type": "git_dependency_reference_not_found",
164
+ "error-detail": { dependency: error.dependency }
165
+ }
166
+ when Dependabot::PrivateSourceAuthenticationFailure
167
+ {
168
+ "error-type": "private_source_authentication_failure",
169
+ "error-detail": { source: error.source }
170
+ }
171
+ when Dependabot::PrivateSourceTimedOut
172
+ {
173
+ "error-type": "private_source_timed_out",
174
+ "error-detail": { source: error.source }
175
+ }
176
+ when Dependabot::PrivateSourceCertificateFailure
177
+ {
178
+ "error-type": "private_source_certificate_failure",
179
+ "error-detail": { source: error.source }
180
+ }
181
+ when Dependabot::MissingEnvironmentVariable
182
+ {
183
+ "error-type": "missing_environment_variable",
184
+ "error-detail": {
185
+ "environment-variable": error.environment_variable
186
+ }
187
+ }
188
+ when Dependabot::GoModulePathMismatch
189
+ {
190
+ "error-type": "go_module_path_mismatch",
191
+ "error-detail": {
192
+ "declared-path": error.declared_path,
193
+ "discovered-path": error.discovered_path,
194
+ "go-mod": error.go_mod
195
+ }
196
+ }
197
+ when Dependabot::NotImplemented
198
+ {
199
+ "error-type": "not_implemented",
200
+ "error-detail": {
201
+ message: error.message
202
+ }
203
+ }
204
+ when *Octokit::RATE_LIMITED_ERRORS
205
+ # If we get a rate-limited error we let dependabot-api handle the
206
+ # retry by re-enqueing the update job after the reset
207
+ {
208
+ "error-type": "octokit_rate_limited",
209
+ "error-detail": {
210
+ "rate-limit-reset": error.response_headers["X-RateLimit-Reset"]
211
+ }
212
+ }
213
+ end
214
+ end
215
+ # rubocop:enable Metrics/MethodLength
216
+
8
217
  class DependabotError < StandardError
9
218
  extend T::Sig
10
219
 
@@ -109,6 +318,31 @@ module Dependabot
109
318
  # File level errors #
110
319
  #####################
111
320
 
321
+ class MisconfiguredTooling < DependabotError
322
+ extend T::Sig
323
+
324
+ sig { returns(String) }
325
+ attr_reader :tool_name
326
+
327
+ sig { returns(String) }
328
+ attr_reader :tool_message
329
+
330
+ sig do
331
+ params(
332
+ tool_name: String,
333
+ tool_message: String
334
+ ).void
335
+ end
336
+ def initialize(tool_name, tool_message)
337
+ @tool_name = tool_name
338
+ @tool_message = tool_message
339
+
340
+ msg = "Dependabot detected that #{tool_name} is misconfigured in this repository. " \
341
+ "Running `#{tool_name.downcase}` results in the following error: #{tool_message}"
342
+ super(msg)
343
+ end
344
+ end
345
+
112
346
  class ToolVersionNotSupported < DependabotError
113
347
  extend T::Sig
114
348
 
@@ -73,7 +73,7 @@ module Dependabot
73
73
 
74
74
  sig { params(file: Dependabot::DependencyFile, dependency: Dependabot::Dependency).returns(T::Boolean) }
75
75
  def requirement_changed?(file, dependency)
76
- changed_requirements = dependency.requirements - dependency.previous_requirements
76
+ changed_requirements = dependency.requirements - T.must(dependency.previous_requirements)
77
77
 
78
78
  changed_requirements.any? { |f| f[:file] == file.name }
79
79
  end
@@ -153,6 +153,12 @@ module Dependabot
153
153
  @local_tag_for_pinned_sha = most_specific_version_tag_for_sha(ref) if pinned_ref_looks_like_commit_sha?
154
154
  end
155
155
 
156
+ def version_for_pinned_sha
157
+ return unless local_tag_for_pinned_sha && version_class.correct?(local_tag_for_pinned_sha)
158
+
159
+ version_class.new(local_tag_for_pinned_sha)
160
+ end
161
+
156
162
  def git_repo_reachable?
157
163
  local_upload_pack
158
164
  true
@@ -1,49 +1,73 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
5
5
  require "open3"
6
+ require "sorbet-runtime"
7
+
6
8
  require "dependabot/errors"
9
+ require "dependabot/git_ref"
7
10
 
8
11
  module Dependabot
9
12
  class GitMetadataFetcher
13
+ extend T::Sig
14
+
10
15
  KNOWN_HOSTS = /github\.com|bitbucket\.org|gitlab.com/i
11
16
 
17
+ sig do
18
+ params(
19
+ url: String,
20
+ credentials: T::Array[T::Hash[String, String]]
21
+ )
22
+ .void
23
+ end
12
24
  def initialize(url:, credentials:)
13
25
  @url = url
14
26
  @credentials = credentials
15
27
  end
16
28
 
29
+ sig { returns(T.nilable(String)) }
17
30
  def upload_pack
18
- @upload_pack ||= fetch_upload_pack_for(url)
31
+ @upload_pack ||= T.let(fetch_upload_pack_for(url), T.nilable(String))
19
32
  rescue Octokit::ClientError
20
33
  raise Dependabot::GitDependenciesNotReachable, [url]
21
34
  end
22
35
 
36
+ sig { returns(T::Array[GitRef]) }
23
37
  def tags
24
38
  return [] unless upload_pack
25
39
 
26
- @tags ||= tags_for_upload_pack.map do |ref|
27
- OpenStruct.new(
28
- name: ref.name,
29
- tag_sha: ref.ref_sha,
30
- commit_sha: ref.commit_sha
31
- )
32
- end
40
+ @tags ||= T.let(
41
+ tags_for_upload_pack.map do |ref|
42
+ GitRef.new(
43
+ name: ref.name,
44
+ tag_sha: ref.ref_sha,
45
+ commit_sha: ref.commit_sha
46
+ )
47
+ end,
48
+ T.nilable(T::Array[GitRef])
49
+ )
33
50
  end
34
51
 
52
+ sig { returns(T::Array[GitRef]) }
35
53
  def tags_for_upload_pack
36
- @tags_for_upload_pack ||= refs_for_upload_pack.select { |ref| ref.ref_type == :tag }
54
+ @tags_for_upload_pack ||= T.let(
55
+ refs_for_upload_pack.select { |ref| ref.ref_type == RefType::Tag },
56
+ T.nilable(T::Array[GitRef])
57
+ )
37
58
  end
38
59
 
60
+ sig { returns(T::Array[GitRef]) }
39
61
  def refs_for_upload_pack
40
- @refs_for_upload_pack ||= parse_refs_for_upload_pack
62
+ @refs_for_upload_pack ||= T.let(parse_refs_for_upload_pack, T.nilable(T::Array[GitRef]))
41
63
  end
42
64
 
65
+ sig { returns(T::Array[String]) }
43
66
  def ref_names
44
67
  refs_for_upload_pack.map(&:name)
45
68
  end
46
69
 
70
+ sig { params(ref: String).returns(T.nilable(String)) }
47
71
  def head_commit_for_ref(ref)
48
72
  if ref == "HEAD"
49
73
  # Remove the opening clause of the upload pack as this isn't always
@@ -51,8 +75,8 @@ module Dependabot
51
75
  # causes problems for our `sha_for_update_pack_line` logic. The format
52
76
  # of this opening clause is documented at
53
77
  # https://git-scm.com/docs/http-protocol#_smart_server_response
54
- line = upload_pack.gsub(/^[0-9a-f]{4}# service=git-upload-pack/, "")
55
- .lines.find { |l| l.include?(" HEAD") }
78
+ line = T.must(upload_pack).gsub(/^[0-9a-f]{4}# service=git-upload-pack/, "")
79
+ .lines.find { |l| l.include?(" HEAD") }
56
80
  return sha_for_update_pack_line(line) if line
57
81
  end
58
82
 
@@ -61,6 +85,7 @@ module Dependabot
61
85
  &.commit_sha
62
86
  end
63
87
 
88
+ sig { params(ref: String).returns(T.nilable(String)) }
64
89
  def head_commit_for_ref_sha(ref)
65
90
  refs_for_upload_pack
66
91
  .find { |r| r.ref_sha == ref }
@@ -69,8 +94,13 @@ module Dependabot
69
94
 
70
95
  private
71
96
 
72
- attr_reader :url, :credentials
97
+ sig { returns(String) }
98
+ attr_reader :url
99
+
100
+ sig { returns(T::Array[T::Hash[String, String]]) }
101
+ attr_reader :credentials
73
102
 
103
+ sig { params(uri: String).returns(String) }
74
104
  def fetch_upload_pack_for(uri)
75
105
  response = fetch_raw_upload_pack_for(uri)
76
106
  return response.body if response.status == 200
@@ -97,6 +127,7 @@ module Dependabot
97
127
  raise Dependabot::GitDependenciesNotReachable, [uri]
98
128
  end
99
129
 
130
+ sig { params(uri: String).returns(Excon::Response) }
100
131
  def fetch_raw_upload_pack_for(uri)
101
132
  url = service_pack_uri(uri)
102
133
  url = url.rpartition("@").tap { |a| a.first.gsub!("@", "%40") }.join
@@ -107,6 +138,7 @@ module Dependabot
107
138
  )
108
139
  end
109
140
 
141
+ sig { params(uri: String).returns(T.untyped) }
110
142
  def fetch_raw_upload_pack_with_git_for(uri)
111
143
  service_pack_uri = uri
112
144
  service_pack_uri += ".git" unless service_pack_uri.end_with?(".git") || skip_git_suffix(uri)
@@ -129,11 +161,12 @@ module Dependabot
129
161
  end
130
162
  end
131
163
 
164
+ sig { returns(T::Array[GitRef]) }
132
165
  def parse_refs_for_upload_pack
133
166
  peeled_lines = []
134
167
 
135
- result = upload_pack.lines.each_with_object({}) do |line, res|
136
- full_ref_name = line.split.last
168
+ result = T.must(upload_pack).lines.each_with_object({}) do |line, res|
169
+ full_ref_name = T.must(line.split.last)
137
170
  next unless full_ref_name.start_with?("refs/tags", "refs/heads")
138
171
 
139
172
  (peeled_lines << line) && next if line.strip.end_with?("^{}")
@@ -141,10 +174,10 @@ module Dependabot
141
174
  ref_name = full_ref_name.sub(%r{^refs/(tags|heads)/}, "").strip
142
175
  sha = sha_for_update_pack_line(line)
143
176
 
144
- res[ref_name] = OpenStruct.new(
177
+ res[ref_name] = GitRef.new(
145
178
  name: ref_name,
146
179
  ref_sha: sha,
147
- ref_type: full_ref_name.start_with?("refs/tags") ? :tag : :head,
180
+ ref_type: full_ref_name.start_with?("refs/tags") ? RefType::Tag : RefType::Head,
148
181
  commit_sha: sha
149
182
  )
150
183
  end
@@ -162,6 +195,7 @@ module Dependabot
162
195
  result.values
163
196
  end
164
197
 
198
+ sig { params(uri: String).returns(String) }
165
199
  def service_pack_uri(uri)
166
200
  service_pack_uri = uri_with_auth(uri)
167
201
  service_pack_uri = service_pack_uri.gsub(%r{/$}, "")
@@ -169,6 +203,7 @@ module Dependabot
169
203
  service_pack_uri + "/info/refs?service=git-upload-pack"
170
204
  end
171
205
 
206
+ sig { params(uri: String).returns(T::Boolean) }
172
207
  def skip_git_suffix(uri)
173
208
  # TODO: Unlike the other providers (GitHub, GitLab, BitBucket), as of 2023-01-18 Azure DevOps does not support the
174
209
  # ".git" suffix. It will return a 404.
@@ -188,6 +223,7 @@ module Dependabot
188
223
 
189
224
  # Add in username and password if present in credentials.
190
225
  # Credentials are never present for production Dependabot.
226
+ sig { params(uri: String).returns(String) }
191
227
  def uri_with_auth(uri)
192
228
  uri = SharedHelpers.scp_to_standard(uri)
193
229
  uri = URI(uri)
@@ -196,7 +232,7 @@ module Dependabot
196
232
 
197
233
  uri.scheme = "https" if uri.scheme != "http"
198
234
 
199
- if !uri.password && cred&.fetch("username", nil) && cred&.fetch("password", nil)
235
+ if !uri.password && cred && cred.fetch("username", nil) && cred.fetch("password", nil)
200
236
  # URI doesn't have authentication details, but we have credentials
201
237
  uri.user = URI.encode_www_form_component(cred["username"])
202
238
  uri.password = URI.encode_www_form_component(cred["password"])
@@ -205,10 +241,12 @@ module Dependabot
205
241
  uri.to_s
206
242
  end
207
243
 
244
+ sig { params(line: String).returns(String) }
208
245
  def sha_for_update_pack_line(line)
209
- line.split.first.chars.last(40).join
246
+ T.must(line.split.first).chars.last(40).join
210
247
  end
211
248
 
249
+ sig { returns(T::Hash[Symbol, T.untyped]) }
212
250
  def excon_defaults
213
251
  # Some git hosts are slow when returning a large number of tags
214
252
  SharedHelpers.excon_defaults(read_timeout: 20)
@@ -0,0 +1,71 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ class GitRef
8
+ extend T::Sig
9
+
10
+ sig { returns(String) }
11
+ attr_accessor :name
12
+
13
+ sig { returns(String) }
14
+ attr_accessor :commit_sha
15
+
16
+ sig { returns(T.nilable(String)) }
17
+ attr_reader :tag_sha
18
+
19
+ sig { returns(T.nilable(String)) }
20
+ attr_reader :ref_sha
21
+
22
+ sig { returns(T.nilable(RefType)) }
23
+ attr_reader :ref_type
24
+
25
+ sig do
26
+ params(
27
+ name: String,
28
+ commit_sha: String,
29
+ tag_sha: T.nilable(String),
30
+ ref_sha: T.nilable(String),
31
+ ref_type: T.nilable(RefType)
32
+ )
33
+ .void
34
+ end
35
+ def initialize(name:, commit_sha:, tag_sha: nil, ref_sha: nil, ref_type: nil)
36
+ @name = name
37
+ @commit_sha = commit_sha
38
+ @ref_sha = ref_sha
39
+ @tag_sha = tag_sha
40
+ @ref_type = ref_type
41
+ end
42
+
43
+ sig { params(other: BasicObject).returns(T::Boolean) }
44
+ def ==(other)
45
+ case other
46
+ when GitRef
47
+ to_h == other.to_h
48
+ else
49
+ false
50
+ end
51
+ end
52
+
53
+ sig { returns(T::Hash[Symbol, T.nilable(String)]) }
54
+ def to_h
55
+ {
56
+ name: name,
57
+ commit_sha: commit_sha,
58
+ tag_sha: tag_sha,
59
+ ref_sha: ref_sha,
60
+ ref_type: ref_type
61
+ }.compact
62
+ end
63
+ end
64
+
65
+ class RefType < T::Enum
66
+ enums do
67
+ Tag = new
68
+ Head = new
69
+ end
70
+ end
71
+ end
@@ -85,7 +85,7 @@ module Dependabot
85
85
  msg = (msg[0..trunc_length] + tr_msg)
86
86
  end
87
87
  # if we used a custom encoding for calculating length, then we need to force back to UTF-8
88
- msg.force_encoding(Encoding::UTF_8) unless pr_message_encoding.nil?
88
+ msg = msg.encode("utf-8", "binary", invalid: :replace, undef: :replace) unless pr_message_encoding.nil?
89
89
  msg
90
90
  end
91
91
 
@@ -162,7 +162,13 @@ module Dependabot
162
162
 
163
163
  def group_pr_name
164
164
  updates = dependencies.map(&:name).uniq.count
165
- "bump the #{dependency_group.name} group#{pr_name_directory} with #{updates} update#{'s' if updates > 1}"
165
+
166
+ if source&.directories
167
+ "bump the #{dependency_group.name} across #{source.directories.count} directories " \
168
+ "with #{updates} update#{'s' if updates > 1}"
169
+ else
170
+ "bump the #{dependency_group.name} group#{pr_name_directory} with #{updates} update#{'s' if updates > 1}"
171
+ end
166
172
  end
167
173
 
168
174
  def pr_name_prefix
@@ -260,6 +266,8 @@ module Dependabot
260
266
  # rubocop:disable Metrics/PerceivedComplexity
261
267
  # rubocop:disable Metrics/AbcSize
262
268
  def version_commit_message_intro
269
+ return multi_directory_group_intro if dependency_group && source&.directories
270
+
263
271
  return group_intro if dependency_group
264
272
 
265
273
  return multidependency_property_intro if dependencies.count > 1 && updating_a_property?
@@ -346,6 +354,42 @@ module Dependabot
346
354
  msg
347
355
  end
348
356
 
357
+ def multi_directory_group_intro
358
+ msg = ""
359
+
360
+ source.directories.each do |directory|
361
+ dependencies_in_directory = dependencies.select { |dep| dep.metadata[:directory] == directory }
362
+ next unless dependencies_in_directory.any?
363
+
364
+ update_count = dependencies_in_directory.map(&:name).uniq.count
365
+
366
+ msg += "Bumps the #{dependency_group.name} " \
367
+ "with #{update_count} update#{update_count > 1 ? 's' : ''} in the #{directory} directory:"
368
+
369
+ msg += if update_count >= 5
370
+ header = %w(Package From To)
371
+ rows = dependencies_in_directory.map do |dep|
372
+ [
373
+ dependency_link(dep),
374
+ "`#{dep.humanized_previous_version}`",
375
+ "`#{dep.humanized_version}`"
376
+ ]
377
+ end
378
+ "\n\n#{table([header] + rows)}"
379
+ elsif update_count > 1
380
+ dependency_links_in_directory = dependency_links_for_directory(directory)
381
+ " #{dependency_links_in_directory[0..-2].join(', ')} and #{dependency_links_in_directory[-1]}."
382
+ else
383
+ dependency_links_in_directory = dependency_links_for_directory(directory)
384
+ " #{dependency_links_in_directory.first}."
385
+ end
386
+
387
+ msg += "\n"
388
+ end
389
+
390
+ msg
391
+ end
392
+
349
393
  def group_intro
350
394
  update_count = dependencies.map(&:name).uniq.count
351
395
 
@@ -427,6 +471,12 @@ module Dependabot
427
471
  @dependency_links = uniq_deps.map { |dep| dependency_link(dep) }
428
472
  end
429
473
 
474
+ def dependency_links_for_directory(directory)
475
+ dependencies_in_directory = dependencies.select { |dep| dep.metadata[:directory] == directory }
476
+ uniq_deps = dependencies_in_directory.each_with_object({}) { |dep, memo| memo[dep.name] ||= dep }.values
477
+ @dependency_links = uniq_deps.map { |dep| dependency_link(dep) }
478
+ end
479
+
430
480
  def dependency_link(dependency)
431
481
  if source_url(dependency)
432
482
  "[#{dependency.display_name}](#{source_url(dependency)})"
@@ -483,8 +533,8 @@ module Dependabot
483
533
  "| #{row.join(' | ')} |"
484
534
  end
485
535
 
486
- def metadata_cascades
487
- return metadata_cascades_for_dep(dependencies.first) if dependencies.one?
536
+ def metadata_cascades # rubocop:disable Metrics/PerceivedComplexity
537
+ return metadata_cascades_for_dep(dependencies.first) if dependencies.one? && !dependency_group
488
538
 
489
539
  dependencies.map do |dep|
490
540
  msg = if dep.removed?
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
data/lib/dependabot.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Dependabot
5
- VERSION = "0.237.0"
5
+ VERSION = "0.238.0"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.237.0
4
+ version: 0.238.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-21 00:00:00.000000000 Z
11
+ date: 2023-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-codecommit
@@ -154,6 +154,20 @@ dependencies:
154
154
  - - '='
155
155
  - !ruby/object:Gem::Version
156
156
  version: 4.19.0
157
+ - !ruby/object:Gem::Dependency
158
+ name: json
159
+ requirement: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - "<"
162
+ - !ruby/object:Gem::Version
163
+ version: '2.7'
164
+ type: :runtime
165
+ prerelease: false
166
+ version_requirements: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - "<"
169
+ - !ruby/object:Gem::Version
170
+ version: '2.7'
157
171
  - !ruby/object:Gem::Dependency
158
172
  name: nokogiri
159
173
  requirement: !ruby/object:Gem::Requirement
@@ -491,6 +505,7 @@ files:
491
505
  - lib/dependabot/file_updaters/vendor_updater.rb
492
506
  - lib/dependabot/git_commit_checker.rb
493
507
  - lib/dependabot/git_metadata_fetcher.rb
508
+ - lib/dependabot/git_ref.rb
494
509
  - lib/dependabot/logger.rb
495
510
  - lib/dependabot/metadata_finders.rb
496
511
  - lib/dependabot/metadata_finders/README.md
@@ -542,7 +557,7 @@ licenses:
542
557
  - Nonstandard
543
558
  metadata:
544
559
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
545
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.237.0
560
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.238.0
546
561
  post_install_message:
547
562
  rdoc_options: []
548
563
  require_paths: