dependabot-common 0.395.0 → 0.396.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: d9566b1a9930f4b53e02d88a9eda5893f794bcd80b37c0a82870dab926432fba
4
- data.tar.gz: 77aa3094407df4ef7627a53cb584abd0cf1b14a16e690b3ee178c8600dad2dd7
3
+ metadata.gz: 38e1442399b4db684997304709cfd914195b3a067d6195989cebead9c1c5d188
4
+ data.tar.gz: 5a949d0992ee5bddd0ce5f0641ddcc9fe7e70ca32ce21dafbd7504e07e293069
5
5
  SHA512:
6
- metadata.gz: f994b68adbb197b61dfa48006b35b8cd39058d5b482393aa2c56993b86958c32364d7e6869b31b068a8fbc85375883eb33110abd9f5fdc254aff940ae4d98c51
7
- data.tar.gz: f5bba21d3f012e715c1918f161ce077d1d9f2a0b2ca681e90fd87d9b321d096b710f98902f1ece9e1814c2cdb16d8b89fb68f0d7670aab3b7630e68acaa0cd9c
6
+ metadata.gz: 7fcf1078fd4be3cfbd95be9b1529f6ee5c159f0891ad0baebbd42931a21fd2f2b6c02172614c6c537f72115d414aee3feede98296ae5a52c65fe6abafcd9dd82
7
+ data.tar.gz: 5304d03eb4968bd94732bee2f81e02405e1714c14258aae73735d68f07705c4a80f2c7c7ff96cfc2b65bc1814fbd01aa44b75fbe9cd5e7450304bcee3e3d5fb0
@@ -0,0 +1,168 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/dependency"
7
+ require "dependabot/package/package_release"
8
+ require "dependabot/update_checkers/cooldown_calculation"
9
+
10
+ module Dependabot
11
+ module Package
12
+ class CooldownDateTracker
13
+ extend T::Sig
14
+
15
+ sig { params(dependency: Dependabot::Dependency, ignored_versions: T::Array[String]).void }
16
+ def initialize(dependency:, ignored_versions:)
17
+ @dependency = dependency
18
+ @ignored_versions = ignored_versions
19
+ @active = T.let(false, T::Boolean)
20
+ @language_version = T.let(nil, T.nilable(T.any(String, Dependabot::Version)))
21
+ @enforce_requirements = T.let(false, T::Boolean)
22
+ @prefiltered = T.let(false, T::Boolean)
23
+ @releases = T.let({}, T::Hash[Dependabot::Package::PackageRelease, Integer])
24
+ end
25
+
26
+ sig { returns(T::Boolean) }
27
+ attr_reader :active
28
+
29
+ sig do
30
+ params(
31
+ language_version: T.nilable(T.any(String, Dependabot::Version)),
32
+ requirements: T::Boolean,
33
+ block: T.proc.returns(T::Array[Dependabot::Package::PackageRelease])
34
+ ).returns(T::Array[Dependabot::Package::PackageRelease])
35
+ end
36
+ def filter(language_version:, requirements:, &block)
37
+ @active = true
38
+ @language_version = language_version
39
+ @enforce_requirements = requirements
40
+
41
+ filtered = yield
42
+ mark_for_selected_release(filtered)
43
+ filtered
44
+ ensure
45
+ @active = false
46
+ @language_version = nil
47
+ @enforce_requirements = false
48
+ @releases.clear
49
+ end
50
+
51
+ sig do
52
+ params(block: T.proc.returns(T::Array[Dependabot::Package::PackageRelease]))
53
+ .returns(T::Array[Dependabot::Package::PackageRelease])
54
+ end
55
+ def filter_prefiltered(&block)
56
+ @prefiltered = true
57
+ filter(language_version: nil, requirements: false, &block)
58
+ ensure
59
+ @prefiltered = false
60
+ end
61
+
62
+ sig do
63
+ params(
64
+ release: Dependabot::Package::PackageRelease,
65
+ current_version: T.nilable(Dependabot::Version),
66
+ days: Integer
67
+ ).void
68
+ end
69
+ def record(release:, current_version:, days:)
70
+ return unless active
71
+ return unless @prefiltered || relevant?(release, current_version)
72
+
73
+ @releases[release] = days
74
+ end
75
+
76
+ private
77
+
78
+ sig { returns(Dependabot::Dependency) }
79
+ attr_reader :dependency
80
+
81
+ sig { returns(T::Array[String]) }
82
+ attr_reader :ignored_versions
83
+
84
+ sig do
85
+ params(
86
+ release: Dependabot::Package::PackageRelease,
87
+ current_version: T.nilable(Dependabot::Version)
88
+ ).returns(T::Boolean)
89
+ end
90
+ def relevant?(release, current_version)
91
+ return false if release.yanked?
92
+ return false if current_version && release.version <= current_version
93
+ return false if unwanted_prerelease?(release)
94
+ return false if ignored?(release)
95
+ return false unless language_supported?(release)
96
+
97
+ requirements_allow?(release)
98
+ end
99
+
100
+ sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
101
+ def unwanted_prerelease?(release)
102
+ release.version.prerelease? && !wants_prerelease?
103
+ end
104
+
105
+ sig { returns(T::Boolean) }
106
+ def wants_prerelease?
107
+ return true if dependency.numeric_version&.prerelease?
108
+
109
+ dependency.requirements.any? do |requirement|
110
+ requirement_string = requirement.requirement_string || ""
111
+ requirement_string.split(",").map(&:strip).any? do |part|
112
+ version_string = part.gsub(/^\s*[!<>=~^]+\s*/, "").strip
113
+ next false unless dependency.version_class.correct?(version_string)
114
+
115
+ dependency.version_class.new(version_string).prerelease?
116
+ end
117
+ end
118
+ end
119
+
120
+ sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
121
+ def ignored?(release)
122
+ requirements = ignored_versions.flat_map do |requirement|
123
+ dependency.requirement_class.requirements_array(requirement)
124
+ end
125
+ requirements.any? { |requirement| requirement.satisfied_by?(release.version) }
126
+ end
127
+
128
+ sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
129
+ def language_supported?(release)
130
+ requirement = release.language&.requirement
131
+ !@language_version || !requirement || requirement.satisfied_by?(@language_version)
132
+ end
133
+
134
+ sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
135
+ def requirements_allow?(release)
136
+ return true unless @enforce_requirements
137
+
138
+ dependency.requirements.filter_map(&:requirement_string).all? do |requirement_string|
139
+ dependency.requirement_class.requirements_array(requirement_string).any? do |requirement|
140
+ requirement.satisfied_by?(release.version)
141
+ end
142
+ end
143
+ end
144
+
145
+ sig { params(filtered: T::Array[Dependabot::Package::PackageRelease]).void }
146
+ def mark_for_selected_release(filtered)
147
+ return if @releases.empty?
148
+
149
+ current_version = dependency.numeric_version
150
+ eligible = if @prefiltered
151
+ filtered
152
+ else
153
+ filtered.select { |release| relevant?(release, current_version) }
154
+ end
155
+ selected = (eligible + @releases.keys).max_by(&:version)
156
+ return unless selected
157
+
158
+ days = @releases[selected]
159
+ return unless days
160
+
161
+ Dependabot::UpdateCheckers::CooldownCalculation.mark_cooldown_date_unavailable(
162
+ dependency,
163
+ cooldown_days: days
164
+ )
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,37 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/errors"
6
+
7
+ module Dependabot
8
+ module Package
9
+ class NpmLockfileDetails < T::ImmutableStruct
10
+ extend T::Sig
11
+
12
+ const :version, T.nilable(String), default: nil
13
+ const :resolved, T.nilable(String), default: nil
14
+ const :resolution, T.nilable(String), default: nil
15
+
16
+ sig { params(value: Object, path: String, context: String).returns(NpmLockfileDetails) }
17
+ def self.from_object(value, path:, context:)
18
+ raise DependencyFileNotParseable.new(path, "#{context} must be an object") unless value.is_a?(Hash)
19
+
20
+ new(
21
+ version: optional_string(T.cast(value["version"], Object), path, "#{context}.version"),
22
+ resolved: optional_string(T.cast(value["resolved"], Object), path, "#{context}.resolved"),
23
+ resolution: optional_string(T.cast(value["resolution"], Object), path, "#{context}.resolution")
24
+ )
25
+ end
26
+
27
+ sig { params(value: Object, path: String, field: String).returns(T.nilable(String)) }
28
+ def self.optional_string(value, path, field)
29
+ return if value.nil?
30
+ return value if value.is_a?(String)
31
+
32
+ raise DependencyFileNotParseable.new(path, "#{field} must be a string or nil")
33
+ end
34
+ private_class_method :optional_string
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,83 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "sorbet-runtime"
6
+
7
+ require "dependabot/dependency_file"
8
+ require "dependabot/package/npm_package_manager_config"
9
+
10
+ module Dependabot
11
+ module Package
12
+ class NpmPackageJson
13
+ extend T::Sig
14
+
15
+ ObjectHash = T.type_alias { T::Hash[String, Object] }
16
+ DEPENDENCY_TYPES = %w(dependencies devDependencies optionalDependencies).freeze
17
+
18
+ sig { params(file: DependencyFile).returns(NpmPackageJson) }
19
+ def self.from_file(file)
20
+ new(T.cast(JSON.parse(T.must(file.content)), Object), file.path)
21
+ end
22
+
23
+ sig { params(data: Object, path: String).void }
24
+ def initialize(data, path)
25
+ @path = path
26
+ @data = T.let(object_hash(data, "root"), ObjectHash)
27
+ end
28
+
29
+ sig do
30
+ params(_block: T.proc.params(name: String, requirement: String, type: String).void).void
31
+ end
32
+ def each_dependency(&_block)
33
+ DEPENDENCY_TYPES.each do |type|
34
+ dependencies = @data[type]
35
+ next unless dependencies
36
+
37
+ object_hash(dependencies, type).each do |name, requirement|
38
+ next unless requirement.is_a?(String)
39
+
40
+ yield name, requirement, type
41
+ end
42
+ end
43
+ end
44
+
45
+ sig { returns(T.nilable(String)) }
46
+ def name
47
+ value = @data["name"]
48
+ value if value.is_a?(String)
49
+ end
50
+
51
+ sig { returns(T::Boolean) }
52
+ def flat?
53
+ !!@data["flat"]
54
+ end
55
+
56
+ sig { returns(T::Boolean) }
57
+ def workspaces?
58
+ !!@data["workspaces"]
59
+ end
60
+
61
+ sig { returns(NpmPackageManagerConfig) }
62
+ def package_manager_config
63
+ NpmPackageManagerConfig.from_package_json(@data)
64
+ end
65
+
66
+ private
67
+
68
+ sig { params(value: Object, field: String).returns(ObjectHash) }
69
+ def object_hash(value, field)
70
+ raise TypeError, "#{@path}: #{field} must be an object" unless value.is_a?(Hash)
71
+
72
+ result = T.let({}, ObjectHash)
73
+ value.each do |raw_key, raw_value|
74
+ key = T.cast(raw_key, Object)
75
+ raise TypeError, "#{@path}: #{field} keys must be strings" unless key.is_a?(String)
76
+
77
+ result[key] = T.cast(raw_value, Object)
78
+ end
79
+ result
80
+ end
81
+ end
82
+ end
83
+ end
@@ -12,10 +12,11 @@ require "dependabot/update_checkers/cooldown_calculation"
12
12
  require "dependabot/registry_client"
