diff --git a/doc/03-cli.md b/doc/03-cli.md
index 396c24f63..8c1c56e16 100644
--- a/doc/03-cli.md
+++ b/doc/03-cli.md
@@ -234,6 +234,7 @@ php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.*
changes to transitive dependencies. Can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var.
* **--interactive:** Interactive interface with autocompletion to select the packages to update.
* **--root-reqs:** Restricts the update to your first degree dependencies.
+* **--bump-after-update:** Runs `bump` after performing the update. Set to `dev` or `no-dev` to only bump those dependencies.
Specifying one of the words `mirrors`, `lock`, or `nothing` as an argument has the same effect as specifying the option `--lock`, for example `composer update mirrors` is exactly the same as `composer update --lock`.
diff --git a/doc/06-config.md b/doc/06-config.md
index 19a0df714..bff70d03d 100644
--- a/doc/06-config.md
+++ b/doc/06-config.md
@@ -481,6 +481,12 @@ throw, but you can set this config option to `["example.org"]` to allow using sv
URLs on that hostname. This is a better/safer alternative to disabling `secure-http`
altogether.
+## bump-after-update
+
+Defaults to `false` and can be any of `true`, `false`, `"dev"` or `"no-dev"`. If
+set to true, Composer will run the `bump` command after running the `update` command.
+If set to `"dev"` or `"no-dev"` then only the corresponding dependencies will be bumped.
+
## allow-missing-requirements
Defaults to `false`. Ignores error during `install` if there are any missing
diff --git a/res/composer-schema.json b/res/composer-schema.json
index 8a3d82b1b..e6f626dcb 100644
--- a/res/composer-schema.json
+++ b/res/composer-schema.json
@@ -663,6 +663,10 @@
"type": ["boolean", "string"],
"description": "Defaults to \"php-only\" which checks only the PHP version. Setting to true will also check the presence of required PHP extensions. If set to false, Composer will not create and require a platform_check.php file as part of the autoloader bootstrap."
},
+ "bump-after-update": {
+ "type": ["string", "boolean"],
+ "description": "Defaults to false and can be any of true, false, \"dev\"` or \"no-dev\"`. If set to true, Composer will run the bump command after running the update command. If set to \"dev\" or \"no-dev\" then only the corresponding dependencies will be bumped."
+ },
"allow-missing-requirements": {
"type": ["boolean"],
"description": "Defaults to false. If set to true, Composer will allow install when lock file is not up to date with the latest changes in composer.json."
diff --git a/src/Composer/Command/BumpCommand.php b/src/Composer/Command/BumpCommand.php
index db5b9464a..5cd7a0f20 100644
--- a/src/Composer/Command/BumpCommand.php
+++ b/src/Composer/Command/BumpCommand.php
@@ -12,6 +12,7 @@
namespace Composer\Command;
+use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Locker;
@@ -72,9 +73,28 @@ EOT
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
+ return $this->doBump(
+ $this->getIO(),
+ $input->getOption('dev-only'),
+ $input->getOption('no-dev-only'),
+ $input->getOption('dry-run'),
+ $input->getArgument('packages')
+ );
+ }
+
+ /**
+ * @param string[] $packagesFilter
+ * @throws \Seld\JsonLint\ParsingException
+ */
+ public function doBump(
+ IOInterface $io,
+ bool $devOnly,
+ bool $noDevOnly,
+ bool $dryRun,
+ array $packagesFilter
+ ): int {
/** @readonly */
$composerJsonPath = Factory::getComposerFile();
- $io = $this->getIO();
if (!Filesystem::isReadable($composerJsonPath)) {
$io->writeError(''.$composerJsonPath.' is not readable.');
@@ -112,7 +132,7 @@ EOT
$repo = $composer->getRepositoryManager()->getLocalRepository();
}
- if ($composer->getPackage()->getType() !== 'project' && !$input->getOption('dev-only')) {
+ if ($composer->getPackage()->getType() !== 'project' && !$devOnly) {
$io->writeError('Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.');
$contents = $composerJson->read();
@@ -125,14 +145,13 @@ EOT
$bumper = new VersionBumper();
$tasks = [];
- if (!$input->getOption('dev-only')) {
+ if (!$devOnly) {
$tasks['require'] = $composer->getPackage()->getRequires();
}
- if (!$input->getOption('no-dev-only')) {
+ if (!$noDevOnly) {
$tasks['require-dev'] = $composer->getPackage()->getDevRequires();
}
- $packagesFilter = $input->getArgument('packages');
if (count($packagesFilter) > 0) {
$pattern = BasePackage::packageNamesToRegexp(array_unique(array_map('strtolower', $packagesFilter)));
foreach ($tasks as $key => $reqs) {
@@ -171,8 +190,6 @@ EOT
}
}
- $dryRun = $input->getOption('dry-run');
-
if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) {
$composerDefinition = $composerJson->read();
foreach ($updates as $key => $packages) {
diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php
index 02e77df60..6dc487574 100644
--- a/src/Composer/Command/ConfigCommand.php
+++ b/src/Composer/Command/ConfigCommand.php
@@ -469,6 +469,18 @@ EOT
'prepend-autoloader' => [$booleanValidator, $booleanNormalizer],
'disable-tls' => [$booleanValidator, $booleanNormalizer],
'secure-http' => [$booleanValidator, $booleanNormalizer],
+ 'bump-after-update' => [
+ static function ($val): bool {
+ return in_array($val, ['dev', 'no-dev', 'true', 'false', '1', '0'], true);
+ },
+ static function ($val) {
+ if ('dev' === $val || 'no-dev' === $val) {
+ return $val;
+ }
+
+ return $val !== 'false' && (bool) $val;
+ },
+ ],
'cafile' => [
static function ($val): bool {
return file_exists($val) && Filesystem::isReadable($val);
diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php
index 1dee17972..756aa00d9 100644
--- a/src/Composer/Command/UpdateCommand.php
+++ b/src/Composer/Command/UpdateCommand.php
@@ -85,6 +85,7 @@ class UpdateCommand extends BaseCommand
new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During a partial update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'),
new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'),
new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'),
+ new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']),
])
->setHelp(
<<disablePlugins();
}
- return $install->run();
+ $result = $install->run();
+
+ if ($result === 0) {
+ $bumpAfterUpdate = $input->getOption('bump-after-update');
+ if (false === $bumpAfterUpdate) {
+ $bumpAfterUpdate = $composer->getConfig()->get('bump-after-update');
+ }
+
+ if (false !== $bumpAfterUpdate) {
+ $io->writeError('Bumping dependencies');
+ $bumpCommand = new BumpCommand();
+ $bumpCommand->setComposer($composer);
+ $result = $bumpCommand->doBump(
+ $io,
+ $bumpAfterUpdate === 'dev',
+ $bumpAfterUpdate === 'no-dev',
+ $input->getOption('dry-run'),
+ $input->getArgument('packages')
+ );
+ }
+ }
+ return $result;
}
/**
diff --git a/src/Composer/Config.php b/src/Composer/Config.php
index 34737c09c..6844eb1e2 100644
--- a/src/Composer/Config.php
+++ b/src/Composer/Config.php
@@ -84,6 +84,7 @@ class Config
'gitlab-token' => [],
'http-basic' => [],
'bearer' => [],
+ 'bump-after-update' => false,
'allow-missing-requirements' => false,
];
diff --git a/tests/Composer/Test/Command/UpdateCommandTest.php b/tests/Composer/Test/Command/UpdateCommandTest.php
index a4d34626e..dd8427d29 100644
--- a/tests/Composer/Test/Command/UpdateCommandTest.php
+++ b/tests/Composer/Test/Command/UpdateCommandTest.php
@@ -24,10 +24,14 @@ class UpdateCommandTest extends TestCase
* @param array $composerJson
* @param array $command
*/
- public function testUpdate(array $composerJson, array $command, string $expected): void
+ public function testUpdate(array $composerJson, array $command, string $expected, bool $createLock = false): void
{
$this->initTempComposer($composerJson);
+ if ($createLock) {
+ $this->createComposerLock();
+ }
+
$appTester = $this->getApplicationTester();
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true, '--no-audit' => true], $command));
@@ -126,6 +130,62 @@ The temporary constraint "^2" for "root/req" must be a subset of the constraint
Run `composer require root/req` or `composer require root/req:^2` instead to replace the constraint
OUTPUT
];
+
+ yield 'update & bump' => [
+ $rootDepAndTransitiveDep,
+ ['--bump-after-update' => true],
+ <<