Skip to content

Commit

Permalink
Add bun ecosystem FileParser
Browse files Browse the repository at this point in the history
  • Loading branch information
markhallen authored and robaiken committed Feb 7, 2025
1 parent cf954e9 commit 9b86e10
Show file tree
Hide file tree
Showing 49 changed files with 40,930 additions and 7 deletions.
4 changes: 4 additions & 0 deletions javascript/lib/dependabot/bun.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ module Bun
ECOSYSTEM = "bun"
end
end

Dependabot::FileFetchers.register("bun", Dependabot::Bun::FileFetcher)
Dependabot::FileParsers.register("bun", Dependabot::Bun::FileParser)
Dependabot::FileUpdaters.register("bun", Dependabot::Bun::FileUpdater)
5 changes: 1 addition & 4 deletions javascript/lib/dependabot/bun/file_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
module Dependabot
module Bun
class FileFetcher < Dependabot::FileFetchers::Base
include Javascript::FileFetcherHelper
include ::Dependabot::Javascript::FileFetcherHelper
extend T::Sig
extend T::Helpers

Expand Down Expand Up @@ -92,6 +92,3 @@ def unknown_ecosystem_versions
end
end
end

Dependabot::FileFetchers
.register(Dependabot::Bun::ECOSYSTEM, Dependabot::Bun::FileFetcher)
3 changes: 0 additions & 3 deletions javascript/lib/dependabot/bun/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,3 @@ def check_required_files; end
end
end
end

Dependabot::FileParsers
.register(Dependabot::Bun::ECOSYSTEM, Dependabot::Bun::FileParser)
68 changes: 68 additions & 0 deletions javascript/lib/dependabot/bun/file_updater.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# typed: strict
# frozen_string_literal: true

module Dependabot
module Bun
class FileUpdater < Javascript::FileUpdater
sig { override.returns(T::Array[Regexp]) }
def self.updated_files_regex
[
%r{^(?:.*/)?package\.json$},
%r{^(?:.*/)?bun\.lock$} # Matches bun.lock files
]
end

private

sig { returns(T::Array[Dependabot::DependencyFile]) }
def bun_locks
@bun_locks ||= T.let(
filtered_dependency_files
.select { |f| f.name.end_with?("bun.lock") },
T.nilable(T::Array[Dependabot::DependencyFile])
)
end

sig { params(bun_lock: Dependabot::DependencyFile).returns(T::Boolean) }
def bun_lock_changed?(bun_lock)
bun_lock.content != updated_bun_lock_content(bun_lock)
end

sig { override.returns(T::Array[Dependabot::DependencyFile]) }
def updated_lockfiles
updated_files = []

bun_locks.each do |bun_lock|
next unless bun_lock_changed?(bun_lock)

updated_files << updated_file(
file: bun_lock,
content: updated_bun_lock_content(bun_lock)
)
end

updated_files
end

sig { params(bun_lock: Dependabot::DependencyFile).returns(String) }
def updated_bun_lock_content(bun_lock)
@updated_bun_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
@updated_bun_lock_content[bun_lock.name] ||=
bun_lockfile_updater.updated_bun_lock_content(bun_lock)
end

sig { returns(Dependabot::Bun::FileUpdater::LockfileUpdater) }
def bun_lockfile_updater
@bun_lockfile_updater ||= T.let(
LockfileUpdater.new(
dependencies: dependencies,
dependency_files: dependency_files,
repo_contents_path: repo_contents_path,
credentials: credentials
),
T.nilable(Dependabot::Bun::FileUpdater::LockfileUpdater)
)
end
end
end
end
136 changes: 136 additions & 0 deletions javascript/lib/dependabot/bun/file_updater/lockfile_updater.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# typed: true
# frozen_string_literal: true

module Dependabot
module Bun
class FileUpdater
class LockfileUpdater
def initialize(dependencies:, dependency_files:, repo_contents_path:, credentials:)
@dependencies = dependencies
@dependency_files = dependency_files
@repo_contents_path = repo_contents_path
@credentials = credentials
end

def updated_bun_lock_content(bun_lock)
@updated_bun_lock_content ||= {}
return @updated_bun_lock_content[bun_lock.name] if @updated_bun_lock_content[bun_lock.name]

