dependabot-core 0.78.0 → 0.79.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/helpers/npm/lib/updater.js +11 -5
  4. data/helpers/npm/package.json +2 -2
  5. data/helpers/npm/yarn.lock +26 -28
  6. data/helpers/yarn/lib/replace-lockfile-declaration.js +15 -3
  7. data/helpers/yarn/lib/updater.js +17 -5
  8. data/helpers/yarn/package.json +2 -2
  9. data/helpers/yarn/yarn.lock +24 -31
  10. data/lib/dependabot/file_fetchers.rb +0 -2
  11. data/lib/dependabot/file_parsers.rb +0 -2
  12. data/lib/dependabot/file_updaters.rb +0 -2
  13. data/lib/dependabot/metadata_finders.rb +0 -2
  14. data/lib/dependabot/update_checkers.rb +0 -2
  15. data/lib/dependabot/utils.rb +0 -4
  16. data/lib/dependabot/version.rb +1 -1
  17. metadata +3 -34
  18. data/helpers/python/lib/__init__.py +0 -0
  19. data/helpers/python/lib/hasher.py +0 -23
  20. data/helpers/python/lib/parser.py +0 -130
  21. data/helpers/python/requirements.txt +0 -9
  22. data/helpers/python/run.py +0 -18
  23. data/lib/dependabot/file_fetchers/python/pip.rb +0 -305
  24. data/lib/dependabot/file_parsers/python/pip.rb +0 -223
  25. data/lib/dependabot/file_parsers/python/pip/pipfile_files_parser.rb +0 -154
  26. data/lib/dependabot/file_parsers/python/pip/poetry_files_parser.rb +0 -141
  27. data/lib/dependabot/file_parsers/python/pip/setup_file_parser.rb +0 -164
  28. data/lib/dependabot/file_updaters/python/pip.rb +0 -147
  29. data/lib/dependabot/file_updaters/python/pip/pip_compile_file_updater.rb +0 -363
  30. data/lib/dependabot/file_updaters/python/pip/pipfile_file_updater.rb +0 -397
  31. data/lib/dependabot/file_updaters/python/pip/pipfile_preparer.rb +0 -125
  32. data/lib/dependabot/file_updaters/python/pip/poetry_file_updater.rb +0 -289
  33. data/lib/dependabot/file_updaters/python/pip/pyproject_preparer.rb +0 -105
  34. data/lib/dependabot/file_updaters/python/pip/requirement_file_updater.rb +0 -166
  35. data/lib/dependabot/file_updaters/python/pip/requirement_replacer.rb +0 -95
  36. data/lib/dependabot/file_updaters/python/pip/setup_file_sanitizer.rb +0 -91
  37. data/lib/dependabot/file_updaters/ruby/.DS_Store +0 -0
  38. data/lib/dependabot/metadata_finders/python/pip.rb +0 -120
  39. data/lib/dependabot/update_checkers/python/pip.rb +0 -227
  40. data/lib/dependabot/update_checkers/python/pip/latest_version_finder.rb +0 -252
  41. data/lib/dependabot/update_checkers/python/pip/pip_compile_version_resolver.rb +0 -380
  42. data/lib/dependabot/update_checkers/python/pip/pipfile_version_resolver.rb +0 -559
  43. data/lib/dependabot/update_checkers/python/pip/poetry_version_resolver.rb +0 -300
  44. data/lib/dependabot/update_checkers/python/pip/requirements_updater.rb +0 -367
  45. data/lib/dependabot/utils/python/requirement.rb +0 -130
  46. data/lib/dependabot/utils/python/version.rb +0 -88
  47. data/lib/python_requirement_parser.rb +0 -33
  48. data/lib/python_versions.rb +0 -21
