dependabot-pub 0.397.0 → 0.399.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: 8ee93bbe3a567d05c49aa43c3cd6be245a3d297b73dd1299377d55ddcd52d8b6
4
- data.tar.gz: 6af10c2f14fdd2e0dc5c324c3a1b0d3af14a82647189c2e92a85b55fcc8cce45
3
+ metadata.gz: bccb4cb21417d15087e804ed056b2ff00451cf1c908fd8cdfb40f7709fd02ce8
4
+ data.tar.gz: 685f8a0c5df291c2be68371b3c798343e61f23cd17ea5745efc879315dd3e178
5
5
  SHA512:
6
- metadata.gz: 9b873c93b89ca23f6eccf6ed6d73829223226b8e86de473ae0f8b106e68d29b68bd23ee8ea42ea10782c7a090c528f8adae34e31d5d99b82693d8d08739df2d3
7
- data.tar.gz: 792a68fbdb3cbd1f4a63df868102bb3b86aa6c14746ce877cc32ec37287cf729684d854bef05e2936518596d5aa3f487696f87f2070ce6617d6f1546a623355b
6
+ metadata.gz: af4a96a16577145fa0f6c3a9baf56c447f90dbade75678af5e8037faf926da725ecf3e4370dfae19e0f7f923c25aa984fb148aae146be7dacb361628809000a7
7
+ data.tar.gz: f615713050dddd07821622e4fd15a059b5c0470179d44236b901855c2db0feddc3fcbf89612ab681c71d4966f6ef5b355ccb73f777cc5d09411accb0188d609a
@@ -0,0 +1,167 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/pub/json_value_parser"
6
+ require "dependabot/shared_helpers"
7
+
8
+ module Dependabot
9
+ module Pub
10
+ module DependencyServicesResult
11
+ extend T::Sig
12
+
13
+ class ListedDependency < T::ImmutableStruct
14
+ const :name, String
15
+ const :version, String
16
+ const :kind, String
17
+ const :constraint, T.nilable(String)
18
+ const :source, T.nilable(JsonValueParser::ObjectHash)
19
+ end
20
+
21
+ class DependencyUpdate < T::ImmutableStruct
22
+ const :name, String
23
+ const :version, T.nilable(String)
24
+ const :kind, String
25
+ const :previous_version, T.nilable(String)
26
+ const :previous_constraint, T.nilable(String)
27
+ const :constraint_bumped, T.nilable(String)
28
+ const :constraint_bumped_if_needed, T.nilable(String)
29
+ const :constraint_widened, T.nilable(String)
30
+ end
31
+
32
+ class ReportEntry < T::ImmutableStruct
33
+ const :name, String
34
+ const :version, String
35
+ const :latest, T.nilable(String)
36
+ const :compatible, T::Array[DependencyUpdate]
37
+ const :single_breaking, T::Array[DependencyUpdate]
38
+ const :multi_breaking, T::Array[DependencyUpdate]
39
+ const :smallest_update, T.nilable(T::Array[DependencyUpdate])
40
+ end
41
+
42
+ class Report < T::ImmutableStruct
43
+ const :dependencies, T::Array[ReportEntry]
44
+ const :cache_content, String
45
+ end
46
+
47
+ sig { params(content: String).returns(T::Array[ListedDependency]) }
48
+ def self.list_from_json(content)
49
+ context = "dependency_services list"
50
+ root = JsonValueParser.object(JsonValueParser.parse(content, context), context)
51
+ JsonValueParser.array(root["dependencies"], "#{context}.dependencies").each_with_index.map do |value, index|
52
+ entry_context = "#{context}.dependencies[#{index}]"
53
+ fields = JsonValueParser.object(value, entry_context)
54
+ ListedDependency.new(
55
+ name: JsonValueParser.string(fields["name"], "#{entry_context}.name"),
56
+ version: JsonValueParser.string(fields["version"], "#{entry_context}.version"),
57
+ kind: JsonValueParser.string(fields["kind"], "#{entry_context}.kind"),
58
+ constraint: JsonValueParser.optional_string(fields["constraint"], "#{entry_context}.constraint"),
59
+ source: JsonValueParser.optional_object(fields["source"], "#{entry_context}.source")
60
+ )
61
+ end
62
+ rescue JsonValueParser::InvalidValue => e
63
+ invalid_result("dependency_services list", e.message)
64
+ end
65
+
66
+ sig { params(content: String).returns(Report) }
67
+ def self.report_from_json(content)
68
+ context = "dependency_services report"
69
+ root = JsonValueParser.object(JsonValueParser.parse(content, context), context)
70
+ build_report(root["dependencies"], "#{context}.dependencies")
71
+ rescue JsonValueParser::InvalidValue => e
72
+ invalid_result("dependency_services report", e.message)
73
+ end
74
+
75
+ sig { params(content: String).returns(Report) }
76
+ def self.report_from_cache(content)
77
+ context = "dependency_services report cache"
78
+ build_report(JsonValueParser.parse(content, context), context)
79
+ rescue JsonValueParser::InvalidValue => e
80
+ invalid_result("dependency_services report cache", e.message)
81
+ end
82
+
83
+ sig { params(reports: T::Array[ReportEntry], name: String).returns(ReportEntry) }
84
+ def self.find_report(reports, name)
85
+ report = reports.find { |entry| entry.name == name }
86
+ return report if report
87
+
88
+ invalid_result(
89
+ "dependency_services report",
90
+ "dependency_services report does not include the requested dependency"
91
+ )
92
+ end
93
+
94
+ sig { params(context: String, message: String).returns(T.noreturn) }
95
+ def self.invalid_result(context, message)
96
+ raise SharedHelpers::HelperSubprocessFailed.new(
97
+ message: message,
98
+ error_class: "TypeError",
99
+ error_context: { function: context }
100
+ ),
101
+ cause: nil
102
+ end
103
+
104
+ sig { params(value: Object, context: String).returns(Report) }
105
+ def self.build_report(value, context)
106
+ raw_entries = JsonValueParser.array(value, context)
107
+ entries = raw_entries.each_with_index.map do |entry, index|
108
+ report_entry(entry, "#{context}[#{index}]")
109
+ end
110
+ Report.new(dependencies: entries, cache_content: JSON.generate(raw_entries))
111
+ end
112
+ private_class_method :build_report
113
+
114
+ sig { params(value: Object, context: String).returns(ReportEntry) }
115
+ def self.report_entry(value, context)
116
+ fields = JsonValueParser.object(value, context)
117
+ ReportEntry.new(
118
+ name: JsonValueParser.string(fields["name"], "#{context}.name"),
119
+ version: JsonValueParser.string(fields["version"], "#{context}.version"),
120
+ latest: JsonValueParser.optional_string(fields["latest"], "#{context}.latest"),
121
+ compatible: updates(fields["compatible"], "#{context}.compatible"),
122
+ single_breaking: updates(fields["singleBreaking"], "#{context}.singleBreaking"),
123
+ multi_breaking: updates(fields["multiBreaking"], "#{context}.multiBreaking"),
124
+ smallest_update: if fields.key?("smallestUpdate")
125
+ updates(
126
+ fields["smallestUpdate"],
127
+ "#{context}.smallestUpdate"
128
+ )
129
+ end
130
+ )
131
+ end
132
+ private_class_method :report_entry
133
+
134
+ sig { params(value: Object, context: String).returns(T::Array[DependencyUpdate]) }
135
+ def self.updates(value, context)
136
+ JsonValueParser.array(value, context).each_with_index.map do |entry, index|
137
+ entry_context = "#{context}[#{index}]"
138
+ fields = JsonValueParser.object(entry, entry_context)
139
+ DependencyUpdate.new(
140
+ name: JsonValueParser.string(fields["name"], "#{entry_context}.name"),
141
+ version: JsonValueParser.optional_string(fields["version"], "#{entry_context}.version"),
142
+ kind: JsonValueParser.string(fields["kind"], "#{entry_context}.kind"),
143
+ previous_version: JsonValueParser.optional_string(
144
+ fields["previousVersion"],
145
+ "#{entry_context}.previousVersion"
146
+ ),
147
+ previous_constraint: JsonValueParser.optional_string(
148
+ fields["previousConstraint"], "#{entry_context}.previousConstraint"
149
+ ),
150
+ constraint_bumped: JsonValueParser.optional_string(
151
+ fields["constraintBumped"],
152
+ "#{entry_context}.constraintBumped"
153
+ ),
154
+ constraint_bumped_if_needed: JsonValueParser.optional_string(
155
+ fields["constraintBumpedIfNeeded"], "#{entry_context}.constraintBumpedIfNeeded"
156
+ ),
157
+ constraint_widened: JsonValueParser.optional_string(
158
+ fields["constraintWidened"],
159
+ "#{entry_context}.constraintWidened"
160
+ )
161
+ )
162
+ end
163
+ end
164
+ private_class_method :updates
165
+ end
166
+ end
167
+ end
@@ -82,9 +82,9 @@ module Dependabot
82
82
  raise "No pubspec.yaml!" unless get_original_file("pubspec.yaml")
