Skip to content

Commit 7abec16

Browse files
committed
Command to list all available recipes
1 parent b5146ae commit 7abec16

File tree

5 files changed

+185
-8
lines changed

5 files changed

+185
-8
lines changed

src/Command/RecipesCommand.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Flex\Command;
13+
14+
use Composer\Command\BaseCommand;
15+
use Composer\Factory;
16+
use Symfony\Component\Console\Input\InputInterface;
17+
use Symfony\Component\Console\Output\OutputInterface;
18+
use Symfony\Flex\InformationOperation;
19+
use Symfony\Flex\Lock;
20+
use Symfony\Flex\Recipe;
21+
22+
class RecipesCommand extends BaseCommand
23+
{
24+
/** @var \Symfony\Flex\Flex */
25+
private $flex;
26+
27+
public function __construct(/* cannot be type-hinted */ $flex)
28+
{
29+
$this->flex = $flex;
30+
31+
parent::__construct();
32+
}
33+
34+
protected function configure()
35+
{
36+
$this->setName('symfony:recipes:show')
37+
->setAliases(['symfony:recipes'])
38+
->setDescription('Shows information about all available recipes.')
39+
;
40+
}
41+
42+
protected function execute(InputInterface $input, OutputInterface $output)
43+
{
44+
$symfonyLock = new Lock(getenv('SYMFONY_LOCKFILE') ?: str_replace('composer.json', 'symfony.lock', Factory::getComposerFile()));
45+
46+
$locker = $this->getComposer()->getLocker();
47+
$lockData = $locker->getLockData();
48+
49+
// Merge all packages installed
50+
$packages = array_merge($lockData['packages'], $lockData['packages-dev']);
51+
52+
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
53+
54+
$operations = [];
55+
foreach ($packages as $key => $value) {
56+
if (null === $pkg = $installedRepo->findPackage($value['name'], '*')) {
57+
$this->getIO()->writeError(sprintf('<error>Package %s is not installed</error>', $value['name']));
58+
59+
return 1;
60+
}
61+
62+
$operations[] = new InformationOperation($pkg);
63+
}
64+
65+
$recipes = $this->flex->fetchRecipes($operations);
66+
ksort($recipes);
67+
68+
$write = [
69+
'',
70+
'<bg=blue;fg=white> </>',
71+
'<bg=blue;fg=white> Recipes available. </>',
72+
'<bg=blue;fg=white> </>',
73+
'',
74+
];
75+
76+
/** @var Recipe $recipe */
77+
foreach ($recipes as $name => $recipe) {
78+
$lockRef = $symfonyLock->get($name)['recipe']['ref'] ?? null;
79+
80+
$additional = '';
81+
if ($recipe->isAuto()) {
82+
$additional = '<comment>(auto recipe)</comment>';
83+
} elseif (null === $lockRef && null !== $recipe->getRef()) {
84+
$additional = '<comment>(new recipe available)</comment>';
85+
} elseif ($recipe->getRef() !== $lockRef) {
86+
$additional = '<comment>(update available)</comment>';
87+
}
88+
$write[] = sprintf(' * %s %s', $name, $additional);
89+
}
90+
91+
// TODO : Uncomment the lines following the implemented features
92+
//$write[] = '';
93+
//$write[] = '<fg=blue>Run</>:';
94+
//$write[] = ' * composer symfony:recipes vendor/package to see details about a recipe.';
95+
//$write[] = ' * composer symfony:recipes:update vendor/package to update that recipe.';
96+
//$write[] = ' * composer symfony:recipes:blame to see a tree of all of the installed/updated files.';
97+
$write[] = '';
98+
99+
$this->getIO()->write($write);
100+
}
101+
}

src/Flex.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
243243
$app->add(new Command\UpdateCommand($resolver));
244244
$app->add(new Command\RemoveCommand($resolver));
245245
$app->add(new Command\UnpackCommand($resolver));
246+
$app->add(new Command\RecipesCommand($this));
246247
$app->add(new Command\SyncRecipesCommand($this, $this->options->get('root-dir')));
247248
$app->add(new Command\GenerateIdCommand($this));
248249
$app->add(new Command\DumpEnvCommand($this->config, $this->options));
@@ -389,7 +390,7 @@ public function install(Event $event = null)
389390
copy($rootDir.'/.env.dist', $rootDir.'/.env');
390391
}
391392

392-
$recipes = $this->fetchRecipes();
393+
$recipes = $this->fetchRecipes($this->operations);
393394

394395
if (2 === $this->displayThanksReminder) {
395396
$love = '\\' === \DIRECTORY_SEPARATOR ? 'love' : '💖 ';
@@ -653,23 +654,23 @@ public function generateFlexId()
653654
$this->updateComposerLock();
654655
}
655656