13
13
  require "dependabot/package/package_details"
14
14
  require "dependabot/package/release_cooldown_options"
15
+ require "dependabot/package/cooldown_date_tracker"
15
16
 
16
17
  module Dependabot
17
18
  module Package
18
- class PackageLatestVersionFinder
19
+ class PackageLatestVersionFinder # rubocop:disable Metrics/ClassLength
19
20
  extend T::Sig
20
21
  extend T::Helpers
21
22
 
@@ -79,6 +80,8 @@ module Dependabot
79
80
  @latest_version_with_no_unlock = T.let(nil, T.nilable(Dependabot::Version))
80
81
  @lowest_security_fix_version = T.let(nil, T.nilable(Dependabot::Version))
81
82
  @package_details = T.let(nil, T.nilable(Dependabot::Package::PackageDetails))
83
+ cooldown_tracker = CooldownDateTracker.new(dependency: dependency, ignored_versions: ignored_versions)
84
+ @cooldown_tracker = T.let(cooldown_tracker, CooldownDateTracker)
82
85
  end
83
86
 
84
87
  sig do
@@ -121,15 +124,16 @@ module Dependabot
121
124
  @lowest_security_fix_version ||= fetch_lowest_security_fix_version(language_version: language_version)
122
125
  end
123
126
 
