dependabot-composer 0.128.2 → 0.129.0

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
+ }
@@ -34,6 +34,7 @@ module Dependabot
34
34
  (?<!with|for|by)\sext\-[^\s/]+\s.*?\s(?=->)|
35
35
  (?<=requires\s)php(?:\-[^\s/]+)?\s.*?\s(?=->)
36
36
  }x.freeze
37
+ MISSING_ENV_VAR_REGEX = /Environment variable '(?<env_var>.[^']+)' is not set/.freeze
37
38
 
38
39
  def initialize(dependencies:, dependency_files:, credentials:)
39
40
  @dependencies = dependencies
@@ -179,10 +180,21 @@ module Dependabot
179
180
  raise GitDependenciesNotReachable, dependency_url
180
181
  end
181
182
 
182
- if error.message.start_with?("Could not find a key for ACF PRO")
183
+ # NOTE: This matches an error message from composer plugins used to install ACF PRO
184
+ # https://github.com/PhilippBaschke/acf-pro-installer/blob/772cec99c6ef8bc67ba6768419014cc60d141b27/src/ACFProInstaller/Exceptions/MissingKeyException.php#L14
185
+ # https://github.com/pivvenit/acf-pro-installer/blob/f2d4812839ee2c333709b0ad4c6c134e4c25fd6d/src/Exceptions/MissingKeyException.php#L25
186
+ if error.message.start_with?("Could not find a key for ACF PRO") ||
187
+ error.message.start_with?("Could not find a license key for ACF PRO")
183
188
  raise MissingEnvironmentVariable, "ACF_PRO_KEY"
184
189
  end
185
190
 
191
+ # NOTE: This matches error output from a composer plugin (private-composer-installer):
192
+ # https://github.com/ffraenz/private-composer-installer/blob/8655e3da4e8f99203f13ccca33b9ab953ad30a31/src/Exception/MissingEnvException.php#L22
193
+ if error.message.match?(MISSING_ENV_VAR_REGEX)
194
+ env_var = error.message.match(MISSING_ENV_VAR_REGEX).named_captures.fetch("env_var")
195
+ raise MissingEnvironmentVariable, env_var
196
+ end
197
+
186
198
  if error.message.start_with?("Unknown downloader type: npm-sign") ||
187
199
  error.message.include?("file could not be downloaded") ||
188
200
  error.message.include?("configuration does not allow connect")
@@ -197,6 +209,7 @@ module Dependabot
197
209
  raise PrivateSourceAuthenticationFailure, source
198
210
  end
199
211
 
212
+ # NOTE: This error is raised by composer v1
200
213
  if error.message.include?("Argument 1 passed to Composer")
201
214
  msg = "One of your Composer plugins is not compatible with the "\
202
215
  "latest version of Composer. Please update Composer and "\
@@ -204,6 +217,12 @@ module Dependabot
204
217
  raise DependencyFileNotResolvable, msg
205
218
  end
206
219
 
220
+ # NOTE: This error is raised by composer v2 and includes helpful
221
+ # information about which plugins or dependencies are not compatible
222
+ if error.message.include?("Your requirements could not be resolved")
223
+ raise DependencyFileNotResolvable, error.message
224
+ end
225
+
207
226
  raise error
208
227
  end
209
228
  # rubocop:enable Metrics/AbcSize
@@ -425,7 +444,17 @@ module Dependabot
425
444
  end
426
445
 
427
446
  def php_helper_path
428
- NativeHelpers.composer_helper_path
447
+ NativeHelpers.composer_helper_path(composer_version: composer_version)
448
+ end
449
+
450
+ def composer_version
451
+ @composer_version ||=
452
+ begin
453
+ return "v2" unless parsed_lockfile["plugin-api-version"]
454
+
455
+ version = Version.new(parsed_lockfile["plugin-api-version"])
456
+ version.canonical_segments.first == 1 ? "v1" : "v2"
457
+ end
429
458
  end
430
459
 
431
460
  def credentials_env