dependabot-common 0.383.0 → 0.385.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: 1fb1a4d6243812d664a20067d226c2eec111080d39e7337217744a58a9d8f0b6
4
- data.tar.gz: 609737fc8b6040222a744d58ea03f8587fec15136cb835d319c31b798f7ebf32
3
+ metadata.gz: 58033d13e6a5c0bee14915505c98d752b8a6f2e26ffb96a4ba6d95ca66057bc3
4
+ data.tar.gz: 0a6c8eebbffb115434da53fbd171236690f1594a617f16fb4e29b1addfc81308
5
5
  SHA512:
6
- metadata.gz: 5e3042cb52f78e0396e75a3c6707a6706a1e2f4f1e12fc16310079eae0ae08336f688a75a92795cc6a8ba50ba62b9d0b9168b3ca4d5780439c7bccf574ab4179
7
- data.tar.gz: a15d7f181d2d4afd8b5bf3e72f0450827d435f8e61e98f7863bfa0c254e8e2b983bb831901f1c95d27da8abfe791bf673d6c6e7fcc29b8d5e61c0e3c631ea9e6
6
+ metadata.gz: 5defea48ad000e82511496f0cb413945bf6ba2a5070361af5f1fa5e6411a11679a24de9e852c478f48fba3299d3678865e2faf97158c310ee429f6ac368fee5c
7
+ data.tar.gz: fcf737b08310353b51bc3b32a53f24546e8bf30aa58a9a0b9e91e025921728bdcba378da8b68f9dc592b1bec349ff487a5d21a2683ab1ad76fa1d8b748ca066b
@@ -20,46 +20,46 @@ module Dependabot
20
20
  end
21
21
 
22
22
  OutputObserver = T.type_alias do
23
- T.nilable(T.proc.params(data: String).returns(T::Hash[Symbol, T.untyped]))
23
+ T.nilable(T.proc.params(data: String).returns(T::Hash[Symbol, T.anything]))
24
24
  end
25
25
 
26
26
  EnvCmdItem = T.type_alias do
27
27
  T.any(
28
28
  String,
29
- T::Hash[T.any(String, Symbol), T.untyped]
29
+ T::Hash[T.any(String, Symbol), T.anything]
30
30
  )
31
31
  end
32
32
 
33
33
  class ProcessStatus
34
34
  extend T::Sig
35
35
 
36
- sig { params(process_status: Process::Status, custom_exitstatus: T.nilable(Integer)).void }
36
+ sig { params(process_status: T.nilable(Process::Status), custom_exitstatus: T.nilable(Integer)).void }
37
37
  def initialize(process_status, custom_exitstatus = nil)
38
- @process_status = process_status
38
+ @process_status = T.let(process_status, T.nilable(Process::Status))
39
39
  @custom_exitstatus = custom_exitstatus
40
40
  end
41
41
 
42
42
  # Return the exit status, either from the process status or the custom one
43
43
  sig { returns(Integer) }
44
44
  def exitstatus
45
- @custom_exitstatus || @process_status.exitstatus || 0
45
+ @custom_exitstatus || @process_status&.exitstatus || 0
46
46
  end
47
47
 
48
48
  # Determine if the process was successful
49
49
  sig { returns(T::Boolean) }
50
50
  def success?
51
- @custom_exitstatus.nil? ? @process_status.success? || false : @custom_exitstatus.zero?
51
+ @custom_exitstatus.nil? ? @process_status&.success? || false : @custom_exitstatus.zero?
52
52
  end
53
53
 
54
54
  # Return the PID of the process (if available)
55
55
  sig { returns(T.nilable(Integer)) }
56
56
  def pid
57
- @process_status.pid
57
+ @process_status&.pid
58
58
  end
59
59
 
60
60
  sig { returns(T.nilable(Integer)) }
61
61
  def termsig
62
- @process_status.termsig
62
+ @process_status&.termsig
63
63
  end
64
64
 
65
65
  # String representation of the status
@@ -68,7 +68,7 @@ module Dependabot
68
68
  if @custom_exitstatus
69
69
  "pid #{pid || 'unknown'}: exit #{@custom_exitstatus} (custom status)"
70
70
  else
71
- @process_status.to_s
71
+ @process_status&.to_s || "unknown status"
72
72
  end
73
73
  end
74
74
  end
@@ -102,33 +102,68 @@ module Dependabot
102
102
  T::Hash[String, String]
103
103
  )
104
104
 
105
- sig { params(cfg: T.nilable(T::Hash[Symbol, T.untyped])).returns(T::Array[IgnoreCondition]) }
105
+ sig { params(cfg: T.nilable(T::Hash[Symbol, T.anything])).returns(T::Array[IgnoreCondition]) }
106
106
  def ignore_conditions(cfg)
107
- ignores = cfg&.dig(:ignore) || []
108
- ignores.map do |ic|
107
+ array_values(cfg&.dig(:ignore)).map do |raw|
108
+ ic = hash_values(raw)
109
109
  IgnoreCondition.new(
110
- dependency_name: ic[:"dependency-name"],
111
- versions: ic[:versions],
112
- update_types: ic[:"update-types"]
110
+ dependency_name: T.must(string_value(ic[:"dependency-name"])),
111
+ versions: string_array(ic[:versions]),
112
+ update_types: string_array(ic[:"update-types"])
113
113
  )
114
114
  end
115
115
  end
116
116
 
117
117
  sig do
118
- params(cfg: T.nilable(T::Hash[Symbol, T.untyped])).returns(UpdateConfig::CommitMessageOptions)
118
+ params(cfg: T.nilable(T::Hash[Symbol, T.anything])).returns(UpdateConfig::CommitMessageOptions)
119
119
  end
120
120
  def commit_message_options(cfg)