124
- sig do
125
- returns(T.nilable(T::Array[Dependabot::Package::PackageRelease]))
126
- end
127
+ sig { returns(T.nilable(T::Array[Dependabot::Package::PackageRelease])) }
127
128
  def available_versions
128
129
  package_details&.releases
129
130
  end
130
131
 
131
132
  protected
132
133
 
134
+ sig { returns(CooldownDateTracker) }
135
+ attr_reader :cooldown_tracker
136
+
133
137
  sig do
134
138
  params(language_version: T.nilable(T.any(String, Dependabot::Version)))
135
139
  .returns(T.nilable(Dependabot::Version))
@@ -139,12 +143,13 @@ module Dependabot
139
143
  return unless releases
140
144
 
141
145
  releases = filter_yanked_versions(releases)
142
- releases = filter_by_cooldown(releases)
143
- releases = filter_unsupported_versions(releases, language_version)
144
- releases = filter_prerelease_versions(releases)
145
- releases = filter_ignored_versions(releases)
146
- releases = filter_out_of_range_versions(releases)
147
- releases = apply_post_fetch_latest_versions_filter(releases)
146
+ releases = @cooldown_tracker.filter(language_version:, requirements: true) do
147
+ filtered = filter_by_cooldown(releases)
148
+ filtered = filter_prerelease_versions(filter_unsupported_versions(filtered, language_version))
149
+ filtered = filter_ignored_versions(filtered)
150
+ filtered = filter_out_of_range_versions(filtered)
151
+ apply_post_fetch_latest_versions_filter(filtered)
152
+ end
148
153
  releases.max_by(&:version)&.version
