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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e1a6baa9e79aa9cd7d64e96693546469b2d2ad636d72f439d301d9139fe286c8
4
+ data.tar.gz: a2fe19f72bdfe6387ebce218dfa1a6f96c7703e52dec88aa5bc0b7b251482e23
5
+ SHA512:
6
+ metadata.gz: 000f2f1bcccb6a184c4afb61a5c0934fbd3ff3c50bb19f5683ff845a8f739aab0117eb4bfd0d4a345434a5a322ea1afd21c0cb78536670b27ced3a517f224417
7
+ data.tar.gz: 121c902aa072824c42ff89bb7491f22a58a2327c2fbf4f4d53960a51d9aa5dc335688b34d977adba067aca1781c46ed8886c11f0a3c774a75c3c71915b336afc
@@ -0,0 +1,49 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "zeitwerk"
5
+
6
+ loader = Zeitwerk::Loader.new
7
+
8
+ # Set autoload paths for common/lib, excluding files whose content does not match the filename
9
+ loader.push_dir(File.join(__dir__, "../../../common/lib"))
10
+ loader.ignore(File.join(__dir__, "../../../common/lib/dependabot/errors.rb"))
11
+ loader.ignore(File.join(__dir__, "../../../common/lib/dependabot/logger.rb"))
12
+ loader.ignore(File.join(__dir__, "../../../common/lib/dependabot/notices.rb"))
13
+ loader.ignore(File.join(__dir__, "../../../common/lib/dependabot/clients/codecommit.rb"))
14
+
15
+ loader.push_dir(File.join(__dir__, ".."))
16
+ loader.ignore("#{__dir__}/../script", "#{__dir__}/../spec", "#{__dir__}/../dependabot-javascript.gemspec")
17
+
18
+ loader.on_load do |_file|
19
+ require "json"
20
+ require "sorbet-runtime"
21
+ require "dependabot/errors"
22
+ require "dependabot/logger"
23
+ require "dependabot/notices"
24
+ require "dependabot/clients/codecommit"
25
+ end
26
+
27
+ loader.log! if ENV["DEBUG"]
28
+ loader.setup
29
+
30
+ Dependabot::PullRequestCreator::Labeler
31
+ .register_label_details("bun", name: "javascript", colour: "168700")
32
+
33
+ Dependabot::Dependency.register_production_check("bun", ->(_) { true })
34
+
35
+ module Dependabot
36
+ module Javascript
37
+ module Bun
38
+ ECOSYSTEM = "bun"
39
+ end
40
+ end
41
+ end
42
+
43
+ Dependabot::FileFetchers.register("bun", Dependabot::Javascript::Bun::FileFetcher)
44
+ Dependabot::FileParsers.register("bun", Dependabot::Javascript::Bun::FileParser)
45
+ Dependabot::FileUpdaters.register("bun", Dependabot::Javascript::Bun::FileUpdater)
46
+ Dependabot::UpdateCheckers.register("bun", Dependabot::Javascript::Bun::UpdateChecker)
47
+ Dependabot::MetadataFinders.register("bun", Dependabot::Javascript::Shared::MetadataFinder)
48
+ Dependabot::Utils.register_requirement_class("bun", Dependabot::Javascript::Bun::Requirement)
49
+ Dependabot::Utils.register_version_class("bun", Dependabot::Javascript::Bun::Version)
@@ -0,0 +1,77 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class FileFetcher < Shared::FileFetcher
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
12
+ def self.required_files_in?(filenames)
13
+ filenames.include?("package.json")
14
+ end
15
+
16
+ sig { override.returns(String) }
17
+ def self.required_files_message
18
+ "Repo must contain a package.json."
19
+ end
20
+
21
+ sig { override.returns(T.nilable(T::Hash[Symbol, T.untyped])) }
22
+ def ecosystem_versions
23
+ return unknown_ecosystem_versions unless ecosystem_enabled?
24
+
25
+ {
26
+ package_managers: {
27
+ "bun" => 1
28
+ }
29
+ }
30
+ end
31
+
32
+ sig { override.returns(T::Array[DependencyFile]) }
33
+ def fetch_files
34
+ fetched_files = T.let([], T::Array[DependencyFile])
35
+ fetched_files << package_json(self)
36
+ fetched_files += bun_files if ecosystem_enabled?
37
+ fetched_files += workspace_package_jsons(self)
38
+ fetched_files += path_dependencies(self, fetched_files)
39
+
40
+ fetched_files.uniq
41
+ end
42
+
43
+ private
44
+
45
+ sig { returns(T::Array[DependencyFile]) }
46
+ def bun_files
47
+ [bun_lock].compact
48
+ end
49
+
50
+ sig { returns(T.nilable(DependencyFile)) }
51
+ def bun_lock
52
+ return @bun_lock if defined?(@bun_lock)
53
+
54
+ @bun_lock ||= T.let(fetch_file_if_present(PackageManager::LOCKFILE_NAME), T.nilable(DependencyFile))
55
+
56
+ return @bun_lock if @bun_lock || directory == "/"
57
+
58
+ @bun_lock = fetch_file_from_parent_directories(self, PackageManager::LOCKFILE_NAME)
59
+ end
60
+
61
+ sig { returns(T::Boolean) }
62
+ def ecosystem_enabled?
63
+ allow_beta_ecosystems? && Experiments.enabled?(:enable_bun_ecosystem)
64
+ end
65
+
66
+ sig { returns(T::Hash[Symbol, String]) }
67
+ def unknown_ecosystem_versions
68
+ {
69
+ package_managers: {
70
+ "unknown" => 0
71
+ }
72
+ }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,156 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class FileParser
8
+ class BunLock < Shared::FileParser::Lockfile
9
+ extend T::Sig
10
+
11
+ sig { params(dependency_file: DependencyFile).void }
12
+ def initialize(dependency_file)
13
+ super
14
+ @parsed = T.let(
15
+ nil,
16
+ T.nilable(T::Hash[String, T.any(Integer, String, T::Array[String], T::Hash[String, String])])
17
+ )
18
+ end
19
+
20
+ sig { override.returns(T::Hash[String, T.untyped]) }
21
+ def parsed
22
+ @parsed ||= begin
23
+ content = begin
24
+ # Since bun.lock is a JSONC file, which is a subset of YAML, we can use YAML to parse it
25
+ YAML.load(T.must(@dependency_file.content))
26
+ rescue Psych::SyntaxError => e
27
+ raise_invalid!("malformed JSONC at line #{e.line}, column #{e.column}")
28
+ end
29
+ raise_invalid!("expected to be an object") unless content.is_a?(Hash)
30
+
31
+ version = content["lockfileVersion"]
32
+ raise_invalid!("expected 'lockfileVersion' to be an integer") unless version.is_a?(Integer)
33
+ raise_invalid!("expected 'lockfileVersion' to be >= 0") unless version >= 0
34
+
35
+ content
36
+ end
37
+ end
38
+
39
+ sig { override.returns(Dependabot::FileParsers::Base::DependencySet) }
40
+ def dependencies
41
+ dependency_set = Dependabot::FileParsers::Base::DependencySet.new
42
+
43
+ # bun.lock v0 format:
44
+ # https://github.com/oven-sh/bun/blob/c130df6c589fdf28f9f3c7f23ed9901140bc9349/src/install/bun.lock.zig#L595-L605
45
+
46
+ packages = parsed["packages"]
47
+ raise_invalid!("expected 'packages' to be an object") unless packages.is_a?(Hash)
48
+
49
+ packages.each do |key, details|
50
+ raise_invalid!("expected 'packages.#{key}' to be an array") unless details.is_a?(Array)
51
+
52
+ resolution = details.first
53
+ raise_invalid!("expected 'packages.#{key}[0]' to be a string") unless resolution.is_a?(String)
54
+
55
+ name, version = resolution.split(/(?<=\w)\@/)
56
+ next if name.empty?
57
+
58
+ semver = Version.semver_for(version)
59
+ next unless semver
60
+
61
+ dependency_set << Dependency.new(
62
+ name: name,
63
+ version: semver.to_s,
64
+ package_manager: "bun",
65
+ requirements: []
66
+ )
67
+ end
68
+
69
+ dependency_set
70
+ end
71
+
72
+ sig do
73
+ override
74
+ .params(
75
+ dependency_name: String,
76
+ requirement: T.untyped,
77
+ manifest_name: String
78
+ )
79
+ .returns(T.nilable(T::Hash[String, T.untyped]))
80
+ end
81
+ def details(dependency_name, requirement, manifest_name) # rubocop:disable Lint/UnusedMethodArgument
82
+ packages = parsed["packages"]
83
+ return unless packages.is_a?(Hash)
84
+
85
+ candidates =
86
+ packages
87
+ .select { |name, _| name == dependency_name }
88
+ .values
89
+
90
+ # If there's only one entry for this dependency, use it, even if
91
+ # the requirement in the lockfile doesn't match
92
+ if candidates.one?
93
+ parse_details(candidates.first)
94
+ else
95
+ candidate = candidates.find do |label, _|
96
+ label.scan(/(?<=\w)\@(?:npm:)?([^\s,]+)/).flatten.include?(requirement)
97
+ end&.last
98
+ parse_details(candidate)
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ sig { params(message: String).void }
105
+ def raise_invalid!(message)
106
+ raise Dependabot::DependencyFileNotParseable.new(@dependency_file.path, "Invalid bun.lock file: #{message}")
107
+ end
108
+
109
+ sig do
110
+ params(entry: T.nilable(T::Array[T.untyped])).returns(T.nilable(T::Hash[String, T.untyped]))
111
+ end
112
+ def parse_details(entry)
113
+ return unless entry.is_a?(Array)
114
+
115
+ # Either:
116
+ # - "{name}@{version}", registry, details, integrity
117
+ # - "{name}@{resolution}", details
118
+ resolution = entry.first
119
+ return unless resolution.is_a?(String)
120
+
121
+ name, version = resolution.split(/(?<=\w)\@/)
122
+ semver = Version.semver_for(version)
123
+
124
+ if semver
125
+ registry, details, integrity = entry[1..3]
126
+ {
127
+ "name" => name,
128
+ "version" => semver.to_s,
129
+ "registry" => registry,
130
+ "details" => details,
131
+ "integrity" => integrity
132
+ }
133
+ else
134
+ details = entry[1]
135
+ {
136
+ "name" => name,
137
+ "resolution" => version,
138
+ "details" => details
139
+ }
140
+ end
141
+ end
142
+ end
143
+
144
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
145
+ def parse
146
+ []
147
+ end
148
+
149
+ private
150
+
151
+ sig { override.void }
152
+ def check_required_files; end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,55 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class FileParser
8
+ class LockfileParser < Shared::FileParser::LockfileParser
9
+ extend T::Sig
10
+
11
+ DEFAULT_LOCKFILES = %w(bun.lock).freeze
12
+
13
+ sig { override.returns(Dependabot::FileParsers::Base::DependencySet) }
14
+ def parse_set
15
+ dependency_set = Dependabot::FileParsers::Base::DependencySet.new
16
+
17
+ bun_locks.each do |file|
18
+ dependency_set += lockfile_for(file).dependencies
19
+ end
20
+
21
+ dependency_set
22
+ end
23
+
24
+ sig { override.returns(T::Array[String]) }
25
+ def default_lockfiles
26
+ DEFAULT_LOCKFILES
27
+ end
28
+
29
+ private
30
+
31
+ sig { override.params(file: DependencyFile).returns(BunLock) }
32
+ def lockfile_for(file)
33
+ @lockfiles ||= T.let({}, T.nilable(T::Hash[String, BunLock]))
34
+ @lockfiles[file.name] ||= case file.name
35
+ when *bun_locks.map(&:name)
36
+ Bun::FileParser::BunLock.new(file)
37
+ else
38
+ raise "Unexpected lockfile: #{file.name}"
39
+ end
40
+ end
41
+
42
+ sig { returns(T::Array[DependencyFile]) }
43
+ def bun_locks
44
+ @bun_locks ||= T.let(select_files_by_extension("bun.lock"), T.nilable(T::Array[DependencyFile]))
45
+ end
46
+
47
+ sig { override.returns(T.class_of(Version)) }
48
+ def version_class
49
+ Version
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,74 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ # See https://docs.npmjs.com/files/package.json for package.json format docs.
5
+
6
+ module Dependabot
7
+ module Javascript
8
+ module Bun
9
+ class FileParser < Shared::FileParser
10
+ extend T::Sig
11
+
12
+ sig { override.returns(Ecosystem) }
13
+ def ecosystem
14
+ @ecosystem ||= T.let(
15
+ Ecosystem.new(
16
+ name: ECOSYSTEM,
17
+ package_manager: PackageManager.new(detected_version:),
18
+ language: Shared::Language.new(detected_version:)
19
+ ),
20
+ T.nilable(Ecosystem)
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ sig { returns(T.nilable(String)) }
27
+ def detected_version
28
+ Helpers.local_package_manager_version(Bun::PackageManager::NAME)
29
+ end
30
+
31
+ sig { override.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) }
32
+ def lockfiles
33
+ {
34
+ bun: bun_lock
35
+ }
36
+ end
37
+
38
+ sig { override.returns(LockfileParser) }
39
+ def lockfile_parser
40
+ @lockfile_parser ||= T.let(LockfileParser.new(
41
+ dependency_files: dependency_files
42
+ ), T.nilable(LockfileParser))
43
+ end
44
+
45
+ sig { override.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) }
46
+ def registry_config_files
47
+ {
48
+ npmrc: npmrc
49
+ }
50
+ end
51
+
52
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
53
+ def bun_lock
54
+ @bun_lock ||= T.let(dependency_files.find do |f|
55
+ f.name.end_with?(PackageManager::LOCKFILE_NAME)
56
+ end, T.nilable(Dependabot::DependencyFile))
57
+ end
58
+
59
+ sig { override.returns(T.class_of(Version)) }
60
+ def version_class
61
+ Version
62
+ end
63
+
64
+ sig { override.returns(T.class_of(Requirement)) }
65
+ def requirement_class
66
+ Requirement
67
+ end
68
+
69
+ sig { override.void }
70
+ def check_required_files; end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,138 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class FileUpdater
8
+ class LockfileUpdater
9
+ def initialize(dependencies:, dependency_files:, repo_contents_path:, credentials:)
10
+ @dependencies = dependencies
11
+ @dependency_files = dependency_files
12
+ @repo_contents_path = repo_contents_path
13
+ @credentials = credentials
14
+ end
15
+
16
+ def updated_bun_lock_content(bun_lock)
17
+ @updated_bun_lock_content ||= {}
18
+ return @updated_bun_lock_content[bun_lock.name] if @updated_bun_lock_content[bun_lock.name]
19
+
20
+ new_content = run_bun_update(bun_lock: bun_lock)
21
+ @updated_bun_lock_content[bun_lock.name] = new_content
22
+ rescue SharedHelpers::HelperSubprocessFailed => e
23
+ handle_bun_lock_updater_error(e, bun_lock)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :dependencies
29
+ attr_reader :dependency_files
30
+ attr_reader :repo_contents_path
31
+ attr_reader :credentials
32
+
33
+ ERR_PATTERNS = {
34
+ /get .* 404/i => Dependabot::DependencyNotFound,
35
+ /installfailed cloning repository/i => Dependabot::DependencyNotFound,
36
+ /file:.* failed to resolve/i => Dependabot::DependencyNotFound,
37
+ /no version matching/i => Dependabot::DependencyFileNotResolvable,
38
+ /failed to resolve/i => Dependabot::DependencyFileNotResolvable
39
+ }.freeze
40
+
41
+ def run_bun_update(bun_lock:)
42
+ SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
43
+ File.write(".npmrc", npmrc_content(bun_lock))
44
+
45
+ SharedHelpers.with_git_configured(credentials: credentials) do
46
+ run_bun_updater
47
+
48
+ write_final_package_json_files
49
+
50
+ run_bun_install
51
+
52
+ File.read(bun_lock.name)
53
+ end
54
+ end
55
+ end
56
+
57
+ def run_bun_updater
58
+ dependency_updates = dependencies.map do |d|
59
+ "#{d.name}@#{d.version}"
60
+ end.join(" ")
61
+
62
+ Helpers.run_bun_command(
63
+ "install #{dependency_updates} --save-text-lockfile",
64
+ fingerprint: "install <dependency_updates> --save-text-lockfile"
65
+ )
66
+ end
67
+
68
+ def run_bun_install
69
+ Helpers.run_bun_command(
70
+ "install --save-text-lockfile"
71
+ )
72
+ end
73
+
74
+ def lockfile_dependencies(lockfile)
75
+ @lockfile_dependencies ||= {}
76
+ @lockfile_dependencies[lockfile.name] ||=
77
+ FileParser.new(
78
+ dependency_files: [lockfile, *package_files],
79
+ source: nil,
80
+ credentials: credentials
81
+ ).parse
82
+ end
83
+
84
+ def handle_bun_lock_updater_error(error, _bun_lock)
85
+ error_message = error.message
86
+
87
+ ERR_PATTERNS.each do |pattern, error_class|
88
+ raise error_class, error_message if error_message.match?(pattern)
89
+ end
90
+
91
+ raise error
92
+ end
93
+
94
+ def write_final_package_json_files
95
+ package_files.each do |file|
96
+ path = file.name
97
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
98
+ File.write(path, updated_package_json_content(file))
99
+ end
100
+ end
101
+
102
+ def npmrc_content(bun_lock)
103
+ Dependabot::Javascript::Shared::FileUpdater::NpmrcBuilder.new(
104
+ credentials: credentials,
105
+ dependency_files: dependency_files,
106
+ dependencies: lockfile_dependencies(bun_lock)
107
+ ).npmrc_content
108
+ end
109
+
110
+ def updated_package_json_content(file)
111
+ @updated_package_json_content ||= {}
112
+ @updated_package_json_content[file.name] ||=
113
+ Dependabot::Javascript::Shared::FileUpdater::PackageJsonUpdater.new(
114
+ package_json: file,
115
+ dependencies: dependencies
116
+ ).updated_package_json.content
117
+ end
118
+
119
+ def package_files
120
+ @package_files ||= dependency_files.select { |f| f.name.end_with?("package.json") }
121
+ end
122
+
123
+ def base_dir
124
+ dependency_files.first.directory
125
+ end
126
+
127
+ def npmrc_file
128
+ dependency_files.find { |f| f.name == ".npmrc" }
129
+ end
130
+
131
+ def sanitize_message(message)
132
+ message.gsub(/"|\[|\]|\}|\{/, "")
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,75 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class FileUpdater < Shared::FileUpdater
8
+ sig { override.returns(T::Array[Regexp]) }
9
+ def self.updated_files_regex
10
+ [
11
+ %r{^(?:.*/)?package\.json$},
12
+ %r{^(?:.*/)?bun\.lock$} # Matches bun.lock files
13
+ ]
14
+ end
15
+
16
+ private
17
+
18
+ sig { override.returns(T.class_of(FileParser::LockfileParser)) }
19
+ def lockfile_parser_class
20
+ FileParser::LockfileParser
21
+ end
22
+
23
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
24
+ def bun_locks
25
+ @bun_locks ||= T.let(
26
+ filtered_dependency_files
27
+ .select { |f| f.name.end_with?("bun.lock") },
28
+ T.nilable(T::Array[Dependabot::DependencyFile])
29
+ )
30
+ end
31
+
32
+ sig { params(bun_lock: Dependabot::DependencyFile).returns(T::Boolean) }
33
+ def bun_lock_changed?(bun_lock)
34
+ bun_lock.content != updated_bun_lock_content(bun_lock)
35
+ end
36
+
37
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
38
+ def updated_lockfiles
39
+ updated_files = []
40
+
41
+ bun_locks.each do |bun_lock|
42
+ next unless bun_lock_changed?(bun_lock)
43
+
44
+ updated_files << updated_file(
45
+ file: bun_lock,
46
+ content: updated_bun_lock_content(bun_lock)
47
+ )
48
+ end
49
+
50
+ updated_files
51
+ end
52
+
53
+ sig { params(bun_lock: Dependabot::DependencyFile).returns(String) }
54
+ def updated_bun_lock_content(bun_lock)
55
+ @updated_bun_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
56
+ @updated_bun_lock_content[bun_lock.name] ||=
57
+ bun_lockfile_updater.updated_bun_lock_content(bun_lock)
58
+ end
59
+
60
+ sig { returns(Bun::FileUpdater::LockfileUpdater) }
61
+ def bun_lockfile_updater
62
+ @bun_lockfile_updater ||= T.let(
63
+ LockfileUpdater.new(
64
+ dependencies: dependencies,
65
+ dependency_files: dependency_files,
66
+ repo_contents_path: repo_contents_path,
67
+ credentials: credentials
68
+ ),
69
+ T.nilable(Bun::FileUpdater::LockfileUpdater)
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end