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,397 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "toml-rb"
4
-
5
- require "python_requirement_parser"
6
- require "dependabot/file_updaters/python/pip"
7
- require "dependabot/shared_helpers"
8
-
9
- # rubocop:disable Metrics/ClassLength
10
- module Dependabot
11
- module FileUpdaters
12
- module Python
13
- class Pip
14
- class PipfileFileUpdater
15
- require_relative "pipfile_preparer"
16
- require_relative "setup_file_sanitizer"
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?(pipfile)
44
- updated_files <<
45
- updated_file(file: pipfile, content: updated_pipfile_content)
46
- end
47
-
48
- if lockfile
49
- if lockfile.content == updated_lockfile_content
50
- raise "Expected Pipfile.lock to change!"
51
- end
52
-
53
- updated_files <<
54
- updated_file(file: lockfile, content: updated_lockfile_content)
55
- end
56
-
57
- updated_files += updated_generated_requirements_files
58
- updated_files
59
- end
60
-
61
- def updated_pipfile_content
62
- dependencies.
63
- select { |dep| requirement_changed?(pipfile, dep) }.
64
- reduce(pipfile.content.dup) do |content, dep|
65
- updated_requirement =
66
- dep.requirements.find { |r| r[:file] == pipfile.name }.
67
- fetch(:requirement)
68
-
69
- old_req =
70
- dep.previous_requirements.
71
- find { |r| r[:file] == pipfile.name }.
72
- fetch(:requirement)
73
-
74
- updated_content =
75
- content.gsub(declaration_regex(dep)) do |line|
76
- line.gsub(old_req, updated_requirement)
77
- end
78
-
79
- raise "Content did not change!" if content == updated_content
80
-
81
- updated_content
82
- end
83
- end
84
-
85
- def updated_lockfile_content
86
- @updated_lockfile_content ||=
87
- updated_generated_files.fetch(:lockfile)
88
- end
89
-
90
- def generate_updated_requirements_files?
91
- return true if generated_requirements_files("default").any?
92
-
93
- generated_requirements_files("develop").any?
94
- end
95
-
96
- def generated_requirements_files(type)
97
- return [] unless lockfile
98
-
99
- pipfile_lock_deps = parsed_lockfile[type]&.keys&.sort || []
100
- pipfile_lock_deps = pipfile_lock_deps.map { |n| normalise(n) }
101
-
102
- regex = PythonRequirementParser::INSTALL_REQ_WITH_REQUIREMENT
103
-
104
- # Find any requirement files that list the same dependencies as
105
- # the (old) Pipfile.lock. Any such files were almost certainly
106
- # generated using `pipenv lock -r`
107
- requirements_files.select do |req_file|
108
- deps = []
109
- req_file.content.scan(regex) { deps << Regexp.last_match }
110
- deps = deps.map { |m| normalise(m[:name]) }
111
- deps.sort == pipfile_lock_deps
112
- end
113
- end
114
-
115
- def updated_generated_requirements_files
116
- updated_files = []
117
-
118
- generated_requirements_files("default").each do |file|
119
- next if file.content == updated_req_content
120
-
121
- updated_files <<
122
- updated_file(file: file, content: updated_req_content)
123
- end
124
-
125
- generated_requirements_files("develop").each do |file|
126
- next if file.content == updated_dev_req_content
127
-
128
- updated_files <<
129
- updated_file(file: file, content: updated_dev_req_content)
130
- end
131
-
132
- updated_files
133
- end
134
-
135
- def updated_req_content
136
- updated_generated_files.fetch(:requirements_txt)
137
- end
138
-
139
- def updated_dev_req_content
140
- updated_generated_files.fetch(:dev_requirements_txt)
141
- end
142
-
143
- def prepared_pipfile_content
144
- content = updated_pipfile_content
145
- content = freeze_other_dependencies(content)
146
- content = freeze_dependencies_being_updated(content)
147
- content = add_private_sources(content)
148
- content
149
- end
150
-
151
- def freeze_other_dependencies(pipfile_content)
152
- PipfilePreparer.
153
- new(pipfile_content: pipfile_content).
154
- freeze_top_level_dependencies_except(dependencies, lockfile)
155
- end
156
-
157
- def freeze_dependencies_being_updated(pipfile_content)
158
- pipfile_object = TomlRB.parse(pipfile_content)
159
-
160
- dependencies.each do |dep|
161
- %w(packages dev-packages).each do |type|
162
- names = pipfile_object[type]&.keys || []
163
- pkg_name = names.find { |nm| normalise(nm) == dep.name }
164
- next unless pkg_name
165
-
166
- if pipfile_object[type][pkg_name].is_a?(Hash)
167
- pipfile_object[type][pkg_name]["version"] =
168
- "==#{dep.version}"
169
- else
170
- pipfile_object[type][pkg_name] = "==#{dep.version}"
171
- end
172
- end
173
- end
174
-
175
- TomlRB.dump(pipfile_object)
176
- end
177
-
178
- def add_private_sources(pipfile_content)
179
- PipfilePreparer.
180
- new(pipfile_content: pipfile_content).
181
- replace_sources(credentials)
182
- end
183
-
184
- def updated_generated_files
185
- @updated_generated_files ||=
186
- SharedHelpers.in_a_temporary_directory do
187
- SharedHelpers.with_git_configured(credentials: credentials) do
188
- write_temporary_dependency_files(prepared_pipfile_content)
189
-
190
- # Initialize a git repo to appease pip-tools
191
- IO.popen("git init", err: %i(child out)) if setup_files.any?
192
-
193
- run_pipenv_command(
194
- pipenv_environment_variables + "pyenv exec pipenv lock"
195
- )
196
-
197
- result = { lockfile: File.read("Pipfile.lock") }
198
- result[:lockfile] = post_process_lockfile(result[:lockfile])
199
-
200
- # Generate updated requirement.txt entries, if needed.
201
- if generate_updated_requirements_files?
202
- generate_updated_requirements_files
203
-
204
- result[:requirements_txt] = File.read("req.txt")
205
- result[:dev_requirements_txt] = File.read("dev-req.txt")
206
- end
207
-
208
- result
209
- end
210
- end
211
- end
212
-
213
- def post_process_lockfile(updated_lockfile_content)
214
- pipfile_hash = pipfile_hash_for(updated_pipfile_content)
215
- original_reqs = parsed_lockfile["_meta"]["requires"]
216
- original_source = parsed_lockfile["_meta"]["sources"]
217
-
218
- new_lockfile = updated_lockfile_content.dup
219
- new_lockfile_json = JSON.parse(new_lockfile)
220
- new_lockfile_json["_meta"]["hash"]["sha256"] = pipfile_hash
221
- new_lockfile_json["_meta"]["requires"] = original_reqs
222
- new_lockfile_json["_meta"]["sources"] = original_source
223
-
224
- JSON.pretty_generate(new_lockfile_json, indent: " ").
225
- gsub(/\{\n\s*\}/, "{}").
226
- gsub(/\}\z/, "}\n")
227
- end
228
-
229
- def generate_updated_requirements_files
230
- run_pipenv_command(
231
- pipenv_environment_variables +
232
- "pyenv exec pipenv lock -r > req.txt"
233
- )
234
- run_pipenv_command(
235
- pipenv_environment_variables +
236
- "pyenv exec pipenv lock -r -d > dev-req.txt"
237
- )
238
- end
239
-
240
- def run_pipenv_command(cmd)
241
- raw_response = nil
242
- IO.popen(cmd, err: %i(child out)) { |p| raw_response = p.read }
243
-
244
- # Raise an error with the output from the shell session if Pipenv
245
- # returns a non-zero status
246
- return if $CHILD_STATUS.success?
247
-
248
- raise SharedHelpers::HelperSubprocessFailed.new(raw_response, cmd)
249
- rescue SharedHelpers::HelperSubprocessFailed => error
250
- original_error ||= error
251
- msg = error.message
252
-
253
- relevant_error =
254
- if error_suggests_bad_python_version?(msg) then original_error
255
- else error
256
- end
257
-
258
- raise relevant_error unless error_suggests_bad_python_version?(msg)
259
- raise relevant_error if cmd.include?("--two")
260
-
261
- cmd = cmd.gsub("pipenv ", "pipenv --two ")
262
- retry
263
- end
264
-
265
- def error_suggests_bad_python_version?(message)
266
- return true if message.include?("UnsupportedPythonVersion")
267
-
268
- message.include?('Command "python setup.py egg_info" failed')
269
- end
270
-
271
- def write_temporary_dependency_files(pipfile_content)
272
- dependency_files.each do |file|
273
- next if file.name == ".python-version"
274
-
275
- path = file.name
276
- FileUtils.mkdir_p(Pathname.new(path).dirname)
277
- File.write(path, file.content)
278
- end
279
-
280
- setup_files.each do |file|
281
- path = file.name
282
- FileUtils.mkdir_p(Pathname.new(path).dirname)
283
- File.write(path, sanitized_setup_file_content(file))
284
- end
285
-
286
- setup_cfg_files.each do |file|
287
- path = file.name
288
- FileUtils.mkdir_p(Pathname.new(path).dirname)
289
- File.write(path, "[metadata]\nname = sanitized-package\n")
290
- end
291
-
292
- # Overwrite the pipfile with updated content
293
- File.write("Pipfile", pipfile_content)
294
- end
295
-
296
- def sanitized_setup_file_content(file)
297
- @sanitized_setup_file_content ||= {}
298
- if @sanitized_setup_file_content[file.name]
299
- return @sanitized_setup_file_content[file.name]
300
- end
301
-
302
- @sanitized_setup_file_content[file.name] =
303
- SetupFileSanitizer.
304
- new(setup_file: file, setup_cfg: setup_cfg(file)).
305
- sanitized_content
306
- end
307
-
308
- def setup_cfg(file)
309
- dependency_files.find do |f|
310
- f.name == file.name.sub(/\.py$/, ".cfg")
311
- end
312
- end
313
-
314
- def pipfile_hash_for(pipfile_content)
315
- SharedHelpers.in_a_temporary_directory do |dir|
316
- File.write(File.join(dir, "Pipfile"), pipfile_content)
317
- SharedHelpers.run_helper_subprocess(
318
- command: "pyenv exec python #{python_helper_path}",
319
- function: "get_pipfile_hash",
320
- args: [dir]
321
- )
322
- end
323
- end
324
-
325
- def declaration_regex(dep)
326
- escaped_name = Regexp.escape(dep.name).gsub("\\-", "[-_.]")
327
- /(?:^|["'])#{escaped_name}["']?\s*=.*$/i
328
- end
329
-
330
- def file_changed?(file)
331
- dependencies.any? { |dep| requirement_changed?(file, dep) }
332
- end
333
-
334
- def requirement_changed?(file, dependency)
335
- changed_requirements =
336
- dependency.requirements - dependency.previous_requirements
337
-
338
- changed_requirements.any? { |f| f[:file] == file.name }
339
- end
340
-
341
- def updated_file(file:, content:)
342
- updated_file = file.dup
343
- updated_file.content = content
344
- updated_file
345
- end
346
-
347
- def python_helper_path
348
- project_root = File.join(File.dirname(__FILE__), "../../../../..")
349
- File.join(project_root, "helpers/python/run.py")
350
- end
351
-
352
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
353
- def normalise(name)
354
- name.downcase.gsub(/[-_.]+/, "-")
355
- end
356
-
357
- def parsed_lockfile
358
- @parsed_lockfile ||= JSON.parse(lockfile.content)
359
- end
360
-
361
- def pipfile
362
- @pipfile ||= dependency_files.find { |f| f.name == "Pipfile" }
363
- end
364
-
365
- def lockfile
366
- @lockfile ||= dependency_files.find { |f| f.name == "Pipfile.lock" }
367
- end
368
-
369
- def setup_files
370
- dependency_files.select { |f| f.name.end_with?("setup.py") }
371
- end
372
-
373
- def setup_cfg_files
374
- dependency_files.select { |f| f.name.end_with?("setup.cfg") }
375
- end
376
-
377
- def requirements_files
378
- dependency_files.select { |f| f.name.end_with?(".txt") }
379
- end
380
-
381
- def pipenv_environment_variables
382
- environment_variables = [
383
- "PIPENV_YES=true", # Install new Python versions if needed
384
- "PIPENV_MAX_RETRIES=3", # Retry timeouts
385
- "PIPENV_NOSPIN=1", # Don't pollute logs with spinner
386
- "PIPENV_TIMEOUT=600", # Set install timeout to 10 minutes
387
- "PIP_DEFAULT_TIMEOUT=60" # Set pip timeout to 1 minute
388
- ]
389
-
390
- environment_variables.join(" ") + " "
391
- end
392
- end
393
- end
394
- end
395
- end
396
- end
397
- # rubocop:enable Metrics/ClassLength
@@ -1,125 +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 PipfilePreparer
13
- def initialize(pipfile_content:)
14
- @pipfile_content = pipfile_content
15
- end
16
-
17
- def replace_sources(credentials)
18
- pipfile_object = TomlRB.parse(pipfile_content)
19
-
20
- pipfile_object["source"] =
21
- pipfile_sources.reject { |h| h["url"].include?("${") } +
22
- config_variable_sources(credentials)
23
-
24
- TomlRB.dump(pipfile_object)
25
- end
26
-
27
- def freeze_top_level_dependencies_except(dependencies, lockfile)
28
- return pipfile_content unless lockfile
29
-
30
- pipfile_object = TomlRB.parse(pipfile_content)
31
- excluded_names = dependencies.map(&:name)
32
-
33
- FileParsers::Python::Pip::DEPENDENCY_GROUP_KEYS.each do |keys|
34
- next unless pipfile_object[keys[:pipfile]]
35
-
36
- pipfile_object.fetch(keys[:pipfile]).each do |dep_name, _|
37
- next if excluded_names.include?(normalise(dep_name))
38
-
39
- freeze_dependency(dep_name, pipfile_object, lockfile, keys)
40
- end
41
- end
42
-
43
- TomlRB.dump(pipfile_object)
44
- end
45
-
46
- # rubocop:disable Metrics/PerceivedComplexity
47
- def freeze_dependency(dep_name, pipfile_object, lockfile, keys)
48
- locked_version = version_from_lockfile(
49
- lockfile,
50
- keys[:lockfile],
51
- normalise(dep_name)
52
- )
53
- locked_ref = ref_from_lockfile(
54
- lockfile,
55
- keys[:lockfile],
56
- normalise(dep_name)
57
- )
58
-
59
- pipfile_req = pipfile_object[keys[:pipfile]][dep_name]
60
- if pipfile_req.is_a?(Hash) && locked_version
61
- pipfile_req["version"] = "==#{locked_version}"
62
- elsif pipfile_req.is_a?(Hash) && locked_ref && !pipfile_req["ref"]
63
- pipfile_req["ref"] = locked_ref
64
- elsif locked_version
65
- pipfile_object[keys[:pipfile]][dep_name] = "==#{locked_version}"
66
- end
67
- end
68
- # rubocop:enable Metrics/PerceivedComplexity
69
-
70
- def update_python_requirement(requirement)
71
- pipfile_object = TomlRB.parse(pipfile_content)
72
-
73
- pipfile_object["requires"] ||= {}
74
- pipfile_object["requires"].delete("python_full_version")
75
- pipfile_object["requires"].delete("python_version")
76
- pipfile_object["requires"]["python_full_version"] = requirement
77
-
78
- TomlRB.dump(pipfile_object)
79
- end
80
-
81
- private
82
-
83
- attr_reader :pipfile_content
84
-
85
- def version_from_lockfile(lockfile, dep_type, dep_name)
86
- details = JSON.parse(lockfile.content).
87
- dig(dep_type, normalise(dep_name))
88
-
89
- case details
90
- when String then details.gsub(/^==/, "")
91
- when Hash then details["version"]&.gsub(/^==/, "")
92
- end
93
- end
94
-
95
- def ref_from_lockfile(lockfile, dep_type, dep_name)
96
- details = JSON.parse(lockfile.content).
97
- dig(dep_type, normalise(dep_name))
98
-
99
- case details
100
- when Hash then details["ref"]
101
- end
102
- end
103
-
104
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
105
- def normalise(name)
106
- name.downcase.gsub(/[-_.]+/, "-")
107
- end
108
-
109
- def pipfile_sources
110
- @pipfile_sources ||=
111
- TomlRB.parse(pipfile_content).fetch("source", []).
112
- map { |h| h.dup.merge("url" => h["url"].gsub(%r{/*$}, "") + "/") }
113
- end
114
-
115
- def config_variable_sources(credentials)
116
- @config_variable_sources ||=
117
- credentials.
118
- select { |cred| cred["type"] == "python_index" }.
119
- map { |cred| { "url" => cred["index-url"] } }
120
- end
121
- end
122
- end
123
- end
124
- end
125
- end