121
- commit_message = cfg&.dig(:"commit-message") || {}
121
+ commit_message = hash_values(cfg&.dig(:"commit-message"))
122
+ prefix = string_value(commit_message[:prefix])
122
123
  UpdateConfig::CommitMessageOptions.new(
123
- prefix: commit_message[:prefix],
124
- prefix_development: commit_message[:"prefix-development"] || commit_message[:prefix],
125
- include: commit_message[:include]
124
+ prefix: prefix,
125
+ prefix_development: string_value(commit_message[:"prefix-development"]) || prefix,
126
+ include: string_value(commit_message[:include])
126
127
  )
127
128
  end
128
129
 
129
- sig { params(cfg: T.nilable(T::Hash[Symbol, T.untyped])).returns(T::Array[String]) }
130
+ sig { params(cfg: T.nilable(T::Hash[Symbol, T.anything])).returns(T::Array[String]) }
130
131
  def exclude_paths(cfg)
131
- Array(cfg&.dig(:"exclude-paths") || [])
132
+ string_array(cfg&.dig(:"exclude-paths")) || []
133
+ end
134
+
135
+ # The methods below narrow the loosely typed, parsed-YAML config values
136
+ # (Symbol-keyed hashes whose values are arbitrary) into the specific
137
+ # types the config objects expect.
138
+
139
+ sig { params(value: T.anything).returns(T.nilable(String)) }
140
+ def string_value(value)
141
+ case value
142
+ when String then value
143
+ end
144
+ end
145
+
146
+ sig { params(value: T.anything).returns(T::Hash[Symbol, T.anything]) }
147
+ def hash_values(value)
148
+ case value
149
+ when Hash then value
150
+ else {}
151
+ end
152
+ end
153
+
154
+ sig { params(value: T.anything).returns(T::Array[T.anything]) }
155
+ def array_values(value)
156
+ case value
157
+ when Array then value
158
+ else []
159
+ end
160
+ end
161
+
162
+ sig { params(value: T.anything).returns(T.nilable(T::Array[String])) }
163
+ def string_array(value)
164
+ case value
165
+ when Array then value.map(&:to_s)
166
+ end
132
167
  end
133
168
  end
134
169
  end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -23,7 +23,7 @@ module Dependabot
23
23
 
24
24
  # rubocop:disable Metrics/MethodLength
25
25
  # rubocop:disable Metrics/CyclomaticComplexity
26
- sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
26
+ sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.anything])) }
27
27
  def self.fetcher_error_details(error)
28
28
  case error
29
29
  when Dependabot::ToolVersionNotSupported
@@ -142,7 +142,7 @@ module Dependabot
142
142
  end
143
143
  # rubocop:enable Metrics/CyclomaticComplexity
144
144
 
145
- sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
145
+ sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.anything])) }
146
146
  def self.parser_error_details(error)
147
147
  case error
148
148
  when Dependabot::ToolFeatureNotSupported
@@ -226,7 +226,7 @@ module Dependabot
226
226
  # rubocop:disable Lint/RedundantCopDisableDirective
227
227
  # rubocop:disable Metrics/CyclomaticComplexity
228
228
  # rubocop:disable Metrics/AbcSize
229
- sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
229
+ sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.anything])) }
230
230
  def self.updater_error_details(error)
231
231
  case error
232
232
  when Dependabot::ToolFeatureNotSupported
@@ -416,7 +416,7 @@ module Dependabot
416
416
 
417
417
  interface!
418
418
 
419
- sig { abstract.returns(T::Hash[Symbol, T.untyped]) }
419
+ sig { abstract.returns(T::Hash[Symbol, T.anything]) }
420
420
  def sentry_context; end
421
421
  end
422
422
 
@@ -484,7 +484,7 @@ module Dependabot
484
484
  super(message || error_type)
485
485
  end
486
486
 
487
- sig { params(hash: T.nilable(T::Hash[Symbol, T.untyped])).returns(T::Hash[Symbol, T.untyped]) }
487
+ sig { params(hash: T.nilable(T::Hash[Symbol, T.anything])).returns(T::Hash[Symbol, T.anything]) }
488
488
  def detail(hash = nil)
489
489
  {
490
490
  "error-type": error_type,
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -7,21 +7,21 @@ module Dependabot
7
7
  module Experiments
8
8
  extend T::Sig
9
9
 
10
- @experiments = T.let({}, T::Hash[T.any(String, Symbol), T.untyped])
10
+ @experiments = T.let({}, T::Hash[T.any(String, Symbol), T.anything])
11
11
 
12
- sig { returns(T::Hash[T.any(String, Symbol), T.untyped]) }
12
+ sig { returns(T::Hash[T.any(String, Symbol), T.anything]) }
13
13
  def self.reset!
14
14
  @experiments = {}
15
15
  end
16
16
 
17
- sig { params(name: T.any(String, Symbol), value: T.untyped).void }
17
+ sig { params(name: T.any(String, Symbol), value: T.anything).void }
18
18
  def self.register(name, value)
19
19
  @experiments[name.to_sym] = value
20
20
  end
21
21
 
22
22
  sig { params(name: T.any(String, Symbol)).returns(T::Boolean) }
23
23
  def self.enabled?(name)
24
- !!@experiments[name.to_sym]
24
+ @experiments[name.to_sym] ? true : false
25
25
  end
26
26
  end
27
27
  end
@@ -25,7 +25,7 @@ module Dependabot
25
25
  sig { returns(T.nilable(Dependabot::Source)) }
26
26
  attr_reader :source
27
27
 
28
- sig { returns(T::Hash[Symbol, T.untyped]) }
28
+ sig { returns(T::Hash[Symbol, T.anything]) }
29
29
  attr_reader :options
30
30
 
31
31
  sig { returns(T::Boolean) }
@@ -40,7 +40,7 @@ module Dependabot
40
40
  repo_contents_path: T.nilable(String),
41
41
  credentials: T::Array[Dependabot::Credential],
42
42
  reject_external_code: T::Boolean,
43
- options: T::Hash[Symbol, T.untyped]
43
+ options: T::Hash[Symbol, T.anything]
44
44
  )
45
45
  .void
46
46
  end
@@ -26,7 +26,7 @@ module Dependabot
26
26
  sig { returns(T::Array[Dependabot::Credential]) }
