dependabot-composer 0.89.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,34 @@
1
+ <?php
2
+
3
+ $finder = PhpCsFixer\Finder::create()
4
+ ->in(__DIR__ . '/src')
5
+ ->in(__DIR__ . '/bin');
6
+
7
+ return PhpCsFixer\Config::create()
8
+ ->setRules([
9
+ '@Symfony' => true,
10
+ 'array_syntax' => ['syntax' => 'short'],
11
+ 'blank_line_after_opening_tag' => true,
12
+ 'concat_space' => ['spacing' => 'one'],
13
+ 'declare_strict_types' => true,
14
+ 'increment_style' => ['style' => 'post'],
15
+ 'is_null' => ['use_yoda_style' => false],
16
+ 'list_syntax' => ['syntax' => 'short'],
17
+ 'method_argument_space' => ['ensure_fully_multiline' => true],
18
+ 'modernize_types_casting' => true,
19
+ 'no_multiline_whitespace_before_semicolons' => true,
20
+ 'no_useless_else' => true,
21
+ 'no_useless_return' => true,
22
+ 'ordered_imports' => true,
23
+ 'phpdoc_align' => false,
24
+ 'phpdoc_order' => true,
25
+ 'php_unit_construct' => true,
26
+ 'php_unit_dedicate_assert' => true,
27
+ 'single_line_comment_style' => true,
28
+ 'ternary_to_null_coalescing' => true,
29
+ 'yoda_style' => false,
30
+ 'void_return' => true,
31
+ ])
32
+ ->setFinder($finder)
33
+ ->setUsingCache(true)
34
+ ->setRiskyAllowed(true);
data/helpers/setup.sh ADDED
@@ -0,0 +1,4 @@
1
+ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
2
+ php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo hash_file('SHA384', 'composer-setup.php'); unlink('composer-setup.php'); } echo PHP_EOL;"
3
+ php composer-setup.php
4
+ php -r "unlink('composer-setup.php');"
@@ -0,0 +1,61 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\PHP;
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
+ class DependabotInstallationManager extends InstallationManager
15
+ {
16
+ private $installed = [];
17
+ private $updated = [];
18
+ private $uninstalled = [];
19
+
20
+ public function install(RepositoryInterface $repo, InstallOperation $operation): void
21
+ {
22
+ parent::install($repo, $operation);
23
+ $this->installed[] = $operation->getPackage();
24
+ }
25
+
26
+ public function update(RepositoryInterface $repo, UpdateOperation $operation): void
27
+ {
28
+ parent::update($repo, $operation);
29
+ $this->updated[] = [$operation->getInitialPackage(), $operation->getTargetPackage()];
30
+ }
31
+
32
+ public function uninstall(RepositoryInterface $repo, UninstallOperation $operation): void
33
+ {
34
+ parent::uninstall($repo, $operation);
35
+ $this->uninstalled[] = $operation->getPackage();
36
+ }
37
+
38
+ /**
39
+ * @return PackageInterface[]
40
+ */
41
+ public function getInstalledPackages(): array
42
+ {
43
+ return $this->installed;
44
+ }
45
+
46
+ /**
47
+ * @return PackageInterface[]
48
+ */
49
+ public function getUpdatedPackages(): array
50
+ {
51
+ return $this->updated;
52
+ }
53
+
54
+ /**
55
+ * @return PackageInterface[]
56
+ */
57
+ public function getUninstalledPackages(): array
58
+ {
59
+ return $this->uninstalled;
60
+ }
61
+ }
@@ -0,0 +1,23 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\PHP;
6
+
7
+ use Composer\Package\PackageInterface;
8
+ use Composer\Plugin\PluginManager;
9
+
10
+ class DependabotPluginManager extends PluginManager
11
+ {
12
+ public function registerPackage(PackageInterface $package, $failOnMissingClasses = 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);
22
+ }
23
+ }
@@ -0,0 +1,25 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\PHP;
6
+
7
+ use Composer\IO\NullIO;
8
+
9
+ class ExceptionIO extends NullIO
10
+ {
11
+ private $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,21 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\PHP;
6
+
7
+ use Composer\Factory;
8
+
9
+ class Hasher
10
+ {
11
+ public static function getContentHash(array $args): ?string
12
+ {
13
+ [$workingDirectory] = $args;
14
+
15
+ $io = new ExceptionIO();
16
+ $composer = Factory::create($io, $workingDirectory . '/composer.json');
17
+ $locker = $composer->getLocker();
18
+
19
+ return $locker->getContentHash(file_get_contents(Factory::getComposerFile()));
20
+ }
21
+ }
@@ -0,0 +1,123 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\PHP;
6
+
7
+ use Composer\Factory;
8
+ use Composer\Installer;
9
+ use Composer\Package\PackageInterface;
10
+
11
+ class UpdateChecker
12
+ {
13
+ public static function getLatestResolvableVersion(array $args): ?string
14
+ {
15
+ [$workingDirectory, $dependencyName, $gitCredentials, $registryCredentials] = $args;
16
+
17
+ $io = new ExceptionIO();
18
+ $composer = Factory::create($io, $workingDirectory . '/composer.json');
19
+ $config = $composer->getConfig();
20
+ $httpBasicCredentials = [];
21
+
22
+ foreach ($gitCredentials as &$cred) {
23
+ $httpBasicCredentials[$cred['host']] = [
24
+ 'username' => $cred['username'],
25
+ 'password' => $cred['password'],
26
+ ];
27
+ }
28
+
29
+ foreach ($registryCredentials as &$cred) {
30
+ $httpBasicCredentials[$cred['registry']] = [
31
+ 'username' => $cred['username'],
32
+ 'password' => $cred['password'],
33
+ ];
34
+ }
35
+
36
+ if ($httpBasicCredentials) {
37
+ $config->merge(
38
+ [
39
+ 'config' => [
40
+ 'http-basic' => $httpBasicCredentials,
41
+ ],
42
+ ]
43
+ );
44
+ $io->loadConfiguration($config);
45
+ }
46
+
47
+ $installationManager = new DependabotInstallationManager();
48
+ $install = new Installer(
49
+ $io,
50
+ $config,
51
+ $composer->getPackage(),
52
+ $composer->getDownloadManager(),
53
+ $composer->getRepositoryManager(),
54
+ $composer->getLocker(),
55
+ $installationManager,
56
+ $composer->getEventDispatcher(),
57
+ $composer->getAutoloadGenerator()
58
+ );
59
+
60
+ // For all potential options, see UpdateCommand in composer
61
+ $install
62
+ ->setDryRun(true)
63
+ ->setUpdate(true)
64
+ ->setDevMode(true)
65
+ ->setUpdateWhitelist([$dependencyName])
66
+ ->setWhitelistTransitiveDependencies(true)
67
+ ->setExecuteOperations(false)
68
+ ->setDumpAutoloader(false)
69
+ ->setRunScripts(false);
70
+
71
+ /*
72
+ * If a platform is set we assume people know what they are doing and we respect the setting.
73
+ * If no platform is set we ignore it so that the php we run as doesn't interfere
74
+ */
75
+ if ($config->get('platform') === []) {
76
+ $install->setIgnorePlatformRequirements(true);
77
+ }
78
+
79
+ $install->run();
80
+
81
+ $installedPackages = $installationManager->getInstalledPackages();
82
+
83
+ $updatedPackage = current(array_filter($installedPackages, function (PackageInterface $package) use ($dependencyName) {
84
+ return $package->getName() == $dependencyName;
85
+ }));
86
+
87
+ // We found the package in the list of updated packages. Return its version.
88
+ if ($updatedPackage) {
89
+ return preg_replace('/^([v])/', '', $updatedPackage->getPrettyVersion());
90
+ }
91
+
92
+ // We didn't find the package in the list of updated packages. Check if
93
+ // it was replaced by another package (in which case we can ignore).
94
+ foreach ($composer->getPackage()->getReplaces() as $link) {
95
+ if ($link->getTarget() == $dependencyName) {
96
+ return null;
97
+ }
98
+ }
99
+ foreach ($installedPackages as $package) {
100
+ foreach ($package->getReplaces() as $link) {
101
+ if ($link->getTarget() == $dependencyName) {
102
+ return null;
103
+ }
104
+ }
105
+ }
106
+
107
+ // Similarly, check if the package was provided by any other package.
108
+ foreach ($composer->getPackage()->getProvides() as $link) {
109
+ if ($link->getTarget() == $dependencyName) {
110
+ return preg_replace('/^([v])/', '', $link->getPrettyConstraint());
111
+ }
112
+ }
113
+ foreach ($installedPackages as $package) {
114
+ foreach ($package->getProvides() as $link) {
115
+ if ($link->getTarget() == $dependencyName) {
116
+ return preg_replace('/^([v])/', '', $link->getPrettyConstraint());
117
+ }
118
+ }
119
+ }
120
+
121
+ throw new \RuntimeException('Package not found in updated packages!');
122
+ }
123
+ }
@@ -0,0 +1,97 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Dependabot\PHP;
6
+
7
+ use Composer\Factory;
8
+ use Composer\Installer;
9
+
10
+ class Updater
11
+ {
12
+ public static function update(array $args): array
13
+ {
14
+ [$workingDirectory, $dependencyName, $dependencyVersion, $gitCredentials, $registryCredentials] = $args;
15
+
16
+ // Change working directory to the one provided, this ensures that we
17
+ // install dependencies into the working dir, rather than a vendor folder
18
+ // in the root of the project
19
+ $originalDir = getcwd();
20
+ chdir($workingDirectory);
21
+
22
+ $io = new ExceptionIO();
23
+ $composer = Factory::create($io);
24
+ $config = $composer->getConfig();
25
+ $httpBasicCredentials = [];
26
+
27
+ $pm = new DependabotPluginManager($io, $composer, null, false);
28
+ $composer->setPluginManager($pm);
29
+ $pm->loadInstalledPlugins();
30
+
31
+ foreach ($gitCredentials as &$cred) {
32
+ $httpBasicCredentials[$cred['host']] = [
33
+ 'username' => $cred['username'],
34
+ 'password' => $cred['password'],
35
+ ];
36
+ }
37
+
38
+ foreach ($registryCredentials as &$cred) {
39
+ $httpBasicCredentials[$cred['registry']] = [
40
+ 'username' => $cred['username'],
41
+ 'password' => $cred['password'],
42
+ ];
43
+ }
44
+
45
+ if ($httpBasicCredentials) {
46
+ $config->merge(
47
+ [
48
+ 'config' => [
49
+ 'http-basic' => $httpBasicCredentials,
50
+ ],
51
+ ]
52
+ );
53
+ $io->loadConfiguration($config);
54
+ }
55
+
56
+ $install = new Installer(
57
+ $io,
58
+ $config,
59
+ $composer->getPackage(),
60
+ $composer->getDownloadManager(),
61
+ $composer->getRepositoryManager(),
62
+ $composer->getLocker(),
63
+ $composer->getInstallationManager(),
64
+ $composer->getEventDispatcher(),
65
+ $composer->getAutoloadGenerator()
66
+ );
67
+
68
+ // For all potential options, see UpdateCommand in composer
69
+ $install
70
+ ->setWriteLock(true)
71
+ ->setUpdate(true)
72
+ ->setDevMode(true)
73
+ ->setUpdateWhitelist([$dependencyName])
74
+ ->setWhitelistTransitiveDependencies(true)
75
+ ->setExecuteOperations(false)
76
+ ->setDumpAutoloader(false)
77
+ ->setRunScripts(false);
78
+
79
+ /*
80
+ * If a platform is set we assume people know what they are doing and we respect the setting.
81
+ * If no platform is set we ignore it so that the php we run as doesn't interfere
82
+ */
83
+ if ($config->get('platform') === []) {
84
+ $install->setIgnorePlatformRequirements(true);
85
+ }
86
+
87
+ $install->run();
88
+
89
+ $result = [
90
+ 'composer.lock' => file_get_contents('composer.lock'),
91
+ ];
92
+
93
+ chdir($originalDir);
94
+
95
+ return $result;
96
+ }
97
+ }
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # These all need to be required so the various classes can be registered in a
4
+ # lookup table of package manager names to concrete classes.
5
+ require "dependabot/composer/file_fetcher"
6
+ require "dependabot/composer/file_parser"
7
+ require "dependabot/composer/update_checker"
8
+ require "dependabot/composer/file_updater"
9
+ require "dependabot/composer/metadata_finder"
10
+ require "dependabot/composer/requirement"
11
+ require "dependabot/composer/version"
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/file_fetchers"
4
+ require "dependabot/file_fetchers/base"
5
+
6
+ module Dependabot
7
+ module Composer
8
+ class FileFetcher < Dependabot::FileFetchers::Base
9
+ def self.required_files_in?(filenames)
10
+ filenames.include?("composer.json")
11
+ end
12
+
13
+ def self.required_files_message
14
+ "Repo must contain a composer.json."
15
+ end
16
+
17
+ private
18
+
19
+ def fetch_files
20
+ fetched_files = []
21
+ fetched_files << composer_json
22
+ fetched_files << composer_lock if composer_lock
23
+ fetched_files << auth_json if auth_json
24
+ fetched_files += path_dependencies
25
+ fetched_files
26
+ end
27
+
28
+ def composer_json
29
+ @composer_json ||= fetch_file_from_host("composer.json")
30
+ end
31
+
32
+ def composer_lock
33
+ return @composer_lock if @composer_lock_lookup_attempted
34
+
35
+ @composer_lock_lookup_attempted = true
36
+ @composer_lock ||= fetch_file_if_present("composer.lock")
37
+ end
38
+
39
+ # Note: This is fetched but currently unused
40
+ def auth_json
41
+ @auth_json ||= fetch_file_if_present("auth.json")&.
42
+ tap { |f| f.support_file = true }
43
+ end
44
+
45
+ def path_dependencies
46
+ @path_dependencies ||=
47
+ begin
48
+ composer_json_files = []
49
+ unfetchable_deps = []
50
+
51
+ path_sources.each do |path|
52
+ directories = path.end_with?("*") ? expand_path(path) : [path]
53
+
54
+ directories.each do |dir|
55
+ file = File.join(dir, "composer.json")
56
+
57
+ begin
58
+ composer_json_files << fetch_file_with_root_fallback(file)
59
+ rescue Dependabot::DependencyFileNotFound
60
+ # Collected, but currently ignored
61
+ unfetchable_deps << file
62
+ end
63
+ end
64
+ end
65
+
66
+ # Mark the path dependencies as support files - we don't currently
67
+ # parse or update them.
68
+ composer_json_files.tap do |files|
69
+ files.each { |f| f.support_file = true }
70
+ end
71
+ end
72
+ end
73
+
74
+ def path_sources
75
+ @path_sources ||=
76
+ JSON.parse(composer_json.content).
77
+ fetch("repositories", []).
78
+ select { |details| details["type"] == "path" }.
79
+ map { |details| details["url"] }
80
+ rescue JSON::ParserError
81
+ raise Dependabot::DependencyFileNotParseable, composer_json.path
82
+ end
83
+
84
+ def expand_path(path)
85
+ repo_contents(dir: path.gsub(/\*$/, "")).
86
+ select { |file| file.type == "dir" }.
87
+ map { |f| path.gsub(/\*$/, f.name) }
88
+ rescue Octokit::NotFound, Gitlab::Error::NotFound
89
+ # If there's no lockfile, or if none of the dependencies are path
90
+ # dependencies, then we can ignore failures to find path deps
91
+ return [] unless composer_lock&.content&.include?('"path"')
92
+
93
+ # Otherwise, we don't know what to do. For now, just raise. If we see
94
+ # this in the wild we can make a call on the correct handling
95
+ raise if directory == "/"
96
+
97
+ # If the directory isn't found at the full path, try looking for it
98
+ # at the root of the repository.
99
+ depth = directory.gsub(%r{^/}, "").gsub(%r{/$}, "").split("/").count
100
+ dir = "../" * depth + path.gsub(/\*$/, "")
101
+
102
+ repo_contents(dir: dir).
103
+ select { |file| file.type == "dir" }.
104
+ map { |f| path.gsub(/\*$/, f.name) }
105
+ end
106
+
107
+ def fetch_file_with_root_fallback(filename, type: "file")
108
+ path = Pathname.new(File.join(directory, filename)).cleanpath.to_path
109
+
110
+ begin
111
+ fetch_file_from_host(filename, type: type)
112
+ rescue Dependabot::DependencyFileNotFound
113
+ # If the file isn't found at the full path, try looking for it
114
+ # without considering the directory (i.e., check if the path should
115
+ # have been relevative to the root of the repository).
116
+ cleaned_filename = Pathname.new(filename).cleanpath.to_path
117
+
118
+ DependencyFile.new(
119
+ name: cleaned_filename,
120
+ content: fetch_file_content(cleaned_filename),
121
+ directory: directory,
122
+ type: type
123
+ )
124
+ end
125
+ rescue Octokit::NotFound, Gitlab::Error::NotFound
126
+ raise Dependabot::DependencyFileNotFound, path
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ Dependabot::FileFetchers.register("composer", Dependabot::Composer::FileFetcher)