Module: Dependabot::NpmAndYarn::Helpers
- Extended by:
- T::Sig
- Defined in:
- lib/dependabot/npm_and_yarn/helpers.rb
Overview
rubocop:disable Metrics/ModuleLength
Constant Summary collapse
- YARN_PATH_NOT_FOUND =
/^.*(?<error>The "yarn-path" option has been set \(in [^)]+\), but the specified location doesn't exist)/- NPM_V11 =
NPM Version Constants
11- NPM_V10 =
10- NPM_V8 =
8- NPM_V6 =
6- NPM_DEFAULT_VERSION =
NPM_V11- PNPM_V10 =
PNPM Version Constants
10- PNPM_V9 =
9- PNPM_V8 =
8- PNPM_V7 =
7- PNPM_V6 =
6- PNPM_DEFAULT_VERSION =
PNPM_V10- PNPM_FALLBACK_VERSION =
PNPM_V6- YARN_V3 =
YARN Version Constants
3- YARN_V2 =
2- YARN_V1 =
1- YARN_DEFAULT_VERSION =
YARN_V3- YARN_FALLBACK_VERSION =
YARN_V1- SUPPORTED_COREPACK_PACKAGE_MANAGERS =
corepack supported package managers
%w(npm yarn pnpm).freeze
Class Method Summary collapse
- .build_corepack_env_variables ⇒ Object
- .command_observer(output) ⇒ Object
- .corepack_supported_package_manager?(name) ⇒ Boolean
- .credentials ⇒ Object
- .credentials=(creds) ⇒ Object
- .dependencies_with_all_versions_metadata(dependency_set) ⇒ Object
- .dependency_files ⇒ Object
- .dependency_files=(files) ⇒ Object
- .fallback_to_local_version(name, env: {}) ⇒ Object
- .fetch_yarnrc_yml_value(key, default_value) ⇒ Object
- .handle_subprocess_failure(error) ⇒ Object
- .install(name, version, env: {}) ⇒ Object
- .local_package_manager_version(name) ⇒ Object
- .merge_corepack_env(env) ⇒ Object
- .node_version ⇒ Object
- .npm_version_numeric(lockfile) ⇒ Object
- .package_manager_activate(name, version, env: {}) ⇒ Object
- .package_manager_install(name, version, env: {}) ⇒ Object
- .package_manager_run_command(name, command, fingerprint: nil, output_observer: nil, env: nil) ⇒ Object
- .package_manager_version(name, env: nil) ⇒ Object
- .parse_npm8?(package_lock) ⇒ Boolean
- .pnpm_lockfile_version(pnpm_lock) ⇒ Object
- .pnpm_version_numeric(pnpm_lock) ⇒ Object
- .run_node_command(command, fingerprint: nil) ⇒ Object
- .run_npm_command(command, fingerprint: command, env: nil) ⇒ Object
- .run_pnpm_command(command, fingerprint: nil) ⇒ Object
- .run_yarn_command(command, fingerprint: nil) ⇒ Object
- .run_yarn_commands(*commands) ⇒ Object
- .setup_yarn_berry ⇒ Object
- .yarn_4_or_higher? ⇒ Boolean
- .yarn_berry?(yarn_lock) ⇒ Boolean
- .yarn_berry_args ⇒ Object
- .yarn_berry_disable_scripts? ⇒ Boolean
- .yarn_berry_skip_build? ⇒ Boolean
- .yarn_major_version ⇒ Object
- .yarn_offline_cache? ⇒ Boolean
- .yarn_version_numeric(yarn_lock) ⇒ Object
- .yarn_zero_install? ⇒ Boolean
Class Method Details
.build_corepack_env_variables ⇒ Object
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 556 def self.build_corepack_env_variables return nil unless Dependabot::Experiments.enabled?(:enable_private_registry_for_corepack) return nil if dependency_files.nil? || credentials.nil? files = T.must(dependency_files) creds = T.must(credentials) registry_helper = RegistryHelper.new( { npmrc: files.find { |f| f.name.end_with?(".npmrc") }, yarnrc: files.find { |f| f.name.end_with?(".yarnrc") && !f.name.end_with?(".yarnrc.yml") }, yarnrc_yml: files.find { |f| f.name.end_with?(".yarnrc.yml") } }, creds ) registry_helper.find_corepack_env_variables end |
.command_observer(output) ⇒ Object
321 322 323 324 325 326 327 328 329 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 321 def self.command_observer(output) # Observe the output for specific error return {} unless output.include?("npm ERR! ERESOLVE") { gracefully_stop: true, # value must be a String reason: "NPM Resolution Error" } end |
.corepack_supported_package_manager?(name) ⇒ Boolean
594 595 596 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 594 def self.corepack_supported_package_manager?(name) SUPPORTED_COREPACK_PACKAGE_MANAGERS.include?(name) end |
.credentials ⇒ Object
38 39 40 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 38 def credentials T.cast(Thread.current[:npm_and_yarn_credentials], T.nilable(T::Array[Dependabot::Credential])) end |
.credentials=(creds) ⇒ Object
33 34 35 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 33 def credentials=(creds) Thread.current[:npm_and_yarn_credentials] = creds end |
.dependencies_with_all_versions_metadata(dependency_set) ⇒ Object
586 587 588 589 590 591 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 586 def self.(dependency_set) dependency_set.dependencies.map do |dependency| dependency.[:all_versions] = dependency_set.all_versions_for_name(dependency.name) dependency end end |
.dependency_files ⇒ Object
28 29 30 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 28 def dependency_files T.cast(Thread.current[:npm_and_yarn_dependency_files], T.nilable(T::Array[Dependabot::DependencyFile])) end |
.dependency_files=(files) ⇒ Object
23 24 25 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 23 def dependency_files=(files) Thread.current[:npm_and_yarn_dependency_files] = files end |
.fallback_to_local_version(name, env: {}) ⇒ Object
433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 433 def self.fallback_to_local_version(name, env: {}) return "Corepack does not support #{name}" unless corepack_supported_package_manager?(name) Dependabot.logger.info("Falling back to activate the currently installed version of #{name}.") # Fetch the currently installed version directly from the environment current_version = local_package_manager_version(name) Dependabot.logger.info("Activating currently installed version of #{name}: #{current_version}") # Prepare the existing version package_manager_activate(name, current_version, env: env) end |
.fetch_yarnrc_yml_value(key, default_value) ⇒ Object
144 145 146 147 148 149 150 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 144 def self.fetch_yarnrc_yml_value(key, default_value) if File.exist?(".yarnrc.yml") && (yarnrc = YAML.load_file(".yarnrc.yml")) yarnrc.fetch(key, default_value) else default_value end end |
.handle_subprocess_failure(error) ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 198 def self.handle_subprocess_failure(error) = error. if YARN_PATH_NOT_FOUND.match?() error = T.must(T.must(YARN_PATH_NOT_FOUND.match())[:error]).sub(Dir.pwd, ".") raise MisconfiguredTooling.new("Yarn", error) end if .include?("Internal Error") && .include?(".yarnrc.yml") raise MisconfiguredTooling.new("Invalid .yarnrc.yml file", ) end raise end |
.install(name, version, env: {}) ⇒ Object
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 404 def self.install(name, version, env: {}) Dependabot.logger.info("Installing \"#{name}@#{version}\"") begin # Try to activate the specified version output = package_manager_activate(name, version, env: env) # Confirm success based on the output if output.include?("immediate activation...") Dependabot.logger.info("#{name}@#{version} successfully installed.") Dependabot.logger.info("Activating currently installed version of #{name}: #{version}") else Dependabot.logger.error("Corepack installation output unexpected: #{output}") fallback_to_local_version(name, env: env) end rescue StandardError => e Dependabot.logger.error("Error activating #{name}@#{version}: #{e.}") fallback_to_local_version(name, env: env) end # Verify the installed version installed_version = package_manager_version(name, env: env) installed_version end |
.local_package_manager_version(name) ⇒ Object
480 481 482 483 484 485 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 480 def self.local_package_manager_version(name) Dependabot::SharedHelpers.run_shell_command( "#{name} -v", fingerprint: "#{name} -v" ).strip end |
.merge_corepack_env(env) ⇒ Object
547 548 549 550 551 552 553 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 547 def self.merge_corepack_env(env) corepack_env = build_corepack_env_variables return env if corepack_env.nil? || corepack_env.empty? return corepack_env if env.nil? corepack_env.merge(env) end |
.node_version ⇒ Object
332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 332 def self.node_version version = run_node_command("-v", fingerprint: "-v").strip # Validate the output format (e.g., "v20.18.1" or "20.18.1") if version.match?(/^v?\d+(\.\d+){2}$/) version.strip.delete_prefix("v") # Remove the "v" prefix if present end rescue StandardError => e Dependabot.logger.error("Error retrieving Node.js version: #{e.}") nil end |
.npm_version_numeric(lockfile) ⇒ Object
73 74 75 76 77 78 79 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 73 def self.npm_version_numeric(lockfile) detected_npm_version = detect_npm_version(lockfile) return NPM_DEFAULT_VERSION if detected_npm_version.nil? || detected_npm_version == NPM_V6 detected_npm_version end |
.package_manager_activate(name, version, env: {}) ⇒ Object
467 468 469 470 471 472 473 474 475 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 467 def self.package_manager_activate(name, version, env: {}) return "Corepack does not support #{name}" unless corepack_supported_package_manager?(name) Dependabot::SharedHelpers.run_shell_command( "corepack prepare #{name}@#{version} --activate", fingerprint: "corepack prepare <name>@<version> --activate", env: env ).strip end |
.package_manager_install(name, version, env: {}) ⇒ Object
455 456 457 458 459 460 461 462 463 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 455 def self.package_manager_install(name, version, env: {}) return "Corepack does not support #{name}" unless corepack_supported_package_manager?(name) Dependabot::SharedHelpers.run_shell_command( "corepack install #{name}@#{version} --global --cache-only", fingerprint: "corepack install <name>@<version> --global --cache-only", env: env ).strip end |
.package_manager_run_command(name, command, fingerprint: nil, output_observer: nil, env: nil) ⇒ Object
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 512 def self.package_manager_run_command( name, command, fingerprint: nil, output_observer: nil, env: nil ) full_command = "corepack #{name} #{command}" fingerprint = "corepack #{name} #{fingerprint || command}" if output_observer return Dependabot::SharedHelpers.run_shell_command( full_command, fingerprint: fingerprint, output_observer: output_observer, env: env ).strip else Dependabot::SharedHelpers.run_shell_command( full_command, fingerprint: fingerprint, env: env ) end.strip rescue StandardError => e Dependabot.logger.error("Error running package manager command: #{full_command}, Error: #{e.}") if e..match?(/Response Code.*:.*404.*\(Not Found\)/) && e..include?("The remote server failed to provide the requested resource") raise RegistryError.new(404, "The remote server failed to provide the requested resource") end raise end |
.package_manager_version(name, env: nil) ⇒ Object
489 490 491 492 493 494 495 496 497 498 499 500 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 489 def self.package_manager_version(name, env: nil) Dependabot.logger.info("Fetching version for package manager: #{name}") version = package_manager_run_command(name, "-v", env: env).strip Dependabot.logger.info("Installed version of #{name}: #{version}") version rescue StandardError => e Dependabot.logger.error("Error fetching version for package manager #{name}: #{e.}") raise end |
.parse_npm8?(package_lock) ⇒ Boolean
153 154 155 156 157 158 159 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 153 def self.parse_npm8?(package_lock) return true unless package_lock&.content detected_npm = detect_npm_version(package_lock) # For conversion reading properly from npm 6 lockfile we need to check if detected version is npm 6 detected_npm.nil? || detected_npm != NPM_V6 end |
.pnpm_lockfile_version(pnpm_lock) ⇒ Object
578 579 580 581 582 583 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 578 def self.pnpm_lockfile_version(pnpm_lock) match = T.must(pnpm_lock.content).match(/^lockfileVersion: ['"]?(?<version>[\d.]+)/) return match[:version] if match nil end |
.pnpm_version_numeric(pnpm_lock) ⇒ Object
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 125 def self.pnpm_version_numeric(pnpm_lock) lockfile_content = pnpm_lock&.content return PNPM_DEFAULT_VERSION if !lockfile_content || lockfile_content.strip.empty? pnpm_lockfile_version_str = pnpm_lockfile_version(pnpm_lock) return PNPM_FALLBACK_VERSION unless pnpm_lockfile_version_str pnpm_lockfile_version = pnpm_lockfile_version_str.to_f return PNPM_V10 if pnpm_lockfile_version >= 9.0 return PNPM_V8 if pnpm_lockfile_version >= 6.0 return PNPM_V7 if pnpm_lockfile_version >= 5.4 PNPM_FALLBACK_VERSION end |
.run_node_command(command, fingerprint: nil) ⇒ Object
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 345 def self.run_node_command(command, fingerprint: nil) full_command = "node #{command}" Dependabot.logger.info("Running node command: #{full_command}") result = Dependabot::SharedHelpers.run_shell_command( full_command, fingerprint: "node #{fingerprint || command}" ) Dependabot.logger.info("Command executed successfully: #{full_command}") result rescue StandardError => e Dependabot.logger.error("Error running node command: #{full_command}, Error: #{e.}") raise end |
.run_npm_command(command, fingerprint: command, env: nil) ⇒ Object
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 298 def self.run_npm_command(command, fingerprint: command, env: nil) merged_env = merge_corepack_env(env) if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn) package_manager_run_command( NpmPackageManager::NAME, command, fingerprint: fingerprint, output_observer: ->(output) { command_observer(output) }, env: merged_env ) else Dependabot::SharedHelpers.run_shell_command( "npm #{command}", fingerprint: "npm #{fingerprint}", output_observer: ->(output) { command_observer(output) } ) end end |
.run_pnpm_command(command, fingerprint: nil) ⇒ Object
371 372 373 374 375 376 377 378 379 380 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 371 def self.run_pnpm_command(command, fingerprint: nil) if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn) package_manager_run_command(PNPMPackageManager::NAME, command, fingerprint: fingerprint) else Dependabot::SharedHelpers.run_shell_command( "pnpm #{command}", fingerprint: "pnpm #{fingerprint || command}" ) end end |
.run_yarn_command(command, fingerprint: nil) ⇒ Object
364 365 366 367 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 364 def self.run_yarn_command(command, fingerprint: nil) setup_yarn_berry run_single_yarn_command(command, fingerprint: fingerprint) end |
.run_yarn_commands(*commands) ⇒ Object
279 280 281 282 283 284 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 279 def self.run_yarn_commands(*commands) setup_yarn_berry commands.each do |cmd, fingerprint| run_single_yarn_command(cmd, fingerprint: fingerprint) if cmd end end |
.setup_yarn_berry ⇒ Object
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 252 def self.setup_yarn_berry # Always disable immutable installs so yarn's CI detection doesn't prevent updates. run_single_yarn_command("config set enableImmutableInstalls false") # Do not generate a cache if offline cache disabled. Otherwise side effects may confuse further checks run_single_yarn_command("config set enableGlobalCache true") unless yarn_berry_skip_build? # We never want to execute postinstall scripts, either set this config or mode=skip-build must be set run_single_yarn_command("config set enableScripts false") if yarn_berry_disable_scripts? if (http_proxy = ENV.fetch("HTTP_PROXY", false)) run_single_yarn_command("config set httpProxy #{http_proxy}", fingerprint: "config set httpProxy <proxy>") end if (https_proxy = ENV.fetch("HTTPS_PROXY", false)) run_single_yarn_command("config set httpsProxy #{https_proxy}", fingerprint: "config set httpsProxy <proxy>") end return unless (ca_file_path = ENV.fetch("NODE_EXTRA_CA_CERTS", false)) if yarn_4_or_higher? run_single_yarn_command("config set httpsCaFilePath #{ca_file_path}") else run_single_yarn_command("config set caFilePath #{ca_file_path}") end end |
.yarn_4_or_higher? ⇒ Boolean
247 248 249 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 247 def self.yarn_4_or_higher? yarn_major_version >= 4 end |
.yarn_berry?(yarn_lock) ⇒ Boolean
162 163 164 165 166 167 168 169 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 162 def self.yarn_berry?(yarn_lock) return false if yarn_lock.nil? || yarn_lock.content.nil? yaml = YAML.safe_load(T.must(yarn_lock.content)) yaml.key?("__metadata") rescue StandardError false end |
.yarn_berry_args ⇒ Object
224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 224 def self.yarn_berry_args if yarn_major_version == 2 "" elsif yarn_berry_skip_build? "--mode=skip-build" else # We only want this mode if the cache is not being updated/managed # as this improperly leaves old versions in the cache "--mode=update-lockfile" end end |
.yarn_berry_disable_scripts? ⇒ Boolean
242 243 244 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 242 def self.yarn_berry_disable_scripts? yarn_major_version == YARN_V2 || !yarn_zero_install? end |
.yarn_berry_skip_build? ⇒ Boolean
237 238 239 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 237 def self.yarn_berry_skip_build? yarn_major_version >= YARN_V3 && (yarn_zero_install? || yarn_offline_cache?) end |
.yarn_major_version ⇒ Object
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 172 def self.yarn_major_version retries = 0 output = run_single_yarn_command("--version") Version.new(output).major rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e # Should never happen, can probably be removed once this settles raise "Failed to replace ENV, not sure why" if T.must(retries).positive? = e. missing_env_var_regex = %r{Environment variable not found \((?:[^)]+)\) in #{Dir.pwd}/(?<path>\S+)} if .match?(missing_env_var_regex) match = T.must(.match(missing_env_var_regex)) path = T.must(match.named_captures["path"]) File.write(path, File.read(path).gsub(/\$\{[^}-]+\}/, "")) retries = T.must(retries) + 1 retry end handle_subprocess_failure(e) end |
.yarn_offline_cache? ⇒ Boolean
218 219 220 221 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 218 def self.yarn_offline_cache? yarn_cache_dir = fetch_yarnrc_yml_value("cacheFolder", ".yarn/cache") File.exist?(yarn_cache_dir) && (fetch_yarnrc_yml_value("nodeLinker", "") == "node-modules") end |
.yarn_version_numeric(yarn_lock) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 109 def self.yarn_version_numeric(yarn_lock) lockfile_content = yarn_lock&.content return YARN_DEFAULT_VERSION if lockfile_content.nil? || lockfile_content.strip.empty? if yarn_berry?(yarn_lock) YARN_DEFAULT_VERSION else YARN_FALLBACK_VERSION end end |
.yarn_zero_install? ⇒ Boolean
213 214 215 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 213 def self.yarn_zero_install? File.exist?(".pnp.cjs") end |