@@ -1,289 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "toml-rb"
4
-
5
- require "dependabot/file_updaters/python/pip"
6
- require "dependabot/shared_helpers"
7
- require "dependabot/utils/python/version"
8
- require "dependabot/utils/python/requirement"
9
- require "python_versions"
10
-
11
- module Dependabot
12
- module FileUpdaters
13
- module Python
14
- class Pip
15
- class PoetryFileUpdater
16
- require_relative "pyproject_preparer"
17
-
18
- attr_reader :dependencies, :dependency_files, :credentials
19
-
20
- def initialize(dependencies:, dependency_files:, credentials:)
21
- @dependencies = dependencies
22
- @dependency_files = dependency_files
23
- @credentials = credentials
24
- end
25
-
26
- def updated_dependency_files
27
- return @updated_dependency_files if @update_already_attempted
28
-
29
- @update_already_attempted = true
30
- @updated_dependency_files ||= fetch_updated_dependency_files
31
- end
32
-
33
- private
34
-
35
- def dependency
36
- # For now, we'll only ever be updating a single dependency
37
- dependencies.first
38
- end
39
-
40
- def fetch_updated_dependency_files
41
- updated_files = []
42
-
43
- if file_changed?(pyproject)
44
- updated_files <<
45
- updated_file(
46
- file: pyproject,
47
- content: updated_pyproject_content
48
- )
49
- end
50
-
51
- if lockfile && lockfile.content == updated_lockfile_content
52
- raise "Expected lockfile to change!"
53
- end
54
-
55
- if lockfile
56
- updated_files <<
57
- updated_file(file: lockfile, content: updated_lockfile_content)
58
- end
59
-
60
- updated_files
61
- end
62
-
63
- def updated_pyproject_content
64
- dependencies.
65
- select { |dep| requirement_changed?(pyproject, dep) }.
66
- reduce(pyproject.content.dup) do |content, dep|
67
- updated_requirement =
68
- dep.requirements.find { |r| r[:file] == pyproject.name }.
69
- fetch(:requirement)
70
-
71
- old_req =
72
- dep.previous_requirements.
73
- find { |r| r[:file] == pyproject.name }.
74
- fetch(:requirement)
75
-
76
- updated_content =
77
- content.gsub(declaration_regex(dep)) do |line|
78
- line.gsub(old_req, updated_requirement)
79
- end
80
-
81
- raise "Content did not change!" if content == updated_content
82
-
83
- updated_content
84
- end
85
- end
86
-
87
- def updated_lockfile_content
88
- @updated_lockfile_content ||=
89
- begin
90
- new_lockfile = updated_lockfile_content_for(prepared_pyproject)
91
-
92
- tmp_hash =
93
- TomlRB.parse(new_lockfile)["metadata"]["content-hash"]
94
- correct_hash = pyproject_hash_for(updated_pyproject_content)
95
-
96
- new_lockfile.gsub(tmp_hash, correct_hash)
97
- end
98
- end
99
-
100
- def prepared_pyproject
101
- content = updated_pyproject_content
102
- content = freeze_other_dependencies(content)
103
- content = freeze_dependencies_being_updated(content)
104
- content = add_private_sources(content)
105
- content = sanitize(content)
106
- content
107
- end
108
-
109
- def freeze_other_dependencies(pyproject_content)
110
- PyprojectPreparer.
111
- new(pyproject_content: pyproject_content).
112
- freeze_top_level_dependencies_except(dependencies, lockfile)
113
- end
114
-
115
- def freeze_dependencies_being_updated(pyproject_content)
116
- pyproject_object = TomlRB.parse(pyproject_content)
117
- poetry_object = pyproject_object.fetch("tool").fetch("poetry")
118
-
119
- dependencies.each do |dep|
120
- %w(dependencies dev-dependencies).each do |type|
121
- names = poetry_object[type]&.keys || []
122
- pkg_name = names.find { |nm| normalise(nm) == dep.name }
123
- next unless pkg_name
124
-
125
- if poetry_object[type][pkg_name].is_a?(Hash)
126
- poetry_object[type][pkg_name]["version"] = dep.version
127
- else
128
- poetry_object[type][pkg_name] = dep.version
129
- end
130
- end
131
- end
132
-
133
- TomlRB.dump(pyproject_object)
134
- end
135
-
136
- def add_private_sources(pyproject_content)
137
- PyprojectPreparer.
138
- new(pyproject_content: pyproject_content).
139
- replace_sources(credentials)
140
- end
141
-
142
- def sanitize(pyproject_content)
143
- PyprojectPreparer.
144
- new(pyproject_content: pyproject_content).
145
- sanitize
146
- end
147
-
148
- def updated_lockfile_content_for(pyproject_content)
149
- SharedHelpers.in_a_temporary_directory do
150
- write_temporary_dependency_files(pyproject_content)
151
-
152
- if python_version && !pre_installed_python?(python_version)
153
- run_poetry_command("pyenv install -s")
154
- run_poetry_command("pyenv exec pip install --upgrade pip")
155
- run_poetry_command(
156
- "pyenv exec pip install -r #{python_requirements_path}"
157
- )
158
- end
159
-
160
- run_poetry_command("pyenv exec poetry lock")
161
-
162
- return File.read("poetry.lock") if File.exist?("poetry.lock")
163
-
164
- File.read("pyproject.lock")
165
- end
166
- end
167
-
168
- def run_poetry_command(cmd)
169
- raw_response = nil
170
- IO.popen(cmd, err: %i(child out)) { |p| raw_response = p.read }
171
-
172
- # Raise an error with the output from the shell session if Pipenv
173
- # returns a non-zero status
174
- return if $CHILD_STATUS.success?
175
-
176
- raise SharedHelpers::HelperSubprocessFailed.new(raw_response, cmd)
177
- end
178
-
179
- def write_temporary_dependency_files(pyproject_content)
180
- dependency_files.each do |file|
181
- path = file.name
182
- FileUtils.mkdir_p(Pathname.new(path).dirname)
183
- File.write(path, file.content)
184
- end
185
-
186
- # Overwrite the .python-version with updated content
187
- File.write(".python-version", python_version) if python_version
188
-
189
- # Overwrite the pyproject with updated content
190
- File.write("pyproject.toml", pyproject_content)
191
- end
192
-
193
- def python_version
194
- pyproject_object = TomlRB.parse(prepared_pyproject)
195
- poetry_object = pyproject_object.dig("tool", "poetry")
196
-
197
- requirement =
198
- poetry_object&.dig("dependencies", "python") ||
199
- poetry_object&.dig("dev-dependencies", "python")
200
-
201
- return python_version_file&.content unless requirement
202
-
203
- requirements =
204
- Utils::Python::Requirement.requirements_array(requirement)
205
-
206
- PythonVersions::PYTHON_VERSIONS.find do |version|
207
- requirements.any? do |r|
208
- r.satisfied_by?(Utils::Python::Version.new(version))
209
- end
210
- end
211
- end
212
-
213
- def pre_installed_python?(version)
214
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.include?(version)
215
- end
216
-
217
- def pyproject_hash_for(pyproject_content)
218
- SharedHelpers.in_a_temporary_directory do |dir|
219
- File.write(File.join(dir, "pyproject.toml"), pyproject_content)
220
- SharedHelpers.run_helper_subprocess(
221
- command: "pyenv exec python #{python_helper_path}",
222
- function: "get_pyproject_hash",
223
- args: [dir]
224
- )
225
- end
226
- end
227
-
228
- def declaration_regex(dep)
229
- escaped_name = Regexp.escape(dep.name).gsub("\\-", "[-_.]")
230
- /(?:^|["'])#{escaped_name}["']?\s*=.*$/i
231
- end
232
-
233
- def file_changed?(file)
234
- dependencies.any? { |dep| requirement_changed?(file, dep) }
235
- end
236
-
237
- def requirement_changed?(file, dependency)
238
- changed_requirements =
239
- dependency.requirements - dependency.previous_requirements
240
-
241
- changed_requirements.any? { |f| f[:file] == file.name }
242
- end
243
-
244
- def updated_file(file:, content:)
245
- updated_file = file.dup
246
- updated_file.content = content
247
- updated_file
248
- end
249
-
250
- def python_helper_path
251
- project_root = File.join(File.dirname(__FILE__), "../../../../..")
252
- File.join(project_root, "helpers/python/run.py")
253
- end
254
-
255
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
256
- def normalise(name)
257
- name.downcase.gsub(/[-_.]+/, "-")
258
- end
259
-
260
- def pyproject
261
- @pyproject ||=
262
- dependency_files.find { |f| f.name == "pyproject.toml" }
263
- end
264
-
265
- def lockfile
266
- @lockfile ||= pyproject_lock || poetry_lock
267
- end
268
-
269
- def pyproject_lock
270
- dependency_files.find { |f| f.name == "pyproject.lock" }
271
- end
272
-
273
- def poetry_lock
274
- dependency_files.find { |f| f.name == "poetry.lock" }
275
- end
276
-
277
- def python_version_file
278
- dependency_files.find { |f| f.name == ".python-version" }
279
- end
280
-
281
- def python_requirements_path
282
- project_root = File.join(File.dirname(__FILE__), "../../../../..")
283
- File.join(project_root, "helpers/python/requirements.txt")
284
- end
285
- end
286
- end
287
- end
288
- end
289
- end
@@ -1,105 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "toml-rb"
4
-
5
- require "dependabot/file_parsers/python/pip"
6
- require "dependabot/file_updaters/python/pip"
7
-
8
- module Dependabot
9
- module FileUpdaters
10
- module Python
11
- class Pip
12
- class PyprojectPreparer
13
- def initialize(pyproject_content:)
14
- @pyproject_content = pyproject_content
15
- end
16
-
17
- def replace_sources(credentials)
18
- pyproject_object = TomlRB.parse(pyproject_content)
19
- poetry_object = pyproject_object.fetch("tool").fetch("poetry")
20
-
21
- poetry_object["source"] = pyproject_sources +
22
- config_variable_sources(credentials)
23
-
24
- TomlRB.dump(pyproject_object)
25
- end
26
-
27
- def sanitize
28
- # {{ name }} syntax not allowed
29
- pyproject_content.gsub(/\{\{.*?\}\}/, "something")
30
- end
31
-
32
- # rubocop:disable Metrics/PerceivedComplexity
33
- def freeze_top_level_dependencies_except(dependencies, lockfile)
34
- return pyproject_content unless lockfile
35
-
36
- pyproject_object = TomlRB.parse(pyproject_content)
37
- poetry_object = pyproject_object["tool"]["poetry"]
38
- excluded_names = dependencies.map(&:name) + ["python"]
39
-
40
- %w(dependencies dev-dependencies).each do |key|
41
- next unless poetry_object[key]
42
-
43
- poetry_object.fetch(key).each do |dep_name, _|
44
- next if excluded_names.include?(normalise(dep_name))
45
-
46
- locked_details = locked_details(dep_name, lockfile)
47
-
48
- next unless (locked_version = locked_details&.fetch("version"))
49
-
50
- if locked_details&.dig("source", "type") == "git"
51
- poetry_object[key][dep_name] = {
52
- "git" => locked_details&.dig("source", "url"),
53
- "rev" => locked_details&.dig("source", "reference")
54
- }
55
- elsif poetry_object[dep_name].is_a?(Hash)
56
- poetry_object[key][dep_name]["version"] = locked_version
57
- else
58
- poetry_object[key][dep_name] = locked_version
59
- end
60
- end
61
- end
62
-
63
- TomlRB.dump(pyproject_object)
64
- end
65
- # rubocop:enable Metrics/PerceivedComplexity
66
-
67
- private
68
-
69
- attr_reader :pyproject_content
70
-
71
- def locked_details(dep_name, lockfile)
72
- parsed_lockfile = TomlRB.parse(lockfile.content)
73
-
74
- parsed_lockfile.fetch("package").
75
- find { |d| d["name"] == normalise(dep_name) }
76
- end
77
-
78
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
79
- def normalise(name)
80
- name.downcase.gsub(/[-_.]+/, "-")
81
- end
82
-
83
- def pyproject_sources
84
- return @pyproject_sources if @pyproject_sources
85
-
86
- pyproject_sources ||=
87
- TomlRB.parse(pyproject_content).
88
- dig("tool", "poetry", "source")
89
-
90
- @pyproject_sources ||=
91
- (pyproject_sources || []).
92
- map { |h| h.dup.merge("url" => h["url"].gsub(%r{/*$}, "") + "/") }
93
- end
94
-
95
- def config_variable_sources(credentials)
96
- @config_variable_sources ||=
97
- credentials.
98
- select { |cred| cred["type"] == "python_index" }.
99
- map { |cred| { "url" => cred["index-url"] } }
100
- end
101
- end
102
- end
103
- end
104
- end
105
- end
@@ -1,166 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "python_requirement_parser"
4
- require "dependabot/file_updaters/python/pip"
5
- require "dependabot/shared_helpers"
6
-
7
- module Dependabot
8
- module FileUpdaters
9
- module Python
10
- class Pip
11
- class RequirementFileUpdater
12
- attr_reader :dependencies, :dependency_files, :credentials
13
-
14
- def initialize(dependencies:, dependency_files:, credentials:)
15
- @dependencies = dependencies
16
- @dependency_files = dependency_files
17
- @credentials = credentials
18
- end
19
-
20
- def updated_dependency_files
21
- return @updated_dependency_files if @update_already_attempted
22
-
23
- @update_already_attempted = true
24
- @updated_dependency_files ||= fetch_updated_dependency_files
25
- end
26
-
27
- private
28
-
29
- def dependency
30
- # For now, we'll only ever be updating a single dependency
31
- dependencies.first
32
- end
33
-
34
- def fetch_updated_dependency_files
35
- reqs = dependency.requirements.zip(dependency.previous_requirements)
36
-
37
- reqs.map do |(new_req, old_req)|
38
- next if new_req == old_req
39
-
40
- file = get_original_file(new_req.fetch(:file)).dup
41
- updated_content =
42
- updated_requirement_or_setup_file_content(new_req, old_req)
43
- next if updated_content == file.content
44
-
45
- file.content = updated_content
46
- file
47
- end.compact
48
- end
49
-
50
- def updated_requirement_or_setup_file_content(new_req, old_req)
51
- content = get_original_file(new_req.fetch(:file)).content
52
-
53
- updated_content =
54
- content.gsub(
55
- original_declaration_replacement_regex(old_req),
56
- updated_dependency_declaration_string(new_req, old_req)
57
- )
58
-
59
- raise "Expected content to change!" if content == updated_content
60
-
61
- updated_content
62
- end
63
-
64
- def original_dependency_declaration_string(requirement)
65
- regex = PythonRequirementParser::INSTALL_REQ_WITH_REQUIREMENT
66
- matches = []
67
-
68
- get_original_file(requirement.fetch(:file)).
69
- content.scan(regex) { matches << Regexp.last_match }
70
- dec = matches.
71
- select { |m| normalise(m[:name]) == dependency.name }.
72
- find do |m|
73
- # The FileParser can mess up a requirement's spacing so we
74
- # sanitize both requirements before comparing
75
- f_req = m[:requirements]&.gsub(/\s/, "")&.split(",")&.sort
76
- p_req = requirement.fetch(:requirement)&.
77
- gsub(/\s/, "")&.split(",")&.sort
78
- f_req == p_req
79
- end
80
-
81
- raise "Declaration not found for #{dependency.name}!" unless dec
82
-
83
- dec.to_s.strip
84
- end
85
-
86
- def updated_dependency_declaration_string(new_req, old_req)
87
- updated_string =
88
- original_dependency_declaration_string(old_req).sub(
89
- PythonRequirementParser::REQUIREMENTS,
90
- new_req.fetch(:requirement)
91
- )
92
- return updated_string unless requirement_includes_hashes?(old_req)
93
-
94
- updated_string.sub(
95
- PythonRequirementParser::HASHES,
96
- package_hashes_for(
97
- name: dependency.name,
98
- version: dependency.version,
99
- algorithm: hash_algorithm(old_req)
100
- ).join(hash_separator(old_req))
101
- )
102
- end
103
-
104
- def original_declaration_replacement_regex(requirement)
105
- original_string =
106
- original_dependency_declaration_string(requirement)
107
- /(?<![\-\w])#{Regexp.escape(original_string)}(?![\-\w])/
108
- end
109
-
110
- def requirement_includes_hashes?(requirement)
111
- original_dependency_declaration_string(requirement).
112
- match?(PythonRequirementParser::HASHES)
113
- end
114
-
115
- def hash_algorithm(requirement)
116
- return unless requirement_includes_hashes?(requirement)
117
-
118
- original_dependency_declaration_string(requirement).
119
- match(PythonRequirementParser::HASHES).
120
- named_captures.fetch("algorithm")
121
- end
122
-
123
- def hash_separator(requirement)
124
- return unless requirement_includes_hashes?(requirement)
125
-
126
- hash_regex = PythonRequirementParser::HASH
127
- current_separator =
128
- original_dependency_declaration_string(requirement).
129
- match(/#{hash_regex}((?<separator>\s*\\?\s*?)#{hash_regex})*/).
130
- named_captures.fetch("separator")
131
-
132
- default_separator =
133
- original_dependency_declaration_string(requirement).
134
- match(PythonRequirementParser::HASH).
135
- pre_match.match(/(?<separator>\s*\\?\s*?)\z/).
136
- named_captures.fetch("separator")
137
-
138
- current_separator || default_separator
139
- end
140
-
141
- def package_hashes_for(name:, version:, algorithm:)
142
- SharedHelpers.run_helper_subprocess(
143
- command: "pyenv exec python #{python_helper_path}",
144
- function: "get_dependency_hash",
145
- args: [name, version, algorithm]
146
- ).map { |h| "--hash=#{algorithm}:#{h['hash']}" }
147
- end
148
-
149
- def python_helper_path
150
- project_root = File.join(File.dirname(__FILE__), "../../../../..")
151
- File.join(project_root, "helpers/python/run.py")
152
- end
153
-
154
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
155
- def normalise(name)
156
- name.downcase.gsub(/[-_.]+/, "-")
157
- end
158
-
159
- def get_original_file(filename)
160
- dependency_files.find { |f| f.name == filename }
161
- end
162
- end
163
- end
164
- end
165
- end
166
- end