dependabot-common 0.392.0 → 0.394.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/clients/azure.rb +42 -22
  3. data/lib/dependabot/clients/bitbucket.rb +52 -26
  4. data/lib/dependabot/clients/bitbucket_with_retries.rb +11 -12
  5. data/lib/dependabot/clients/codecommit.rb +73 -53
  6. data/lib/dependabot/clients/github_resource_parser.rb +70 -0
  7. data/lib/dependabot/clients/github_with_retries.rb +63 -23
  8. data/lib/dependabot/clients/gitlab_with_retries.rb +19 -17
  9. data/lib/dependabot/clients/json_response_parser.rb +103 -0
  10. data/lib/dependabot/command_helpers.rb +79 -11
  11. data/lib/dependabot/credential.rb +12 -3
  12. data/lib/dependabot/errors.rb +4 -1
  13. data/lib/dependabot/file_fetchers/base.rb +199 -151
  14. data/lib/dependabot/file_filtering.rb +0 -2
  15. data/lib/dependabot/file_updaters/vendor_updater.rb +8 -2
  16. data/lib/dependabot/git_tag_details.rb +8 -14
  17. data/lib/dependabot/metadata_finders/base/changelog_finder.rb +211 -110
  18. data/lib/dependabot/metadata_finders/base/commit_response_parser.rb +68 -0
  19. data/lib/dependabot/metadata_finders/base/commits_finder.rb +44 -38
  20. data/lib/dependabot/package/npm_package_manager_config.rb +72 -0
  21. data/lib/dependabot/package/npm_registry_package.rb +186 -0
  22. data/lib/dependabot/pull_request_creator/azure.rb +1 -1
  23. data/lib/dependabot/pull_request_creator/codecommit.rb +10 -12
  24. data/lib/dependabot/pull_request_creator/github.rb +55 -35
  25. data/lib/dependabot/pull_request_creator/gitlab.rb +37 -11
  26. data/lib/dependabot/pull_request_creator/labeler.rb +65 -15
  27. data/lib/dependabot/pull_request_creator/pr_name_prefixer.rb +179 -78
  28. data/lib/dependabot/pull_request_updater/azure.rb +63 -11
  29. data/lib/dependabot/pull_request_updater/github.rb +44 -34
  30. data/lib/dependabot/pull_request_updater/gitlab.rb +23 -12
  31. data/lib/dependabot/pull_request_updater.rb +3 -3
  32. data/lib/dependabot/shared_helpers.rb +77 -32
  33. data/lib/dependabot/update_checkers/base.rb +9 -5
  34. data/lib/dependabot/update_checkers/version_filters.rb +5 -0
  35. data/lib/dependabot/update_checkers/vulnerability_audit.rb +282 -0
  36. data/lib/dependabot/workspace/git.rb +35 -4
  37. data/lib/dependabot.rb +1 -1
  38. metadata +10 -4
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -10,6 +10,8 @@ require "dependabot/git_metadata_fetcher"
10
10
  require "dependabot/git_commit_checker"
11
11
  require "dependabot/metadata_finders/base"
12
12
  require "dependabot/credential"
13
+ require "dependabot/errors"
14
+ require_relative "commit_response_parser"
13
15
 
14
16
  module Dependabot
15
17
  module MetadataFinders
@@ -283,24 +285,21 @@ module Dependabot
283
285
 
284
286
  args = { sha: previous_tag, path: path }.compact
285
287
  previous_commit_shas =
286
- T.unsafe(github_client.commits(repo, **args)).map(&:sha)
288
+ github_client.commits(repo, args).map { |commit| commit_response_parser.github_commit_sha(commit) }
287
289
 
288
290
  # NOTE: We reverse this so it's consistent with the array we get
289
291
  # from `github_client.compare(...)`
290
292
  args = { sha: new_tag, path: path }.compact
291
- T.unsafe(
292
- github_client
293
- .commits(repo, **args)
294
- )
295
- .reject { |c| previous_commit_shas.include?(c.sha) }.reverse
293
+ github_client
294
+ .commits(repo, args)
295
+ .reject { |commit| previous_commit_shas.include?(commit_response_parser.github_commit_sha(commit)) }
296
+ .reverse
296
297
  end
