dependabot-composer 0.279.0 → 0.280.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c22cabf7b08405e201bd5f15c7336f512317349573e19acfd5d3fc9c68b82c4
4
- data.tar.gz: 4ad19b03443097f64680c10479df514ba38bf522292068de7d11dee00d89e5d8
3
+ metadata.gz: 153db9225b35887e37d54abbbe02bd0b0411c7583d1299e81c0dcbd8d8e4c580
4
+ data.tar.gz: a8c5bcf77d7dfd6a7bfe47eec1fa2d57472c397bace27803b5c7c2d59f7e4827
5
5
  SHA512:
6
- metadata.gz: 3e059ba992147dfebbf4ebad0afe9887463655248c9a029a86d0877d59fa060d0e7ac4766f03c6314e8a330a70c2a1e6bf0feb80973801ffe0b71c5dea653345
7
- data.tar.gz: 63e2110b166ef6dc05e42aaf9621eec90296b8bf53472a32e3357e89e86a4b5d7cd4f3d61a612204eb98d9d87d6c474c684df8c091f2e7f910fd69462b78ed73
6
+ metadata.gz: baa6fef6857fd718bab1b4a6b8bada9780a854a79f878887c683e438b279f2bcc52e5756772b9da6a002e712cc99394ec45a82b30c47225714c04510751642bc
7
+ data.tar.gz: 560d052941b5ba14565a3d2cc9270250ad9989fae4f09bd7745c3e5a48cfc60edf6cb75406569afb465c404e91690ece5f5f6c9ef494fb6ce83d6baba5349f3f
@@ -26,7 +26,7 @@ module Dependabot
26
26
  def ecosystem_versions
27
27
  {
28
28
  package_managers: {
29
- "composer" => Helpers.composer_version(parsed_composer_json, parsed_lockfile) || "unknown"
29
+ "composer" => Helpers.composer_version(parsed_composer_json, parsed_lockfile)
30
30
  }
31
31
  }
32
32
  end
@@ -1,6 +1,7 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "dependabot/composer"
4
5
  require "dependabot/dependency"
5
6
  require "dependabot/composer/version"
6
7
  require "dependabot/file_parsers"
@@ -13,7 +14,7 @@ module Dependabot
13
14
  class FileParser < Dependabot::FileParsers::Base
14
15
  require "dependabot/file_parsers/base/dependency_set"
15
16
 
16
- DEPENDENCY_GROUP_KEYS = [
17
+ DEPENDENCY_GROUP_KEYS = T.let([
17
18
  {
18
19
  manifest: "require",
19
20
  lockfile: "packages",
@@ -24,28 +25,41 @@ module Dependabot
24
25
  lockfile: "packages-dev",
25
26
  group: "development"
26
27
  }
27
- ].freeze
28
+ ].freeze, T::Array[T::Hash[Symbol, String]])
28
29
 
30
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
29
31
  def parse
30
- dependency_set = DependencySet.new
32
+ dependency_set = T.let(DependencySet.new, DependencySet)
31
33
  dependency_set += manifest_dependencies
32
34
  dependency_set += lockfile_dependencies
33
35
  dependency_set.dependencies
34
36
  end
35
37
 
38
+ sig { returns(PackageManagerBase) }
39
+ def package_manager
40
+ PackageManager.new(composer_version)
41
+ end
42
+
36
43
  private
37
44
 
38
- def manifest_dependencies
39
- dependencies = DependencySet.new
45
+ sig { returns(DependencySet) }
46
+ def manifest_dependencies # rubocop:disable Metrics/PerceivedComplexity
47
+ dependencies = T.let(DependencySet.new, DependencySet)
40
48
 
41
49
  DEPENDENCY_GROUP_KEYS.each do |keys|
42
- next unless parsed_composer_json[keys[:manifest]].is_a?(Hash)
50
+ manifest = keys[:manifest]
51
+ next unless manifest.is_a?(String)
43
52
 
44
- parsed_composer_json[keys[:manifest]].each do |name, req|
53
+ next unless parsed_composer_json[manifest].is_a?(Hash)
54
+
55
+ parsed_composer_json[manifest].each do |name, req|
45
56
  next unless package?(name)
46
57
 
47
58
  if lockfile
48
- version = dependency_version(name: name, type: keys[:group])
59
+ group = keys[:group]
60
+ next unless group.is_a?(String)
61
+
62
+ version = dependency_version(name: name, type: group)
49
63
 
50
64
  # Ignore dependency versions which don't appear in the
