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
@@ -0,0 +1,378 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require "dependabot/bun/file_updater"
7
+
8
+ module Dependabot
9
+ module Bun
10
+ class FileUpdater < Dependabot::FileUpdaters::Base
11
+ class PackageJsonUpdater
12
+ extend T::Sig
13
+
14
+ LOCAL_PACKAGE = T.let([/portal:/, /file:/].freeze, T::Array[Regexp])
15
+
16
+ PATCH_PACKAGE = T.let([/patch:/].freeze, T::Array[Regexp])
17
+
18
+ sig do
19
+ params(
20
+ package_json: Dependabot::DependencyFile,
21
+ dependencies: T::Array[Dependabot::Dependency]
22
+ ).void
23
+ end
24
+ def initialize(package_json:, dependencies:)
25
+ @package_json = package_json
26
+ @dependencies = dependencies
27
+ end
28
+
29
+ sig { returns(Dependabot::DependencyFile) }
30
+ def updated_package_json
31
+ updated_file = package_json.dup
32
+ updated_file.content = updated_package_json_content
33
+ updated_file
34
+ end
35
+
36
+ private
37
+
38
+ sig { returns(Dependabot::DependencyFile) }
39
+ attr_reader :package_json
40
+
41
+ sig { returns(T::Array[Dependabot::Dependency]) }
42
+ attr_reader :dependencies
43
+
44
+ # rubocop:disable Metrics/PerceivedComplexity
45
+
46
+ sig { returns(T.nilable(String)) }
47
+ def updated_package_json_content
48
+ # checks if we are updating single dependency in package.json
49
+ unique_deps_count = dependencies.map(&:name).to_a.uniq.compact.length
50
+
51
+ dependencies.reduce(package_json.content.dup) do |content, dep|
52
+ updated_requirements(dep)&.each do |new_req|
53
+ old_req = old_requirement(dep, new_req)
54
+
55
+ new_content = update_package_json_declaration(
56
+ package_json_content: T.must(content),
57
+ dependency_name: dep.name,
58
+ old_req: old_req,
59
+ new_req: new_req
60
+ )
61
+
62
+ if Dependabot::Experiments.enabled?(:avoid_duplicate_updates_package_json) &&
63
+ (content == new_content && unique_deps_count > 1)
64
+
65
+ # (we observed that) package.json does not always contains the same dependencies compared to
66
+ # "dependencies" list, for example, dependencies object can contain same name dependency "dep"=> "1.0.0"
67
+ # and "dev" => "1.0.1" while package.json can only contain "dep" => "1.0.0",the other dependency is
68
+ # not present in package.json so we don't have to update it, this is most likely (as observed)
69
+ # a transitive dependency which only needs update in lockfile, So we avoid throwing exception and let
70
+ # the update continue.
71
+
72
+ Dependabot.logger.info("experiment: avoid_duplicate_updates_package_json.
73
+ Updating package.json for #{dep.name} ")
74
+
75
+ raise "Expected content to change!"
76
+ end
77
+
78
+ if !Dependabot::Experiments.enabled?(:avoid_duplicate_updates_package_json) && (content == new_content)
79
+ raise "Expected content to change!"
80
+ end
81
+
82
+ content = new_content
83
+ end
84
+
85
+ new_requirements(dep).each do |new_req|
86
+ old_req = old_requirement(dep, new_req)
87
+
88
+ content = update_package_json_resolutions(
89
+ package_json_content: T.must(content),
90
+ new_req: new_req,
91
+ dependency: dep,
92
+ old_req: old_req
93
+ )
94
+ end
95
+
96
+ content
97
+ end
98
+ end
99
+ # rubocop:enable Metrics/PerceivedComplexity
100
+ sig do
101
+ params(
102
+ dependency: Dependabot::Dependency,
103
+ new_requirement: T::Hash[Symbol, T.untyped]
104
+ )
105
+ .returns(T.nilable(T::Hash[Symbol, T.untyped]))
106
+ end
107
+ def old_requirement(dependency, new_requirement)
108
+ T.must(dependency.previous_requirements)
109
+ .select { |r| r[:file] == package_json.name }
110
+ .find { |r| r[:groups] == new_requirement[:groups] }
111
+ end
112
+
113
+ sig { params(dependency: Dependabot::Dependency).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
114
+ def new_requirements(dependency)
115
+ dependency.requirements.select { |r| r[:file] == package_json.name }
116
+ end
117
+
118
+ sig { params(dependency: Dependabot::Dependency).returns(T.nilable(T::Array[T::Hash[Symbol, T.untyped]])) }
119
+ def updated_requirements(dependency)
120
+ return unless dependency.previous_requirements
121
+
122
+ preliminary_check_for_update(dependency)
123
+
124
+ updated_requirement_pairs =
125
+ dependency.requirements.zip(T.must(dependency.previous_requirements))
126
+ .reject do |new_req, old_req|
127
+ next true if new_req == old_req
128
+ next false unless old_req&.fetch(:source).nil?
129
+
130
+ new_req[:requirement] == old_req&.fetch(:requirement)
131
+ end
132
+
133
+ updated_requirement_pairs
134
+ .map(&:first)
135
+ .select { |r| r[:file] == package_json.name }
136
+ end
137
+
138
+ sig do
139
+ params(
140
+ package_json_content: String,
141
+ new_req: T::Hash[Symbol, T.untyped],
142
+ dependency_name: String,
143
+ old_req: T.nilable(T::Hash[Symbol, T.untyped])
144
+ )
145
+ .returns(String)
146
+ end
147
+ def update_package_json_declaration(package_json_content:, new_req:, dependency_name:, old_req:)
148
+ original_line = declaration_line(
149
+ dependency_name: dependency_name,
150
+ dependency_req: old_req,
151
+ content: package_json_content
152
+ )
153
+
154
+ replacement_line = replacement_declaration_line(
155
+ original_line: original_line,
156
+ old_req: old_req,
157
+ new_req: new_req
158
+ )
159
+
160
+ groups = new_req.fetch(:groups)
161
+
162
+ update_package_json_sections(
163
+ groups,
164
+ package_json_content,
165
+ original_line,
166
+ replacement_line
167
+ )
168
+ end
169
+
170
+ # For full details on how Yarn resolutions work, see
171
+ # https://github.com/yarnpkg/rfcs/blob/master/implemented/
172
+ # 0000-selective-versions-resolutions.md
173
+ sig do
174
+ params(
175
+ package_json_content: String,
176
+ new_req: T::Hash[Symbol, T.untyped],
177
+ dependency: Dependabot::Dependency,
178
+ old_req: T.nilable(T::Hash[Symbol, T.untyped])
179
+ )
180
+ .returns(String)
181
+ end
182
+ def update_package_json_resolutions(package_json_content:, new_req:, dependency:, old_req:)
183
+ dep = dependency
184
+ parsed_json_content = JSON.parse(package_json_content)
185
+ resolutions =
186
+ parsed_json_content.fetch("resolutions", parsed_json_content.dig("pnpm", "overrides") || {})
187
+ .reject { |_, v| v != old_req && v != dep.previous_version }
188
+ .select { |k, _| k == dep.name || k.end_with?("/#{dep.name}") }
189
+
190
+ return package_json_content unless resolutions.any?
191
+
192
+ content = package_json_content
193
+ resolutions.each do |_, resolution|
194
+ original_line = declaration_line(
195
+ dependency_name: dep.name,
196
+ dependency_req: { requirement: resolution },
197
+ content: content
198
+ )
199
+
200
+ new_resolution = resolution == old_req ? new_req : dep.version
201
+
202
+ replacement_line = replacement_declaration_line(
203
+ original_line: original_line,
204
+ old_req: { requirement: resolution },
205
+ new_req: { requirement: new_resolution }
206
+ )
207
+
208
+ content = update_package_json_sections(
209
+ %w(resolutions overrides), content, original_line, replacement_line
210
+ )
211
+ end
212
+ content
213
+ end
214
+
215
+ sig do
216
+ params(
217
+ dependency_name: String,
218
+ dependency_req: T.nilable(T::Hash[Symbol, T.untyped]),
219
+ content: String
220
+ )
221
+ .returns(String)
222
+ end
223
+ def declaration_line(dependency_name:, dependency_req:, content:)
224
+ git_dependency = dependency_req&.dig(:source, :type) == "git"
225
+
226
+ unless git_dependency
227
+ requirement = dependency_req&.fetch(:requirement)
228
+ return content.match(/"#{Regexp.escape(dependency_name)}"\s*:\s*
229
+ "#{Regexp.escape(requirement)}"/x).to_s
230
+ end
231
+
232
+ username, repo =
233
+ dependency_req&.dig(:source, :url)&.split("/")&.last(2)
234
+
235
+ content.match(
236
+ %r{"#{Regexp.escape(dependency_name)}"\s*:\s*
237
+ ".*?#{Regexp.escape(username)}/#{Regexp.escape(repo)}.*"}x
238
+ ).to_s
239
+ end
240
+
241
+ sig do
242
+ params(
243
+ original_line: String,
244
+ old_req: T.nilable(T::Hash[Symbol, T.untyped]),
245
+ new_req: T::Hash[Symbol, T.untyped]
246
+ )
247
+ .returns(String)
248
+ end
249
+ def replacement_declaration_line(original_line:, old_req:, new_req:)
250
+ was_git_dependency = old_req&.dig(:source, :type) == "git"
251
+ now_git_dependency = new_req.dig(:source, :type) == "git"
252
+
253
+ unless was_git_dependency
254
+ return original_line.gsub(
255
+ %("#{old_req&.fetch(:requirement)}"),
256
+ %("#{new_req.fetch(:requirement)}")
257
+ )
258
+ end
259
+
260
+ unless now_git_dependency
261
+ return original_line.gsub(
262
+ /(?<=\s").*[^\\](?=")/,
263
+ new_req.fetch(:requirement)
264
+ )
265
+ end
266
+
267
+ if original_line.match?(/#[\^~=<>]|semver:/)
268
+ return update_git_semver_requirement(
269
+ original_line: original_line,
270
+ old_req: old_req,
271
+ new_req: new_req
272
+ )
273
+ end
274
+
275
+ original_line.gsub(
276
+ %(##{old_req&.dig(:source, :ref)}"),
277
+ %(##{new_req.dig(:source, :ref)}")
278
+ )
279
+ end
280
+
281
+ sig do
282
+ params(
283
+ original_line: String,
284
+ old_req: T.nilable(T::Hash[Symbol, String]),
285
+ new_req: T::Hash[Symbol, String]
286
+ )
287
+ .returns(String)
288
+ end
289
+ def update_git_semver_requirement(original_line:, old_req:, new_req:)
290
+ if original_line.include?("semver:")
291
+ return original_line.gsub(
292
+ %(semver:#{old_req&.fetch(:requirement)}"),
293
+ %(semver:#{new_req.fetch(:requirement)}")
294
+ )
295
+ end
296
+
297
+ raise "Not a semver req!" unless original_line.match?(/#[\^~=<>]/)
298
+
299
+ original_line.gsub(
300
+ %(##{old_req&.fetch(:requirement)}"),
301
+ %(##{new_req.fetch(:requirement)}")
302
+ )
303
+ end
304
+
305
+ sig do
306
+ params(
307
+ sections: T::Array[String],
308
+ content: String,
309
+ old_line: String,
310
+ new_line: String
311
+ )
312
+ .returns(String)
313
+ end
314
+ def update_package_json_sections(sections, content, old_line, new_line)
315
+ # Currently, Dependabot doesn't update peerDependencies. However,
316
+ # if a development dependency is being updated and its requirement
317
+ # matches the requirement on a peer dependency we probably want to
318
+ # update the peer too.
319
+ #
320
+ # TODO: Move this logic to the UpdateChecker (and parse peer deps)
321
+ sections += ["peerDependencies"]
322
+ sections_regex = /#{sections.join('|')}/
323
+
324
+ declaration_blocks = T.let([], T::Array[String])
325
+
326
+ content.scan(/['"]#{sections_regex}['"]\s*:\s*\{/m) do
327
+ mtch = T.must(Regexp.last_match)
328
+ declaration_blocks <<
329
+ (mtch.to_s + T.must(mtch.post_match[0..closing_bracket_index(mtch.post_match)]))
330
+ end
331
+
332
+ declaration_blocks.reduce(content.dup) do |new_content, block|
333
+ updated_block = block.sub(old_line, new_line)
334
+ new_content.sub(block, updated_block)
335
+ end
336
+ end
337
+
338
+ sig { params(string: String).returns(Integer) }
339
+ def closing_bracket_index(string)
340
+ closes_required = 1
341
+
342
+ string.chars.each_with_index do |char, index|
343
+ closes_required += 1 if char == "{"
344
+ closes_required -= 1 if char == "}"
345
+ return index if closes_required.zero?
346
+ end
347
+
348
+ 0
349
+ end
350
+
351
+ sig { params(dependency: Dependabot::Dependency).void }
352
+ def preliminary_check_for_update(dependency)
353
+ T.must(dependency.previous_requirements).each do |req, _dep|
354
+ next if req.fetch(:requirement).nil?
355
+
356
+ # some deps are patched with local patches, we don't need to update them
357
+ if req.fetch(:requirement).match?(Regexp.union(PATCH_PACKAGE))
358
+ Dependabot.logger.info("Func: updated_requirements. dependency patched #{dependency.name}," \
359
+ " Requirement: '#{req.fetch(:requirement)}'")
360
+
361
+ raise DependencyFileNotResolvable,
362
+ "Dependency is patched locally, Update not required."
363
+ end
364
+
365
+ # some deps are added as local packages, we don't need to update them as they are referred to a local path
366
+ next unless req.fetch(:requirement).match?(Regexp.union(LOCAL_PACKAGE))
367
+
368
+ Dependabot.logger.info("Func: updated_requirements. local package #{dependency.name}," \
369
+ " Requirement: '#{req.fetch(:requirement)}'")
370
+
371
+ raise DependencyFileNotResolvable,
372
+ "Local package, Update not required."
373
+ end
374
+ end
375
+ end
376
+ end
377
+ end
378
+ end
@@ -0,0 +1,203 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/file_updaters"
5
+ require "dependabot/file_updaters/base"
6
+ require "dependabot/file_updaters/vendor_updater"
7
+ require "dependabot/file_updaters/artifact_updater"
8
+ require "dependabot/bun/dependency_files_filterer"
9
+ require "dependabot/bun/sub_dependency_files_filterer"
10
+ require "sorbet-runtime"
11
+
12
+ module Dependabot
13
+ module Bun
14
+ class FileUpdater < Dependabot::FileUpdaters::Base
15
+ extend T::Sig
16
+
17
+ require_relative "file_updater/package_json_updater"
18
+ require_relative "file_updater/bun_lockfile_updater"
19
+
20
+ class NoChangeError < StandardError
21
+ extend T::Sig
22
+
23
+ sig { params(message: String, error_context: T::Hash[Symbol, T.untyped]).void }
24
+ def initialize(message:, error_context:)
25
+ super(message)
26
+ @error_context = error_context
27
+ end
28
+
29
+ sig { returns(T::Hash[Symbol, T.untyped]) }
30
+ def sentry_context
31
+ { extra: @error_context }
32
+ end
33
+ end
34
+
35
+ sig { override.returns(T::Array[Regexp]) }
36
+ def self.updated_files_regex
37
+ [
38
+ %r{^(?:.*/)?package\.json$},
39
+ %r{^(?:.*/)?\.pnp\.(?:js|cjs)$} # Matches .pnp.js or .pnp.cjs files
40
+ ]
41
+ end
42
+
43
+ sig { override.returns(T::Array[DependencyFile]) }
44
+ def updated_dependency_files
45
+ updated_files = T.let([], T::Array[DependencyFile])
46
+
47
+ updated_files += updated_manifest_files
48
+ updated_files += updated_lockfiles
49
+
50
+ if updated_files.none?
51
+
52
+ raise NoChangeError.new(
53
+ message: "No files were updated!",
54
+ error_context: error_context(updated_files: updated_files)
55
+ )
56
+ end
57
+
58
+ sorted_updated_files = updated_files.sort_by(&:name)
59
+ if sorted_updated_files == filtered_dependency_files.sort_by(&:name)
60
+ raise NoChangeError.new(
61
+ message: "Updated files are unchanged!",
62
+ error_context: error_context(updated_files: updated_files)
63
+ )
64
+ end
65
+
66
+ vendor_updated_files(updated_files)
67
+ end
68
+
69
+ private
70
+
71
+ sig { params(updated_files: T::Array[Dependabot::DependencyFile]).returns(T::Array[Dependabot::DependencyFile]) }
72
+ def vendor_updated_files(updated_files)
73
+ base_dir = T.must(updated_files.first).directory
74
+ pnp_updater.updated_files(base_directory: base_dir, only_paths: [".pnp.cjs", ".pnp.data.json"]).each do |file|
75
+ updated_files << file
76
+ end
77
+
78
+ updated_files
79
+ end
80
+
81
+ sig { returns(Dependabot::FileUpdaters::ArtifactUpdater) }
82
+ def pnp_updater
83
+ Dependabot::FileUpdaters::ArtifactUpdater.new(
84
+ repo_contents_path: repo_contents_path,
85
+ target_directory: "./"
86
+ )
87
+ end
88
+
89
+ sig { returns(T::Array[DependencyFile]) }
90
+ def filtered_dependency_files
91
+ @filtered_dependency_files ||= T.let(
92
+ if dependencies.any?(&:top_level?)
93
+ DependencyFilesFilterer.new(
94
+ dependency_files: dependency_files,
95
+ updated_dependencies: dependencies
96
+ ).files_requiring_update
97
+ else
98
+ SubDependencyFilesFilterer.new(
99
+ dependency_files: dependency_files,
100
+ updated_dependencies: dependencies
101
+ ).files_requiring_update
102
+ end, T.nilable(T::Array[DependencyFile])
103
+ )
104
+ end
105
+
106
+ sig { override.void }
107
+ def check_required_files
108
+ raise DependencyFileNotFound.new(nil, "package.json not found.") unless get_original_file("package.json")
109
+ end
110
+
111
+ sig { params(updated_files: T::Array[DependencyFile]).returns(T::Hash[Symbol, T.untyped]) }
112
+ def error_context(updated_files:)
113
+ {
114
+ dependencies: dependencies.map(&:to_h),
115
+ updated_files: updated_files.map(&:name),
116
+ dependency_files: dependency_files.map(&:name)
117
+ }
118
+ end
119
+
120
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
121
+ def bun_locks
122
+ @bun_locks ||= T.let(
123
+ filtered_dependency_files
124
+ .select { |f| f.name.end_with?("bun.lock") },
125
+ T.nilable(T::Array[Dependabot::DependencyFile])
126
+ )
127
+ end
128
+
129
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
130
+ def package_files
131
+ @package_files ||= T.let(
132
+ filtered_dependency_files.select do |f|
133
+ f.name.end_with?("package.json")
134
+ end, T.nilable(T::Array[DependencyFile])
135
+ )
136
+ end
137
+
138
+ sig { params(bun_lock: Dependabot::DependencyFile).returns(T::Boolean) }
139
+ def bun_lock_changed?(bun_lock)
140
+ bun_lock.content != updated_bun_lock_content(bun_lock)
141
+ end
142
+
143
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
144
+ def updated_manifest_files
145
+ package_files.filter_map do |file|
146
+ updated_content = updated_package_json_content(file)
147
+ next if updated_content == file.content
148
+
149
+ updated_file(file: file, content: T.must(updated_content))
150
+ end
151
+ end
152
+
153
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
154
+ def updated_lockfiles
155
+ updated_files = []
156
+
157
+ bun_locks.each do |bun_lock|
158
+ next unless bun_lock_changed?(bun_lock)
159
+
160
+ updated_files << updated_file(
161
+ file: bun_lock,
162
+ content: updated_bun_lock_content(bun_lock)
163
+ )
164
+ end
165
+
166
+ updated_files
167
+ end
168
+
169
+ sig { params(bun_lock: Dependabot::DependencyFile).returns(String) }
170
+ def updated_bun_lock_content(bun_lock)
171
+ @updated_bun_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
172
+ @updated_bun_lock_content[bun_lock.name] ||=
173
+ bun_lockfile_updater.updated_bun_lock_content(bun_lock)
174
+ end
175
+
176
+ sig { returns(Dependabot::Bun::FileUpdater::BunLockfileUpdater) }
177
+ def bun_lockfile_updater
178
+ @bun_lockfile_updater ||= T.let(
179
+ BunLockfileUpdater.new(
180
+ dependencies: dependencies,
181
+ dependency_files: dependency_files,
182
+ repo_contents_path: repo_contents_path,
183
+ credentials: credentials
184
+ ),
185
+ T.nilable(Dependabot::Bun::FileUpdater::BunLockfileUpdater)
186
+ )
187
+ end
188
+
189
+ sig { params(file: Dependabot::DependencyFile).returns(T.nilable(String)) }
190
+ def updated_package_json_content(file)
191
+ @updated_package_json_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
192
+ @updated_package_json_content[file.name] ||=
193
+ PackageJsonUpdater.new(
194
+ package_json: file,
195
+ dependencies: dependencies
196
+ ).updated_package_json.content
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ Dependabot::FileUpdaters
203
+ .register("bun", Dependabot::Bun::FileUpdater)
@@ -0,0 +1,93 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/dependency"
5
+ require "dependabot/file_parsers"
6
+ require "dependabot/file_parsers/base"
7
+ require "dependabot/shared_helpers"
8
+ require "sorbet-runtime"
9
+
10
+ module Dependabot
11
+ module Bun
12
+ module Helpers
13
+ extend T::Sig
14
+
15
+ # BUN Version Constants
16
+ BUN_V1 = 1
17
+ BUN_DEFAULT_VERSION = BUN_V1
18
+
19
+ sig { params(_bun_lock: T.nilable(DependencyFile)).returns(Integer) }
20
+ def self.bun_version_numeric(_bun_lock)
21
+ BUN_DEFAULT_VERSION
22
+ end
23
+
24
+ sig { returns(T.nilable(String)) }
25
+ def self.node_version
26
+ version = run_node_command("-v", fingerprint: "-v").strip
27
+
28
+ # Validate the output format (e.g., "v20.18.1" or "20.18.1")
29
+ if version.match?(/^v?\d+(\.\d+){2}$/)
30
+ version.strip.delete_prefix("v") # Remove the "v" prefix if present
31
+ end
32
+ rescue StandardError => e
33
+ Dependabot.logger.error("Error retrieving Node.js version: #{e.message}")
34
+ nil
35
+ end
36
+
37
+ sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
38
+ def self.run_node_command(command, fingerprint: nil)
39
+ full_command = "node #{command}"
40
+
41
+ Dependabot.logger.info("Running node command: #{full_command}")
42
+
43
+ result = Dependabot::SharedHelpers.run_shell_command(
44
+ full_command,
45
+ fingerprint: "node #{fingerprint || command}"
46
+ )
47
+
48
+ Dependabot.logger.info("Command executed successfully: #{full_command}")
49
+ result
50
+ rescue StandardError => e
51
+ Dependabot.logger.error("Error running node command: #{full_command}, Error: #{e.message}")
52
+ raise
53
+ end
54
+
55
+ sig { returns(T.nilable(String)) }
56
+ def self.bun_version
57
+ version = run_bun_command("--version", fingerprint: "--version").strip
58
+ if version.include?("+")
59
+ version.split("+").first # Remove build info, if present
60
+ end
61
+ rescue StandardError => e
62
+ Dependabot.logger.error("Error retrieving Bun version: #{e.message}")
63
+ nil
64
+ end
65
+
66
+ sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
67
+ def self.run_bun_command(command, fingerprint: nil)
68
+ full_command = "bun #{command}"
69
+
70
+ Dependabot.logger.info("Running bun command: #{full_command}")
71
+
72
+ result = Dependabot::SharedHelpers.run_shell_command(
73
+ full_command,
74
+ fingerprint: "bun #{fingerprint || command}"
75
+ )
76
+
77
+ Dependabot.logger.info("Command executed successfully: #{full_command}")
78
+ result
79
+ rescue StandardError => e
80
+ Dependabot.logger.error("Error running bun command: #{full_command}, Error: #{e.message}")
81
+ raise
82
+ end
83
+
84
+ sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).returns(T::Array[Dependency]) }
85
+ def self.dependencies_with_all_versions_metadata(dependency_set)
86
+ dependency_set.dependencies.map do |dependency|
87
+ dependency.metadata[:all_versions] = dependency_set.all_versions_for_name(dependency.name)
88
+ dependency
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end