149
154
  end
150
155
 
@@ -154,9 +159,8 @@ module Dependabot
154
159
  end
155
160
  def filter_yanked_versions(releases)
156
161
  filtered = releases.reject(&:yanked?)
157
- if releases.count > filtered.count
158
- Dependabot.logger.info("Filtered out #{releases.count - filtered.count} yanked versions")
159
- end
162
+ removed_count = releases.count - filtered.count
163
+ Dependabot.logger.info("Filtered out #{removed_count} yanked versions") if removed_count.positive?
160
164
  filtered
161
165
  end
162
166
 
@@ -165,8 +169,13 @@ module Dependabot
165
169
  .returns(T::Array[Dependabot::Package::PackageRelease])
166
170
  end
167
171
  def filter_by_cooldown(releases)
168
- return releases unless cooldown_enabled?
169
- return releases unless cooldown_options
172
+ unless @cooldown_tracker.active
173
+ return @cooldown_tracker.filter(language_version: nil, requirements: false) do
174
+ filter_by_cooldown(releases)
175
+ end
176
+ end
177
+
178
+ return releases unless cooldown_enabled? && cooldown_options
170
179
 
171
180
  filtered = releases.reject { |release| in_cooldown_period?(release) }
172
181
 
@@ -176,21 +185,11 @@ module Dependabot
176
185
 