297
- return [] unless commits
298
-
299
298
  commits.map do |commit|
300
299
  {
301
- message: commit.commit.message,
302
- sha: commit.sha,
303
- html_url: commit.html_url
300
+ message: commit_response_parser.github_commit_message(commit),
301
+ sha: commit_response_parser.github_commit_sha(commit),
302
+ html_url: commit_response_parser.sawyer_string(commit, :html_url, "commit")
304
303
  }
305
304
  end
306
305
  rescue Octokit::NotFound
@@ -312,10 +311,13 @@ module Dependabot
312
311
  bitbucket_client
313
312
  .compare(T.must(source).repo, T.must(previous_tag), T.must(new_tag))
314
313
  .map do |commit|
314
+ summary = T.cast(commit.fetch("summary"), Dependabot::Clients::Bitbucket::JsonObject)
315
+ links = T.cast(commit.fetch("links"), Dependabot::Clients::Bitbucket::JsonObject)
316
+ html = T.cast(links.fetch("html"), Dependabot::Clients::Bitbucket::JsonObject)
315
317
  {
316
- message: commit.dig("summary", "raw"),
317
- sha: commit["hash"],
318
- html_url: commit.dig("links", "html", "href")
318
+ message: T.cast(summary.fetch("raw"), String),
319
+ sha: T.cast(commit.fetch("hash"), String),
320
+ html_url: T.cast(html.fetch("href"), String)
319
321
  }
320
322
  end
321
323
  rescue Dependabot::Clients::Bitbucket::NotFound,
@@ -329,16 +331,20 @@ module Dependabot
329
331
 
330
332
  sig { returns(T::Array[T::Hash[Symbol, String]]) }
331
333
  def fetch_gitlab_commits
332
- T.unsafe(
333
- gitlab_client
334
- .compare(T.must(source).repo, T.must(previous_tag), T.must(new_tag))
335
- )
336
- .commits
337
- .map do |commit|
334
+ comparison = gitlab_client.compare(T.must(source).repo, T.must(previous_tag), T.must(new_tag))
335
+ commits = T.cast(comparison["commits"], Object)
336
+ commit_response_parser.raise_bad_response("GitLab", "commits must be an array") unless commits.is_a?(Array)
337
+
338
+ commits.map do |raw_commit|
339
+ commit = T.cast(raw_commit, Object)
340
+ commit_response_parser.raise_bad_response("GitLab", "commit must be an object") unless
341
+ commit.is_a?(Gitlab::ObjectifiedHash)
342
+
343
+ id = commit_response_parser.gitlab_string(commit, "id", "commit")
338
344
  {
339
- message: commit["message"],
340
- sha: commit["id"],
341
- html_url: "#{T.must(source).url}/commit/#{commit['id']}"
345
+ message: commit_response_parser.gitlab_string(commit, "message", "commit"),
346
+ sha: id,
347
+ html_url: "#{T.must(source).url}/commit/#{id}"
342
348
  }
343
349
  end
344
350
  rescue Gitlab::Error::NotFound
@@ -352,9 +358,9 @@ module Dependabot
352
358
  .compare(previous_tag, new_tag, type)
353
359
  .map do |commit|
354
360
  {
355
- message: commit["comment"],
356
- sha: commit["commitId"],
357
- html_url: commit["remoteUrl"]
361
+ message: commit_response_parser.object_string(commit, "comment", "commit"),
362
+ sha: commit_response_parser.object_string(commit, "commitId", "commit"),
363
+ html_url: commit_response_parser.object_string(commit, "remoteUrl", "commit")
358
364
  }
359
365
  end
360
366
  rescue Dependabot::Clients::Azure::NotFound,
@@ -366,6 +372,14 @@ module Dependabot
366
372
  []
367
373
  end
368
374
 
375
+ sig { returns(CommitResponseParser) }
376
+ def commit_response_parser
377
+ @commit_response_parser ||= T.let(
378
+ CommitResponseParser.new(source_url: T.must(source).url),
379
+ T.nilable(CommitResponseParser)
380
+ )
381
+ end
382
+
369
383
  sig { returns(Dependabot::Clients::GitlabWithRetries) }
370
384
  def gitlab_client
371
385
  @gitlab_client ||=
@@ -410,21 +424,13 @@ module Dependabot
410
424
  end
