dependabot-composer 0.128.0 → 0.129.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ parameters:
2
+ inferPrivatePropertyTypeFromConstructor: true
3
+ level: 5
4
+ paths:
5
+ - src
@@ -0,0 +1,67 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\Composer;
6
+
7
+ use Composer\DependencyResolver\Operation\InstallOperation;
8
+ use Composer\DependencyResolver\Operation\UninstallOperation;
9
+ use Composer\DependencyResolver\Operation\UpdateOperation;
10
+ use Composer\Installer\InstallationManager;
11
+ use Composer\Package\PackageInterface;
12
+ use Composer\Repository\RepositoryInterface;
13
+
14
+ final class DependabotInstallationManager extends InstallationManager
15
+ {
16
+ private array $installed = [];
17
+ private array $updated = [];
18
+ private array $uninstalled = [];
19
+
20
+ public function execute(RepositoryInterface $repo, array $operations, $devMode = true, $runScripts = true): void
21
+ {
22
+ foreach ($operations as $operation) {
23
+ $method = $operation->getOperationType();
24
+ // NOTE: skipping download() step
25
+ $this->$method($repo, $operation);
26
+ }
27
+ }
28
+
29
+ public function install(RepositoryInterface $repo, InstallOperation $operation): void
30
+ {
31
+ $this->installed[] = $operation->getPackage();
32
+ }
33
+
34
+ public function update(RepositoryInterface $repo, UpdateOperation $operation): void
35
+ {
36
+ $this->updated[] = [$operation->getInitialPackage(), $operation->getTargetPackage()];
37
+ }
38
+
39
+ public function uninstall(RepositoryInterface $repo, UninstallOperation $operation): void
40
+ {
41
+ $this->uninstalled[] = $operation->getPackage();
42
+ }
43
+
44
+ /**
45
+ * @return PackageInterface[]
46
+ */
47
+ public function getInstalledPackages(): array
48
+ {
49
+ return $this->installed;
50
+ }
51
+
52
+ /**
53
+ * @return PackageInterface[]
54
+ */
55
+ public function getUpdatedPackages(): array
56
+ {
57
+ return $this->updated;
58
+ }
59
+
60
+ /**
61
+ * @return PackageInterface[]
62
+ */
63
+ public function getUninstalledPackages(): array
64
+ {
65
+ return $this->uninstalled;
66
+ }
67
+ }
@@ -0,0 +1,23 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\Composer;
6
+
7
+ use Composer\Package\PackageInterface;
8
+ use Composer\Plugin\PluginManager;
9
+
10
+ final class DependabotPluginManager extends PluginManager
11
+ {
12
+ public function registerPackage(PackageInterface $package, $failOnMissingClasses = false, $isGlobalPlugin = false): void
13
+ {
14
+ // This package does some setup for PHP_CodeSniffer, but errors out the
15
+ // install if Symfony isn't installed (which it won't be for a lockfile
16
+ // only install run). Safe to ignore
17
+ if (strpos($package->getName(), 'phpcodesniffer') !== false) {
18
+ return;
19
+ }
20
+
21
+ parent::registerPackage($package, $failOnMissingClasses, $isGlobalPlugin);
22
+ }
23
+ }
@@ -0,0 +1,25 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\Composer;
6
+
7
+ use Composer\IO\NullIO;
8
+
9
+ final class ExceptionIO extends NullIO
10
+ {
11
+ private bool $raise_next_error = false;
12
+
13
+ public function writeError($messages, $newline = true, $verbosity = self::NORMAL): void
14
+ {
15
+ if (is_array($messages)) {
16
+ return;
17
+ }
18
+ if ($this->raise_next_error) {
19
+ throw new \RuntimeException('Your requirements could not be resolved to an installable set of packages.' . $messages);
20
+ }
21
+ if (strpos($messages, 'Your requirements could not be resolved') !== false) {
22
+ $this->raise_next_error = true;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,28 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\Composer;
6
+
7
+ use Composer\Package\Locker;
8
+
9
+ final class Hasher
10
+ {
11
+ /**
12
+ * @throws \RuntimeException
13
+ */
14
+ public static function getContentHash(array $args): string
15
+ {
16
+ [$workingDirectory] = $args;
17
+
18
+ $config = $workingDirectory . '/composer.json';
19
+
20
+ $contents = file_get_contents($config);
21
+
22
+ if (!is_string($contents)) {
23
+ throw new \RuntimeException(sprintf('Failed to load contents of "%s".', $config));
24
+ }
25
+
26
+ return Locker::getContentHash($contents);
27
+ }
28
+ }
@@ -0,0 +1,133 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\Composer;
6
+
7
+ use Composer\DependencyResolver\Request;
8
+ use Composer\Factory;
9
+ use Composer\Installer;
10
+ use Composer\Package\PackageInterface;
11
+ use Composer\Util\Filesystem;
12
+
13
+ final class UpdateChecker
14
+ {
15
+ public static function getLatestResolvableVersion(array $args): ?string
16
+ {
17
+ [$workingDirectory, $dependencyName, $gitCredentials, $registryCredentials] = $args;
18
+
19
+ $httpBasicCredentials = [];
20
+
21
+ foreach ($gitCredentials as $credentials) {
22
+ $httpBasicCredentials[$credentials['host']] = [
23
+ 'username' => $credentials['username'],
24
+ 'password' => $credentials['password'],
25
+ ];
26
+ }
27
+
28
+ foreach ($registryCredentials as $credentials) {
29
+ $httpBasicCredentials[$credentials['registry']] = [
30
+ 'username' => $credentials['username'],
31
+ 'password' => $credentials['password'],
32
+ ];
33
+ }
34
+
35
+ $io = new ExceptionIO();
36
+
37
+ $composer = Factory::create($io, $workingDirectory . '/composer.json');
38
+
39
+ $config = $composer->getConfig();
40
+
41
+ if (0 < count($httpBasicCredentials)) {
42
+ $config->merge([
43
+ 'config' => [
44
+ 'http-basic' => $httpBasicCredentials,
45
+ ],
46
+ ]);
47
+
48
+ $io->loadConfiguration($config);
49
+ }
50
+
51
+ $installationManager = new DependabotInstallationManager($composer->getLoop(), $io);
52
+
53
+ $fs = new Filesystem(null);
54
+ $binaryInstaller = new Installer\BinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fs);
55
+
56
+ $installationManager->addInstaller(new Installer\LibraryInstaller($io, $composer, null, $fs, $binaryInstaller));
57
+ $installationManager->addInstaller(new Installer\PluginInstaller($io, $composer, $fs, $binaryInstaller));
58
+ $installationManager->addInstaller(new Installer\MetapackageInstaller($io));
59
+
60
+ $install = new Installer(
61
+ $io,
62
+ $config,
63
+ $composer->getPackage(),
64
+ $composer->getDownloadManager(),
65
+ $composer->getRepositoryManager(),
66
+ $composer->getLocker(),
67
+ $installationManager,
68
+ $composer->getEventDispatcher(),
69
+ $composer->getAutoloadGenerator()
70
+ );
71
+
72
+ // For all potential options, see UpdateCommand in composer
73
+ $install
74
+ ->setUpdate(true)
75
+ ->setDevMode(true)
76
+ ->setUpdateAllowTransitiveDependencies(Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS)
77
+ ->setDumpAutoloader(false)
78
+ ->setRunScripts(false)
79
+ ->setIgnorePlatformRequirements(false);
80
+
81
+ // if no lock is present, we do not do a partial update as
82
+ // this is not supported by the Installer
83
+ if ($composer->getLocker()->isLocked()) {
84
+ $install->setUpdateAllowList([$dependencyName]);
85
+ }
86
+
87
+ $install->run();
88
+
89
+ $installedPackages = $installationManager->getInstalledPackages();
90
+
91
+ $updatedPackage = current(array_filter($installedPackages, static function (PackageInterface $package) use ($dependencyName): bool {
92
+ return $package->getName() === $dependencyName;
93
+ }));
94
+
95
+ // We found the package in the list of updated packages. Return its version.
96
+ if ($updatedPackage instanceof PackageInterface) {
97
+ return preg_replace('/^([v])/', '', $updatedPackage->getPrettyVersion());
98
+ }
99
+
100
+ // We didn't find the package in the list of updated packages. Check if
101
+ // it was replaced by another package (in which case we can ignore).
102
+ foreach ($composer->getPackage()->getReplaces() as $link) {
103
+ if ($link->getTarget() === $dependencyName) {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ foreach ($installedPackages as $package) {
109
+ foreach ($package->getReplaces() as $link) {
110
+ if ($link->getTarget() === $dependencyName) {
111
+ return null;
112
+ }
113
+ }
114
+ }
115
+
116
+ // Similarly, check if the package was provided by any other package.
117
+ foreach ($composer->getPackage()->getProvides() as $link) {
118
+ if ($link->getTarget() === $dependencyName) {
119
+ return preg_replace('/^([v])/', '', $link->getPrettyConstraint());
120
+ }
121
+ }
122
+
123
+ foreach ($installedPackages as $package) {
124
+ foreach ($package->getProvides() as $link) {
125
+ if ($link->getTarget() === $dependencyName) {
126
+ return preg_replace('/^([v])/', '', $link->getPrettyConstraint());
127
+ }
128
+ }
129
+ }
130
+
131
+ throw new \RuntimeException('Package not found in updated packages!');
132
+ }
133
+ }
@@ -0,0 +1,99 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\Composer;
6
+
7
+ use Composer\DependencyResolver\Request;
8
+ use Composer\Factory;
9
+ use Composer\Installer;
10
+
11
+ final class Updater
12
+ {
13
+ /**
14
+ * @throws \RuntimeException
15
+ */
16
+ public static function update(array $args): array
17
+ {
18
+ [$workingDirectory, $dependencyName, $dependencyVersion, $gitCredentials, $registryCredentials] = $args;
19
+
20
+ // Change working directory to the one provided, this ensures that we
21
+ // install dependencies into the working dir, rather than a vendor folder
22
+ // in the root of the project
23
+ $originalDir = getcwd();
24
+
25
+ if (!is_string($originalDir)) {
26
+ throw new \RuntimeException('Failed determining the current working directory.');
27
+ }
28
+
29
+ chdir($workingDirectory);
30
+
31
+ $io = new ExceptionIO();
32
+ $composer = Factory::create($io);
33
+ $config = $composer->getConfig();
34
+ $httpBasicCredentials = [];
35
+
36
+ $pm = new DependabotPluginManager($io, $composer, null, false);
37
+ $composer->setPluginManager($pm);
38
+ $pm->loadInstalledPlugins();
39
+
40
+ foreach ($gitCredentials as &$cred) {
41
+ $httpBasicCredentials[$cred['host']] = [
42
+ 'username' => $cred['username'],
43
+ 'password' => $cred['password'],
44
+ ];
45
+ }
46
+
47
+ foreach ($registryCredentials as &$cred) {
48
+ $httpBasicCredentials[$cred['registry']] = [
49
+ 'username' => $cred['username'],
50
+ 'password' => $cred['password'],
51
+ ];
52
+ }
53
+
54
+ if ($httpBasicCredentials) {
55
+ $config->merge(
56
+ [
57
+ 'config' => [
58
+ 'http-basic' => $httpBasicCredentials,
59
+ ],
60
+ ]
61
+ );
62
+ $io->loadConfiguration($config);
63
+ }
64
+
65
+ $install = new Installer(
66
+ $io,
67
+ $config,
68
+ $composer->getPackage(),
69
+ $composer->getDownloadManager(),
70
+ $composer->getRepositoryManager(),
71
+ $composer->getLocker(),
72
+ $composer->getInstallationManager(),
73
+ $composer->getEventDispatcher(),
74
+ $composer->getAutoloadGenerator()
75
+ );
76
+
77
+ // For all potential options, see UpdateCommand in composer
78
+ $install
79
+ ->setWriteLock(true)
80
+ ->setUpdate(true)
81
+ ->setDevMode(true)
82
+ ->setUpdateAllowList([$dependencyName])
83
+ ->setUpdateAllowTransitiveDependencies(Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS)
84
+ ->setExecuteOperations(true)
85
+ ->setDumpAutoloader(false)
86
+ ->setRunScripts(false)
87
+ ->setIgnorePlatformRequirements(false);
88
+
89
+ $install->run();
90
+
91
+ $result = [
92
+ 'composer.lock' => file_get_contents('composer.lock'),
93
+ ];
94
+
95
+ chdir($originalDir);
96
+
97
+ return $result;
98
+ }
99
+ }
@@ -7,6 +7,7 @@ require "dependabot/composer/file_updater"
7
7
  require "dependabot/composer/version"
8
8
  require "dependabot/composer/requirement"
9
9
  require "dependabot/composer/native_helpers"
10
+ require "dependabot/composer/helpers"
10
11
 
11
12
  # rubocop:disable Metrics/ClassLength
12
13
  module Dependabot
@@ -34,6 +35,7 @@ module Dependabot
34
35
  (?<!with|for|by)\sext\-[^\s/]+\s.*?\s(?=->)|
35
36
  (?<=requires\s)php(?:\-[^\s/]+)?\s.*?\s(?=->)
36
37
  }x.freeze
38
+ MISSING_ENV_VAR_REGEX = /Environment variable '(?<env_var>.[^']+)' is not set/.freeze
37
39
 
38
40
  def initialize(dependencies:, dependency_files:, credentials:)
39
41
  @dependencies = dependencies
@@ -179,10 +181,21 @@ module Dependabot
179
181
  raise GitDependenciesNotReachable, dependency_url
180
182
  end
181
183
 
182
- if error.message.start_with?("Could not find a key for ACF PRO")
184
+ # NOTE: This matches an error message from composer plugins used to install ACF PRO
185
+ # https://github.com/PhilippBaschke/acf-pro-installer/blob/772cec99c6ef8bc67ba6768419014cc60d141b27/src/ACFProInstaller/Exceptions/MissingKeyException.php#L14
186
+ # https://github.com/pivvenit/acf-pro-installer/blob/f2d4812839ee2c333709b0ad4c6c134e4c25fd6d/src/Exceptions/MissingKeyException.php#L25
187
+ if error.message.start_with?("Could not find a key for ACF PRO") ||
188
+ error.message.start_with?("Could not find a license key for ACF PRO")
183
189
  raise MissingEnvironmentVariable, "ACF_PRO_KEY"
184
190
  end
185
191
 
192
+ # NOTE: This matches error output from a composer plugin (private-composer-installer):
193
+ # https://github.com/ffraenz/private-composer-installer/blob/8655e3da4e8f99203f13ccca33b9ab953ad30a31/src/Exception/MissingEnvException.php#L22
194
+ if error.message.match?(MISSING_ENV_VAR_REGEX)
195
+ env_var = error.message.match(MISSING_ENV_VAR_REGEX).named_captures.fetch("env_var")
196
+ raise MissingEnvironmentVariable, env_var
197
+ end
198
+
186
199
  if error.message.start_with?("Unknown downloader type: npm-sign") ||
187
200
  error.message.include?("file could not be downloaded") ||
188
201
  error.message.include?("configuration does not allow connect")
@@ -197,6 +210,7 @@ module Dependabot
197
210
  raise PrivateSourceAuthenticationFailure, source
198
211
  end
199
212
 
213
+ # NOTE: This error is raised by composer v1
200
214
  if error.message.include?("Argument 1 passed to Composer")
201
215
  msg = "One of your Composer plugins is not compatible with the "\
202
216
  "latest version of Composer. Please update Composer and "\
@@ -204,6 +218,12 @@ module Dependabot
204
218
  raise DependencyFileNotResolvable, msg
205
219
  end
206
220
 
221
+ # NOTE: This error is raised by composer v2 and includes helpful
222
+ # information about which plugins or dependencies are not compatible
223
+ if error.message.include?("Your requirements could not be resolved")
224
+ raise DependencyFileNotResolvable, error.message
225
+ end
226
+
207
227
  raise error
208
228
  end
209
229
  # rubocop:enable Metrics/AbcSize
@@ -425,7 +445,11 @@ module Dependabot
425
445
  end
426
446
 
427
447
  def php_helper_path
428
- NativeHelpers.composer_helper_path
448
+ NativeHelpers.composer_helper_path(composer_version: composer_version)
449
+ end
450
+
451
+ def composer_version
452
+ @composer_version ||= Helpers.composer_version(parsed_composer_json, parsed_lockfile)
429
453
  end
430
454
 
431
455
  def credentials_env