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) ⇒ 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) ⇒ 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
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 552 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
590 591 592 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 590 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
582 583 584 585 586 587 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 582 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) ⇒ 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) 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) 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) end rescue StandardError => e Dependabot.logger.error("Error activating #{name}@#{version}: #{e.}") fallback_to_local_version(name) end # Verify the installed version installed_version = package_manager_version(name) 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
543 544 545 546 547 548 549 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 543 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 |
# 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) 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) ⇒ 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) Dependabot.logger.info("Fetching version for package manager: #{name}") version = package_manager_run_command(name, "-v").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
574 575 576 577 578 579 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 574 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 |