Skip to content

Commit

Permalink
Add option to run bump after update
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-granados committed Apr 22, 2024
1 parent 9f84f0c commit 6903fb0
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 11 deletions.
3 changes: 3 additions & 0 deletions doc/03-cli.md
Expand Up @@ -234,6 +234,9 @@ 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.
* **--bump-dev-only:** Used if "Bump after Update" option is selected. Only bump requirements in "require-dev".
* **--bump-no-dev-only:** Used if "Bump after Update" option is selected. Only bump requirements in "require".

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`.

Expand Down
5 changes: 5 additions & 0 deletions doc/06-config.md
Expand Up @@ -476,4 +476,9 @@ 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. If set to true, Composer will run the Bump command after running
the Update command.

← [Repositories](05-repositories.md) | [Runtime](07-runtime.md) →
4 changes: 4 additions & 0 deletions res/composer-schema.json
Expand Up @@ -645,6 +645,10 @@
"platform-check": {
"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": "boolean",
"description": "Defaults to false. If set to true, Composer will run the Bump command after running the Update command."
}
}
},
Expand Down
38 changes: 29 additions & 9 deletions src/Composer/Command/BumpCommand.php
Expand Up @@ -12,6 +12,7 @@

namespace Composer\Command;

use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Locker;
Expand Down Expand Up @@ -70,11 +71,31 @@ protected function configure(): void
/**
* @throws \Seld\JsonLint\ParsingException
*/
protected function execute(InputInterface $input, OutputInterface $output): int
public 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,
bool $calledFromUpdate = false
): int {
/** @readonly */
$composerJsonPath = Factory::getComposerFile();
$io = $this->getIO();

if (!Filesystem::isReadable($composerJsonPath)) {
$io->writeError('<error>'.$composerJsonPath.' is not readable.</error>');
Expand Down Expand Up @@ -112,27 +133,28 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$repo = $composer->getRepositoryManager()->getLocalRepository();
}

if ($composer->getPackage()->getType() !== 'project' && !$input->getOption('dev-only')) {
if ($composer->getPackage()->getType() !== 'project' && !$devOnly) {
$io->writeError('<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>');

$contents = $composerJson->read();
if (!isset($contents['type'])) {
$io->writeError('<warning>If your package is not a library, you can explicitly specify the "type" by using "composer config type project".</warning>');
$io->writeError('<warning>Alternatively you can use --dev-only to only bump dependencies within "require-dev".</warning>');
$io->writeError('<warning>Alternatively you can use --' .
($calledFromUpdate ? 'bump-' : '') .
'dev-only to only bump dependencies within "require-dev".</warning>');
}
unset($contents);
}

$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) {
Expand Down Expand Up @@ -171,8 +193,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}

$dryRun = $input->getOption('dry-run');

if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) {
$composerDefinition = $composerJson->read();
foreach ($updates as $key => $packages) {
Expand Down
1 change: 1 addition & 0 deletions src/Composer/Command/ConfigCommand.php
Expand Up @@ -469,6 +469,7 @@ static function ($val) {
'prepend-autoloader' => [$booleanValidator, $booleanNormalizer],
'disable-tls' => [$booleanValidator, $booleanNormalizer],
'secure-http' => [$booleanValidator, $booleanNormalizer],
'bump-after-update' => [$booleanValidator, $booleanNormalizer],
'cafile' => [
static function ($val): bool {
return file_exists($val) && Filesystem::isReadable($val);
Expand Down
26 changes: 25 additions & 1 deletion src/Composer/Command/UpdateCommand.php
Expand Up @@ -25,6 +25,7 @@
use Composer\Util\HttpDownloader;
use Composer\Advisory\Auditor;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
Expand Down Expand Up @@ -78,6 +79,9 @@ protected function configure()
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_NONE, 'Runs Bump after performing the update.'),
new InputOption('bump-dev-only', null, InputOption::VALUE_NONE, 'Used if "Bump after Update" option is selected. Only bump requirements in "require-dev".'),
new InputOption('bump-no-dev-only', null, InputOption::VALUE_NONE, 'Used if "Bump after Update" option is selected. Only bump requirements in "require".'),
])
->setHelp(
<<<EOT
Expand Down Expand Up @@ -248,7 +252,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$install->disablePlugins();
}

return $install->run();
$result = $install->run();
$bumpAfterUpdate = $input->getOption('bump-after-update') || $composer->getConfig()->get('bump-after-update');

if ($bumpAfterUpdate) {
if ($result === 0) {
$io->writeError('<info>Running Bump after Update.</info>');
$bumpCommand = new BumpCommand();
$bumpCommand->setComposer($composer);
$result = $bumpCommand->doBump(
$io,
$input->getOption('bump-dev-only'),
$input->getOption('bump-no-dev-only'),
$input->getOption('dry-run'),
$input->getArgument('packages'),
true
);
} else {
$io->writeError('<warning>Not running Bump after Update because the update command did not finish successfully.</warning>');
}
}
return $result;
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/Composer/Config.php
Expand Up @@ -84,6 +84,7 @@ class Config
'gitlab-token' => [],
'http-basic' => [],
'bearer' => [],
'bump-after-update' => false,
];

/** @var array<string, mixed> */
Expand Down
64 changes: 63 additions & 1 deletion tests/Composer/Test/Command/UpdateCommandTest.php
Expand Up @@ -22,10 +22,14 @@ class UpdateCommandTest extends TestCase
* @param array<mixed> $composerJson
* @param array<mixed> $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));

Expand Down Expand Up @@ -124,6 +128,64 @@ public static function provideUpdates(): \Generator
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],
<<<OUTPUT
Loading composer repositories with package information
Updating dependencies
Lock file operations: 2 installs, 0 updates, 0 removals
- Locking dep/pkg (1.0.2)
- Locking root/req (1.0.0)
Installing dependencies from lock file (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
- Installing dep/pkg (1.0.2)
- Installing root/req (1.0.0)
Running Bump after Update.
<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>
<warning>If your package is not a library, you can explicitly specify the "type" by using "composer config type project".</warning>
<warning>Alternatively you can use --bump-dev-only to only bump dependencies within "require-dev".</warning>
No requirements to update in ./composer.json.
OUTPUT,
true
];

yield 'update & bump dev only' => [
$rootDepAndTransitiveDep,

Check failure on line 155 in tests/Composer/Test/Command/UpdateCommandTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.2, false)

Part $rootDepAndTransitiveDep (array{repositories: array{packages: array{type: 'package', package: array{array{name: 'root/req', version: '1.0.0', require: array{dep/pkg: '^1'}}, array{name: 'dep/pkg', version: '1.0.0'}, array{name: 'dep/pkg', version: '1.0.1'}, array{name: 'dep/pkg', version: '1.0.2'}}}}, require: array{root/req: '1.*'}}) of encapsed string cannot be cast to string.
['--bump-after-update' => true, '--bump-dev-only' => true],
<<<OUTPUT
Loading composer repositories with package information
Updating dependencies
Lock file operations: 2 installs, 0 updates, 0 removals
- Locking dep/pkg (1.0.2)
- Locking root/req (1.0.0)
Installing dependencies from lock file (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
- Installing dep/pkg (1.0.2)
- Installing root/req (1.0.0)
Running Bump after Update.
No requirements to update in ./composer.json.
OUTPUT,
true
];

yield 'update & dump with failing update' => [
$rootDepAndTransitiveDep,

Check failure on line 174 in tests/Composer/Test/Command/UpdateCommandTest.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.2, false)

Part $rootDepAndTransitiveDep (array{repositories: array{packages: array{type: 'package', package: array{array{name: 'root/req', version: '1.0.0', require: array{dep/pkg: '^1'}}, array{name: 'dep/pkg', version: '1.0.0'}, array{name: 'dep/pkg', version: '1.0.1'}, array{name: 'dep/pkg', version: '1.0.2'}}}}, require: array{root/req: '1.*'}}) of encapsed string cannot be cast to string.
['--with' => ['dep/pkg:^2'], '--bump-after-update' => true],
<<<OUTPUT
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires root/req 1.* -> satisfiable by root/req[1.0.0].
- root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2).
<warning>Not running Bump after Update because the update command did not finish successfully.</warning>
OUTPUT
];

}

public function testInteractiveModeThrowsIfNoPackageEntered(): void
Expand Down

0 comments on commit 6903fb0

Please sign in to comment.