656-
private function fetchRecipes(): array
657+
public function fetchRecipes(array &$operations): array
657658
{
658659
if (!$this->downloader->isEnabled()) {
659660
$this->io->writeError('<warning>Symfony recipes are disabled: "symfony/flex" not found in the root composer.json</warning>');
660661

661662
return [];
662663
}
663664
$devPackages = null;
664-
$data = $this->downloader->getRecipes($this->operations);
665+
$data = $this->downloader->getRecipes($operations);
665666
$manifests = $data['manifests'] ?? [];
666667
$locks = $data['locks'] ?? [];
667668
// symfony/flex and symfony/framework-bundle recipes should always be applied first
668669
$recipes = [
669670
'symfony/flex' => null,
670671
'symfony/framework-bundle' => null,
671672
];
672-
foreach ($this->operations as $i => $operation) {
673+
foreach ($operations as $i => $operation) {
673674
if ($operation instanceof UpdateOperation) {
674675
$package = $operation->getTargetPackage();
675676
} else {
@@ -706,7 +707,7 @@ private function fetchRecipes(): array
706707
}
707708

708709
if (isset($manifests[$name])) {
709-
$recipes[$name] = new Recipe($package, $name, $job, $manifests[$name]);
710+
$recipes[$name] = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []);
710711
}
711712

712713
$noRecipe = !isset($manifests[$name]) || (isset($manifests[$name]['not_installable']) && $manifests[$name]['not_installable']);
@@ -726,7 +727,7 @@ private function fetchRecipes(): array
726727
}
727728
}
728729
}
729-
$this->operations = [];
730+
$operations = [];
730731

731732
return array_filter($recipes);
732733
}

src/InformationOperation.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Symfony\Flex;
4+
5+
use Composer\DependencyResolver\Operation\SolverOperation;
6+
use Composer\Package\PackageInterface;
7+
8+
class InformationOperation extends SolverOperation
9+
{
10+
protected $package;
11+
12+
/**
13+
* Initializes operation.
14+
*
15+
* @param PackageInterface $package package instance
16+
* @param string $reason operation reason
17+
*/
18+
public function __construct(PackageInterface $package, $reason = null)
19+
{
20+
parent::__construct($reason);
21+
22+
$this->package = $package;
23+
}
24+
25+
/**
26+
* Returns package instance.
27+
*
28+
* @return PackageInterface
29+
*/
30+
public function getPackage()
31+
{
32+
return $this->package;
33+
}
34+
35+
/**
36+
* Returns job type.
37+
*
38+
* @return string
39+
*/
40+
public function getJobType()
41+
{
42+
return 'information';
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function __toString()
49+
{
50+
return 'Information '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
51+
}
52+
}

src/Recipe.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ class Recipe
2222
private $name;
2323
private $job;
2424
private $data;
25+
private $lock;
2526

26-
public function __construct(PackageInterface $package, string $name, string $job, array $data)
27+
public function __construct(PackageInterface $package, string $name, string $job, array $data, array $lock = [])
2728
{
2829
$this->package = $package;
2930
$this->name = $name;
3031
$this->job = $job;
3132
$this->data = $data;
33+
$this->lock = $lock;
3234
}
3335

3436
public function getPackage(): PackageInterface
@@ -84,4 +86,19 @@ public function isContrib(): bool
8486
{
8587
return $this->data['is_contrib'] ?? false;
8688
}
89+
90+
public function getRef(): string
91+
{
92+
return $this->lock['recipe']['ref'] ?? null;
93+
}
94+
95+
public function isAuto(): bool
96+
{
97+
return !isset($this->lock['recipe']);
98+
}
99+
100+
public function getVersion(): string
101+
{
102+
return $this->lock['version'];
103+
}
87104
}

tests/FlexTest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,16 @@ public function testPostInstall()
5353
'origin' => 'dummy/dummy:[email protected]/symfony/recipes:master',
5454
],
5555
],
56+
'locks' => [
57+
'dummy/dummy' => [
58+
'recipe' => [],
59+
'version' => '',
60+
],
61+
],
5662
];
5763

5864
$configurator = $this->getMockBuilder(Configurator::class)->disableOriginalConstructor()->getMock();
59-
$configurator->expects($this->once())->method('install')->with($this->equalTo(new Recipe($package, 'dummy/dummy', 'install', $data['manifests']['dummy/dummy'])));
65+
$configurator->expects($this->once())->method('install')->with($this->equalTo(new Recipe($package, 'dummy/dummy', 'install', $data['manifests']['dummy/dummy'], $data['locks']['dummy/dummy'])));
6066

6167
$downloader = $this->getMockBuilder(Downloader::class)->disableOriginalConstructor()->getMock();
6268
$downloader->expects($this->once())->method('getRecipes')->willReturn($data);

0 commit comments

Comments
 (0)