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,477 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # See https://docs.npmjs.com/files/package.json for package.json format docs.
5
+
6
+ require "dependabot/dependency"
7
+ require "dependabot/file_parsers"
8
+ require "dependabot/file_parsers/base"
9
+ require "dependabot/shared_helpers"
10
+ require "dependabot/bun/helpers"
11
+ require "dependabot/bun/native_helpers"
12
+ require "dependabot/bun/version"
13
+ require "dependabot/bun/requirement"
14
+ require "dependabot/bun/package_manager"
15
+ require "dependabot/bun/registry_parser"
16
+ require "dependabot/git_metadata_fetcher"
17
+ require "dependabot/git_commit_checker"
18
+ require "dependabot/errors"
19
+ require "sorbet-runtime"
20
+
21
+ module Dependabot
22
+ module Bun
23
+ class FileParser < Dependabot::FileParsers::Base # rubocop:disable Metrics/ClassLength
24
+ extend T::Sig
25
+
26
+ require "dependabot/file_parsers/base/dependency_set"
27
+ require_relative "file_parser/lockfile_parser"
28
+
29
+ DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String])
30
+ GIT_URL_REGEX = %r{
31
+ (?<git_prefix>^|^git.*?|^github:|^bitbucket:|^gitlab:|github\.com/)
32
+ (?<username>[a-z0-9-]+)/
33
+ (?<repo>[a-z0-9_.-]+)
34
+ (
35
+ (?:\#semver:(?<semver>.+))|
36
+ (?:\#(?=[\^~=<>*])(?<semver>.+))|
37
+ (?:\#(?<ref>.+))
38
+ )?$
39
+ }ix
40
+
41
+ sig do
42
+ params(
43
+ json: T::Hash[String, T.untyped],
44
+ _block: T.proc.params(arg0: String, arg1: String, arg2: String).void
45
+ )
46
+ .void
47
+ end
48
+ def self.each_dependency(json, &_block)
49
+ DEPENDENCY_TYPES.each do |type|
50
+ deps = json[type] || {}
51
+ deps.each do |name, requirement|
52
+ yield(name, requirement, type)
53
+ end
54
+ end
55
+ end
56
+
57
+ sig { override.returns(T::Array[Dependency]) }
58
+ def parse
59
+ dependency_set = DependencySet.new
60
+ dependency_set += manifest_dependencies
61
+ dependency_set += lockfile_dependencies
62
+
63
+ dependencies = Helpers.dependencies_with_all_versions_metadata(dependency_set)
64
+
65
+ dependencies.reject do |dep|
66
+ reqs = dep.requirements
67
+
68
+ # Ignore dependencies defined in support files, since we don't want PRs for those
69
+ support_reqs = reqs.select { |r| support_package_files.any? { |f| f.name == r[:file] } }
70
+ next true if support_reqs.any?
71
+
72
+ # TODO: Currently, Dependabot can't handle dependencies that have both
73
+ # a git source *and* a non-git source. Fix that!
74
+ git_reqs = reqs.select { |r| r.dig(:source, :type) == "git" }
75
+ next false if git_reqs.none?
76
+ next true if git_reqs.map { |r| r.fetch(:source) }.uniq.count > 1
77
+
78
+ dep.requirements.any? { |r| r.dig(:source, :type) != "git" }
79
+ end
80
+ end
81
+
82
+ sig { returns(Ecosystem) }
83
+ def ecosystem
84
+ @ecosystem ||= T.let(
85
+ Ecosystem.new(
86
+ name: ECOSYSTEM,
87
+ package_manager: package_manager_helper.package_manager,
88
+ language: package_manager_helper.language
89
+ ),
90
+ T.nilable(Ecosystem)
91
+ )
92
+ end
93
+
94
+ private
95
+
96
+ sig { returns(PackageManagerHelper) }
97
+ def package_manager_helper
98
+ @package_manager_helper ||= T.let(
99
+ PackageManagerHelper.new(
100
+ parsed_package_json,
101
+ lockfiles,
102
+ registry_config_files,
103
+ credentials
104
+ ), T.nilable(PackageManagerHelper)
105
+ )
106
+ end
107
+
108
+ sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) }
109
+ def lockfiles
110
+ {
111
+ bun: bun_lock
112
+ }
113
+ end
114
+
115
+ sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) }
116
+ def registry_config_files
117
+ {
118
+ npmrc: npmrc
119
+ }
120
+ end
121
+
122
+ sig { returns(T.untyped) }
123
+ def parsed_package_json
124
+ JSON.parse(T.must(package_json.content))
125
+ rescue JSON::ParserError
126
+ raise Dependabot::DependencyFileNotParseable, package_json.path
127
+ end
128
+
129
+ sig { returns(Dependabot::DependencyFile) }
130
+ def package_json
131
+ # Declare the instance variable with T.let and the correct type
132
+ @package_json ||= T.let(
133
+ T.must(dependency_files.find { |f| f.name == MANIFEST_FILENAME }),
134
+ T.nilable(Dependabot::DependencyFile)
135
+ )
136
+ end
137
+
138
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
139
+ def bun_lock
140
+ @bun_lock ||= T.let(dependency_files.find do |f|
141
+ f.name.end_with?(BunPackageManager::LOCKFILE_NAME)
142
+ end, T.nilable(Dependabot::DependencyFile))
143
+ end
144
+
145
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
146
+ def npmrc
147
+ @npmrc ||= T.let(dependency_files.find do |f|
148
+ f.name.end_with?(BunPackageManager::RC_FILENAME)
149
+ end, T.nilable(Dependabot::DependencyFile))
150
+ end
151
+
152
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
153
+ def manifest_dependencies
154
+ dependency_set = DependencySet.new
155
+
156
+ package_files.each do |file|
157
+ json = JSON.parse(T.must(file.content))
158
+
159
+ # TODO: Currently, Dependabot can't handle flat dependency files
160
+ # (and will error at the FileUpdater stage, because the
161
+ # UpdateChecker doesn't take account of flat resolution).
162
+ next if json["flat"]
163
+
164
+ self.class.each_dependency(json) do |name, requirement, type|
165
+ next unless requirement.is_a?(String)
166
+
167
+ # Skip dependencies using Yarn workspace cross-references as requirements
168
+ next if requirement.start_with?("workspace:", "catalog:")
169
+
170
+ requirement = "*" if requirement == ""
171
+ dep = build_dependency(
172
+ file: file, type: type, name: name, requirement: requirement
173
+ )
174
+ dependency_set << dep if dep
175
+ end
176
+ end
177
+
178
+ dependency_set
179
+ end
180
+
181
+ sig { returns(LockfileParser) }
182
+ def lockfile_parser
183
+ @lockfile_parser ||= T.let(
184
+ LockfileParser.new(
185
+ dependency_files: dependency_files
186
+ ), T.nilable(Dependabot::Bun::FileParser::LockfileParser)
187
+ )
188
+ end
189
+
190
+ sig { returns(Dependabot::FileParsers::Base::DependencySet) }
191
+ def lockfile_dependencies
192
+ lockfile_parser.parse_set
193
+ end
194
+
195
+ sig do
196
+ params(file: DependencyFile, type: T.untyped, name: String, requirement: String)
197
+ .returns(T.nilable(Dependency))
198
+ end
199
+ def build_dependency(file:, type:, name:, requirement:)
200
+ lockfile_details = lockfile_parser.lockfile_details(
201
+ dependency_name: name,
202
+ requirement: requirement,
203
+ manifest_name: file.name
204
+ )
205
+ version = version_for(requirement, lockfile_details)
206
+ converted_version = T.let(if version.nil?
207
+ nil
208
+ elsif version.is_a?(String)
209
+ version
210
+ else
211
+ Dependabot::Version.new(version)
212
+ end, T.nilable(T.any(String, Dependabot::Version)))
213
+
214
+ return if lockfile_details && !version
215
+ return if ignore_requirement?(requirement)
216
+ return if workspace_package_names.include?(name)
217
+
218
+ # TODO: Handle aliased packages:
219
+ # https://github.com/dependabot/dependabot-core/pull/1115
220
+ #
221
+ # Ignore dependencies with an alias in the name
222
+ # Example: "my-fetch-factory@npm:fetch-factory"
223
+ return if aliased_package_name?(name)
224
+
225
+ Dependency.new(
226
+ name: name,
227
+ version: converted_version,
228
+ package_manager: ECOSYSTEM,
229
+ requirements: [{
230
+ requirement: requirement_for(requirement),
231
+ file: file.name,
232
+ groups: [type],
233
+ source: source_for(name, requirement, lockfile_details)
234
+ }]
235
+ )
236
+ end
237
+
238
+ sig { override.void }
239
+ def check_required_files
240
+ return if get_original_file(MANIFEST_FILENAME)
241
+
242
+ raise DependencyFileNotFound.new(nil,
243
+ "#{MANIFEST_FILENAME} not found.")
244
+ end
245
+
246
+ sig { params(requirement: String).returns(T::Boolean) }
247
+ def ignore_requirement?(requirement)
248
+ return true if local_path?(requirement)
249
+ return true if non_git_url?(requirement)
250
+
251
+ # TODO: Handle aliased packages:
252
+ # https://github.com/dependabot/dependabot-core/pull/1115
253
+ alias_package?(requirement)
254
+ end
255
+
256
+ sig { params(requirement: String).returns(T::Boolean) }
257
+ def local_path?(requirement)
258
+ requirement.start_with?("link:", "file:", "/", "./", "../", "~/")
259
+ end
260
+
261
+ sig { params(requirement: String).returns(T::Boolean) }
262
+ def alias_package?(requirement)
263
+ requirement.start_with?("#{BunPackageManager::NAME}:")
264
+ end
265
+
266
+ sig { params(requirement: String).returns(T::Boolean) }
267
+ def non_git_url?(requirement)
268
+ requirement.include?("://") && !git_url?(requirement)
269
+ end
270
+
271
+ sig { params(requirement: String).returns(T::Boolean) }
272
+ def git_url?(requirement)
273
+ requirement.match?(GIT_URL_REGEX)
274
+ end
275
+
276
+ sig { params(requirement: String).returns(T::Boolean) }
277
+ def git_url_with_semver?(requirement)
278
+ return false unless git_url?(requirement)
279
+
280
+ !T.must(requirement.match(GIT_URL_REGEX)).named_captures.fetch("semver").nil?
281
+ end
282
+
283
+ sig { params(name: String).returns(T::Boolean) }
284
+ def aliased_package_name?(name)
285
+ name.include?("@#{BunPackageManager::NAME}:")
286
+ end
287
+
288
+ sig { returns(T::Array[String]) }
289
+ def workspace_package_names
290
+ @workspace_package_names ||= T.let(package_files.filter_map do |f|
291
+ JSON.parse(T.must(f.content))["name"]
292
+ end, T.nilable(T::Array[String]))
293
+ end
294
+
295
+ sig do
296
+ params(requirement: String, lockfile_details: T.nilable(T::Hash[String, T.untyped]))
297
+ .returns(T.nilable(T.any(String, Integer, Gem::Version)))
298
+ end
299
+ def version_for(requirement, lockfile_details)
300
+ if git_url_with_semver?(requirement)
301
+ semver_version = lockfile_version_for(lockfile_details)
302
+ return semver_version if semver_version
303
+
304
+ git_revision = git_revision_for(lockfile_details)
305
+ version_from_git_revision(requirement, git_revision) || git_revision
306
+ elsif git_url?(requirement)
307
+ git_revision_for(lockfile_details)
308
+ elsif lockfile_details
309
+ lockfile_version_for(lockfile_details)
310
+ else
311
+ exact_version = exact_version_for(requirement)
312
+ return unless exact_version
313
+
314
+ semver_version_for(exact_version)
315
+ end
316
+ end
317
+
318
+ sig { params(lockfile_details: T.nilable(T::Hash[String, T.untyped])).returns(T.nilable(String)) }
319
+ def git_revision_for(lockfile_details)
320
+ version = T.cast(lockfile_details&.fetch("version", nil), T.nilable(String))
321
+ resolved = T.cast(lockfile_details&.fetch("resolved", nil), T.nilable(String))
322
+ [
323
+ version&.split("#")&.last,
324
+ resolved&.split("#")&.last,
325
+ resolved&.split("/")&.last
326
+ ].find { |str| commit_sha?(str) }
327
+ end
328
+
329
+ sig { params(string: T.nilable(String)).returns(T::Boolean) }
330
+ def commit_sha?(string)
331
+ return false unless string.is_a?(String)
332
+
333
+ string.match?(/^[0-9a-f]{40}$/)
334
+ end
335
+
336
+ sig { params(requirement: String, git_revision: T.nilable(String)).returns(T.nilable(String)) }
337
+ def version_from_git_revision(requirement, git_revision)
338
+ tags =
339
+ Dependabot::GitMetadataFetcher.new(
340
+ url: git_source_for(requirement).fetch(:url),
341
+ credentials: credentials
342
+ ).tags
343
+ .select { |t| [t.commit_sha, t.tag_sha].include?(git_revision) }
344
+
345
+ tags.each do |t|
346
+ next unless t.name.match?(Dependabot::GitCommitChecker::VERSION_REGEX)
347
+
348
+ version = T.must(t.name.match(Dependabot::GitCommitChecker::VERSION_REGEX))
349
+ .named_captures.fetch("version")
350
+ next unless version_class.correct?(version)
351
+
352
+ return version
353
+ end
354
+
355
+ nil
356
+ rescue Dependabot::GitDependenciesNotReachable
357
+ nil
358
+ end
359
+
360
+ sig do
361
+ params(lockfile_details: T.nilable(T::Hash[String, T.untyped]))
362
+ .returns(T.nilable(T.any(String, Integer, Gem::Version)))
363
+ end
364
+ def lockfile_version_for(lockfile_details)
365
+ semver_version_for(lockfile_details&.fetch("version", ""))
366
+ end
367
+
368
+ sig { params(version: T.nilable(String)).returns(T.nilable(T.any(String, Integer, Gem::Version))) }
369
+ def semver_version_for(version)
370
+ version_class.semver_for(version)
371
+ end
372
+
373
+ sig { params(requirement: String).returns(T.nilable(String)) }
374
+ def exact_version_for(requirement)
375
+ req = requirement_class.new(requirement)
376
+ return unless req.exact?
377
+
378
+ req.requirements.first.last.to_s
379
+ rescue Gem::Requirement::BadRequirementError
380
+ # If it doesn't parse, it's definitely not exact
381
+ end
382
+
383
+ sig do
384
+ params(name: String, requirement: String, lockfile_details: T.nilable(T::Hash[String, T.untyped]))
385
+ .returns(T.nilable(T::Hash[Symbol, T.untyped]))
386
+ end
387
+ def source_for(name, requirement, lockfile_details)
388
+ return git_source_for(requirement) if git_url?(requirement)
389
+
390
+ resolved_url = lockfile_details&.fetch("resolved", nil)
391
+
392
+ resolution = lockfile_details&.fetch("resolution", nil)
393
+ package_match = resolution&.match(/__archiveUrl=(?<package_url>.+)/)
394
+ resolved_url = CGI.unescape(package_match.named_captures.fetch("package_url", "")) if package_match
395
+
396
+ return unless resolved_url
397
+ return unless resolved_url.start_with?("http")
398
+ return if resolved_url.match?(/(?<!pkg\.)github/)
399
+
400
+ RegistryParser.new(
401
+ resolved_url: resolved_url,
402
+ credentials: credentials
403
+ ).registry_source_for(name)
404
+ end
405
+
406
+ sig { params(requirement: String).returns(T.nilable(String)) }
407
+ def requirement_for(requirement)
408
+ return requirement unless git_url?(requirement)
409
+
410
+ details = T.must(requirement.match(GIT_URL_REGEX)).named_captures
411
+ details["semver"]
412
+ end
413
+
414
+ sig { params(requirement: String).returns(T::Hash[Symbol, T.untyped]) }
415
+ def git_source_for(requirement)
416
+ details = T.must(requirement.match(GIT_URL_REGEX)).named_captures
417
+ prefix = T.must(details.fetch("git_prefix"))
418
+
419
+ host = if prefix.include?("git@") || prefix.include?("://")
420
+ T.must(prefix.split("git@").last)
421
+ .sub(%r{.*?://}, "")
422
+ .sub(%r{[:/]$}, "")
423
+ .split("#").first
424
+ elsif prefix.include?("bitbucket") then "bitbucket.org"
425
+ elsif prefix.include?("gitlab") then "gitlab.com"
426
+ else
427
+ "github.com"
428
+ end
429
+
430
+ {
431
+ type: "git",
432
+ url: "https://#{host}/#{details['username']}/#{details['repo']}",
433
+ branch: nil,
434
+ ref: details["ref"] || "master"
435
+ }
436
+ end
437
+
438
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
439
+ def support_package_files
440
+ @support_package_files ||= T.let(sub_package_files.select(&:support_file?), T.nilable(T::Array[DependencyFile]))
441
+ end
442
+
443
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
444
+ def sub_package_files
445
+ return T.must(@sub_package_files) if defined?(@sub_package_files)
446
+
447
+ files = dependency_files.select { |f| f.name.end_with?(MANIFEST_FILENAME) }
448
+ .reject { |f| f.name == MANIFEST_FILENAME }
449
+ .reject { |f| f.name.include?("node_modules/") }
450
+ @sub_package_files ||= T.let(files, T.nilable(T::Array[Dependabot::DependencyFile]))
451
+ end
452
+
453
+ sig { returns(T::Array[DependencyFile]) }
454
+ def package_files
455
+ @package_files ||= T.let(
456
+ [
457
+ dependency_files.find { |f| f.name == MANIFEST_FILENAME },
458
+ *sub_package_files
459
+ ].compact, T.nilable(T::Array[DependencyFile])
460
+ )
461
+ end
462
+
463
+ sig { returns(T.class_of(Dependabot::Bun::Version)) }
464
+ def version_class
465
+ Bun::Version
466
+ end
467
+
468
+ sig { returns(T.class_of(Dependabot::Bun::Requirement)) }
469
+ def requirement_class
470
+ Bun::Requirement
471
+ end
472
+ end
473
+ end
474
+ end
475
+
476
+ Dependabot::FileParsers
477
+ .register(Dependabot::Bun::ECOSYSTEM, Dependabot::Bun::FileParser)
@@ -0,0 +1,144 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/bun/helpers"
5
+ require "dependabot/bun/update_checker/registry_finder"
6
+ require "dependabot/bun/registry_parser"
7
+ require "dependabot/shared_helpers"
8
+
9
+ module Dependabot
10
+ module Bun
11
+ class FileUpdater < Dependabot::FileUpdaters::Base
12
+ class BunLockfileUpdater
13
+ require_relative "npmrc_builder"
14
+ require_relative "package_json_updater"
15
+
16
+ def initialize(dependencies:, dependency_files:, repo_contents_path:, credentials:)
17
+ @dependencies = dependencies
18
+ @dependency_files = dependency_files
19
+ @repo_contents_path = repo_contents_path
20
+ @credentials = credentials
21
+ end
22
+
23
+ def updated_bun_lock_content(bun_lock)
24
+ @updated_bun_lock_content ||= {}
25
+ return @updated_bun_lock_content[bun_lock.name] if @updated_bun_lock_content[bun_lock.name]
26
+
27
+ new_content = run_bun_update(bun_lock: bun_lock)
28
+ @updated_bun_lock_content[bun_lock.name] = new_content
29
+ rescue SharedHelpers::HelperSubprocessFailed => e
30
+ handle_bun_lock_updater_error(e, bun_lock)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :dependencies
36
+ attr_reader :dependency_files
37
+ attr_reader :repo_contents_path
38
+ attr_reader :credentials
39
+
40
+ ERR_PATTERNS = {
41
+ /get .* 404/i => Dependabot::DependencyNotFound,
42
+ /installfailed cloning repository/i => Dependabot::DependencyNotFound,
43
+ /file:.* failed to resolve/i => Dependabot::DependencyNotFound,
44
+ /no version matching/i => Dependabot::DependencyFileNotResolvable,
45
+ /failed to resolve/i => Dependabot::DependencyFileNotResolvable
46
+ }.freeze
47
+
48
+ def run_bun_update(bun_lock:)
49
+ SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
50
+ File.write(".npmrc", npmrc_content(bun_lock))
51
+
52
+ SharedHelpers.with_git_configured(credentials: credentials) do
53
+ run_bun_updater
54
+
55
+ write_final_package_json_files
56
+
57
+ run_bun_install
58
+
59
+ File.read(bun_lock.name)
60
+ end
61
+ end
62
+ end
63
+
64
+ def run_bun_updater
65
+ dependency_updates = dependencies.map do |d|
66
+ "#{d.name}@#{d.version}"
67
+ end.join(" ")
68
+
69
+ Helpers.run_bun_command(
70
+ "install #{dependency_updates} --save-text-lockfile",
71
+ fingerprint: "install <dependency_updates> --save-text-lockfile"
72
+ )
73
+ end
74
+
75
+ def run_bun_install
76
+ Helpers.run_bun_command(
77
+ "install --save-text-lockfile"
78
+ )
79
+ end
80
+
81
+ def lockfile_dependencies(lockfile)
82
+ @lockfile_dependencies ||= {}
83
+ @lockfile_dependencies[lockfile.name] ||=
84
+ Bun::FileParser.new(
85
+ dependency_files: [lockfile, *package_files],
86
+ source: nil,
87
+ credentials: credentials
88
+ ).parse
89
+ end
90
+
91
+ def handle_bun_lock_updater_error(error, _bun_lock)
92
+ error_message = error.message
93
+
94
+ ERR_PATTERNS.each do |pattern, error_class|
95
+ raise error_class, error_message if error_message.match?(pattern)
96
+ end
97
+
98
+ raise error
99
+ end
100
+
101
+ def write_final_package_json_files
102
+ package_files.each do |file|
103
+ path = file.name
104
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
105
+ File.write(path, updated_package_json_content(file))
106
+ end
107
+ end
108
+
109
+ def npmrc_content(bun_lock)
110
+ NpmrcBuilder.new(
111
+ credentials: credentials,
112
+ dependency_files: dependency_files,
113
+ dependencies: lockfile_dependencies(bun_lock)
114
+ ).npmrc_content
115
+ end
116
+
117
+ def updated_package_json_content(file)
118
+ @updated_package_json_content ||= {}
119
+ @updated_package_json_content[file.name] ||=
120
+ PackageJsonUpdater.new(
121
+ package_json: file,
122
+ dependencies: dependencies
123
+ ).updated_package_json.content
124
+ end
125
+
126
+ def package_files
127
+ @package_files ||= dependency_files.select { |f| f.name.end_with?("package.json") }
128
+ end
129
+
130
+ def base_dir
131
+ dependency_files.first.directory
132
+ end
133
+
134
+ def npmrc_file
135
+ dependency_files.find { |f| f.name == ".npmrc" }
136
+ end
137
+
138
+ def sanitize_message(message)
139
+ message.gsub(/"|\[|\]|\}|\{/, "")
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end