dependabot-composer 0.89.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 +7 -0
- data/helpers/.php_cs +32 -0
- data/helpers/bin/run.php +84 -0
- data/helpers/build +14 -0
- data/helpers/composer.json +14 -0
- data/helpers/composer.lock +1528 -0
- data/helpers/php/.php_cs +34 -0
- data/helpers/setup.sh +4 -0
- data/helpers/src/DependabotInstallationManager.php +61 -0
- data/helpers/src/DependabotPluginManager.php +23 -0
- data/helpers/src/ExceptionIO.php +25 -0
- data/helpers/src/Hasher.php +21 -0
- data/helpers/src/UpdateChecker.php +123 -0
- data/helpers/src/Updater.php +97 -0
- data/lib/dependabot/composer.rb +11 -0
- data/lib/dependabot/composer/file_fetcher.rb +132 -0
- data/lib/dependabot/composer/file_parser.rb +179 -0
- data/lib/dependabot/composer/file_updater.rb +78 -0
- data/lib/dependabot/composer/file_updater/lockfile_updater.rb +267 -0
- data/lib/dependabot/composer/file_updater/manifest_updater.rb +66 -0
- data/lib/dependabot/composer/metadata_finder.rb +68 -0
- data/lib/dependabot/composer/native_helpers.rb +20 -0
- data/lib/dependabot/composer/requirement.rb +98 -0
- data/lib/dependabot/composer/update_checker.rb +176 -0
- data/lib/dependabot/composer/update_checker/requirements_updater.rb +253 -0
- data/lib/dependabot/composer/update_checker/version_resolver.rb +214 -0
- data/lib/dependabot/composer/version.rb +26 -0
- metadata +195 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/composer/file_updater"
|
4
|
+
|
5
|
+
module Dependabot
|
6
|
+
module Composer
|
7
|
+
class FileUpdater
|
8
|
+
class ManifestUpdater
|
9
|
+
def initialize(dependencies:, manifest:)
|
10
|
+
@dependencies = dependencies
|
11
|
+
@manifest = manifest
|
12
|
+
end
|
13
|
+
|
14
|
+
def updated_manifest_content
|
15
|
+
dependencies.reduce(manifest.content.dup) do |content, dep|
|
16
|
+
updated_content = content
|
17
|
+
updated_requirements(dep).each do |new_req|
|
18
|
+
old_req = old_requirement(dep, new_req).fetch(:requirement)
|
19
|
+
updated_req = new_req.fetch(:requirement)
|
20
|
+
|
21
|
+
regex =
|
22
|
+
/
|
23
|
+
"#{Regexp.escape(dep.name)}"\s*:\s*
|
24
|
+
"#{Regexp.escape(old_req)}"
|
25
|
+
/x
|
26
|
+
|
27
|
+
updated_content = content.gsub(regex) do |declaration|
|
28
|
+
declaration.gsub(%("#{old_req}"), %("#{updated_req}"))
|
29
|
+
end
|
30
|
+
|
31
|
+
raise "Expected content to change!" if content == updated_content
|
32
|
+
end
|
33
|
+
|
34
|
+
updated_content
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :dependencies, :manifest
|
41
|
+
|
42
|
+
def new_requirements(dependency)
|
43
|
+
dependency.requirements.select { |r| r[:file] == manifest.name }
|
44
|
+
end
|
45
|
+
|
46
|
+
def old_requirement(dependency, new_requirement)
|
47
|
+
dependency.previous_requirements.
|
48
|
+
select { |r| r[:file] == manifest.name }.
|
49
|
+
find { |r| r[:groups] == new_requirement[:groups] }
|
50
|
+
end
|
51
|
+
|
52
|
+
def updated_requirements(dependency)
|
53
|
+
new_requirements(dependency).
|
54
|
+
reject { |r| dependency.previous_requirements.include?(r) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def requirement_changed?(file, dependency)
|
58
|
+
changed_requirements =
|
59
|
+
dependency.requirements - dependency.previous_requirements
|
60
|
+
|
61
|
+
changed_requirements.any? { |f| f[:file] == file.name }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "excon"
|
4
|
+
require "dependabot/metadata_finders"
|
5
|
+
require "dependabot/metadata_finders/base"
|
6
|
+
require "dependabot/shared_helpers"
|
7
|
+
require "dependabot/composer/version"
|
8
|
+
|
9
|
+
module Dependabot
|
10
|
+
module Composer
|
11
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
12
|
+
private
|
13
|
+
|
14
|
+
def look_up_source
|
15
|
+
source_from_dependency || look_up_source_from_packagist
|
16
|
+
end
|
17
|
+
|
18
|
+
def source_from_dependency
|
19
|
+
source_url =
|
20
|
+
dependency.requirements.
|
21
|
+
map { |r| r.fetch(:source) }.compact.
|
22
|
+
first&.fetch(:url, nil)
|
23
|
+
|
24
|
+
Source.from_url(source_url)
|
25
|
+
end
|
26
|
+
|
27
|
+
def look_up_source_from_packagist
|
28
|
+
return nil if packagist_listing&.fetch("packages", nil) == []
|
29
|
+
unless packagist_listing&.dig("packages", dependency.name.downcase)
|
30
|
+
return nil
|
31
|
+
end
|
32
|
+
|
33
|
+
version_listings =
|
34
|
+
packagist_listing["packages"][dependency.name.downcase].
|
35
|
+
select { |version, _| Composer::Version.correct?(version) }.
|
36
|
+
sort_by { |version, _| Composer::Version.new(version) }.
|
37
|
+
map { |_, listing| listing }.
|
38
|
+
reverse
|
39
|
+
|
40
|
+
potential_source_urls =
|
41
|
+
version_listings.
|
42
|
+
flat_map { |info| [info["homepage"], info.dig("source", "url")] }.
|
43
|
+
compact
|
44
|
+
|
45
|
+
source_url = potential_source_urls.find { |url| Source.from_url(url) }
|
46
|
+
|
47
|
+
Source.from_url(source_url)
|
48
|
+
end
|
49
|
+
|
50
|
+
def packagist_listing
|
51
|
+
return @packagist_listing unless @packagist_listing.nil?
|
52
|
+
|
53
|
+
response = Excon.get(
|
54
|
+
"https://packagist.org/p/#{dependency.name.downcase}.json",
|
55
|
+
idempotent: true,
|
56
|
+
**SharedHelpers.excon_defaults
|
57
|
+
)
|
58
|
+
|
59
|
+
return nil unless response.status == 200
|
60
|
+
|
61
|
+
@packagist_listing = JSON.parse(response.body)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
Dependabot::MetadataFinders.
|
68
|
+
register("composer", Dependabot::Composer::MetadataFinder)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dependabot
|
4
|
+
module Composer
|
5
|
+
module NativeHelpers
|
6
|
+
def self.composer_helper_path
|
7
|
+
File.join(composer_helpers_dir, "bin/run.php")
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.composer_helpers_dir
|
11
|
+
File.join(native_helpers_root, "composer/helpers")
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.native_helpers_root
|
15
|
+
default_path = File.join(__dir__, "../../../..")
|
16
|
+
ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/utils"
|
4
|
+
|
5
|
+
module Dependabot
|
6
|
+
module Composer
|
7
|
+
class Requirement < Gem::Requirement
|
8
|
+
AND_SEPARATOR =
|
9
|
+
/(?<=[a-zA-Z0-9*])(?<!\sas)[\s,]+(?![\s,]*[|-]|as)/.freeze
|
10
|
+
OR_SEPARATOR = /(?<=[a-zA-Z0-9*])[\s,]*\|\|?\s*/.freeze
|
11
|
+
|
12
|
+
def self.parse(obj)
|
13
|
+
new_obj = obj.gsub(/@\w+/, "").gsub(/[a-z0-9\-_\.]*\sas\s+/i, "")
|
14
|
+
super(new_obj)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns an array of requirements. At least one requirement from the
|
18
|
+
# returned array must be satisfied for a version to be valid.
|
19
|
+
def self.requirements_array(requirement_string)
|
20
|
+
requirement_string.strip.split(OR_SEPARATOR).map do |req_string|
|
21
|
+
new(req_string)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(*requirements)
|
26
|
+
requirements =
|
27
|
+
requirements.flatten.
|
28
|
+
flat_map { |req_string| req_string.split(AND_SEPARATOR) }.
|
29
|
+
flat_map { |req| convert_php_constraint_to_ruby_constraint(req) }
|
30
|
+
|
31
|
+
super(requirements)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
37
|
+
def convert_php_constraint_to_ruby_constraint(req_string)
|
38
|
+
req_string = req_string.gsub(/v(?=\d)/, "")
|
39
|
+
|
40
|
+
# Return an unlikely version if a dev requirement is specified. This
|
41
|
+
# ensures that the dev-requirement doesn't match anything.
|
42
|
+
return "0-dev-branch-match" if req_string.strip.start_with?("dev-")
|
43
|
+
|
44
|
+
if req_string.start_with?("*") then ">= 0"
|
45
|
+
elsif req_string.include?("*") then convert_wildcard_req(req_string)
|
46
|
+
elsif req_string.match?(/^~[^>]/) then convert_tilde_req(req_string)
|
47
|
+
elsif req_string.start_with?("^") then convert_caret_req(req_string)
|
48
|
+
elsif req_string.match?(/\s-\s/) then convert_hyphen_req(req_string)
|
49
|
+
else req_string
|
50
|
+
end
|
51
|
+
end
|
52
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
53
|
+
|
54
|
+
def convert_wildcard_req(req_string)
|
55
|
+
version = req_string.gsub(/^~/, "").gsub(/(?:\.|^)\*/, "")
|
56
|
+
"~> #{version}.0"
|
57
|
+
end
|
58
|
+
|
59
|
+
def convert_tilde_req(req_string)
|
60
|
+
version = req_string.gsub(/^~/, "")
|
61
|
+
"~> #{version}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def convert_caret_req(req_string)
|
65
|
+
version = req_string.gsub(/^\^/, "")
|
66
|
+
parts = version.split(".")
|
67
|
+
first_non_zero = parts.find { |d| d != "0" }
|
68
|
+
first_non_zero_index =
|
69
|
+
first_non_zero ? parts.index(first_non_zero) : parts.count - 1
|
70
|
+
upper_bound = parts.map.with_index do |part, i|
|
71
|
+
if i < first_non_zero_index then part
|
72
|
+
elsif i == first_non_zero_index then (part.to_i + 1).to_s
|
73
|
+
else 0
|
74
|
+
end
|
75
|
+
end.join(".")
|
76
|
+
|
77
|
+
[">= #{version}", "< #{upper_bound}"]
|
78
|
+
end
|
79
|
+
|
80
|
+
def convert_hyphen_req(req_string)
|
81
|
+
req_string = req_string
|
82
|
+
lower_bound, upper_bound = req_string.split(/\s+-\s+/)
|
83
|
+
if upper_bound.split(".").count < 3
|
84
|
+
upper_bound_parts = upper_bound.split(".")
|
85
|
+
upper_bound_parts[-1] = (upper_bound_parts[-1].to_i + 1).to_s
|
86
|
+
upper_bound = upper_bound_parts.join(".")
|
87
|
+
|
88
|
+
[">= #{lower_bound}", "< #{upper_bound}"]
|
89
|
+
else
|
90
|
+
[">= #{lower_bound}", "<= #{upper_bound}"]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
Dependabot::Utils.
|
98
|
+
register_requirement_class("composer", Dependabot::Composer::Requirement)
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "excon"
|
4
|
+
require "json"
|
5
|
+
require "dependabot/update_checkers"
|
6
|
+
require "dependabot/update_checkers/base"
|
7
|
+
require "dependabot/shared_helpers"
|
8
|
+
require "dependabot/errors"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module Composer
|
12
|
+
class UpdateChecker < Dependabot::UpdateCheckers::Base
|
13
|
+
require_relative "update_checker/requirements_updater"
|
14
|
+
require_relative "update_checker/version_resolver"
|
15
|
+
|
16
|
+
def latest_version
|
17
|
+
return nil if path_dependency?
|
18
|
+
|
19
|
+
# Fall back to latest_resolvable_version if no listings found
|
20
|
+
latest_version_from_registry || latest_resolvable_version
|
21
|
+
end
|
22
|
+
|
23
|
+
def latest_resolvable_version
|
24
|
+
return nil if path_dependency?
|
25
|
+
|
26
|
+
@latest_resolvable_version ||=
|
27
|
+
VersionResolver.new(
|
28
|
+
credentials: credentials,
|
29
|
+
dependency: dependency,
|
30
|
+
dependency_files: dependency_files,
|
31
|
+
latest_allowable_version: latest_version_from_registry,
|
32
|
+
requirements_to_unlock: :own
|
33
|
+
).latest_resolvable_version
|
34
|
+
end
|
35
|
+
|
36
|
+
def latest_resolvable_version_with_no_unlock
|
37
|
+
return nil if path_dependency?
|
38
|
+
|
39
|
+
@latest_resolvable_version_with_no_unlock ||=
|
40
|
+
VersionResolver.new(
|
41
|
+
credentials: credentials,
|
42
|
+
dependency: dependency,
|
43
|
+
dependency_files: dependency_files,
|
44
|
+
latest_allowable_version: latest_version_from_registry,
|
45
|
+
requirements_to_unlock: :none
|
46
|
+
).latest_resolvable_version
|
47
|
+
end
|
48
|
+
|
49
|
+
def updated_requirements
|
50
|
+
RequirementsUpdater.new(
|
51
|
+
requirements: dependency.requirements,
|
52
|
+
latest_version: latest_version&.to_s,
|
53
|
+
latest_resolvable_version: latest_resolvable_version&.to_s,
|
54
|
+
update_strategy: requirements_update_strategy
|
55
|
+
).updated_requirements
|
56
|
+
end
|
57
|
+
|
58
|
+
def requirements_update_strategy
|
59
|
+
# If passed in as an option (in the base class) honour that option
|
60
|
+
if @requirements_update_strategy
|
61
|
+
return @requirements_update_strategy.to_sym
|
62
|
+
end
|
63
|
+
|
64
|
+
# Otherwise, widen ranges for libraries and bump versions for apps
|
65
|
+
library? ? :widen_ranges : :bump_versions_if_necessary
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def latest_version_resolvable_with_full_unlock?
|
71
|
+
# Full unlock checks aren't implemented for Composer (yet)
|
72
|
+
false
|
73
|
+
end
|
74
|
+
|
75
|
+
def latest_version_from_registry
|
76
|
+
versions =
|
77
|
+
registry_versions.
|
78
|
+
select { |version| version_class.correct?(version.gsub(/^v/, "")) }.
|
79
|
+
map { |version| version_class.new(version.gsub(/^v/, "")) }
|
80
|
+
|
81
|
+
versions.reject!(&:prerelease?) unless wants_prerelease?
|
82
|
+
versions.reject! { |v| ignore_reqs.any? { |r| r.satisfied_by?(v) } }
|
83
|
+
versions.max
|
84
|
+
end
|
85
|
+
|
86
|
+
def wants_prerelease?
|
87
|
+
current_version = dependency.version
|
88
|
+
if current_version && version_class.new(current_version).prerelease?
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
|
92
|
+
dependency.requirements.any? do |req|
|
93
|
+
req[:requirement].match?(/\d-[A-Za-z]/)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def updated_dependencies_after_full_unlock
|
98
|
+
raise NotImplementedError
|
99
|
+
end
|
100
|
+
|
101
|
+
def path_dependency?
|
102
|
+
dependency.requirements.any? { |r| r.dig(:source, :type) == "path" }
|
103
|
+
end
|
104
|
+
|
105
|
+
def composer_file
|
106
|
+
composer_file =
|
107
|
+
dependency_files.find { |f| f.name == "composer.json" }
|
108
|
+
raise "No composer.json!" unless composer_file
|
109
|
+
|
110
|
+
composer_file
|
111
|
+
end
|
112
|
+
|
113
|
+
def lockfile
|
114
|
+
dependency_files.find { |f| f.name == "composer.lock" }
|
115
|
+
end
|
116
|
+
|
117
|
+
def registry_versions
|
118
|
+
return @registry_versions unless @registry_versions.nil?
|
119
|
+
|
120
|
+
repositories =
|
121
|
+
JSON.parse(composer_file.content).
|
122
|
+
fetch("repositories", []).
|
123
|
+
select { |r| r.is_a?(Hash) }
|
124
|
+
|
125
|
+
urls = repositories.
|
126
|
+
select { |h| h["type"] == "composer" }.
|
127
|
+
map { |h| h["url"] }.compact.
|
128
|
+
map { |url| url.gsub(%r{\/$}, "") + "/packages.json" }
|
129
|
+
|
130
|
+
unless repositories.any? { |rep| rep["packagist.org"] == false }
|
131
|
+
urls << "https://packagist.org/p/#{dependency.name.downcase}.json"
|
132
|
+
end
|
133
|
+
|
134
|
+
@registry_versions = []
|
135
|
+
urls.each do |url|
|
136
|
+
@registry_versions += fetch_registry_versions_from_url(url)
|
137
|
+
end
|
138
|
+
@registry_versions.uniq
|
139
|
+
end
|
140
|
+
|
141
|
+
def fetch_registry_versions_from_url(url)
|
142
|
+
cred = registry_credentials.find { |c| url.include?(c["registry"]) }
|
143
|
+
|
144
|
+
response = Excon.get(
|
145
|
+
url,
|
146
|
+
idempotent: true,
|
147
|
+
user: cred&.fetch("username", nil),
|
148
|
+
password: cred&.fetch("password", nil),
|
149
|
+
**SharedHelpers.excon_defaults
|
150
|
+
)
|
151
|
+
|
152
|
+
return [] unless response.status == 200
|
153
|
+
|
154
|
+
listing = JSON.parse(response.body)
|
155
|
+
return [] if listing.nil?
|
156
|
+
return [] if listing.fetch("packages", []) == []
|
157
|
+
return [] unless listing.dig("packages", dependency.name.downcase)
|
158
|
+
|
159
|
+
listing.dig("packages", dependency.name.downcase).keys
|
160
|
+
rescue Excon::Error::Socket, Excon::Error::Timeout
|
161
|
+
[]
|
162
|
+
end
|
163
|
+
|
164
|
+
def library?
|
165
|
+
JSON.parse(composer_file.content)["type"] == "library"
|
166
|
+
end
|
167
|
+
|
168
|
+
def registry_credentials
|
169
|
+
credentials.select { |cred| cred["type"] == "composer_repository" }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
Dependabot::UpdateCheckers.
|
176
|
+
register("composer", Dependabot::Composer::UpdateChecker)
|
@@ -0,0 +1,253 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
################################################################################
|
4
|
+
# For more details on Composer version constraints, see: #
|
5
|
+
# https://getcomposer.org/doc/articles/versions.md#writing-version-constraints #
|
6
|
+
################################################################################
|
7
|
+
|
8
|
+
require "dependabot/composer/update_checker"
|
9
|
+
require "dependabot/composer/version"
|
10
|
+
require "dependabot/composer/requirement"
|
11
|
+
|
12
|
+
module Dependabot
|
13
|
+
module Composer
|
14
|
+
class UpdateChecker
|
15
|
+
class RequirementsUpdater
|
16
|
+
ALIAS_REGEX = /[a-z0-9\-_\.]*\sas\s+/.freeze
|
17
|
+
VERSION_REGEX =
|
18
|
+
/(?:#{ALIAS_REGEX})?[0-9]+(?:\.[a-zA-Z0-9*\-]+)*/.freeze
|
19
|
+
AND_SEPARATOR =
|
20
|
+
/(?<=[a-zA-Z0-9*])(?<!\sas)[\s,]+(?![\s,]*[|-]|as)/.freeze
|
21
|
+
OR_SEPARATOR = /(?<=[a-zA-Z0-9*])[\s,]*\|\|?\s*/.freeze
|
22
|
+
SEPARATOR = /(?:#{AND_SEPARATOR})|(?:#{OR_SEPARATOR})/.freeze
|
23
|
+
ALLOWED_UPDATE_STRATEGIES =
|
24
|
+
%i(widen_ranges bump_versions bump_versions_if_necessary).freeze
|
25
|
+
|
26
|
+
def initialize(requirements:, update_strategy:,
|
27
|
+
latest_version:, latest_resolvable_version:)
|
28
|
+
@requirements = requirements
|
29
|
+
@update_strategy = update_strategy
|
30
|
+
|
31
|
+
check_update_strategy
|
32
|
+
|
33
|
+
@latest_version = version_class.new(latest_version) if latest_version
|
34
|
+
return unless latest_resolvable_version
|
35
|
+
|
36
|
+
@latest_resolvable_version =
|
37
|
+
version_class.new(latest_resolvable_version)
|
38
|
+
end
|
39
|
+
|
40
|
+
def updated_requirements
|
41
|
+
return requirements unless latest_resolvable_version
|
42
|
+
|
43
|
+
requirements.map { |req| updated_requirement(req) }
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :requirements, :update_strategy,
|
49
|
+
:latest_version, :latest_resolvable_version
|
50
|
+
|
51
|
+
def check_update_strategy
|
52
|
+
return if ALLOWED_UPDATE_STRATEGIES.include?(update_strategy)
|
53
|
+
|
54
|
+
raise "Unknown update strategy: #{update_strategy}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
58
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
59
|
+
def updated_requirement(req)
|
60
|
+
req_string = req[:requirement].strip
|
61
|
+
or_string_reqs = req_string.split(OR_SEPARATOR)
|
62
|
+
or_separator = req_string.match(OR_SEPARATOR)&.to_s || " || "
|
63
|
+
numeric_or_string_reqs = or_string_reqs.
|
64
|
+
reject { |r| r.start_with?("dev-") }
|
65
|
+
branch_or_string_reqs = or_string_reqs.
|
66
|
+
select { |r| r.start_with?("dev-") }
|
67
|
+
|
68
|
+
return req unless req_string.match?(/\d/)
|
69
|
+
return req if numeric_or_string_reqs.none?
|
70
|
+
return updated_alias(req) if req_string.match?(ALIAS_REGEX)
|
71
|
+
return req if req_satisfied_by_latest_resolvable?(req_string) &&
|
72
|
+
update_strategy != :bump_versions
|
73
|
+
|
74
|
+
new_req =
|
75
|
+
case update_strategy
|
76
|
+
when :widen_ranges
|
77
|
+
widen_requirement(req, or_separator)
|
78
|
+
when :bump_versions, :bump_versions_if_necessary
|
79
|
+
update_requirement_version(req, or_separator)
|
80
|
+
end
|
81
|
+
|
82
|
+
new_req_string =
|
83
|
+
[new_req[:requirement], *branch_or_string_reqs].join(or_separator)
|
84
|
+
new_req.merge(requirement: new_req_string)
|
85
|
+
end
|
86
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
87
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
88
|
+
|
89
|
+
def updated_alias(req)
|
90
|
+
req_string = req[:requirement]
|
91
|
+
real_version = req_string.split(/\sas\s/).first.strip
|
92
|
+
|
93
|
+
# If the version we're aliasing isn't a version then we don't know
|
94
|
+
# how to update it, so we just return the existing requirement.
|
95
|
+
return req unless version_class.correct?(real_version)
|
96
|
+
|
97
|
+
new_version_string = latest_resolvable_version.to_s
|
98
|
+
new_req = req_string.sub(real_version, new_version_string)
|
99
|
+
req.merge(requirement: new_req)
|
100
|
+
end
|
101
|
+
|
102
|
+
def widen_requirement(req, or_separator)
|
103
|
+
current_requirement = req[:requirement]
|
104
|
+
reqs = current_requirement.strip.split(SEPARATOR).map(&:strip)
|
105
|
+
|
106
|
+
updated_requirement =
|
107
|
+
if reqs.any? { |r| r.start_with?("^") }
|
108
|
+
update_caret_requirement(current_requirement, or_separator)
|
109
|
+
elsif reqs.any? { |r| r.start_with?("~") }
|
110
|
+
update_tilda_requirement(current_requirement, or_separator)
|
111
|
+
elsif reqs.any? { |r| r.include?("*") }
|
112
|
+
update_wildcard_requirement(current_requirement, or_separator)
|
113
|
+
elsif reqs.any? { |r| r.match?(/<|(\s+-\s+)/) }
|
114
|
+
update_range_requirement(current_requirement, or_separator)
|
115
|
+
else
|
116
|
+
update_version_string(current_requirement)
|
117
|
+
end
|
118
|
+
|
119
|
+
req.merge(requirement: updated_requirement)
|
120
|
+
end
|
121
|
+
|
122
|
+
def update_requirement_version(req, or_separator)
|
123
|
+
current_requirement = req[:requirement]
|
124
|
+
reqs = current_requirement.strip.split(SEPARATOR).map(&:strip)
|
125
|
+
|
126
|
+
updated_requirement =
|
127
|
+
if reqs.count > 1
|
128
|
+
"^#{latest_resolvable_version}"
|
129
|
+
elsif reqs.any? { |r| r.match?(/<|(\s+-\s+)/) }
|
130
|
+
update_range_requirement(current_requirement, or_separator)
|
131
|
+
elsif reqs.any? { |r| r.match?(/>[^=]/) }
|
132
|
+
current_requirement
|
133
|
+
else
|
134
|
+
update_version_string(current_requirement)
|
135
|
+
end
|
136
|
+
|
137
|
+
req.merge(requirement: updated_requirement)
|
138
|
+
end
|
139
|
+
|
140
|
+
def req_satisfied_by_latest_resolvable?(requirement_string)
|
141
|
+
ruby_requirements(requirement_string).
|
142
|
+
any? { |r| r.satisfied_by?(latest_resolvable_version) }
|
143
|
+
end
|
144
|
+
|
145
|
+
def update_version_string(req_string)
|
146
|
+
req_string.
|
147
|
+
sub(VERSION_REGEX) do |old_version|
|
148
|
+
unless req_string.match?(/[~*\^]/)
|
149
|
+
next latest_resolvable_version.to_s
|
150
|
+
end
|
151
|
+
|
152
|
+
old_parts = old_version.split(".")
|
153
|
+
new_parts = latest_resolvable_version.to_s.split(".").
|
154
|
+
first(old_parts.count)
|
155
|
+
new_parts.map.with_index do |part, i|
|
156
|
+
old_parts[i] == "*" ? "*" : part
|
157
|
+
end.join(".")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def ruby_requirements(requirement_string)
|
162
|
+
Composer::Requirement.requirements_array(requirement_string)
|
163
|
+
end
|
164
|
+
|
165
|
+
def update_caret_requirement(req_string, or_separator)
|
166
|
+
caret_requirements =
|
167
|
+
req_string.split(SEPARATOR).select { |r| r.start_with?("^") }
|
168
|
+
version_parts = latest_resolvable_version.segments
|
169
|
+
|
170
|
+
min_existing_precision =
|
171
|
+
caret_requirements.map { |r| r.split(".").count }.min
|
172
|
+
first_non_zero_index =
|
173
|
+
version_parts.count.times.find { |i| version_parts[i] != 0 }
|
174
|
+
|
175
|
+
precision = [min_existing_precision, first_non_zero_index + 1].max
|
176
|
+
version = version_parts.first(precision).map.with_index do |part, i|
|
177
|
+
i <= first_non_zero_index ? part : 0
|
178
|
+
end.join(".")
|
179
|
+
|
180
|
+
req_string + "#{or_separator}^#{version}"
|
181
|
+
end
|
182
|
+
|
183
|
+
def update_tilda_requirement(req_string, or_separator)
|
184
|
+
tilda_requirements =
|
185
|
+
req_string.split(SEPARATOR).select { |r| r.start_with?("~") }
|
186
|
+
precision = tilda_requirements.map { |r| r.split(".").count }.min
|
187
|
+
|
188
|
+
version_parts = latest_resolvable_version.segments.first(precision)
|
189
|
+
version_parts[-1] = 0
|
190
|
+
version = version_parts.join(".")
|
191
|
+
|
192
|
+
req_string + "#{or_separator}~#{version}"
|
193
|
+
end
|
194
|
+
|
195
|
+
def update_wildcard_requirement(req_string, or_separator)
|
196
|
+
wildcard_requirements =
|
197
|
+
req_string.split(SEPARATOR).select { |r| r.include?("*") }
|
198
|
+
precision = wildcard_requirements.map do |r|
|
199
|
+
r.split(".").reject { |s| s == "*" }.count
|
200
|
+
end.min
|
201
|
+
wildcard_count = wildcard_requirements.map do |r|
|
202
|
+
r.split(".").select { |s| s == "*" }.count
|
203
|
+
end.min
|
204
|
+
|
205
|
+
version_parts = latest_resolvable_version.segments.first(precision)
|
206
|
+
version = version_parts.join(".")
|
207
|
+
|
208
|
+
req_string + "#{or_separator}#{version}#{'.*' * wildcard_count}"
|
209
|
+
end
|
210
|
+
|
211
|
+
def update_range_requirement(req_string, or_separator)
|
212
|
+
range_requirements =
|
213
|
+
req_string.split(SEPARATOR).select { |r| r.match?(/<|(\s+-\s+)/) }
|
214
|
+
|
215
|
+
if range_requirements.count == 1
|
216
|
+
range_requirement = range_requirements.first
|
217
|
+
versions = range_requirement.scan(VERSION_REGEX)
|
218
|
+
upper_bound = versions.map { |v| version_class.new(v) }.max
|
219
|
+
new_upper_bound = update_greatest_version(
|
220
|
+
upper_bound,
|
221
|
+
latest_resolvable_version
|
222
|
+
)
|
223
|
+
|
224
|
+
req_string.sub(upper_bound.to_s, new_upper_bound.to_s)
|
225
|
+
else
|
226
|
+
req_string + "#{or_separator}^#{latest_resolvable_version}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def update_greatest_version(old_version, version_to_be_permitted)
|
231
|
+
version = version_class.new(old_version)
|
232
|
+
version = version.release if version.prerelease?
|
233
|
+
|
234
|
+
index_to_update =
|
235
|
+
version.segments.map.with_index { |seg, i| seg.zero? ? 0 : i }.max
|
236
|
+
|
237
|
+
version.segments.map.with_index do |_, index|
|
238
|
+
if index < index_to_update
|
239
|
+
version_to_be_permitted.segments[index]
|
240
|
+
elsif index == index_to_update
|
241
|
+
version_to_be_permitted.segments[index] + 1
|
242
|
+
else 0
|
243
|
+
end
|
244
|
+
end.join(".")
|
245
|
+
end
|
246
|
+
|
247
|
+
def version_class
|
248
|
+
Composer::Version
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|