27
27
  attr_reader :credentials
28
28
 
29
- sig { returns(T::Hash[Symbol, T.untyped]) }
29
+ sig { returns(T::Hash[Symbol, T.anything]) }
30
30
  attr_reader :options
31
31
 
32
32
  sig do
@@ -35,7 +35,7 @@ module Dependabot
35
35
  dependency_files: T::Array[Dependabot::DependencyFile],
36
36
  credentials: T::Array[Dependabot::Credential],
37
37
  repo_contents_path: T.nilable(String),
38
- options: T::Hash[Symbol, T.untyped]
38
+ options: T::Hash[Symbol, T.anything]
39
39
  ).void
40
40
  end
41
41
  def initialize(dependencies:, dependency_files:, credentials:, repo_contents_path: nil, options: {})
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
5
+ require "time"
5
6
  require "sorbet-runtime"
6
7
  require "gitlab"
7
8
  require "dependabot/clients/github_with_retries"
@@ -15,10 +16,15 @@ require "dependabot/dependency"
15
16
  require "dependabot/credential"
16
17
  require "dependabot/git_metadata_fetcher"
17
18
  require "dependabot/git_tag_details"
19
+ require "dependabot/package/package_release"
20
+ require "dependabot/package/release_cooldown_options"
21
+ require "dependabot/update_checkers/cooldown_calculation"
22
+ require "dependabot/git_cooldown_date_resolver"
18
23
  module Dependabot
19
24
  # rubocop:disable Metrics/ClassLength
20
25
  class GitCommitChecker
21
26
  extend T::Sig
27
+ include Dependabot::GitCooldownDateResolver
22
28
 
23
29
  VERSION_REGEX = /
24
30
  (?<version>
@@ -174,9 +180,35 @@ module Dependabot
174
180
  max_local_tag_for_lower_precision(allowed_refs)
175
181
  end
176
182
 
177
- sig { returns(T.nilable(Dependabot::GitTagDetails)) }
178
- def local_tag_for_latest_version
179
- max_local_tag(allowed_version_tags)
183
+ # Returns the latest allowed version tag, or nil when every candidate tag is
184
+ # still within its cooldown window. All cooldown handling — the nil /
185
+ # disabled / excluded short-circuits and the logging — lives in
186
+ # #apply_cooldown, so this method stays a thin orchestration step.
187
+ sig do
188
+ params(cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions))
189
+ .returns(T.nilable(Dependabot::GitTagDetails))
190
+ end
191
+ def local_tag_for_latest_version(cooldown_options = nil)
192
+ filtered_tags = apply_cooldown(allowed_version_tags, cooldown_options)
193
+ return nil if filtered_tags.nil?
194
+
195
+ max_local_tag(filtered_tags)
196
+ end
197
+
198
+ # Returns the latest allowed version tag, but only when the dependency is
199
+ # pinned to a ref that looks like a version. Most ecosystems share this
200
+ # precondition before resolving a newer version tag for a git dependency,
201
+ # so it lives here instead of being repeated at each call site. Returns nil
202
+ # for dependencies that aren't pinned to a version (e.g. branch- or
203
+ # SHA-pinned), which never want a version-tag update.
204
+ sig do
205
+ params(cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions))
206
+ .returns(T.nilable(Dependabot::GitTagDetails))
207
+ end
208
+ def local_tag_for_pinned_version_ref(cooldown_options = nil)
209
+ return nil unless pinned_ref_looks_like_version?
210
+
211
+ local_tag_for_latest_version(cooldown_options)
180
212
  end
181
213
 
182
214
  sig { returns(T::Array[Dependabot::GitTagDetails]) }
@@ -285,6 +317,30 @@ module Dependabot
285
317
  allowed_versions(local_tags, filter_by_prefix: false)
286
318
  end
287
319
 
320
+ # Returns the allowed version tags paired with their parsed release
321
+ # dates as a single list, so callers don't have to re-correlate two
322
+ # separate lists (tags and tag-dates) by tag name themselves.
323
+ sig { returns(T::Array[Dependabot::Package::PackageRelease]) }
324
+ def allowed_version_tags_with_release_dates
325
+ allowed_version_tags.map do |tag|
326
+ Dependabot::Package::PackageRelease.new(
327
+ version: T.cast(version_from_tag(tag), Dependabot::Version),
328
+ tag: tag.name,
329
+ released_at: release_date_for_tag_name(tag.name)
330
+ )
331
+ end
332
+ end
333
+
334
+ sig { override.returns(T.nilable(String)) }
335
+ def cooldown_source_url
336
+ dependency_source_details&.fetch(:url, nil)
337
+ end
338
+
339
+ sig { override.returns(T::Array[Dependabot::Credential]) }
340
+ def cooldown_credentials
341
+ credentials
342
+ end
343
+
288
344
  private
289
345
 
290
346
  sig { returns(Dependabot::Dependency) }
@@ -689,7 +745,9 @@ module Dependabot
689
745
  source: T.must(source),
690
746
  credentials: credentials
691
747
  )
692
- client.releases(T.must(source).repo, per_page: 100)
748
+ # The Octokit RBI types this as non-nil, but it can be nil at runtime
749
+ # (e.g. an empty/unexpected registry response), so guard against it.
750
+ T.unsafe(client.releases(T.must(source).repo, per_page: 100)) || []
693
751
  rescue Octokit::Error
694
752
  []
695
753
  end,
@@ -712,6 +770,107 @@ module Dependabot
712
770
  T.must(T.must(name.match(VERSION_REGEX)).named_captures.fetch("version"))
713
771
  end
714
772
 
