dependabot-pub 0.313.0 → 0.314.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: '01096fb7d16aa388209c212b5886f6e18663855ec26ead152776f5a58af5ef5e'
4
- data.tar.gz: f4df8bca6ca81d4e876e24d0be79a544c91b708e8aa6bfaaadda206ef7519289
3
+ metadata.gz: b1d8d80fff978983eed70309e27fff98acb23bd39b17465cf5f8f4a4b54e7143
4
+ data.tar.gz: b713d25fcd36c560602fb896d0aa0c6c7b33d74097274ac633f736b9e79db15b
5
5
  SHA512:
6
- metadata.gz: 50f5e426b7c6bbf96d8e0e199488b78b30606e7c871e894fc63eeeaf4c1c8951e01c392b80c6569f349163e2b824fb6842690ccd3c881f31c7ce08c1c2d89727
7
- data.tar.gz: 6c0121a571a1a25b82d38b3b0fd294294d0503c7fca43bd6105f63ca4515f5a070070c6ea4948afa6d98be4bf186c6845a766f254716e1ab957cff0b90fa9d1d
6
+ metadata.gz: 8df8ac5de2592d0c59e926483115141fd28788d9b89873e101bcf3d906740706bcfb621296c36bafc6420fd3a5c2754824982ebadd2505a8d47534ff956f94ad
7
+ data.tar.gz: ce021fa1b516ea8a806ca620374691d3b5565f9f0dd03772354bec6b1b7f55018513ceefe992d408eaf168c4f64fc893624ab44b32ce8a6cb117d4e0ff66a21d
@@ -120,6 +120,11 @@ module Dependabot
120
120
  )
121
121
  end
122
122
 
123
+ sig { params(dependency: Dependabot::Dependency).returns(Excon::Response) }
124
+ def fetch_package_metadata(dependency)
125
+ Dependabot::RegistryClient.get(url: "#{repository_url(dependency)}/api/packages/#{dependency.name}")
126
+ end
127
+
123
128
  # Clones the flutter repo into /tmp/flutter if needed
124
129
  sig { void }
125
130
  def ensure_flutter_repo
