Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Bun file FileFetcher #11287

Merged
merged 1 commit into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def package_required_lockfile?(lockfile)

sig { params(lockfile: DependencyFile).returns(T::Boolean) }
def workspaces_lockfile?(lockfile)
return false unless ["yarn.lock", "package-lock.json", "pnpm-lock.yaml"].include?(lockfile.name)
return false unless ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lock"].include?(lockfile.name)

return false unless parsed_root_package_json["workspaces"] || dependency_files.any? do |file|
file.name.end_with?("pnpm-workspace.yaml") && File.dirname(file.name) == File.dirname(lockfile.name)
Expand Down Expand Up @@ -148,6 +148,7 @@ def lockfile?(file)
"package-lock.json",
"yarn.lock",
"pnpm-lock.yaml",
"bun.lock",
"npm-shrinkwrap.json"
)
end
Expand Down
83 changes: 50 additions & 33 deletions npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def ecosystem_versions
package_managers["npm"] = npm_version if npm_version
package_managers["yarn"] = yarn_version if yarn_version
package_managers["pnpm"] = pnpm_version if pnpm_version
package_managers["bun"] = bun_version if bun_version
package_managers["unknown"] = 1 if package_managers.empty?

{
Expand All @@ -83,6 +84,7 @@ def fetch_files
fetched_files += npm_files if npm_version
fetched_files += yarn_files if yarn_version
fetched_files += pnpm_files if pnpm_version
fetched_files += bun_files if bun_version
fetched_files += lerna_files
fetched_files += workspace_package_jsons
fetched_files += path_dependencies(fetched_files)
Expand Down Expand Up @@ -120,6 +122,13 @@ def pnpm_files
fetched_pnpm_files
end

sig { returns(T::Array[DependencyFile]) }
def bun_files
fetched_bun_files = []
fetched_bun_files << bun_lock if bun_lock
fetched_bun_files
end

sig { returns(T::Array[DependencyFile]) }
def lerna_files
fetched_lerna_files = []
Expand Down Expand Up @@ -202,6 +211,16 @@ def pnpm_version
)
end

sig { returns(T.nilable(T.any(Integer, String))) }
def bun_version
return @bun_version = nil unless Experiments.enabled?(:bun_updates)

@bun_version ||= T.let(
package_manager_helper.setup(BunPackageManager::NAME),
T.nilable(T.any(Integer, String))
)
end