51
65
  # composer.lock or are non-numeric and not a git SHA, since they
@@ -61,35 +75,39 @@ module Dependabot
61
75
  dependencies
62
76
  end
63
77
 
78
+ sig { params(name: String, req: String, keys: T::Hash[Symbol, String]).returns(Dependabot::Dependency) }
64
79
  def build_manifest_dependency(name, req, keys)
65
- Dependency.new(
80
+ group = T.must(keys[:group])
81
+
82
+ Dependabot::Dependency.new(
66
83
  name: name,
67
- version: dependency_version(name: name, type: keys[:group]),
84
+ version: dependency_version(name: name, type: group),
68
85
  requirements: [{
69
86
  requirement: req,
70
87
  file: "composer.json",
71
88
  source: dependency_source(
72
89
  name: name,
73
- type: keys[:group],
90
+ type: group,
74
91
  requirement: req
75
92
  ),
76
- groups: [keys[:group]]
93
+ groups: [group]
77
94
  }],
78
95
  package_manager: "composer"
79
96
  )
80
97
  end
81
98
 
82
99
  # rubocop:disable Metrics/PerceivedComplexity
100
+ sig { returns(DependencySet) }
83
101
  def lockfile_dependencies
84
- dependencies = DependencySet.new
102
+ dependencies = T.let(DependencySet.new, DependencySet)
85
103
 
86
104
  return dependencies unless lockfile
87
105
 
88
106
  DEPENDENCY_GROUP_KEYS.each do |keys|
89
107
  key = keys.fetch(:lockfile)
90
- next unless parsed_lockfile[key].is_a?(Array)
108
+ next unless parsed_lockfile&.[](key).is_a?(Array)
91
109
 
92
- parsed_lockfile[key].each do |details|
110
+ parsed_lockfile&.[](key)&.each do |details|
93
111
  name = details["name"]
94
112
  next unless name.is_a?(String) && package?(name)
95
113
 
@@ -104,11 +122,11 @@ module Dependabot
104
122
 
105
123
  dependencies
106
124
  end
107
-
108
125
  # rubocop:enable Metrics/PerceivedComplexity
109
126
 
127
+ sig { params(name: String, version: String, keys: T::Hash[Symbol, String]).returns(Dependabot::Dependency) }
110
128
  def build_lockfile_dependency(name, version, keys)
111
- Dependency.new(
129
+ Dependabot::Dependency.new(
112
130
  name: name,
113
131
  version: version,
114
132
  requirements: [],
@@ -119,6 +137,7 @@ module Dependabot
119
137
  )
120
138
  end
121
139
 
140
+ sig { params(name: String, type: String).returns(T.nilable(String)) }
122
141
  def dependency_version(name:, type:)
123
142
  return unless lockfile
124
143
 
@@ -131,6 +150,9 @@ module Dependabot
131
150
  package.dig("source", "reference")
132
151
  end
133
152
 
153
+ sig do
154
+ params(name: String, type: String, requirement: String).returns(T.nilable(T::Hash[Symbol, T.nilable(String)]))
155
+ end
134
156
  def dependency_source(name:, type:, requirement:)
135
157
  return unless lockfile
136
158
 
@@ -145,6 +167,10 @@ module Dependabot
145
167
  git_dependency_details(package_details, requirement)
146
168
  end
147
169
 
170
+ sig do
171
+ params(package_details: T::Hash[String, T.untyped],
172
+ requirement: String).returns(T.nilable(T::Hash[Symbol, T.nilable(String)]))
173
+ end
148
174
  def git_dependency_details(package_details, requirement)
149
175
  return unless package_details.dig("source", "type") == "git"
150
176
 
@@ -164,11 +190,13 @@ module Dependabot
164
190
  details.merge(branch: branch, ref: nil)
165
191
  end
166
192
 
193
+ sig { params(name: String, type: String).returns(T.nilable(T::Hash[String, T.untyped])) }
167
194
  def lockfile_details(name:, type:)
168
195
  key = lockfile_key(type)
169
- parsed_lockfile.fetch(key, [])&.find { |d| d["name"] == name }
196
+ parsed_lockfile&.fetch(key, [])&.find { |d| d["name"] == name }
170
197
  end
171
198
 
199
+ sig { params(type: String).returns(String) }
172
200
  def lockfile_key(type)
173
201
  case type
174
202
  when "runtime" then "packages"
@@ -177,36 +205,53 @@ module Dependabot
177
205
  end
178
206
  end
179
207
 
208
+ sig { params(name: String).returns(T::Boolean) }
180
209
  def package?(name)
181
- # Filter out php, ext-, composer-plugin-api, and other special
182
- # packages which don't behave as normal
183
210
  name.split("/").count == 2
184
211
  end
185
212
 
213
+ sig { override.void }
186
214
  def check_required_files
187
215
  raise "No composer.json!" unless get_original_file("composer.json")
188
216
  end
189
217
 
190
- def parsed_lockfile
218
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
219
+ def parsed_lockfile # rubocop:disable Metrics/PerceivedComplexity
191
220
  return unless lockfile
192
221
 
193
- @parsed_lockfile ||= JSON.parse(lockfile.content)
222
+ content = lockfile&.content
223
+
224
+ raise Dependabot::DependencyFileNotParseable, lockfile&.path || "" if content.nil? || content.strip.empty?
225
+
226
+ @parsed_lockfile ||= T.let(JSON.parse(content), T.nilable(T::Hash[String, T.untyped]))
194
227
  rescue JSON::ParserError
195
- raise Dependabot::DependencyFileNotParseable, lockfile.path
228
+ raise Dependabot::DependencyFileNotParseable, lockfile&.path || ""
196
229
  end
197
230
 
231
+ sig { returns(T::Hash[String, T.untyped]) }
198
232
  def parsed_composer_json
199
- @parsed_composer_json ||= JSON.parse(composer_json.content)
233
+ content = composer_json&.content
234
+
235
+ raise Dependabot::DependencyFileNotParseable, composer_json&.path || "" if content.nil? || content.strip.empty?
236
+
237
+ @parsed_composer_json ||= T.let(JSON.parse(content), T.nilable(T::Hash[String, T.untyped]))
200
238
  rescue JSON::ParserError
201
- raise Dependabot::DependencyFileNotParseable, composer_json.path
239
+ raise Dependabot::DependencyFileNotParseable, composer_json&.path || ""
202
240
  end
203
241
 
242
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
204
243
  def composer_json
205
- @composer_json ||= get_original_file("composer.json")
244
+ @composer_json ||= T.let(get_original_file("composer.json"), T.nilable(Dependabot::DependencyFile))
206
245
  end
207
246
 
247
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
208
248
  def lockfile
209
- @lockfile ||= get_original_file("composer.lock")
249
+ @lockfile ||= T.let(get_original_file("composer.lock"), T.nilable(Dependabot::DependencyFile))
250
+ end
251
+
252
+ sig { returns(String) }
253
+ def composer_version
254
+ @composer_version ||= T.let(Helpers.composer_version(parsed_composer_json, parsed_lockfile), T.nilable(String))
210
255
  end
211
256
  end
212
257
  end
@@ -1,48 +1,99 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/composer/version"
5
+ require "sorbet-runtime"
5
6
 
6
7
  module Dependabot
7
8
  module Composer
8
9
  module Helpers
10
+ extend T::Sig
11
+
12
+ V1 = T.let("1", String)
13
+ V2 = T.let("2", String)
14
+ # If we are updating a project with no lock file then the default should be the newest version
15
+ DEFAULT = T.let(V2, String)
16
+
9
17
  # From composers json-schema: https://getcomposer.org/schema.json
10
- COMPOSER_V2_NAME_REGEX = %r{^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$}
18
+ COMPOSER_V2_NAME_REGEX = T.let(
19
+ %r{^[a-z0-9]([_.-]?[a-z0-9]++)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]++)*$},
20
+ Regexp
21
+ )
22
+
11
23
  # From https://github.com/composer/composer/blob/b7d770659b4e3ef21423bd67ade935572913a4c1/src/Composer/Repository/PlatformRepository.php#L33
