Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"templates/": "templates/"
},
"dependencies": {
"recipe": ["Button"],
"recipe": ["button"],
"composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0", "tales-from-a-dev/twig-tailwind-extra"],
"npm": ["el-transition"],
"importmap": ["el-transition"]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% props variant = 'default' %}
<twig:Button variant="{{ variant }}" {{ ...attributes }}>
{{ block(outerBlocks.content) }}
{{- block(outerBlocks.content) -}}
</twig:Button>
2 changes: 1 addition & 1 deletion src/Toolkit/src/Command/DebugKitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$io->section('Recipes');
foreach ($kit->getRecipes() as $recipe) {
(new Table($io))
->setHeaderTitle(\sprintf('Recipe: "%s"', $recipe->manifest->name))
->setHeaderTitle(\sprintf('Recipe: "%s"', $recipe->name))
->setHorizontal()
->setHeaders([
'File(s)',
Expand Down
22 changes: 11 additions & 11 deletions src/Toolkit/src/Command/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ public function __construct(
protected function configure(): void
{
$this
->addArgument('recipe', InputArgument::OPTIONAL, 'The recipe name (Ex: Button)')
->addOption('kit', 'k', InputOption::VALUE_OPTIONAL, 'The kit name (Ex: "shadcn", or "github.com/user/my-ux-toolkit-kit")')
->addArgument('recipe', InputArgument::OPTIONAL, 'The recipe name (ex: "button")')
->addOption('kit', 'k', InputOption::VALUE_OPTIONAL, 'The kit name (ex: "shadcn", or "github.com/user/my-ux-toolkit-kit")')
->addOption(
'destination',
'd',
Expand All @@ -67,13 +67,13 @@ protected function configure(): void

To install a recipe, use:

<info>php %command.full_name% Button</info>
<info>php %command.full_name% button</info>

To install a recipe from a specific Kit (either official or external), use the <info>--kit</info> option:

<info>php %command.full_name% Button --kit=shadcn</info>
<info>php %command.full_name% Button --kit=https://github.com/user/my-kit</info>
<info>php %command.full_name% Button --kit=https://github.com/user/my-kit:branch</info>
<info>php %command.full_name% button --kit=shadcn</info>
<info>php %command.full_name% button --kit=https://github.com/user/my-kit</info>
<info>php %command.full_name% button --kit=https://github.com/user/my-kit:branch</info>
EOF
);
}
Expand Down Expand Up @@ -150,7 +150,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return Command::FAILURE;
}
} elseif ($alternativeRecipesCount > 0) {
$io->warning(\sprintf('%s'."\n".'Possible alternatives: "%s"', $message, implode('", "', array_map(fn (Recipe $c) => $c->manifest->name, $alternativeRecipes))));
$io->warning(\sprintf('%s'."\n".'Possible alternatives: "%s"', $message, implode('", "', array_map(fn (Recipe $r) => $r->name, $alternativeRecipes))));

return Command::FAILURE;
} else {
Expand All @@ -160,7 +160,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}

$io->writeln(\sprintf('Installing recipe <info>%s</> from the <info>%s</> kit...', $recipe->manifest->name, $kit->manifest->name));
$io->writeln(\sprintf('Installing recipe "<info>%s</>" from the <info>%s</> kit...', $recipe->name, $kit->manifest->name));

$installer = new Installer($this->filesystem, fn (string $question) => $this->io->confirm($question, $input->isInteractive()));
$installationReport = $installer->installRecipe($kit, $recipe, $destinationPath = $input->getOption('destination'), $input->getOption('force'));
Expand Down Expand Up @@ -220,13 +220,13 @@ private function getAlternativeRecipes(Kit $kit, string $recipeName): array
$alternativeRecipes = [];

foreach ($kit->getRecipes() as $recipe) {
$lev = levenshtein($recipeName, $recipe->manifest->name, 2, 5, 10);
if ($lev <= 8 || str_contains($recipe->manifest->name, $recipeName)) {
$lev = levenshtein($recipeName, $recipe->name, 2, 5, 10);
if ($lev <= 8 || str_contains($recipe->name, $recipeName)) {
$alternativeRecipes[] = $recipe;
}
}

usort($alternativeRecipes, fn (Recipe $recipeA, Recipe $recipeB) => strcmp($recipeA->manifest->name, $recipeB->manifest->name));
usort($alternativeRecipes, fn (Recipe $recipeA, Recipe $recipeB) => strcmp($recipeA->name, $recipeB->name));

return $alternativeRecipes;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Toolkit/src/Installer/PoolResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function resolveForRecipe(Kit $kit, Recipe $recipe): Pool
$pool->addImportmapPackageDependency($dependency);
} elseif ($dependency instanceof RecipeDependency) {
if (null === $recipeDependency = $kit->getRecipe($dependency->name)) {
throw new \LogicException(\sprintf('The recipe "%s" has a dependency on unregistered recipe "%s".', $currentRecipe->manifest->name, $dependency->name));
throw new \LogicException(\sprintf('The recipe "%s" has a dependency on unregistered recipe "%s".', $currentRecipe->name, $dependency->name));
}

$recipesStack[] = $recipeDependency;
Expand Down
22 changes: 11 additions & 11 deletions src/Toolkit/src/Kit/Kit.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
final class Kit
{
/**
* @var list<Recipe>
* @var array<string,Recipe>
*/
private array $recipes = [];

Expand All @@ -44,13 +44,11 @@ public function __construct(

public function addRecipe(Recipe $recipe): void
{
foreach ($this->recipes as $existingRecipe) {
if ($existingRecipe->manifest->name === $recipe->manifest->name) {
throw new \InvalidArgumentException(\sprintf('Recipe "%s" is already registered in the kit.', $recipe->manifest->name));
}
if (\array_key_exists($recipe->name, $this->recipes)) {
throw new \InvalidArgumentException(\sprintf('Recipe "%s" is already registered in the kit.', $recipe->manifest->name));
}

$this->recipes[] = $recipe;
$this->recipes[$recipe->name] = $recipe;
}

/**
Expand All @@ -67,12 +65,14 @@ public function getRecipes(?RecipeType $type = null): array

public function getRecipe(string $name, ?RecipeType $type = null): ?Recipe
{
foreach ($this->recipes as $recipe) {
if ($recipe->manifest->name === $name && (null === $type || $recipe->manifest->type === $type)) {
return $recipe;
}
if (null === $recipe = $this->recipes[$name] ?? null) {
return null;
}

return null;
if (null !== $type && $recipe->manifest->type !== $type) {
return null;
}

return $recipe;
}
}
2 changes: 2 additions & 0 deletions src/Toolkit/src/Recipe/Recipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
final class Recipe
{
/**
* @param non-empty-string $name
* @param non-empty-string $absolutePath
*/
public function __construct(
public readonly string $name,
public readonly string $absolutePath,
public readonly RecipeManifest $manifest,
) {
Expand Down
1 change: 1 addition & 0 deletions src/Toolkit/src/Recipe/RecipeSynchronizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function synchronizeRecipe(Kit $kit, SplFileInfo $manifestFile): void
}

$recipe = new Recipe(
name: $manifestFile->getPathInfo()->getBasename(),
absolutePath: $manifestFile->getPath(),
manifest: $manifest,
);
Expand Down
10 changes: 5 additions & 5 deletions src/Toolkit/tests/Command/DebugKitCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ public function testShouldBeAbleToDebugShadcnKit()
->assertOutputContains('License MIT')
// Components details
->assertOutputContains(implode(\PHP_EOL, [
'+--------------+------------------------ Recipe: "Avatar" ----------------------------------------+',
'+--------------+------------------------ Recipe: "avatar" ----------------------------------------+',
'| File(s) | templates/components/Avatar.html.twig |',
'| | templates/components/Avatar/Image.html.twig |',
'| | templates/components/Avatar/Text.html.twig |',
'| Dependencies | tales-from-a-dev/twig-tailwind-extra |',
'+--------------+----------------------------------------------------------------------------------+',
]))
->assertOutputContains(implode(\PHP_EOL, [
'+--------------+------------------------- Recipe: "Table" ----------------------------------------+',
'+--------------+------------------------- Recipe: "table" ----------------------------------------+',
'| File(s) | templates/components/Table.html.twig |',
'| | templates/components/Table/Body.html.twig |',
'| | templates/components/Table/Caption.html.twig |',
Expand All @@ -66,9 +66,9 @@ public function testShouldBeAbleToDebugFixtureKitWithManyDependencies()
->assertOutputContains('License MIT')
// Components details
->assertOutputContains(implode(\PHP_EOL, [
'+--------------+------------------------- Recipe: "Alert" ----------------------------------------+',
'+--------------+------------------------- Recipe: "alert" ----------------------------------------+',
'| File(s) | N/A |',
'| Dependencies | Button |',
'| Dependencies | button |',
'| | twig/html-extra:^3.12.0 |',
'| | tales-from-a-dev/twig-tailwind-extra |',
'| | tailwindcss:^4.0.0 |',
Expand All @@ -77,7 +77,7 @@ public function testShouldBeAbleToDebugFixtureKitWithManyDependencies()
'+--------------+----------------------------------------------------------------------------------+',
]))
->assertOutputContains(implode(\PHP_EOL, [
'+--------------+------------------------ Recipe: "Button" ----------------------------------------+',
'+--------------+------------------------ Recipe: "button" ----------------------------------------+',
'| File(s) | N/A |',
'| Dependencies | twig/html-extra:^3.12.0 |',
'| | another/php-package:^2.0 |',
Expand Down
34 changes: 17 additions & 17 deletions src/Toolkit/tests/Command/InstallCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,24 @@ protected function setUp(): void
public function testShouldAbleToInstallComponentTableAndItsDependencies()
{
$expectedFiles = [
'Table/templates/components/Table.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table.html.twig'),
'Table/templates/components/Table/Body.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Body.html.twig'),
'Table/templates/components/Table/Caption.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Caption.html.twig'),
'Table/templates/components/Table/Cell.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Cell.html.twig'),
'Table/templates/components/Table/Footer.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Footer.html.twig'),
'Table/templates/components/Table/Head.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Head.html.twig'),
'Table/templates/components/Table/Header.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Header.html.twig'),
'Table/templates/components/Table/Row.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Row.html.twig'),
'table/templates/components/Table.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table.html.twig'),
'table/templates/components/Table/Body.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Body.html.twig'),
'table/templates/components/Table/Caption.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Caption.html.twig'),
'table/templates/components/Table/Cell.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Cell.html.twig'),
'table/templates/components/Table/Footer.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Footer.html.twig'),
'table/templates/components/Table/Head.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Head.html.twig'),
'table/templates/components/Table/Header.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Header.html.twig'),
'table/templates/components/Table/Row.html.twig' => Path::normalize($this->tmpDir.'/templates/components/Table/Row.html.twig'),
];