83
83
  end
84
84
 
85
- sig { returns(T::Array[T::Hash[String, Object]]) }
85
+ sig { returns(T::Array[DependencyServicesResult::ListedDependency]) }
86
86
  def list
87
- @list ||= T.let(dependency_services_list, T.nilable(T::Array[T::Hash[String, Object]]))
87
+ @list ||= T.let(dependency_services_list, T.nilable(T::Array[DependencyServicesResult::ListedDependency]))
88
88
  end
89
89
  end
90
90
  end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "digest"
@@ -8,6 +8,8 @@ require "sorbet-runtime"
8
8
 
9
9
  require "dependabot/errors"
10
10
  require "dependabot/logger"
11
+ require "dependabot/pub/dependency_services_result"
12
+ require "dependabot/pub/package/registry_package"
11
13
  require "dependabot/pub/requirement"
12
14
  require "dependabot/pub/requirement_source"
13
15
  require "dependabot/requirements_update_strategy"
@@ -23,13 +25,19 @@ module Dependabot
23
25
 
24
26
  abstract!
25
27
 
28
+ class SdkVersions < T::ImmutableStruct
29
+ const :flutter, String
30
+ const :dart, String
31
+ const :channel, T.nilable(String), default: nil
32
+ end
33
+
26
34
  sig { abstract.returns(T::Array[Dependabot::Credential]) }
27
35
  def credentials; end
28
36
 
29
37
  sig { abstract.returns(T::Array[Dependabot::DependencyFile]) }
30
38
  def dependency_files; end
31
39
 
32
- sig { abstract.returns(T::Hash[Symbol, T.untyped]) }
40
+ sig { abstract.returns(T::Hash[Symbol, T.anything]) }
33
41
  def options; end
34
42
 
35
43
  sig { returns(String) }
@@ -42,7 +50,7 @@ module Dependabot
42
50
  dir: T.any(Pathname, String),
43
51
  url: T.nilable(String)
44
52
  )
45
- .returns(T.nilable(T::Hash[String, T.untyped]))
53
+ .returns(T.nilable(SdkVersions))
46
54
  end
47
55
  def self.run_infer_sdk_versions(dir, url: nil)
48
56
  env = {}
@@ -51,23 +59,31 @@ module Dependabot
51
59
  stdout, _, status = Open3.capture3(env, cmd, opts, chdir: dir)
52
60
  return nil unless status.success?
53
61
 
54
- JSON.parse(stdout)
62
+ fields = JsonValueParser.object(JsonValueParser.parse(stdout, "infer_sdk_versions"), "infer_sdk_versions")
63
+ SdkVersions.new(
64
+ flutter: JsonValueParser.string(fields["flutter"], "infer_sdk_versions.flutter"),
65
+ dart: JsonValueParser.string(fields["dart"], "infer_sdk_versions.dart"),
66
+ channel: JsonValueParser.string(fields["channel"], "infer_sdk_versions.channel")
67
+ )
68
+ rescue JsonValueParser::InvalidValue => e
69
+ DependencyServicesResult.invalid_result("infer_sdk_versions", e.message)
55
70
  end
56
71
 
57
72
  private
58
73
 
59
- sig { returns(T::Array[T::Hash[String, T.untyped]]) }
74
+ sig { returns(T::Array[DependencyServicesResult::ListedDependency]) }
60
75
  def dependency_services_list
61
- JSON.parse(run_dependency_services("list"))["dependencies"]
76
+ DependencyServicesResult.list_from_json(run_dependency_services("list"))
62
77
  end
63
78
 
64
79
  sig { params(dependency: Dependabot::Dependency).returns(String) }
65
80
  def repository_url(dependency)
66
- RequirementSource.new(dependency.requirements.first).description_string("url") || options[:pub_hosted_url] ||
67
- "https://pub.dev"
81
+ repository_url = RequirementSource.new(dependency.requirements.first).description_string("url") ||
82
+ option_string(:pub_hosted_url) || "https://pub.dev"
83
+ repository_url.delete_suffix("/")
68
84
  end
