dependabot-core 0.78.0 → 0.79.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.
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