773
+ # Filters out git tags that are still within their cooldown window. Returns
774
+ # the candidate tags unchanged when cooldown does not apply (no options,
775
+ # disabled, or the dependency is excluded), and nil when every candidate tag
776
+ # is still within its cooldown window, so the caller proposes no update.
777
+ sig do
778
+ params(
779
+ candidate_tags: T::Array[Dependabot::GitRef],
780
+ cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
781
+ ).returns(T.nilable(T::Array[Dependabot::GitRef]))
782
+ end
783
+ def apply_cooldown(candidate_tags, cooldown_options)
784
+ if Dependabot::UpdateCheckers::CooldownCalculation.skip_cooldown?(cooldown_options, dependency.name)
785
+ return candidate_tags
786
+ end
787
+
788
+ cooldown = T.must(cooldown_options)
789
+ filtered_tags = candidate_tags.reject { |tag| tag_in_cooldown_period?(tag, cooldown) }
790
+
791
+ if filtered_tags.empty? && candidate_tags.any?
792
+ Dependabot.logger.info(
793
+ "All git tags for #{dependency.name} are within their cooldown period; skipping update"
794
+ )
795
+ return nil
796
+ end
797
+
798
+ filtered_tags
799
+ end
800
+
801
+ sig do
802
+ params(tag: Dependabot::GitRef, cooldown: Dependabot::Package::ReleaseCooldownOptions)
803
+ .returns(T::Boolean)
804
+ end
805
+ def tag_in_cooldown_period?(tag, cooldown)
806
+ released_at = release_date_for_tag_name(tag.name)
807
+ return false unless released_at
808
+
809
+ cooldown_days = Dependabot::UpdateCheckers::CooldownCalculation.cooldown_days_for(
810
+ cooldown,
811
+ T.cast(current_version, T.nilable(Dependabot::Version)),
812
+ T.cast(version_from_tag(tag), Dependabot::Version)
813
+ )
814
+
815
+ in_cooldown = Dependabot::UpdateCheckers::CooldownCalculation
816
+ .within_cooldown_window?(released_at, cooldown_days)
817
+
818
+ if in_cooldown
819
+ passed_days = (Time.now.to_i - released_at.to_i) /
820
+ Dependabot::UpdateCheckers::CooldownCalculation::DAY_IN_SECONDS
821
+ Dependabot.logger.info(
822
+ "Filtered git tag #{tag.name} for #{dependency.name}: released " \
823
+ "#{released_at.strftime('%Y-%m-%d')} (#{passed_days}/#{cooldown_days} cooldown days)"
824
+ )
825
+ end
826
+
827
+ in_cooldown
828
+ end
829
+
830
+ # The allowed version tags may carry a "tags/" prefix (when the dependency
831
+ # is pinned via "tags/<ref>"), whereas refs_for_tag_with_detail returns the
832
+ # raw tag names, so we try both forms to line dates up regardless of prefix.
833
+ #
834
+ # A GitHub Release's published_at is preferred when available, falling back
835
+ # to the tag creation date from refs_for_tag_with_detail. This mirrors the
836
+ # priority used by Dependabot::GitCooldownDateResolver.
837
+ sig { params(tag_name: String).returns(T.nilable(Time)) }
838
+ def release_date_for_tag_name(tag_name)
839
+ normalized = normalize_tag_name(tag_name)
840
+
841
+ github_release_published_at(normalized) ||
842
+ release_dates_by_tag_name[tag_name] ||
843
+ release_dates_by_tag_name[normalized]
844
+ end
845
+
846
+ sig { returns(T::Hash[String, Time]) }
847
+ def release_dates_by_tag_name
848
+ @release_dates_by_tag_name ||= T.let(
849
+ build_release_dates_by_tag_name,
850
+ T.nilable(T::Hash[String, Time])
851
+ )
852
+ end
853
+
854
+ sig { returns(T::Hash[String, Time]) }
855
+ def build_release_dates_by_tag_name
856
+ dates = T.let({}, T::Hash[String, Time])
857
+ refs_for_tag_with_detail.each do |detail|
858
+ released_at = parse_release_date(detail.release_date)
859
+ dates[detail.tag] = released_at if released_at
860
+ end
861
+ dates
862
+ end
863
+
864
+ sig { params(release_date: T.nilable(String)).returns(T.nilable(Time)) }
865
+ def parse_release_date(release_date)
866
+ return nil if release_date.nil? || release_date.empty?
867
+
868
+ Time.parse(release_date)
869
+ rescue ArgumentError => e
870
+ Dependabot.logger.error("Invalid release date format: #{release_date} (#{e.message})")
871
+ nil
872
+ end
873
+
715
874
  sig { returns(T.class_of(Gem::Version)) }
716
875
  def version_class
717
876
  @version_class ||= T.let(
@@ -1,7 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "cgi"
5
4
  require "excon"
6
5
  require "nokogiri"
7
6
  require "sorbet-runtime"
@@ -40,7 +39,7 @@ module Dependabot
40
39
  sig { returns(T.nilable(ReleaseCooldownOptions)) }
41
40
  attr_reader :cooldown_options
42
41
 
43
- sig { returns(T::Hash[Symbol, T.untyped]) }
42
+ sig { returns(T::Hash[Symbol, T.anything]) }
44
43
  attr_reader :options
45
44
 
46
45
  sig do
@@ -52,7 +51,7 @@ module Dependabot
52
51
  security_advisories: T::Array[Dependabot::SecurityAdvisory],
53
52
  cooldown_options: T.nilable(ReleaseCooldownOptions),
54
53
  raise_on_ignored: T::Boolean,
55
- options: T::Hash[Symbol, T.untyped]
54
+ options: T::Hash[Symbol, T.anything]
56
55
  ).void
57
56
  end
58
57
  def initialize(
@@ -336,7 +335,7 @@ module Dependabot
336
335
  end
337
336
  end
338
337
 
339
- sig { returns(T::Array[T.untyped]) }
338
+ sig { returns(T::Array[Dependabot::Requirement]) }
340
339
  def ignore_requirements
341
340
  ignored_versions.flat_map { |req| requirement_class.requirements_array(req) }
342
341
  end
@@ -24,7 +24,7 @@ module Dependabot
24
24
  package_type: T.nilable(String),
25
25
  language: T.nilable(Dependabot::Package::PackageLanguage),
26
26
  tag: T.nilable(String),
27
- details: T::Hash[String, T.untyped]
27
+ details: T::Hash[String, T.anything]
28
28
  ).void
