Skip to content

Commit c740dcb

Browse files
committed
[Toolkit] Add StimulusController support, extract logic to KitSynchronizer, rewrite installation system, create dedicated Installer/Pool system, make classes not readonly (PHP 8.1 comptability)
1 parent dbbf94f commit c740dcb

File tree

64 files changed

+1120
-628
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1120
-628
lines changed

src/Toolkit/bin/ux-toolkit-kit-debug

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use Symfony\Component\HttpClient\HttpClient;
2020
use Symfony\Contracts\Service\ServiceLocatorTrait;
2121
use Symfony\Contracts\Service\ServiceProviderInterface;
2222
use Symfony\UX\Toolkit\Command\DebugKitCommand;
23-
use Symfony\UX\Toolkit\Dependency\DependenciesResolver;
23+
use Symfony\UX\Toolkit\Kit\KitSynchronizer;
2424
use Symfony\UX\Toolkit\Kit\KitFactory;
2525
use Symfony\UX\Toolkit\Registry\GitHubRegistry;
2626
use Symfony\UX\Toolkit\Registry\LocalRegistry;
@@ -46,7 +46,7 @@ if (!class_exists(Application::class)) {
4646
}
4747

4848
$filesystem = new Filesystem();
49-
$kitFactory = new KitFactory($filesystem, new DependenciesResolver($filesystem));
49+
$kitFactory = new KitFactory($filesystem, new KitSynchronizer($filesystem));
5050
$registryFactory = new RegistryFactory(new class([
5151
Type::Local->value => fn () => new LocalRegistry($kitFactory, $filesystem, getcwd() ?? throw new \RuntimeException('The current working directory could not be determined.')),
5252
Type::GitHub->value => fn () => new GitHubRegistry($kitFactory, $filesystem, class_exists(HttpClient::class) ? HttpClient::create() : null),

src/Toolkit/bin/ux-toolkit-kit-lint

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use Symfony\Component\HttpClient\HttpClient;
2020
use Symfony\Contracts\Service\ServiceLocatorTrait;
2121
use Symfony\Contracts\Service\ServiceProviderInterface;
2222
use Symfony\UX\Toolkit\Command\LintKitCommand;
23-
use Symfony\UX\Toolkit\Dependency\DependenciesResolver;
23+
use Symfony\UX\Toolkit\Kit\KitSynchronizer;
2424
use Symfony\UX\Toolkit\Kit\KitFactory;
2525
use Symfony\UX\Toolkit\Registry\GitHubRegistry;
2626
use Symfony\UX\Toolkit\Registry\LocalRegistry;
@@ -46,7 +46,7 @@ if (!class_exists(Application::class)) {
4646
}
4747

4848
$filesystem = new Filesystem();
49-
$kitFactory = new KitFactory($filesystem, new DependenciesResolver($filesystem));
49+
$kitFactory = new KitFactory($filesystem, new KitSynchronizer($filesystem));
5050
$registryFactory = new RegistryFactory(new class([
5151
Type::Local->value => fn () => new LocalRegistry($kitFactory, $filesystem, getcwd() ?? throw new \RuntimeException('The current working directory could not be determined.')),
5252
Type::GitHub->value => fn () => new GitHubRegistry($kitFactory, $filesystem, class_exists(HttpClient::class) ? HttpClient::create() : null),

src/Toolkit/config/services.php

+4-29
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@
1313

1414
use Symfony\UX\Toolkit\Command\DebugKitCommand;
1515
use Symfony\UX\Toolkit\Command\InstallComponentCommand;
16-
use Symfony\UX\Toolkit\Command\InstallKitCommand;
1716
use Symfony\UX\Toolkit\Command\LintKitCommand;
18-
use Symfony\UX\Toolkit\Component\ComponentInstaller;
19-
use Symfony\UX\Toolkit\Dependency\DependenciesResolver;
2017
use Symfony\UX\Toolkit\Kit\KitFactory;
18+
use Symfony\UX\Toolkit\Kit\KitSynchronizer;
2119
use Symfony\UX\Toolkit\Registry\GitHubRegistry;
2220
use Symfony\UX\Toolkit\Registry\LocalRegistry;
2321
use Symfony\UX\Toolkit\Registry\RegistryFactory;
@@ -40,15 +38,7 @@
4038
->args([
4139
param('ux_toolkit.kit'),
4240
service('.ux_toolkit.registry.factory'),
43-
service('.ux_toolkit.component.component_installer'),
44-
])
45-
->tag('console.command')
46-
47-
->set('.ux_toolkit.command.install_kit', InstallKitCommand::class)
48-
->args([
49-
param('ux_toolkit.kit'),
50-
service('.ux_toolkit.registry.factory'),
51-
service('.ux_toolkit.component.component_installer'),
41+
service('filesystem'),
5242
])
5343
->tag('console.command')
5444

@@ -84,30 +74,15 @@
8474

8575
// Kit
8676

87-
->set('.ux_toolkit.kit.factory', KitFactory::class)
88-
->args([
89-
service('filesystem'),
90-
service('.ux_toolkit.dependency.dependencies_resolver'),
91-
])
92-
9377
->set('.ux_toolkit.kit.kit_factory', KitFactory::class)
9478
->args([
9579
service('filesystem'),
96-
service('.ux_toolkit.dependency.dependencies_resolver'),
97-
])
98-
99-
// Component
100-
->set('.ux_toolkit.component.component_installer', ComponentInstaller::class)
101-
->args([
102-
service('filesystem'),
80+
service('.ux_toolkit.kit.kit_synchronizer'),
10381
])
10482

105-
// Dependency
106-
107-
->set('.ux_toolkit.dependency.dependencies_resolver', DependenciesResolver::class)
83+
->set('.ux_toolkit.kit.kit_synchronizer', KitSynchronizer::class)
10884
->args([
10985
service('filesystem'),
11086
])
111-
11287
;
11388
};

src/Toolkit/src/Assert.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\UX\Toolkit;
1313

14-
final readonly class Assert
14+
final class Assert
1515
{
1616
/**
1717
* Assert that the kit name is valid (ex: "Shadcn", "Tailwind", "Bootstrap", etc.).
@@ -52,7 +52,14 @@ public static function phpPackageName(string $name): void
5252
{
5353
// Taken from https://github.com/composer/composer/blob/main/res/composer-schema.json
5454
if (1 !== preg_match('/^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$/', $name)) {
55-
throw new \InvalidArgumentException(\sprintf('Invalid package name "%s".', $name));
55+
throw new \InvalidArgumentException(\sprintf('Invalid PHP package name "%s".', $name));
56+
}
57+
}
58+
59+
public static function stimulusControllerName(string $name): void
60+
{
61+
if (1 !== preg_match('/^[a-z][a-z0-9-]*[a-z0-9]$/', $name)) {
62+
throw new \InvalidArgumentException(\sprintf('Invalid Stimulus controller name "%s".', $name));
5663
}
5764
}
5865
}

src/Toolkit/src/Component/Component.php renamed to src/Toolkit/src/Asset/Component.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Symfony\UX\Toolkit\Component;
12+
namespace Symfony\UX\Toolkit\Asset;
1313

1414
use Symfony\UX\Toolkit\Assert;
1515
use Symfony\UX\Toolkit\Dependency\ComponentDependency;
1616
use Symfony\UX\Toolkit\Dependency\Dependency;
1717
use Symfony\UX\Toolkit\Dependency\PhpPackageDependency;
18+
use Symfony\UX\Toolkit\Dependency\StimulusControllerDependency;
1819
use Symfony\UX\Toolkit\File\Doc;
1920
use Symfony\UX\Toolkit\File\File;
2021

@@ -58,6 +59,10 @@ public function addDependency(Dependency $dependency): void
5859
if ($existingDependency instanceof ComponentDependency && $existingDependency->name === $dependency->name) {
5960
return;
6061
}
62+
63+
if ($existingDependency instanceof StimulusControllerDependency && $existingDependency->name === $dependency->name) {
64+
return;
65+
}
6166
}
6267

6368
$this->dependencies[] = $dependency;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\UX\Toolkit\Asset;
13+
14+
use Symfony\UX\Toolkit\Assert;
15+
use Symfony\UX\Toolkit\File\File;
16+
17+
/**
18+
* @internal
19+
*
20+
* @author Hugo Alliaume <[email protected]>
21+
*/
22+
class StimulusController
23+
{
24+
/**
25+
* @param non-empty-string $name
26+
* @param list<File> $files
27+
*/
28+
public function __construct(
29+
public readonly string $name,
30+
public readonly array $files,
31+
) {
32+
Assert::stimulusControllerName($this->name);
33+
34+
if ([] === $files) {
35+
throw new \InvalidArgumentException(\sprintf('Stimulus controller "%s" has no files.', $name));
36+
}
37+
}
38+
}

src/Toolkit/src/Command/InstallComponentCommand.php

+17-55
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,18 @@
1111

1212
namespace Symfony\UX\Toolkit\Command;
1313

14-
use Composer\InstalledVersions;
1514
use Symfony\Component\Console\Attribute\AsCommand;
1615
use Symfony\Component\Console\Command\Command;
1716
use Symfony\Component\Console\Input\InputArgument;
1817
use Symfony\Component\Console\Input\InputInterface;
1918
use Symfony\Component\Console\Input\InputOption;
2019
use Symfony\Component\Console\Output\OutputInterface;
2120
use Symfony\Component\Console\Style\SymfonyStyle;
21+
use Symfony\Component\Filesystem\Filesystem;
2222
use Symfony\Component\Filesystem\Path;
23-
use Symfony\UX\Toolkit\Component\Component;
24-
use Symfony\UX\Toolkit\Component\ComponentInstaller;
25-
use Symfony\UX\Toolkit\Dependency\ComponentDependency;
26-
use Symfony\UX\Toolkit\Dependency\PhpPackageDependency;
27-
use Symfony\UX\Toolkit\Exception\ComponentAlreadyExistsException;
23+
use Symfony\UX\Toolkit\Asset\Component;
24+
use Symfony\UX\Toolkit\File\File;
25+
use Symfony\UX\Toolkit\Installer\Installer;
2826
use Symfony\UX\Toolkit\Kit\Kit;
2927
use Symfony\UX\Toolkit\Registry\RegistryFactory;
3028

@@ -46,7 +44,7 @@ class InstallComponentCommand extends Command
4644
public function __construct(
4745
private readonly string $kitName,
4846
private readonly RegistryFactory $registryFactory,
49-
private readonly ComponentInstaller $componentInstaller,
47+
private readonly Filesystem $filesystem,
5048
) {
5149
parent::__construct();
5250
}
@@ -87,7 +85,6 @@ protected function configure(): void
8785
protected function initialize(InputInterface $input, OutputInterface $output): void
8886
{
8987
$this->io = new SymfonyStyle($input, $output);
90-
$this->isInteractive = $input->isInteractive();
9188
}
9289

9390
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -123,29 +120,23 @@ protected function execute(InputInterface $input, OutputInterface $output): int
123120
}
124121
}
125122

126-
// Install the component and dependencies
127-
$destination = $input->getOption('destination');
123+
$this->io->text(\sprintf('Installing component <info>%s</>...', $component->name));
128124

129-
if (!$this->installComponent($kit, $component, $destination)) {
130-
return Command::FAILURE;
131-
}
125+
$installer = new Installer($this->filesystem, fn (string $question) => $this->io->confirm($question, $input->isInteractive()));
126+
$installationReport = $installer->installComponent($kit, $component, $destinationPath = $input->getOption('destination'), $input->getOption('force'));
132127

133-
// Iterate over the component's dependencies
134-
$phpDependenciesToInstall = [];
135-
foreach ($component->getDependencies() as $dependency) {
136-
if ($dependency instanceof ComponentDependency) {
137-
if (!$this->installComponent($kit, $kit->getComponent($dependency->name), $destination)) {
138-
return Command::FAILURE;
139-
}
140-
} elseif ($dependency instanceof PhpPackageDependency && !InstalledVersions::isInstalled($dependency->name)) {
141-
$phpDependenciesToInstall[] = $dependency;
142-
}
128+
if ([] === $installationReport->newFiles) {
129+
$this->io->warning('The component has not been installed.');
130+
131+
return Command::SUCCESS;
143132
}
144133

145-
$this->io->success(\sprintf('The component "%s" has been installed.', $component->name));
134+
$this->io->success('The component has been installed.');
135+
$this->io->writeln('The following file(s) have been added to your project:');
136+
$this->io->listing(array_map(fn (File $file) => Path::join($destinationPath, $file->relativePathName), $installationReport->newFiles));
146137

147-
if ([] !== $phpDependenciesToInstall) {
148-
$this->io->writeln(\sprintf('Run <info>composer require %s</info> to install the required PHP dependencies.', implode(' ', $phpDependenciesToInstall)));
138+
if ([] !== $installationReport->suggestedPhpPackages) {
139+
$this->io->writeln(\sprintf('Run <info>composer require %s</> to install the required PHP dependencies.', implode(' ', $installationReport->suggestedPhpPackages)));
149140
$this->io->newLine();
150141
}
151142

@@ -168,33 +159,4 @@ private function getAlternativeComponents(Kit $kit, string $componentName): arra
168159

169160
return $alternative;
170161
}
171-
172-
private function installComponent(Kit $kit, Component $component, string $destination, bool $force = false): bool
173-
{
174-
try {
175-
$this->io->text(\sprintf('<info>Installing component "%s"...</>', $component->name));
176-
177-
$this->componentInstaller->install($kit, $component, $destination);
178-
} catch (ComponentAlreadyExistsException) {
179-
if ($force) {
180-
$this->componentInstaller->install($kit, $component, $destination, true);
181-
182-
return true;
183-
}
184-
185-
$this->io->warning(\sprintf('The component "%s" already exists.', $component->name));
186-
187-
if ($this->isInteractive) {
188-
if ($this->io->confirm('Do you want to overwrite it?')) {
189-
$this->componentInstaller->install($kit, $component, $destination, true);
190-
191-
return true;
192-
}
193-
} else {
194-
return false;
195-
}
196-
}
197-
198-
return true;
199-
}
200162
}

0 commit comments

Comments
 (0)