177
186
  # If all releases were filtered out due to cooldown and we have a current version, use it as fallback
178
187
  if filtered.empty? && !releases.empty? && dependency.version
179
- current_version_str = dependency.version
180
-
181
188
  Dependabot.logger.info(
182
189
  "All versions filtered by cooldown for #{dependency.name}, " \
183
- "falling back to current version #{current_version_str}"
184
- )
185
-
186
- # Create a PackageRelease for the current version
187
- current_version = version_class.new(current_version_str)
188
- current_release = Dependabot::Package::PackageRelease.new(
189
- version: current_version,
190
- released_at: nil,
191
- tag: nil
190
+ "falling back to current version #{dependency.version}"
192
191
  )
193
- return [current_release]
192
+ return [cooldown_fallback_release]
194
193
  end
195
194
 
196
195
  filtered
@@ -198,20 +197,17 @@ module Dependabot
198
197
 
199
198
  sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
200
199
  def in_cooldown_period?(release)
201
- return false unless release.released_at
200
+ current_version = version_class.correct?(dependency.version) ? version_class.new(dependency.version) : nil
201
+ days = cooldown_days_for(current_version, release.version)
202
+ return false unless days.positive?
202
203
 
203
- cooldown = @cooldown_options
204
- return false if Dependabot::UpdateCheckers::CooldownCalculation.skip_cooldown?(
205
- cooldown, dependency.name, cooldown_enabled: cooldown_enabled?
206
- )
204
+ released_at = released_at_for(release)
205
+ unless released_at
206
+ @cooldown_tracker.record(release: release, current_version: current_version, days: days)
207
+ return missing_release_date_blocks_update?(release)
208
+ end
207
209
 
208
- current_version = version_class.correct?(dependency.version) ? version_class.new(dependency.version) : nil
209
- days = Dependabot::UpdateCheckers::CooldownCalculation.cooldown_days_for(
210
- T.must(cooldown), current_version, release.version
211
- )
212
- Dependabot::UpdateCheckers::CooldownCalculation.within_cooldown_window?(
213
- T.must(release.released_at), days
214
- )
210
+ Dependabot::UpdateCheckers::CooldownCalculation.within_cooldown_window?(released_at, days)
215
211
  end
216
212
 
217
213
  sig do
@@ -223,10 +219,20 @@ module Dependabot
223
219
  end
224
220
  def filter_unsupported_versions(releases, language_version)
225
221
  filtered = releases.filter_map do |release|
226
- language_requirement = release.language&.requirement
227
- next release unless language_version
222
+ language = release.language
223
+ next release unless language_version && language
224
+
225
+ language_requirement = language.requirement
228
226
  next release unless language_requirement
229
- next unless language_requirement.satisfied_by?(language_version)
227
+
228
+ unless language_requirement.satisfied_by?(language_version)
229
+ Dependabot.logger.info(
230
+ "Filtered out #{dependency.name} #{release.version} because " \
231
+ "#{language.name} requirement #{language_requirement} is not satisfied by " \
232
+ "#{language.name} #{language_version}"
233
+ )
234
+ next
235
+ end
230
236
 
231
237
  release
232
238
  end
@@ -361,6 +367,20 @@ module Dependabot
361
367
  true
362
368
  end
363
369
 
