dependabot-bun 0.296.0 → 0.296.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) 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 +343 -38
  70. data/lib/dependabot/bun/file_parser/bun_lock.rb +3 -11
  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 +41 -27
  79. data/lib/dependabot/bun/language.rb +2 -2
  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 +261 -27
  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 +134 -2
  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 +128 -2
  100. data/lib/dependabot/bun/version_selector.rb +61 -0
  101. data/lib/dependabot/bun.rb +338 -26
  102. metadata +101 -27
  103. data/lib/dependabot/javascript/file_fetcher_helper.rb +0 -245
  104. data/lib/dependabot/javascript/requirement.rb +0 -141
  105. data/lib/dependabot/javascript/version.rb +0 -135
  106. data/lib/dependabot/javascript.rb +0 -8
@@ -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)
@@ -1,6 +1,12 @@
1
1
  # typed: strong
2
2
  # frozen_string_literal: true
3
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
+
4
10
  module Dependabot
5
11
  module Bun
6
12
  module Helpers
@@ -15,9 +21,43 @@ module Dependabot
15
21
  BUN_DEFAULT_VERSION
16
22
  end
17
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
+
18
55
  sig { returns(T.nilable(String)) }
19
56
  def self.bun_version
20
- run_bun_command("--version", fingerprint: "--version").strip
57
+ version = run_bun_command("--version", fingerprint: "--version").strip
58
+ if version.include?("+")
59
+ version.split("+").first # Remove build info, if present
60
+ end
21
61
  rescue StandardError => e
22
62
  Dependabot.logger.error("Error retrieving Bun version: #{e.message}")
23
63
  nil
@@ -41,34 +81,8 @@ module Dependabot
41
81
  raise
42
82
  end
43
83
 
44
- # Fetch the currently installed version of the package manager directly
45
- # from the system
46
- sig { params(name: String).returns(String) }
47
- def self.local_package_manager_version(name)
48
- Dependabot::SharedHelpers.run_shell_command(
49
- "#{name} -v",
50
- fingerprint: "#{name} -v"
51
- ).strip
52
- end
53
-
54
- # Run single command on package manager returning stdout/stderr
55
- sig do
56
- params(
57
- name: String,
58
- command: String,
59
- fingerprint: T.nilable(String)
60
- ).returns(String)
61
- end
62
- def self.package_manager_run_command(name, command, fingerprint: nil)
63
- return run_bun_command(command, fingerprint: fingerprint) if name == PackageManager::NAME
64
-
65
- # TODO: remove this method and just use the one in the PackageManager class
66
- "noop"
67
- end
68
-
69
84
  sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).returns(T::Array[Dependency]) }
70
85
  def self.dependencies_with_all_versions_metadata(dependency_set)
71
- # TODO: Check if we still need this method
72
86
  dependency_set.dependencies.map do |dependency|
73
87
  dependency.metadata[:all_versions] = dependency_set.all_versions_for_name(dependency.name)
74
88
  dependency
@@ -1,7 +1,7 @@
1
1
  # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "dependabot/npm_and_yarn/package_manager"
4
+ require "dependabot/bun/package_manager"
5
5
 
6
6
  module Dependabot
7
7
  module Bun
@@ -17,7 +17,7 @@ module Dependabot
17
17
  params(
18
18
  detected_version: T.nilable(String),
19
19
  raw_version: T.nilable(String),
20
- requirement: T.nilable(Dependabot::NpmAndYarn::Requirement)
20
+ requirement: T.nilable(Dependabot::Bun::Requirement)
21
21
  ).void
22
22
  end
23
23
  def initialize(detected_version: nil, raw_version: nil, requirement: nil)