dependabot-bun 0.296.2 → 0.296.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/.eslintrc +11 -0
  3. data/helpers/README.md +29 -0
  4. data/helpers/build +26 -0
  5. data/helpers/jest.config.js +5 -0
  6. data/helpers/lib/npm/conflicting-dependency-parser.js +78 -0
  7. data/helpers/lib/npm/index.js +9 -0
  8. data/helpers/lib/npm/vulnerability-auditor.js +291 -0
  9. data/helpers/lib/npm6/helpers.js +25 -0
  10. data/helpers/lib/npm6/index.js +9 -0
  11. data/helpers/lib/npm6/peer-dependency-checker.js +111 -0
  12. data/helpers/lib/npm6/remove-dependencies-from-lockfile.js +22 -0
  13. data/helpers/lib/npm6/subdependency-updater.js +78 -0
  14. data/helpers/lib/npm6/updater.js +199 -0
  15. data/helpers/lib/pnpm/index.js +5 -0
  16. data/helpers/lib/pnpm/lockfile-parser.js +82 -0
  17. data/helpers/lib/yarn/conflicting-dependency-parser.js +176 -0
  18. data/helpers/lib/yarn/fix-duplicates.js +80 -0
  19. data/helpers/lib/yarn/helpers.js +54 -0
  20. data/helpers/lib/yarn/index.js +14 -0
  21. data/helpers/lib/yarn/lockfile-parser.js +21 -0
  22. data/helpers/lib/yarn/peer-dependency-checker.js +132 -0
  23. data/helpers/lib/yarn/replace-lockfile-declaration.js +57 -0
  24. data/helpers/lib/yarn/subdependency-updater.js +83 -0
  25. data/helpers/lib/yarn/updater.js +209 -0
  26. data/helpers/package-lock.json +28519 -0
  27. data/helpers/package.json +29 -0
  28. data/helpers/patches/npm++pacote+9.5.12.patch +14 -0
  29. data/helpers/run.js +30 -0
  30. data/helpers/test/npm6/conflicting-dependency-parser.test.js +66 -0
  31. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json +591 -0
  32. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
  33. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package-lock.json +188 -0
  34. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
  35. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package-lock.json +27 -0
  36. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
  37. data/helpers/test/npm6/fixtures/updater/original/package-lock.json +16 -0
  38. data/helpers/test/npm6/fixtures/updater/original/package.json +9 -0
  39. data/helpers/test/npm6/fixtures/updater/updated/package-lock.json +16 -0
  40. data/helpers/test/npm6/helpers.js +21 -0
  41. data/helpers/test/npm6/updater.test.js +30 -0
  42. data/helpers/test/pnpm/fixtures/parser/empty_version/pnpm-lock.yaml +72 -0
  43. data/helpers/test/pnpm/fixtures/parser/no_lockfile_change/pnpm-lock.yaml +2744 -0
  44. data/helpers/test/pnpm/fixtures/parser/only_dev_dependencies/pnpm-lock.yaml +16 -0
  45. data/helpers/test/pnpm/fixtures/parser/peer_disambiguation/pnpm-lock.yaml +855 -0
  46. data/helpers/test/pnpm/lockfile-parser.test.js +62 -0
  47. data/helpers/test/yarn/conflicting-dependency-parser.test.js +83 -0
  48. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
  49. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/yarn.lock +496 -0
  50. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/package.json +14 -0
  51. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/yarn.lock +21 -0
  52. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
  53. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/yarn.lock +183 -0
  54. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
  55. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/yarn.lock +21 -0
  56. data/helpers/test/yarn/fixtures/updater/illegal_character/package.json +8 -0
  57. data/helpers/test/yarn/fixtures/updater/illegal_character/yarn.lock +14 -0
  58. data/helpers/test/yarn/fixtures/updater/original/package.json +6 -0
  59. data/helpers/test/yarn/fixtures/updater/original/yarn.lock +11 -0
  60. data/helpers/test/yarn/fixtures/updater/updated/yarn.lock +12 -0
  61. data/helpers/test/yarn/fixtures/updater/with-version-comments/package.json +5 -0
  62. data/helpers/test/yarn/fixtures/updater/with-version-comments/yarn.lock +13 -0
  63. data/helpers/test/yarn/helpers.js +18 -0
  64. data/helpers/test/yarn/updater.test.js +117 -0
  65. data/lib/dependabot/bun/bun_package_manager.rb +47 -0
  66. data/lib/dependabot/bun/constraint_helper.rb +359 -0
  67. data/lib/dependabot/bun/dependency_files_filterer.rb +157 -0
  68. data/lib/dependabot/bun/file_fetcher/path_dependency_builder.rb +184 -0
  69. data/lib/dependabot/bun/file_fetcher.rb +402 -0
  70. data/lib/dependabot/bun/file_parser/bun_lock.rb +140 -0
  71. data/lib/dependabot/bun/file_parser/lockfile_parser.rb +105 -0
  72. data/lib/dependabot/bun/file_parser.rb +477 -0
  73. data/lib/dependabot/bun/file_updater/bun_lockfile_updater.rb +144 -0
  74. data/lib/dependabot/bun/file_updater/npmrc_builder.rb +256 -0
  75. data/lib/dependabot/bun/file_updater/package_json_preparer.rb +88 -0
  76. data/lib/dependabot/bun/file_updater/package_json_updater.rb +378 -0
  77. data/lib/dependabot/bun/file_updater.rb +203 -0
  78. data/lib/dependabot/bun/helpers.rb +93 -0
  79. data/lib/dependabot/bun/language.rb +45 -0
  80. data/lib/dependabot/bun/metadata_finder.rb +214 -0
  81. data/lib/dependabot/bun/native_helpers.rb +19 -0
  82. data/lib/dependabot/bun/package_manager.rb +280 -0
  83. data/lib/dependabot/bun/package_name.rb +118 -0
  84. data/lib/dependabot/bun/pnpm_package_manager.rb +55 -0
  85. data/lib/dependabot/bun/registry_helper.rb +188 -0
  86. data/lib/dependabot/bun/registry_parser.rb +93 -0
  87. data/lib/dependabot/bun/requirement.rb +146 -0
  88. data/lib/dependabot/bun/sub_dependency_files_filterer.rb +82 -0
  89. data/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb +59 -0
  90. data/lib/dependabot/bun/update_checker/dependency_files_builder.rb +79 -0
  91. data/lib/dependabot/bun/update_checker/latest_version_finder.rb +448 -0
  92. data/lib/dependabot/bun/update_checker/library_detector.rb +76 -0
  93. data/lib/dependabot/bun/update_checker/registry_finder.rb +279 -0
  94. data/lib/dependabot/bun/update_checker/requirements_updater.rb +206 -0
  95. data/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb +154 -0
  96. data/lib/dependabot/bun/update_checker/version_resolver.rb +583 -0
  97. data/lib/dependabot/bun/update_checker/vulnerability_auditor.rb +164 -0
  98. data/lib/dependabot/bun/update_checker.rb +455 -0
  99. data/lib/dependabot/bun/version.rb +138 -0
  100. data/lib/dependabot/bun/version_selector.rb +61 -0
  101. data/lib/dependabot/bun.rb +337 -35
  102. metadata +108 -65
  103. data/lib/dependabot/javascript/bun/file_fetcher.rb +0 -77
  104. data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +0 -156
  105. data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +0 -55
  106. data/lib/dependabot/javascript/bun/file_parser.rb +0 -74
  107. data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +0 -138
  108. data/lib/dependabot/javascript/bun/file_updater.rb +0 -75
  109. data/lib/dependabot/javascript/bun/helpers.rb +0 -72
  110. data/lib/dependabot/javascript/bun/package_manager.rb +0 -48
  111. data/lib/dependabot/javascript/bun/requirement.rb +0 -11
  112. data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +0 -64
  113. data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +0 -47
  114. data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +0 -450
  115. data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +0 -76
  116. data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +0 -203
  117. data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +0 -144
  118. data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +0 -525
  119. data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +0 -165
  120. data/lib/dependabot/javascript/bun/update_checker.rb +0 -440
  121. data/lib/dependabot/javascript/bun/version.rb +0 -11
  122. data/lib/dependabot/javascript/shared/constraint_helper.rb +0 -359
  123. data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +0 -164
  124. data/lib/dependabot/javascript/shared/file_fetcher.rb +0 -283
  125. data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +0 -106
  126. data/lib/dependabot/javascript/shared/file_parser.rb +0 -454
  127. data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +0 -394
  128. data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +0 -87
  129. data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +0 -376
  130. data/lib/dependabot/javascript/shared/file_updater.rb +0 -179
  131. data/lib/dependabot/javascript/shared/language.rb +0 -45
  132. data/lib/dependabot/javascript/shared/metadata_finder.rb +0 -209
  133. data/lib/dependabot/javascript/shared/native_helpers.rb +0 -21
  134. data/lib/dependabot/javascript/shared/package_manager_detector.rb +0 -72
  135. data/lib/dependabot/javascript/shared/package_name.rb +0 -118
  136. data/lib/dependabot/javascript/shared/registry_helper.rb +0 -190
  137. data/lib/dependabot/javascript/shared/registry_parser.rb +0 -93
  138. data/lib/dependabot/javascript/shared/requirement.rb +0 -144
  139. data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +0 -79
  140. data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +0 -87
  141. data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +0 -358
  142. data/lib/dependabot/javascript/shared/version.rb +0 -133
  143. data/lib/dependabot/javascript/shared/version_selector.rb +0 -60
  144. data/lib/dependabot/javascript.rb +0 -39