foreach ($expectedFiles as $expectedFile) {
$this->assertFileDoesNotExist($expectedFile);
}

$testCommand = $this->consoleCommand(\sprintf('ux:install Table --destination="%s"', str_replace('\\', '\\\\', $this->tmpDir)))
$testCommand = $this->consoleCommand(\sprintf('ux:install table --destination="%s"', str_replace('\\', '\\\\', $this->tmpDir)))
->execute()
->assertSuccessful()
->assertOutputContains('Installing recipe Table from the Shadcn UI kit...')
->assertOutputContains('Installing recipe "table" from the Shadcn UI kit...')
->assertOutputContains('[OK] The recipe has been installed.')
;

Expand All @@ -72,11 +72,11 @@ public function testShouldFailAndSuggestAlternativeRecipesWhenKitIsExplicit()
mkdir($destination);

$this->bootKernel();
$this->consoleCommand('ux:install A --kit=shadcn --destination='.$destination)
$this->consoleCommand('ux:install a --kit=shadcn --destination='.$destination)
->execute()
->assertFaulty()
->assertOutputContains('[WARNING] The recipe "A" does not exist')
->assertOutputContains('Possible alternatives: "Alert", "Alert Dialog", "Aspect Ratio"')
->assertOutputContains('[WARNING] The recipe "a" does not exist')
->assertOutputContains('Possible alternatives: "alert", "alert-dialog", "aspect-ratio"')
;
}

