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 0032a0b081..ad1da67a7b 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 @@ -207,7 +207,9 @@ def package_manager_helper @package_manager_helper ||= T.let( PackageManagerHelper.new( parsed_package_json, - lockfiles: lockfiles + lockfiles, + registry_config_files, + credentials ), T.nilable(PackageManagerHelper) ) end @@ -221,6 +223,17 @@ def lockfiles } end + # Returns the .npmrc, and .yarnrc files for the repository. + # @return [Hash{Symbol => Dependabot::DependencyFile}] + sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } + def registry_config_files + { + npmrc: npmrc, + yarnrc: yarnrc, + yarnrc_yml: yarnrc_yml + } + end + sig { returns(DependencyFile) } def package_json @package_json ||= T.let(fetch_file_from_host(MANIFEST_FILENAME), T.nilable(DependencyFile)) diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb index 6acf9978d2..11f3bb0a7a 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb @@ -98,7 +98,9 @@ def package_manager_helper @package_manager_helper ||= T.let( PackageManagerHelper.new( parsed_package_json, - lockfiles: lockfiles + lockfiles, + registry_config_files, + credentials ), T.nilable(PackageManagerHelper) ) end @@ -112,6 +114,15 @@ def lockfiles } end + sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } + def registry_config_files + { + npmrc: npmrc, + yarnrc: yarnrc, + yarnrc_yml: yarnrc_yml + } + end + sig { returns(T.untyped) } def parsed_package_json JSON.parse(T.must(package_json.content)) @@ -156,6 +167,27 @@ def pnpm_lock end, T.nilable(Dependabot::DependencyFile)) end + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def npmrc + @npmrc ||= T.let(dependency_files.find do |f| + f.name == NpmPackageManager::RC_FILENAME + end, T.nilable(Dependabot::DependencyFile)) + end + + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def yarnrc + @yarnrc ||= T.let(dependency_files.find do |f| + f.name == YarnPackageManager::RC_FILENAME + end, T.nilable(Dependabot::DependencyFile)) + end + + sig { returns(T.nilable(DependencyFile)) } + def yarnrc_yml + @yarnrc_yml ||= T.let(dependency_files.find do |f| + f.name == YarnPackageManager::RC_YML_FILENAME + end, T.nilable(Dependabot::DependencyFile)) + end + sig { returns(Dependabot::FileParsers::Base::DependencySet) } def manifest_dependencies dependency_set = DependencySet.new diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb index 3646208811..5129d1d6d3 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb @@ -388,17 +388,28 @@ def self.run_single_yarn_command(command, fingerprint: nil) end # Install the package manager for specified version by using corepack - sig { params(name: String, version: String).returns(String) } - def self.install(name, version) + sig do + params( + name: String, + version: String, + env: T.nilable(T::Hash[String, String]) + ) + .returns(String) + end + def self.install(name, version, env: {}) Dependabot.logger.info("Installing \"#{name}@#{version}\"") begin # Try to install the specified version - output = package_manager_install(name, version) + output = package_manager_install(name, version, env: env) # Confirm success based on the output if output.match?(/Adding #{name}@.* to the cache/) Dependabot.logger.info("#{name}@#{version} successfully installed.") + + Dependabot.logger.info("Activating currently installed version of #{name}: #{version}") + package_manager_activate(name, version) + else Dependabot.logger.error("Corepack installation output unexpected: #{output}") fallback_to_local_version(name) @@ -428,11 +439,19 @@ def self.fallback_to_local_version(name) end # Install the package manager for specified version by using corepack - sig { params(name: String, version: String).returns(String) } - def self.package_manager_install(name, version) + sig do + params( + name: String, + version: String, + env: T.nilable(T::Hash[String, String]) + ) + .returns(String) + end + def self.package_manager_install(name, version, env: {}) Dependabot::SharedHelpers.run_shell_command( "corepack install #{name}@#{version} --global --cache-only", - fingerprint: "corepack install @ --global --cache-only" + fingerprint: "corepack install @ --global --cache-only", + env: env ).strip end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb index 54b9b22ebd..199d7c828f 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb @@ -5,6 +5,7 @@ require "dependabot/ecosystem" require "dependabot/npm_and_yarn/requirement" require "dependabot/npm_and_yarn/version_selector" +require "dependabot/npm_and_yarn/registry_helper" module Dependabot module NpmAndYarn @@ -311,17 +312,24 @@ class PackageManagerHelper sig do params( package_json: T.nilable(T::Hash[String, T.untyped]), - lockfiles: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)] + lockfiles: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)], + registry_config_files: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)], + credentials: T.nilable(T::Array[Dependabot::Credential]) ).void end - def initialize(package_json, lockfiles:) + def initialize(package_json, lockfiles, registry_config_files, credentials) @package_json = package_json @lockfiles = lockfiles + @registry_helper = T.let( + RegistryHelper.new(registry_config_files, credentials), + Dependabot::NpmAndYarn::RegistryHelper + ) @package_manager_detector = T.let(PackageManagerDetector.new(lockfiles, package_json), PackageManagerDetector) @manifest_package_manager = T.let(package_json&.fetch(MANIFEST_PACKAGE_MANAGER_KEY, nil), T.nilable(String)) @engines = T.let(package_json&.fetch(MANIFEST_ENGINES_KEY, nil), T.nilable(T::Hash[String, T.untyped])) @installed_versions = T.let({}, T::Hash[String, String]) + @registries = T.let({}, T::Hash[String, String]) @language = T.let(nil, T.nilable(Ecosystem::VersionManager)) @language_requirement = T.let(nil, T.nilable(Requirement)) @@ -379,8 +387,8 @@ def find_engine_constraints_as_requirement(name) end # rubocop:disable Metrics/CyclomaticComplexity - # rubocop:disable Metrics/PerceivedComplexity # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/PerceivedComplexity sig { params(name: String).returns(T.nilable(T.any(Integer, String))) } def setup(name) # we prioritize version mentioned in "packageManager" instead of "engines" @@ -438,6 +446,9 @@ def setup(name) end version end + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/PerceivedComplexity sig { params(name: T.nilable(String)).returns(Ecosystem::VersionManager) } def package_manager_by_name(name) @@ -456,21 +467,15 @@ def package_manager_by_name(name) Dependabot.logger.info("No version requirement found for #{name}") end - package_manager_instance = package_manager_class.new( + package_manager_class.new( installed_version, requirement: package_manager_requirement ) - - Dependabot.logger.info("Package manager resolved for #{name}: #{package_manager_instance}") - package_manager_instance rescue StandardError => e Dependabot.logger.error("Error resolving package manager for #{name || 'default'}: #{e.message}") raise end - # rubocop:enable Metrics/CyclomaticComplexity - # rubocop:enable Metrics/PerceivedComplexity - # rubocop:enable Metrics/AbcSize # Retrieve the installed version of the package manager by executing # the "corepack -v" command and using the output. # If the output does not match the expected version format (PACKAGE_MANAGER_VERSION_REGEX), @@ -510,7 +515,12 @@ def raise_if_unsupported!(name, version) sig { params(name: String, version: T.nilable(String)).void } def install(name, version) if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn) - return Helpers.install(name, version.to_s) + env = {} + if Dependabot::Experiments.enabled?(:enable_private_registry_for_corepack) + env = @registry_helper.find_corepack_env_variables + end + # Use the Helpers.install method to install the package manager + return Helpers.install(name, version.to_s, env: env) end Dependabot.logger.info("Installing \"#{name}@#{version}\"") diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/registry_helper.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/registry_helper.rb new file mode 100644 index 0000000000..c095405d1e --- /dev/null +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/registry_helper.rb @@ -0,0 +1,188 @@ +# typed: strict +# frozen_string_literal: true + +require "yaml" +require "dependabot/dependency_file" +require "sorbet-runtime" + +module Dependabot + module NpmAndYarn + class RegistryHelper + extend T::Sig + + # Keys for configurations + REGISTRY_KEY = "registry" + AUTH_KEY = "authToken" + + # Yarn-specific keys + NPM_AUTH_TOKEN_KEY_FOR_YARN = "npmAuthToken" + NPM_SCOPE_KEY_FOR_YARN = "npmScopes" + NPM_REGISTER_KEY_FOR_YARN = "npmRegistryServer" + + # Environment variable keys + COREPACK_NPM_REGISTRY_ENV = "COREPACK_NPM_REGISTRY" + COREPACK_NPM_TOKEN_ENV = "COREPACK_NPM_TOKEN" + + sig do + params( + registry_config_files: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)], + credentials: T.nilable(T::Array[Dependabot::Credential]) + ).void + end + def initialize(registry_config_files, credentials) + @registry_config_files = T.let(registry_config_files, T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) + @credentials = T.let(credentials, T.nilable(T::Array[Dependabot::Credential])) + end + + sig { returns(T::Hash[String, String]) } + def find_corepack_env_variables + registry_info = find_registry_and_token + + env_variables = {} + env_variables[COREPACK_NPM_REGISTRY_ENV] = registry_info[:registry] if registry_info[:registry] + env_variables[COREPACK_NPM_TOKEN_ENV] = registry_info[:auth_token] if registry_info[:auth_token] + + env_variables + end + + private + + sig { returns(T::Hash[Symbol, T.nilable(String)]) } + def find_registry_and_token + # Step 1: Check dependabot.yml configuration + dependabot_config = config_npm_registry_and_token + return dependabot_config if dependabot_config[:registry] + + # Step 2: Check .npmrc + npmrc_config = @registry_config_files[:npmrc] + npmrc_result = parse_registry_from_npmrc_yarnrc(npmrc_config, "=", "npm") + + return npmrc_result if npmrc_result[:registry] + + # Step 3: Check .yarnrc + yarnrc_config = @registry_config_files[:yarnrc] + yarnrc_result = parse_registry_from_npmrc_yarnrc(yarnrc_config, " ", "npm") + return yarnrc_result if yarnrc_result[:registry] + + # Step 4: Check yarnrc.yml + yarnrc_yml_config = @registry_config_files[:yarnrc_yml] + yarnrc_yml_result = parse_npm_from_yarnrc_yml(yarnrc_yml_config) + return yarnrc_yml_result if yarnrc_yml_result[:registry] + + # Default values if no registry is found + {} + end + + sig { returns(T::Hash[Symbol, T.nilable(String)]) } + def config_npm_registry_and_token + registries = {} + + return registries unless @credentials&.any? + + @credentials.each do |cred| + next unless cred["type"] == "npm_registry" # Skip if not an npm registry + next unless cred["replaces-base"] # Skip if not a reverse-proxy registry + + # Set the registry if it's not already set + registries[:registry] ||= cred["registry"] + + # Set the token if it's not already set + registries[:auth_token] ||= cred["token"] + end + registries + end + + # Find registry and token in .npmrc or .yarnrc file + sig do + params( + file: T.nilable(Dependabot::DependencyFile), + separator: String + ).returns(T::Hash[Symbol, T.nilable(String)]) + end + def parse_npm_from_npm_or_yarn_rc(file, separator = "=") + parse_registry_from_npmrc_yarnrc(file, separator, NpmPackageManager::NAME) + end + + # Find registry and token in .npmrc or .yarnrc file + sig do + params( + file: T.nilable(Dependabot::DependencyFile), + separator: String, + scope: T.nilable(String) + ).returns(T::Hash[Symbol, T.nilable(String)]) + end + def parse_registry_from_npmrc_yarnrc(file, separator = "=", scope = nil) + content = file&.content + return { registry: nil, auth_token: nil } unless content + + global_registry = T.let(nil, T.nilable(String)) + scoped_registry = T.let(nil, T.nilable(String)) + auth_token = T.let(nil, T.nilable(String)) + + content.split("\n").each do |line| + # Split using the provided separator + key, value = line.strip.split(separator, 2) + next unless key && value + + # Remove surrounding quotes from keys and values + cleaned_key = key.strip.gsub(/\A["']|["']\z/, "") + cleaned_value = value.strip.gsub(/\A["']|["']\z/, "") + + case cleaned_key + when "registry" + # Case 1: Found a global registry + global_registry = cleaned_value + when "_authToken" + # Case 2: Found an auth token + auth_token = cleaned_value + else + # Handle scoped registry if a scope is provided + scoped_registry = cleaned_value if scope && cleaned_key == "@#{scope}:registry" + end + end + + # Determine the registry to return (global first, fallback to scoped) + registry = global_registry || scoped_registry + + { registry: registry, auth_token: auth_token } + end + + # rubocop:disable Metrics/PerceivedComplexity + sig { params(file: T.nilable(Dependabot::DependencyFile)).returns(T::Hash[Symbol, T.nilable(String)]) } + def parse_npm_from_yarnrc_yml(file) + content = file&.content + return { registry: nil, auth_token: nil } unless content + + result = {} + yaml_data = safe_load_yaml(content) + + # Step 1: Extract global registry and auth token + result[:registry] = yaml_data[NPM_REGISTER_KEY_FOR_YARN] if yaml_data.key?(NPM_REGISTER_KEY_FOR_YARN) + result[:auth_token] = yaml_data[NPM_AUTH_TOKEN_KEY_FOR_YARN] if yaml_data.key?(NPM_AUTH_TOKEN_KEY_FOR_YARN) + + # Step 2: Fallback to any scoped registry and auth token if global is missing + if result[:registry].nil? && yaml_data.key?(NPM_SCOPE_KEY_FOR_YARN) + yaml_data[NPM_SCOPE_KEY_FOR_YARN].each do |_current_scope, config| + next unless config.is_a?(Hash) + + result[:registry] ||= config[NPM_REGISTER_KEY_FOR_YARN] + result[:auth_token] ||= config[NPM_AUTH_TOKEN_KEY_FOR_YARN] + end + end + + result + end + # rubocop:enable Metrics/PerceivedComplexity + + # Safely loads the YAML content and logs any parsing errors + sig { params(content: String).returns(T::Hash[String, T.untyped]) } + def safe_load_yaml(content) + YAML.safe_load(content, permitted_classes: [Symbol, String]) || {} + rescue Psych::SyntaxError => e + # Log the error instead of raising it + Dependabot.logger.error("YAML parsing error: #{e.message}") + {} + end + end + end +end 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 170c882803..db4b0e3e58 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 @@ -365,6 +365,20 @@ body: fixture("github", "package_json_content.json"), headers: json_header ) + stub_request(:get, File.join(url, ".yarnrc?ref=sha")) + .with(headers: { "Authorization" => "token token" }) + .to_return( + status: 404, + body: nil, + headers: json_header + ) + stub_request(:get, File.join(url, "packages/.yarnrc?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" }) diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/helpers_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/helpers_spec.rb index 08d417caf2..6419a70df9 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/helpers_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/helpers_spec.rb @@ -160,7 +160,8 @@ allow(Dependabot::SharedHelpers).to receive(:run_shell_command).and_return("7.0.0/n") expect(Dependabot::SharedHelpers).to receive(:run_shell_command).with( "corepack install npm@7.0.0 --global --cache-only", - fingerprint: "corepack install @ --global --cache-only" + fingerprint: "corepack install @ --global --cache-only", + env: {} ) described_class.package_manager_install("npm", "7.0.0") end @@ -202,7 +203,8 @@ # Mock for `package_manager_install("npm", "8.0.0")` allow(Dependabot::SharedHelpers).to receive(:run_shell_command).with( "corepack install npm@8.0.0 --global --cache-only", - fingerprint: "corepack install @ --global --cache-only" + fingerprint: "corepack install @ --global --cache-only", + env: {} ).and_return("Adding npm@8.0.0 to the cache") # Mock for `package_manager_activate("npm", "8.0.0")` @@ -226,6 +228,7 @@ # Log expectations expect(Dependabot.logger).to receive(:info).with("Installing \"npm@8.0.0\"") expect(Dependabot.logger).to receive(:info).with("npm@8.0.0 successfully installed.") + expect(Dependabot.logger).to receive(:info).with("Activating currently installed version of npm: 8.0.0") expect(Dependabot.logger).to receive(:info).with("Fetching version for package manager: npm") expect(Dependabot.logger).to receive(:info).with("Installed version of npm: 8.0.0") @@ -240,7 +243,8 @@ # Mock for `package_manager_install("npm", "8.0.0")` allow(Dependabot::SharedHelpers).to receive(:run_shell_command).with( "corepack install npm@8.0.0 --global --cache-only", - fingerprint: "corepack install @ --global --cache-only" + fingerprint: "corepack install @ --global --cache-only", + env: {} ).and_return("Unexpected output") # Mock for `package_manager_activate("npm", "10.8.2")` @@ -282,7 +286,8 @@ # Mock for `package_manager_install("npm", "8.0.0")` (raises an error) allow(Dependabot::SharedHelpers).to receive(:run_shell_command).with( "corepack install npm@8.0.0 --global --cache-only", - fingerprint: "corepack install @ --global --cache-only" + fingerprint: "corepack install @ --global --cache-only", + env: {} ).and_raise(StandardError, "Corepack failed") # Mock for `package_manager_activate("npm", "10.8.2")` diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/package_manager_helper_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_manager_helper_spec.rb index 5f1febe98f..016087ce3a 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/package_manager_helper_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_manager_helper_spec.rb @@ -63,8 +63,11 @@ end let(:lockfiles) { { npm: npm_lockfile, yarn: yarn_lockfile, pnpm: pnpm_lockfile } } + + let(:register_config_files) { {} } + let(:package_json) { { "packageManager" => "npm@7" } } - let(:helper) { described_class.new(package_json, lockfiles: lockfiles) } + let(:helper) { described_class.new(package_json, lockfiles, register_config_files, []) } describe "#package_manager" do context "when npm lockfile exists" do diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/registry_helper_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/registry_helper_spec.rb new file mode 100644 index 0000000000..b8e1641b45 --- /dev/null +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/registry_helper_spec.rb @@ -0,0 +1,155 @@ +# typed: false +# frozen_string_literal: true + +require "dependabot/dependency_file" +require "dependabot/npm_and_yarn/registry_helper" +require "spec_helper" + +RSpec.describe Dependabot::NpmAndYarn::RegistryHelper do + let(:npmrc_file) do + Dependabot::DependencyFile.new( + name: ".npmrc", + content: <<~NPMRC + registry=https://custom-registry.com/ + _authToken=custom-token + NPMRC + ) + end + + let(:npmrc_without_token_file) do + Dependabot::DependencyFile.new( + name: ".npmrc", + content: <<~NPMRC + registry=https://custom-registry.com/ + NPMRC + ) + end + + let(:empty_npmrc_file) do + Dependabot::DependencyFile.new(name: ".npmrc", content: "") + end + + let(:yarnrc_file) do + Dependabot::DependencyFile.new( + name: ".yarnrc", + content: <<~YARNRC + registry "https://yarn-registry.com/" + "@scope:registry" "https://custom-registry.example.com/" + "_authToken" "your-auth-token-here" + YARNRC + ) + end + + let(:yarnrc_without_token_file) do + Dependabot::DependencyFile.new( + name: ".yarnrc", + content: <<~YARNRC + registry "https://yarn-registry.com/" + YARNRC + ) + end + + let(:empty_yarnrc_file) do + Dependabot::DependencyFile.new(name: ".yarnrc", content: "") + end + + let(:yarnrc_yml_file) do + Dependabot::DependencyFile.new( + name: "yarnrc.yml", + content: <<~YAML + npmRegistryServer: "https://yarnrc-yml-registry.com/" + npmAuthToken: "yarnrc-yml-token" + YAML + ) + end + + let(:yarnrc_yml_without_token_file) do + Dependabot::DependencyFile.new( + name: "yarnrc.yml", + content: <<~YAML + npmRegistryServer: "https://yarnrc-yml-registry.com/" + YAML + ) + end + + let(:empty_yarnrc_yml_file) do + Dependabot::DependencyFile.new(name: "yarnrc.yml", content: "") + end + + describe "#find_corepack_env_variables" do + context "when npmrc is provided" do + let(:registry_config_files) { { npmrc: npmrc_file } } + + it "returns registry and token from npmrc" do + helper = described_class.new(registry_config_files, []) + env_variables = helper.find_corepack_env_variables + expect(env_variables).to eq( + "COREPACK_NPM_REGISTRY" => "https://custom-registry.com/", + "COREPACK_NPM_TOKEN" => "custom-token" + ) + end + end + + context "when npmrc has registry but no token" do + let(:registry_config_files) { { npmrc: npmrc_without_token_file } } + + it "returns only the registry from npmrc" do + helper = described_class.new(registry_config_files, []) + env_variables = helper.find_corepack_env_variables + expect(env_variables).to eq( + "COREPACK_NPM_REGISTRY" => "https://custom-registry.com/" + ) + end + end + + context "when yarnrc is provided" do + let(:registry_config_files) { { yarnrc: yarnrc_file } } + + it "returns registry and token from yarnrc" do + helper = described_class.new(registry_config_files, []) + env_variables = helper.find_corepack_env_variables + expect(env_variables).to eq( + "COREPACK_NPM_REGISTRY" => "https://yarn-registry.com/", + "COREPACK_NPM_TOKEN" => "your-auth-token-here" + ) + end + end + + context "when yarnrc has registry but no token" do + let(:registry_config_files) { { yarnrc: yarnrc_without_token_file } } + + it "returns only the registry from yarnrc" do + helper = described_class.new(registry_config_files, []) + env_variables = helper.find_corepack_env_variables + expect(env_variables).to eq( + "COREPACK_NPM_REGISTRY" => "https://yarn-registry.com/" + ) + end + end + + context "when yarnrc.yml is provided" do + let(:registry_config_files) { { yarnrc_yml: yarnrc_yml_file } } + + it "returns registry and token from yarnrc.yml" do + helper = described_class.new(registry_config_files, []) + env_variables = helper.find_corepack_env_variables + expect(env_variables).to eq( + "COREPACK_NPM_REGISTRY" => "https://yarnrc-yml-registry.com/", + "COREPACK_NPM_TOKEN" => "yarnrc-yml-token" + ) + end + end + + context "when yarnrc.yml has registry but no token" do + let(:registry_config_files) { { yarnrc_yml: yarnrc_yml_without_token_file } } + + it "returns only the registry from yarnrc.yml" do + helper = described_class.new(registry_config_files, []) + env_variables = helper.find_corepack_env_variables + expect(env_variables).to eq( + "COREPACK_NPM_REGISTRY" => "https://yarnrc-yml-registry.com/" + ) + end + end + end +end