sig { returns(PackageManagerHelper) }
def package_manager_helper
@package_manager_helper ||= T.let(
Expand All @@ -219,7 +238,8 @@ def lockfiles
{
npm: package_lock || shrinkwrap,
yarn: yarn_lock,
pnpm: pnpm_lock
pnpm: pnpm_lock,
bun: bun_lock
}
end

Expand Down Expand Up @@ -261,17 +281,18 @@ def pnpm_lock

return @pnpm_lock if @pnpm_lock || directory == "/"

# Loop through parent directories looking for a pnpm-lock
(1..directory.split("/").count).each do |i|
@pnpm_lock = fetch_file_from_host(("../" * i) + PNPMPackageManager::LOCKFILE_NAME)
.tap { |f| f.support_file = true }
break if @pnpm_lock
rescue Dependabot::DependencyFileNotFound
# Ignore errors (pnpm_lock.yaml may not be present)
nil
end
@pnpm_lock = fetch_file_from_parent_directories(PNPMPackageManager::LOCKFILE_NAME)
end

sig { returns(T.nilable(DependencyFile)) }
def bun_lock
return @bun_lock if defined?(@bun_lock)

@bun_lock ||= T.let(fetch_file_if_present(BunPackageManager::LOCKFILE_NAME), T.nilable(DependencyFile))

@pnpm_lock
return @bun_lock if @bun_lock || directory == "/"

@bun_lock = fetch_file_from_parent_directories(BunPackageManager::LOCKFILE_NAME)
end

sig { returns(T.nilable(DependencyFile)) }
Expand All @@ -294,17 +315,7 @@ def npmrc

return @npmrc if @npmrc || directory == "/"

# Loop through parent directories looking for an npmrc
(1..directory.split("/").count).each do |i|
@npmrc = fetch_file_from_host(("../" * i) + NpmPackageManager::RC_FILENAME)
.tap { |f| f.support_file = true }
break if @npmrc
rescue Dependabot::DependencyFileNotFound
# Ignore errors (.npmrc may not be present)
nil
end

@npmrc
@npmrc = fetch_file_from_parent_directories(NpmPackageManager::RC_FILENAME)
end

sig { returns(T.nilable(DependencyFile)) }
Expand All @@ -315,17 +326,7 @@ def yarnrc

return @yarnrc if @yarnrc || directory == "/"

# Loop through parent directories looking for an yarnrc
(1..directory.split("/").count).each do |i|
@yarnrc = fetch_file_from_host(("../" * i) + YarnPackageManager::RC_FILENAME)
.tap { |f| f.support_file = true }
break if @yarnrc
rescue Dependabot::DependencyFileNotFound
# Ignore errors (.yarnrc may not be present)
nil
end

@yarnrc
@yarnrc = fetch_file_from_parent_directories(YarnPackageManager::RC_FILENAME)
end

sig { returns(T.nilable(DependencyFile)) }
Expand Down Expand Up @@ -699,6 +700,22 @@ def create_yarn_cache
Dependabot.logger.info("Repository contents path does not exist")
end
end

sig { params(filename: String).returns(T.nilable(DependencyFile)) }
def fetch_file_with_support(filename)
fetch_file_from_host(filename).tap { |f| f.support_file = true }
rescue Dependabot::DependencyFileNotFound
nil
end

sig { params(filename: String).returns(T.nilable(DependencyFile)) }
def fetch_file_from_parent_directories(filename)
(1..directory.split("/").count).each do |i|
file = fetch_file_with_support(("../" * i) + filename)
return file if file
end
nil
end
end
end
end
Expand Down
9 changes: 9 additions & 0 deletions npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ module Helpers # rubocop:disable Metrics/ModuleLength
PNPM_DEFAULT_VERSION = PNPM_V9
PNPM_FALLBACK_VERSION = PNPM_V6

# BUN Version Constants
BUN_V1 = 1
BUN_DEFAULT_VERSION = BUN_V1

# YARN Version Constants
YARN_V3 = 3
YARN_V2 = 2
Expand Down Expand Up @@ -159,6 +163,11 @@ def self.pnpm_version_numeric(pnpm_lock)
PNPM_FALLBACK_VERSION
end

sig { params(_bun_lock: T.nilable(DependencyFile)).returns(Integer) }
def self.bun_version_numeric(_bun_lock)
BUN_DEFAULT_VERSION
end

sig { params(key: String, default_value: String).returns(T.untyped) }
def self.fetch_yarnrc_yml_value(key, default_value)
if File.exist?(".yarnrc.yml") && (yarnrc = YAML.load_file(".yarnrc.yml"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def lockfile?(file)
"package-lock.json",
"yarn.lock",
"npm-shrinkwrap.json",
"bun.lock",
"pnpm-lock.yaml"
)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,17 @@ def project_dependency_file(file_name)
end
end

context "when using bun.lock" do
let(:project_name) { "bun/simple_v0" }

it do
expect(files_requiring_update).to contain_exactly(
project_dependency_file("package.json"),
project_dependency_file("bun.lock")
)
end
end

context "with multiple dependencies" do
let(:project_name) { "npm6_and_yarn/nested_dependency_update" }
let(:updated_dependencies) { [dependency, other_dependency] }
Expand Down
77 changes: 77 additions & 0 deletions npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,20 @@
body: nil,
headers: json_header
)
stub_request(:get, File.join(url, "bun.lock?ref=sha"))
.with(headers: { "Authorization" => "token token" })
.to_return(
status: 404,
body: nil,
headers: json_header
)
stub_request(:get, File.join(url, "packages/bun.lock?ref=sha"))
.with(headers: { "Authorization" => "token token" })
.to_return(
status: 404,
body: nil,
headers: json_header
)
# FileFetcher will iterate trying to find `pnpm-lock.yaml` upwards in the folder tree
stub_request(:get, File.join(url, "packages/pnpm-lock.yaml?ref=sha"))
.with(headers: { "Authorization" => "token token" })
Expand Down Expand Up @@ -488,6 +502,57 @@
end
end

context "with a bun.lock but no package-lock.json file" do
before do
stub_request(:get, url + "?ref=sha")
.with(headers: { "Authorization" => "token token" })
.to_return(
status: 200,
body: fixture("github", "contents_js_bun.json"),
headers: json_header
)
stub_request(:get, File.join(url, "package-lock.json?ref=sha"))
.with(headers: { "Authorization" => "token token" })
.to_return(status: 404)
stub_request(:get, File.join(url, "bun.lock?ref=sha"))
.with(headers: { "Authorization" => "token token" })
.to_return(
status: 200,
body: fixture("github", "bun_lock_content.json"),
headers: json_header
)
end

describe "fetching and parsing the bun.lock" do
before do
allow(Dependabot::Experiments).to receive(:enabled?)
allow(Dependabot::Experiments).to receive(:enabled?).with(:bun_updates).and_return(enable_bun_updates)
end

context "when the experiment :bun_updates is inactive" do
let(:enable_bun_updates) { false }

it "does not fetch or parse the the bun.lock" do
expect(file_fetcher_instance.files.map(&:name))
.to match_array(%w(package.json))
expect(file_fetcher_instance.ecosystem_versions)
.to match({ package_managers: { "unknown" => an_instance_of(Integer) } })
end
end

context "when the experiment :bun_updates is active" do
let(:enable_bun_updates) { true }

it "fetches and parses the bun.lock" do
expect(file_fetcher_instance.files.map(&:name))
.to match_array(%w(package.json bun.lock))
expect(file_fetcher_instance.ecosystem_versions)
.to match({ package_managers: { "bun" => an_instance_of(Integer) } })
end
end
end
end

context "with an npm-shrinkwrap.json but no package-lock.json file" do
before do
stub_request(:get, url + "?ref=sha")
Expand Down Expand Up @@ -1271,6 +1336,12 @@
"pnpm-lock.yaml?ref=sha"
).with(headers: { "Authorization" => "token token" })
.to_return(status: 404)
stub_request(
:get,
"https://api.github.com/repos/gocardless/bump/contents/" \
"bun.lock?ref=sha"
).with(headers: { "Authorization" => "token token" })
.to_return(status: 404)
end

