Skip to content

Commit

Permalink
feat: blend command
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Jul 11, 2024
1 parent 09acdf3 commit ee28460
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 7 deletions.
101 changes: 101 additions & 0 deletions src/Command/BlendCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

/*
* This file is part of the PMU project.
*
* (c) Antoine Bluchet <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Pmu\Command;

use Composer\Command\BaseCommand;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Composer\Json\JsonFile;
use Composer\Package\PackageInterface;
use Pmu\Config;
use SplObjectStorage;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class BlendCommand extends BaseCommand
{
protected function configure(): void
{
$this->setName('blend')
->setDefinition([
new InputOption('dev', 'D', InputOption::VALUE_NONE, 'Blend dev requirements.'),
new InputArgument('projects', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
])
->setDescription('Blend the mono-repository dependencies into each projects.');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$composer = $this->requireComposer();
$config = Config::create($composer);
$repo = $composer->getRepositoryManager();
$requireKey = true === $input->getOption('dev') ? 'require-dev' : 'require';
$packageAccessor = true === $input->getOption('dev') ? 'getDevRequires' : 'getRequires';

$projects = null;
if (is_array($p = $input->getArgument('projects')) && $p) {
$projects = array_map('strval', $p);
}

foreach ($config->projects as $p) {
if ($projects && !in_array($p, $projects, true)) {
continue;
}

$commandPackage = $repo->findPackage($p, '*');
if (!$commandPackage || !$commandPackage instanceof PackageInterface) {
$output->writeln(sprintf('Package "%s" could not be found.', $p));
return 1;
}

/**
* @var SplObjectStorage<PackageInterface, array<int|string, mixed>>
*/
$packagesToUpdate = new SplObjectStorage();
foreach ($composer->getPackage()->{$packageAccessor}() as $g) {
foreach ($commandPackage->{$packageAccessor}() as $r) {
if ($g->getTarget() == $r->getTarget()) {
if (!$packagesToUpdate->contains($commandPackage)) {
$packagesToUpdate[$commandPackage] = [];
}

$a = $packagesToUpdate[$commandPackage];
$a[$g->getTarget()] = $g->getPrettyConstraint();
$packagesToUpdate[$commandPackage] = $a;
}
}
}

foreach ($packagesToUpdate as $package) {
$dir = $package->getDistUrl();
if (!is_string($dir) || !is_dir($dir)) {
$output->writeln(sprintf('Package "%s" could not be found at path "%s".', $p, $dir));
continue;
}

$json = new JsonFile($dir . '/composer.json');
/** @var array{require: array<string, string>, 'require-dev': array<string, string>} */
$composerDefinition = $json->read();

foreach ($packagesToUpdate[$package] as $target => $constraint) {
$composerDefinition[$requireKey][$target] = $constraint;
}

$json->write($composerDefinition);
}
}

return 0;
}
}
3 changes: 2 additions & 1 deletion src/Composer/CommandProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Composer\IO\IOInterface;
use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
use Pmu\Command\AllCommand;
use Pmu\Command\BlendCommand;
use Pmu\Command\CheckCommand;
use Pmu\Command\ComposerCommand;
use Pmu\Command\GraphCommand;
Expand All @@ -37,7 +38,7 @@ public function __construct(array $ctorArgs)
public function getCommands(): array
{
$config = Config::create($this->composer);
$commands = [new GraphCommand(), new AllCommand(), new CheckCommand()];
$commands = [new GraphCommand(), new AllCommand(), new CheckCommand(), new BlendCommand()];
foreach ($config->projects as $project) {
$commands[] = new ComposerCommand($project);
}
Expand Down
61 changes: 61 additions & 0 deletions tests/functional/BlendCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

/*
* This file is part of the PMU project.
*
* (c) Antoine Bluchet <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Pmu\Tests\Functional;

use Composer\Console\Application;
use PHPUnit\Framework\TestCase;
use Pmu\Command\BlendCommand;
use RuntimeException;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;

final class BlendCommandTest extends TestCase {
private Application $application;
private string $backup;
private string $file;
public function setUp(): void {
$this->application = new Application();
$this->application->add(new BlendCommand);
$this->application->setAutoExit(false);
chdir(__DIR__ . '/../monorepo');
}

public function testGraph(): void {
$this->file = __DIR__ . '/../monorepo/packages/A/composer.json';
$this->backup = file_get_contents($this->file) ?: throw new RuntimeException;
$output = new BufferedOutput;
$this->application->run(new StringInput('blend'), $output);
$json = file_get_contents($this->file);

/** @var array{require: array<string, string>, 'require-dev': array<string, string>} */
$new = json_decode($json, true);
$this->assertEquals($new['require']['soyuka/noop'], '^3.0.0');
}

public function testGraphDev(): void {
$this->file = __DIR__ . '/../monorepo/packages/A/composer.json';
$this->backup = file_get_contents($this->file) ?: throw new RuntimeException;
$output = new BufferedOutput;
$this->application->run(new StringInput('blend --dev'), $output);
$json = file_get_contents($this->file);

/** @var array{require: array<string, string>, 'require-dev': array<string, string>} */
$new = json_decode($json, true);
$this->assertEquals($new['require-dev']['soyuka/noop-dev'], '^3.0.0');
}

protected function tearDown(): void {
file_put_contents($this->file, $this->backup);
}
}
6 changes: 5 additions & 1 deletion tests/monorepo/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"description": "test project",
"license": "MIT",
"require-dev": {
"soyuka/pmu": "*@dev"
"soyuka/pmu": "*@dev",
"soyuka/noop-dev": "^3.0.0"
},
"require": {
"soyuka/noop": "^3.0.0"
},
"minimum-stability": "dev",
"autoload": {
Expand Down
8 changes: 5 additions & 3 deletions tests/monorepo/packages/A/composer.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"name": "test/a",
"require": {
"test/b": "*@dev || ^1.0"
},
"test/b": "*@dev || ^1.0",
"soyuka/noop": "^2.0.0"
},
"require-dev": {
"test/c": "*@dev || ^1.0"
"test/c": "*@dev || ^1.0",
"soyuka/noop-dev": "^2.0.0"
},
"autoload": {
"psr-4": {
Expand Down
4 changes: 2 additions & 2 deletions tests/monorepo/packages/B/composer.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "test/b",
"require": {
},
"require-dev": {
"soyuka/noop": "^3.0.0"
},
"require-dev": [],
"autoload": {
"psr-4": {
"MonoRepo\\B\\": "."
Expand Down

0 comments on commit ee28460

Please sign in to comment.