29
29
  end
30
30
  def initialize(
@@ -50,7 +50,7 @@ module Dependabot
50
50
  @package_type = T.let(package_type, T.nilable(String))
51
51
  @language = T.let(language, T.nilable(Dependabot::Package::PackageLanguage))
52
52
  @tag = T.let(tag, T.nilable(String))
53
- @details = T.let(details, T::Hash[String, T.untyped])
53
+ @details = T.let(details, T::Hash[String, T.anything])
54
54
  end
55
55
 
56
56
  sig { returns(Dependabot::Version) }
@@ -83,7 +83,7 @@ module Dependabot
83
83
  sig { returns(T.nilable(String)) }
84
84
  attr_reader :tag
85
85
 
86
- sig { returns(T::Hash[String, T.untyped]) }
86
+ sig { returns(T::Hash[String, T.anything]) }
87
87
  attr_reader :details
88
88
 
89
89
  sig { returns(T::Boolean) }
@@ -0,0 +1,70 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ class PullRequestCreator
8
+ # Typed view over the `commit_message_options` hash threaded into the PR
9
+ # creator's title and prefix builders. The raw options arrive as a loosely
10
+ # typed hash sourced from user config, so this parses the known keys once at
11
+ # the boundary and lets downstream builders use typed readers instead of
12
+ # digging an untyped hash.
13
+ class CommitMessageOptions
14
+ extend T::Sig
15
+
16
+ sig { returns(T.nilable(String)) }
17
+ attr_reader :prefix
18
+
19
+ sig { returns(T.nilable(String)) }
20
+ attr_reader :prefix_development
21
+
22
+ sig { returns(T::Boolean) }
23
+ attr_reader :include_scope
24
+
25
+ sig do
26
+ params(
27
+ prefix: T.nilable(String),
28
+ prefix_development: T.nilable(String),
29
+ include_scope: T::Boolean
30
+ ).void
31
+ end
32
+ def initialize(prefix: nil, prefix_development: nil, include_scope: false)
33
+ @prefix = prefix
34
+ @prefix_development = prefix_development
35
+ @include_scope = include_scope
36
+ end
37
+
38
+ # Parses a raw options hash. Unknown keys are ignored, and absent or
39
+ # non-string prefixes become nil.
40
+ sig { params(options: T::Hash[Symbol, T.anything]).returns(CommitMessageOptions) }
41
+ def self.from_hash(options)
42
+ new(
43
+ prefix: coerce_string(options[:prefix]),
44
+ prefix_development: coerce_string(options[:prefix_development]),
45
+ include_scope: options[:include_scope] ? true : false
46
+ )
47
+ end
48
+
49
+ sig { params(value: T.anything).returns(T.nilable(String)) }
50
+ def self.coerce_string(value)
51
+ case value
52
+ when String then value
53
+ end
54
+ end
55
+ private_class_method :coerce_string
56
+
57
+ # True when an explicit (non-nil) prefix was provided.
58
+ sig { returns(T::Boolean) }
59
+ def prefix?
60
+ !prefix.nil?
61
+ end
62
+
63
+ # True when an explicit (non-nil) development prefix was provided.
64
+ sig { returns(T::Boolean) }
65
+ def prefix_development?
66
+ !prefix_development.nil?
67
+ end
68
+ end
69
+ end
70
+ end
@@ -247,7 +247,7 @@ module Dependabot
247
247
  end
248
248
  end
249
249
 
250
- sig { params(details: T::Hash[String, T.untyped]).returns(String) }
250
+ sig { params(details: T::Hash[String, T.anything]).returns(String) }
251
251
  def vulnerability_version_range_lines(details)
252
252
  msg = ""
253
253
  %w(
@@ -256,9 +256,12 @@ module Dependabot
256
256
  affected_versions
257
257
  ).each do |tp|
258
258
  type = T.must(tp.split("_").first).capitalize
259
- next unless details[tp]
259
+ versions = case (value = details[tp])
260
+ when Array then value
261
+ end
262
+ next unless versions
260
263
 
261
- versions_string = details[tp].any? ? details[tp].join("; ") : "none"
264
+ versions_string = versions.any? ? versions.join("; ") : "none"
262
265
  versions_string = versions_string.gsub(/(?<!\\)~/, '\~')
263
266
  msg += "> #{type} versions: #{versions_string}\n"
264
267
  end
@@ -1,9 +1,10 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
5
5
  require "dependabot/dependency"
6
6
  require "dependabot/logger"
7
+ require "dependabot/pull_request_creator/commit_message_options"
7
8
  require "dependabot/pull_request_creator/pr_name_prefixer"
8
9
 
9
10
  module Dependabot
@@ -25,7 +26,7 @@ module Dependabot
25
26
  sig { returns(T.nilable(Dependabot::PullRequestCreator::PrNamePrefixer)) }
26
27
  attr_reader :prefixer
27
28
 
28
- sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
29
+ sig { returns(T.nilable(CommitMessageOptions)) }
29
30
  attr_reader :commit_message_options
30
31
 
31
32
  sig { returns(T.nilable(T::Array[Dependabot::Dependency])) }
@@ -35,14 +36,17 @@ module Dependabot
35
36
  params(
36
37
  base_title: String,
37
38
  prefixer: T.nilable(Dependabot::PullRequestCreator::PrNamePrefixer),
38
- commit_message_options: T.nilable(T::Hash[Symbol, T.untyped]),
39
+ commit_message_options: T.nilable(T::Hash[Symbol, T.anything]),
39
40
  dependencies: T.nilable(T::Array[Dependabot::Dependency])
40
41
  ).void
41
42
  end
42
43
  def initialize(base_title:, prefixer: nil, commit_message_options: nil, dependencies: nil)
43
44
  @base_title = base_title
44
45
  @prefixer = prefixer
45
- @commit_message_options = commit_message_options
46
+ @commit_message_options = T.let(
47
+ commit_message_options && CommitMessageOptions.from_hash(commit_message_options),
48
+ T.nilable(CommitMessageOptions)
49
+ )
46
50
  @dependencies = dependencies
47
51
  end
48
52
 
@@ -85,12 +89,12 @@ module Dependabot
85
89
  # but without requiring source/credentials.
86
90
  sig { returns(String) }
87
91
  def build_explicit_prefix
88
- return "" unless commit_message_options&.key?(:prefix)
92
+ return "" unless commit_message_options&.prefix?
89
93
 
90
94
  prefix = explicit_prefix_string
91
95
  return "" if prefix.empty?
92
96
 
93
- prefix += "(#{scope})" if commit_message_options&.dig(:include_scope)
97
+ prefix += "(#{scope})" if commit_message_options&.include_scope
94
98
  # Append colon after alphanumeric or closing bracket to follow
95
99
  # conventional commit format (e.g., "chore: ..." or "fix(deps): ...")
96
100
  prefix += ":" if prefix.match?(/[A-Za-z0-9\)\]]\Z/)