@@ -0,0 +1,114 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "time"
6
+ require "cgi"
7
+ require "excon"
8
+ require "nokogiri"
9
+ require "sorbet-runtime"
10
+ require "dependabot/registry_client"
11
+ require "dependabot/pub"
12
+ require "dependabot/package/package_release"
13
+ require "dependabot/package/package_details"
14
+ require "dependabot/pub/helpers"
15
+ require "dependabot/requirements_update_strategy"
16
+ require "dependabot/update_checkers"
17
+ require "dependabot/update_checkers/base"
18
+ require "dependabot/update_checkers/version_filters"
19
+
20
+ module Dependabot
21
+ module Pub
22
+ module Package
23
+ class PackageDetailsFetcher
24
+ extend T::Sig
25
+ include Dependabot::Pub::Helpers
26
+
27
+ sig { returns(Dependabot::Dependency) }
28
+ attr_reader :dependency
29
+
30
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
31
+ attr_reader :dependency_files
32
+
33
+ sig { override.returns(T::Hash[Symbol, T.untyped]) }
34
+ attr_reader :options
35
+
36
+ sig { override.returns(T::Array[Dependabot::Credential]) }
37
+ attr_reader :credentials
38
+
39
+ sig do
40
+ params(
41
+ dependency: Dependabot::Dependency,
42
+ dependency_files: T::Array[Dependabot::DependencyFile],
43
+ credentials: T::Array[Dependabot::Credential],
44
+ ignored_versions: T::Array[String],
45
+ security_advisories: T::Array[Dependabot::SecurityAdvisory],
46
+ options: T::Hash[Symbol, T.untyped]
47
+ )
48
+ .void
49
+ end
50
+ def initialize(dependency:, dependency_files:, credentials:,
51
+ ignored_versions: [],
52
+ security_advisories: [], options: {})
53
+ @dependency = dependency
54
+ @dependency_files = dependency_files
55
+ @credentials = credentials
56
+ @ignored_versions = ignored_versions
57
+ @security_advisories = security_advisories
58
+ @options = options
59
+ end
60
+
61
+ sig { returns(T::Array[T::Hash[String, T.untyped]]) }
62
+ def report
63
+ @report ||= T.let(
64
+ dependency_services_report,
65
+ T.nilable(T::Array[T::Hash[String, T.untyped]])
66
+ )
67
+ end
68
+
69
+ sig { returns(T.any(T::Array[Dependabot::Package::PackageRelease], T.untyped)) }
70
+ def package_details_metadata
71
+ package_releases = []
72
+ T.let({}, T::Hash[String, T.untyped])
73
+
74
+ Dependabot.logger.error("Initializing package metadata for \"#{@dependency.name}\"")
75
+
76
+ response = fetch_package_metadata(dependency)
77
+ return package_releases if response.status >= 500
78
+
79
+ begin
80
+ package_details_metadata = JSON.parse(response.body)
81
+
82
+ package_details_metadata["versions"].select do |v|
83
+ package_releases << package_release(version: v["version"],
84
+ publish_date: Time.parse(v["published"]))
85
+ end
86
+
87
+ package_releases
88
+ rescue JSON::ParserError
89
+ Dependabot.logger.error("Failed to parse package metadata")
90
+ package_releases
91
+ end
92
+ rescue StandardError => e
93
+ Dependabot.logger.error("Failed to fetch package metadata #{e.message}")
94
+ package_releases
95
+ end
96
+
97
+ private
98
+
99
+ sig do
100
+ params(
101
+ version: String,
102
+ publish_date: T.nilable(Time)
103
+ ).returns(Dependabot::Package::PackageRelease)
104
+ end
105
+ def package_release(version:, publish_date: nil)
106
+ Dependabot::Package::PackageRelease.new(
107
+ version: Pub::Version.new(version),
108
+ released_at: publish_date
109
+ )
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,215 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "excon"
5
+ require "dependabot/pub/update_checker"
6
+ require "dependabot/update_checkers/version_filters"
7
+ require "dependabot/registry_client"
8
+ require "dependabot/pub/package/package_details_fetcher"
9
+ require "dependabot/package/package_latest_version_finder"
10
+ require "sorbet-runtime"
11
+
12
+ module Dependabot
13
+ module Pub
14
+ class UpdateChecker
15
+ class LatestVersionFinder
16
+ extend T::Sig
17
+
18
+ include Dependabot::Pub::Package
19
+
20
+ DAY_IN_SECONDS = T.let(24 * 60 * 60, Integer)
21
+
22
+ sig do
23
+ params(
24
+ dependency: Dependabot::Dependency,
25
+ dependency_files: T::Array[Dependabot::DependencyFile],
26
+ credentials: T::Array[Dependabot::Credential],
27
+ ignored_versions: T::Array[String],
28
+ security_advisories: T::Array[Dependabot::SecurityAdvisory],
29
+ options: T::Hash[Symbol, T.untyped],
30
+ cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
31
+ ).void
32
+ end
33
+ def initialize(dependency:, dependency_files:, credentials:,
34
+ ignored_versions: [],
35
+ security_advisories: [], options: {},
36
+ cooldown_options: nil)
37
+ @dependency = dependency
38
+ @dependency_files = dependency_files
39
+ @credentials = credentials
40
+ @ignored_versions = ignored_versions
41
+ @security_advisories = security_advisories
42
+ @options = options
43
+ @cooldown_options = cooldown_options
44
+ end
45
+
46
+ sig { returns(T::Hash[String, T.untyped]) }
47
+ def current_report
48
+ @current_report ||= T.let(T.must(PackageDetailsFetcher.new(
49
+ dependency: dependency,
50
+ dependency_files: dependency_files,
51
+ credentials: credentials,
52
+ ignored_versions: ignored_versions,
53
+ security_advisories: security_advisories,
54
+ options: options
55
+ ).report.find { |d| d["name"] == dependency.name }), T.nilable(T::Hash[String, T.untyped]))
56
+ end
57
+
58
+ sig { returns(T.nilable(String)) }
59
+ def latest_version
60
+ latest_version = current_report["latest"]
61
+ return nil unless latest_version
62
+
63
+ filter_cooldown_versions(latest_version)
64
+ end
65
+
66
+ sig { returns(T.nilable(String)) }
67
+ def latest_resolvable_version
68
+ latest_resolvable_version = current_report["singleBreaking"]&.find { |d| d["name"] == dependency.name }
69
+ return nil unless latest_resolvable_version
70
+
71
+ filter_cooldown_versions(latest_resolvable_version["version"])
72
+ end
73
+
74
+ sig { returns(T.nilable(String)) }
75
+ def latest_resolvable_version_with_no_unlock
76
+ version_with_no_unlock = current_report["compatible"]&.find { |d| d["name"] == dependency.name }
77
+ return nil unless version_with_no_unlock
78
+
79
+ filter_cooldown_versions(version_with_no_unlock["version"])
80
+ end
81
+
82
+ sig { returns(T.nilable(String)) }
83
+ def latest_version_resolvable_with_full_unlock
84
+ version_with_full_unlock = current_report["multiBreaking"]&.find { |d| d["name"] == dependency.name }
85
+ return nil unless version_with_full_unlock
86
+
87
+ filter_cooldown_versions(version_with_full_unlock["version"])
88
+ end
89
+
90
+ sig { returns(T.untyped) }
91
+ def latest_version_resolvable_with_full_unlock_hash
92
+ current_report["multiBreaking"]
93
+ end
94
+
95
+ sig { returns(T.untyped) }
96
+ def latest_resolvable_version_hash
97
+ current_report["singleBreaking"].find { |d| d["name"] == dependency.name }
98
+ end
99
+
100
+ private
101
+
102
+ sig do
103
+ params(
104
+ unparsed_version: String
105
+ ).returns(T.nilable(String))
106
+ end
107
+ def filter_cooldown_versions(unparsed_version)
108
+ return unparsed_version unless cooldown_enabled?
109
+ return unparsed_version unless cooldown_options
110
+
111
+ @package_details ||= T.let(PackageDetailsFetcher.new(
112
+ dependency: dependency,
113
+ dependency_files: dependency_files,
114
+ credentials: credentials,
115
+ ignored_versions: ignored_versions,
116
+ security_advisories: security_advisories,
117
+ options: options
118
+ ).package_details_metadata, T.nilable(T::Array[Dependabot::Package::PackageRelease]))
119
+
120
+ return unparsed_version unless @package_details.any?
121
+
122
+ version_release = @package_details.find do |release|
123
+ release.version == unparsed_version
124
+ end
125
+
126
+ return unparsed_version unless in_cooldown_period?(version_release)
127
+
128
+ dependency.version
129
+ rescue StandardError => e
130
+ Dependabot.logger.error("Failed to filter cooldown versions for \"#{dependency.name}\": #{e.backtrace}")
131
+ unparsed_version
132
+ end
133
+
134
+ sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
135
+ def in_cooldown_period?(release)
136
+ unless release.released_at
137
+ Dependabot.logger.info("Release date not available for version #{release.version}")
138
+ return false
139
+ end
140
+
141
+ current_version = version_class.correct?(dependency.version) ? version_class.new(dependency.version) : nil
142
+ days = cooldown_days_for(current_version, release.version)
143
+
144
+ # Calculate the number of seconds passed since the release
145
+ passed_seconds = Time.now.to_i - release.released_at.to_i
146
+ passed_days = passed_seconds / DAY_IN_SECONDS
147
+
148
+ if passed_days < days
149
+ Dependabot.logger.info("Version #{release.version}, Release date: #{release.released_at}." \
150
+ " Days since release: #{passed_days} (cooldown days: #{days})")
151
+ end
152
+
153
+ # Check if the release is within the cooldown period
154
+ passed_seconds < days * DAY_IN_SECONDS
155
+ end
156
+
157
+ sig do
158
+ params(
159
+ current_version: T.nilable(Dependabot::Version),
160
+ new_version: Dependabot::Version
161
+ ).returns(Integer)
162
+ end
163
+ def cooldown_days_for(current_version, new_version)
164
+ cooldown = @cooldown_options
165
+ return 0 if cooldown.nil?
166
+ return 0 unless cooldown_enabled?
167
+ return 0 unless cooldown.included?(dependency.name)
168
+ return cooldown.default_days if current_version.nil?
169
+
170
+ current_version_semver = current_version.semver_parts
171
+ new_version_semver = new_version.semver_parts
172
+
173
+ # If semver_parts is nil for either, return default cooldown
174
+ return cooldown.default_days if current_version_semver.nil? || new_version_semver.nil?
175
+
176
+ # Ensure values are always integers
177
+ current_major, current_minor, current_patch = current_version_semver
178
+ new_major, new_minor, new_patch = new_version_semver
179
+
180
+ # Determine cooldown based on version difference
181
+ return cooldown.semver_major_days if new_major > current_major
182
+ return cooldown.semver_minor_days if new_minor > current_minor
183
+ return cooldown.semver_patch_days if new_patch > current_patch
184
+
185
+ cooldown.default_days
186
+ end
187
+
188
+ sig { returns(T::Boolean) }
189
+ def cooldown_enabled?
190
+ Dependabot::Experiments.enabled?(:enable_cooldown_for_pub)
191
+ end
192
+
193
+ sig { returns(T.class_of(Dependabot::Version)) }
194
+ def version_class
195
+ dependency.version_class
196
+ end
197
+
198
+ sig { returns(Dependabot::Dependency) }
199
+ attr_reader :dependency
200
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
201
+ attr_reader :dependency_files
202
+ sig { returns(T::Array[Dependabot::Credential]) }
203
+ attr_reader :credentials
204
+ sig { returns(T::Array[String]) }
205
+ attr_reader :ignored_versions
206
+ sig { returns(T::Array[Dependabot::SecurityAdvisory]) }
207
+ attr_reader :security_advisories
208
+ sig { returns(T::Hash[Symbol, T.untyped]) }
209
+ attr_reader :options
210
+ sig { returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions)) }
211
+ attr_reader :cooldown_options
212
+ end
213
+ end
214
+ end
215
+ end
@@ -17,39 +17,25 @@ module Dependabot
17
17
 