new_content = run_bun_update(bun_lock: bun_lock)
@updated_bun_lock_content[bun_lock.name] = new_content
rescue SharedHelpers::HelperSubprocessFailed => e
handle_bun_lock_updater_error(e, bun_lock)
end

private

attr_reader :dependencies
attr_reader :dependency_files
attr_reader :repo_contents_path
attr_reader :credentials

ERR_PATTERNS = {
/get .* 404/i => Dependabot::DependencyNotFound,
/installfailed cloning repository/i => Dependabot::DependencyNotFound,
/file:.* failed to resolve/i => Dependabot::DependencyNotFound,
/no version matching/i => Dependabot::DependencyFileNotResolvable,
/failed to resolve/i => Dependabot::DependencyFileNotResolvable
}.freeze

def run_bun_update(bun_lock:)
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
File.write(".npmrc", npmrc_content(bun_lock))

SharedHelpers.with_git_configured(credentials: credentials) do
run_bun_updater

write_final_package_json_files

run_bun_install

File.read(bun_lock.name)
end
end
end

def run_bun_updater
dependency_updates = dependencies.map do |d|
"#{d.name}@#{d.version}"
end.join(" ")

Helpers.run_bun_command(
"install #{dependency_updates} --save-text-lockfile",
fingerprint: "install <dependency_updates> --save-text-lockfile"
)
end

def run_bun_install
Helpers.run_bun_command(
"install --save-text-lockfile"
)
end

def lockfile_dependencies(lockfile)
@lockfile_dependencies ||= {}
@lockfile_dependencies[lockfile.name] ||=
FileParser.new(
dependency_files: [lockfile, *package_files],
source: nil,
credentials: credentials
).parse
end

def handle_bun_lock_updater_error(error, _bun_lock)
error_message = error.message

ERR_PATTERNS.each do |pattern, error_class|
raise error_class, error_message if error_message.match?(pattern)
end

raise error
end

def write_final_package_json_files
package_files.each do |file|
path = file.name
FileUtils.mkdir_p(Pathname.new(path).dirname)
File.write(path, updated_package_json_content(file))
end
end

def npmrc_content(bun_lock)
Javascript::FileUpdater::NpmrcBuilder.new(
credentials: credentials,
dependency_files: dependency_files,
dependencies: lockfile_dependencies(bun_lock)
).npmrc_content
end

def updated_package_json_content(file)
@updated_package_json_content ||= {}
@updated_package_json_content[file.name] ||=
Javascript::FileUpdater::PackageJsonUpdater.new(
package_json: file,
dependencies: dependencies
).updated_package_json.content
end

def package_files
@package_files ||= dependency_files.select { |f| f.name.end_with?("package.json") }
end

def base_dir
dependency_files.first.directory
end

def npmrc_file
dependency_files.find { |f| f.name == ".npmrc" }
end

def sanitize_message(message)
message.gsub(/"|\[|\]|\}|\{/, "")
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# typed: strong
# frozen_string_literal: true

module Dependabot
module Bun
class UpdateChecker
class DependencyFilesBuilder < Javascript::UpdateChecker::DependencyFilesBuilder
extend T::Sig

sig { returns(T::Array[DependencyFile]) }
def bun_locks
@bun_locks ||= T.let(
dependency_files
.select { |f| f.name.end_with?("bun.lock") },
T.nilable(T::Array[DependencyFile])
)
end

sig { returns(T.nilable(DependencyFile)) }
def root_bun_lock
@root_bun_lock ||= T.let(
dependency_files
.find { |f| f.name == "bun.lock" },
T.nilable(DependencyFile)
)
end

sig { override.returns(T::Array[DependencyFile]) }
def lockfiles
[*bun_locks]
end

private

sig { override.returns(T::Array[DependencyFile]) }
def write_lockfiles
[*bun_locks].each do |f|
FileUtils.mkdir_p(Pathname.new(f.name).dirname)
File.write(f.name, f.content)
end
end
end
end
end
end
2 changes: 2 additions & 0 deletions javascript/lib/dependabot/javascript.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# typed: strong
# frozen_string_literal: true

require "dependabot/bun"

module Dependabot
module Javascript
DEFAULT_PACKAGE_MANAGER = "npm"
Expand Down
Loading

0 comments on commit 9b86e10

Please # to comment.