12
- PLATFORM_PACKAGE_REGEX = /
24
+ PLATFORM_PACKAGE_REGEX = T.let(
25
+ /
13
26
  ^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*
14
27
  |composer-(?:plugin|runtime)-api)$
15
- /x
28
+ /x,
29
+ Regexp
30
+ )
16
31
 
17
- FAILED_GIT_CLONE_WITH_MIRROR = /^Failed to execute git clone --(mirror|checkout)[^']*'(?<url>[^']*?)'/
18
- FAILED_GIT_CLONE = /^Failed to clone (?<url>.*?)/
32
+ FAILED_GIT_CLONE_WITH_MIRROR = T.let(
33
+ /^Failed to execute git clone --(mirror|checkout)[^']*'(?<url>[^']*?)'/,
34
+ Regexp
35
+ )
36
+ FAILED_GIT_CLONE = T.let(/^Failed to clone (?<url>.*?)/, Regexp)
19
37
 
38
+ sig do
39
+ params(
40
+ composer_json: T::Hash[String, T.untyped],
41
+ parsed_lockfile: T.nilable(T::Hash[String, T.untyped])
42
+ )
43
+ .returns(String)
44
+ end
20
45
  def self.composer_version(composer_json, parsed_lockfile = nil)
46
+ v1_unsupported = Dependabot::Experiments.enabled?(:composer_v1_unsupported_error)
47
+
48
+ # If the parsed lockfile has a plugin API version, we return either V1 or V2
49
+ # based on the major version of the lockfile.
21
50
  if parsed_lockfile && parsed_lockfile["plugin-api-version"]
22
51
  version = Composer::Version.new(parsed_lockfile["plugin-api-version"])
23
- return version.canonical_segments.first == 1 ? "1" : "2"
24
- else
25
- return "1" if composer_json["name"] && composer_json["name"] !~ COMPOSER_V2_NAME_REGEX
26
- return "1" if invalid_v2_requirement?(composer_json)
52
+ return version.canonical_segments.first == 1 ? V1 : V2
53
+ end
54
+
55
+ # Check if the composer name does not follow the Composer V2 naming conventions.
56
+ # This happens if "name" is present in composer.json but doesn't match the required pattern.
57
+ composer_name_invalid = composer_json["name"] && composer_json["name"] !~ COMPOSER_V2_NAME_REGEX
58
+
59
+ # If the name is invalid returns the fallback version.
60
+ if composer_name_invalid
61
+ return v1_unsupported ? V2 : V1
62
+ end
63
+
64
+ # Check if the composer.json file contains "require" entries that don't follow
65
+ # either the platform package naming conventions or the Composer V2 name conventions.
66
+ invalid_v2 = invalid_v2_requirement?(composer_json)
67
+
68
+ # If there are invalid requirements returns fallback version.
69
+ if invalid_v2
70
+ return v1_unsupported ? V2 : V1
27
71
  end
28
72
 
29
- "2"
73
+ # If no conditions are met return V2 by default.
74
+ V2
30
75
  end
31
76
 
77
+ sig { params(message: String).returns(T.nilable(String)) }
32
78
  def self.dependency_url_from_git_clone_error(message)
33
- if message.match?(FAILED_GIT_CLONE_WITH_MIRROR)
34
- dependency_url = message.match(FAILED_GIT_CLONE_WITH_MIRROR).named_captures.fetch("url")
35
- raise "Could not parse dependency_url from git clone error: #{message}" if dependency_url.empty?
79
+ extract_and_clean_dependency_url(message, FAILED_GIT_CLONE_WITH_MIRROR) ||
80
+ extract_and_clean_dependency_url(message, FAILED_GIT_CLONE)
81
+ end
36
82
 
37
- clean_dependency_url(dependency_url)
38
- elsif message.match?(FAILED_GIT_CLONE)
39
- dependency_url = message.match(FAILED_GIT_CLONE).named_captures.fetch("url")
40
- raise "Could not parse dependency_url from git clone error: #{message}" if dependency_url.empty?
83
+ sig { params(message: String, regex: Regexp).returns(T.nilable(String)) }
84
+ def self.extract_and_clean_dependency_url(message, regex)
85
+ if (match_data = message.match(regex))
86
+ dependency_url = match_data.named_captures.fetch("url")
87
+ if dependency_url.nil? || dependency_url.empty?
88
+ raise "Could not parse dependency_url from git clone error: #{message}"
89
+ end
41
90
 
42
- clean_dependency_url(dependency_url)
91
+ return clean_dependency_url(dependency_url)
43
92
  end
93
+ nil
44
94
  end
45
95
 
96
+ sig { params(composer_json: T::Hash[String, T.untyped]).returns(T::Boolean) }
46
97
  def self.invalid_v2_requirement?(composer_json)
47
98
  return false unless composer_json.key?("require")
48
99
 
@@ -52,6 +103,7 @@ module Dependabot
52
103
  end
53
104
  private_class_method :invalid_v2_requirement?
54
105
 
106
+ sig { params(dependency_url: String).returns(String) }
55
107
  def self.clean_dependency_url(dependency_url)
56
108
  return dependency_url unless URI::DEFAULT_PARSER.regexp[:ABS_URI].match?(dependency_url)
57
109
 
@@ -0,0 +1,61 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/package_manager"
6
+ require "dependabot/composer/version"
7
+
8
+ module Dependabot
9
+ module Composer
10
+ PACKAGE_MANAGER = "composer"
11
+
12
+ # Keep versions in ascending order
13
+ SUPPORTED_COMPOSER_VERSIONS = T.let([Version.new("2")].freeze, T::Array[Dependabot::Version])
14
+
15
+ DEPRECATED_COMPOSER_VERSIONS = T.let([
16
+ Version.new("1")
17
+ ].freeze, T::Array[Dependabot::Version])
18
+
19
+ class PackageManager < PackageManagerBase
20
+ extend T::Sig
21
+
22
+ sig { params(version: T.any(String, Dependabot::Version)).void }
23
+ def initialize(version)
24
+ @version = T.let(Version.new(version), Dependabot::Version)
25
+ @name = T.let(PACKAGE_MANAGER, String)
26
+ @deprecated_versions = T.let(DEPRECATED_COMPOSER_VERSIONS, T::Array[Dependabot::Version])
27
+ @supported_versions = T.let(SUPPORTED_COMPOSER_VERSIONS, T::Array[Dependabot::Version])
28
+ end
29
+
30
+ sig { override.returns(String) }
31
+ attr_reader :name
32
+
33
+ sig { override.returns(Dependabot::Version) }
34
+ attr_reader :version
35
+
36
+ sig { override.returns(T::Array[Dependabot::Version]) }
37
+ attr_reader :deprecated_versions
38
+
39
+ sig { override.returns(T::Array[Dependabot::Version]) }
40
+ attr_reader :supported_versions
41
+
42
+ sig { override.returns(T::Boolean) }
43
+ def deprecated?
44
+ return false if unsupported?
45
+
46
+ # Check if the feature flag for Composer v1 deprecation warning is enabled.
47
+ return false unless Dependabot::Experiments.enabled?(:composer_v1_deprecation_warning)
48
+
49
+ deprecated_versions.include?(version)
50
+ end
51
+
52
+ sig { override.returns(T::Boolean) }
53
+ def unsupported?
54
+ # Check if the feature flag for Composer v1 unsupported error is enabled.
55
+ return false unless Dependabot::Experiments.enabled?(:composer_v1_unsupported_error)
56
+
57
+ supported_versions.all? { |supported| supported > version }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -10,6 +10,8 @@ require "dependabot/composer/file_updater"
10
10
  require "dependabot/composer/metadata_finder"
11
11
  require "dependabot/composer/requirement"
12
12
  require "dependabot/composer/version"
13
+ require "dependabot/composer/helpers"
14
+ require "dependabot/composer/package_manager"
13
15
 
14
16
  require "dependabot/pull_request_creator/labeler"
15
17
  Dependabot::PullRequestCreator::Labeler
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-composer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.279.0
4
+ version: 0.280.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-03 00:00:00.000000000 Z
11
+ date: 2024-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.279.0
19
+ version: 0.280.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.279.0
26
+ version: 0.280.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: debug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -156,14 +156,14 @@ dependencies:
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 0.8.1
159
+ version: 0.8.5
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 0.8.1
166
+ version: 0.8.5
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: simplecov
169
169
  requirement: !ruby/object:Gem::Requirement
@@ -275,6 +275,7 @@ files:
275
275
  - lib/dependabot/composer/helpers.rb
276
276
  - lib/dependabot/composer/metadata_finder.rb
277
277
  - lib/dependabot/composer/native_helpers.rb
278
+ - lib/dependabot/composer/package_manager.rb
278
279
  - lib/dependabot/composer/requirement.rb
279
280
  - lib/dependabot/composer/update_checker.rb
280
281
  - lib/dependabot/composer/update_checker/latest_version_finder.rb
@@ -286,8 +287,8 @@ licenses:
286
287
  - MIT
287
288
  metadata:
288
289
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
289
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.279.0
290
- post_install_message:
290
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.280.0
291
+ post_install_message:
291
292
  rdoc_options: []
292
293
  require_paths:
293
294
  - lib
@@ -303,7 +304,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
303
304
  version: 3.1.0
304
305
  requirements: []
305
306
  rubygems_version: 3.5.9
306
- signing_key:
307
+ signing_key:
307
308
  specification_version: 4
308
309
  summary: Provides Dependabot support for PHP (composer)
309
310
  test_files: []