18
18
  include Dependabot::Pub::Helpers
19
19
 
20
+ require_relative "update_checker/latest_version_finder"
21
+
20
22
  sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
21
23
  def latest_version
22
- version = version_unless_ignored(current_report["latest"], current_version: dependency.version)
24
+ version = version_unless_ignored(T.must(version_report.latest_version), current_version: dependency.version)
25
+
23
26
  raise AllVersionsIgnored if version.nil? && @raise_on_ignored
24
27
 
25
28
  version
26
29
  end
27
30
 
28
- sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
29
- def latest_resolvable_version_with_no_unlock
30
- # Version we can get if we're not allowed to change pubspec.yaml, but we
31
- # allow changes in the pubspec.lock file.
32
- entry = current_report["compatible"].find { |d| d["name"] == dependency.name }
33
- return nil unless entry
34
-
35
- version_unless_ignored(entry["version"])
36
- end
37
-
38
31
  sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
39
32
  def latest_resolvable_version
40
33
  # Latest version we can get if we're allowed to unlock the current
41
34
  # package in pubspec.yaml
42
- entry = current_report["singleBreaking"].find { |d| d["name"] == dependency.name }
35
+ entry = version_report.latest_resolvable_version
43
36
  return nil unless entry
44
37
 
45
- version_unless_ignored(entry["version"])
46
- end
47
-
48
- sig { override.returns(T.nilable(Dependabot::Version)) }
49
- def lowest_resolvable_security_fix_version
50
- raise "Dependency not vulnerable!" unless vulnerable?
51
-
52
- lowest_security_fix_version
38
+ version_unless_ignored(entry)
53
39
  end
