dependabot-composer 0.279.0 → 0.280.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: 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: []