Expand All @@ -86,10 +86,10 @@ public function testShouldFailWhenComponentDoesNotExist()
mkdir($destination);

$this->bootKernel();
$this->consoleCommand('ux:install Unknown --destination='.$destination)
$this->consoleCommand('ux:install unknown --destination='.$destination)
->execute()
->assertFaulty()
->assertOutputContains('The recipe "Unknown" does not exist');
->assertOutputContains('The recipe "unknown" does not exist');
}

public function testShouldWarnWhenComponentFileAlreadyExistsInNonInteractiveMode()
Expand All @@ -98,11 +98,11 @@ public function testShouldWarnWhenComponentFileAlreadyExistsInNonInteractiveMode
mkdir($destination);

$this->bootKernel();
$this->consoleCommand('ux:install Badge --destination='.$destination)
$this->consoleCommand('ux:install badge --destination='.$destination)
->execute()
->assertSuccessful();

$this->consoleCommand('ux:install Badge --destination='.$destination)
$this->consoleCommand('ux:install badge --destination='.$destination)
->execute()
->assertFaulty()
->assertOutputContains('[WARNING] The recipe has not been installed.')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"templates/": "templates/"
},
"dependencies": {
"recipe": ["B"]
"recipe": ["b"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"templates/": "templates/"
},
"dependencies": {
"recipe": ["C"]
"recipe": ["c"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"templates/": "templates/"
},
"dependencies": {
"recipe": ["A"]
"recipe": ["a"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"name": "Alert",
"description": "Component Alert",
"dependencies": {
"recipe": ["Button"],
"recipe": ["button"],
"composer": ["twig/html-extra:^3.12.0", "tales-from-a-dev/twig-tailwind-extra"],
"npm": ["tailwindcss@^4.0.0", "@tailwindplus/elements@1"],
"importmap": ["@hotwired/stimulus"]
Expand Down
2 changes: 1 addition & 1 deletion src/Toolkit/tests/Functional/ComponentsRenderingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static function provideTestComponentRendering(): iterable
}

foreach ($codeBlockMatches['code'] as $i => $code) {
yield \sprintf('Kit %s, component %s, code #%d', $kitName, $recipe->manifest->name, $i + 1) => [$kitName, $recipe->manifest->name, $code];
yield \sprintf('Kit %s, component %s, code #%d', $kitName, $recipe->name, $i + 1) => [$kitName, $recipe->name, $code];
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
</p>
</header>
<footer class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end "><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2" data-action="click-&gt;alert-dialog#close">Cancel</button>
<button class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2">Continue
</button>
<button class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2">Continue</button>
</footer>
</div>
</section>
Expand Down
Loading
Loading