Module: Dependabot::NpmAndYarn::Helpers
- Extended by:
- T::Sig
- Defined in:
- lib/dependabot/npm_and_yarn/helpers.rb
Constant Summary collapse
- YARN_PATH_NOT_FOUND =
/^.*(?<error>The "yarn-path" option has been set \(in [^)]+\), but the specified location doesn't exist)/
- NPM_V8 =
NPM Version Constants
8
- NPM_V6 =
6
- NPM_DEFAULT_VERSION =
NPM_V8
- PNPM_V9 =
PNPM Version Constants
9
- PNPM_V8 =
8
- PNPM_V7 =
7
- PNPM_V6 =
6
- PNPM_DEFAULT_VERSION =
PNPM_V9
- 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
Class Method Summary collapse
- .dependencies_with_all_versions_metadata(dependency_set) ⇒ Object
- .fetch_yarnrc_yml_value(key, default_value) ⇒ Object
- .handle_subprocess_failure(error) ⇒ Object
- .npm8?(package_lock) ⇒ Boolean
- .npm_version_numeric(lockfile) ⇒ Object
- .npm_version_numeric_npm6_or_higher(lockfile) ⇒ Object
- .npm_version_numeric_npm8_or_higher(lockfile) ⇒ Object
- .pnpm_lockfile_version(pnpm_lock) ⇒ Object
- .pnpm_version_numeric(pnpm_lock) ⇒ Object
-
.run_npm_command(command, fingerprint: command) ⇒ Object
Run single npm command returning stdout/stderr.
-
.run_pnpm_command(command, fingerprint: nil) ⇒ Object
Run single pnpm command returning stdout/stderr.
-
.run_yarn_command(command, fingerprint: nil) ⇒ Object
Setup yarn and run a single yarn command returning stdout/stderr.
-
.run_yarn_commands(*commands) ⇒ Object
Run any number of yarn commands while ensuring that ‘enableScripts` is set to false.
- .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
.dependencies_with_all_versions_metadata(dependency_set) ⇒ Object
276 277 278 279 280 281 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 276 def self.(dependency_set) dependency_set.dependencies.map do |dependency| dependency.[:all_versions] = dependency_set.all_versions_for_name(dependency.name) dependency end end |
.fetch_yarnrc_yml_value(key, default_value) ⇒ Object
110 111 112 113 114 115 116 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 110 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
161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 161 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 |
.npm8?(package_lock) ⇒ Boolean
119 120 121 122 123 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 119 def self.npm8?(package_lock) return true unless package_lock npm_version_numeric(package_lock) == NPM_V8 end |
.npm_version_numeric(lockfile) ⇒ Object
41 42 43 44 45 46 47 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 41 def self.npm_version_numeric(lockfile) fallback_version_npm8 = Dependabot::Experiments.enabled?(:npm_fallback_version_above_v6) return npm_version_numeric_npm8_or_higher(lockfile) if fallback_version_npm8 npm_version_numeric_npm6_or_higher(lockfile) end |
.npm_version_numeric_npm6_or_higher(lockfile) ⇒ Object
50 51 52 53 54 55 56 57 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 50 def self.npm_version_numeric_npm6_or_higher(lockfile) lockfile_content = T.must(lockfile.content) return NPM_V8 if JSON.parse(lockfile_content)["lockfileVersion"].to_i >= 2 NPM_V6 rescue JSON::ParserError NPM_V6 end |
.npm_version_numeric_npm8_or_higher(lockfile) ⇒ Object
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 64 def self.npm_version_numeric_npm8_or_higher(lockfile) lockfile_content = lockfile.content # Return default NPM version if there's no lockfile or it's empty return NPM_DEFAULT_VERSION if lockfile_content.nil? || lockfile_content.strip.empty? parsed_lockfile = JSON.parse(lockfile_content) lockfile_version_str = parsed_lockfile["lockfileVersion"] # Default to npm default version if lockfileVersion is missing or empty return NPM_DEFAULT_VERSION if lockfile_version_str.nil? || lockfile_version_str.to_s.strip.empty? lockfile_version = lockfile_version_str.to_i # Using npm 8 as the default for lockfile_version > 2. # Update needed to support npm 9+ based on lockfile version. return NPM_V8 if lockfile_version >= 2 NPM_DEFAULT_VERSION rescue JSON::ParserError NPM_DEFAULT_VERSION # Fallback to default npm version if parsing fails end |
.pnpm_lockfile_version(pnpm_lock) ⇒ Object
271 272 273 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 271 def self.pnpm_lockfile_version(pnpm_lock) pnpm_lock.content.match(/^lockfileVersion: ['"]?(?<version>[\d.]+)/)[:version] end |
.pnpm_version_numeric(pnpm_lock) ⇒ Object
101 102 103 104 105 106 107 108 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 101 def self.pnpm_version_numeric(pnpm_lock) pnpm_lockfile_version = pnpm_lockfile_version(pnpm_lock).to_f return PNPM_V9 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_npm_command(command, fingerprint: command) ⇒ Object
Run single npm command returning stdout/stderr.
NOTE: Needs to be explicitly run through corepack to respect the ‘packageManager` setting in `package.json`, because corepack does not add shims for NPM.
250 251 252 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 250 def self.run_npm_command(command, fingerprint: command) SharedHelpers.run_shell_command("corepack npm #{command}", fingerprint: "corepack npm #{fingerprint}") end |
.run_pnpm_command(command, fingerprint: nil) ⇒ Object
Run single pnpm command returning stdout/stderr
261 262 263 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 261 def self.run_pnpm_command(command, fingerprint: nil) SharedHelpers.run_shell_command("pnpm #{command}", fingerprint: "pnpm #{fingerprint || command}") end |
.run_yarn_command(command, fingerprint: nil) ⇒ Object
Setup yarn and run a single yarn command returning stdout/stderr
255 256 257 258 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 255 def self.run_yarn_command(command, fingerprint: nil) setup_yarn_berry run_single_yarn_command(command, fingerprint: fingerprint) end |
.run_yarn_commands(*commands) ⇒ Object
Run any number of yarn commands while ensuring that ‘enableScripts` is set to false. Yarn commands should not be ran outside of this helper to ensure that postinstall scripts are never executed, as they could contain malicious code.
240 241 242 243 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 240 def self.run_yarn_commands(*commands) setup_yarn_berry commands.each { |cmd, fingerprint| run_single_yarn_command(cmd, fingerprint: fingerprint) } end |
.setup_yarn_berry ⇒ Object
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 214 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
210 211 212 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 210 def self.yarn_4_or_higher? yarn_major_version >= 4 end |
.yarn_berry?(yarn_lock) ⇒ Boolean
126 127 128 129 130 131 132 133 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 126 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
187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 187 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
205 206 207 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 205 def self.yarn_berry_disable_scripts? yarn_major_version == YARN_V2 || !yarn_zero_install? end |
.yarn_berry_skip_build? ⇒ Boolean
200 201 202 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 200 def self.yarn_berry_skip_build? yarn_major_version >= YARN_V3 && (yarn_zero_install? || yarn_offline_cache?) end |
.yarn_major_version ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 136 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
181 182 183 184 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 181 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
89 90 91 92 93 94 95 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 89 def self.yarn_version_numeric(yarn_lock) if yarn_berry?(yarn_lock) YARN_DEFAULT_VERSION else YARN_FALLBACK_VERSION end end |
.yarn_zero_install? ⇒ Boolean
176 177 178 |
# File 'lib/dependabot/npm_and_yarn/helpers.rb', line 176 def self.yarn_zero_install? File.exist?(".pnp.cjs") end |