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 +4 -4
- data/lib/dependabot/pub/dependency_services_result.rb +167 -0
- data/lib/dependabot/pub/file_parser.rb +2 -2
- data/lib/dependabot/pub/helpers.rb +101 -95
- data/lib/dependabot/pub/json_value_parser.rb +65 -0
- data/lib/dependabot/pub/metadata_finder.rb +6 -9
- data/lib/dependabot/pub/package/package_details_fetcher.rb +18 -45
- data/lib/dependabot/pub/package/registry_package.rb +67 -0
- data/lib/dependabot/pub/update_checker/latest_version_finder.rb +98 -36
- data/lib/dependabot/pub/update_checker.rb +22 -13
- data/lib/dependabot/pub/version.rb +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bccb4cb21417d15087e804ed056b2ff00451cf1c908fd8cdfb40f7709fd02ce8
|
|
4
|
+
data.tar.gz: 685f8a0c5df291c2be68371b3c798343e61f23cd17ea5745efc879315dd3e178
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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[
|
|
85
|
+
sig { returns(T::Array[DependencyServicesResult::ListedDependency]) }
|
|
86
86
|
def list
|
|
87
|
-
@list ||= T.let(dependency_services_list, T.nilable(T::Array[
|
|
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:
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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[
|
|
74
|
+
sig { returns(T::Array[DependencyServicesResult::ListedDependency]) }
|
|
60
75
|
def dependency_services_list
|
|
61
|
-
|
|
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") ||
|
|
67
|
-
|
|
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(
|
|
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
|
-
|
|
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)
|
|
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[
|
|
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
|
|
112
|
+
return DependencyServicesResult.report_from_cache(File.read(cache_file)).dependencies if File.file?(cache_file)
|
|
99
113
|
|
|
100
|
-
report =
|
|
101
|
-
File.write(cache_file,
|
|
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(
|
|
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
|
|
183
|
-
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
|
|
189
|
-
"from channel #{versions
|
|
202
|
+
"Installing the Flutter SDK version: #{versions.flutter} " \
|
|
203
|
+
"from channel #{versions.channel} with Dart #{versions.dart}"
|
|
190
204
|
)
|
|
191
|
-
"refs/tags/#{versions
|
|
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(
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
248
|
-
|
|
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" =>
|
|
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
|
|
290
|
+
"_PUB_TEST_SDK_VERSION" => sdk_versions.dart
|
|
279
291
|
}
|
|
280
|
-
command_dir = File.join(temp_dir, dependency_files.first
|
|
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(
|
|
314
|
-
def parse_listed_dependency(
|
|
315
|
-
|
|
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
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
352
|
+
def parse_updated_dependency(entry, requirements_update_strategy)
|
|
347
353
|
requirements = []
|
|
348
|
-
|
|
354
|
+
constraint = constraint_from_update_strategy(entry, requirements_update_strategy)
|
|
349
355
|
|
|
350
|
-
if
|
|
351
|
-
constraint = json[constraint_field]
|
|
356
|
+
if entry.kind != "transitive" && !constraint.nil?
|
|
352
357
|
requirements << {
|
|
353
358
|
requirement: constraint,
|
|
354
|
-
groups: [
|
|
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
|
|
362
|
-
constraint = json["previousConstraint"]
|
|
366
|
+
if entry.previous_version && entry.kind != "transitive" && !entry.previous_constraint.nil?
|
|
363
367
|
previous_requirements << {
|
|
364
|
-
requirement:
|
|
365
|
-
groups: [
|
|
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:
|
|
373
|
-
version:
|
|
376
|
+
name: entry.name,
|
|
377
|
+
version: entry.version,
|
|
374
378
|
package_manager: "pub",
|
|
375
379
|
requirements: requirements,
|
|
376
|
-
previous_version:
|
|
377
|
-
previous_requirements:
|
|
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
|
|
394
|
+
def constraint_from_update_strategy(entry, requirements_update_strategy)
|
|
390
395
|
case requirements_update_strategy
|
|
391
396
|
when RequirementsUpdateStrategy::WidenRanges
|
|
392
|
-
|
|
397
|
+
entry.constraint_widened
|
|
393
398
|
when RequirementsUpdateStrategy::BumpVersions
|
|
394
|
-
|
|
399
|
+
entry.constraint_bumped
|
|
395
400
|
when RequirementsUpdateStrategy::BumpVersionsIfNecessary
|
|
396
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
39
|
+
Package::RegistryPackage.from_json(response.body)
|
|
43
40
|
end
|
|
44
41
|
end
|
|
45
42
|
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
# typed:
|
|
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.
|
|
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.
|
|
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[
|
|
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[
|
|
67
|
+
T.nilable(T::Array[DependencyServicesResult::ReportEntry])
|
|
70
68
|
)
|
|
71
69
|
end
|
|
72
70
|
|
|
73
|
-
sig { returns(T
|
|
71
|
+
sig { returns(T::Array[Dependabot::Package::PackageRelease]) }
|
|
74
72
|
def package_details_metadata
|
|
75
|
-
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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(
|
|
52
|
+
sig { returns(DependencyServicesResult::ReportEntry) }
|
|
52
53
|
def current_report
|
|
53
54
|
@current_report ||= T.let(
|
|
54
|
-
|
|
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
|
|
63
|
+
).report,
|
|
64
|
+
dependency.name
|
|
63
65
|
),
|
|
64
|
-
T.nilable(
|
|
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
|
|
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
|
-
|
|
79
|
-
return nil unless
|
|
80
|
+
version = latest_resolvable_update&.version
|
|
81
|
+
return nil unless version
|
|
80
82
|
|
|
81
|
-
filter_cooldown_versions(
|
|
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
|
-
|
|
87
|
-
return nil unless
|
|
88
|
+
version = current_report.compatible.find { |entry| entry.name == dependency.name }&.version
|
|
89
|
+
return nil unless version
|
|
88
90
|
|
|
89
|
-
filter_cooldown_versions(
|
|
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
|
-
|
|
95
|
-
return nil unless
|
|
96
|
+
version = full_unlock_updates.find { |entry| entry.name == dependency.name }&.version
|
|
97
|
+
return nil unless version
|
|
96
98
|
|
|
97
|
-
filter_cooldown_versions(
|
|
99
|
+
filter_cooldown_versions(version)
|
|
98
100
|
end
|
|
99
101
|
|
|
100
|
-
sig { returns(T
|
|
101
|
-
def
|
|
102
|
-
current_report
|
|
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.
|
|
106
|
-
def
|
|
107
|
-
current_report
|
|
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
|
-
)
|
|
130
|
-
T.nilable(
|
|
172
|
+
),
|
|
173
|
+
T.nilable(PackageDetailsFetcher)
|
|
131
174
|
)
|
|
175
|
+
end
|
|
132
176
|
|
|
133
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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 -
|
|
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.
|
|
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
|
-
|
|
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
|
|
54
|
+
upgrade = e.find { |u| u.name == dependency.name }
|
|
55
|
+
|
|
56
|
+
version = upgrade&.version
|
|
57
|
+
return unless version
|
|
52
58
|
|
|
53
|
-
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
|
|
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
|
|
95
|
+
updates&.find { |u| u.name == dependency.name }
|
|
91
96
|
else
|
|
92
|
-
version_report.
|
|
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[
|
|
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 =
|
|
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.
|
|
135
|
-
T.nilable(T::Array[
|
|
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.
|
|
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
|
|
200
|
+
d.kind == "transitive"
|
|
192
201
|
end
|
|
193
202
|
direct_deps.map do |d|
|
|
194
203
|
parse_updated_dependency(d, resolved_requirements_update_strategy)
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|