411
425
 
412
426
  sig { returns(T.class_of(Dependabot::Version)) }
413
- def version_class
414
- dependency.version_class
415
- end
427
+ def version_class = dependency.version_class
416
428
 
417
429
  sig { returns(T.class_of(Dependabot::Requirement)) }
418
- def requirement_class
419
- dependency.requirement_class
420
- end
430
+ def requirement_class = dependency.requirement_class
421
431
 
422
432
  sig { params(version: T.nilable(String)).returns(T::Boolean) }
423
- def git_sha?(version)
424
- return false unless version
425
-
426
- version.match?(/^[0-9a-f]{40}$/)
427
- end
433
+ def git_sha?(version) = version&.match?(/^[0-9a-f]{40}$/) || false
428
434
 
429
435
  sig { returns(T::Boolean) }
430
436
  def reliable_source_directory?
@@ -0,0 +1,72 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ module Package
8
+ class NpmPackageManagerConfig < T::ImmutableStruct
9
+ extend T::Sig
10
+
11
+ ObjectHash = T.type_alias { T::Hash[String, Object] }
12
+
13
+ const :package_manager, T.nilable(String), default: nil
14
+ const :engines, T.nilable(T::Hash[String, String]), default: nil
15
+
16
+ sig { params(package_json: T.nilable(Object)).returns(NpmPackageManagerConfig) }
17
+ def self.from_package_json(package_json)
18
+ return new if package_json.nil?
19
+
20
+ manifest = object_hash(package_json, "package.json")
21
+
22
+ new(
23
+ package_manager: optional_string(manifest["packageManager"], "packageManager"),
24
+ engines: parse_engines(manifest)
25
+ )
26
+ end
27
+
28
+ sig { params(manifest: ObjectHash).returns(T.nilable(T::Hash[String, String])) }
29
+ def self.parse_engines(manifest)
30
+ return unless manifest.key?("engines")
31
+
32
+ raw_engines = manifest["engines"]
33
+ return if raw_engines.nil?
34
+
35
+ engines = T.let({}, T::Hash[String, String])
36
+ object_hash(raw_engines, "engines").each do |name, raw_requirement|
37
+ next if raw_requirement.nil?
38
+
39
+ raise TypeError, "engines.#{name} must be a string" unless raw_requirement.is_a?(String)
40
+
41
+ engines[name] = raw_requirement
42
+ end
43
+ engines
44
+ end
45
+ private_class_method :parse_engines
46
+
47
+ sig { params(value: Object, context: String).returns(ObjectHash) }
48
+ def self.object_hash(value, context)
49
+ raise TypeError, "#{context} must be an object" unless value.is_a?(Hash)
50
+
51
+ result = T.let({}, ObjectHash)
52
+ value.each do |raw_key, raw_value|
53
+ key = T.cast(raw_key, Object)
54
+ raise TypeError, "#{context} keys must be strings" unless key.is_a?(String)
55
+
56
+ result[key] = T.cast(raw_value, Object)
57
+ end
58
+ result
59
+ end
60
+ private_class_method :object_hash
61
+
62
+ sig { params(value: T.nilable(Object), context: String).returns(T.nilable(String)) }
63
+ def self.optional_string(value, context)
64
+ return if value.nil?
65
+ return value if value.is_a?(String)
66
+
67
+ raise TypeError, "#{context} must be a string"
68
+ end
69
+ private_class_method :optional_string
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,186 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "time"
6
+ require "sorbet-runtime"
7
+
8
+ module Dependabot
9
+ module Package
10
+ class NpmRegistryPackage < T::ImmutableStruct
11
+ extend T::Sig
12
+
13
+ ObjectHash = T.type_alias { T::Hash[String, Object] }
14
+ VersionFilter = T.type_alias { T.proc.params(version: String).returns(T::Boolean) }
15
+
16
+ class Repository < T::ImmutableStruct
17
+ extend T::Sig
18
+
19
+ const :git, T::Boolean
20
+ const :url, T.nilable(String), default: nil
21
+ const :type, T.nilable(String), default: nil
22
+
23
+ sig { returns(T::Boolean) }
24
+ def git?
25
+ git
26
+ end
27
+ end
28
+
29
+ class Release < T::ImmutableStruct
30
+ extend T::Sig
31
+
32
+ const :version, String
33
+ const :released_at, T.nilable(Time), default: nil
34
+ const :node_requirement, T.nilable(String), default: nil
35
+ const :repository, T.nilable(Repository), default: nil
36
+ const :details, ObjectHash
37
+
38
+ sig { returns(String) }
39
+ def package_type
40
+ repository&.git? ? "git" : "npm"
41
+ end
42
+ end
43
+
44
+ const :releases, T::Hash[String, Release]
45
+ const :dist_tags, T.nilable(T::Hash[String, String]), default: nil
46
+
47
+ sig { params(json: String, version_filter: VersionFilter).returns(NpmRegistryPackage) }
48
+ def self.from_json(json, &version_filter)
49
+ parsed = T.cast(JSON.parse(json), Object)
50
+ package = object_hash(parsed, "npm registry package")
51
+
52
+ versions = package["versions"]
53
+ times = package["time"]
54
+ dist_tags = package["dist-tags"]
55
+
56
+ release_details = versions.nil? ? {} : object_hash(versions, "versions")
57
+ accepted_versions = T.let({}, T::Hash[String, T::Boolean])
58
+ release_details.each_key do |version|
59
+ accepted_versions[version] = true if yield(version)
60
+ end
61
+
62
+ release_times = if times.nil?
63
+ {}
64
+ else
65
+ string_map(times, "time", included_keys: accepted_versions)
66
+ end
67
+
68
+ releases = T.let({}, T::Hash[String, Release])
69
+ release_details.each do |version, details|
70
+ next unless accepted_versions.key?(version)
71
+
72
+ releases[version] = parse_release(
73
+ version: version,
74
+ details: details,
75
+ released_at: release_times[version]
76
+ )
77
+ end
78
+
79
+ new(
80
+ releases: releases,
81
+ dist_tags: dist_tags.nil? ? nil : string_map(dist_tags, "dist-tags")
82
+ )
83
+ end
84
+
85
+ sig do
86
+ params(
87
+ version: String,
88
+ details: Object,
89
+ released_at: T.nilable(String)
90
+ ).returns(Release)
91
+ end
92
+ def self.parse_release(version:, details:, released_at:)
93
+ details_hash = object_hash(details, "version #{version} details")
94
+
95
+ Release.new(
96
+ version: version,
97
+ released_at: released_at && Time.parse(released_at),
98
+ node_requirement: parse_node_requirement(details_hash, version),
99
+ repository: parse_repository(details_hash["repository"], version),
100
+ details: details_hash
101
+ )
102
+ end
103
+ private_class_method :parse_release
104
+
105
+ sig { params(details: ObjectHash, version: String).returns(T.nilable(String)) }
106
+ def self.parse_node_requirement(details, version)
107
+ engines = details["engines"]
108
+ return unless engines.is_a?(Hash)
109
+
110
+ engines_hash = object_hash(engines, "version #{version} engines")
111
+ optional_string(
112
+ engines_hash["node"],
113
+ "version #{version} engines.node"
114
+ )
115
+ end
116
+ private_class_method :parse_node_requirement
117
+
118
+ sig { params(value: T.nilable(Object), version: String).returns(T.nilable(Repository)) }
119
+ def self.parse_repository(value, version)
120
+ case value
121
+ when nil
122
+ nil
123
+ when String
124
+ Repository.new(url: value, git: value.start_with?("git+"))
125
+ when Hash
126
+ repository = object_hash(value, "version #{version} repository")
127
+ url = optional_string(repository["url"], "version #{version} repository.url")
128
+ type = optional_string(repository["type"], "version #{version} repository.type")
129
+
130
+ Repository.new(
131
+ url: url,
132
+ type: type,
133
+ git: type == "git"
134
+ )
135
+ else
136
+ raise TypeError, "version #{version} repository must be a string or object"
137
+ end
138
+ end
139
+ private_class_method :parse_repository
140
+
141
+ sig { params(value: Object, context: String).returns(ObjectHash) }
142
+ def self.object_hash(value, context)
143
+ raise TypeError, "#{context} must be an object" unless value.is_a?(Hash)
144
+
145
+ result = T.let({}, ObjectHash)
146
+ value.each do |raw_key, raw_value|
147
+ key = T.cast(raw_key, Object)
148
+ raise TypeError, "#{context} keys must be strings" unless key.is_a?(String)
149
+
150
+ result[key] = T.cast(raw_value, Object)
151
+ end
152
+ result
153
+ end
154
+ private_class_method :object_hash
155
+
156
+ sig do
157
+ params(
158
+ value: Object,
159
+ context: String,
160
+ included_keys: T.nilable(T::Hash[String, T::Boolean])
161
+ ).returns(T::Hash[String, String])
162
+ end
163
+ def self.string_map(value, context, included_keys: nil)
164
+ result = T.let({}, T::Hash[String, String])
165
+ object_hash(value, context).each do |key, raw_value|
166
+ next if included_keys && !included_keys.key?(key)
167
+
168
+ raise TypeError, "#{context} values must be strings" unless raw_value.is_a?(String)
169
+
170
+ result[key] = raw_value
171
+ end
172
+ result
173
+ end
174
+ private_class_method :string_map
175
+
176
+ sig { params(value: T.nilable(Object), context: String).returns(T.nilable(String)) }
177
+ def self.optional_string(value, context)
178
+ return if value.nil?
179
+ return value if value.is_a?(String)
180
+
181
+ raise TypeError, "#{context} must be a string"
182
+ end
183
+ private_class_method :optional_string
184
+ end
185
+ end
186
+ end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -110,7 +110,7 @@ module Dependabot
110
110
  ) == base_commit
