dependabot-hex 0.88.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/build +19 -0
- data/helpers/deps/jason/.fetch +0 -0
- data/helpers/deps/jason/.hex +2 -0
- data/helpers/deps/jason/CHANGELOG.md +60 -0
- data/helpers/deps/jason/LICENSE +13 -0
- data/helpers/deps/jason/README.md +179 -0
- data/helpers/deps/jason/hex_metadata.config +20 -0
- data/helpers/deps/jason/lib/codegen.ex +158 -0
- data/helpers/deps/jason/lib/decoder.ex +657 -0
- data/helpers/deps/jason/lib/encode.ex +630 -0
- data/helpers/deps/jason/lib/encoder.ex +216 -0
- data/helpers/deps/jason/lib/formatter.ex +253 -0
- data/helpers/deps/jason/lib/fragment.ex +11 -0
- data/helpers/deps/jason/lib/helpers.ex +90 -0
- data/helpers/deps/jason/lib/jason.ex +228 -0
- data/helpers/deps/jason/mix.exs +92 -0
- data/helpers/lib/check_update.exs +92 -0
- data/helpers/lib/do_update.exs +39 -0
- data/helpers/lib/parse_deps.exs +103 -0
- data/helpers/lib/run.exs +76 -0
- data/helpers/mix.exs +21 -0
- data/helpers/mix.lock +3 -0
- data/lib/dependabot/hex.rb +11 -0
- data/lib/dependabot/hex/file_fetcher.rb +79 -0
- data/lib/dependabot/hex/file_parser.rb +125 -0
- data/lib/dependabot/hex/file_updater.rb +71 -0
- data/lib/dependabot/hex/file_updater/lockfile_updater.rb +142 -0
- data/lib/dependabot/hex/file_updater/mixfile_git_pin_updater.rb +51 -0
- data/lib/dependabot/hex/file_updater/mixfile_requirement_updater.rb +72 -0
- data/lib/dependabot/hex/file_updater/mixfile_sanitizer.rb +26 -0
- data/lib/dependabot/hex/file_updater/mixfile_updater.rb +94 -0
- data/lib/dependabot/hex/metadata_finder.rb +70 -0
- data/lib/dependabot/hex/native_helpers.rb +20 -0
- data/lib/dependabot/hex/requirement.rb +53 -0
- data/lib/dependabot/hex/update_checker.rb +275 -0
- data/lib/dependabot/hex/update_checker/file_preparer.rb +191 -0
- data/lib/dependabot/hex/update_checker/requirements_updater.rb +173 -0
- data/lib/dependabot/hex/update_checker/version_resolver.rb +170 -0
- data/lib/dependabot/hex/version.rb +67 -0
- metadata +208 -0
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
|
|
8
|
+
module Dependabot
|
|
9
|
+
module Hex
|
|
10
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
|
11
|
+
SOURCE_KEYS = %w(
|
|
12
|
+
GitHub Github github
|
|
13
|
+
GitLab Gitlab gitlab
|
|
14
|
+
BitBucket Bitbucket bitbucket
|
|
15
|
+
Source source
|
|
16
|
+
).freeze
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def look_up_source
|
|
21
|
+
case new_source_type
|
|
22
|
+
when "default" then find_source_from_hex_listing
|
|
23
|
+
when "git" then find_source_from_git_url
|
|
24
|
+
else raise "Unexpected source type: #{new_source_type}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def new_source_type
|
|
29
|
+
sources =
|
|
30
|
+
dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact
|
|
31
|
+
|
|
32
|
+
return "default" if sources.empty?
|
|
33
|
+
raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
|
|
34
|
+
|
|
35
|
+
sources.first[:type] || sources.first.fetch("type")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def find_source_from_hex_listing
|
|
39
|
+
potential_source_urls =
|
|
40
|
+
SOURCE_KEYS.
|
|
41
|
+
map { |key| hex_listing.dig("meta", "links", key) }.
|
|
42
|
+
compact
|
|
43
|
+
|
|
44
|
+
source_url = potential_source_urls.find { |url| Source.from_url(url) }
|
|
45
|
+
Source.from_url(source_url)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def find_source_from_git_url
|
|
49
|
+
info = dependency.requirements.map { |r| r[:source] }.compact.first
|
|
50
|
+
|
|
51
|
+
url = info[:url] || info.fetch("url")
|
|
52
|
+
Source.from_url(url)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def hex_listing
|
|
56
|
+
return @hex_listing unless @hex_listing.nil?
|
|
57
|
+
|
|
58
|
+
response = Excon.get(
|
|
59
|
+
"https://hex.pm/api/packages/#{dependency.name}",
|
|
60
|
+
idempotent: true,
|
|
61
|
+
**SharedHelpers.excon_defaults
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@hex_listing = JSON.parse(response.body)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
Dependabot::MetadataFinders.register("hex", Dependabot::Hex::MetadataFinder)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dependabot
|
|
4
|
+
module Hex
|
|
5
|
+
module NativeHelpers
|
|
6
|
+
def self.hex_helpers_dir
|
|
7
|
+
File.join(native_helpers_root, "hex/helpers")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.native_helpers_root
|
|
11
|
+
default_path = File.join(__dir__, "../../../..")
|
|
12
|
+
ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.clean_path(path)
|
|
16
|
+
Pathname.new(path).cleanpath.to_path
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dependabot/utils"
|
|
4
|
+
require "dependabot/hex/version"
|
|
5
|
+
|
|
6
|
+
module Dependabot
|
|
7
|
+
module Hex
|
|
8
|
+
class Requirement < Gem::Requirement
|
|
9
|
+
AND_SEPARATOR = /\s+and\s+/.freeze
|
|
10
|
+
OR_SEPARATOR = /\s+or\s+/.freeze
|
|
11
|
+
|
|
12
|
+
# Add the double-equality matcher to the list of allowed operations
|
|
13
|
+
OPS = OPS.merge("==" => ->(v, r) { v == r })
|
|
14
|
+
|
|
15
|
+
# Override the version pattern to allow local versions
|
|
16
|
+
quoted = OPS.keys.map { |k| Regexp.quote k }.join "|"
|
|
17
|
+
PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Hex::Version::VERSION_PATTERN})\\s*"
|
|
18
|
+
PATTERN = /\A#{PATTERN_RAW}\z/.freeze
|
|
19
|
+
|
|
20
|
+
# Returns an array of requirements. At least one requirement from the
|
|
21
|
+
# returned array must be satisfied for a version to be valid.
|
|
22
|
+
def self.requirements_array(requirement_string)
|
|
23
|
+
requirement_string.strip.split(OR_SEPARATOR).map do |req_string|
|
|
24
|
+
requirements = req_string.strip.split(AND_SEPARATOR)
|
|
25
|
+
new(requirements)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Override the parser to create Hex::Versions
|
|
30
|
+
def self.parse(obj)
|
|
31
|
+
return ["=", Hex::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
|
32
|
+
|
|
33
|
+
unless (matches = PATTERN.match(obj.to_s))
|
|
34
|
+
msg = "Illformed requirement [#{obj.inspect}]"
|
|
35
|
+
raise BadRequirementError, msg
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
|
|
39
|
+
|
|
40
|
+
[matches[1] || "=", Hex::Version.new(matches[2])]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def satisfied_by?(version)
|
|
44
|
+
version = Hex::Version.new(version.to_s)
|
|
45
|
+
|
|
46
|
+
requirements.all? { |op, rv| (OPS[op] || OPS["="]).call(version, rv) }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Dependabot::Utils.
|
|
53
|
+
register_requirement_class("hex", Dependabot::Hex::Requirement)
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "excon"
|
|
4
|
+
require "dependabot/git_commit_checker"
|
|
5
|
+
require "dependabot/update_checkers"
|
|
6
|
+
require "dependabot/update_checkers/base"
|
|
7
|
+
require "dependabot/shared_helpers"
|
|
8
|
+
|
|
9
|
+
require "json"
|
|
10
|
+
|
|
11
|
+
module Dependabot
|
|
12
|
+
module Hex
|
|
13
|
+
class UpdateChecker < Dependabot::UpdateCheckers::Base
|
|
14
|
+
require_relative "update_checker/file_preparer"
|
|
15
|
+
require_relative "update_checker/requirements_updater"
|
|
16
|
+
require_relative "update_checker/version_resolver"
|
|
17
|
+
|
|
18
|
+
def latest_version
|
|
19
|
+
@latest_version ||=
|
|
20
|
+
if git_dependency?
|
|
21
|
+
latest_version_for_git_dependency
|
|
22
|
+
else
|
|
23
|
+
latest_release_from_hex_registry || latest_resolvable_version
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def latest_resolvable_version
|
|
28
|
+
@latest_resolvable_version ||=
|
|
29
|
+
if git_dependency?
|
|
30
|
+
latest_resolvable_version_for_git_dependency
|
|
31
|
+
else
|
|
32
|
+
fetch_latest_resolvable_version(unlock_requirement: true)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def latest_resolvable_version_with_no_unlock
|
|
37
|
+
@latest_resolvable_version_with_no_unlock ||=
|
|
38
|
+
if git_dependency?
|
|
39
|
+
latest_resolvable_commit_with_unchanged_git_source
|
|
40
|
+
else
|
|
41
|
+
fetch_latest_resolvable_version(unlock_requirement: false)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def updated_requirements
|
|
46
|
+
RequirementsUpdater.new(
|
|
47
|
+
requirements: dependency.requirements,
|
|
48
|
+
updated_source: updated_source,
|
|
49
|
+
latest_resolvable_version: latest_resolvable_version&.to_s
|
|
50
|
+
).updated_requirements
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def latest_version_resolvable_with_full_unlock?
|
|
56
|
+
# Full unlock checks aren't implemented for Elixir (yet)
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def updated_dependencies_after_full_unlock
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def latest_version_for_git_dependency
|
|
65
|
+
latest_git_version_sha
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def latest_resolvable_version_for_git_dependency
|
|
69
|
+
# If the gem isn't pinned, the latest version is just the latest
|
|
70
|
+
# commit for the specified branch.
|
|
71
|
+
unless git_commit_checker.pinned?
|
|
72
|
+
return latest_resolvable_commit_with_unchanged_git_source
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# If the dependency is pinned to a tag that looks like a version then
|
|
76
|
+
# we want to update that tag. The latest version will then be the SHA
|
|
77
|
+
# of the latest tag that looks like a version.
|
|
78
|
+
if git_commit_checker.pinned_ref_looks_like_version? &&
|
|
79
|
+
latest_git_tag_is_resolvable?
|
|
80
|
+
new_tag = git_commit_checker.local_tag_for_latest_version
|
|
81
|
+
return new_tag.fetch(:commit_sha)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# If the dependency is pinned then there's nothing we can do.
|
|
85
|
+
dependency.version
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def latest_resolvable_commit_with_unchanged_git_source
|
|
89
|
+
fetch_latest_resolvable_version(unlock_requirement: false)
|
|
90
|
+
rescue SharedHelpers::HelperSubprocessFailed,
|
|
91
|
+
Dependabot::DependencyFileNotResolvable => error
|
|
92
|
+
# Resolution may fail, as Elixir updates straight to the tip of the
|
|
93
|
+
# branch. Just return `nil` if it does (so no update).
|
|
94
|
+
return if error.message.include?("resolution failed")
|
|
95
|
+
|
|
96
|
+
raise error
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def git_dependency?
|
|
100
|
+
git_commit_checker.git_dependency?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def latest_git_version_sha
|
|
104
|
+
# If the gem isn't pinned, the latest version is just the latest
|
|
105
|
+
# commit for the specified branch.
|
|
106
|
+
unless git_commit_checker.pinned?
|
|
107
|
+
return git_commit_checker.head_commit_for_current_branch
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# If the dependency is pinned to a tag that looks like a version then
|
|
111
|
+
# we want to update that tag. The latest version will then be the SHA
|
|
112
|
+
# of the latest tag that looks like a version.
|
|
113
|
+
if git_commit_checker.pinned_ref_looks_like_version?
|
|
114
|
+
latest_tag = git_commit_checker.local_tag_for_latest_version
|
|
115
|
+
return latest_tag&.fetch(:commit_sha) || dependency.version
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# If the dependency is pinned to a tag that doesn't look like a
|
|
119
|
+
# version then there's nothing we can do.
|
|
120
|
+
dependency.version
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def latest_git_tag_is_resolvable?
|
|
124
|
+
return @git_tag_resolvable if @latest_git_tag_is_resolvable_checked
|
|
125
|
+
|
|
126
|
+
@latest_git_tag_is_resolvable_checked = true
|
|
127
|
+
|
|
128
|
+
return false if git_commit_checker.local_tag_for_latest_version.nil?
|
|
129
|
+
|
|
130
|
+
replacement_tag = git_commit_checker.local_tag_for_latest_version
|
|
131
|
+
|
|
132
|
+
prepared_files = FilePreparer.new(
|
|
133
|
+
dependency: dependency,
|
|
134
|
+
dependency_files: dependency_files,
|
|
135
|
+
replacement_git_pin: replacement_tag.fetch(:tag)
|
|
136
|
+
).prepared_dependency_files
|
|
137
|
+
|
|
138
|
+
resolver_result = VersionResolver.new(
|
|
139
|
+
dependency: dependency,
|
|
140
|
+
prepared_dependency_files: prepared_files,
|
|
141
|
+
original_dependency_files: dependency_files,
|
|
142
|
+
credentials: credentials
|
|
143
|
+
).latest_resolvable_version
|
|
144
|
+
|
|
145
|
+
@git_tag_resolvable = !resolver_result.nil?
|
|
146
|
+
rescue SharedHelpers::HelperSubprocessFailed,
|
|
147
|
+
Dependabot::DependencyFileNotResolvable => error
|
|
148
|
+
raise error unless error.message.include?("resolution failed")
|
|
149
|
+
|
|
150
|
+
@git_tag_resolvable = false
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def updated_source
|
|
154
|
+
# Never need to update source, unless a git_dependency
|
|
155
|
+
return dependency_source_details unless git_dependency?
|
|
156
|
+
|
|
157
|
+
# Update the git tag if updating a pinned version
|
|
158
|
+
if git_commit_checker.pinned_ref_looks_like_version? &&
|
|
159
|
+
latest_git_tag_is_resolvable?
|
|
160
|
+
new_tag = git_commit_checker.local_tag_for_latest_version
|
|
161
|
+
return dependency_source_details.merge(ref: new_tag.fetch(:tag))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Otherwise return the original source
|
|
165
|
+
dependency_source_details
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def dependency_source_details
|
|
169
|
+
sources =
|
|
170
|
+
dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact
|
|
171
|
+
|
|
172
|
+
raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
|
|
173
|
+
|
|
174
|
+
sources.first
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def fetch_latest_resolvable_version(unlock_requirement:)
|
|
178
|
+
@latest_resolvable_version_hash ||= {}
|
|
179
|
+
@latest_resolvable_version_hash[unlock_requirement] ||=
|
|
180
|
+
version_resolver(unlock_requirement: unlock_requirement).
|
|
181
|
+
latest_resolvable_version
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def version_resolver(unlock_requirement:)
|
|
185
|
+
@version_resolver ||= {}
|
|
186
|
+
@version_resolver[unlock_requirement] ||=
|
|
187
|
+
begin
|
|
188
|
+
prepared_dependency_files = prepared_dependency_files(
|
|
189
|
+
unlock_requirement: unlock_requirement,
|
|
190
|
+
latest_allowable_version: latest_release_from_hex_registry
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
VersionResolver.new(
|
|
194
|
+
dependency: dependency,
|
|
195
|
+
prepared_dependency_files: prepared_dependency_files,
|
|
196
|
+
original_dependency_files: dependency_files,
|
|
197
|
+
credentials: credentials
|
|
198
|
+
)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def prepared_dependency_files(unlock_requirement:,
|
|
203
|
+
latest_allowable_version: nil)
|
|
204
|
+
FilePreparer.new(
|
|
205
|
+
dependency: dependency,
|
|
206
|
+
dependency_files: dependency_files,
|
|
207
|
+
unlock_requirement: unlock_requirement,
|
|
208
|
+
latest_allowable_version: latest_allowable_version
|
|
209
|
+
).prepared_dependency_files
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def latest_release_from_hex_registry
|
|
213
|
+
@latest_release_from_hex_registry ||=
|
|
214
|
+
begin
|
|
215
|
+
versions = hex_registry_response&.fetch("releases", []) || []
|
|
216
|
+
versions =
|
|
217
|
+
versions.
|
|
218
|
+
select { |release| version_class.correct?(release["version"]) }.
|
|
219
|
+
map { |release| version_class.new(release["version"]) }
|
|
220
|
+
|
|
221
|
+
versions.reject!(&:prerelease?) unless wants_prerelease?
|
|
222
|
+
versions.reject! do |v|
|
|
223
|
+
ignore_reqs.any? { |r| r.satisfied_by?(v) }
|
|
224
|
+
end
|
|
225
|
+
versions.max
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def hex_registry_response
|
|
230
|
+
return @hex_registry_response if @hex_registry_requested
|
|
231
|
+
|
|
232
|
+
@hex_registry_requested = true
|
|
233
|
+
|
|
234
|
+
response = Excon.get(
|
|
235
|
+
dependency_url,
|
|
236
|
+
idempotent: true,
|
|
237
|
+
**SharedHelpers.excon_defaults
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return unless response.status == 200
|
|
241
|
+
|
|
242
|
+
@hex_registry_response = JSON.parse(response.body)
|
|
243
|
+
rescue Excon::Error::Socket, Excon::Error::Timeout
|
|
244
|
+
nil
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def wants_prerelease?
|
|
248
|
+
current_version = dependency.version
|
|
249
|
+
if current_version &&
|
|
250
|
+
version_class.correct?(current_version) &&
|
|
251
|
+
version_class.new(current_version).prerelease?
|
|
252
|
+
return true
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
dependency.requirements.any? do |req|
|
|
256
|
+
req[:requirement]&.match?(/\d-[A-Za-z0-9]/)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def dependency_url
|
|
261
|
+
"https://hex.pm/api/packages/#{dependency.name}"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def git_commit_checker
|
|
265
|
+
@git_commit_checker ||=
|
|
266
|
+
GitCommitChecker.new(
|
|
267
|
+
dependency: dependency,
|
|
268
|
+
credentials: credentials
|
|
269
|
+
)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
Dependabot::UpdateCheckers.register("hex", Dependabot::Hex::UpdateChecker)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dependabot/dependency_file"
|
|
4
|
+
require "dependabot/hex/update_checker"
|
|
5
|
+
require "dependabot/hex/file_updater/mixfile_requirement_updater"
|
|
6
|
+
require "dependabot/hex/file_updater/mixfile_git_pin_updater"
|
|
7
|
+
require "dependabot/hex/file_updater/mixfile_sanitizer"
|
|
8
|
+
require "dependabot/hex/version"
|
|
9
|
+
|
|
10
|
+
module Dependabot
|
|
11
|
+
module Hex
|
|
12
|
+
class UpdateChecker
|
|
13
|
+
# This class takes a set of dependency files and sanitizes them for use
|
|
14
|
+
# in UpdateCheckers::Elixir::Hex.
|
|
15
|
+
class FilePreparer
|
|
16
|
+
def initialize(dependency_files:, dependency:,
|
|
17
|
+
unlock_requirement: true,
|
|
18
|
+
replacement_git_pin: nil,
|
|
19
|
+
latest_allowable_version: nil)
|
|
20
|
+
@dependency_files = dependency_files
|
|
21
|
+
@dependency = dependency
|
|
22
|
+
@unlock_requirement = unlock_requirement
|
|
23
|
+
@replacement_git_pin = replacement_git_pin
|
|
24
|
+
@latest_allowable_version = latest_allowable_version
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def prepared_dependency_files
|
|
28
|
+
files = []
|
|
29
|
+
files += mixfiles.map do |file|
|
|
30
|
+
DependencyFile.new(
|
|
31
|
+
name: file.name,
|
|
32
|
+
content: mixfile_content_for_update_check(file),
|
|
33
|
+
directory: file.directory
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
files << lockfile if lockfile
|
|
37
|
+
files += support_files
|
|
38
|
+
files
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :dependency_files, :dependency, :replacement_git_pin,
|
|
44
|
+
:latest_allowable_version
|
|
45
|
+
|
|
46
|
+
def unlock_requirement?
|
|
47
|
+
@unlock_requirement
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def replace_git_pin?
|
|
51
|
+
!replacement_git_pin.nil?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def mixfile_content_for_update_check(file)
|
|
55
|
+
content = file.content
|
|
56
|
+
|
|
57
|
+
unless dependency_appears_in_file?(file.name)
|
|
58
|
+
return sanitize_mixfile(content)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
content = relax_version(content, filename: file.name)
|
|
62
|
+
if replace_git_pin?
|
|
63
|
+
content = replace_git_pin(content, filename: file.name)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
sanitize_mixfile(content)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def relax_version(content, filename:)
|
|
70
|
+
old_requirement =
|
|
71
|
+
dependency.requirements.find { |r| r.fetch(:file) == filename }.
|
|
72
|
+
fetch(:requirement)
|
|
73
|
+
|
|
74
|
+
Hex::FileUpdater::MixfileRequirementUpdater.new(
|
|
75
|
+
dependency_name: dependency.name,
|
|
76
|
+
mixfile_content: content,
|
|
77
|
+
previous_requirement: old_requirement,
|
|
78
|
+
updated_requirement: updated_version_requirement_string(filename),
|
|
79
|
+
insert_if_bare: true
|
|
80
|
+
).updated_content
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def updated_version_requirement_string(filename)
|
|
84
|
+
lower_bound_req = updated_version_req_lower_bound(filename)
|
|
85
|
+
|
|
86
|
+
return lower_bound_req if latest_allowable_version.nil?
|
|
87
|
+
unless version_class.correct?(latest_allowable_version)
|
|
88
|
+
return lower_bound_req
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
lower_bound_req + " and <= #{latest_allowable_version}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# rubocop:disable Metrics/AbcSize
|
|
95
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
96
|
+
def updated_version_req_lower_bound(filename)
|
|
97
|
+
original_req = dependency.requirements.
|
|
98
|
+
find { |r| r.fetch(:file) == filename }&.
|
|
99
|
+
fetch(:requirement)
|
|
100
|
+
|
|
101
|
+
if original_req && !unlock_requirement? then original_req
|
|
102
|
+
elsif dependency.version&.match?(/^[0-9a-f]{40}$/) then ">= 0"
|
|
103
|
+
elsif dependency.version then ">= #{dependency.version}"
|
|
104
|
+
else
|
|
105
|
+
version_for_requirement =
|
|
106
|
+
dependency.requirements.map { |r| r[:requirement] }.compact.
|
|
107
|
+
reject { |req_string| req_string.start_with?("<") }.
|
|
108
|
+
select { |req_string| req_string.match?(version_regex) }.
|
|
109
|
+
map { |req_string| req_string.match(version_regex) }.
|
|
110
|
+
select { |version| version_class.correct?(version.to_s) }.
|
|
111
|
+
max_by { |version| version_class.new(version.to_s) }
|
|
112
|
+
|
|
113
|
+
return ">= 0" unless version_for_requirement
|
|
114
|
+
|
|
115
|
+
# Elixir requires that versions are specified to three places
|
|
116
|
+
# when used with a >= specifier
|
|
117
|
+
parts = version_for_requirement.to_s.split(".")
|
|
118
|
+
parts << "0" while parts.count < 3
|
|
119
|
+
">= #{parts.join('.')}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
# rubocop:enable Metrics/AbcSize
|
|
123
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
124
|
+
|
|
125
|
+
def replace_git_pin(content, filename:)
|
|
126
|
+
old_pin =
|
|
127
|
+
dependency.requirements.find { |r| r.fetch(:file) == filename }&.
|
|
128
|
+
dig(:source, :ref)
|
|
129
|
+
|
|
130
|
+
return content unless old_pin
|
|
131
|
+
return content if old_pin == replacement_git_pin
|
|
132
|
+
|
|
133
|
+
Hex::FileUpdater::MixfileGitPinUpdater.new(
|
|
134
|
+
dependency_name: dependency.name,
|
|
135
|
+
mixfile_content: content,
|
|
136
|
+
previous_pin: old_pin,
|
|
137
|
+
updated_pin: replacement_git_pin
|
|
138
|
+
).updated_content
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def sanitize_mixfile(content)
|
|
142
|
+
Hex::FileUpdater::MixfileSanitizer.new(
|
|
143
|
+
mixfile_content: content
|
|
144
|
+
).sanitized_content
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def mixfiles
|
|
148
|
+
mixfiles =
|
|
149
|
+
dependency_files.
|
|
150
|
+
select { |f| f.name.end_with?("mix.exs") }
|
|
151
|
+
raise "No mix.exs!" if mixfiles.none?
|
|
152
|
+
|
|
153
|
+
mixfiles
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def lockfile
|
|
157
|
+
@lockfile ||= dependency_files.find { |f| f.name == "mix.lock" }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def support_files
|
|
161
|
+
@support_files ||= dependency_files.select(&:support_file)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def wants_prerelease?
|
|
165
|
+
current_version = dependency.version
|
|
166
|
+
if current_version &&
|
|
167
|
+
version_class.correct?(current_version) &&
|
|
168
|
+
version_class.new(current_version).prerelease?
|
|
169
|
+
return true
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
dependency.requirements.any? do |req|
|
|
173
|
+
req[:requirement].match?(/\d-[A-Za-z0-9]/)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def version_class
|
|
178
|
+
Hex::Version
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def version_regex
|
|
182
|
+
version_class::VERSION_PATTERN
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def dependency_appears_in_file?(file_name)
|
|
186
|
+
dependency.requirements.any? { |r| r[:file] == file_name }
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|