69
85
 
70
- sig { params(dependency: Dependabot::Dependency).returns(T::Hash[String, T.untyped]) }
86
+ sig { params(dependency: Dependabot::Dependency).returns(Package::RegistryPackage) }
71
87
  def fetch_package_listing(dependency)
72
88
  # Because we get the security_advisories as a set of constraints, we
73
89
  # fetch the list of all versions and filter them to a list of vulnerable
@@ -76,17 +92,15 @@ module Dependabot
76
92
  # Ideally we would like the helper to be the only one doing requests to
77
93
  # the repository. But this should work for now:
78
94
  response = Dependabot::RegistryClient.get(url: "#{repository_url(dependency)}/api/packages/#{dependency.name}")
79
- JSON.parse(response.body)
95
+ Package::RegistryPackage.from_json(response.body)
80
96
  end
81
97
 
82
98
  sig { params(dependency: Dependabot::Dependency).returns(T::Array[Dependabot::Pub::Version]) }
83
99
  def available_versions(dependency)
84
- fetch_package_listing(dependency)["versions"].map do |v|
85
- Dependabot::Pub::Version.new(v["version"])
86
- end
100
+ fetch_package_listing(dependency).versions
87
101
  end
88
102
 
89
- sig { returns(T::Array[T::Hash[String, T.untyped]]) }
103
+ sig { returns(T::Array[DependencyServicesResult::ReportEntry]) }
90
104
  def dependency_services_report
91
105
  sha256 = Digest::SHA256.new
92
106
  dependency_files.each do |f|
@@ -95,11 +109,11 @@ module Dependabot
95
109
  hash = sha256.hexdigest
96
110
 
97
111
  cache_file = "/tmp/report-#{hash}-pid-#{Process.pid}.json"
98
- return JSON.parse(File.read(cache_file)) if File.file?(cache_file)
112
+ return DependencyServicesResult.report_from_cache(File.read(cache_file)).dependencies if File.file?(cache_file)
99
113
 
100
- report = JSON.parse(run_dependency_services("report"))["dependencies"]
101
- File.write(cache_file, JSON.generate(report))
102
- report
114
+ report = DependencyServicesResult.report_from_json(run_dependency_services("report"))
115
+ File.write(cache_file, report.cache_content)
116
+ report.dependencies
103
117
  end
104
118
 
105
119
  sig do
@@ -176,19 +190,19 @@ module Dependabot
176
190
  ## Detects the right flutter release to use for the pubspec.yaml.
177
191
  ## Then checks it out if it is not already.
178
192
  ## Returns the sdk versions
179
- sig { params(dir: T.any(Pathname, String)).returns(T::Hash[String, String]) }
193
+ sig { params(dir: T.any(Pathname, String)).returns(SdkVersions) }
180
194
  def ensure_right_flutter_release(dir)
181
195
  versions = Helpers.run_infer_sdk_versions(
182
- File.join(dir, dependency_files.first&.directory),
183
- url: options[:flutter_releases_url]
196
+ File.join(dir, T.must(dependency_files.first).directory),
197
+ url: option_string(:flutter_releases_url)
184
198
  )
185
199
  flutter_ref =
186
200
  if versions
187
201
  Dependabot.logger.info(
188
- "Installing the Flutter SDK version: #{versions['flutter']} " \
189
- "from channel #{versions['channel']} with Dart #{versions['dart']}"
202
+ "Installing the Flutter SDK version: #{versions.flutter} " \
203
+ "from channel #{versions.channel} with Dart #{versions.dart}"
190
204
  )
191
- "refs/tags/#{versions['flutter']}"
205
+ "refs/tags/#{versions.flutter}"
192
206
  else
193
207
  Dependabot.logger.info(
194
208
  "Failed to infer the flutter version. Attempting to use latest stable release."
@@ -217,7 +231,7 @@ module Dependabot
217
231
  end
218
232
 
219
233
  # Runs `flutter version` and returns the dart and flutter version numbers in a map.
220
- sig { returns(T::Hash[String, String]) }
234
+ sig { returns(SdkVersions) }
221
235
  def run_flutter_version
222
236
  Dependabot.logger.info "Running `flutter --version`"
223
237
  # Run `flutter --version --machine` to get the current flutter version.
@@ -233,20 +247,18 @@ module Dependabot
233
247
  "Running 'flutter --version --machine' failed: #{stderr}"
234
248
  end
235
249
 
236
- parsed = JSON.parse(stdout)
237
- flutter_version = parsed["frameworkVersion"]
238
- dart_version = parsed["dartSdkVersion"]&.split&.first
239
- unless flutter_version && dart_version
240
- raise Dependabot::DependabotError,
241
- "Bad output from `flutter --version`: #{stdout}"
242
- end
250
+ context = "flutter --version"
251
+ parsed = JsonValueParser.object(JsonValueParser.parse(stdout, context), context)
252
+ flutter_version = JsonValueParser.string(parsed["frameworkVersion"], "#{context}.frameworkVersion")
253
+ dart_version = JsonValueParser.string(parsed["dartSdkVersion"], "#{context}.dartSdkVersion").split.first
254
+ raise JsonValueParser::InvalidValue, "#{context}.dartSdkVersion must contain a version" unless dart_version
255
+
243
256
  Dependabot.logger.info(
244
257
  "Installed the Flutter SDK version: #{flutter_version} with Dart #{dart_version}."
245
258
  )
246
- {
247
- "flutter" => flutter_version,
248
- "dart" => dart_version
249
- }
259
+ SdkVersions.new(flutter: flutter_version, dart: dart_version)
260
+ rescue JsonValueParser::InvalidValue => e
261
+ DependencyServicesResult.invalid_result("flutter --version", e.message)
250
262
  end
251
263
 
252
264
  sig do
@@ -272,12 +284,12 @@ module Dependabot
272
284
  "PUB_ENVIRONMENT" => "dependabot",
273
285
  "FLUTTER_ROOT" => "/tmp/flutter",
274
286
  "DART_ROOT" => "/tmp/flutter/bin/cache/dart-sdk",
275
- "PUB_HOSTED_URL" => options[:pub_hosted_url],
287
+ "PUB_HOSTED_URL" => option_string(:pub_hosted_url),
276
288
  # This variable will make the solver run assuming that Dart SDK version.
277
289
  # TODO(sigurdm): Would be nice to have a better handle for fixing the dart sdk version.
278
- "_PUB_TEST_SDK_VERSION" => sdk_versions["dart"]
290
+ "_PUB_TEST_SDK_VERSION" => sdk_versions.dart
279
291
  }
