Class: Dependabot::NpmAndYarn::PackageManagerHelper

Inherits:
Object
  • Object
show all
Extended by:
T::Helpers, T::Sig
Defined in:
lib/dependabot/npm_and_yarn/package_manager.rb

Instance Method Summary collapse

Constructor Details

#initialize(package_json, lockfiles, registry_config_files, credentials) ⇒ PackageManagerHelper

Returns a new instance of PackageManagerHelper.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 154

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))
end

Instance Method Details

#detect_version(name) ⇒ Object



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 360

def detect_version(name)
  # Prioritize version mentioned in "packageManager" instead of "engines"
  if @manifest_package_manager&.start_with?("#{name}@")
    detected_version = @manifest_package_manager.split("@").last.to_s
  end

  # If "packageManager" has no version specified, check if we can extract "engines" information
  detected_version ||= check_engine_version(name) if detected_version.to_s.empty?

  # If neither "packageManager" nor "engines" have versions, infer version from lockfileVersion
  detected_version ||= guessed_version(name) if detected_version.to_s.empty?

  # Strip and validate version format
  detected_version_string = detected_version.to_s.strip

  # Ensure detected_version is neither "0" nor invalid format
  return if detected_version_string == "0" || !detected_version_string.match?(ConstraintHelper::VERSION_REGEX)

  detected_version_string
end

#find_engine_constraints_as_requirement(name) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 193

def find_engine_constraints_as_requirement(name)
  Dependabot.logger.info("Processing engine constraints for #{name}")

  raw_constraint = raw_engine_constraint(name)
  return nil if raw_constraint.empty?

  constraint_groups = parse_constraint_groups(raw_constraint)
  if constraint_groups.nil?
    Dependabot.logger.warn(
      "Unrecognized constraint format for #{name}: #{raw_constraint}"
    )
    return nil
  end

  # A wildcard/latest branch translates to no constraints, which means
  # there is effectively no engine requirement.
  return nil if constraint_groups.any?(&:empty?)

  constraint_groups = constraint_groups.reject(&:empty?)

  return nil if constraint_groups.empty?

  parsed_constraints = constraint_groups.map { |group| group.join(" ") }.join(" || ")
  Dependabot.logger.info("Parsed constraints for #{name}: #{parsed_constraints}")

  requirement_for_group(constraint_groups, name)
rescue StandardError => e
  Dependabot.logger.error("Error processing constraints for #{name}: #{e.message}")
  nil
end

#installed_version(name) ⇒ Object



427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 427

def installed_version(name)
  # Return the memoized version if it has already been computed
  return T.must(@installed_versions[name]) if @installed_versions.key?(name)

  # Attempt to get the installed version through the package manager version command
  @installed_versions[name] = Helpers.package_manager_version(name)

  # If we can't get the installed version, we need to install the package manager and get the version
  unless @installed_versions[name]&.match?(PACKAGE_MANAGER_VERSION_REGEX)
    setup(name)
    @installed_versions[name] = Helpers.package_manager_version(name)
  end

  # If we can't get the installed version or the version is invalid, we need to get inferred version
  unless @installed_versions[name]&.match?(PACKAGE_MANAGER_VERSION_REGEX)
    @installed_versions[name] = Helpers.public_send(:"#{name}_version_numeric", @lockfiles[name.to_sym]).to_s
  end

  T.must(@installed_versions[name])
end

#languageObject



180
181
182
183
184
185
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 180

def language
  @language ||= Language.new(
    raw_version: Helpers.node_version,
    requirement: language_requirement
  )
end

#language_requirementObject



188
189
190
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 188

def language_requirement
  @language_requirement ||= find_engine_constraints_as_requirement(Language::NAME)
end

#package_managerObject



173
174
175
176
177
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 173

def package_manager
  package_manager_by_name(
    @package_manager_detector.detect_package_manager
  )
end

#package_manager_by_name(name) ⇒ Object



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 382

def package_manager_by_name(name)
  Dependabot.logger.info("Resolving package manager for: #{name || 'default'}")

  name = ensure_valid_package_manager(name)
  package_manager_class = T.must(PACKAGE_MANAGER_CLASSES[name])

  detected_version = detect_version(name)

  # if we have a detected version, we check if it is deprecated or unsupported
  if detected_version
    package_manager = package_manager_class.new(
      detected_version: detected_version.to_s
    )
    return package_manager if package_manager.deprecated? || package_manager.unsupported?
  end

  installed_version = installed_version(name)
  Dependabot.logger.info("Installed version for #{name}: #{installed_version}")

  package_manager_requirement = find_engine_constraints_as_requirement(name)
  if package_manager_requirement
    Dependabot.logger.info("Version requirement for #{name}: #{package_manager_requirement}")
  else
    Dependabot.logger.info("No version requirement found for #{name}")
  end

  package_manager_class.new(
    detected_version: detected_version,
    raw_version: installed_version,
    requirement: package_manager_requirement
  )
rescue ArgumentError => e
  raise DependencyFileNotParseable, e.message if e.message.include?(ERROR_MALFORMED_VERSION_NUMBER)

  raise
rescue StandardError => e
  Dependabot.logger.error("Error resolving package manager for #{name || 'default'}: #{e.message}")
  raise
end

#setup(name) ⇒ Object



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 296

def setup(name)
  # we prioritize version mentioned in "packageManager" instead of "engines"
  # i.e. if { engines : "pnpm" : "6" } and { packageManager: "pnpm@6.0.2" },
  # we go for the specificity mentioned in packageManager (6.0.2)

  unless @manifest_package_manager&.start_with?("#{name}@") ||
         (@manifest_package_manager&.==name.to_s) ||
         @manifest_package_manager.nil?
    return
  end

  return package_manager.version.to_s if package_manager.deprecated? || package_manager.unsupported?

  if @engines && @manifest_package_manager.nil?
    # if "packageManager" doesn't exists in manifest file,
    # we check if we can extract "engines" information
    version = check_engine_version(name)

  elsif @manifest_package_manager&.==name.to_s
    # if "packageManager" is found but no version is specified (i.e. pnpm@1.2.3),
    # we check if we can get "engines" info to override default version
    version = check_engine_version(name) if @engines

  elsif @manifest_package_manager&.start_with?("#{name}@")
    # if "packageManager" info has version specification i.e. yarn@3.3.1
    # we go with the version in "packageManager"
    Dependabot.logger.info(
      "Found \"#{MANIFEST_PACKAGE_MANAGER_KEY}\" : \"#{@manifest_package_manager}\". " \
      "Skipped checking \"#{MANIFEST_ENGINES_KEY}\"."
    )
  end

  if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
    version ||= requested_version(name) || guessed_version(name)

    if version
      raise_if_unsupported!(name, version.to_s)
      install(name, version.to_s)
    end
  else
    version ||= requested_version(name)

    if version
      raise_if_unsupported!(name, version.to_s)

      install(name, version)
    else
      version = guessed_version(name)

      if version
        raise_if_unsupported!(name, version.to_s)

        install(name, version.to_s) if name == PNPMPackageManager::NAME
      end
    end
  end
  version
end