Module: Dependabot::Composer::Helpers

Extended by:
T::Sig
Defined in:
lib/dependabot/composer/helpers.rb

Constant Summary collapse

V1 =
T.let("1", String)
V2 =
T.let("2", String)
DEFAULT =

If we are updating a project with no lock file then the default should be the newest version

T.let(V2, String)
COMPOSER_V2_NAME_REGEX =

From composers json-schema: getcomposer.org/schema.json

T.let(
  %r{^[a-z0-9]([_.-]?[a-z0-9]++)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]++)*$},
  Regexp
)
PLATFORM_PACKAGE_REGEX =
T.let(
  /
  ^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*
  |composer-(?:plugin|runtime)-api)$
  /x,
  Regexp
)
FAILED_GIT_CLONE_WITH_MIRROR =
T.let(
  /^Failed to execute git clone --(mirror|checkout)[^']*'(?<url>[^']*?)'/,
  Regexp
)
FAILED_GIT_CLONE =
T.let(/^Failed to clone (?<url>.*?)/, Regexp)
GIT_REPO_URL =
T.let(
  %r{((git|ssh|http(s)?)|(git@[\w\.]+))(:(//)?)([\w\.@\:/\-~]+)(/)?},
  Regexp
)

Class Method Summary collapse

Class Method Details

.capture_platform(parsed_composer_json, name) ⇒ Object



154
155
156
# File 'lib/dependabot/composer/helpers.rb', line 154

def self.capture_platform(parsed_composer_json, name)
  parsed_composer_json.dig(PackageManager::CONFIG_KEY, PackageManager::PLATFORM_KEY, name)
end

.capture_platform_php(parsed_composer_json) ⇒ Object



148
149
150
# File 'lib/dependabot/composer/helpers.rb', line 148

def self.capture_platform_php(parsed_composer_json)
  capture_platform(parsed_composer_json, Language::NAME)
end

.capture_version(output, regex) ⇒ Object



141
142
143
144
# File 'lib/dependabot/composer/helpers.rb', line 141

def self.capture_version(output, regex)
  match = output.match(regex)
  match&.named_captures&.fetch("version", nil)
end

.composer_version(composer_json, parsed_lockfile = nil) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/dependabot/composer/helpers.rb', line 50

def self.composer_version(composer_json, parsed_lockfile = nil)
  # If the parsed lockfile has a plugin API version, we return either V1 or V2
  # based on the major version of the lockfile.
  if parsed_lockfile && parsed_lockfile[PackageManager::PLUGIN_API_VERSION_KEY]
    version = Composer::Version.new(parsed_lockfile[PackageManager::PLUGIN_API_VERSION_KEY])
    major_version = version.canonical_segments.first

    return major_version.nil? || major_version > 1 ? V2 : V1
  end

  # Check if the composer name does not follow the Composer V2 naming conventions.
  # This happens if "name" is present in composer.json but doesn't match the required pattern.
  composer_name_invalid = composer_json["name"] && composer_json["name"] !~ COMPOSER_V2_NAME_REGEX

  # If the name is invalid returns the fallback version.
  return V2 if composer_name_invalid

  # Check if the composer.json file contains "require" entries that don't follow
  # either the platform package naming conventions or the Composer V2 name conventions.
  invalid_v2 = invalid_v2_requirement?(composer_json)

  # If there are invalid requirements returns fallback version.
  return V2 if invalid_v2

  # If no conditions are met return V2 by default.
  V2
end

.dependency_constraint(parsed_composer_json, name) ⇒ Object



166
167
168
# File 'lib/dependabot/composer/helpers.rb', line 166

def self.dependency_constraint(parsed_composer_json, name)
  parsed_composer_json.dig(PackageManager::REQUIRE_KEY, name)
end

.dependency_url_from_git_clone_error(message) ⇒ Object



79
80
81
82
# File 'lib/dependabot/composer/helpers.rb', line 79

def self.dependency_url_from_git_clone_error(message)
  extract_and_clean_dependency_url(message, FAILED_GIT_CLONE_WITH_MIRROR) ||
    extract_and_clean_dependency_url(message, FAILED_GIT_CLONE)
end

.extract_and_clean_dependency_url(message, regex) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/dependabot/composer/helpers.rb', line 85

def self.extract_and_clean_dependency_url(message, regex)
  if message.match(regex)
    dependency_url = message[GIT_REPO_URL]
    if dependency_url.nil? || dependency_url.empty?
      raise "Could not parse dependency_url from git clone error: #{message}"
    end

    return clean_dependency_url(dependency_url)
  end
  nil
end

.fetch_composer_and_php_versionsObject



125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/dependabot/composer/helpers.rb', line 125

def self.fetch_composer_and_php_versions
  output = package_manager_run_command("--version").strip

  composer_version = capture_version(output, /Composer version (?<version>\d+\.\d+\.\d+)/)
  php_version = capture_version(output, /PHP version (?<version>\d+\.\d+\.\d+)/)

  Dependabot.logger.info("Dependabot running with Composer version: #{composer_version}")
  Dependabot.logger.info("Dependabot running with PHP version: #{php_version}")

  { composer: composer_version, php: php_version }
rescue StandardError => e
  Dependabot.logger.error("Error fetching versions for package manager and language #{name}: #{e.message}")
  {}
end

.package_manager_run_command(command, fingerprint: nil) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/dependabot/composer/helpers.rb', line 99

def self.package_manager_run_command(command, fingerprint: nil)
  full_command = "composer #{command}"

  Dependabot.logger.info("Running composer command: #{full_command}")

  result = Dependabot::SharedHelpers.run_shell_command(
    full_command,
    fingerprint: "composer #{fingerprint || command}"
  ).strip

  Dependabot.logger.info("Command executed successfully: #{full_command}")
  result
rescue StandardError => e
  Dependabot.logger.error("Error running composer command: #{full_command}, Error: #{e.message}")
  raise
end

.php_constraint(parsed_composer_json) ⇒ Object



160
161
162
# File 'lib/dependabot/composer/helpers.rb', line 160

def self.php_constraint(parsed_composer_json)
  dependency_constraint(parsed_composer_json, Language::NAME)
end