280
- command_dir = File.join(temp_dir, dependency_files.first&.directory)
292
+ command_dir = File.join(temp_dir, T.must(dependency_files.first).directory)
281
293
 
282
294
  stdout, stderr, status = Open3.capture3(
283
295
  env.compact,
@@ -310,25 +322,19 @@ module Dependabot
310
322
  end
311
323
 
312
324
  # Parses a dependency as listed by `dependency_services list`.
313
- sig { params(json: T::Hash[String, T.untyped]).returns(Dependabot::Dependency) }
314
- def parse_listed_dependency(json)
315
- params = {
316
- name: json["name"],
317
- version: json["version"],
318
- package_manager: "pub",
319
- requirements: []
320
- }
325
+ sig { params(entry: DependencyServicesResult::ListedDependency).returns(Dependabot::Dependency) }
326
+ def parse_listed_dependency(entry)
327
+ requirements = []
321
328
 
322
- if json["kind"] != "transitive" && !json["constraint"].nil?
323
- constraint = json["constraint"]
324
- params[:requirements] << {
325
- requirement: constraint,
326
- groups: [json["kind"]],
327
- source: json["source"],
329
+ if entry.kind != "transitive" && !entry.constraint.nil?
330
+ requirements << {
331
+ requirement: entry.constraint,
332
+ groups: [entry.kind],
333
+ source: entry.source,
328
334
  file: "pubspec.yaml"
329
335
  }
330
336
  end
331
- Dependency.new(**params)
337
+ Dependency.new(name: entry.name, version: entry.version, package_manager: "pub", requirements: requirements)
332
338
  end
333
339
 
334
340
  # Parses the updated dependencies returned by
@@ -338,43 +344,41 @@ module Dependabot
338
344
  # used to chose the right updated constraint.
339
345
  sig do
340
346
  params(
341
- json: T::Hash[String, T.untyped],
347
+ entry: DependencyServicesResult::DependencyUpdate,
342
348
  requirements_update_strategy: Dependabot::RequirementsUpdateStrategy
343
349
  )
344
350
  .returns(Dependabot::Dependency)
345
351
  end
346
- def parse_updated_dependency(json, requirements_update_strategy)
352
+ def parse_updated_dependency(entry, requirements_update_strategy)
347
353
  requirements = []
348
- constraint_field = constraint_field_from_update_strategy(requirements_update_strategy)
354
+ constraint = constraint_from_update_strategy(entry, requirements_update_strategy)
349
355
 
350
- if json["kind"] != "transitive" && !json[constraint_field].nil?
351
- constraint = json[constraint_field]
356
+ if entry.kind != "transitive" && !constraint.nil?
352
357
  requirements << {
353
358
  requirement: constraint,
354
- groups: [json["kind"]],
359
+ groups: [entry.kind],
355
360
  source: nil, # TODO: Expose some information about the source
356
361
  file: "pubspec.yaml"
357
362
  }
358
363
  end
359
364
 
360
365
  previous_requirements = []
361
- if json["previousVersion"] && json["kind"] != "transitive" && !json["previousConstraint"].nil?
362
- constraint = json["previousConstraint"]
366
+ if entry.previous_version && entry.kind != "transitive" && !entry.previous_constraint.nil?
363
367
  previous_requirements << {
364
- requirement: constraint,
365
- groups: [json["kind"]],
368
+ requirement: entry.previous_constraint,
369
+ groups: [entry.kind],
366
370
  source: nil, # TODO: Expose some information about the source
367
371
  file: "pubspec.yaml"
368
372
  }
369
373
  end
370
374
 
371
375
  Dependency.new(
372
- name: json["name"],
373
- version: json["version"],
376
+ name: entry.name,
377
+ version: entry.version,
374
378
  package_manager: "pub",
375
379
  requirements: requirements,
376
- previous_version: json["previousVersion"],
377
- previous_requirements: json["previousVersion"] ? previous_requirements : nil
380
+ previous_version: entry.previous_version,
381
+ previous_requirements: entry.previous_version ? previous_requirements : nil
378
382
  )
379
383
  end
380
384
 
@@ -382,23 +386,35 @@ module Dependabot
382
386
  # strategies.
383
387
  sig do
384
388
  params(
389
+ entry: DependencyServicesResult::DependencyUpdate,
385
390
  requirements_update_strategy: Dependabot::RequirementsUpdateStrategy
386
391
  )
387
- .returns(String)
392
+ .returns(T.nilable(String))
388
393
  end
389
- def constraint_field_from_update_strategy(requirements_update_strategy)
394
+ def constraint_from_update_strategy(entry, requirements_update_strategy)
390
395
  case requirements_update_strategy
391
396
  when RequirementsUpdateStrategy::WidenRanges
392
- "constraintWidened"
397
+ entry.constraint_widened
393
398
  when RequirementsUpdateStrategy::BumpVersions
394
- "constraintBumped"
399
+ entry.constraint_bumped
395
400
  when RequirementsUpdateStrategy::BumpVersionsIfNecessary
396
- "constraintBumpedIfNeeded"
401
+ entry.constraint_bumped_if_needed
397
402
  else
398
403
  raise "Unexpected requirements_update_strategy #{requirements_update_strategy}"
399
404
  end
400
405
  end
401
406
 
407
+ sig { params(key: Symbol).returns(T.nilable(String)) }
408
+ def option_string(key)
409
+ value = options[key]
410
+
411
+ case value
412
+ when String then value
413
+ when nil then nil
414
+ else raise TypeError, "Pub option #{key} must be a string or nil"
415
+ end
416
+ end
417
+
402
418
  sig do
403
419
  params(
404
420
  dependencies: T.nilable(T::Array[Dependabot::Dependency])
@@ -406,29 +422,19 @@ module Dependabot
406
422
  .returns(T.nilable(String))
407
423
  end
408
424
  def dependencies_to_json(dependencies)
409
- if dependencies.nil?
410
- nil
411
- else
412
- deps = dependencies.map do |d|
413
- source = d.requirements.empty? ? nil : d.requirements.first&.[](:source)
414
- obj = {
415
- "name" => d.name,
416
- "version" => d.version,
417
- "source" => source
418
- }
419
-
420
- unless d.requirements.nil? || d.requirements.empty?
421
- obj["constraint"] =
422
- d.requirements[0]&.[](:requirement).to_s
423
- end
424
- obj
425
- end
426
- JSON.generate(
427
- {
428
- "dependencyChanges" => deps
429
- }
430
- )
425
+ return if dependencies.nil?
426
+
427
+ changes = dependencies.map do |dependency|
428
+ requirement = dependency.requirements.first
429
+ fields = {
430
+ "name" => dependency.name,
431
+ "version" => dependency.version,
432
+ "source" => requirement&.source
433
+ }
434
+ fields["constraint"] = requirement.requirement.to_s if requirement
435
+ fields
431
436
  end
437
+ JSON.generate("dependencyChanges" => changes)
432
438
  end
433
439
  end
434
440
  end
@@ -0,0 +1,65 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "sorbet-runtime"
6
+
7
+ module Dependabot
8
+ module Pub
9
+ module JsonValueParser
10
+ extend T::Sig
11
+
12
+ class InvalidValue < TypeError; end
13
+
14
+ ObjectHash = T.type_alias { T::Hash[String, Object] }
15
+
16
+ sig { params(content: String, context: String).returns(Object) }
17
+ def self.parse(content, context)
18
+ T.cast(JSON.parse(content), Object)
19
+ rescue JSON::ParserError
20
+ raise InvalidValue.new("#{context} must be valid JSON"), cause: nil
21
+ end
22
+
23
+ sig { params(value: Object, context: String).returns(ObjectHash) }
24
+ def self.object(value, context)
25
+ raise InvalidValue, "#{context} must be an object" unless value.is_a?(Hash)
26
+
27
+ value.to_h do |raw_key, raw_value|
28
+ key = T.cast(raw_key, Object)
29
+ raise InvalidValue, "#{context} keys must be strings" unless key.is_a?(String)
30
+
31
+ [key, T.cast(raw_value, Object)]
32
+ end
33
+ end
34
+
35
+ sig { params(value: Object, context: String).returns(T.nilable(ObjectHash)) }
36
+ def self.optional_object(value, context)
37
+ return if value.nil?
38
+
39
+ object(value, context)
40
+ end
41
+
42
+ sig { params(value: Object, context: String).returns(T::Array[Object]) }
43
+ def self.array(value, context)
44
+ raise InvalidValue, "#{context} must be an array" unless value.is_a?(Array)
45
+
46
+ value.map { |entry| T.cast(entry, Object) }
47
+ end
48
+
49
+ sig { params(value: Object, context: String).returns(String) }
50
+ def self.string(value, context)
51
+ return value if value.is_a?(String)
52
+
53
+ raise InvalidValue, "#{context} must be a string"
54
+ end
55
+
56
+ sig { params(value: Object, context: String).returns(T.nilable(String)) }
57
+ def self.optional_string(value, context)
58
+ return if value.nil?
59
+ return value if value.is_a?(String)
60
+
61
+ raise InvalidValue, "#{context} must be a string or nil"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
@@ -6,6 +6,7 @@ require "sorbet-runtime"
6
6
  require "dependabot/metadata_finders"
7
7
  require "dependabot/metadata_finders/base"
8
8
  require "dependabot/pub/requirement_source"
9
+ require "dependabot/pub/package/registry_package"
9
10
  require "dependabot/registry_client"
10
11
 
11
12
  module Dependabot
@@ -24,22 +25,18 @@ module Dependabot
24
25
  result.commit = source.description_string("resolved-ref")
25
26
  return result
26
27
  end
27
- repository_url = source.description_string("url") || "https://pub.dev"
28
+ repository_url = (source.description_string("url") || "https://pub.dev").delete_suffix("/")
28
29
 
29
- listing = repository_listing(repository_url)
30
- repo = listing.dig("latest", "pubspec", "repository")
31
- # The repository field did not always exist in pubspec.yaml, and some
32
- # packages specify a git repository in the "homepage" field.
33
- repo ||= listing.dig("latest", "pubspec", "homepage")
30
+ repo = repository_listing(repository_url).source_url
34
31
  return nil unless repo
35
32
 
36
33
  Source.from_url(repo)
37
34
  end
38
35
 
39
- sig { params(repository_url: String).returns(T::Hash[String, T.untyped]) }
36
+ sig { params(repository_url: String).returns(Package::RegistryPackage) }
40
37
  def repository_listing(repository_url)
41
38
  response = Dependabot::RegistryClient.get(url: "#{repository_url}/api/packages/#{dependency.name}")
42
- JSON.parse(response.body)
39
+ Package::RegistryPackage.from_json(response.body)
43
40
  end
44
41
  end
45
42
  end
@@ -1,8 +1,6 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "json"
5
- require "time"
6
4
  require "excon"
7
5
  require "nokogiri"
8
6
  require "sorbet-runtime"
@@ -29,7 +27,7 @@ module Dependabot
29
27
  sig { override.returns(T::Array[Dependabot::DependencyFile]) }
30
28
  attr_reader :dependency_files
31
29
 
32
- sig { override.returns(T::Hash[Symbol, T.untyped]) }
30
+ sig { override.returns(T::Hash[Symbol, T.anything]) }
33
31
  attr_reader :options
34
32
 
35
33
  sig { override.returns(T::Array[Dependabot::Credential]) }
@@ -42,7 +40,7 @@ module Dependabot
42
40
  credentials: T::Array[Dependabot::Credential],
43
41
  ignored_versions: T::Array[String],
44
42
  security_advisories: T::Array[Dependabot::SecurityAdvisory],
45
- options: T::Hash[Symbol, T.untyped]
43
+ options: T::Hash[Symbol, T.anything]
46
44
  )
47
45
  .void
48
46
  end
@@ -62,57 +60,32 @@ module Dependabot
62
60
  @options = options
63
61
  end
64
62
 
65
- sig { returns(T::Array[T::Hash[String, T.untyped]]) }
63
+ sig { returns(T::Array[DependencyServicesResult::ReportEntry]) }
66
64
  def report
67
65
  @report ||= T.let(
68
66
  dependency_services_report,
69
- T.nilable(T::Array[T::Hash[String, T.untyped]])
67
+ T.nilable(T::Array[DependencyServicesResult::ReportEntry])
70
68
  )
71
69
  end
72
70
 
73
- sig { returns(T.any(T::Array[Dependabot::Package::PackageRelease], T.untyped)) }
71
+ sig { returns(T::Array[Dependabot::Package::PackageRelease]) }
74
72
  def package_details_metadata
75
- package_releases = []
76
- T.let({}, T::Hash[String, T.untyped])
77
-
78
- Dependabot.logger.error("Initializing package metadata for \"#{@dependency.name}\"")
73
+ Dependabot.logger.info("Initializing package metadata for \"#{@dependency.name}\"")
79
74
 
80
75
  response = fetch_package_metadata(dependency)
81
- return package_releases if response.status >= 500
82
-
83
- begin
84
- package_details_metadata = JSON.parse(response.body)
85
-
86
- package_details_metadata["versions"].select do |v|
87
- package_releases << package_release(
88
- version: v["version"],
89
- publish_date: Time.parse(v["published"])
90
- )
91
- end
92
-
93
- package_releases
94
- rescue JSON::ParserError
95
- Dependabot.logger.error("Failed to parse package metadata")
96
- package_releases
97
- end
76
+ return [] if response.status >= 500
77
+
78
+ # Build the full list up front. If any release can't be parsed (e.g. a
79
+ # missing or invalid publish date) the partial result is discarded, so
80
+ # callers can treat a non-empty list as a complete, trustworthy snapshot
81
+ # and an empty list as "no usable metadata".
82
+ RegistryPackage.from_json(response.body).releases
83
+ rescue JsonValueParser::InvalidValue
84
+ Dependabot.logger.error("Failed to parse package metadata")
85
+ []
98
86
  rescue StandardError => e
99
87
  Dependabot.logger.error("Failed to fetch package metadata #{e.message}")
100
- package_releases
101
- end
102
-
103
- private
104
-
105
- sig do
106
- params(
107
- version: String,
108
- publish_date: T.nilable(Time)
109
- ).returns(Dependabot::Package::PackageRelease)
110
- end
111
- def package_release(version:, publish_date: nil)
112
- Dependabot::Package::PackageRelease.new(
113
- version: Pub::Version.new(version),
114
- released_at: publish_date
115
- )
88
+ []
116
89
  end
117
90
  end
118
91
  end
@@ -0,0 +1,67 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "time"
5
+ require "sorbet-runtime"
6
+ require "dependabot/package/package_release"
7
+ require "dependabot/pub/json_value_parser"
8
+ require "dependabot/pub/version"
9
+
10
+ module Dependabot
11
+ module Pub
12
+ module Package
13
+ class RegistryPackage
14
+ extend T::Sig
15
+
16
+ sig { params(content: String).returns(RegistryPackage) }
17
+ def self.from_json(content)
18
+ new(JsonValueParser.object(JsonValueParser.parse(content, "Pub package"), "Pub package"))
19
+ end
20
+
21
+ sig { params(data: JsonValueParser::ObjectHash).void }
22
+ def initialize(data)
23
+ @data = data
24
+ end
25
+
26
+ sig { returns(T::Array[Dependabot::Pub::Version]) }
27
+ def versions
28
+ version_entries(@data["versions"]).map do |fields|
29
+ Dependabot::Pub::Version.new(JsonValueParser.string(fields["version"], "Pub package version"))
30
+ end
31
+ end
32
+
33
+ sig { returns(T::Array[Dependabot::Package::PackageRelease]) }
34
+ def releases
35
+ version_entries(@data.fetch("versions", [])).map do |fields|
36
+ Dependabot::Package::PackageRelease.new(
37
+ version: Dependabot::Pub::Version.new(JsonValueParser.string(fields["version"], "Pub package version")),
38
+ released_at: Time.parse(JsonValueParser.string(fields["published"], "Pub package published"))
39
+ )
40
+ end
41
+ end
42
+
43
+ sig { returns(T.nilable(String)) }
44
+ def source_url
45
+ latest = JsonValueParser.optional_object(@data["latest"], "Pub package latest")
46
+ return unless latest
47
+
48
+ pubspec = JsonValueParser.optional_object(latest["pubspec"], "Pub package latest.pubspec")
49
+ return unless pubspec
50
+
51
+ value = pubspec["repository"] || pubspec["homepage"]
52
+ # Preserve the existing false-as-absent behavior of optional source metadata.
53
+ JsonValueParser.optional_string(value || nil, "Pub package repository or homepage")
54
+ end
55
+
56
+ private
57
+
58
+ sig { params(value: Object).returns(T::Array[JsonValueParser::ObjectHash]) }
59
+ def version_entries(value)
60
+ JsonValueParser.array(value, "Pub package versions").each_with_index.map do |entry, index|
61
+ JsonValueParser.object(entry, "Pub package versions[#{index}]")
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
@@ -7,6 +7,7 @@ require "dependabot/update_checkers/version_filters"
7
7
  require "dependabot/registry_client"
8
8
  require "dependabot/pub/package/package_details_fetcher"
9
9
  require "dependabot/package/package_latest_version_finder"
10
+ require "dependabot/update_checkers/cooldown_calculation"
10
11
  require "sorbet-runtime"
11
12
 
12
13
  module Dependabot
@@ -26,7 +27,7 @@ module Dependabot
26
27
  credentials: T::Array[Dependabot::Credential],
27
28
  ignored_versions: T::Array[String],
28
29
  security_advisories: T::Array[Dependabot::SecurityAdvisory],
29
- options: T::Hash[Symbol, T.untyped],
30
+ options: T::Hash[Symbol, T.anything],
30
31
  cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
31
32
  ).void
32
33
  end
@@ -48,10 +49,10 @@ module Dependabot
48
49
  @cooldown_options = cooldown_options
49
50
  end
50
51
 
51
- sig { returns(T::Hash[String, T.untyped]) }
52
+ sig { returns(DependencyServicesResult::ReportEntry) }
52
53
  def current_report
53
54
  @current_report ||= T.let(
54
- T.must(
55
+ DependencyServicesResult.find_report(
55
56
  PackageDetailsFetcher.new(
56
57
  dependency: dependency,
57
58
  dependency_files: dependency_files,
@@ -59,15 +60,16 @@ module Dependabot
59
60
  ignored_versions: ignored_versions,
60
61
  security_advisories: security_advisories,
61
62
  options: options
62
- ).report.find { |d| d["name"] == dependency.name }
63
+ ).report,
64
+ dependency.name
63
65
  ),
64
- T.nilable(T::Hash[String, T.untyped])
66
+ T.nilable(DependencyServicesResult::ReportEntry)
65
67
  )
66
68
  end
67
69
 
68
70
  sig { returns(T.nilable(String)) }
69
71
  def latest_version
70
- latest_version = current_report["latest"]
72
+ latest_version = current_report.latest
71
73
  return nil unless latest_version
72
74
 
73
75
  filter_cooldown_versions(latest_version)
@@ -75,36 +77,36 @@ module Dependabot
75
77
 
76
78
  sig { returns(T.nilable(String)) }
77
79
  def latest_resolvable_version
78
- latest_resolvable_version = current_report["singleBreaking"]&.find { |d| d["name"] == dependency.name }
79
- return nil unless latest_resolvable_version
80
+ version = latest_resolvable_update&.version
81
+ return nil unless version
80
82
 
81
- filter_cooldown_versions(latest_resolvable_version["version"])
83
+ filter_cooldown_versions(version)
82
84
  end
83
85
 
84
86
  sig { returns(T.nilable(String)) }
85
87
  def latest_resolvable_version_with_no_unlock
86
- version_with_no_unlock = current_report["compatible"]&.find { |d| d["name"] == dependency.name }
87
- return nil unless version_with_no_unlock
88
+ version = current_report.compatible.find { |entry| entry.name == dependency.name }&.version
89
+ return nil unless version
88
90
 
89
- filter_cooldown_versions(version_with_no_unlock["version"])
91
+ filter_cooldown_versions(version)
90
92
  end
91
93
 
92
94
  sig { returns(T.nilable(String)) }
93
95
  def latest_version_resolvable_with_full_unlock
94
- version_with_full_unlock = current_report["multiBreaking"]&.find { |d| d["name"] == dependency.name }
95
- return nil unless version_with_full_unlock
96
+ version = full_unlock_updates.find { |entry| entry.name == dependency.name }&.version
97
+ return nil unless version
96
98
 
97
- filter_cooldown_versions(version_with_full_unlock["version"])
99
+ filter_cooldown_versions(version)
98
100
  end
99
101
 
100
- sig { returns(T.untyped) }
101
- def latest_version_resolvable_with_full_unlock_hash
102
- current_report["multiBreaking"]
102
+ sig { returns(T::Array[DependencyServicesResult::DependencyUpdate]) }
103
+ def full_unlock_updates
104
+ current_report.multi_breaking
103
105
  end
104
106
 
105
- sig { returns(T.untyped) }
106
- def latest_resolvable_version_hash
107
- current_report["singleBreaking"].find { |d| d["name"] == dependency.name }
107
+ sig { returns(T.nilable(DependencyServicesResult::DependencyUpdate)) }
108
+ def latest_resolvable_update
109
+ current_report.single_breaking.find { |entry| entry.name == dependency.name }
108
110
  end
109
111
 
110
112
  private
@@ -119,6 +121,47 @@ module Dependabot
119
121
  return unparsed_version unless cooldown_options
120
122
 
121
123
  @package_details ||= T.let(
124
+ package_details_fetcher.package_details_metadata,
125
+ T.nilable(T::Array[Dependabot::Package::PackageRelease])
126
+ )
127
+
128
+ version_release = @package_details.find do |release|
129
+ release.version.to_s == unparsed_version
130
+ end
131
+
132
+ if version_release&.released_at
133
+ return unparsed_version unless in_cooldown_period?(version_release)
134
+
135
+ return dependency.version
136
+ end
137
+
138
+ # No dated release for this candidate. A Pub release always carries a
139
+ # publication date, so a candidate is only missing here for one of two
140
+ # reasons, handled differently:
141
+ #
142
+ # metadata present, candidate absent -> the date source is healthy and the
143
+ # candidate simply isn't published on pub.dev (the resolver and registry
144
+ # listings diverge). Skip cooldown quietly.
145
+ #
146
+ # metadata empty -> we have no dates at all, so cooldown can't be applied.
147
+ # Surface the shared "cooldown not applied" notice, matching docker/common.
148
+ unless @package_details.empty?
149
+ Dependabot.logger.info(
150
+ "No matching release metadata for #{dependency.name} #{unparsed_version}; skipping cooldown"
151
+ )
152
+ return unparsed_version
153
+ end
154
+
155
+ record_cooldown_date_unavailable(unparsed_version)
156
+ unparsed_version
157
+ rescue StandardError => e
158
+ Dependabot.logger.error("Failed to filter cooldown versions for \"#{dependency.name}\": #{e.backtrace}")
159
+ unparsed_version
160
+ end
161
+
162
+ sig { returns(PackageDetailsFetcher) }
163
+ def package_details_fetcher
164
+ @package_details_fetcher ||= T.let(
122
165
  PackageDetailsFetcher.new(
123
166
  dependency: dependency,
124
167
  dependency_files: dependency_files,
@@ -126,27 +169,46 @@ module Dependabot
126
169
  ignored_versions: ignored_versions,
127
170
  security_advisories: security_advisories,
128
171
  options: options
129
- ).package_details_metadata,
130
- T.nilable(T::Array[Dependabot::Package::PackageRelease])
172
+ ),
173
+ T.nilable(PackageDetailsFetcher)
131
174
  )
