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



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 309

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



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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 195

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

  return nil unless @engines.is_a?(Hash) && @engines[name]

  raw_constraint = @engines[name].to_s.strip
  return nil if raw_constraint.empty?

  if Dependabot::Experiments.enabled?(:enable_engine_version_detection)
    constraints = ConstraintHelper.extract_ruby_constraints(raw_constraint)
    # When constraints are invalid we return constraints array nil
    if constraints.nil?
      Dependabot.logger.warn(
        "Unrecognized constraint format for #{name}: #{raw_constraint}"
      )
    end
  else
    raw_constraints = raw_constraint.split
    constraints = raw_constraints.map do |constraint|
      case constraint
      when /^\d+$/
        ">=#{constraint}.0.0 <#{constraint.to_i + 1}.0.0"
      when /^\d+\.\d+$/
        ">=#{constraint} <#{constraint.split('.').first.to_i + 1}.0.0"
      when /^\d+\.\d+\.\d+$/
        "=#{constraint}"
      else
        Dependabot.logger.warn("Unrecognized constraint format for #{name}: #{constraint}")
        constraint
      end
    end

  end

  if constraints && !constraints.empty?
    Dependabot.logger.info("Parsed constraints for #{name}: #{constraints.join(', ')}")
    Requirement.new(constraints)
  end
rescue StandardError => e
  Dependabot.logger.error("Error processing constraints for #{name}: #{e.message}")
  nil
end

#installed_version(name) ⇒ Object



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 376

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



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 331

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



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/dependabot/npm_and_yarn/package_manager.rb', line 245

def setup(name)
  # we prioritize version mentioned in "packageManager" instead of "engines"
  # i.e. if { engines : "pnpm" : "6" } and { packageManager: "[email protected]" },
  # 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. [email protected]),
    # 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. [email protected]
    # 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