dependabot-common 0.237.0 → 0.238.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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: