dependabot-composer 0.89.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)