175
+ end
132
176
 
133
- return unparsed_version unless @package_details.any?
177
+ sig { params(unparsed_version: String).void }
178
+ def record_cooldown_date_unavailable(unparsed_version)
179
+ return unless version_class.correct?(unparsed_version)
134
180
 
135
- version_release = @package_details.find do |release|
136
- release.version == unparsed_version
137
- end
181
+ new_version = version_class.new(unparsed_version)
182
+ current_version = version_class.correct?(dependency.version) ? version_class.new(dependency.version) : nil
138
183
 
139
- return unparsed_version unless in_cooldown_period?(version_release)
184
+ # Nothing to cool down when the candidate isn't an upgrade, or when it is
185
+ # ignored and would be discarded anyway; mirrors the common cooldown
186
+ # tracker's relevance check and avoids job-wide warnings when no update can
187
+ # be produced.
188
+ return if current_version && new_version <= current_version
189
+ return if ignored?(new_version)
140
190
 
141
- dependency.version
142
- rescue StandardError => e
143
- Dependabot.logger.error("Failed to filter cooldown versions for \"#{dependency.name}\": #{e.backtrace}")
144
- unparsed_version
191
+ Dependabot.logger.info(
192
+ "No publication date available for #{dependency.name} #{unparsed_version}; cooldown not applied"
193
+ )
194
+ Dependabot::UpdateCheckers::CooldownCalculation.mark_cooldown_date_unavailable(
195
+ dependency,
196
+ cooldown_days: cooldown_days_for(current_version, new_version)
197
+ )
198
+ end
199
+
200
+ sig { params(version: Dependabot::Version).returns(T::Boolean) }
201
+ def ignored?(version)
202
+ requirements = ignored_versions.flat_map do |requirement|
203
+ dependency.requirement_class.requirements_array(requirement)
204
+ end
205
+ requirements.any? { |requirement| requirement.satisfied_by?(version) }
145
206
  end
