diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_files_filterer.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_files_filterer.rb index 930a957b53..91a22228a7 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_files_filterer.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_files_filterer.rb @@ -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) @@ -148,6 +148,7 @@ def lockfile?(file) "package-lock.json", "yarn.lock", "pnpm-lock.yaml", + "bun.lock", "npm-shrinkwrap.json" ) end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb index aa18e5a886..289b20ccb2 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb @@ -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? { @@ -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) @@ -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 = [] @@ -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( @@ -219,7 +238,8 @@ def lockfiles { npm: package_lock || shrinkwrap, yarn: yarn_lock, - pnpm: pnpm_lock + pnpm: pnpm_lock, + bun: bun_lock } end @@ -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)) } @@ -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)) } @@ -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)) } @@ -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 diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/sub_dependency_files_filterer.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/sub_dependency_files_filterer.rb index 9a6334a004..feb13eb0ba 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/sub_dependency_files_filterer.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/sub_dependency_files_filterer.rb @@ -68,6 +68,7 @@ def lockfile?(file) "package-lock.json", "yarn.lock", "npm-shrinkwrap.json", + "bun.lock", "pnpm-lock.yaml" ) end diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/dependency_files_filterer_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/dependency_files_filterer_spec.rb index 2688987838..03446c15cb 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/dependency_files_filterer_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/dependency_files_filterer_spec.rb @@ -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] } diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb index 3ef3f99a60..1a18fde224 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_fetcher_spec.rb @@ -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" }) @@ -488,6 +502,39 @@ 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 + + it "fetches the package.json and bun.lock" do + expect(file_fetcher_instance.files.map(&:name)) + .to match_array(%w(package.json bun.lock)) + end + + it "parses the bun.lock" do + expect(file_fetcher_instance.ecosystem_versions).to match( + { package_managers: { "bun" => an_instance_of(Integer) } } + ) + end + end + context "with an npm-shrinkwrap.json but no package-lock.json file" do before do stub_request(:get, url + "?ref=sha") @@ -1271,6 +1318,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 @@ -1840,6 +1893,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