Skip to content

Commit f26a79a

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

File tree

5 files changed

+187
-8
lines changed

5 files changed

+187
-8
lines changed

src/Command/RecipesCommand.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
$locker = $this->getComposer()->getLocker();
45+
$lockData = $locker->getLockData();
46+
47+
// Merge all packages installed
48+
$packages = array_merge($lockData['packages'], $lockData['packages-dev']);
49+
50+
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
51+
52+
$operations = [];
53+
foreach ($packages as $key => $value) {
54+
if (null === $pkg = $installedRepo->findPackage($value['name'], '*')) {
55+
$this->getIO()->writeError(sprintf('<error>Package %s is not installed</error>', $value['name']));
56+
57+
continue;
58+
}
59+
60+
$operations[] = new InformationOperation($pkg);
61+
}
62+
63+
$recipes = $this->flex->fetchRecipes($operations);
64+
ksort($recipes);
65+
66+
if (\count($recipes) <= 0) {
67+
$this->getIO()->writeError('<error>No recipe found</error>');
68+
69+
return 1;
70+
}
71+
72+
$write = [
73+
'',
74+
'<bg=blue;fg=white> </>',
75+
'<bg=blue;fg=white> Recipes available. </>',
76+
'<bg=blue;fg=white> </>',
77+
'',
78+
];
79+
80+
$symfonyLock = new Lock(getenv('SYMFONY_LOCKFILE') ?: str_replace('composer.json', 'symfony.lock', Factory::getComposerFile()));
81+
82+
/** @var Recipe $recipe */
83+
foreach ($recipes as $name => $recipe) {
84+
$lockRef = $symfonyLock->get($name)['recipe']['ref'] ?? null;
85+
86+
$additional = '';
87+
if ($recipe->isAuto()) {
88+
$additional = '<comment>(auto recipe)</comment>';
89+
} elseif (null === $lockRef && null !== $recipe->getRef()) {
90+
$additional = '<comment>(new recipe available)</comment>';
91+
} elseif ($recipe->getRef() !== $lockRef) {
92+
$additional = '<comment>(update available)</comment>';
93+
}
94+
$write[] = sprintf(' * %s %s', $name, $additional);
95+
}
96+
97+
// TODO : Uncomment the lines following the implemented features
98+
//$write[] = '';
99+
//$write[] = '<fg=blue>Run</>:';
100+
//$write[] = ' * composer symfony:recipes vendor/package to see details about a recipe.';
101+
//$write[] = ' * composer symfony:recipes:update vendor/package to update that recipe.';
102+
//$write[] = ' * composer symfony:recipes:blame to see a tree of all of the installed/updated files.';
103+
$write[] = '';
104+
105+
$this->getIO()->write($write);
106+
107+
return 0;
108+
}
109+
}

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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
private $package;
11+
12+
public function __construct(PackageInterface $package, $reason = null)
13+
{
14+
parent::__construct($reason);
15+
16+
$this->package = $package;
17+
}
18+
19+
/**
20+
* Returns package instance.
21+
*
22+
* @return PackageInterface
23+
*/
24+
public function getPackage()
25+
{
26+
return $this->package;
27+
}
28+
29+
/**
30+
* Returns job type.
31+
*
32+
* @return string
33+
*/
34+
public function getJobType()
35+
{
36+
return 'information';
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
public function __toString()
43+
{
44+
return 'Information '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
45+
}
46+
}

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()
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)