-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fc8025f
commit 3266104
Showing
31 changed files
with
2,593 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# typed: strong | ||
# frozen_string_literal: true | ||
|
||
require "zeitwerk" | ||
|
||
loader = Zeitwerk::Loader.new | ||
|
||
# Set autoload paths for common/lib, excluding files whose content does not match the filename | ||
loader.push_dir(File.join(__dir__, "../../../common/lib")) | ||
loader.ignore(File.join(__dir__, "../../../common/lib/dependabot/errors.rb")) | ||
loader.ignore(File.join(__dir__, "../../../common/lib/dependabot/logger.rb")) | ||
loader.ignore(File.join(__dir__, "../../../common/lib/dependabot/notices.rb")) | ||
loader.ignore(File.join(__dir__, "../../../common/lib/dependabot/clients/codecommit.rb")) | ||
|
||
loader.push_dir(File.join(__dir__, "..")) | ||
loader.ignore("#{__dir__}/../script", "#{__dir__}/../spec", "#{__dir__}/../dependabot-bun.gemspec") | ||
|
||
loader.on_load do |_file| | ||
require "json" | ||
require "sorbet-runtime" | ||
require "dependabot/errors" | ||
require "dependabot/logger" | ||
require "dependabot/notices" | ||
require "dependabot/clients/codecommit" | ||
end | ||
|
||
loader.log! if ENV["DEBUG"] | ||
loader.setup | ||
|
||
Dependabot::PullRequestCreator::Labeler | ||
.register_label_details("bun", name: "javascript", colour: "168700") | ||
|
||
Dependabot::Dependency.register_production_check("bun", ->(_) { true }) | ||
|
||
module Dependabot | ||
module Bun | ||
ECOSYSTEM = "bun" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# typed: strong | ||
# frozen_string_literal: true | ||
|
||
module Dependabot | ||
module Bun | ||
class FileFetcher < Dependabot::FileFetchers::Base | ||
include Javascript::FileFetcherHelper | ||
extend T::Sig | ||
extend T::Helpers | ||
|
||
sig { override.params(filenames: T::Array[String]).returns(T::Boolean) } | ||
def self.required_files_in?(filenames) | ||
filenames.include?("package.json") | ||
end | ||
|
||
sig { override.returns(String) } | ||
def self.required_files_message | ||
"Repo must contain a package.json." | ||
end | ||
|
||
sig { override.returns(T.nilable(T::Hash[Symbol, T.untyped])) } | ||
def ecosystem_versions | ||
return unknown_ecosystem_versions unless ecosystem_enabled? | ||
|
||
{ | ||
package_managers: { | ||
"bun" => 1 | ||
} | ||
} | ||
end | ||
|
||
sig { override.returns(T::Array[DependencyFile]) } | ||
def fetch_files | ||
fetched_files = T.let([], T::Array[DependencyFile]) | ||
fetched_files << package_json(self) | ||
fetched_files += bun_files if ecosystem_enabled? | ||
fetched_files += workspace_package_jsons(self) | ||
fetched_files += path_dependencies(self, fetched_files) | ||
|
||
fetched_files.uniq | ||
end | ||
|
||
sig { params(filename: String, fetch_submodules: T::Boolean).returns(DependencyFile) } | ||
def fetch_file(filename, fetch_submodules: false) | ||
fetch_file_from_host(filename, fetch_submodules: fetch_submodules) | ||
end | ||
|
||
sig do | ||
params( | ||
dir: T.any(Pathname, String), | ||
ignore_base_directory: T::Boolean, | ||
raise_errors: T::Boolean, | ||
fetch_submodules: T::Boolean | ||
) | ||
.returns(T::Array[T.untyped]) | ||
end | ||
def fetch_repo_contents(dir: ".", ignore_base_directory: false, raise_errors: true, fetch_submodules: false) | ||
repo_contents(dir: dir, ignore_base_directory:, raise_errors:, fetch_submodules:) | ||
end | ||
|
||
private | ||
|
||
sig { returns(T::Array[DependencyFile]) } | ||
def bun_files | ||
[bun_lock].compact | ||
end | ||
|
||
sig { returns(T.nilable(DependencyFile)) } | ||
def bun_lock | ||
return @bun_lock if defined?(@bun_lock) | ||
|
||
@bun_lock ||= T.let(fetch_file_if_present(PackageManager::LOCKFILE_NAME), T.nilable(DependencyFile)) | ||
|
||
return @bun_lock if @bun_lock || directory == "/" | ||
|
||
@bun_lock = fetch_file_from_parent_directories(self, PackageManager::LOCKFILE_NAME) | ||
end | ||
|
||
sig { returns(T::Boolean) } | ||
def ecosystem_enabled? | ||
allow_beta_ecosystems? && Experiments.enabled?(:enable_bun_ecosystem) | ||
end | ||
|
||
sig { returns(T::Hash[Symbol, String]) } | ||
def unknown_ecosystem_versions | ||
{ | ||
package_managers: { | ||
"unknown" => 0 | ||
} | ||
} | ||
end | ||
end | ||
end | ||
end | ||
|
||
Dependabot::FileFetchers | ||
.register(Dependabot::Bun::ECOSYSTEM, Dependabot::Bun::FileFetcher) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
# typed: strict | ||
# frozen_string_literal: true | ||
|
||
require "yaml" | ||
require "sorbet-runtime" | ||
|
||
module Dependabot | ||
module Bun | ||
class FileParser < Dependabot::FileParsers::Base | ||
class BunLock | ||
extend T::Sig | ||
|
||
sig { params(dependency_file: DependencyFile).void } | ||
def initialize(dependency_file) | ||
@dependency_file = dependency_file | ||
end | ||
|
||
sig { returns(T::Hash[String, T.untyped]) } | ||
def parsed | ||
@parsed ||= begin | ||
content = begin | ||
# Since bun.lock is a JSONC file, which is a subset of YAML, we can use YAML to parse it | ||
YAML.load(T.must(@dependency_file.content)) | ||
rescue Psych::SyntaxError => e | ||
raise_invalid!("malformed JSONC at line #{e.line}, column #{e.column}") | ||
end | ||
raise_invalid!("expected to be an object") unless content.is_a?(Hash) | ||
|
||
version = content["lockfileVersion"] | ||
raise_invalid!("expected 'lockfileVersion' to be an integer") unless version.is_a?(Integer) | ||
raise_invalid!("expected 'lockfileVersion' to be >= 0") unless version >= 0 | ||
|
||
T.let(content, T.untyped) | ||
end | ||
end | ||
|
||
sig { returns(Dependabot::FileParsers::Base::DependencySet) } | ||
def dependencies | ||
dependency_set = Dependabot::FileParsers::Base::DependencySet.new | ||
|
||
# bun.lock v0 format: | ||
# https://github.com/oven-sh/bun/blob/c130df6c589fdf28f9f3c7f23ed9901140bc9349/src/install/bun.lock.zig#L595-L605 | ||
|
||
packages = parsed["packages"] | ||
raise_invalid!("expected 'packages' to be an object") unless packages.is_a?(Hash) | ||
|
||
packages.each do |key, details| | ||
raise_invalid!("expected 'packages.#{key}' to be an array") unless details.is_a?(Array) | ||
|
||
resolution = details.first | ||
raise_invalid!("expected 'packages.#{key}[0]' to be a string") unless resolution.is_a?(String) | ||
|
||
name, version = resolution.split(/(?<=\w)\@/) | ||
next if name.empty? | ||
|
||
semver = Version.semver_for(version) | ||
next unless semver | ||
|
||
dependency_set << Dependency.new( | ||
name: name, | ||
version: semver.to_s, | ||
package_manager: "npm_and_yarn", | ||
requirements: [] | ||
) | ||
end | ||
|
||
dependency_set | ||
end | ||
|
||
sig do | ||
params(dependency_name: String, requirement: T.untyped, _manifest_name: String) | ||
.returns(T.nilable(T::Hash[String, T.untyped])) | ||
end | ||
def details(dependency_name, requirement, _manifest_name) | ||
packages = parsed["packages"] | ||
return unless packages.is_a?(Hash) | ||
|
||
candidates = | ||
packages | ||
.select { |name, _| name == dependency_name } | ||
.values | ||
|
||
# If there's only one entry for this dependency, use it, even if | ||
# the requirement in the lockfile doesn't match | ||
if candidates.one? | ||
parse_details(candidates.first) | ||
else | ||
candidate = candidates.find do |label, _| | ||
label.scan(/(?<=\w)\@(?:npm:)?([^\s,]+)/).flatten.include?(requirement) | ||
end&.last | ||
parse_details(candidate) | ||
end | ||
end | ||
|
||
private | ||
|
||
sig { params(message: String).void } | ||
def raise_invalid!(message) | ||
raise Dependabot::DependencyFileNotParseable.new(@dependency_file.path, "Invalid bun.lock file: #{message}") | ||
end | ||
|
||
sig do | ||
params(entry: T.nilable(T::Array[T.untyped])).returns(T.nilable(T::Hash[String, T.untyped])) | ||
end | ||
def parse_details(entry) | ||
return unless entry.is_a?(Array) | ||
|
||
# Either: | ||
# - "{name}@{version}", registry, details, integrity | ||
# - "{name}@{resolution}", details | ||
resolution = entry.first | ||
return unless resolution.is_a?(String) | ||
|
||
name, version = resolution.split(/(?<=\w)\@/) | ||
semver = Version.semver_for(version) | ||
|
||
if semver | ||
registry, details, integrity = entry[1..3] | ||
{ | ||
"name" => name, | ||
"version" => semver.to_s, | ||
"registry" => registry, | ||
"details" => details, | ||
"integrity" => integrity | ||
} | ||
else | ||
details = entry[1] | ||
{ | ||
"name" => name, | ||
"resolution" => version, | ||
"details" => details | ||
} | ||
end | ||
end | ||
end | ||
|
||
sig { override.returns(T::Array[Dependabot::Dependency]) } | ||
def parse | ||
[] | ||
end | ||
|
||
private | ||
|
||
sig { override.void } | ||
def check_required_files; end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# typed: strong | ||
# frozen_string_literal: true | ||
|
||
module Dependabot | ||
module Bun | ||
module Helpers | ||
extend T::Sig | ||
|
||
# BUN Version Constants | ||
BUN_V1 = 1 | ||
BUN_DEFAULT_VERSION = BUN_V1 | ||
|
||
sig { params(_bun_lock: T.nilable(DependencyFile)).returns(Integer) } | ||
def self.bun_version_numeric(_bun_lock) | ||
BUN_DEFAULT_VERSION | ||
end | ||
|
||
sig { returns(T.nilable(String)) } | ||
def self.bun_version | ||
run_bun_command("--version", fingerprint: "--version").strip | ||
rescue StandardError => e | ||
Dependabot.logger.error("Error retrieving Bun version: #{e.message}") | ||
nil | ||
end | ||
|
||
sig { params(command: String, fingerprint: T.nilable(String)).returns(String) } | ||
def self.run_bun_command(command, fingerprint: nil) | ||
full_command = "bun #{command}" | ||
|
||
Dependabot.logger.info("Running bun command: #{full_command}") | ||
|
||
result = Dependabot::SharedHelpers.run_shell_command( | ||
full_command, | ||
fingerprint: "bun #{fingerprint || command}" | ||
) | ||
|
||
Dependabot.logger.info("Command executed successfully: #{full_command}") | ||
result | ||
rescue StandardError => e | ||
Dependabot.logger.error("Error running bun command: #{full_command}, Error: #{e.message}") | ||
raise | ||
end | ||
|
||
# Fetch the currently installed version of the package manager directly | ||
# from the system | ||
sig { params(name: String).returns(String) } | ||
def self.local_package_manager_version(name) | ||
Dependabot::SharedHelpers.run_shell_command( | ||
"#{name} -v", | ||
fingerprint: "#{name} -v" | ||
).strip | ||
end | ||
|
||
# Run single command on package manager returning stdout/stderr | ||
sig do | ||
params( | ||
name: String, | ||
command: String, | ||
fingerprint: T.nilable(String) | ||
).returns(String) | ||
end | ||
def self.package_manager_run_command(name, command, fingerprint: nil) | ||
return run_bun_command(command, fingerprint: fingerprint) if name == PackageManager::NAME | ||
|
||
# TODO: remove this method and just use the one in the PackageManager class | ||
"noop" | ||
end | ||
|
||
sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).returns(T::Array[Dependency]) } | ||
def self.dependencies_with_all_versions_metadata(dependency_set) | ||
# TODO: Check if we still need this method | ||
dependency_set.dependencies.map do |dependency| | ||
dependency.metadata[:all_versions] = dependency_set.all_versions_for_name(dependency.name) | ||
dependency | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.