@@ -101,11 +105,11 @@ module Dependabot
101
105
  sig { returns(String) }
102
106
  def explicit_prefix_string
103
107
  if production_dependencies?
104
- commit_message_options&.dig(:prefix).to_s
105
- elsif commit_message_options&.key?(:prefix_development)
106
- commit_message_options&.dig(:prefix_development).to_s
108
+ commit_message_options&.prefix.to_s
109
+ elsif commit_message_options&.prefix_development?
110
+ commit_message_options&.prefix_development.to_s
107
111
  else
108
- commit_message_options&.dig(:prefix).to_s
112
+ commit_message_options&.prefix.to_s
109
113
  end
110
114
  end
111
115
 
@@ -45,10 +45,10 @@ module Dependabot
45
45
  sig { returns(T.nilable(String)) }
46
46
  attr_reader :pr_message_footer
47
47
 
48
- sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
48
+ sig { returns(T.nilable(T::Hash[Symbol, T.anything])) }
49
49
  attr_reader :commit_message_options
50
50
 
51
- sig { returns(T::Hash[String, T.untyped]) }
51
+ sig { returns(T::Hash[String, T::Array[T::Hash[String, String]]]) }
52
52
  attr_reader :vulnerabilities_fixed
53
53
 
54
54
  sig { returns(T.nilable(String)) }
@@ -79,8 +79,8 @@ module Dependabot
79
79
  credentials: T::Array[Dependabot::Credential],
80
80
  pr_message_header: T.nilable(String),
81
81
  pr_message_footer: T.nilable(String),
82
- commit_message_options: T.nilable(T::Hash[Symbol, T.untyped]),
83
- vulnerabilities_fixed: T::Hash[String, T.untyped],
82
+ commit_message_options: T.nilable(T::Hash[Symbol, T.anything]),
83
+ vulnerabilities_fixed: T::Hash[String, T::Array[T::Hash[String, String]]],
84
84
  github_redirection_service: T.nilable(String),
85
85
  dependency_group: T.nilable(Dependabot::DependencyGroup),
86
86
  pr_message_max_length: T.nilable(Integer),
@@ -379,7 +379,7 @@ module Dependabot
379
379
 
380
380
  sig { returns(T.nilable(String)) }
381
381
  def custom_trailers
382
- trailers = commit_message_options&.dig(:trailers)
382
+ trailers = T.cast(commit_message_options&.dig(:trailers), T.nilable(Object))
383
383
  return if trailers.nil?
384
384
  raise("Commit trailers must be a Hash object") unless trailers.is_a?(Hash)
385
385
 
@@ -395,7 +395,7 @@ module Dependabot
395
395
 
396
396
  sig { returns(T.nilable(String)) }
397
397
  def signoff_message
398
- signoff_details = commit_message_options&.dig(:signoff_details)
398
+ signoff_details = T.cast(commit_message_options&.dig(:signoff_details), T.nilable(Object))
399
399
  return unless signoff_details.is_a?(Hash)
400
400
  return unless signoff_details[:name] && signoff_details[:email]
401
401
 
@@ -404,7 +404,7 @@ module Dependabot
404
404
 
405
405
  sig { returns(T.nilable(String)) }
406
406
  def on_behalf_of_message
407
- signoff_details = commit_message_options&.dig(:signoff_details)
407
+ signoff_details = T.cast(commit_message_options&.dig(:signoff_details), T.nilable(Object))
408
408
  return unless signoff_details.is_a?(Hash)
409
409
  return unless signoff_details[:org_name] && signoff_details[:org_email]
410
410
 
@@ -9,6 +9,7 @@ require "dependabot/clients/codecommit"
9
9
  require "dependabot/clients/github_with_retries"
10
10
  require "dependabot/clients/gitlab_with_retries"
11
11
  require "dependabot/pull_request_creator"
12
+ require "dependabot/pull_request_creator/commit_message_options"
12
13
 
13
14
  module Dependabot
14
15
  class PullRequestCreator
@@ -39,7 +40,7 @@ module Dependabot
39
40
  dependencies: T::Array[Dependency],
40
41
  credentials: T::Array[Dependabot::Credential],
41
42
  security_fix: T::Boolean,
42
- commit_message_options: T.nilable(T::Hash[Symbol, T.untyped])
43
+ commit_message_options: T.nilable(T::Hash[Symbol, T.anything])
43
44
  )
44
45
  .void
45
46
  end
@@ -54,7 +55,10 @@ module Dependabot
54
55
  @source = source
55
56
  @credentials = credentials
56
57
  @security_fix = security_fix
57
- @commit_message_options = commit_message_options
58
+ @commit_message_options = T.let(
59
+ commit_message_options && CommitMessageOptions.from_hash(commit_message_options),
60
+ T.nilable(CommitMessageOptions)
61
+ )
58
62
  end
59
63
 
60
64
  sig { returns(String) }
@@ -85,7 +89,7 @@ module Dependabot
85
89
  sig { returns(T::Array[Dependabot::Credential]) }