111
111
  end
112
112
 
113
- sig { returns(T.nilable(Aws::CodeCommit::Types::CreatePullRequestOutput)) }
113
+ sig { returns(T.nilable(Seahorse::Client::Response)) }
114
114
  def create_pull_request
115
115
  branch = create_or_get_branch(base_commit)
116
116
  return unless branch
@@ -124,7 +124,6 @@ module Dependabot
124
124
  pr_description
125
125
  # codecommit doesn't support PR labels
126
126
  )
127
- return unless pull_request
128
127
 
129
128
  pull_request
130
129
  end
@@ -167,7 +166,7 @@ module Dependabot
167
166
  def branch_exists?(branch_name)
168
167
  @branch_ref ||= T.let(
169
168
  codecommit_client_for_source.branch(branch_name),
170
- T.nilable(String)
169
+ T.nilable(Seahorse::Client::Response)
171
170
  )
172
171
  !@branch_ref.nil?
173
172
  rescue Aws::CodeCommit::Errors::BranchDoesNotExistException
@@ -178,15 +177,14 @@ module Dependabot
178
177
  def unmerged_pull_request_exists?
179
178
  unmerged_prs = []
180
179
  pull_requests_for_branch.each do |pr|
181
- unless T.unsafe(pr).pull_request
182
- .pull_request_targets[0].merge_metadata.is_merged
183
- unmerged_prs << pr
184
- end
180
+ output = T.cast(pr.data, Aws::CodeCommit::Types::GetPullRequestOutput)
181
+ target = T.must(output.pull_request.pull_request_targets[0])
182
+ unmerged_prs << pr unless target.merge_metadata&.is_merged
185
183
  end
186
184
  unmerged_prs.any?
187
185
  end
188
186
 
189
- sig { returns(T::Array[Aws::CodeCommit::Types::PullRequest]) }
187
+ sig { returns(T::Array[Seahorse::Client::Response]) }
190
188
  def pull_requests_for_branch
191
189
  @pull_requests_for_branch ||=
192
190
  T.let(
@@ -194,17 +192,17 @@ module Dependabot
194
192
  open_prs = codecommit_client_for_source.pull_requests(
195
193
  source.repo,
196
194
  "open",
197
- source.branch || default_branch
195
+ branch_name
198
196
  )
199
197
  closed_prs = codecommit_client_for_source.pull_requests(
200
198
  source.repo,
201
199
  "closed",
202
- source.branch || default_branch
200
+ branch_name
203
201
  )
204
202
 
205
203
  [*open_prs, *closed_prs]
206
204
  end,
207
- T.nilable(T::Array[Aws::CodeCommit::Types::PullRequest])
205
+ T.nilable(T::Array[Seahorse::Client::Response])
208
206
  )
209
207
  end
210
208