From d54f2f21c5e6c2fb094d012bed547f7c825d3ab7 Mon Sep 17 00:00:00 2001 From: Stephan Jorek <38257959+brandung-sjorek@users.noreply.github.com> Date: Tue, 25 Oct 2022 16:24:02 +0200 Subject: [PATCH] Feature: Add support for autocompleting setting-keys in config-command (#11130) * Add support for autocompleting setting-keys in config-command This PR implements the support for autocompletion of setting-keys in the config-command and its related funtional tests. The implementation takes existing command-line options like `--unset` or `--global` into account, as well as existing exposable package-properties. The result is a context- sensitive auto-completion of individual lists of setting-keys. Additionally showing the value `suggest.` sub-keys has been allowed in the config-command, as setting and unsetting them has alwas been possible, and I did not want to distinguish between showing, setting and unsetting those keys. In the setting-key flattening-method I used the `array_is_list()` function, which was added in PHP 8.1 recently. For backwards PHP-compatiblity the package `symfony/polyfill-php81` has been added. Signed-off-by: Stephan Jorek --- composer.json | 1 + composer.lock | 81 ++++++++- src/Composer/Command/ConfigCommand.php | 171 ++++++++++++++++-- .../Test/CompletionFunctionalTest.php | 40 ++++ .../functional/installed-versions.test | 8 +- .../functional/installed-versions2.test | 12 +- 6 files changed, 290 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index cfef15bf2..60e2508e5 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "composer/pcre": "^2 || ^3", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", "seld/signal-handler": "^2.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 0e88b76cd..da33b029f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a676a5855e58daf2c80e70dbb6fce5da", + "content-hash": "2dc8cd355eee4bb6c9fb0d2e10ffb7c6", "packages": [ { "name": "composer/ca-bundle", @@ -1724,6 +1724,85 @@ ], "time": "2022-05-10T07:21:04+00:00" }, + { + "name": "symfony/polyfill-php81", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, { "name": "symfony/process", "version": "v5.4.11", diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index f92079e77..e979f6e14 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -17,7 +17,8 @@ use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\Silencer; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputArgument; +use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Config; @@ -34,6 +35,26 @@ use Composer\Package\BasePackage; */ class ConfigCommand extends BaseCommand { + /** + * List of additional configurable package-properties + * + * @var string[] + */ + protected const CONFIGURABLE_PACKAGE_PROPERTIES = [ + 'name', + 'type', + 'description', + 'homepage', + 'version', + 'minimum-stability', + 'prefer-stable', + 'keywords', + 'license', + 'repositories', + 'suggest', + 'extra', + ]; + /** * @var Config */ @@ -76,7 +97,7 @@ class ConfigCommand extends BaseCommand new InputOption('merge', 'm', InputOption::VALUE_NONE, 'Merge the setting value with the current value, to be used with extra.* keys in combination with --json'), new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)'), new InputOption('source', null, InputOption::VALUE_NONE, 'Display where the config value is loaded from'), - new InputArgument('setting-key', null, 'Setting key'), + new InputArgument('setting-key', null, 'Setting key', null, $this->suggestSettingKeys()), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), ]) ->setHelp( @@ -161,11 +182,7 @@ EOT $io = $this->getIO(); $this->config = Factory::createConfig($io); - // Get the local composer.json, global config.json, or if the user - // passed in a file to use - $configFile = $input->getOption('global') - ? ($this->config->get('home') . '/config.json') - : ($input->getOption('file') ?: Factory::getComposerFile()); + $configFile = $this->getComposerConfigFile($input, $this->config); // Create global composer.json if this was invoked using `composer global config` if ( @@ -179,9 +196,7 @@ EOT $this->configFile = new JsonFile($configFile, null, $io); $this->configSource = new JsonConfigSource($this->configFile); - $authConfigFile = $input->getOption('global') - ? ($this->config->get('home') . '/auth.json') - : dirname($configFile) . '/auth.json'; + $authConfigFile = $this->getAuthConfigFile($input, $this->config); $this->authConfigFile = new JsonFile($authConfigFile, null, $io); $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true); @@ -258,7 +273,7 @@ EOT // show the value if no value is provided if ([] === $input->getArgument('setting-value') && !$input->getOption('unset')) { - $properties = ['name', 'type', 'description', 'homepage', 'version', 'minimum-stability', 'prefer-stable', 'keywords', 'license', 'extra']; + $properties = self::CONFIGURABLE_PACKAGE_PROPERTIES; $rawData = $this->configFile->read(); $data = $this->config->all(); if (Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) { @@ -273,7 +288,7 @@ EOT } } elseif (strpos($settingKey, '.')) { $bits = explode('.', $settingKey); - if ($bits[0] === 'extra') { + if ($bits[0] === 'extra' || $bits[0] === 'suggest') { $data = $rawData; } else { $data = $data['config']; @@ -914,4 +929,136 @@ EOT } } } + + /** + * Get the local composer.json, global config.json, or the file passed by the user + */ + private function getComposerConfigFile(InputInterface $input, Config $config): string + { + return $input->getOption('global') + ? ($config->get('home') . '/config.json') + : ($input->getOption('file') ?: Factory::getComposerFile()) + ; + } + + /** + * Get the local auth.json or global auth.json, or if the user passed in a file to use, + * the corresponding auth.json + */ + private function getAuthConfigFile(InputInterface $input, Config $config): string + { + return $input->getOption('global') + ? ($config->get('home') . '/auth.json') + : dirname($this->getComposerConfigFile($input, $config)) . '/auth.json' + ; + } + + /** + * Suggest setting-keys, while taking given options in acount. + */ + private function suggestSettingKeys(): \Closure + { + return function (CompletionInput $input): array { + if ($input->getOption('list') || $input->getOption('editor') || $input->getOption('auth')) { + return []; + } + + // initialize configuration + $config = Factory::createConfig(); + + // load configuration + $configFile = new JsonFile($this->getComposerConfigFile($input, $config)); + if ($configFile->exists()) { + $config->merge($configFile->read(), $configFile->getPath()); + } + + // load auth-configuration + $authConfigFile = new JsonFile($this->getAuthConfigFile($input, $config)); + if ($authConfigFile->exists()) { + $config->merge(['config' => $authConfigFile->read()], $authConfigFile->getPath()); + } + + // collect all configuration setting-keys + $rawConfig = $config->raw(); + $keys = array_merge( + $this->flattenSettingKeys($rawConfig['config']), + $this->flattenSettingKeys($rawConfig['repositories'], 'repositories.') + ); + + // if unsetting … + if ($input->getOption('unset')) { + // … keep only the currently customized setting-keys … + $sources = [$configFile->getPath(), $authConfigFile->getPath()]; + $keys = array_filter( + $keys, + static function (string $key) use ($config, $sources): bool { + return in_array($config->getSourceOfValue($key), $sources, true); + } + ); + + // … else if showing or setting a value … + } else { + // … add all configurable package-properties, no matter if it exist + $keys = array_merge($keys, self::CONFIGURABLE_PACKAGE_PROPERTIES); + + // it would be nice to distinguish between showing and setting + // a value, but that makes the implementation much more complex + // and partially impossible because symfony's implementation + // does not complete arguments followed by other arguments + } + + // add all existing configurable package-properties + if ($configFile->exists()) { + $properties = array_filter( + $configFile->read(), + static function (string $key): bool { + return in_array($key, self::CONFIGURABLE_PACKAGE_PROPERTIES, true); + }, + ARRAY_FILTER_USE_KEY + ); + + $keys = array_merge( + $keys, + $this->flattenSettingKeys($properties) + ); + } + + // filter settings-keys by completion value + $completionValue = $input->getCompletionValue(); + + if ($completionValue !== '') { + $keys = array_filter( + $keys, + static function (string $key) use ($completionValue): bool { + return str_starts_with($key, $completionValue); + } + ); + } + + sort($keys); + + return array_unique($keys); + }; + } + + /** + * build a flat list of dot-separated setting-keys from given config + * + * @param array $config + * @return string[] + */ + private function flattenSettingKeys(array $config, string $prefix = ''): array + { + $keys = []; + foreach ($config as $key => $value) { + $keys[] = [$prefix . $key]; + // array-lists must not be added to completion + // sub-keys of repository-keys must not be added to completion + if (is_array($value) && !array_is_list($value) && $prefix !== 'repositories.') { + $keys[] = $this->flattenSettingKeys($value, $prefix . $key . '.'); + } + } + + return array_merge(...$keys); + } } diff --git a/tests/Composer/Test/CompletionFunctionalTest.php b/tests/Composer/Test/CompletionFunctionalTest.php index 43c0ddcd1..8702d47d3 100644 --- a/tests/Composer/Test/CompletionFunctionalTest.php +++ b/tests/Composer/Test/CompletionFunctionalTest.php @@ -78,6 +78,46 @@ class CompletionFunctionalTest extends TestCase yield ['update --prefer-install ', $preferInstall]; yield ['update ', $installedPackages]; + + yield ['config --list ', null]; + yield ['config --editor ', null]; + yield ['config --auth ', null]; + + yield ['config ', ['bin-compat', 'extra', 'extra.branch-alias', 'home', 'name', 'repositories', 'repositories.packagist.org', 'suggest', 'suggest.ext-zip', 'type', 'version']]; + yield ['config bin', ['bin-dir']]; // global setting + yield ['config nam', ['name']]; // existing package-property + yield ['config ver', ['version']]; // non-existing package-property + yield ['config repo', ['repositories', 'repositories.packagist.org']]; + yield ['config repositories.', ['repositories.packagist.org']]; + yield ['config sug', ['suggest', 'suggest.ext-zip']]; + yield ['config suggest.ext-', ['suggest.ext-zip']]; + yield ['config ext', ['extra', 'extra.branch-alias', 'extra.branch-alias.dev-main']]; + + // as this test does not use a fixture (yet?), the completion + // of setting authentication settings can have varying results + // yield ['config http-basic.', […]]; + + yield ['config --unset ', ['extra', 'extra.branch-alias', 'extra.branch-alias.dev-main', 'name', 'suggest', 'suggest.ext-zip', 'type']]; + yield ['config --unset bin-dir', null]; // global setting + yield ['config --unset nam', ['name']]; // existing package-property + yield ['config --unset version', null]; // non-existing package-property + yield ['config --unset extra.', ['extra.branch-alias', 'extra.branch-alias.dev-main']]; + + // as this test does not use a fixture (yet?), the completion + // of unsetting authentication settings can have varying results + // yield ['config --unset http-basic.', […]]; + + yield ['config --global ', ['bin-compat', 'home', 'repositories', 'repositories.packagist.org']]; + yield ['config --global repo', ['repositories', 'repositories.packagist.org']]; + yield ['config --global repositories.', ['repositories.packagist.org']]; + + // as this test does not use a fixture (yet?), the completion + // of unsetting global settings can have varying results + // yield ['config --global --unset ', null]; + + // as this test does not use a fixture (yet?), the completion of + // unsetting global authentication settings can have varying results + // yield ['config --global --unset http-basic.', […]]; } /** diff --git a/tests/Composer/Test/Fixtures/functional/installed-versions.test b/tests/Composer/Test/Fixtures/functional/installed-versions.test index c50686711..731767ede 100644 --- a/tests/Composer/Test/Fixtures/functional/installed-versions.test +++ b/tests/Composer/Test/Fixtures/functional/installed-versions.test @@ -8,7 +8,7 @@ Checks that package versions in InstalledVersions are correct on initial install update --EXPECT-- > Hooks::preUpdate -!!PreUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string"] +!!PreUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string"] !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% Loading composer repositories with package information %((Info|Warning) from .*\n)?%Updating dependencies @@ -26,12 +26,12 @@ Package operations: 6 installs, 0 updates, 0 removals%(\nAs there is no 'unzip' - Downloading symfony/filesystem (%v?[2-8]\.\d+\.\d+%) - Installing symfony/console (99999.1.2): Symlinking from symfony-console - Installing plugin/a (1.1.1): Symlinking from plugin-a -!!PluginAInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","root/pkg"] +!!PluginAInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string","root/pkg"] !!PluginA:null !!PluginB:null !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% - Installing plugin/b (2.2.2): Symlinking from plugin-b -!!PluginBInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","root/pkg"] +!!PluginBInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string","plugin/a","root/pkg"] !!PluginA:1.1.1.0 !!PluginB:null !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% @@ -42,7 +42,7 @@ Generating autoload files 2 packages you are using are looking for funding. Use the `composer fund` command to find out more! > Hooks::postUpdate -!!PostUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PostUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% --EXPECT-EXIT-CODE-- diff --git a/tests/Composer/Test/Fixtures/functional/installed-versions2.test b/tests/Composer/Test/Fixtures/functional/installed-versions2.test index a2dc9f9b8..2a17d4a4e 100644 --- a/tests/Composer/Test/Fixtures/functional/installed-versions2.test +++ b/tests/Composer/Test/Fixtures/functional/installed-versions2.test @@ -7,14 +7,14 @@ Checks that package versions in InstalledVersions are correct during an upgrade. --RUN-- update plugin/* symfony/console symfony/filesystem symfony/process --EXPECT-- -!!PluginA:1.1.1.0["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PluginA:1.1.1.0["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!PluginB:2.2.2.0 !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% -!!PluginB:2.2.2.0["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PluginB:2.2.2.0["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!PluginA:1.1.1.0 !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% > Hooks::preUpdate -!!PreUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PreUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% Loading composer repositories with package information %((Info|Warning) from .*\n)?%Updating dependencies @@ -30,12 +30,12 @@ Package operations: 0 installs, 5 updates, 0 removals%(\nAs there is no 'unzip' - Downloading symfony/filesystem (%v?[2-8]\.\d+\.\d+%) - Upgrading symfony/console (99999.1.2 => 99999.1.3): Mirroring from symfony-console - Upgrading plugin/a (1.1.1 => 1.1.2): Mirroring from plugin-a -!!PluginAInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PluginAInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!PluginA:1.1.1.0 !!PluginB:2.2.2.0 !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% - Upgrading plugin/b (2.2.2 => 2.2.3): Mirroring from plugin-b -!!PluginBInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PluginBInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!PluginA:1.1.2.0 !!PluginB:2.2.2.0 !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% @@ -45,7 +45,7 @@ Generating autoload files 2 packages you are using are looking for funding. Use the `composer fund` command to find out more! > Hooks::postUpdate -!!PostUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PostUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/polyfill-php81","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% !!PluginA:1.1.2.0 !!PluginB:2.2.3.0