370
+ # Registries that only expose a publication date through a second request resolve it here.
371
+ sig { overridable.params(release: Dependabot::Package::PackageRelease).returns(T.nilable(Time)) }
372
+ def released_at_for(release)
373
+ release.released_at
374
+ end
375
+
376
+ sig { overridable.params(_release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
377
+ def missing_release_date_blocks_update?(_release)
378
+ false
379
+ end
380
+
381
+ sig { overridable.returns(Dependabot::Package::PackageRelease) }
382
+ def cooldown_fallback_release = PackageRelease.new(version: version_class.new(T.must(dependency.version)))
383
+
364
384
  sig do
365
385
  params(language_version: T.nilable(T.any(String, Dependabot::Version)))
366
386
  .returns(T.nilable(Dependabot::Version))
@@ -378,11 +398,12 @@ module Dependabot
378
398
  return unless releases
379
399
 
380
400
  releases = filter_yanked_versions(releases)
381
- releases = filter_by_cooldown(releases)
382
- releases = filter_unsupported_versions(releases, language_version)
383
- releases = filter_prerelease_versions(releases)
384
- releases = filter_ignored_versions(releases)
385
- releases = apply_post_fetch_latest_versions_filter(releases)
401
+ releases = @cooldown_tracker.filter(language_version:, requirements: false) do
402
+ filtered = filter_by_cooldown(releases)
403
+ filtered = filter_prerelease_versions(filter_unsupported_versions(filtered, language_version))
404
+ filtered = filter_ignored_versions(filtered)
405
+ apply_post_fetch_latest_versions_filter(filtered)
406
+ end
386
407
  releases.max_by(&:version)
387
408
  end
388
409
 
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
5
+ require "dependabot/dependency"
5
6
  require "dependabot/package/release_cooldown_options"
6
7
  require "dependabot/version"
7
8
 
@@ -16,6 +17,14 @@ module Dependabot
16
17
  extend T::Sig
17
18
 
18
19
  DAY_IN_SECONDS = T.let(24 * 60 * 60, Integer)
20
+ DATE_UNAVAILABLE_METADATA_KEY = :cooldown_date_unavailable
21
+
22
+ # Shared by the pull request notice and the job-level warning so both channels
23
+ # report the same diagnostic, whether or not the update produced a pull request.
24
+ DATE_UNAVAILABLE_NOTICE_TYPE = "cooldown_date_unavailable"
25
+ DATE_UNAVAILABLE_TITLE = "Cooldown was not applied"
26
+ DATE_UNAVAILABLE_DESCRIPTION =
27
+ "Cooldown could not be applied because no publication date was available from the registry."
19
28
 
20
29
  sig { params(release_date: Time, cooldown_days: Integer).returns(T::Boolean) }
21
30
  def self.within_cooldown_window?(release_date, cooldown_days)
@@ -50,6 +59,18 @@ module Dependabot
50
59
  def self.skip_cooldown?(cooldown, dependency_name, cooldown_enabled: true)
51
60
  cooldown.nil? || !cooldown_enabled || !cooldown.included?(dependency_name)
52
61
  end
62
+
63
+ sig { params(dependency: Dependabot::Dependency, cooldown_days: Integer).void }
64
+ def self.mark_cooldown_date_unavailable(dependency, cooldown_days:)
65
+ return unless cooldown_days.positive?
66
+
67
+ dependency.metadata[DATE_UNAVAILABLE_METADATA_KEY] = true
68
+ end
69
+
70
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
71
+ def self.cooldown_date_unavailable?(dependency)
72
+ dependency.metadata[DATE_UNAVAILABLE_METADATA_KEY] == true
73
+ end
53
74
  end
54
75
  end
55
76
  end
data/lib/dependabot.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Dependabot
5
- VERSION = "0.395.0"
5
+ VERSION = "0.396.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.395.0
4
+ version: 0.396.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -572,6 +572,9 @@ files:
572
572
  - lib/dependabot/metadata_finders/base/commits_finder.rb
573
573
  - lib/dependabot/metadata_finders/base/release_finder.rb
574
574
  - lib/dependabot/notices.rb
575
+ - lib/dependabot/package/cooldown_date_tracker.rb
576
+ - lib/dependabot/package/npm_lockfile_details.rb
577
+ - lib/dependabot/package/npm_package_json.rb
575
578
  - lib/dependabot/package/npm_package_manager_config.rb
576
579
  - lib/dependabot/package/npm_registry_package.rb
577
580
  - lib/dependabot/package/package_details.rb
@@ -631,7 +634,7 @@ licenses:
631
634
  - MIT
632
635
  metadata:
633
636
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
634
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.395.0
637
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.396.0
635
638
  rdoc_options: []
636
639
  require_paths:
637
640
  - lib