Skip to content

Commit 137cc29

Browse files
committed
Command to list all available recipes
1 parent e34c826 commit 137cc29

File tree

5 files changed

+191
-8
lines changed

5 files changed

+191
-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: 8 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));
@@ -402,7 +403,8 @@ public function install(Event $event = null)
402403
copy($rootDir.'/.env.dist', $rootDir.'/.env');
403404
}
404405

405-
$recipes = $this->fetchRecipes();
406+
$recipes = $this->fetchRecipes($this->operations);
407+
$this->operations = []; // Reset the operation after getting recipes
406408

407409
if (2 === $this->displayThanksReminder) {
408410
$love = '\\' === \DIRECTORY_SEPARATOR ? 'love' : '💖 ';
@@ -700,23 +702,23 @@ private function updateAutoloadFile()
700702
);
701703
}
702704

703-
private function fetchRecipes(): array
705+
public function fetchRecipes(array $operations): array
704706
{
705707
if (!$this->downloader->isEnabled()) {
706708
$this->io->writeError('<warning>Symfony recipes are disabled: "symfony/flex" not found in the root composer.json</>');
707709

708710
return [];
709711
}
710712
$devPackages = null;
711-
$data = $this->downloader->getRecipes($this->operations);
713+
$data = $this->downloader->getRecipes($operations);
712714
$manifests = $data['manifests'] ?? [];
713715
$locks = $data['locks'] ?? [];
714716
// symfony/flex and symfony/framework-bundle recipes should always be applied first
715717
$recipes = [
716718
'symfony/flex' => null,
717719
'symfony/framework-bundle' => null,
718720
];
719-
foreach ($this->operations as $i => $operation) {
721+
foreach ($operations as $i => $operation) {
720722
if ($operation instanceof UpdateOperation) {
721723
$package = $operation->getTargetPackage();
722724
} else {
@@ -753,7 +755,7 @@ private function fetchRecipes(): array
753755
}
754756

755757
if (isset($manifests[$name])) {
756-
$recipes[$name] = new Recipe($package, $name, $job, $manifests[$name]);
758+
$recipes[$name] = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []);
757759
}
758760

759761
$noRecipe = !isset($manifests[$name]) || (isset($manifests[$name]['not_installable']) && $manifests[$name]['not_installable']);
@@ -773,7 +775,7 @@ private function fetchRecipes(): array
773775
}
774776
}
775777
}
776-
$this->operations = [];
778+
$operations = [];
777779

778780
return array_filter($recipes);
779781
}

src/InformationOperation.php

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

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)