54
40
 
55
41
  sig { override.returns(T.nilable(Dependabot::Version)) }
@@ -68,7 +54,23 @@ module Dependabot
68
54
  T.cast(version_unless_ignored(version), Dependabot::Version)
69
55
  end
70
56
 
71
- # rubocop:disable Metrics/PerceivedComplexity
57
+ sig { override.returns(T.nilable(Dependabot::Version)) }
58
+ def lowest_resolvable_security_fix_version
59
+ raise "Dependency not vulnerable!" unless vulnerable?
60
+
61
+ lowest_security_fix_version
62
+ end
63
+
64
+ sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
65
+ def latest_resolvable_version_with_no_unlock
66
+ # Version we can get if we're not allowed to change pubspec.yaml, but we
67
+ # allow changes in the pubspec.lock file.
68
+ entry = version_report.latest_resolvable_version_with_no_unlock
69
+ return nil unless entry
70
+
71
+ version_unless_ignored(entry)
72
+ end
73
+
72
74
  sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
73
75
  def updated_requirements
74
76
  # Requirements that need to be changed, if obtain:
@@ -87,14 +89,13 @@ module Dependabot
87
89
 
88
90
  updates&.find { |u| u["name"] == dependency.name }
89
91
  else
90
- current_report["singleBreaking"].find { |d| d["name"] == dependency.name }
92
+ version_report.latest_resolvable_version_hash
91
93
  end
92
94
  return [] unless entry
93
95
 
94
96
  parse_updated_dependency(entry, resolved_requirements_update_strategy)
95
97
  .requirements
96
98
  end
97
- # rubocop:enable Metrics/PerceivedComplexity
98
99
 
99
100
  private
100
101
 
