dependabot-javascript 0.296.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/lib/dependabot/bun.rb +49 -0
  3. data/lib/dependabot/javascript/bun/file_fetcher.rb +77 -0
  4. data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +156 -0
  5. data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +55 -0
  6. data/lib/dependabot/javascript/bun/file_parser.rb +74 -0
  7. data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +138 -0
  8. data/lib/dependabot/javascript/bun/file_updater.rb +75 -0
  9. data/lib/dependabot/javascript/bun/helpers.rb +72 -0
  10. data/lib/dependabot/javascript/bun/package_manager.rb +48 -0
  11. data/lib/dependabot/javascript/bun/requirement.rb +11 -0
  12. data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +64 -0
  13. data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +47 -0
  14. data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +450 -0
  15. data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +76 -0
  16. data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +203 -0
  17. data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +144 -0
  18. data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +525 -0
  19. data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +165 -0
  20. data/lib/dependabot/javascript/bun/update_checker.rb +440 -0
  21. data/lib/dependabot/javascript/bun/version.rb +11 -0
  22. data/lib/dependabot/javascript/shared/constraint_helper.rb +359 -0
  23. data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +164 -0
  24. data/lib/dependabot/javascript/shared/file_fetcher.rb +283 -0
  25. data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +106 -0
  26. data/lib/dependabot/javascript/shared/file_parser.rb +454 -0
  27. data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +394 -0
  28. data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +87 -0
  29. data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +376 -0
  30. data/lib/dependabot/javascript/shared/file_updater.rb +179 -0
  31. data/lib/dependabot/javascript/shared/language.rb +45 -0
  32. data/lib/dependabot/javascript/shared/metadata_finder.rb +209 -0
  33. data/lib/dependabot/javascript/shared/native_helpers.rb +21 -0
  34. data/lib/dependabot/javascript/shared/package_manager_detector.rb +72 -0
  35. data/lib/dependabot/javascript/shared/package_name.rb +118 -0
  36. data/lib/dependabot/javascript/shared/registry_helper.rb +190 -0
  37. data/lib/dependabot/javascript/shared/registry_parser.rb +93 -0
  38. data/lib/dependabot/javascript/shared/requirement.rb +144 -0
  39. data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +79 -0
  40. data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +87 -0
  41. data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +358 -0
  42. data/lib/dependabot/javascript/shared/version.rb +133 -0
  43. data/lib/dependabot/javascript/shared/version_selector.rb +60 -0
  44. data/lib/dependabot/javascript.rb +39 -0
  45. metadata +327 -0