it "fetches package.json from the workspace dependencies" do
Expand Down Expand Up @@ -1840,6 +1911,12 @@
"pnpm-lock.yaml?ref=sha"
).with(headers: { "Authorization" => "token token" })
.to_return(status: 404)
stub_request(
:get,
"https://api.github.com/repos/gocardless/bump/contents/" \
"bun.lock?ref=sha"
).with(headers: { "Authorization" => "token token" })
.to_return(status: 404)
end

it "fetches package.json from the workspace dependencies" do
Expand Down
18 changes: 18 additions & 0 deletions npm_and_yarn/spec/fixtures/github/bun_lock_content.json

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions npm_and_yarn/spec/fixtures/github/contents_js_bun.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[
{
"name": "bun.lock",
"path": "bun.lock",
"sha": "5b0a45fe1229f38af2dedd390b9c531458b07262",
"size": 102275,
"url": "https://api.github.com/repos/toddledev/toddle/contents/bun.lock?ref=main",
"html_url": "https://github.com/toddledev/toddle/blob/main/bun.lock",
"git_url": "https://api.github.com/repos/toddledev/toddle/git/blobs/5b0a45fe1229f38af2dedd390b9c531458b07262",
"download_url": "https://raw.githubusercontent.com/toddledev/toddle/main/bun.lock",
"type": "file",
"_links": {
"self": "https://api.github.com/repos/toddledev/toddle/contents/bun.lock?ref=main",
"git": "https://api.github.com/repos/toddledev/toddle/git/blobs/5b0a45fe1229f38af2dedd390b9c531458b07262",
"html": "https://github.com/toddledev/toddle/blob/main/bun.lock"
}
},
{
"name": "bunfig.toml",
"path": "bunfig.toml",
"sha": "d8bd900b81f2e7462aaa38a3bd592040f5e9ba78",
"size": 33,
"url": "https://api.github.com/repos/toddledev/toddle/contents/bunfig.toml?ref=main",
"html_url": "https://github.com/toddledev/toddle/blob/main/bunfig.toml",
"git_url": "https://api.github.com/repos/toddledev/toddle/git/blobs/d8bd900b81f2e7462aaa38a3bd592040f5e9ba78",
"download_url": "https://raw.githubusercontent.com/toddledev/toddle/main/bunfig.toml",
"type": "file",
"_links": {
"self": "https://api.github.com/repos/toddledev/toddle/contents/bunfig.toml?ref=main",
"git": "https://api.github.com/repos/toddledev/toddle/git/blobs/d8bd900b81f2e7462aaa38a3bd592040f5e9ba78",
"html": "https://github.com/toddledev/toddle/blob/main/bunfig.toml"
}
},
{
"name": "package.json",
"path": "package.json",
"sha": "1393fcaa2e31945e9599741fbf04ed271cb7de22",
"size": 1371,
"url": "https://api.github.com/repos/toddledev/toddle/contents/package.json?ref=main",
"html_url": "https://github.com/toddledev/toddle/blob/main/package.json",
"git_url": "https://api.github.com/repos/toddledev/toddle/git/blobs/1393fcaa2e31945e9599741fbf04ed271cb7de22",
"download_url": "https://raw.githubusercontent.com/toddledev/toddle/main/package.json",
"type": "file",
"_links": {
"self": "https://api.github.com/repos/toddledev/toddle/contents/package.json?ref=main",
"git": "https://api.github.com/repos/toddledev/toddle/git/blobs/1393fcaa2e31945e9599741fbf04ed271cb7de22",
"html": "https://github.com/toddledev/toddle/blob/main/package.json"
}
}
]
Loading