146
207
 
147
208
  sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
148
209
  def in_cooldown_period?(release)
149
- unless release.released_at
210
+ released_at = release.released_at
211
+ unless released_at
150
212
  Dependabot.logger.info("Release date not available for version #{release.version}")
151
213
  return false
152
214
  end
@@ -155,7 +217,7 @@ module Dependabot
155
217
  days = cooldown_days_for(current_version, release.version)
156
218
 
157
219
  # Calculate the number of seconds passed since the release
158
- passed_seconds = Time.now.to_i - release.released_at.to_i
220
+ passed_seconds = Time.now.to_i - released_at.to_i
159
221
  passed_days = passed_seconds / DAY_IN_SECONDS
160
222
 
161
223
  if passed_days < days
@@ -225,7 +287,7 @@ module Dependabot
225
287
  sig { returns(T::Array[Dependabot::SecurityAdvisory]) }
226
288
  attr_reader :security_advisories
227
289
 
228
- sig { returns(T::Hash[Symbol, T.untyped]) }
290
+ sig { returns(T::Hash[Symbol, T.anything]) }
229
291
  attr_reader :options
230
292
 
231
293
  sig { returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions)) }
@@ -21,7 +21,10 @@ module Dependabot
21
21
 
22
22
  sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
23
23
  def latest_version