@@ -167,20 +168,15 @@ module Dependabot
167
168
  end
168
169
  end
169
170
 
170
- sig { params(version_string: String).returns(T::Boolean) }
171
- def git_revision?(version_string)
172
- version_string.match?(/^[0-9a-f]{6,}$/)
173
- end
174
-
175
171
  sig { override.returns(T::Boolean) }
176
172
  def latest_version_resolvable_with_full_unlock?
177
- entry = current_report["multiBreaking"].find { |d| d["name"] == dependency.name }
173
+ entry = version_report.latest_version_resolvable_with_full_unlock
178
174
  # This a bit dumb, but full-unlock is only considered if we can get the
179
175
  # latest version!
180
176
  return false unless entry
181
177
 
182
- (!git_revision?(entry["version"]) && latest_version == Dependabot::Pub::Version.new(entry["version"])) ||
183
- latest_version == entry["version"]
178
+ (!git_revision?(entry) && latest_version == Dependabot::Pub::Version.new(entry)) ||
179
+ latest_version == entry
184
180
  end
185
181
 
186
182
  sig { override.returns(T::Array[Dependabot::Dependency]) }
@@ -188,7 +184,7 @@ module Dependabot
188
184
  report_section = if vulnerable?
189
185
  dependency_services_smallest_update
190
186
  else
191
- current_report["multiBreaking"]
187
+ version_report.latest_version_resolvable_with_full_unlock_hash
192
188
  end
193
189
  # We only expose non-transitive dependencies here...
194
190
  direct_deps = report_section.reject do |d|
@@ -199,17 +195,19 @@ module Dependabot
199
195
  end
200
196
  end
201
197
 
202
- sig { returns(T::Array[T::Hash[String, T.untyped]]) }
203
- def report
204
- @report ||= T.let(
205
- dependency_services_report,
206
- T.nilable(T::Array[T::Hash[String, T.untyped]])
207
- )
208
- end
209
-
210
- sig { returns(T::Hash[String, T.untyped]) }
211
- def current_report
212
- T.must(report.find { |d| d["name"] == dependency.name })
198
+ sig { returns(Dependabot::Pub::UpdateChecker::LatestVersionFinder) }
199
+ def version_report
200
+ @version_report ||=
201
+ T.let(LatestVersionFinder.new(
202
+ dependency: dependency,
203
+ dependency_files: dependency_files,
204
+ credentials: credentials,
205
+ ignored_versions: ignored_versions,
206
+ security_advisories: security_advisories,
207
+ options: options,
208
+ cooldown_options: update_cooldown
209
+ ),
210
+ T.nilable(Dependabot::Pub::UpdateChecker::LatestVersionFinder))
213
211
  end
214
212
 
215
213
  sig { returns(Dependabot::RequirementsUpdateStrategy) }
@@ -220,6 +218,11 @@ module Dependabot
220
218
  )
221
219
  end
222
220
 
221
+ sig { params(version_string: String).returns(T::Boolean) }
222
+ def git_revision?(version_string)
223
+ version_string.match?(/^[0-9a-f]{6,}$/)
224
+ end
225
+
223
226
  sig { returns(Dependabot::RequirementsUpdateStrategy) }
224
227
  def resolve_requirements_update_strategy
225
228
  raise "Unexpected requirements_update_strategy #{requirements_update_strategy}" unless
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-pub
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.313.0
4
+ version: 0.314.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-15 00:00:00.000000000 Z
10
+ date: 2025-05-22 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dependabot-common
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.313.0
18
+ version: 0.314.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.313.0
25
+ version: 0.314.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -248,16 +248,18 @@ files:
248
248
  - lib/dependabot/pub/helpers.rb
249
249
  - lib/dependabot/pub/language.rb
250
250
  - lib/dependabot/pub/metadata_finder.rb
251
+ - lib/dependabot/pub/package/package_details_fetcher.rb
251
252
  - lib/dependabot/pub/package_manager.rb
252
253
  - lib/dependabot/pub/requirement.rb
253
254
  - lib/dependabot/pub/update_checker.rb
255
+ - lib/dependabot/pub/update_checker/latest_version_finder.rb
254
256
  - lib/dependabot/pub/version.rb
255
257
  homepage: https://github.com/dependabot/dependabot-core
256
258
  licenses:
257
259
  - MIT
258
260
  metadata:
259
261
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
260
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.313.0
262
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.314.0
261
263
  rdoc_options: []
262
264
  require_paths:
263
265
  - lib