*/
final class BumpCommand extends BaseCommand
{
use CompletionTrait;
private const ERROR_GENERIC = 1;
private const ERROR_LOCK_OUTDATED = 2;
protected function configure(): void
{
$this
->setName('bump')
->setDescription('Increases the lower limit of your composer.json requirements to the currently installed versions')
->setDefinition([
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name(s) to restrict which packages are bumped.', null, $this->suggestRootRequirement()),
new InputOption('dev-only', 'D', InputOption::VALUE_NONE, 'Only bump requirements in "require-dev".'),
new InputOption('no-dev-only', 'R', InputOption::VALUE_NONE, 'Only bump requirements in "require".'),
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the packages to bump, but will not execute anything.'),
])
->setHelp(
"The bump command increases the lower limit of your composer.json requirements\nto the currently installed versions. This helps to ensure your dependencies do not\naccidentally get downgraded due to some other conflict, and can slightly improve\ndependency resolution performance as it limits the amount of package versions\nComposer has to look at.\n\nRunning this blindly on libraries is **NOT** recommended as it will narrow down\nyour allowed dependencies, which may cause dependency hell for your users.\nRunning it with --dev-only on libraries may be fine however as dev requirements\nare local to the library and do not affect consumers of the package.\n"
)
;
}
/**
* @throws \Seld\JsonLint\ParsingException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$composerJsonPath = Factory::getComposerFile();
$io = $this->getIO();
if (!Filesystem::isReadable($composerJsonPath)) {
$io->writeError(''.$composerJsonPath.' is not readable.');
return self::ERROR_GENERIC;
}
$composerJson = new JsonFile($composerJsonPath);
$contents = file_get_contents($composerJson->getPath());
if (false === $contents) {
$io->writeError(''.$composerJsonPath.' is not readable.');
return self::ERROR_GENERIC;
}
// check for writability by writing to the file as is_writable can not be trusted on network-mounts
// see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
if (!is_writable($composerJsonPath) && false === Silencer::call('file_put_contents', $composerJsonPath, $contents)) {
$io->writeError(''.$composerJsonPath.' is not writable.');
return self::ERROR_GENERIC;
}
unset($contents);
$composer = $this->requireComposer();
if ($composer->getLocker()->isLocked()) {
if (!$composer->getLocker()->isFresh()) {
$io->writeError('The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.');
return self::ERROR_LOCK_OUTDATED;
}
$repo = $composer->getLocker()->getLockedRepository(true);
} else {
$repo = $composer->getRepositoryManager()->getLocalRepository();
}
if ($composer->getPackage()->getType() !== 'project' && !$input->getOption('dev-only')) {
$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();
if (!isset($contents['type'])) {
$io->writeError('If your package is not a library, you can explicitly specify the "type" by using "composer config type project".');
$io->writeError('Alternatively you can use --dev-only to only bump dependencies within "require-dev".');
}
unset($contents);
}
$bumper = new VersionBumper();
$tasks = [];
if (!$input->getOption('dev-only')) {
$tasks['require'] = $composer->getPackage()->getRequires();
}
if (!$input->getOption('no-dev-only')) {
$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) {
foreach ($reqs as $pkgName => $link) {
if (!Preg::isMatch($pattern, $pkgName)) {
unset($tasks[$key][$pkgName]);
}
}
}
}
$updates = [];
foreach ($tasks as $key => $reqs) {
foreach ($reqs as $pkgName => $link) {
if (PlatformRepository::isPlatformPackage($pkgName)) {
continue;
}
$currentConstraint = $link->getPrettyConstraint();
$package = $repo->findPackage($pkgName, '*');
// name must be provided or replaced
if (null === $package) {
continue;
}
while ($package instanceof AliasPackage) {
$package = $package->getAliasOf();
}
$bumped = $bumper->bumpRequirement($link->getConstraint(), $package);
if ($bumped === $currentConstraint) {
continue;
}
$updates[$key][$pkgName] = $bumped;
}
}
$dryRun = $input->getOption('dry-run');
if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) {
$composerDefinition = $composerJson->read();
foreach ($updates as $key => $packages) {
foreach ($packages as $package => $version) {
$composerDefinition[$key][$package] = $version;
}
}
$composerJson->write($composerDefinition);
}
$changeCount = array_sum(array_map('count', $updates));
if ($changeCount > 0) {
if ($dryRun) {
$io->write('' . $composerJsonPath . ' would be updated with:');
foreach ($updates as $requireType => $packages) {
foreach ($packages as $package => $version) {
$io->write(sprintf(' - %s.%s: %s', $requireType, $package, $version));
}
}
} else {
$io->write('' . $composerJsonPath . ' has been updated (' . $changeCount . ' changes).');
}
} else {
$io->write('No requirements to update in '.$composerJsonPath.'.');
}
if (!$dryRun && $composer->getLocker()->isLocked() && $changeCount > 0) {
$contents = file_get_contents($composerJson->getPath());
if (false === $contents) {
throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.');
}
$lock = new JsonFile(Factory::getLockFile($composerJsonPath));
$lockData = $lock->read();
$lockData['content-hash'] = Locker::getContentHash($contents);
$lock->write($lockData);
}
if ($dryRun && $changeCount > 0) {
return self::ERROR_GENERIC;
}
return 0;
}
/**
* @param array<'require'|'require-dev', array> $updates
*/
private function updateFileCleanly(JsonFile $json, array $updates): bool
{
$contents = file_get_contents($json->getPath());
if (false === $contents) {
throw new \RuntimeException('Unable to read '.$json->getPath().' contents.');
}
$manipulator = new JsonManipulator($contents);
foreach ($updates as $key => $packages) {
foreach ($packages as $package => $version) {
if (!$manipulator->addLink($key, $package, $version)) {
return false;
}
}
}
if (false === file_put_contents($json->getPath(), $manipulator->getContents())) {
throw new \RuntimeException('Unable to write new '.$json->getPath().' contents.');
}
return true;
}
}
__halt_compiler();----SIGNATURE:----sdqxdVx1ihkDop8ET6dFCnI96Rbo/ouwfcqkR579GQOg5ZnrLWtGUKDGAFky1Dn2jo8Q7FaDJ8VuYs+zlUUoMRCAtm8OxWs4SG3y1SJq0Nzl6A/Y3DZ+9BCh85/VzRDaiLrrRy3T8B/KFjXhX8lZYtKpSfKivXGD21jllbs94410aorpW8iq8viYPShqn17bZjyN0wWdkv6rR5nf+JbQrFmYaXnw740H09RdUk2Lq7RgVgewcVg6+pJDqht11+9n4CVIw4wEeh2Nl3EfJJu09nH6Clp2P8qynHx9EVVncFXSIiO0GkJayvJ0E8Ag7aaxVzNqEm6TYdn8Xw5nCFPt75FU6EXlxo7z56JaRZxhZBJkT7Sm8WqzRAYRVZRWM6IQO+VbpgsoR5dgxahsYhBmq8kqrcWsmHb+mPVjsEjFrAI9uBoEQQloipuyoEWf7FE4Se5EcvCNZPgraoxc7UYPIgz9G4upinYBnXjszaFbH17F7YmzOafWVhMJo+IiC8DX/cp7+Y8oiw45Vjs+lk2rqifZeMFWGxZQuZihHYbLgJGQLQl+LSygktm7QyVHDatokBtyBef3bcq8WJGHw9fhVTxUVtf1BY3U0odsMjRyJ2kgG6tQ14pWnLIMySaMzBIefSY+elP7r4ECLAWSlcQdGvbocM5c9Q7T+cNmpwdtq8U=----ATTACHMENT:----MTU4MzEzODkwNTE4MTI0MCAxNDkxNDQ1MTA4MDc2ODkzIDY5MjI1MzU4OTExMjk1MzM=