24
- version = version_unless_ignored(T.must(version_report.latest_version), current_version: dependency.version)
24
+ candidate = version_report.latest_version
25
+ return nil unless candidate
26
+
27
+ version = version_unless_ignored(candidate, current_version: dependency.version)
25
28
 
26
29
  raise AllVersionsIgnored if version.nil? && @raise_on_ignored
27
30
 
@@ -48,10 +51,12 @@ module Dependabot
48
51
  e = dependency_services_smallest_update
49
52
  return nil if e.nil?
50
53
 
51
- upgrade = e.find { |u| u["name"] == dependency.name }
54
+ upgrade = e.find { |u| u.name == dependency.name }
55
+
56
+ version = upgrade&.version
57
+ return unless version
52
58
 
53
- version = T.must(upgrade)["version"]
54
- T.cast(version_unless_ignored(version), Dependabot::Version)
59
+ T.cast(version_unless_ignored(version), T.nilable(Dependabot::Version))
55
60
  end
56
61
 
57
62
  sig { override.returns(T.nilable(Dependabot::Version)) }
@@ -80,16 +85,16 @@ module Dependabot
80
85
 
81
86
  # Ideally we would like to do any upgrade that migrates away from the vulnerability
82
87
  # but this method can only return a single requirement udate.
83
- breaking_changes = updates&.filter { |d| d["previousConstraint"] != d["constraintBumpedIfNeeded"] }
88
+ breaking_changes = updates&.filter { |d| d.previous_constraint != d.constraint_bumped_if_needed }
84
89
 