@@ -1,525 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- module Dependabot
5
- module Javascript
6
- module Bun
7
- class UpdateChecker
8
- class VersionResolver # rubocop:disable Metrics/ClassLength
9
- extend T::Sig
10
-
11
- require_relative "latest_version_finder"
12
-
13
- TIGHTLY_COUPLED_MONOREPOS = {
14
- "vue" => %w(vue vue-template-compiler)
15
- }.freeze
16
-
17
- def initialize(dependency:, credentials:, dependency_files:,
18
- latest_allowable_version:, latest_version_finder:, repo_contents_path:, dependency_group: nil)
19
- @dependency = dependency
20
- @credentials = credentials
21
- @dependency_files = dependency_files
22
- @latest_allowable_version = latest_allowable_version
23
- @dependency_group = dependency_group
24
-
25
- @latest_version_finder = {}
26
- @latest_version_finder[dependency] = latest_version_finder
27
- @repo_contents_path = repo_contents_path
28
- end
29
-
30
- def latest_resolvable_version
31
- return latest_allowable_version if git_dependency?(dependency)
32
- return if part_of_tightly_locked_monorepo?
33
- return if types_update_available?
34
- return if original_package_update_available?
35
-
36
- return latest_allowable_version unless relevant_unmet_peer_dependencies.any?
37
-
38
- satisfying_versions.first
39
- end
40
-
41
- def latest_version_resolvable_with_full_unlock?
42
- return false if dependency_updates_from_full_unlock.nil?
43
-
44
- true
45
- end
46
-
47
- def latest_resolvable_previous_version(updated_version)
48
- resolve_latest_previous_version(dependency, updated_version)
49
- end
50
-
51
- # rubocop:disable Metrics/PerceivedComplexity
52
- def dependency_updates_from_full_unlock
53
- return if git_dependency?(dependency)
54
- return updated_monorepo_dependencies if part_of_tightly_locked_monorepo?
55
- return if newly_broken_peer_reqs_from_dep.any?
56
- return if original_package_update_available?
57
-
58
- updates = [{
59
- dependency: dependency,
60
- version: latest_allowable_version,
61
- previous_version: latest_resolvable_previous_version(
62
- latest_allowable_version
63
- )
64
- }]
65
- newly_broken_peer_reqs_on_dep.each do |peer_req|
66
- dep_name = peer_req.fetch(:requiring_dep_name)
67
- dep = top_level_dependencies.find { |d| d.name == dep_name }
68
-
69
- # Can't handle reqs from sub-deps or git source deps (yet)
70
- return nil if dep.nil?
71
- return nil if git_dependency?(dep)
72
-
73
- updated_version =
74
- latest_version_of_dep_with_satisfied_peer_reqs(dep)
75
- return nil unless updated_version
76
-
77
- updates << {
78
- dependency: dep,
79
- version: updated_version,
80
- previous_version: resolve_latest_previous_version(
81
- dep, updated_version
82
- )
83
- }
84
- end
85
- updates += updated_types_dependencies if types_update_available?
86
- updates.uniq
87
- end
88
- # rubocop:enable Metrics/PerceivedComplexity
89
-
90
- private
91
-
92
- sig { returns(Dependabot::Dependency) }
93
- attr_reader :dependency
94
- attr_reader :credentials
95
- attr_reader :dependency_files
96
- attr_reader :latest_allowable_version
97
- attr_reader :repo_contents_path
98
- attr_reader :dependency_group
99
-
100
- def latest_version_finder(dep)
101
- @latest_version_finder[dep] ||=
102
- LatestVersionFinder.new(
103
- dependency: dep,
104
- credentials: credentials,
105
- dependency_files: dependency_files,
106
- ignored_versions: [],
107
- security_advisories: []
108
- )
109
- end
110
-
111
- # rubocop:disable Metrics/PerceivedComplexity
112
- def resolve_latest_previous_version(dep, updated_version)
113
- return dep.version if dep.version
114
-
115
- @resolve_latest_previous_version ||= {}
116
- @resolve_latest_previous_version[dep] ||= begin
117
- relevant_versions = latest_version_finder(dependency)
118
- .possible_previous_versions_with_details
119
- .map(&:first)
120
- reqs = dep.requirements.filter_map { |r| r[:requirement] }
121
- .map { |r| requirement_class.requirements_array(r) }
122
-
123
- # Pick the lowest version from the max possible version from all
124
- # requirements. This matches the logic when combining the same
125
- # dependency in DependencySet from multiple manifest files where we
126
- # pick the lowest version from the duplicates.
127
- latest_previous_version = reqs.flat_map do |req|
128
- relevant_versions.select do |version|
129
- req.any? { |r| r.satisfied_by?(version) }
130
- end.max
131
- end.min&.to_s
132
-
133
- # Handle cases where the latest resolvable previous version is the
134
- # latest version. This often happens if you don't have lockfiles and
135
- # have requirements update strategy set to bump_versions, where an
136
- # update might go from ^1.1.1 to ^1.1.2 (both resolve to 1.1.2).
137
- if updated_version.to_s == latest_previous_version
138
- nil
139
- else
140
- latest_previous_version
141
- end
142
- end
143
- end
144
- # rubocop:enable Metrics/PerceivedComplexity
145
-
146
- def part_of_tightly_locked_monorepo?
147
- monorepo_dep_names =
148
- TIGHTLY_COUPLED_MONOREPOS.values
149
- .find { |deps| deps.include?(dependency.name) }
150
- return false unless monorepo_dep_names
151
-
152
- deps_to_update =
153
- top_level_dependencies
154
- .select { |d| monorepo_dep_names.include?(d.name) }
155
-
156
- deps_to_update.count > 1
157
- end
158
-
159
- def updated_monorepo_dependencies
160
- monorepo_dep_names =
161
- TIGHTLY_COUPLED_MONOREPOS.values
162
- .find { |deps| deps.include?(dependency.name) }
163
-
164
- deps_to_update =
165
- top_level_dependencies
166
- .select { |d| monorepo_dep_names.include?(d.name) }
167
-
168
- updates = []
169
- deps_to_update.each do |dep|
170
- next if git_dependency?(dep)
171
- next if dep.version &&
172
- version_class.new(dep.version) >= latest_allowable_version
173
-
174
- updated_version =
175
- latest_version_finder(dep)
176
- .possible_versions
177
- .find { |v| v == latest_allowable_version }
178
- next unless updated_version
179
-
180
- updates << {
181
- dependency: dep,
182
- version: updated_version,
183
- previous_version: resolve_latest_previous_version(
184
- dep, updated_version
185
- )
186
- }
187
- end
188
-
189
- updates
190
- end
191
-
192
- def types_package
193
- @types_package ||= begin
194
- types_package_name = Dependabot::Javascript::Shared::PackageName.new(dependency.name).types_package_name
195
- top_level_dependencies.find { |d| types_package_name.to_s == d.name } if types_package_name
196
- end
197
- end
198
-
199
- def original_package
200
- @original_package ||= begin
201
- original_package_name = Dependabot::Javascript::Shared::PackageName.new(dependency.name).library_name
202
- top_level_dependencies.find { |d| original_package_name.to_s == d.name } if original_package_name
203
- end
204
- end
205
-
206
- def latest_types_package_version
207
- @latest_types_package_version ||= latest_version_finder(types_package).latest_version_from_registry
208
- end
209
-
210
- def types_update_available?
211
- return false if types_package.nil?
212
-
213
- return false if latest_types_package_version.nil?
214
-
215
- return false unless latest_allowable_version.backwards_compatible_with?(latest_types_package_version)
216
-
217
- return false unless version_class.correct?(types_package.version)
218
-
219
- current_types_package_version = version_class.new(types_package.version)
220
-
221
- return false unless current_types_package_version < latest_types_package_version
222
-
223
- true
224
- end
225
-
226
- def original_package_update_available?
227
- return false if original_package.nil?
228
-
229
- return false unless version_class.correct?(original_package.version)
230
-
231
- original_package_version = version_class.new(original_package.version)
232
-
233
- latest_version = latest_version_finder(original_package).latest_version_from_registry
234
-
235
- # If the latest version is within the scope of the current requirements,
236
- # latest_version will be nil. In such cases, there is no update available.
237
- return false if latest_version.nil?
238
-
239
- original_package_version < latest_version
240
- end
241
-
242
- def updated_types_dependencies
243
- [{
244
- dependency: types_package,
245
- version: latest_types_package_version,
246
- previous_version: resolve_latest_previous_version(
247
- types_package, latest_types_package_version
248
- )
249
- }]
250
- end
251
-
252
- def peer_dependency_errors
253
- return @peer_dependency_errors if @peer_dependency_errors_checked
254
-
255
- @peer_dependency_errors_checked = true
256
-
257
- @peer_dependency_errors =
258
- fetch_peer_dependency_errors(version: latest_allowable_version)
259
- end
260
-
261
- def old_peer_dependency_errors
262
- return @old_peer_dependency_errors if @old_peer_dependency_errors_checked
263
-
264
- @old_peer_dependency_errors_checked = true
265
-
266
- version = version_for_dependency(dependency)
267
-
268
- @old_peer_dependency_errors =
269
- fetch_peer_dependency_errors(version: version)
270
- end
271
-
272
- def fetch_peer_dependency_errors(version:)
273
- # TODO: Add all of the error handling that the FileUpdater does
274
- # here (since problematic repos will be resolved here before they're
275
- # seen by the FileUpdater)
276
- base_dir = dependency_files.first.directory
277
- SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
278
- dependency_files_builder.write_temporary_dependency_files
279
-
280
- paths_requiring_update_check.flat_map do |path|
281
- run_checker(path: path, version: version)
282
- end.compact
283
- end
284
- rescue SharedHelpers::HelperSubprocessFailed
285
- # Fall back to allowing the version through. Whatever error
286
- # occurred should be properly handled by the FileUpdater. We
287
- # can slowly migrate error handling to this class over time.
288
- []
289
- end
290
-
291
- def unmet_peer_dependencies
292
- peer_dependency_errors
293
- .map { |captures| error_details_from_captures(captures) }
294
- end
295
-
296
- def old_unmet_peer_dependencies
297
- old_peer_dependency_errors
298
- .map { |captures| error_details_from_captures(captures) }
299
- end
300
-
301
- def error_details_from_captures(captures)
302
- return {} unless captures.is_a?(Hash)
303
-
304
- required_dep_captures = captures.fetch("required_dep")
305
- requiring_dep_captures = captures.fetch("requiring_dep")
306
- return {} unless required_dep_captures && requiring_dep_captures
307
-
308
- {
309
- requirement_name: required_dep_captures.sub(/@[^@]+$/, ""),
310
- requirement_version: required_dep_captures.split("@").last.delete('"'),
311
- requiring_dep_name: requiring_dep_captures.sub(/@[^@]+$/, "")
312
- }
313
- end
314
-
315
- def relevant_unmet_peer_dependencies
316
- relevant_unmet_peer_dependencies =
317
- unmet_peer_dependencies.select do |dep|
318
- dep[:requirement_name] == dependency.name ||
319
- dep[:requiring_dep_name] == dependency.name
320
- end
321
-
322
- unless dependency_group.nil?
323
- # Ignore unmet peer dependencies that are in the dependency group because
324
- # the update is also updating those dependencies.
325
- relevant_unmet_peer_dependencies.reject! do |dep|
326
- dependency_group.dependencies.any? do |group_dep|
327
- dep[:requirement_name] == group_dep.name ||
328
- dep[:requiring_dep_name] == group_dep.name
329
- end
330
- end
331
- end
332
-
333
- return [] if relevant_unmet_peer_dependencies.empty?
334
-
335
- # Prune out any pre-existing warnings
336
- relevant_unmet_peer_dependencies.reject do |issue|
337
- old_unmet_peer_dependencies.any? do |old_issue|
338
- old_issue.slice(:requirement_name, :requiring_dep_name) ==
339
- issue.slice(:requirement_name, :requiring_dep_name)
340
- end
341
- end
342
- end
343
-
344
- # rubocop:disable Metrics/PerceivedComplexity
345
- def satisfying_versions
346
- latest_version_finder(dependency)
347
- .possible_versions_with_details
348
- .select do |version, details|
349
- next false unless satisfies_peer_reqs_on_dep?(version)
350
- next true unless details["peerDependencies"]
351
- next true if version == version_for_dependency(dependency)
352
-
353
- details["peerDependencies"].all? do |dep, req|
354
- dep = top_level_dependencies.find { |d| d.name == dep }
355
- next false unless dep
356
- next git_dependency?(dep) if req.include?("/")
357
-
358
- reqs = requirement_class.requirements_array(req)
359
- next false unless version_for_dependency(dep)
360
-
361
- reqs.any? { |r| r.satisfied_by?(version_for_dependency(dep)) }
362
- rescue Gem::Requirement::BadRequirementError
363
- false
364
- end
365
- end
366
- .map(&:first)
367
- end
368
-
369
- # rubocop:enable Metrics/PerceivedComplexity
370
-
371
- def satisfies_peer_reqs_on_dep?(version)
372
- newly_broken_peer_reqs_on_dep.all? do |peer_req|
373
- req = peer_req.fetch(:requirement_version)
374
-
375
- # Git requirements can't be satisfied by a version
376
- next false if req.include?("/")
377
-
378
- reqs = requirement_class.requirements_array(req)
379
- reqs.any? { |r| r.satisfied_by?(version) }
380
- rescue Gem::Requirement::BadRequirementError
381
- false
382
- end
383
- end
384
-
385
- def latest_version_of_dep_with_satisfied_peer_reqs(dep)
386
- latest_version_finder(dep)
387
- .possible_versions_with_details
388
- .find do |version, details|
389
- next false unless version > version_for_dependency(dep)
390
- next true unless details["peerDependencies"]
391
-
392
- details["peerDependencies"].all? do |peer_dep_name, req|
393
- # Can't handle multiple peer dependencies
394
- next false unless peer_dep_name == dependency.name
395
- next git_dependency?(dependency) if req.include?("/")
396
-
397
- reqs = requirement_class.requirements_array(req)
398
-
399
- reqs.any? { |r| r.satisfied_by?(latest_allowable_version) }
400
- rescue Gem::Requirement::BadRequirementError
401
- false
402
- end
403
- end
404
- &.first
405
- end
406
-
407
- def git_dependency?(dep)
408
- # ignored_version/raise_on_ignored are irrelevant.
409
- GitCommitChecker
410
- .new(dependency: dep, credentials: credentials)
411
- .git_dependency?
412
- end
413
-
414
- def newly_broken_peer_reqs_on_dep
415
- relevant_unmet_peer_dependencies
416
- .select { |dep| dep[:requirement_name] == dependency.name }
417
- end
418
-
419
- def newly_broken_peer_reqs_from_dep
420
- relevant_unmet_peer_dependencies
421
- .select { |dep| dep[:requiring_dep_name] == dependency.name }
422
- end
423
-
424
- def lockfiles_for_path(lockfiles:, path:)
425
- lockfiles.select do |lockfile|
426
- File.dirname(lockfile.name) == File.dirname(path)
427
- end
428
- end
429
-
430
- def run_checker(path:, version:)
431
- bun_lockfiles = lockfiles_for_path(lockfiles: dependency_files_builder.bun_locks, path: path)
432
- return run_bun_checker(path: path, version: version) if bun_lockfiles.any?
433
-
434
- root_bun_lock = dependency_files_builder.root_bun_lock
435
- run_bun_checker(path: path, version: version) if root_bun_lock
436
- end
437
-
438
- def run_bun_checker(path:, version:)
439
- SharedHelpers.with_git_configured(credentials: credentials) do
440
- Dir.chdir(path) do
441
- Helpers.run_bun_command(
442
- "update #{dependency.name}@#{version} --save-text-lockfile",
443
- fingerprint: "update <dependency_name>@<version> --save-text-lockfile"
444
- )
445
- end
446
- end
447
- end
448
-
449
- def version_install_arg(version:)
450
- git_source = dependency.requirements.find { |req| req[:source] && req[:source][:type] == "git" }
451
-
452
- if git_source
453
- "#{dependency.name}@#{git_source[:source][:url]}##{version}"
454
- else
455
- "#{dependency.name}@#{version}"
456
- end
457
- end
458
-
459
- def requirements_for_path(requirements, path)
460
- return requirements if path.to_s == "."
461
-
462
- requirements.filter_map do |r|
463
- next unless r[:file].start_with?("#{path}/")
464
-
465
- r.merge(file: r[:file].gsub(/^#{Regexp.quote("#{path}/")}/, ""))
466
- end
467
- end
468
-
469
- # Top level dependencies are required in the peer dep checker
470
- # to fetch the manifests for all top level deps which may contain
471
- # "peerDependency" requirements
472
- def top_level_dependencies
473
- @top_level_dependencies ||= Bun::FileParser.new(
474
- dependency_files: dependency_files,
475
- source: nil,
476
- credentials: credentials
477
- ).parse.select(&:top_level?)
478
- end
479
-
480
- def paths_requiring_update_check
481
- @paths_requiring_update_check ||=
482
- Dependabot::Javascript::Shared::DependencyFilesFilterer.new(
483
- dependency_files: dependency_files,
484
- updated_dependencies: [dependency],
485
- lockfile_parser_class: FileParser::LockfileParser
486
- ).paths_requiring_update_check
487
- end
488
-
489
- def dependency_files_builder
490
- @dependency_files_builder ||=
491
- DependencyFilesBuilder.new(
492
- dependency: dependency,
493
- dependency_files: dependency_files,
494
- credentials: credentials
495
- )
496
- end
497
-
498
- def version_for_dependency(dep)
499
- return version_class.new(dep.version) if dep.version && version_class.correct?(dep.version)
500
-
501
- dep.requirements.filter_map { |r| r[:requirement] }
502
- .reject { |req_string| req_string.start_with?("<") }
503
- .select { |req_string| req_string.match?(version_regex) }
504
- .map { |req_string| req_string.match(version_regex) }
505
- .select { |version| version_class.correct?(version.to_s) }
506
- .map { |version| version_class.new(version.to_s) }
507
- .max
508
- end
509
-
510
- def version_class
511
- dependency.version_class
512
- end
513
-
514
- def requirement_class
515
- dependency.requirement_class
516
- end
517
-
518
- def version_regex
519
- Dependabot::Javascript::Shared::Version::VERSION_PATTERN
520
- end
521
- end
522
- end
523
- end
524
- end
525
- end
@@ -1,165 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require "stringio"
5
-
6
- module Dependabot
7
- module Javascript
8
- module Bun
9
- class UpdateChecker
10
- class VulnerabilityAuditor
11
- def initialize(dependency_files:, credentials:)
12
- @dependency_files = dependency_files
13
- @credentials = credentials
14
- end
15
-
16
- # rubocop:disable Metrics/MethodLength
17
- # Finds any dependencies in the `package-lock.json` or `npm-shrinkwrap.json` that have
18
- # a subdependency on the given dependency that is locked to a vuln version range.
19
- #
20
- # NOTE: yarn is currently not supported.
21
- #
22
- # @param dependency [Dependabot::Dependency] the dependency to check
23
- # @param security_advisories [Array<Dependabot::SecurityAdvisory>] advisories for the dependency
24
- # @return [Hash<String, [String, Array<Hash<String, String>>]>] the audit results
25
- # * :dependency_name [String] the name of the dependency
26
- # * :fix_available [Boolean] whether a fix is available
27
- # * :current_version [String] the version of the dependency
28
- # * :target_version [String] the version of the dependency after the fix
29
- # * :fix_updates [Array<Hash<String, String>>] a list of dependencies to update in order to fix
30
- # * :dependency_name [String] the name of the blocking dependency
31
- # * :current_version [String] the current version of the blocking dependency
32
- # * :target_version [String] the target version of the blocking dependency
33
- # * :top_level_ancestors [Array<String>] the names of top-level dependencies with a transitive
34
- # dependency on the blocking dependency
35
- # * :top_level_ancestors [Array<String>] the names of all top-level dependencies with a transitive
36
- # dependency on the dependency
37
- # * :explanation [String] an explanation for why the project failed the vulnerability auditor run
38
- def audit(dependency:, security_advisories:)
39
- Dependabot.logger.info("VulnerabilityAuditor: starting audit")
40
-
41
- fix_unavailable = {
42
- "dependency_name" => dependency.name,
43
- "fix_available" => false,
44
- "fix_updates" => [],
45
- "top_level_ancestors" => []
46
- }
47
-
48
- SharedHelpers.in_a_temporary_directory do
49
- dependency_files_builder = DependencyFilesBuilder.new(
50
- dependency: dependency,
51
- dependency_files: dependency_files,
52
- credentials: credentials
53
- )
54
- dependency_files_builder.write_temporary_dependency_files
55
-
56
- # `npm-shrinkwrap.js`, if present, takes precedence over `package-lock.js`.
57
- # Both files use the same format. See https://bit.ly/3lDIAJV for more.
58
- lockfile = dependency_files_builder.lockfiles.first
59
- unless lockfile
60
- Dependabot.logger.info("VulnerabilityAuditor: missing lockfile")
61
- return fix_unavailable
62
- end
63
-
64
- vuln_versions = security_advisories.map do |a|
65
- {
66
- dependency_name: a.dependency_name,
67
- affected_versions: a.vulnerable_version_strings
68
- }
69
- end
70
-
71
- audit_result = SharedHelpers.run_helper_subprocess(
72
- command: Dependabot::Javascript::Shared::NativeHelpers.helper_path,
73
- function: "npm:vulnerabilityAuditor",
74
- args: [Dir.pwd, vuln_versions]
75
- )
76
-
77
- validation_result = validate_audit_result(audit_result, security_advisories)
78
- if validation_result != :viable
79
- Dependabot.logger.info("VulnerabilityAuditor: audit result not viable: #{validation_result}")
80
- fix_unavailable["explanation"] = explain_fix_unavailable(validation_result, dependency)
81
- return fix_unavailable
82
- end
83
-
84
- Dependabot.logger.info("VulnerabilityAuditor: audit result viable")
85
- audit_result
86
- end
87
- rescue SharedHelpers::HelperSubprocessFailed => e
88
- log_helper_subprocess_failure(dependency, e)
89
- fix_unavailable
90
- end
91
- # rubocop:enable Metrics/MethodLength
92
-
93
- private
94
-
95
- attr_reader :dependency_files
96
- attr_reader :credentials
97
-
98
- def explain_fix_unavailable(validation_result, dependency)
99
- case validation_result
100
- when :fix_unavailable, :dependency_still_vulnerable, :downgrades_dependencies
101
- "No patched version available for #{dependency.name}"
102
- when :fix_incomplete
103
- "The lockfile might be out of sync?"
104
- end
105
- end
106
-
107
- def validate_audit_result(audit_result, security_advisories)
108
- return :fix_unavailable unless audit_result["fix_available"]
109
- return :dependency_still_vulnerable if dependency_still_vulnerable?(audit_result, security_advisories)
110
- return :downgrades_dependencies if downgrades_dependencies?(audit_result)
111
- return :fix_incomplete if fix_incomplete?(audit_result)
112
-
113
- :viable
114
- end
115
-
116
- def dependency_still_vulnerable?(audit_result, security_advisories)
117
- # vulnerable dependency is removed if the target version is nil
118
- return false unless audit_result["target_version"]
119
-
120
- version = Version.new(audit_result["target_version"])
121
- security_advisories.any? { |a| a.vulnerable?(version) }
122
- end
123
-
124
- def downgrades_dependencies?(audit_result)
125
- return true if downgrades_version?(audit_result["current_version"], audit_result["target_version"])
126
-
127
- audit_result["fix_updates"].any? do |update|
128
- downgrades_version?(update["current_version"], update["target_version"])
129
- end
130
- end
131
-
132
- def downgrades_version?(current_version, target_version)
133
- return false unless target_version
134
-
135
- current = Version.new(current_version)
136
- target = Version.new(target_version)
137
- current > target
138
- end
139
-
140
- def fix_incomplete?(audit_result)
141
- audit_result["fix_updates"].any? { |update| !update.key?("target_version") } ||
142
- audit_result["fix_updates"].empty?
143
- end
144
-
145
- def log_helper_subprocess_failure(dependency, error)
146
- # See `Dependabot::SharedHelpers.run_helper_subprocess` for details on error context
147
- context = error.error_context || {}
148
-
149
- builder = ::StringIO.new
150
- builder << "VulnerabilityAuditor: "
151
- builder << "#{context[:function]} " if context[:function]
152
- builder << "failed"
153
- builder << " after #{context[:time_taken].truncate(2)}s" if context[:time_taken]
154
- builder << " while auditing #{dependency.name}: "
155
- builder << error.message
156
- builder << "\n" << context[:trace]
157
-
158
- msg = builder.string
159
- Dependabot.logger.info(msg) # TODO: is this the right log level?
160
- end
161
- end
162
- end
163
- end
164
- end
165
- end