86
90
  attr_reader :credentials
87
91
 
88
- sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
92
+ sig { returns(T.nilable(CommitMessageOptions)) }
89
93
  attr_reader :commit_message_options
90
94
 
91
95
  sig { returns(T::Boolean) }
@@ -96,7 +100,7 @@ module Dependabot
96
100
  sig { returns(T.nilable(String)) }
97
101
  def commit_prefix
98
102
  # If a preferred prefix has been explicitly provided, use it
99
- return prefix_from_explicitly_provided_details if commit_message_options&.key?(:prefix)
103
+ return prefix_from_explicitly_provided_details if commit_message_options&.prefix?
100
104
 
101
105
  # Otherwise, if there is a previous Dependabot commit and it used a
102
106
  # known style, use that as our model for subsequent commits
@@ -112,7 +116,7 @@ module Dependabot
112
116
  prefix = explicitly_provided_prefix_string
113
117
  return if prefix.empty?
114
118
 
115
- prefix += "(#{scope})" if commit_message_options&.dig(:include_scope)
119
+ prefix += "(#{scope})" if commit_message_options&.include_scope
116
120
  prefix += ":" if prefix.match?(/[A-Za-z0-9\)\]]\Z/)
117
121
  prefix += " " unless prefix.end_with?(" ")
118
122
  prefix
@@ -121,14 +125,14 @@ module Dependabot
121
125
  # rubocop:disable Metrics/PerceivedComplexity
122
126
  sig { returns(String) }
123
127
  def explicitly_provided_prefix_string
124
- raise "No explicitly provided prefix!" unless commit_message_options&.key?(:prefix)
128
+ raise "No explicitly provided prefix!" unless commit_message_options&.prefix?
125
129
 
126
130
  if dependencies.any?(&:production?)
127
- commit_message_options&.dig(:prefix).to_s
128
- elsif commit_message_options&.key?(:prefix_development)
129
- commit_message_options&.dig(:prefix_development).to_s
131
+ commit_message_options&.prefix.to_s
132
+ elsif commit_message_options&.prefix_development?
133
+ commit_message_options&.prefix_development.to_s
130
134
  else
131
- commit_message_options&.dig(:prefix).to_s
135
+ commit_message_options&.prefix.to_s
132
136
  end
133
137
  end
134
138
  # rubocop:enable Metrics/PerceivedComplexity
@@ -97,10 +97,10 @@ module Dependabot
97
97
  sig { returns(T.nilable(String)) }
98
98
  attr_reader :signature_key
99
99
 
100
- sig { returns(T::Hash[Symbol, T.untyped]) }
100
+ sig { returns(T::Hash[Symbol, T.anything]) }
101
101
  attr_reader :commit_message_options
102
102
 
103
- sig { returns(T::Hash[String, String]) }
103
+ sig { returns(T::Hash[String, T::Array[T::Hash[String, String]]]) }
104
104
  attr_reader :vulnerabilities_fixed
105
105
 
106
106
  AzureReviewers = T.type_alias { T.nilable(T::Array[String]) }
@@ -132,7 +132,7 @@ module Dependabot
132
132
  sig { returns(T.nilable(T::Hash[String, String])) }
133
133
  attr_reader :custom_headers
134
134
 
135
- sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
135
+ sig { returns(T.nilable(T::Hash[Symbol, T.anything])) }
136
136
  attr_reader :provider_metadata
137
137
 
138
138
  sig { returns(T.nilable(Dependabot::DependencyGroup)) }
@@ -156,8 +156,8 @@ module Dependabot
156
156
  custom_labels: T.nilable(T::Array[String]),
157
157
  author_details: T.nilable(T::Hash[Symbol, String]),
158
158
  signature_key: T.nilable(String),
159
- commit_message_options: T::Hash[Symbol, T.untyped],
160
- vulnerabilities_fixed: T::Hash[String, String],
159
+ commit_message_options: T::Hash[Symbol, T.anything],
160
+ vulnerabilities_fixed: T::Hash[String, T::Array[T::Hash[String, String]]],
161
161
  reviewers: Reviewers,
162
162
  assignees: T.nilable(T.any(T::Array[String], T::Array[Integer])),
163
163
  milestone: T.nilable(T.any(T::Array[String], Integer)),
@@ -169,7 +169,7 @@ module Dependabot
169
169
  github_redirection_service: String,
170
170
  custom_headers: T.nilable(T::Hash[String, String]),
171
171
  require_up_to_date_base: T::Boolean,
172
- provider_metadata: T.nilable(T::Hash[Symbol, T.untyped]),
172
+ provider_metadata: T.nilable(T::Hash[Symbol, T.anything]),
173
173
  message: T.nilable(
174
174
  T.any(Dependabot::PullRequestCreator::Message, Dependabot::PullRequestCreator::MessageBuilder)
175
175
  ),
@@ -253,7 +253,7 @@ module Dependabot
253
253
  # TODO: This returns client-specific objects.
254
254
  # We should create a standard interface (`Dependabot::PullRequest`) and
255
255
  # then convert to that
256
- sig { returns(T.untyped) }
256
+ sig { returns(T.anything) }
257
257
  def create
258
258
  case source.provider
259
259
  when "github" then github_creator.create
@@ -21,8 +21,8 @@ module Dependabot
21
21
  sig do
22
22
  params(
23
23
  url: String,
24
- headers: T::Hash[T.any(String, Symbol), T.untyped],
25
- options: T::Hash[Symbol, T.untyped]
24
+ headers: T::Hash[T.any(String, Symbol), T.anything],
25
+ options: T::Hash[Symbol, T.anything]
26
26
  )
27
27
  .returns(Excon::Response)
28
28
  end
@@ -43,8 +43,8 @@ module Dependabot
43
43
  sig do
44
44
  params(
45
45
  url: String,
46
- headers: T::Hash[T.any(String, Symbol), T.untyped],
47
- options: T::Hash[Symbol, T.untyped]
46
+ headers: T::Hash[T.any(String, Symbol), T.anything],
47
+ options: T::Hash[Symbol, T.anything]
48
48
  )