@@ -0,0 +1,440 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
8
+ def up_to_date?
9
+ return false if security_update? &&
10
+ dependency.version &&
11
+ version_class.correct?(dependency.version) &&
12
+ vulnerable_versions.any? &&
13
+ !vulnerable_versions.include?(current_version)
14
+
15
+ super
16
+ end
17
+
18
+ def vulnerable?
19
+ super || vulnerable_versions.any?
20
+ end
21
+
22
+ def latest_version
23
+ @latest_version ||=
24
+ if git_dependency?
25
+ latest_version_for_git_dependency
26
+ else
27
+ latest_version_details&.fetch(:version)
28
+ end
29
+ end
30
+
31
+ def latest_resolvable_version
32
+ return unless latest_version
33
+
34
+ @latest_resolvable_version ||=
35
+ if dependency.top_level?
36
+ version_resolver.latest_resolvable_version
37
+ else
38
+ # If the dependency is indirect its version is constrained by the
39
+ # requirements placed on it by dependencies lower down the tree
40
+ subdependency_version_resolver.latest_resolvable_version
41
+ end
42
+ end
43
+
44
+ def lowest_security_fix_version
45
+ # This will require a full unlock to update multiple top level ancestors.
46
+ return if vulnerability_audit["fix_available"] && vulnerability_audit["top_level_ancestors"].count > 1
47
+
48
+ latest_version_finder.lowest_security_fix_version
49
+ end
50
+
51
+ def lowest_resolvable_security_fix_version
52
+ raise "Dependency not vulnerable!" unless vulnerable?
53
+
54
+ # NOTE: Currently, we don't resolve transitive/sub-dependencies as
55
+ # npm/yarn don't provide any control over updating to a specific
56
+ # sub-dependency version.
57
+
58
+ # Return nil for vulnerable transitive dependencies if there are conflicting dependencies.
59
+ # This helps catch errors in such cases.
60
+ return nil if !dependency.top_level? && conflicting_dependencies.any?
61
+
62
+ # For transitive dependencies without conflicts, return the latest resolvable transitive
63
+ # security fix version that does not require unlocking other dependencies.
64
+ return latest_resolvable_transitive_security_fix_version_with_no_unlock unless dependency.top_level?
65
+
66
+ # For top-level dependencies, return the lowest security fix version.
67
+ # TODO: Consider checking resolvability here in the future.
68
+ lowest_security_fix_version
69
+ end
70
+
71
+ def latest_resolvable_version_with_no_unlock
72
+ return latest_resolvable_version unless dependency.top_level?
73
+
74
+ return latest_resolvable_version_with_no_unlock_for_git_dependency if git_dependency?
75
+
76
+ latest_version_finder.latest_version_with_no_unlock
77
+ end
78
+
79
+ def latest_resolvable_previous_version(updated_version)
80
+ version_resolver.latest_resolvable_previous_version(updated_version)
81
+ end
82
+
83
+ def updated_requirements
84
+ resolvable_version =
85
+ if preferred_resolvable_version.is_a?(version_class)
86
+ preferred_resolvable_version.to_s
87
+ elsif preferred_resolvable_version.nil?
88
+ nil
89
+ else
90
+ # If the preferred_resolvable_version came back as anything other
91
+ # than a version class or `nil` it must be because this is a git
92
+ # dependency, for which we don't check resolvability.
93
+ latest_version_details&.fetch(:version, nil)&.to_s
94
+ end
95
+
96
+ @updated_requirements ||=
97
+ RequirementsUpdater.new(
98
+ requirements: dependency.requirements,
99
+ updated_source: updated_source,
100
+ latest_resolvable_version: resolvable_version,
101
+ update_strategy: requirements_update_strategy
102
+ ).updated_requirements
103
+ end
104
+
105
+ def requirements_unlocked_or_can_be?
106
+ !requirements_update_strategy.lockfile_only?
107
+ end
108
+
109
+ def requirements_update_strategy
110
+ # If passed in as an option (in the base class) honour that option
111
+ return @requirements_update_strategy if @requirements_update_strategy
112
+
113
+ # Otherwise, widen ranges for libraries and bump versions for apps
114
+ library? ? RequirementsUpdateStrategy::WidenRanges : RequirementsUpdateStrategy::BumpVersions
115
+ end
116
+
117
+ def conflicting_dependencies
118
+ conflicts = ConflictingDependencyResolver.new(
119
+ dependency_files: dependency_files,
120
+ credentials: credentials
121
+ ).conflicting_dependencies(
122
+ dependency: dependency,
123
+ target_version: lowest_security_fix_version
124
+ )
125
+ return conflicts unless vulnerability_audit_performed?
126
+
127
+ vulnerable = [vulnerability_audit].select do |hash|
128
+ !hash["fix_available"] && hash["explanation"]
129
+ end
130
+
131
+ conflicts + vulnerable
132
+ end
133
+
134
+ private
135
+
136
+ def vulnerability_audit_performed?
137
+ defined?(@vulnerability_audit)
138
+ end
139
+
140
+ def vulnerability_audit
141
+ @vulnerability_audit ||=
142
+ VulnerabilityAuditor.new(
143
+ dependency_files: dependency_files,
144
+ credentials: credentials
145
+ ).audit(
146
+ dependency: dependency,
147
+ security_advisories: security_advisories
148
+ )
149
+ end
150
+
151
+ def vulnerable_versions
152
+ @vulnerable_versions ||=
153
+ begin
154
+ all_versions = dependency.all_versions
155
+ .filter_map { |v| version_class.new(v) if version_class.correct?(v) }
156
+
157
+ all_versions.select do |v|
158
+ security_advisories.any? { |advisory| advisory.vulnerable?(v) }
159
+ end
160
+ end
161
+ end
162
+
163
+ def latest_version_resolvable_with_full_unlock?
164
+ return false unless latest_version
165
+
166
+ return version_resolver.latest_version_resolvable_with_full_unlock? if dependency.top_level?
167
+
168
+ return false unless security_advisories.any?
169
+
170
+ vulnerability_audit["fix_available"]
171
+ end
172
+
173
+ def updated_dependencies_after_full_unlock
174
+ return conflicting_updated_dependencies if security_advisories.any? && vulnerability_audit["fix_available"]
175
+
176
+ version_resolver.dependency_updates_from_full_unlock
177
+ .map { |update_details| build_updated_dependency(update_details) }
178
+ end
179
+
180
+ # rubocop:disable Metrics/AbcSize
181
+ def conflicting_updated_dependencies
182
+ top_level_dependencies = top_level_dependency_lookup
183
+
184
+ updated_deps = []
185
+ vulnerability_audit["fix_updates"].each do |update|
186
+ dependency_name = update["dependency_name"]
187
+ requirements = top_level_dependencies[dependency_name]&.requirements || []
188
+
189
+ updated_deps << build_updated_dependency(
190
+ dependency: Dependency.new(
191
+ name: dependency_name,
192
+ package_manager: "bun",
193
+ requirements: requirements
194
+ ),
195
+ version: update["target_version"],
196
+ previous_version: update["current_version"]
197
+ )
198
+ end
199
+ # rubocop:enable Metrics/AbcSize
200
+
201
+ # We don't need to directly update the target dependency if it will
202
+ # be updated as a side effect of updating the parent. However, we need
203
+ # to include it so it's described in the PR and we'll pass validation
204
+ # that this dependency is at a non-vulnerable version.
205
+ if updated_deps.none? { |dep| dep.name == dependency.name }
206
+ target_version = vulnerability_audit["target_version"]
207
+ updated_deps << build_updated_dependency(
208
+ dependency: dependency,
209
+ version: target_version,
210
+ previous_version: dependency.version,
211
+ removed: target_version.nil?,
212
+ metadata: { information_only: true } # Instruct updater to not directly update this dependency
213
+ )
214
+ end
215
+
216
+ # Target dependency should be first in the result to support rebases
217
+ updated_deps.select { |dep| dep.name == dependency.name } +
218
+ updated_deps.reject { |dep| dep.name == dependency.name }
219
+ end
220
+
221
+ def top_level_dependency_lookup
222
+ top_level_dependencies = FileParser.new(
223
+ dependency_files: dependency_files,
224
+ credentials: credentials,
225
+ source: nil
226
+ ).parse.select(&:top_level?)
227
+
228
+ top_level_dependencies.to_h { |dep| [dep.name, dep] }
229
+ end
230
+
231
+ def build_updated_dependency(update_details)
232
+ original_dep = update_details.fetch(:dependency)
233
+ removed = update_details.fetch(:removed, false)
234
+ version = update_details.fetch(:version).to_s unless removed
235
+ previous_version = update_details.fetch(:previous_version)&.to_s
236
+ metadata = update_details.fetch(:metadata, {})
237
+
238
+ Dependency.new(
239
+ name: original_dep.name,
240
+ version: version,
241
+ requirements: RequirementsUpdater.new(
242
+ requirements: original_dep.requirements,
243
+ updated_source: original_dep == dependency ? updated_source : original_source(original_dep),
244
+ latest_resolvable_version: version,
245
+ update_strategy: requirements_update_strategy
246
+ ).updated_requirements,
247
+ previous_version: previous_version,
248
+ previous_requirements: original_dep.requirements,
249
+ package_manager: original_dep.package_manager,
250
+ removed: removed,
251
+ metadata: metadata
252
+ )
253
+ end
254
+
255
+ def latest_resolvable_transitive_security_fix_version_with_no_unlock
256
+ fix_possible = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(
257
+ [latest_resolvable_version].compact,
258
+ security_advisories
259
+ ).any?
260
+ return nil unless fix_possible
261
+
262
+ latest_resolvable_version
263
+ end
264
+
265
+ def latest_resolvable_version_with_no_unlock_for_git_dependency
266
+ reqs = dependency.requirements.filter_map do |r|
267
+ next if r.fetch(:requirement).nil?
268
+
269
+ requirement_class.requirements_array(r.fetch(:requirement))
270
+ end
271
+
272
+ current_version =
273
+ if existing_version_is_sha? ||
274
+ !version_class.correct?(dependency.version)
275
+ dependency.version
276
+ else
277
+ version_class.new(dependency.version)
278
+ end
279
+
280
+ return current_version if git_commit_checker.pinned?
281
+
282
+ # TODO: Really we should get a tag that satisfies the semver req
283
+ return current_version if reqs.any?
284
+
285
+ git_commit_checker.head_commit_for_current_branch
286
+ end
287
+
288
+ def latest_version_for_git_dependency
289
+ @latest_version_for_git_dependency ||=
290
+ if version_class.correct?(dependency.version)
291
+ latest_git_version_details[:version] &&
292
+ version_class.new(latest_git_version_details[:version])
293
+ else
294
+ latest_git_version_details[:sha]
295
+ end
296
+ end
297
+
298
+ def latest_released_version
299
+ @latest_released_version ||=
300
+ latest_version_finder.latest_version_from_registry
301
+ end
302
+
303
+ def latest_version_details
304
+ @latest_version_details ||=
305
+ if git_dependency?
306
+ latest_git_version_details
307
+ else
308
+ { version: latest_released_version }
309
+ end
310
+ end
311
+
312
+ def latest_version_finder
313
+ @latest_version_finder ||=
314
+ LatestVersionFinder.new(
315
+ dependency: dependency,
316
+ credentials: credentials,
317
+ dependency_files: dependency_files,
318
+ ignored_versions: ignored_versions,
319
+ raise_on_ignored: raise_on_ignored,
320
+ security_advisories: security_advisories
321
+ )
322
+ end
323
+
324
+ def version_resolver
325
+ @version_resolver ||=
326
+ VersionResolver.new(
327
+ dependency: dependency,
328
+ credentials: credentials,
329
+ dependency_files: dependency_files,
330
+ latest_allowable_version: latest_version,
331
+ latest_version_finder: latest_version_finder,
332
+ repo_contents_path: repo_contents_path,
333
+ dependency_group: dependency_group
334
+ )
335
+ end
336
+
337
+ def subdependency_version_resolver
338
+ @subdependency_version_resolver ||=
339
+ SubdependencyVersionResolver.new(
340
+ dependency: dependency,
341
+ credentials: credentials,
342
+ dependency_files: dependency_files,
343
+ ignored_versions: ignored_versions,
344
+ latest_allowable_version: latest_version,
345
+ repo_contents_path: repo_contents_path
346
+ )
347
+ end
348
+
349
+ def git_dependency?
350
+ git_commit_checker.git_dependency?
351
+ end
352
+
353
+ def latest_git_version_details
354
+ semver_req =
355
+ dependency.requirements
356
+ .find { |req| req.dig(:source, :type) == "git" }
357
+ &.fetch(:requirement)
358
+
359
+ # If there was a semver requirement provided or the dependency was
360
+ # pinned to a version, look for the latest tag
361
+ if semver_req || git_commit_checker.pinned_ref_looks_like_version?
362
+ latest_tag = git_commit_checker.local_tag_for_latest_version
363
+ return {
364
+ sha: latest_tag&.fetch(:commit_sha),
365
+ version: latest_tag&.fetch(:tag)&.gsub(/^[^\d]*/, "")
366
+ }
367
+ end
368
+
369
+ # Otherwise, if the gem isn't pinned, the latest version is just the
370
+ # latest commit for the specified branch.
371
+ return { sha: git_commit_checker.head_commit_for_current_branch } unless git_commit_checker.pinned?
372
+
373
+ # If the dependency is pinned to a tag that doesn't look like a
374
+ # version then there's nothing we can do.
375
+ { sha: dependency.version }
376
+ end
377
+
378
+ def updated_source
379
+ # Never need to update source, unless a git_dependency
380
+ return dependency_source_details unless git_dependency?
381
+
382
+ # Update the git tag if updating a pinned version
383
+ if git_commit_checker.pinned_ref_looks_like_version? &&
384
+ !git_commit_checker.local_tag_for_latest_version.nil?
385
+ new_tag = git_commit_checker.local_tag_for_latest_version
386
+ return dependency_source_details.merge(ref: new_tag.fetch(:tag))
387
+ end
388
+
389
+ # Otherwise return the original source
390
+ dependency_source_details
391
+ end
392
+
393
+ def library?
394
+ return true unless dependency.version
395
+ return true if dependency_files.any? { |f| f.name == "lerna.json" }
396
+
397
+ @library =
398
+ LibraryDetector.new(
399
+ package_json_file: package_json,
400
+ credentials: credentials,
401
+ dependency_files: dependency_files
402
+ ).library?
403
+ end
404
+
405
+ def security_update?
406
+ security_advisories.any?
407
+ end
408
+
409
+ def dependency_source_details
410
+ original_source(dependency)
411
+ end
412
+
413
+ def original_source(updated_dependency)
414
+ sources =
415
+ updated_dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact
416
+ .sort_by do |source|
417
+ Dependabot::Javascript::Shared::UpdateChecker::RegistryFinder.central_registry?(source[:url]) ? 1 : 0
418
+ end
419
+
420
+ sources.first
421
+ end
422
+
423
+ def package_json
424
+ @package_json ||=
425
+ dependency_files.find { |f| f.name == "package.json" }
426
+ end
427
+
428
+ def git_commit_checker
429
+ @git_commit_checker ||=
430
+ GitCommitChecker.new(
431
+ dependency: dependency,
432
+ credentials: credentials,
433
+ ignored_versions: ignored_versions,
434
+ raise_on_ignored: raise_on_ignored
435
+ )
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class Version < Dependabot::Javascript::Shared::Version
8
+ end
9
+ end
10
+ end
11
+ end