85
90
  # This security update would require unlocking other packages, which is not currently supported.
86
91
  # Because of that, return original requirements, so that no requirements are actually updated and
87
92
  # the error bubbles up as security_update_not_possible to the user.
88
93
  return dependency.requirements if breaking_changes&.size&.> 1
89
94
 
90
- updates&.find { |u| u["name"] == dependency.name }
95
+ updates&.find { |u| u.name == dependency.name }
91
96
  else
92
- version_report.latest_resolvable_version_hash
97
+ version_report.latest_resolvable_update
93
98
  end
94
99
  return [] unless entry
95
100
 
@@ -100,7 +105,7 @@ module Dependabot
100
105
  private
101
106
 
102
107
  # rubocop:disable Metrics/AbcSize
103
- sig { returns(T.nilable(T::Array[T::Hash[String, T.untyped]])) }
108
+ sig { returns(T.nilable(T::Array[DependencyServicesResult::DependencyUpdate])) }
104
109
  def dependency_services_smallest_update
105
110
  return @smallest_update if @smallest_update
106
111
 
@@ -129,10 +134,12 @@ module Dependabot
129
134
  }
130
135
  ]
131
136
  }
132
- report = JSON.parse(run_dependency_services("report", stdin_data: JSON.generate(input)))["dependencies"]
137
+ report = DependencyServicesResult.report_from_json(
138
+ run_dependency_services("report", stdin_data: JSON.generate(input))
139
+ )
133
140
  @smallest_update = T.let(
134
- report.find { |d| d["name"] == dependency.name }["smallestUpdate"],
135
- T.nilable(T::Array[T::Hash[String, T.untyped]])
141
+ DependencyServicesResult.find_report(report.dependencies, dependency.name).smallest_update,
142
+ T.nilable(T::Array[DependencyServicesResult::DependencyUpdate])
136
143
  )
137
144
  end
138
145
  # rubocop:enable Metrics/AbcSize
@@ -184,11 +191,13 @@ module Dependabot
184
191
  report_section = if vulnerable?
185
192
  dependency_services_smallest_update
186
193
  else
187
- version_report.latest_version_resolvable_with_full_unlock_hash
194
+ version_report.full_unlock_updates
188
195
  end
196
+ return [] unless report_section
197
+
189
198
  # We only expose non-transitive dependencies here...
190
199
  direct_deps = report_section.reject do |d|
191
- d["kind"] == "transitive"
200
+ d.kind == "transitive"
192
201
  end
193
202
  direct_deps.map do |d|
194
203
  parse_updated_dependency(d, resolved_requirements_update_strategy)
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-pub
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.397.0
4
+ version: 0.399.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.397.0
18
+ version: 0.399.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.397.0
25
+ version: 0.399.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -242,13 +242,16 @@ extensions: []
242
242
  extra_rdoc_files: []
243
243
  files:
244
244
  - lib/dependabot/pub.rb
245
+ - lib/dependabot/pub/dependency_services_result.rb
245
246
  - lib/dependabot/pub/file_fetcher.rb
246
247
  - lib/dependabot/pub/file_parser.rb
247
248
  - lib/dependabot/pub/file_updater.rb
248
249
  - lib/dependabot/pub/helpers.rb
250
+ - lib/dependabot/pub/json_value_parser.rb
249
251
  - lib/dependabot/pub/language.rb
250
252
  - lib/dependabot/pub/metadata_finder.rb
251
253
  - lib/dependabot/pub/package/package_details_fetcher.rb
254
+ - lib/dependabot/pub/package/registry_package.rb
252
255
  - lib/dependabot/pub/package_manager.rb
253
256
  - lib/dependabot/pub/requirement.rb
254
257
  - lib/dependabot/pub/requirement_source.rb
@@ -260,7 +263,7 @@ licenses:
260
263
  - MIT
261
264
  metadata:
262
265
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
263
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.397.0
266
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.399.0
264
267
  rdoc_options: []
265
268
  require_paths:
266
269
  - lib