49
49
  .returns(Excon::Response)
50
50
  end
@@ -9,14 +9,14 @@ module Dependabot
9
9
  extend T::Sig
10
10
  extend T::Generic
11
11
 
12
- sig { returns(T.nilable(T::Array[T.proc.params(name: String, params: T::Hash[Symbol, T.untyped]).void])) }
12
+ sig { returns(T.nilable(T::Array[T.proc.params(name: String, params: T::Hash[Symbol, T.anything]).void])) }
13
13
  attr_accessor :subscribers
14
14
 
15
- sig { params(block: T.proc.params(name: String, params: T::Hash[Symbol, T.untyped]).void).void }
15
+ sig { params(block: T.proc.params(name: String, params: T::Hash[Symbol, T.anything]).void).void }
16
16
  def subscribe(&block)
17
17
  @subscribers ||= T.let(
18
18
  [],
19
- T.nilable(T::Array[T.proc.params(name: String, params: T::Hash[Symbol, T.untyped]).void])
19
+ T.nilable(T::Array[T.proc.params(name: String, params: T::Hash[Symbol, T.anything]).void])
20
20
  )
21
21
  @subscribers << block
22
22
  end
@@ -25,7 +25,7 @@ module Dependabot
25
25
  type_parameters(:T)
26
26
  .params(
27
27
  name: String,
28
- params: T::Hash[Symbol, T.untyped],
28
+ params: T::Hash[Symbol, T.anything],
29
29
  block: T.proc.returns(T.type_parameter(:T))
30
30
  )
31
31
  .returns(T.nilable(T.type_parameter(:T)))
@@ -46,7 +46,7 @@ module Dependabot
46
46
  sig { returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions)) }
47
47
  attr_reader :update_cooldown
48
48
 
49
- sig { returns(T::Hash[Symbol, T.untyped]) }
49
+ sig { returns(T::Hash[Symbol, T.anything]) }
50
50
  attr_reader :options
51
51
 
52
52
  sig do
@@ -61,7 +61,7 @@ module Dependabot
61
61
  requirements_update_strategy: T.nilable(Dependabot::RequirementsUpdateStrategy),
62
62
  dependency_group: T.nilable(Dependabot::DependencyGroup),
63
63
  update_cooldown: T.nilable(Dependabot::Package::ReleaseCooldownOptions),
64
- options: T::Hash[Symbol, T.untyped]
64
+ options: T::Hash[Symbol, T.anything]
65
65
  )
66
66
  .void
67
67
  end
@@ -233,7 +233,7 @@ module Dependabot
233
233
  # instances are returned as-is; plain hashes are wrapped. Re-wrapping
234
234
  # would produce a value-equal copy, so skipping it avoids needless
235
235
  # allocations.
236
- sig { params(requirements: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Array[Dependabot::DependencyRequirement]) }
236
+ sig { params(requirements: T::Array[T::Hash[Symbol, T.anything]]).returns(T::Array[Dependabot::DependencyRequirement]) }
237
237
  def wrap_requirements(requirements)
238
238
  requirements.map do |requirement|
239
239
  requirement.is_a?(Dependabot::DependencyRequirement) ? requirement : Dependabot::DependencyRequirement.create(requirement)
@@ -406,7 +406,7 @@ module Dependabot
406
406
  version_class.correct?(latest_version.to_s)) || false
407
407
  end
408
408
 
409
- sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
409
+ sig { returns(T::Array[Dependabot::DependencyRequirement]) }
410
410
  def changed_requirements
411
411
  (updated_requirements - dependency.requirements)
412
412
  end
@@ -32,6 +32,19 @@ module Dependabot
32
32
  version.to_s.match?(ANCHORED_VERSION_PATTERN)
33
33
  end
34
34
 
35
+ # RubyGems 4 eagerly computes a numeric sort key in Gem::Version#initialize and
36
+ # skips it for prereleases. Dependabot's subclasses override #prerelease? with
37
+ # ecosystem-specific semantics, so a version with alphabetic segments (e.g.
38
+ # "1.0.0.RELEASE" or "2.0.0.post1") can report prerelease? == false and reach
39
+ # RubyGems' radix comparison, which raises when a segment is a String. Returning
40
+ # nil for those versions falls back to the segment-by-segment comparison.
41
+ sig { override.overridable.returns(T.nilable(Integer)) }
42
+ def compute_sort_key
43
+ return if canonical_segments.any?(String)
44
+
45
+ super
46
+ end
47
+
35
48
  sig { overridable.returns(String) }
36
49
  def to_semver
37
50
  @original_version
data/lib/dependabot.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Dependabot
5
- VERSION = "0.383.0"
5
+ VERSION = "0.385.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.383.0
4
+ version: 0.385.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -580,6 +580,7 @@ files:
580
580
  - lib/dependabot/pull_request_creator/branch_namer/multi_ecosystem_strategy.rb
581
581
  - lib/dependabot/pull_request_creator/branch_namer/solo_strategy.rb
582
582
  - lib/dependabot/pull_request_creator/codecommit.rb
583
+ - lib/dependabot/pull_request_creator/commit_message_options.rb
583
584
  - lib/dependabot/pull_request_creator/commit_signer.rb
584
585
  - lib/dependabot/pull_request_creator/github.rb
585
586
  - lib/dependabot/pull_request_creator/gitlab.rb
@@ -620,7 +621,7 @@ licenses:
620
621
  - MIT
621
622
  metadata:
622
623
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
623
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.383.0
624
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.385.0
624
625
  rdoc_options: []
625
626
  require_paths:
626
627
  - lib
@@ -635,7 +636,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
635
636
  - !ruby/object:Gem::Version
636
637
  version: 3.3.7
637
638
  requirements: []
638
- rubygems_version: 3.7.2
639
+ rubygems_version: 4.0.14
639
640
  specification_version: 4
640
641
  summary: Shared code used across Dependabot Core
641
642
  test_files: []