diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/berry_lockfile_handler.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/berry_lockfile_handler.rb new file mode 100644 index 00000000000..da9b4d02db1 --- /dev/null +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/berry_lockfile_handler.rb @@ -0,0 +1,108 @@ +# typed: strict +# frozen_string_literal: true + +require "sorbet-runtime" +require "yaml" + +require "dependabot/npm_and_yarn/file_updater" + +# Handles yarn berry lockfile manipulation — parsing descriptors, finding +# entries, and rewriting keys from exact versions back to ranges. This is +# the berry equivalent of yarn classic's replace-lockfile-declaration.ts. +module Dependabot + module NpmAndYarn + class FileUpdater < Dependabot::FileUpdaters::Base + class BerryLockfileHandler + extend T::Sig + + # Parses a yarn berry lockfile (YAML format). Returns nil if unparseable. + sig { params(lockfile_path: String).returns(T.nilable(T::Hash[String, T.untyped])) } + def self.parse(lockfile_path) + return unless File.exist?(lockfile_path) + + parsed = YAML.safe_load_file(lockfile_path) + parsed.is_a?(Hash) ? parsed : nil + end + + # Checks if the parsed lockfile has the target version for a dependency. + sig { params(parsed: T::Hash[String, T.untyped], dep_name: String, version: String).returns(T::Boolean) } + def self.version_matches?(parsed, dep_name, version) + parsed.any? do |key, value| + next false unless value.is_a?(Hash) + + key.to_s.split(", ").any? { |part| split_descriptor(part)[0] == dep_name } && + value["version"] == version + end + end + + # Rewrites a lockfile descriptor key from exact version to range. + # Example: "axios@npm:1.15.2" → "axios@npm:^1.15.2" + # The resolved version, checksum, and dependencies remain unchanged. + sig do + params( + lockfile_path: String, + dep_name: String, + version: String, + requirement: String + ).void + end + def self.replace_declaration(lockfile_path, dep_name, version, requirement) + return unless File.exist?(lockfile_path) + + content = File.read(lockfile_path) + parsed = parse(lockfile_path) + return unless parsed + + exact_key = find_exact_key(parsed, dep_name, version) + return unless exact_key + + protocol = extract_protocol(exact_key, dep_name) + new_key = "#{dep_name}@#{protocol}#{requirement}" + + escaped = Regexp.escape(exact_key) + File.write(lockfile_path, content.gsub(/^"#{escaped}":/m, "\"#{new_key}\":")) + end + + # Finds the lockfile key containing the given dep name with exact version. + # Handles composite keys (e.g., "a@npm:1.0, a@npm:^1.0"). + sig { params(parsed: T::Hash[String, T.untyped], dep_name: String, version: String).returns(T.nilable(String)) } + def self.find_exact_key(parsed, dep_name, version) + parsed.keys.find do |key| + next false unless key.is_a?(String) + + key.split(", ").any? do |part| + name, desc = split_descriptor(part) + name == dep_name && (desc&.end_with?(version) || false) + end + end + end + + # Splits a yarn berry descriptor into [package_name, version/range]. + # Handles scoped packages like @scope/pkg@npm:^1.0.0. + sig { params(descriptor: String).returns([String, T.nilable(String)]) } + def self.split_descriptor(descriptor) + if descriptor.start_with?("@") + at_index = descriptor.index("@", 1) + return [descriptor, nil] unless at_index + + [T.must(descriptor[0...at_index]), descriptor[(at_index + 1)..]] + else + parts = descriptor.split("@", 2) + [T.must(parts[0]), parts[1]] + end + end + + # Extracts the protocol prefix (e.g., "npm:") from a descriptor. + sig { params(key: String, dep_name: String).returns(String) } + def self.extract_protocol(key, dep_name) + part = key.split(", ").find { |p| split_descriptor(p)[0] == dep_name } + return "" unless part + + _, descriptor = split_descriptor(part) + match = descriptor&.match(/^([a-z]+:)/) + match ? T.must(match[1]) : "" + end + end + end + end +end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb index beab8a2a152..7df8d283681 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb @@ -21,6 +21,7 @@ class YarnLockfileUpdater require_relative "npmrc_builder" require_relative "package_json_updater" require_relative "package_json_preparer" + require_relative "berry_lockfile_handler" extend T::Sig @@ -222,6 +223,13 @@ def run_yarn_berry_top_level_updater(top_level_dependency_updates:, yarn_lock:) if top_level_dependency_updates.all? { |dep| requirements_changed?(dep[:name]) } Helpers.run_yarn_command("install #{yarn_berry_args}".strip) + + # Yarn berry resolves ranges to the latest matching version, which + # may differ from Dependabot's target. If the lockfile resolved to a + # different version, re-install with the exact target and rewrite + # the lockfile descriptor back to the range — same approach as yarn + # classic's replaceLockfileDeclaration. + pin_berry_versions_if_needed(top_level_dependency_updates, yarn_lock) else updates = top_level_dependency_updates.collect do |dep| dep[:name] @@ -243,6 +251,78 @@ def requirements_changed?(dependency_name) dep.requirements != dep.previous_requirements end + # Checks if yarn resolved to a different version than Dependabot's target + # and re-pins if needed. Yarn berry resolves ranges to the latest matching + # version, which can bypass Dependabot's version selection — including + # security updates (minimum safe version), ignore conditions, and cooldown. + sig do + params( + top_level_dependency_updates: T::Array[T::Hash[Symbol, T.untyped]], + yarn_lock: Dependabot::DependencyFile + ).void + end + def pin_berry_versions_if_needed(top_level_dependency_updates, yarn_lock) + parsed = BerryLockfileHandler.parse(yarn_lock.name) + return unless parsed + + top_level_dependency_updates.each do |dep| + pin_berry_version_if_needed(dep, yarn_lock, parsed) + end + end + + sig do + params( + dep: T::Hash[Symbol, T.untyped], + yarn_lock: Dependabot::DependencyFile, + parsed_lockfile: T::Hash[String, T.untyped] + ).void + end + def pin_berry_version_if_needed(dep, yarn_lock, parsed_lockfile) + version = dep[:version] + return unless version + + dep_name = T.cast(dep[:name], String) + reqs = dep[:requirements] + return if reqs.nil? || reqs.empty? + return if reqs.any? { |req| req[:source] && req[:source][:type] == "git" } + return if BerryLockfileHandler.version_matches?(parsed_lockfile, dep_name, T.cast(version, String)) + + saved_package_jsons = save_package_jsons + + Helpers.run_yarn_command( + "up #{dep_name}@#{version} #{yarn_berry_args}".strip, + fingerprint: "up @ #{yarn_berry_args}".strip + ) + + reqs.each do |req| + requirement = req[:requirement] + next unless requirement + + BerryLockfileHandler.replace_declaration(yarn_lock.name, dep_name, T.cast(version, String), requirement) + end + + # Restore package.json and re-install to normalize lockfile descriptors, + # same as yarn classic's replaceLockfileDeclaration flow. + restore_package_jsons(saved_package_jsons) + Helpers.run_yarn_command("install #{yarn_berry_args}".strip) + end + + sig { returns(T::Hash[String, String]) } + def save_package_jsons + result = T.let({}, T::Hash[String, String]) + package_files.each do |file| + next unless File.exist?(file.name) + + result[file.name] = File.read(file.name) + end + result + end + + sig { params(saved: T::Hash[String, String]).void } + def restore_package_jsons(saved) + saved.each { |path, content| File.write(path, content) } + end + sig { params(yarn_lock: Dependabot::DependencyFile).returns(T::Hash[String, String]) } def run_yarn_berry_subdependency_updater(yarn_lock:) dep = T.must(sub_dependencies.first) diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/berry_lockfile_handler_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/berry_lockfile_handler_spec.rb new file mode 100644 index 00000000000..1e91466bcc2 --- /dev/null +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/berry_lockfile_handler_spec.rb @@ -0,0 +1,195 @@ +# typed: false +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/npm_and_yarn/file_updater/berry_lockfile_handler" + +RSpec.describe Dependabot::NpmAndYarn::FileUpdater::BerryLockfileHandler do + let(:fixture_path) do + File.join("spec", "fixtures", "projects", "yarn_berry", "security_update", "yarn.lock") + end + + describe ".parse" do + it "parses a valid yarn berry lockfile" do + parsed = described_class.parse(fixture_path) + expect(parsed).to be_a(Hash) + expect(parsed.keys).to include("__metadata") + end + + it "returns nil for a non-existent file" do + expect(described_class.parse("nonexistent.lock")).to be_nil + end + end + + describe ".split_descriptor" do + it "splits a simple descriptor" do + name, version = described_class.split_descriptor("axios@npm:^1.15.0") + expect(name).to eq("axios") + expect(version).to eq("npm:^1.15.0") + end + + it "splits a scoped package descriptor" do + name, version = described_class.split_descriptor("@scope/pkg@npm:^1.0.0") + expect(name).to eq("@scope/pkg") + expect(version).to eq("npm:^1.0.0") + end + + it "handles descriptor without version" do + name, version = described_class.split_descriptor("axios") + expect(name).to eq("axios") + expect(version).to be_nil + end + + it "handles scoped package without version" do + name, version = described_class.split_descriptor("@scope/pkg") + expect(name).to eq("@scope/pkg") + expect(version).to be_nil + end + end + + describe ".version_matches?" do + let(:parsed) do + { + "axios@npm:^1.15.0" => { "version" => "1.15.0", "resolution" => "axios@npm:1.15.0" }, + "@scope/pkg@npm:^2.0.0" => { "version" => "2.1.0", "resolution" => "@scope/pkg@npm:2.1.0" }, + "__metadata" => { "version" => 8 } + } + end + + it "returns true when version matches" do + expect(described_class.version_matches?(parsed, "axios", "1.15.0")).to be true + end + + it "returns false when version differs" do + expect(described_class.version_matches?(parsed, "axios", "1.15.2")).to be false + end + + it "returns false for unknown dependency" do + expect(described_class.version_matches?(parsed, "unknown-pkg", "1.0.0")).to be false + end + + it "handles scoped packages" do + expect(described_class.version_matches?(parsed, "@scope/pkg", "2.1.0")).to be true + expect(described_class.version_matches?(parsed, "@scope/pkg", "2.0.0")).to be false + end + + context "with composite keys" do + let(:parsed) do + { + "lodash@npm:1.3.1, lodash@npm:^1.3.1" => { "version" => "1.3.1" } + } + end + + it "matches composite keys" do + expect(described_class.version_matches?(parsed, "lodash", "1.3.1")).to be true + expect(described_class.version_matches?(parsed, "lodash", "1.3.0")).to be false + end + end + end + + describe ".find_exact_key" do + let(:parsed) do + { + "axios@npm:1.15.2" => { "version" => "1.15.2" }, + "lodash@npm:^1.3.1" => { "version" => "1.3.1" }, + "@scope/pkg@npm:2.0.0" => { "version" => "2.0.0" } + } + end + + it "finds exact version key" do + expect(described_class.find_exact_key(parsed, "axios", "1.15.2")).to eq("axios@npm:1.15.2") + end + + it "finds scoped package key" do + expect(described_class.find_exact_key(parsed, "@scope/pkg", "2.0.0")).to eq("@scope/pkg@npm:2.0.0") + end + + it "returns nil when not found" do + expect(described_class.find_exact_key(parsed, "axios", "9.9.9")).to be_nil + end + + it "does not match range keys" do + expect(described_class.find_exact_key(parsed, "lodash", "1.3")).to be_nil + end + end + + describe ".extract_protocol" do + it "extracts npm protocol" do + expect(described_class.extract_protocol("axios@npm:1.15.2", "axios")).to eq("npm:") + end + + it "extracts protocol from scoped package" do + expect(described_class.extract_protocol("@scope/pkg@npm:^1.0.0", "@scope/pkg")).to eq("npm:") + end + + it "extracts protocol from composite key" do + expect(described_class.extract_protocol("lodash@npm:1.3.1, lodash@npm:^1.3.1", "lodash")).to eq("npm:") + end + + it "returns empty string when no protocol" do + expect(described_class.extract_protocol("axios@1.15.2", "axios")).to eq("") + end + end + + describe ".replace_declaration" do + let(:tmp_dir) { Dir.mktmpdir } + let(:lockfile_path) { File.join(tmp_dir, "yarn.lock") } + + after { FileUtils.rm_rf(tmp_dir) } + + it "replaces exact descriptor with range" do + File.write(lockfile_path, <<~YAML) + "axios@npm:1.15.2": + version: 1.15.2 + resolution: "axios@npm:1.15.2" + checksum: abc123 + YAML + + described_class.replace_declaration(lockfile_path, "axios", "1.15.2", "^1.15.2") + + content = File.read(lockfile_path) + expect(content).to include('"axios@npm:^1.15.2":') + expect(content).not_to include('"axios@npm:1.15.2":') + expect(content).to include("version: 1.15.2") + expect(content).to include('resolution: "axios@npm:1.15.2"') + end + + it "handles tilde ranges" do + File.write(lockfile_path, <<~YAML) + "lodash@npm:1.3.1": + version: 1.3.1 + resolution: "lodash@npm:1.3.1" + YAML + + described_class.replace_declaration(lockfile_path, "lodash", "1.3.1", "~1.3.1") + + content = File.read(lockfile_path) + expect(content).to include('"lodash@npm:~1.3.1":') + end + + it "handles scoped packages" do + File.write(lockfile_path, <<~YAML) + "@scope/pkg@npm:2.0.0": + version: 2.0.0 + resolution: "@scope/pkg@npm:2.0.0" + YAML + + described_class.replace_declaration(lockfile_path, "@scope/pkg", "2.0.0", "^2.0.0") + + content = File.read(lockfile_path) + expect(content).to include('"@scope/pkg@npm:^2.0.0":') + end + + it "does nothing when exact key not found" do + original = <<~YAML + "axios@npm:^1.15.0": + version: 1.15.0 + YAML + File.write(lockfile_path, original) + + described_class.replace_declaration(lockfile_path, "axios", "1.15.2", "^1.15.2") + + expect(File.read(lockfile_path)).to eq(original) + end + end +end diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb index b706674dced..fe0ba85bddc 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb @@ -3038,6 +3038,74 @@ .to include(%("acorn@npm:^5.0.0, acorn@npm:^5.1.2":\n version: 5.7.3)) end end + + context "when the target version differs from latest in range (security update)" do + let(:project_name) { "yarn_berry/security_update" } + let(:files) { project_dependency_files(project_name) } + let(:repo_contents_path) { build_tmp_repo(project_name, path: "projects") } + + let(:dependency_name) { "axios" } + let(:version) { "1.15.2" } + let(:previous_version) { "1.15.0" } + let(:requirements) do + [{ + file: "package.json", + requirement: "^1.15.2", + groups: ["dependencies"], + source: nil + }] + end + let(:previous_requirements) do + [{ + file: "package.json", + requirement: "^1.15.0", + groups: ["dependencies"], + source: nil + }] + end + + it "pins to the exact target version with the caret range descriptor" do + parsed_lockfile = YAML.safe_load(updated_yarn_lock.content) + axios_entry = parsed_lockfile.find { |k, _| k.is_a?(String) && k.include?("axios") } + + expect(axios_entry&.first).to include("^1.15.2") + expect(axios_entry&.last&.dig("version")).to eq("1.15.2") + end + end + + context "when the target version differs from latest in range (version update with ignore)" do + let(:project_name) { "yarn_berry/security_update" } + let(:files) { project_dependency_files(project_name) } + let(:repo_contents_path) { build_tmp_repo(project_name, path: "projects") } + + let(:dependency_name) { "lodash" } + let(:version) { "4.17.10" } + let(:previous_version) { "4.17.0" } + let(:requirements) do + [{ + file: "package.json", + requirement: "~4.17.10", + groups: ["dependencies"], + source: nil + }] + end + let(:previous_requirements) do + [{ + file: "package.json", + requirement: "~4.17.0", + groups: ["dependencies"], + source: nil + }] + end + + it "pins to the exact target version with the tilde range descriptor" do + parsed_lockfile = YAML.safe_load(updated_yarn_lock.content) + lodash_entry = parsed_lockfile.find { |k, _| k.is_a?(String) && k.include?("lodash") } + + expect(lodash_entry&.first).to include("~4.17.10") + expect(lodash_entry&.last&.dig("version")).to eq("4.17.10") + end + end end ####################### diff --git a/npm_and_yarn/spec/fixtures/projects/yarn_berry/security_update/package.json b/npm_and_yarn/spec/fixtures/projects/yarn_berry/security_update/package.json new file mode 100644 index 00000000000..412861fcd7b --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/yarn_berry/security_update/package.json @@ -0,0 +1,9 @@ +{ + "name": "yarn-berry-combined-test", + "version": "1.0.0", + "packageManager": "yarn@4.12.0", + "dependencies": { + "axios": "^1.15.0", + "lodash": "~4.17.0" + } +} diff --git a/npm_and_yarn/spec/fixtures/projects/yarn_berry/security_update/yarn.lock b/npm_and_yarn/spec/fixtures/projects/yarn_berry/security_update/yarn.lock new file mode 100644 index 00000000000..d8c4d18f00c --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/yarn_berry/security_update/yarn.lock @@ -0,0 +1,256 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"axios@npm:^1.15.0": + version: 1.15.0 + resolution: "axios@npm:1.15.0" + dependencies: + follow-redirects: "npm:^1.15.11" + form-data: "npm:^4.0.5" + proxy-from-env: "npm:^2.1.0" + checksum: 10c0/47e0f860e98d4d7aa145e89ce0cae00e1fb0f1d2485f065c21fce955ddb1dba4103a46bd0e47acd18a27208a7f62c96249e620db575521b92a968619ab133409 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.2 + resolution: "es-object-atoms@npm:1.1.2" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/1772861f094f739d6f41b579cfb9a18579daffeb434552a370a5fbef50a32d22227e27b63fdbb757b7ddd429d1b42fe52ccae7966d9302a2ec221b6f1b41bbc4 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.11": + version: 1.16.0 + resolution: "follow-redirects@npm:1.16.0" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/a1e2900163e6f1b4d1ed5c221b607f41decbab65534c63fe7e287e40a5d552a6496e7d9d7d976fa4ba77b4c51c11e5e9f683f10b43011ea11e442ff128d0e181 + languageName: node + linkType: hard + +"form-data@npm:^4.0.5": + version: 4.0.5 + resolution: "form-data@npm:4.0.5" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" + mime-types: "npm:^2.1.12" + checksum: 10c0/dd6b767ee0bbd6d84039db12a0fa5a2028160ffbfaba1800695713b46ae974a5f6e08b3356c3195137f8530dcd9dfcb5d5ae1eeff53d0db1e5aad863b619ce3b + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.6": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.3 + resolution: "hasown@npm:2.0.3" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/f5eb28c3fd0d3e4facd821c1eeee3836c37b70ab0b0fc532e8a39976e18fef43652415dadc52f8c7a5ff6d5ac93b7bef128789aa6f90f4e9b9a9083dce74ab38 + languageName: node + linkType: hard + +"lodash@npm:~4.17.0": + version: 4.17.0 + resolution: "lodash@npm:4.17.0" + checksum: 10c0/48714dd7475af2a8de2a1e903eb7066c5f883b6d3ee3345d09882146df8c4377208b4331c2911f7005ff5b80d3951fd422545dc82ee42d9768143119f3e5b08e + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 + languageName: node + linkType: hard + +"yarn-berry-combined-test@workspace:.": + version: 0.0.0-use.local + resolution: "yarn-berry-combined-test@workspace:." + dependencies: + axios: "npm:^1.15.0" + lodash: "npm:~4.17.0" + languageName: unknown + linkType: soft