From 7d0a21440f990bb77939147f7684a8cef7df0fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Thu, 30 Jan 2025 13:15:27 +0100 Subject: [PATCH 01/51] Initialized the Toolkit component --- Toolkit/composer.json | 36 + Toolkit/composer.lock | 2871 +++++++++++++++++ Toolkit/phpunit.xml.dist | 24 + .../src/Command/UxToolkitInstallCommand.php | 111 + .../ComponentRepository.php | 24 + .../ComponentRepository/GithubRepository.php | 47 + .../OfficialRepository.php | 51 + .../ComponentRepository/RepositoryFactory.php | 43 + Toolkit/src/UxToolkitBundle.php | 44 + Toolkit/templates/components/Badge.html.twig | 38 + .../Command/UxToolkitInstallCommandTest.php | 82 + .../RepositoryFactoryTest.php | 39 + 12 files changed, 3410 insertions(+) create mode 100644 Toolkit/composer.json create mode 100644 Toolkit/composer.lock create mode 100644 Toolkit/phpunit.xml.dist create mode 100644 Toolkit/src/Command/UxToolkitInstallCommand.php create mode 100644 Toolkit/src/ComponentRepository/ComponentRepository.php create mode 100644 Toolkit/src/ComponentRepository/GithubRepository.php create mode 100644 Toolkit/src/ComponentRepository/OfficialRepository.php create mode 100644 Toolkit/src/ComponentRepository/RepositoryFactory.php create mode 100644 Toolkit/src/UxToolkitBundle.php create mode 100644 Toolkit/templates/components/Badge.html.twig create mode 100644 Toolkit/tests/Command/UxToolkitInstallCommandTest.php create mode 100644 Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php diff --git a/Toolkit/composer.json b/Toolkit/composer.json new file mode 100644 index 0000000000..a40d2ce097 --- /dev/null +++ b/Toolkit/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/ux-toolkit", + "type": "symfony-bundle", + "description": "Twig toolkit for Symfony", + "keywords": [ + "symfony-ux", + "twig", + "components" + ], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "ext-http": "*", + "tales-from-a-dev/twig-tailwind-extra": "^0.3.0", + "twig/extra-bundle": "^2.12|^3.0", + "twig/html-extra": "^3.19", + "twig/twig": "^2.12|^3.0" + }, + "autoload": { + "psr-4": { + "Symfony\\Ux\\Toolkit\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Symfony\\Ux\\Toolkit\\Tests\\": "tests/" + } + } +} diff --git a/Toolkit/composer.lock b/Toolkit/composer.lock new file mode 100644 index 0000000000..322e732b71 --- /dev/null +++ b/Toolkit/composer.lock @@ -0,0 +1,2871 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "9cd3783c95fdd7cdc8a19e27579ad9f8", + "packages": [ + { + "name": "gehrisandro/tailwind-merge-php", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/gehrisandro/tailwind-merge-php.git", + "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gehrisandro/tailwind-merge-php/zipball/dc11b9d4a625dd5be885900e5ef14c3efa260277", + "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277", + "shasum": "" + }, + "require": { + "php": "^8.1.0", + "psr/simple-cache": "^3.0" + }, + "require-dev": { + "laravel/pint": "^1.13.8", + "nunomaduro/collision": "^7.10", + "pestphp/pest": "^v2.24.0", + "pestphp/pest-plugin-arch": "^2.6", + "pestphp/pest-plugin-mock": "^2.0.0", + "pestphp/pest-plugin-type-coverage": "^2.8", + "phpstan/phpstan": "^1.10.55", + "rector/rector": "^1.0.5", + "symfony/var-dumper": "^6.4.2" + }, + "type": "library", + "autoload": { + "files": [ + "src/TailwindMerge.php" + ], + "psr-4": { + "TailwindMerge\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "TailwindMerge for PHP merges multiple Tailwind CSS classes by automatically resolving conflicts between them", + "keywords": [ + "classes", + "merge", + "php", + "tailwindcss" + ], + "support": { + "issues": "https://github.com/gehrisandro/tailwind-merge-php/issues", + "source": "https://github.com/gehrisandro/tailwind-merge-php/tree/v1.1.2" + }, + "funding": [ + { + "url": "https://github.com/gehrisandro", + "type": "github" + } + ], + "time": "2024-05-21T17:32:42+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "8d773a575e446de220dca03d600b2d8e1c1c10ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/8d773a575e446de220dca03d600b2d8e1c1c10ec", + "reference": "8d773a575e446de220dca03d600b2d8e1c1c10ec", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-27T11:08:17+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/config", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "7716594aaae91d9141be080240172a92ecca4d44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/7716594aaae91d9141be080240172a92ecca4d44", + "reference": "7716594aaae91d9141be080240172a92ecca4d44", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-22T12:07:01+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "1d321c4bc3fe926fd4c38999a4c9af4f5d61ddfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/1d321c4bc3fe926fd4c38999a4c9af4f5d61ddfc", + "reference": "1d321c4bc3fe926fd4c38999a4c9af4f5d61ddfc", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-17T10:56:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/959a74d044a6db21f4caa6d695648dcb5584cb49", + "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-07T09:39:55+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.2.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-30T19:00:17+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "d37a43dd0b2079605fcab3056dac71934f06dc0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/d37a43dd0b2079605fcab3056dac71934f06dc0f", + "reference": "d37a43dd0b2079605fcab3056dac71934f06dc0f", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/filesystem": "^7.1", + "symfony/finder": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^7.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^6.4|^7.0" + }, + "conflict": { + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/asset": "<6.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.4", + "symfony/console": "<6.4", + "symfony/dom-crawler": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/lock": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", + "symfony/security-core": "<6.4", + "symfony/security-csrf": "<7.2", + "symfony/serializer": "<7.1", + "symfony/stopwatch": "<6.4", + "symfony/translation": "<6.4", + "symfony/twig-bridge": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/webhook": "<7.2", + "symfony/workflow": "<6.4" + }, + "require-dev": { + "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/scheduler": "^6.4.4|^7.0.4", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/semaphore": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/type-info": "^7.1", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/webhook": "^7.2", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-29T07:13:55+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee1b504b8926198be89d05e5b6fc4c3810c090f0", + "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-17T10:56:55+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", + "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-29T07:40:13+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/2fc3b4bd67e4747e45195bc4c98bea4628476204", + "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-27T11:08:17+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/ee9a67edc6baa33e5fae662f94f91fd262930996", + "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-17T10:56:55+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v7.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "29e4c66de9618e67dc1f5f13bc667aca2a228f1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/29e4c66de9618e67dc1f5f13bc667aca2a228f1e", + "reference": "29e4c66de9618e67dc1f5f13bc667aca2a228f1e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.12" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.4", + "symfony/serializer": "<6.4", + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/emoji": "^7.1", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Twig with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bridge/tree/v7.2.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-19T14:25:03+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "cd2be4563afaef5285bb6e0a06c5445e644a5c01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/cd2be4563afaef5285bb6e0a06c5445e644a5c01", + "reference": "cd2be4563afaef5285bb6e0a06c5445e644a5c01", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.2", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "conflict": { + "symfony/framework-bundle": "<6.4", + "symfony/translation": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\TwigBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bundle/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-23T08:11:15+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "82b478c69745d8878eb60f9a049a4d584996f73a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a", + "reference": "82b478c69745d8878eb60f9a049a4d584996f73a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-17T11:39:41+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-18T07:58:17+00:00" + }, + { + "name": "tales-from-a-dev/twig-tailwind-extra", + "version": "v0.3.0", + "source": { + "type": "git", + "url": "https://github.com/tales-from-a-dev/twig-tailwind-extra.git", + "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tales-from-a-dev/twig-tailwind-extra/zipball/a3cb86414dd5810740cf91966bc1cf10047ce8ef", + "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef", + "shasum": "" + }, + "require": { + "gehrisandro/tailwind-merge-php": "^1.0", + "php": ">=8.2", + "symfony/cache": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", + "twig/twig": "^3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.38", + "symfony/phpunit-bridge": "^6.4 || ^7.0" + }, + "type": "twig", + "autoload": { + "psr-4": { + "TalesFromADev\\Twig\\Extra\\Tailwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Monteil", + "email": "monteil.romain@gmail.com" + } + ], + "description": "A Twig extension for Tailwind", + "homepage": "https://github.com/tales-from-a-dev/twig-tailwind-extra", + "keywords": [ + "extension", + "symfony", + "tailwind", + "twig" + ], + "support": { + "issues": "https://github.com/tales-from-a-dev/twig-tailwind-extra/issues", + "source": "https://github.com/tales-from-a-dev/twig-tailwind-extra/tree/v0.3.0" + }, + "time": "2024-08-07T23:27:08+00:00" + }, + { + "name": "twig/extra-bundle", + "version": "v3.19.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "9746573ca4bc1cd03a767a183faadaf84e0c31fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/9746573ca4bc1cd03a767a183faadaf84e0c31fa", + "reference": "9746573ca4bc1cd03a767a183faadaf84e0c31fa", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/framework-bundle": "^5.4|^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0", + "twig/twig": "^3.2|^4.0" + }, + "require-dev": { + "league/commonmark": "^1.0|^2.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "twig/cache-extra": "^3.0", + "twig/cssinliner-extra": "^3.0", + "twig/html-extra": "^3.0", + "twig/inky-extra": "^3.0", + "twig/intl-extra": "^3.0", + "twig/markdown-extra": "^3.0", + "twig/string-extra": "^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Twig\\Extra\\TwigExtraBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Symfony bundle for extra Twig extensions", + "homepage": "https://twig.symfony.com", + "keywords": [ + "bundle", + "extra", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.19.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-09-26T19:22:23+00:00" + }, + { + "name": "twig/html-extra", + "version": "v3.19.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/html-extra.git", + "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/c63b28e192c1b7c15bb60f81d2e48b140846239a", + "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/mime": "^5.4|^6.4|^7.0", + "twig/twig": "^3.13|^4.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Twig\\Extra\\Html\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for HTML", + "homepage": "https://twig.symfony.com", + "keywords": [ + "html", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/html-extra/tree/v3.19.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-12-29T10:29:59+00:00" + }, + { + "name": "twig/twig", + "version": "v3.19.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/d4f8c2b86374f08efc859323dbcd95c590f7124e", + "reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.19.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2025-01-29T07:06:14+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.2" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/Toolkit/phpunit.xml.dist b/Toolkit/phpunit.xml.dist new file mode 100644 index 0000000000..4164fb601e --- /dev/null +++ b/Toolkit/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + tests + + + diff --git a/Toolkit/src/Command/UxToolkitInstallCommand.php b/Toolkit/src/Command/UxToolkitInstallCommand.php new file mode 100644 index 0000000000..bd319cd714 --- /dev/null +++ b/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; +use Throwable; + +/** + * @author Jean-François Lépine + * + * @internal + */ +#[AsCommand( + name: 'ux:toolkit:install', + description: 'This command will install a new UX Component in your project', +)] +class UxToolkitInstallCommand extends Command +{ + public function __construct( + private readonly RepositoryFactory $repositoryFactory + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('component', InputArgument::REQUIRED, 'The component name (Ex: button) or repository URL') + ->addOption( + 'destination', + 'd', + InputArgument::OPTIONAL, + 'The destination directory', + 'templates/components/Ux' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $component = $input->getArgument('component'); + + // Get the correct source (remote or official) + try { + $repository = $this->repositoryFactory->factory($component); + $component = $repository->getName(); + } catch (Throwable $e) { + $io->error($e->getMessage()); + return Command::FAILURE; + } + + // Prepare destination + $directory = $input->getOption('destination'); + if (empty($directory)) { + $io->error('The destination directory is not valid'); + return Command::FAILURE; + } + + if (!file_exists(dirname($directory)) || !is_writable(dirname($directory))) { + $io->error(sprintf('The directory "%s" does not exist or is not writable', dirname($directory))); + return Command::FAILURE; + } + + if (!file_exists($directory)) { + mkdir($directory, 0777, true); + } + + + $filename = $directory . '/' . $component . '.html.twig'; + if (file_exists($filename)) { + $io->error(sprintf('The component "%s" already exists', $component)); + if (!$input->isInteractive()) { + return Command::FAILURE; + } + + // ask if we should overwrite the file + if (!$io->confirm('Do you want to overwrite the file?')) { + return Command::FAILURE; + } + } + + if (!is_writable(dirname($filename))) { + $io->error(sprintf('The directory "%s" is not writable', dirname($filename))); + return Command::FAILURE; + } + + file_put_contents($filename, $repository->getContent()); + + if ($io->isVerbose()) { + $io->text(sprintf('The component "%s" has been installed in "%s"', $component, $filename)); + } + + $io->success(sprintf('The component "%s" has been installed', $component)); + return Command::SUCCESS; + } +} diff --git a/Toolkit/src/ComponentRepository/ComponentRepository.php b/Toolkit/src/ComponentRepository/ComponentRepository.php new file mode 100644 index 0000000000..8582464d74 --- /dev/null +++ b/Toolkit/src/ComponentRepository/ComponentRepository.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\ComponentRepository; + +/** + * @author Jean-François Lépine + * + * @internal + */ +interface ComponentRepository +{ + public function getContent(): string; + + public function getName(): string; +} \ No newline at end of file diff --git a/Toolkit/src/ComponentRepository/GithubRepository.php b/Toolkit/src/ComponentRepository/GithubRepository.php new file mode 100644 index 0000000000..27fe28d940 --- /dev/null +++ b/Toolkit/src/ComponentRepository/GithubRepository.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\ComponentRepository; + +/** + * @author Jean-François Lépine + * + * @internal + */ +final readonly class GithubRepository implements ComponentRepository +{ + + public function __construct( + private string $vendor, + private string $package, + private string $name, + private ?string $version = 'main' + ) { + } + + public function getContent(): string + { + $url = sprintf( + 'https://raw.githubusercontent.com/%s/%s/%s/templates/components/%s.html.twig', + $this->vendor, + $this->package, + $this->version, + $this->name + ); + + return file_get_contents($url); + } + + public function getName(): string + { + return $this->name; + } +} \ No newline at end of file diff --git a/Toolkit/src/ComponentRepository/OfficialRepository.php b/Toolkit/src/ComponentRepository/OfficialRepository.php new file mode 100644 index 0000000000..c63dc33e3f --- /dev/null +++ b/Toolkit/src/ComponentRepository/OfficialRepository.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\ComponentRepository; + +use InvalidArgumentException; + +/** + * @author Jean-François Lépine + * + * @internal + */ +final readonly class OfficialRepository implements ComponentRepository +{ + + public function __construct( + private string $name + ) { + } + + public function getContent(): string + { + $source = sprintf(__DIR__ . '/../../templates/components/%s.html.twig', $this->name); + if (!file_exists($source) || !is_readable($source)) { + throw new InvalidArgumentException(sprintf('The component "%s" does not exist', $this->name)); + } + + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + throw new InvalidArgumentException( + sprintf('Error reading file "%s" at line %d: %s', $errfile, $errline, $errstr) + ); + }); + $content = file_get_contents($source); + restore_error_handler(); + + return $content; + } + + public function getName(): string + { + return $this->name; + } +} \ No newline at end of file diff --git a/Toolkit/src/ComponentRepository/RepositoryFactory.php b/Toolkit/src/ComponentRepository/RepositoryFactory.php new file mode 100644 index 0000000000..84a89bf3ed --- /dev/null +++ b/Toolkit/src/ComponentRepository/RepositoryFactory.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\ComponentRepository; + +use InvalidArgumentException; + +/** + * @author Jean-François Lépine + * + * @internal + */ +final class RepositoryFactory +{ + public function factory(string $name): ComponentRepository + { + if (preg_match('!^\w+$!', $name)) { + return new OfficialRepository(ucfirst($name)); + } + + // github.com/vendor/package:component@version + // github.com/vendor/package:component (version is optional) + $name = preg_replace('!^(https://|http://)!', '', $name); + if (preg_match('!^github.com/(\w+)/(\w+):(\w+)(@\w+)?$!', $name, $matches)) { + return new GithubRepository( + $matches[1], + $matches[2], + ucfirst($matches[3]), + $matches[4] ?? 'main' + ); + } + + throw new InvalidArgumentException('Source is not supported for this component'); + } +} \ No newline at end of file diff --git a/Toolkit/src/UxToolkitBundle.php b/Toolkit/src/UxToolkitBundle.php new file mode 100644 index 0000000000..b080ace2b2 --- /dev/null +++ b/Toolkit/src/UxToolkitBundle.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\AbstractBundle; +use Symfony\Ux\Toolkit\Command\UxToolkitInstallCommand; +use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; + +/** + * @author Jean-François Lépine + */ +class UxToolkitBundle extends AbstractBundle +{ + + public function build(ContainerBuilder $container): void + { + parent::build($container); + + $container->autowire(RepositoryFactory::class); + + $container->autowire(UxToolkitInstallCommand::class); + $container + ->registerForAutoconfiguration(UxToolkitInstallCommand::class) + ->addTag('console.command'); + + $container + ->getDefinition(UxToolkitInstallCommand::class) + ->setPublic(true) + ->addTag('console.command') + ; + } + +} + diff --git a/Toolkit/templates/components/Badge.html.twig b/Toolkit/templates/components/Badge.html.twig new file mode 100644 index 0000000000..f15cd2bb26 --- /dev/null +++ b/Toolkit/templates/components/Badge.html.twig @@ -0,0 +1,38 @@ +{% props + default=false, + secondary=false, + destructive=false, + outline=false, +%} + + +{% set config = html_cva( + base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', + variants: { + variant: { + default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground" + }, + defaultVariant: { + variant: 'default', + } + } +) %} + +{% set variant = "default" %} +{% if secondary %} + {% set variant = "secondary" %} +{% elseif destructive %} + {% set variant = "destructive" %} +{% elseif outline %} + {% set variant = "outline" %} +{% endif %} + +
+ {% block content %}{% endblock %} +
diff --git a/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/Toolkit/tests/Command/UxToolkitInstallCommandTest.php new file mode 100644 index 0000000000..52c4b44eca --- /dev/null +++ b/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Ux\Toolkit\Command\UxToolkitInstallCommand; +use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; + +/** + * @author Jean-François Lépine + */ +class UxToolkitInstallCommandTest extends TestCase +{ + + + public function testShouldAbleToCreateTheBadgeComponent(): void + { + $repositoryFactory = new RepositoryFactory(); + $command = new UxToolkitInstallCommand($repositoryFactory); + + $destination = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(); + mkdir($destination); + + $input = new ArrayInput([ + 'component' => 'badge', + '--destination' => $destination + ]); + + $input->setInteractive(false); + $output = new BufferedOutput(); + + $result = $command->run($input, $output); + + // Command should be successful + $this->assertEquals(Command::SUCCESS, $result); + + // A file should be created + $expectedFile = __DIR__ . '/../../templates/components/Badge.html.twig'; + $this->assertFileExists($expectedFile); + + // The content of the file should be the same as the content of the Badge component + $expectedContent = file_get_contents(__DIR__ . '/../../templates/components/Badge.html.twig'); + $actualContent = file_get_contents($expectedFile); + $this->assertEquals($expectedContent, $actualContent); + } + + public function testByDefaultCannotEraseComponentByMistake(): void + { + $repositoryFactory = new RepositoryFactory(); + $command = new UxToolkitInstallCommand($repositoryFactory); + + $destination = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(); + mkdir($destination); + + $input = new ArrayInput([ + 'component' => 'badge', + '--destination' => $destination + ]); + + $input->setInteractive(false); + $output = new BufferedOutput(); + + $result = $command->run($input, $output); + $this->assertEquals(Command::SUCCESS, $result); + + // run again => should fail (non interactive mode) + $result = $command->run($input, $output); + $this->assertEquals(Command::FAILURE, $result); + } +} \ No newline at end of file diff --git a/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php b/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php new file mode 100644 index 0000000000..dbec3ce967 --- /dev/null +++ b/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\Tests\ComponentRepository; + +use PHPUnit\Framework\TestCase; +use Symfony\Ux\Toolkit\ComponentRepository\GithubRepository; +use Symfony\Ux\Toolkit\ComponentRepository\OfficialRepository; +use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; + +/** + * @author Jean-François Lépine + */ +class RepositoryFactoryTest extends TestCase +{ + public function testItShouldFactoryRepositoryAccordingToItsName(): void + { + $factory = new RepositoryFactory(); + + $this->assertInstanceOf(OfficialRepository::class, $factory->factory('button')); + $this->assertInstanceOf(GithubRepository::class, $factory->factory('github.com/symfony/bar:button')); + $this->assertInstanceOf(GithubRepository::class, $factory->factory('github.com/foo/bar:button@dev')); + $this->assertInstanceOf(GithubRepository::class, $factory->factory('https://github.com/foo/bar:button@dev')); + + $official = $factory->factory('button'); + $github = $factory->factory('github.com/symfony/bar:button@version'); + + $this->assertEquals('Button', $official->getName()); + $this->assertEquals('Button', $github->getName()); + } +} \ No newline at end of file From bd9379894087c79ad17eb428fb0da97e06eb6c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Thu, 30 Jan 2025 21:03:31 +0100 Subject: [PATCH 02/51] fixed path --- {Toolkit => src/Toolkit}/composer.json | 10 +- {Toolkit => src/Toolkit}/composer.lock | 264 +++++++++++++++++- {Toolkit => src/Toolkit}/phpunit.xml.dist | 0 .../src/Command/UxToolkitInstallCommand.php | 0 .../ComponentRepository.php | 0 .../ComponentRepository/GithubRepository.php | 0 .../OfficialRepository.php | 0 .../ComponentRepository/RepositoryFactory.php | 0 .../Toolkit}/src/UxToolkitBundle.php | 0 .../templates/components/Badge.html.twig | 0 .../Command/UxToolkitInstallCommandTest.php | 0 .../RepositoryFactoryTest.php | 0 12 files changed, 269 insertions(+), 5 deletions(-) rename {Toolkit => src/Toolkit}/composer.json (79%) rename {Toolkit => src/Toolkit}/composer.lock (91%) rename {Toolkit => src/Toolkit}/phpunit.xml.dist (100%) rename {Toolkit => src/Toolkit}/src/Command/UxToolkitInstallCommand.php (100%) rename {Toolkit => src/Toolkit}/src/ComponentRepository/ComponentRepository.php (100%) rename {Toolkit => src/Toolkit}/src/ComponentRepository/GithubRepository.php (100%) rename {Toolkit => src/Toolkit}/src/ComponentRepository/OfficialRepository.php (100%) rename {Toolkit => src/Toolkit}/src/ComponentRepository/RepositoryFactory.php (100%) rename {Toolkit => src/Toolkit}/src/UxToolkitBundle.php (100%) rename {Toolkit => src/Toolkit}/templates/components/Badge.html.twig (100%) rename {Toolkit => src/Toolkit}/tests/Command/UxToolkitInstallCommandTest.php (100%) rename {Toolkit => src/Toolkit}/tests/ComponentRepository/RepositoryFactoryTest.php (100%) diff --git a/Toolkit/composer.json b/src/Toolkit/composer.json similarity index 79% rename from Toolkit/composer.json rename to src/Toolkit/composer.json index a40d2ce097..fa4f313e29 100644 --- a/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -17,11 +17,11 @@ ], "require": { "php": ">=8.2", - "ext-http": "*", "tales-from-a-dev/twig-tailwind-extra": "^0.3.0", "twig/extra-bundle": "^2.12|^3.0", "twig/html-extra": "^3.19", - "twig/twig": "^2.12|^3.0" + "twig/twig": "^2.12|^3.0", + "symfony/console": "^7.2" }, "autoload": { "psr-4": { @@ -32,5 +32,11 @@ "psr-4": { "Symfony\\Ux\\Toolkit\\Tests\\": "tests/" } + }, + "extra": { + "thanks": { + "name": "symfony/ux", + "url": "https://github.com/symfony/ux" + } } } diff --git a/Toolkit/composer.lock b/src/Toolkit/composer.lock similarity index 91% rename from Toolkit/composer.lock rename to src/Toolkit/composer.lock index 322e732b71..bc31a79b4a 100644 --- a/Toolkit/composer.lock +++ b/src/Toolkit/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9cd3783c95fdd7cdc8a19e27579ad9f8", + "content-hash": "6c65d84b4115c8f0703588319f528b36", "packages": [ { "name": "gehrisandro/tailwind-merge-php", @@ -575,6 +575,99 @@ ], "time": "2025-01-22T12:07:01+00:00" }, + { + "name": "symfony/console", + "version": "v7.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-11T03:49:26+00:00" + }, { "name": "symfony/dependency-injection", "version": "v7.2.3", @@ -1588,6 +1681,84 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-intl-idn", "version": "v1.31.0", @@ -2148,6 +2319,93 @@ ], "time": "2024-09-25T14:20:29+00:00" }, + { + "name": "symfony/string", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-13T13:31:26+00:00" + }, { "name": "symfony/translation-contracts", "version": "v3.5.1", @@ -2860,12 +3118,12 @@ "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.2" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/Toolkit/phpunit.xml.dist b/src/Toolkit/phpunit.xml.dist similarity index 100% rename from Toolkit/phpunit.xml.dist rename to src/Toolkit/phpunit.xml.dist diff --git a/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php similarity index 100% rename from Toolkit/src/Command/UxToolkitInstallCommand.php rename to src/Toolkit/src/Command/UxToolkitInstallCommand.php diff --git a/Toolkit/src/ComponentRepository/ComponentRepository.php b/src/Toolkit/src/ComponentRepository/ComponentRepository.php similarity index 100% rename from Toolkit/src/ComponentRepository/ComponentRepository.php rename to src/Toolkit/src/ComponentRepository/ComponentRepository.php diff --git a/Toolkit/src/ComponentRepository/GithubRepository.php b/src/Toolkit/src/ComponentRepository/GithubRepository.php similarity index 100% rename from Toolkit/src/ComponentRepository/GithubRepository.php rename to src/Toolkit/src/ComponentRepository/GithubRepository.php diff --git a/Toolkit/src/ComponentRepository/OfficialRepository.php b/src/Toolkit/src/ComponentRepository/OfficialRepository.php similarity index 100% rename from Toolkit/src/ComponentRepository/OfficialRepository.php rename to src/Toolkit/src/ComponentRepository/OfficialRepository.php diff --git a/Toolkit/src/ComponentRepository/RepositoryFactory.php b/src/Toolkit/src/ComponentRepository/RepositoryFactory.php similarity index 100% rename from Toolkit/src/ComponentRepository/RepositoryFactory.php rename to src/Toolkit/src/ComponentRepository/RepositoryFactory.php diff --git a/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php similarity index 100% rename from Toolkit/src/UxToolkitBundle.php rename to src/Toolkit/src/UxToolkitBundle.php diff --git a/Toolkit/templates/components/Badge.html.twig b/src/Toolkit/templates/components/Badge.html.twig similarity index 100% rename from Toolkit/templates/components/Badge.html.twig rename to src/Toolkit/templates/components/Badge.html.twig diff --git a/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php similarity index 100% rename from Toolkit/tests/Command/UxToolkitInstallCommandTest.php rename to src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php diff --git a/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php b/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php similarity index 100% rename from Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php rename to src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php From 9557e28704c9a0f493429115790101770be67459 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Thu, 30 Jan 2025 08:50:08 +0100 Subject: [PATCH 03/51] [Toolkit] Init the component --- src/Toolkit/.gitattributes | 5 +++ src/Toolkit/.github/PULL_REQUEST_TEMPLATE.md | 8 +++++ .../.github/workflows/close-pull-request.yml | 20 +++++++++++ src/Toolkit/.gitignore | 3 ++ src/Toolkit/.symfony.bundle.yaml | 3 ++ src/Toolkit/CHANGELOG.md | 5 +++ src/Toolkit/LICENSE | 19 ++++++++++ src/Toolkit/README.md | 16 +++++++++ src/Toolkit/composer.json | 11 ++++-- src/Toolkit/config/services.php | 21 +++++++++++ src/Toolkit/doc/index.rst | 28 +++++++++++++++ src/Toolkit/phpunit.xml.dist | 10 ++++++ src/Toolkit/src/UXToolkitBundle.php | 35 +++++++++++++++++++ 13 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 src/Toolkit/.gitattributes create mode 100644 src/Toolkit/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 src/Toolkit/.github/workflows/close-pull-request.yml create mode 100644 src/Toolkit/.gitignore create mode 100644 src/Toolkit/.symfony.bundle.yaml create mode 100644 src/Toolkit/CHANGELOG.md create mode 100644 src/Toolkit/LICENSE create mode 100644 src/Toolkit/README.md create mode 100644 src/Toolkit/config/services.php create mode 100644 src/Toolkit/doc/index.rst create mode 100644 src/Toolkit/src/UXToolkitBundle.php diff --git a/src/Toolkit/.gitattributes b/src/Toolkit/.gitattributes new file mode 100644 index 0000000000..81d9dbfaa9 --- /dev/null +++ b/src/Toolkit/.gitattributes @@ -0,0 +1,5 @@ +/.git* export-ignore +/.symfony.bundle.yaml export-ignore +/phpunit.xml.dist export-ignore +/doc export-ignore +/tests export-ignore diff --git a/src/Toolkit/.github/PULL_REQUEST_TEMPLATE.md b/src/Toolkit/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..df3b474b45 --- /dev/null +++ b/src/Toolkit/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Please do not submit any Pull Requests here. They will be closed. +--- + +Please submit your PR here instead: +https://github.com/symfony/ux + +This repository is what we call a "subtree split": a read-only subset of that main repository. +We're looking forward to your PR there! diff --git a/src/Toolkit/.github/workflows/close-pull-request.yml b/src/Toolkit/.github/workflows/close-pull-request.yml new file mode 100644 index 0000000000..57e4e3fb07 --- /dev/null +++ b/src/Toolkit/.github/workflows/close-pull-request.yml @@ -0,0 +1,20 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: | + Thanks for your Pull Request! We love contributions. + + However, you should instead open your PR on the main repository: + https://github.com/symfony/ux + + This repository is what we call a "subtree split": a read-only subset of that main repository. + We're looking forward to your PR there! diff --git a/src/Toolkit/.gitignore b/src/Toolkit/.gitignore new file mode 100644 index 0000000000..50b321e33a --- /dev/null +++ b/src/Toolkit/.gitignore @@ -0,0 +1,3 @@ +vendor +composer.lock +.phpunit.result.cache diff --git a/src/Toolkit/.symfony.bundle.yaml b/src/Toolkit/.symfony.bundle.yaml new file mode 100644 index 0000000000..6d9a74acb7 --- /dev/null +++ b/src/Toolkit/.symfony.bundle.yaml @@ -0,0 +1,3 @@ +branches: ["2.x"] +maintained_branches: ["2.x"] +doc_dir: "doc" diff --git a/src/Toolkit/CHANGELOG.md b/src/Toolkit/CHANGELOG.md new file mode 100644 index 0000000000..6c851b51a4 --- /dev/null +++ b/src/Toolkit/CHANGELOG.md @@ -0,0 +1,5 @@ +# CHANGELOG + +## 2.23 + +- Component added diff --git a/src/Toolkit/LICENSE b/src/Toolkit/LICENSE new file mode 100644 index 0000000000..bc38d714ef --- /dev/null +++ b/src/Toolkit/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2025-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Toolkit/README.md b/src/Toolkit/README.md new file mode 100644 index 0000000000..bb6e5d0d96 --- /dev/null +++ b/src/Toolkit/README.md @@ -0,0 +1,16 @@ +# Symfony UX Toolkit + +**EXPERIMENTAL** This component is currently experimental and is +likely to change, or even change drastically. + +Symfony UX Toolkit provides a set of ready-to-use UI components for Symfony applications. + +**This repository is a READ-ONLY sub-tree split**. See +https://github.com/symfony/ux to create issues or submit pull requests. + +## Resources + +- [Documentation](https://symfony.com/bundles/ux-toolkit/current/index.html) +- [Report issues](https://github.com/symfony/ux/issues) and + [send Pull Requests](https://github.com/symfony/ux/pulls) + in the [main Symfony UX repository](https://github.com/symfony/ux) diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index fa4f313e29..f7176c4d58 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -21,18 +21,25 @@ "twig/extra-bundle": "^2.12|^3.0", "twig/html-extra": "^3.19", "twig/twig": "^2.12|^3.0", - "symfony/console": "^7.2" + "symfony/console": "^7.2", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0" }, "autoload": { "psr-4": { "Symfony\\Ux\\Toolkit\\": "src" - } + }, + "exclude-from-classmap": [] }, "autoload-dev": { "psr-4": { "Symfony\\Ux\\Toolkit\\Tests\\": "tests/" } }, + "conflict": { + "symfony/ux-twig-component": "<2.21" + }, "extra": { "thanks": { "name": "symfony/ux", diff --git a/src/Toolkit/config/services.php b/src/Toolkit/config/services.php new file mode 100644 index 0000000000..86e4656058 --- /dev/null +++ b/src/Toolkit/config/services.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +/* + * @author Hugo Alliaume + */ +return static function (ContainerConfigurator $container): void { + $container->services() + + ; +}; diff --git a/src/Toolkit/doc/index.rst b/src/Toolkit/doc/index.rst new file mode 100644 index 0000000000..7bc10576c1 --- /dev/null +++ b/src/Toolkit/doc/index.rst @@ -0,0 +1,28 @@ +Symfony UX Toolkit +================== + +**EXPERIMENTAL** This component is currently experimental and is likely +to change, or even change drastically. + +Symfony UX Toolkit provides a set of ready-to-use UI components for Symfony applications. +It is part of `the Symfony UX initiative`_. + +Installation +------------ + +TODO + +Configuration +------------- + +TODO + +Backward Compatibility promise +------------------------------ + +This bundle aims at following the same Backward Compatibility promise as +the Symfony framework: +https://symfony.com/doc/current/contributing/code/bc.html + +.. _`the Symfony UX initiative`: https://ux.symfony.com/ +.. _`Twig Component`: https://symfony.com/bundles/ux-twig-component/current/index.html diff --git a/src/Toolkit/phpunit.xml.dist b/src/Toolkit/phpunit.xml.dist index 4164fb601e..42c5a43e94 100644 --- a/src/Toolkit/phpunit.xml.dist +++ b/src/Toolkit/phpunit.xml.dist @@ -6,7 +6,15 @@ backupGlobals="false" colors="true" bootstrap="vendor/autoload.php" + failOnRisky="true" + failOnWarning="true" > + + + ./src + + + @@ -14,6 +22,8 @@ + + diff --git a/src/Toolkit/src/UXToolkitBundle.php b/src/Toolkit/src/UXToolkitBundle.php new file mode 100644 index 0000000000..474abdaa07 --- /dev/null +++ b/src/Toolkit/src/UXToolkitBundle.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit; + +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + +/** + * @author Hugo Alliaume + */ +final class UXToolkitBundle extends AbstractBundle +{ + protected string $extensionAlias = 'ux_toolkit'; + + public function configure(DefinitionConfigurator $definition): void + { + // TODO + } + + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + $container->import('../config/services.php'); + } +} From 0044e0d3bf6e14c3f90b6b14e2f43922bf905994 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Thu, 30 Jan 2025 09:28:03 +0100 Subject: [PATCH 04/51] [Toolkit] Prepare registry --- src/Toolkit/bin/build-registry.php | 4 ++++ .../registry/default/examples/ButtonOutline.html.twig | 3 +++ src/Toolkit/registry/default/ui/Alert.twig | 5 +++++ src/Toolkit/registry/default/ui/Badge.twig | 5 +++++ src/Toolkit/registry/default/ui/Button.html.twig | 5 +++++ src/Toolkit/registry/default/ui/Card.twig | 5 +++++ src/Toolkit/registry/default/ui/Navbar.twig | 5 +++++ src/Toolkit/registry/default/ui/Table.twig | 5 +++++ src/Toolkit/registry/default/ui/Table/Row.twig | 5 +++++ 9 files changed, 42 insertions(+) create mode 100755 src/Toolkit/bin/build-registry.php create mode 100644 src/Toolkit/registry/default/examples/ButtonOutline.html.twig create mode 100644 src/Toolkit/registry/default/ui/Alert.twig create mode 100644 src/Toolkit/registry/default/ui/Badge.twig create mode 100644 src/Toolkit/registry/default/ui/Button.html.twig create mode 100644 src/Toolkit/registry/default/ui/Card.twig create mode 100644 src/Toolkit/registry/default/ui/Navbar.twig create mode 100644 src/Toolkit/registry/default/ui/Table.twig create mode 100644 src/Toolkit/registry/default/ui/Table/Row.twig diff --git a/src/Toolkit/bin/build-registry.php b/src/Toolkit/bin/build-registry.php new file mode 100755 index 0000000000..0e8614bd9b --- /dev/null +++ b/src/Toolkit/bin/build-registry.php @@ -0,0 +1,4 @@ +#!/usr/bin/env php + + + diff --git a/src/Toolkit/registry/default/ui/Alert.twig b/src/Toolkit/registry/default/ui/Alert.twig new file mode 100644 index 0000000000..a3523268db --- /dev/null +++ b/src/Toolkit/registry/default/ui/Alert.twig @@ -0,0 +1,5 @@ +
+ {% block content %}Alert{% endblock %} +
diff --git a/src/Toolkit/registry/default/ui/Badge.twig b/src/Toolkit/registry/default/ui/Badge.twig new file mode 100644 index 0000000000..fb68a2e55f --- /dev/null +++ b/src/Toolkit/registry/default/ui/Badge.twig @@ -0,0 +1,5 @@ +
+ {% block content %}Badge{% endblock %} +
diff --git a/src/Toolkit/registry/default/ui/Button.html.twig b/src/Toolkit/registry/default/ui/Button.html.twig new file mode 100644 index 0000000000..7c75b3e94e --- /dev/null +++ b/src/Toolkit/registry/default/ui/Button.html.twig @@ -0,0 +1,5 @@ + diff --git a/src/Toolkit/registry/default/ui/Card.twig b/src/Toolkit/registry/default/ui/Card.twig new file mode 100644 index 0000000000..fea8ed360e --- /dev/null +++ b/src/Toolkit/registry/default/ui/Card.twig @@ -0,0 +1,5 @@ +
+ {% block content %}{% endblock %} +
diff --git a/src/Toolkit/registry/default/ui/Navbar.twig b/src/Toolkit/registry/default/ui/Navbar.twig new file mode 100644 index 0000000000..60a10b244d --- /dev/null +++ b/src/Toolkit/registry/default/ui/Navbar.twig @@ -0,0 +1,5 @@ + diff --git a/src/Toolkit/registry/default/ui/Table.twig b/src/Toolkit/registry/default/ui/Table.twig new file mode 100644 index 0000000000..3ca92b85db --- /dev/null +++ b/src/Toolkit/registry/default/ui/Table.twig @@ -0,0 +1,5 @@ + + {% block content %}{% endblock %} +
diff --git a/src/Toolkit/registry/default/ui/Table/Row.twig b/src/Toolkit/registry/default/ui/Table/Row.twig new file mode 100644 index 0000000000..452cfd3628 --- /dev/null +++ b/src/Toolkit/registry/default/ui/Table/Row.twig @@ -0,0 +1,5 @@ + + {% block content %}Button{% endblock %} + From d9db7d78ce9fadf752a8e1d3b8e4df55b4476d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Thu, 30 Jan 2025 21:25:37 +0100 Subject: [PATCH 05/51] merged different versions of component --- src/Toolkit/composer.json | 12 +++ src/Toolkit/composer.lock | 84 ++++++++++++++++++- src/Toolkit/config/services.php | 21 ----- src/Toolkit/registry/default/ui/Badge.twig | 5 -- src/Toolkit/src/UXToolkitBundle.php | 35 -------- src/Toolkit/src/UxToolkitBundle.php | 1 + .../ui => templates/components}/Alert.twig | 0 .../components}/Button.html.twig | 0 .../ui => templates/components}/Card.twig | 0 .../ui => templates/components}/Navbar.twig | 0 .../ui => templates/components}/Table.twig | 0 .../components}/Table/Row.twig | 0 12 files changed, 96 insertions(+), 62 deletions(-) delete mode 100644 src/Toolkit/config/services.php delete mode 100644 src/Toolkit/registry/default/ui/Badge.twig delete mode 100644 src/Toolkit/src/UXToolkitBundle.php rename src/Toolkit/{registry/default/ui => templates/components}/Alert.twig (100%) rename src/Toolkit/{registry/default/ui => templates/components}/Button.html.twig (100%) rename src/Toolkit/{registry/default/ui => templates/components}/Card.twig (100%) rename src/Toolkit/{registry/default/ui => templates/components}/Navbar.twig (100%) rename src/Toolkit/{registry/default/ui => templates/components}/Table.twig (100%) rename src/Toolkit/{registry/default/ui => templates/components}/Table/Row.twig (100%) diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index f7176c4d58..cb0b0331f2 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -13,6 +13,18 @@ { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" + }, + { + "name": "Hugo Alliaume", + "email": "hugo@alliau.me" + }, + { + "name": "Jean-François Lépine", + "email": "lepinejeanfrancois@gmail.com" + }, + { + "name": "Simon André", + "email": "smn.andre@gmail.com" } ], "require": { diff --git a/src/Toolkit/composer.lock b/src/Toolkit/composer.lock index bc31a79b4a..845c808990 100644 --- a/src/Toolkit/composer.lock +++ b/src/Toolkit/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6c65d84b4115c8f0703588319f528b36", + "content-hash": "5bbeafcbe298c7c8aef27c62709dedb6", "packages": [ { "name": "gehrisandro/tailwind-merge-php", @@ -1602,6 +1602,88 @@ ], "time": "2025-01-27T11:08:17+00:00" }, + { + "name": "symfony/phpunit-bridge", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/phpunit-bridge.git", + "reference": "2bbde92ab25a0e2c88160857af7be9db5da0d145" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/2bbde92ab25a0e2c88160857af7be9db5da0d145", + "reference": "2bbde92ab25a0e2c88160857af7be9db5da0d145", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "conflict": { + "phpunit/phpunit": "<7.5|9.1.2" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.4|^7.0", + "symfony/polyfill-php81": "^1.27" + }, + "bin": [ + "bin/simple-phpunit" + ], + "type": "symfony-bridge", + "extra": { + "thanks": { + "url": "https://github.com/sebastianbergmann/phpunit", + "name": "phpunit/phpunit" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Bridge\\PhpUnit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/", + "/bin/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides utilities for PHPUnit, especially user deprecation notices management", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-13T16:15:23+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.31.0", diff --git a/src/Toolkit/config/services.php b/src/Toolkit/config/services.php deleted file mode 100644 index 86e4656058..0000000000 --- a/src/Toolkit/config/services.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Loader\Configurator; - -/* - * @author Hugo Alliaume - */ -return static function (ContainerConfigurator $container): void { - $container->services() - - ; -}; diff --git a/src/Toolkit/registry/default/ui/Badge.twig b/src/Toolkit/registry/default/ui/Badge.twig deleted file mode 100644 index fb68a2e55f..0000000000 --- a/src/Toolkit/registry/default/ui/Badge.twig +++ /dev/null @@ -1,5 +0,0 @@ -
- {% block content %}Badge{% endblock %} -
diff --git a/src/Toolkit/src/UXToolkitBundle.php b/src/Toolkit/src/UXToolkitBundle.php deleted file mode 100644 index 474abdaa07..0000000000 --- a/src/Toolkit/src/UXToolkitBundle.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit; - -use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; -use Symfony\Component\HttpKernel\Bundle\AbstractBundle; - -/** - * @author Hugo Alliaume - */ -final class UXToolkitBundle extends AbstractBundle -{ - protected string $extensionAlias = 'ux_toolkit'; - - public function configure(DefinitionConfigurator $definition): void - { - // TODO - } - - public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void - { - $container->import('../config/services.php'); - } -} diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index b080ace2b2..eb3adc8be9 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -18,6 +18,7 @@ /** * @author Jean-François Lépine + * @author Hugo Alliaume */ class UxToolkitBundle extends AbstractBundle { diff --git a/src/Toolkit/registry/default/ui/Alert.twig b/src/Toolkit/templates/components/Alert.twig similarity index 100% rename from src/Toolkit/registry/default/ui/Alert.twig rename to src/Toolkit/templates/components/Alert.twig diff --git a/src/Toolkit/registry/default/ui/Button.html.twig b/src/Toolkit/templates/components/Button.html.twig similarity index 100% rename from src/Toolkit/registry/default/ui/Button.html.twig rename to src/Toolkit/templates/components/Button.html.twig diff --git a/src/Toolkit/registry/default/ui/Card.twig b/src/Toolkit/templates/components/Card.twig similarity index 100% rename from src/Toolkit/registry/default/ui/Card.twig rename to src/Toolkit/templates/components/Card.twig diff --git a/src/Toolkit/registry/default/ui/Navbar.twig b/src/Toolkit/templates/components/Navbar.twig similarity index 100% rename from src/Toolkit/registry/default/ui/Navbar.twig rename to src/Toolkit/templates/components/Navbar.twig diff --git a/src/Toolkit/registry/default/ui/Table.twig b/src/Toolkit/templates/components/Table.twig similarity index 100% rename from src/Toolkit/registry/default/ui/Table.twig rename to src/Toolkit/templates/components/Table.twig diff --git a/src/Toolkit/registry/default/ui/Table/Row.twig b/src/Toolkit/templates/components/Table/Row.twig similarity index 100% rename from src/Toolkit/registry/default/ui/Table/Row.twig rename to src/Toolkit/templates/components/Table/Row.twig From 710985fd913dee4ad4f6dc3871b413798c6a34aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Thu, 30 Jan 2025 22:24:02 +0100 Subject: [PATCH 06/51] added tools for testing components --- src/Toolkit/.gitignore | 1 + src/Toolkit/composer.json | 3 +- src/Toolkit/composer.lock | 321 +++++++++++++++++- src/Toolkit/phpunit.xml.dist | 1 + .../src/Command/UxToolkitInstallCommand.php | 2 +- .../OfficialRepository.php | 2 +- .../templates/components/{ => ux}/Alert.twig | 0 .../components/{ => ux}/Badge.html.twig | 0 .../components/{ => ux}/Button.html.twig | 0 .../templates/components/{ => ux}/Card.twig | 0 .../templates/components/{ => ux}/Navbar.twig | 0 .../templates/components/{ => ux}/Table.twig | 1 + .../components/{ => ux}/Table/Row.twig | 0 .../Command/UxToolkitInstallCommandTest.php | 4 +- src/Toolkit/tests/Component/BadgeTest.php | 42 +++ src/Toolkit/tests/Fixtures/Kernel.php | 59 ++++ 16 files changed, 430 insertions(+), 6 deletions(-) rename src/Toolkit/templates/components/{ => ux}/Alert.twig (100%) rename src/Toolkit/templates/components/{ => ux}/Badge.html.twig (100%) rename src/Toolkit/templates/components/{ => ux}/Button.html.twig (100%) rename src/Toolkit/templates/components/{ => ux}/Card.twig (100%) rename src/Toolkit/templates/components/{ => ux}/Navbar.twig (100%) rename src/Toolkit/templates/components/{ => ux}/Table.twig (85%) rename src/Toolkit/templates/components/{ => ux}/Table/Row.twig (100%) create mode 100644 src/Toolkit/tests/Component/BadgeTest.php create mode 100644 src/Toolkit/tests/Fixtures/Kernel.php diff --git a/src/Toolkit/.gitignore b/src/Toolkit/.gitignore index 50b321e33a..f264a1213b 100644 --- a/src/Toolkit/.gitignore +++ b/src/Toolkit/.gitignore @@ -1,3 +1,4 @@ vendor composer.lock .phpunit.result.cache +var \ No newline at end of file diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index cb0b0331f2..3a95d51bd0 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -36,7 +36,8 @@ "symfony/console": "^7.2", "symfony/framework-bundle": "^6.4|^7.0", "symfony/phpunit-bridge": "^6.4|^7.0", - "symfony/twig-bundle": "^6.4|^7.0" + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/ux-twig-component": "^2.22" }, "autoload": { "psr-4": { diff --git a/src/Toolkit/composer.lock b/src/Toolkit/composer.lock index 845c808990..ed6fd21a0a 100644 --- a/src/Toolkit/composer.lock +++ b/src/Toolkit/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5bbeafcbe298c7c8aef27c62709dedb6", + "content-hash": "c2568dddd4d88908cb27985a17efa397", "packages": [ { "name": "gehrisandro/tailwind-merge-php", @@ -2237,6 +2237,167 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/property-access", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "b28732e315d81fbec787f838034de7d6c9b2b902" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/b28732e315d81fbec787f838034de7d6c9b2b902", + "reference": "b28732e315d81fbec787f838034de7d6c9b2b902", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/property-info": "^6.4|^7.0" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-17T10:56:55+00:00" + }, + { + "name": "symfony/property-info", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "dedb118fd588a92f226b390250b384d25f4192fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/dedb118fd588a92f226b390250b384d25f4192fe", + "reference": "dedb118fd588a92f226b390250b384d25f4192fe", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "~7.1.9|^7.2.2" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-27T11:08:17+00:00" + }, { "name": "symfony/routing", "version": "v7.2.3", @@ -2760,6 +2921,164 @@ ], "time": "2024-10-23T08:11:15+00:00" }, + { + "name": "symfony/type-info", + "version": "v7.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "3b5a17470fff0034f25fd4287cbdaa0010d2f749" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/3b5a17470fff0034f25fd4287cbdaa0010d2f749", + "reference": "3b5a17470fff0034f25fd4287cbdaa0010d2f749", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.0|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v7.2.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-20T13:38:37+00:00" + }, + { + "name": "symfony/ux-twig-component", + "version": "v2.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-twig-component.git", + "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/9b347f6ca2d9e18cee630787f0a6aa453982bf18", + "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/deprecation-contracts": "^2.2|^3.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "twig/twig": "^3.8" + }, + "conflict": { + "symfony/config": "<5.4.0" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/phpunit-bridge": "^6.0|^7.0", + "symfony/stimulus-bundle": "^2.9.1", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/webpack-encore-bundle": "^1.15" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\TwigComponent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Twig components for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "components", + "symfony-ux", + "twig" + ], + "support": { + "source": "https://github.com/symfony/ux-twig-component/tree/v2.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T18:05:50+00:00" + }, { "name": "symfony/var-dumper", "version": "v7.2.3", diff --git a/src/Toolkit/phpunit.xml.dist b/src/Toolkit/phpunit.xml.dist index 42c5a43e94..039a42b06c 100644 --- a/src/Toolkit/phpunit.xml.dist +++ b/src/Toolkit/phpunit.xml.dist @@ -24,6 +24,7 @@ + diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index bd319cd714..0f47e121b6 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -46,7 +46,7 @@ protected function configure(): void 'd', InputArgument::OPTIONAL, 'The destination directory', - 'templates/components/Ux' + 'templates/components/ux' ); } diff --git a/src/Toolkit/src/ComponentRepository/OfficialRepository.php b/src/Toolkit/src/ComponentRepository/OfficialRepository.php index c63dc33e3f..5f25bbb6f4 100644 --- a/src/Toolkit/src/ComponentRepository/OfficialRepository.php +++ b/src/Toolkit/src/ComponentRepository/OfficialRepository.php @@ -28,7 +28,7 @@ public function __construct( public function getContent(): string { - $source = sprintf(__DIR__ . '/../../templates/components/%s.html.twig', $this->name); + $source = sprintf(__DIR__ . '/../../templates/components/ux/%s.html.twig', $this->name); if (!file_exists($source) || !is_readable($source)) { throw new InvalidArgumentException(sprintf('The component "%s" does not exist', $this->name)); } diff --git a/src/Toolkit/templates/components/Alert.twig b/src/Toolkit/templates/components/ux/Alert.twig similarity index 100% rename from src/Toolkit/templates/components/Alert.twig rename to src/Toolkit/templates/components/ux/Alert.twig diff --git a/src/Toolkit/templates/components/Badge.html.twig b/src/Toolkit/templates/components/ux/Badge.html.twig similarity index 100% rename from src/Toolkit/templates/components/Badge.html.twig rename to src/Toolkit/templates/components/ux/Badge.html.twig diff --git a/src/Toolkit/templates/components/Button.html.twig b/src/Toolkit/templates/components/ux/Button.html.twig similarity index 100% rename from src/Toolkit/templates/components/Button.html.twig rename to src/Toolkit/templates/components/ux/Button.html.twig diff --git a/src/Toolkit/templates/components/Card.twig b/src/Toolkit/templates/components/ux/Card.twig similarity index 100% rename from src/Toolkit/templates/components/Card.twig rename to src/Toolkit/templates/components/ux/Card.twig diff --git a/src/Toolkit/templates/components/Navbar.twig b/src/Toolkit/templates/components/ux/Navbar.twig similarity index 100% rename from src/Toolkit/templates/components/Navbar.twig rename to src/Toolkit/templates/components/ux/Navbar.twig diff --git a/src/Toolkit/templates/components/Table.twig b/src/Toolkit/templates/components/ux/Table.twig similarity index 85% rename from src/Toolkit/templates/components/Table.twig rename to src/Toolkit/templates/components/ux/Table.twig index 3ca92b85db..e3acbe647e 100644 --- a/src/Toolkit/templates/components/Table.twig +++ b/src/Toolkit/templates/components/ux/Table.twig @@ -1,3 +1,4 @@ +{# ux:with{Row, Button} #} diff --git a/src/Toolkit/templates/components/Table/Row.twig b/src/Toolkit/templates/components/ux/Table/Row.twig similarity index 100% rename from src/Toolkit/templates/components/Table/Row.twig rename to src/Toolkit/templates/components/ux/Table/Row.twig diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index 52c4b44eca..21921169ca 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -47,11 +47,11 @@ public function testShouldAbleToCreateTheBadgeComponent(): void $this->assertEquals(Command::SUCCESS, $result); // A file should be created - $expectedFile = __DIR__ . '/../../templates/components/Badge.html.twig'; + $expectedFile = $destination . DIRECTORY_SEPARATOR . 'Badge.html.twig'; $this->assertFileExists($expectedFile); // The content of the file should be the same as the content of the Badge component - $expectedContent = file_get_contents(__DIR__ . '/../../templates/components/Badge.html.twig'); + $expectedContent = file_get_contents(__DIR__ . '/../../templates/components/ux/Badge.html.twig'); $actualContent = file_get_contents($expectedFile); $this->assertEquals($expectedContent, $actualContent); } diff --git a/src/Toolkit/tests/Component/BadgeTest.php b/src/Toolkit/tests/Component/BadgeTest.php new file mode 100644 index 0000000000..6ac36f771c --- /dev/null +++ b/src/Toolkit/tests/Component/BadgeTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\Tests\Component; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Twig\Environment; +use Twig\Extra\Html\HtmlExtension; + +/** + * @author Jean-François Lépine + */ +class BadgeTest extends KernelTestCase +{ + public function testDefaultRenderingIsPossible(): void + { + $this->bootKernel(); + + $html = <<Demo +my badge +EOT; + /** @var Environment $twig */ + $twig = static::getContainer()->get('twig'); + $twig->addExtension(new HtmlExtension()); + + + $template = $twig->createTemplate($html); + $output = $template->render([]); + + $this->assertStringContainsString('class="inline-flex items-center ', $output); + $this->assertStringContainsString('my badge', $output); + } +} \ No newline at end of file diff --git a/src/Toolkit/tests/Fixtures/Kernel.php b/src/Toolkit/tests/Fixtures/Kernel.php new file mode 100644 index 0000000000..90df835108 --- /dev/null +++ b/src/Toolkit/tests/Fixtures/Kernel.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests\Fixtures; + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; +use Symfony\Bundle\TwigBundle\TwigBundle; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\HttpKernel\Kernel as BaseKernel; +use Symfony\Ux\Toolkit\UxToolkitBundle; +use Symfony\UX\TwigComponent\TwigComponentBundle; + +/** + * @author Jean-François Lépine + */ +final class Kernel extends BaseKernel +{ + use MicroKernelTrait; + + public function registerBundles(): iterable + { + return [ + new FrameworkBundle(), + new TwigBundle(), + new TwigComponentBundle(), + new UxToolkitBundle(), + ]; + } + + protected function configureContainer(ContainerConfigurator $containerConfigurator): void + { + $config = [ + 'secret' => 'SECRET', + 'test' => true, + ]; + + $containerConfigurator->extension('framework', $config); + $containerConfigurator->extension('twig', [ + 'default_path' => __DIR__ . '/../../templates', + ]); + + $config = [ + 'anonymous_template_directory' => 'components/', + ]; + + $containerConfigurator->extension('twig_component', $config); + } + + +} \ No newline at end of file From a07a60725db55ff83b3f4e8f051cff97c1a53a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 06:56:38 +0100 Subject: [PATCH 07/51] isolated identifier / repository --- src/Toolkit/.phpunit.cache/test-results | 1 + src/Toolkit/phpunit.xml.dist | 54 +++++++-------- .../src/Command/UxToolkitInstallCommand.php | 45 ++++++++----- .../ComponentIdentifier.php | 41 ++++++++++++ .../ComponentRepository/ComponentIdentity.php | 48 ++++++++++++++ .../ComponentRepository.php | 4 +- .../ComponentRepository/GithubRepository.php | 30 +++------ .../OfficialRepository.php | 27 ++------ .../ComponentRepository/RepositoryFactory.php | 30 ++++----- src/Toolkit/src/UxToolkitBundle.php | 6 ++ .../Command/UxToolkitInstallCommandTest.php | 39 ++++++----- .../ComponentIdentifierTest.php | 65 +++++++++++++++++++ .../RepositoryFactoryTest.php | 44 +++++++++---- 13 files changed, 297 insertions(+), 137 deletions(-) create mode 100644 src/Toolkit/.phpunit.cache/test-results create mode 100644 src/Toolkit/src/ComponentRepository/ComponentIdentifier.php create mode 100644 src/Toolkit/src/ComponentRepository/ComponentIdentity.php create mode 100644 src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php diff --git a/src/Toolkit/.phpunit.cache/test-results b/src/Toolkit/.phpunit.cache/test-results new file mode 100644 index 0000000000..7b52bca59f --- /dev/null +++ b/src/Toolkit/.phpunit.cache/test-results @@ -0,0 +1 @@ +{"version":1,"defects":{"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenComponentDoesNotExist":7,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testShouldAbleToCreateTheBadgeComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testByDefaultCannotEraseComponentByMistake":8,"Symfony\\Ux\\Toolkit\\Tests\\Component\\BadgeTest::testDefaultRenderingIsPossible":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#2":7,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponentWithVersion":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyOfficialComponent":7},"times":{"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testShouldAbleToCreateTheBadgeComponent":0.015,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testByDefaultCannotEraseComponentByMistake":0.003,"Symfony\\Ux\\Toolkit\\Tests\\Component\\BadgeTest::testDefaultRenderingIsPossible":0.036,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName":0.016,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenRepositoryIsNotSupported":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenComponentDoesNotExist":0.01,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#0":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#1":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#2":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#3":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifiyOfficialComponent":0.002,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyOfficialComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifiyGithubComponentEventWithoutScheme":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponentWithVersion":0}} \ No newline at end of file diff --git a/src/Toolkit/phpunit.xml.dist b/src/Toolkit/phpunit.xml.dist index 039a42b06c..f2f679f845 100644 --- a/src/Toolkit/phpunit.xml.dist +++ b/src/Toolkit/phpunit.xml.dist @@ -1,35 +1,25 @@ - - - - - ./src - - - - - - - - - - - - - - - - - - tests - - + + + + + + + + + + + + + + + tests + + + + + ./src + + diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index 0f47e121b6..05ceac952f 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -17,8 +17,8 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentifier; use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; -use Throwable; /** * @author Jean-François Lépine @@ -32,7 +32,8 @@ class UxToolkitInstallCommand extends Command { public function __construct( - private readonly RepositoryFactory $repositoryFactory + private readonly RepositoryFactory $repositoryFactory, + private readonly ComponentIdentifier $componentIdentifier, ) { parent::__construct(); } @@ -53,26 +54,37 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $component = $input->getArgument('component'); + $name = $input->getArgument('component'); - // Get the correct source (remote or official) try { + $component = $this->componentIdentifier->identify($name); + + // Get the correct source (remote or official) $repository = $this->repositoryFactory->factory($component); - $component = $repository->getName(); - } catch (Throwable $e) { + } catch (\Throwable $e) { $io->error($e->getMessage()); + return Command::FAILURE; } + if ($io->isVerbose()) { + $io->text('Component information:'); + $io->table(['Vendor', 'Package', 'Version', 'Name'], [ + [$component->getVendor(), $component->getPackage(), $component->getVersion(), $component->getName()], + ]); + } + // Prepare destination $directory = $input->getOption('destination'); if (empty($directory)) { $io->error('The destination directory is not valid'); + return Command::FAILURE; } - if (!file_exists(dirname($directory)) || !is_writable(dirname($directory))) { - $io->error(sprintf('The directory "%s" does not exist or is not writable', dirname($directory))); + if (!file_exists(\dirname($directory)) || !is_writable(\dirname($directory))) { + $io->error(\sprintf('The directory "%s" does not exist or is not writable', \dirname($directory))); + return Command::FAILURE; } @@ -80,10 +92,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int mkdir($directory, 0777, true); } - - $filename = $directory . '/' . $component . '.html.twig'; + $filename = $directory.'/'.$component->getName().'.html.twig'; if (file_exists($filename)) { - $io->error(sprintf('The component "%s" already exists', $component)); + $io->error(\sprintf('The component "%s" already exists', $component->getName())); if (!$input->isInteractive()) { return Command::FAILURE; } @@ -94,18 +105,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - if (!is_writable(dirname($filename))) { - $io->error(sprintf('The directory "%s" is not writable', dirname($filename))); + if (!is_writable(\dirname($filename))) { + $io->error(\sprintf('The directory "%s" is not writable', \dirname($filename))); + return Command::FAILURE; } - file_put_contents($filename, $repository->getContent()); + file_put_contents($filename, $repository->getContent($component)); if ($io->isVerbose()) { - $io->text(sprintf('The component "%s" has been installed in "%s"', $component, $filename)); + $io->text(\sprintf('The component "%s" has been installed in "%s"', $component->getName(), $filename)); } - $io->success(sprintf('The component "%s" has been installed', $component)); + $io->success(\sprintf('The component "%s" has been installed', $component->getName())); + return Command::SUCCESS; } } diff --git a/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php b/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php new file mode 100644 index 0000000000..0163fbb1b2 --- /dev/null +++ b/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\ComponentRepository; + +/** + * @author Jean-François Lépine + * + * @internal + */ +final class ComponentIdentifier +{ + public function identify(string $name): ComponentIdentity + { + if (preg_match('!^\w+$!', $name)) { + return new ComponentIdentity('symfony', 'ux-toolkit', ucfirst($name)); + } + + // github.com/vendor/package:component@version + // github.com/vendor/package:component (version is optional) + $name = preg_replace('!^(https://|http://)!', '', $name); + if (preg_match('!^github.com/(\w+)/(\w+):(\w+)(@.+)?$!', $name, $matches)) { + return new ComponentIdentity( + $matches[1], + $matches[2], + ucfirst($matches[3]), + trim($matches[4] ?? 'main', '@') + ); + } + + throw new \InvalidArgumentException('Source is not supported for this component'); + } +} diff --git a/src/Toolkit/src/ComponentRepository/ComponentIdentity.php b/src/Toolkit/src/ComponentRepository/ComponentIdentity.php new file mode 100644 index 0000000000..2944d4aa4e --- /dev/null +++ b/src/Toolkit/src/ComponentRepository/ComponentIdentity.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\ComponentRepository; + +/** + * @author Jean-François Lépine + * + * @internal + */ +readonly final class ComponentIdentity +{ + public function __construct( + private string $vendor, + private ?string $package = null, + private ?string $name = null, + private ?string $version = 'main' + ) { + } + + public function getVendor(): string + { + return $this->vendor; + } + + public function getPackage(): ?string + { + return $this->package; + } + + public function getName(): ?string + { + return $this->name; + } + + public function getVersion(): ?string + { + return $this->version; + } +} \ No newline at end of file diff --git a/src/Toolkit/src/ComponentRepository/ComponentRepository.php b/src/Toolkit/src/ComponentRepository/ComponentRepository.php index 8582464d74..b05f5f3568 100644 --- a/src/Toolkit/src/ComponentRepository/ComponentRepository.php +++ b/src/Toolkit/src/ComponentRepository/ComponentRepository.php @@ -18,7 +18,5 @@ */ interface ComponentRepository { - public function getContent(): string; - - public function getName(): string; + public function getContent(ComponentIdentity $component): string; } \ No newline at end of file diff --git a/src/Toolkit/src/ComponentRepository/GithubRepository.php b/src/Toolkit/src/ComponentRepository/GithubRepository.php index 27fe28d940..9fcde3e03b 100644 --- a/src/Toolkit/src/ComponentRepository/GithubRepository.php +++ b/src/Toolkit/src/ComponentRepository/GithubRepository.php @@ -16,32 +16,18 @@ * * @internal */ -final readonly class GithubRepository implements ComponentRepository +class GithubRepository implements ComponentRepository { - - public function __construct( - private string $vendor, - private string $package, - private string $name, - private ?string $version = 'main' - ) { - } - - public function getContent(): string + public function getContent(ComponentIdentity $component): string { - $url = sprintf( + $url = \sprintf( 'https://raw.githubusercontent.com/%s/%s/%s/templates/components/%s.html.twig', - $this->vendor, - $this->package, - $this->version, - $this->name + $component->getVendor(), + $component->getPackage(), + $component->getVersion(), + $component->getName() ); return file_get_contents($url); } - - public function getName(): string - { - return $this->name; - } -} \ No newline at end of file +} diff --git a/src/Toolkit/src/ComponentRepository/OfficialRepository.php b/src/Toolkit/src/ComponentRepository/OfficialRepository.php index 5f25bbb6f4..3fb95a3391 100644 --- a/src/Toolkit/src/ComponentRepository/OfficialRepository.php +++ b/src/Toolkit/src/ComponentRepository/OfficialRepository.php @@ -11,41 +11,26 @@ namespace Symfony\Ux\Toolkit\ComponentRepository; -use InvalidArgumentException; - /** * @author Jean-François Lépine * * @internal */ -final readonly class OfficialRepository implements ComponentRepository +class OfficialRepository implements ComponentRepository { - - public function __construct( - private string $name - ) { - } - - public function getContent(): string + public function getContent(ComponentIdentity $component): string { - $source = sprintf(__DIR__ . '/../../templates/components/ux/%s.html.twig', $this->name); + $source = \sprintf(__DIR__.'/../../templates/components/ux/%s.html.twig', $component->getName()); if (!file_exists($source) || !is_readable($source)) { - throw new InvalidArgumentException(sprintf('The component "%s" does not exist', $this->name)); + throw new \InvalidArgumentException(\sprintf('The component "%s" does not exist', $component->getName())); } set_error_handler(function ($errno, $errstr, $errfile, $errline) { - throw new InvalidArgumentException( - sprintf('Error reading file "%s" at line %d: %s', $errfile, $errline, $errstr) - ); + throw new \InvalidArgumentException(\sprintf('Error reading file "%s" at line %d: %s', $errfile, $errline, $errstr)); }); $content = file_get_contents($source); restore_error_handler(); return $content; } - - public function getName(): string - { - return $this->name; - } -} \ No newline at end of file +} diff --git a/src/Toolkit/src/ComponentRepository/RepositoryFactory.php b/src/Toolkit/src/ComponentRepository/RepositoryFactory.php index 84a89bf3ed..e408efcf55 100644 --- a/src/Toolkit/src/ComponentRepository/RepositoryFactory.php +++ b/src/Toolkit/src/ComponentRepository/RepositoryFactory.php @@ -11,8 +11,6 @@ namespace Symfony\Ux\Toolkit\ComponentRepository; -use InvalidArgumentException; - /** * @author Jean-François Lépine * @@ -20,24 +18,22 @@ */ final class RepositoryFactory { - public function factory(string $name): ComponentRepository + public function __construct( + private readonly OfficialRepository $officialRepository, + private readonly GithubRepository $githubRepository, + ) { + } + + public function factory(ComponentIdentity $component): ComponentRepository { - if (preg_match('!^\w+$!', $name)) { - return new OfficialRepository(ucfirst($name)); + if ('symfony' === $component->getVendor()) { + return $this->officialRepository; } - // github.com/vendor/package:component@version - // github.com/vendor/package:component (version is optional) - $name = preg_replace('!^(https://|http://)!', '', $name); - if (preg_match('!^github.com/(\w+)/(\w+):(\w+)(@\w+)?$!', $name, $matches)) { - return new GithubRepository( - $matches[1], - $matches[2], - ucfirst($matches[3]), - $matches[4] ?? 'main' - ); + if (str_starts_with($component->getVendor(), 'github.com/')) { + return $this->githubRepository; } - throw new InvalidArgumentException('Source is not supported for this component'); + throw new \InvalidArgumentException('Source is not supported for this component'); } -} \ No newline at end of file +} diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index eb3adc8be9..233d0e10c2 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -14,6 +14,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; use Symfony\Ux\Toolkit\Command\UxToolkitInstallCommand; +use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentifier; +use Symfony\Ux\Toolkit\ComponentRepository\GithubRepository; +use Symfony\Ux\Toolkit\ComponentRepository\OfficialRepository; use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; /** @@ -27,7 +30,10 @@ public function build(ContainerBuilder $container): void { parent::build($container); + $container->autowire(OfficialRepository::class); + $container->autowire(GithubRepository::class); $container->autowire(RepositoryFactory::class); + $container->autowire(ComponentIdentifier::class); $container->autowire(UxToolkitInstallCommand::class); $container diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index 21921169ca..fcc83c832f 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -16,6 +16,9 @@ use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Ux\Toolkit\Command\UxToolkitInstallCommand; +use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentifier; +use Symfony\Ux\Toolkit\ComponentRepository\GithubRepository; +use Symfony\Ux\Toolkit\ComponentRepository\OfficialRepository; use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; /** @@ -23,19 +26,21 @@ */ class UxToolkitInstallCommandTest extends TestCase { - - public function testShouldAbleToCreateTheBadgeComponent(): void { - $repositoryFactory = new RepositoryFactory(); - $command = new UxToolkitInstallCommand($repositoryFactory); - - $destination = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(); + $repositoryFactory = new RepositoryFactory( + new OfficialRepository(), + $this->createMock(GithubRepository::class) + ); + $identifier = new ComponentIdentifier(); + $command = new UxToolkitInstallCommand($repositoryFactory, $identifier); + + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); mkdir($destination); $input = new ArrayInput([ 'component' => 'badge', - '--destination' => $destination + '--destination' => $destination, ]); $input->setInteractive(false); @@ -47,26 +52,30 @@ public function testShouldAbleToCreateTheBadgeComponent(): void $this->assertEquals(Command::SUCCESS, $result); // A file should be created - $expectedFile = $destination . DIRECTORY_SEPARATOR . 'Badge.html.twig'; + $expectedFile = $destination.\DIRECTORY_SEPARATOR.'Badge.html.twig'; $this->assertFileExists($expectedFile); // The content of the file should be the same as the content of the Badge component - $expectedContent = file_get_contents(__DIR__ . '/../../templates/components/ux/Badge.html.twig'); + $expectedContent = file_get_contents(__DIR__.'/../../templates/components/ux/Badge.html.twig'); $actualContent = file_get_contents($expectedFile); $this->assertEquals($expectedContent, $actualContent); } public function testByDefaultCannotEraseComponentByMistake(): void { - $repositoryFactory = new RepositoryFactory(); - $command = new UxToolkitInstallCommand($repositoryFactory); - - $destination = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(); + $repositoryFactory = new RepositoryFactory( + new OfficialRepository(), + $this->createMock(GithubRepository::class) + ); + $identifier = new ComponentIdentifier(); + $command = new UxToolkitInstallCommand($repositoryFactory, $identifier); + + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); mkdir($destination); $input = new ArrayInput([ 'component' => 'badge', - '--destination' => $destination + '--destination' => $destination, ]); $input->setInteractive(false); @@ -79,4 +88,4 @@ public function testByDefaultCannotEraseComponentByMistake(): void $result = $command->run($input, $output); $this->assertEquals(Command::FAILURE, $result); } -} \ No newline at end of file +} diff --git a/src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php b/src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php new file mode 100644 index 0000000000..f4106e6cf8 --- /dev/null +++ b/src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\Tests\ComponentRepository; + +use PHPUnit\Framework\TestCase; +use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentifier; +use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentity; + +/** + * @author Jean-François Lépine + */ +class ComponentIdentifierTest extends TestCase +{ + public function testItShouldIdentifyOfficialComponent(): void + { + $identifier = new ComponentIdentifier(); + $identity = $identifier->identify('button'); + + $this->assertInstanceOf(ComponentIdentity::class, $identity); + $this->assertEquals('symfony', $identity->getVendor()); + $this->assertEquals('Button', $identity->getName()); + } + + public function testItShouldIdentifyGithubComponent(): void + { + $identifier = new ComponentIdentifier(); + $identity = $identifier->identify('https://github.com/Halleck45/uikit:button'); + + $this->assertEquals('Halleck45', $identity->getVendor()); + $this->assertEquals('uikit', $identity->getPackage()); + $this->assertEquals('Button', $identity->getName()); + $this->assertEquals('main', $identity->getVersion()); + } + + public function testItShouldIdentifiyGithubComponentEventWithoutScheme(): void + { + $identifier = new ComponentIdentifier(); + $identity = $identifier->identify('github.com/Halleck45/uikit:button'); + + $this->assertEquals('Halleck45', $identity->getVendor()); + $this->assertEquals('uikit', $identity->getPackage()); + $this->assertEquals('Button', $identity->getName()); + $this->assertEquals('main', $identity->getVersion()); + } + + public function testItShouldIdentifyGithubComponentWithVersion(): void + { + $identifier = new ComponentIdentifier(); + $identity = $identifier->identify('github.com/Halleck45/uikit:button@v1.0.0'); + + $this->assertEquals('Halleck45', $identity->getVendor()); + $this->assertEquals('uikit', $identity->getPackage()); + $this->assertEquals('Button', $identity->getName()); + $this->assertEquals('v1.0.0', $identity->getVersion()); + } +} diff --git a/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php b/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php index dbec3ce967..5a4d57aecb 100644 --- a/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php +++ b/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php @@ -12,6 +12,7 @@ namespace Symfony\Ux\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; +use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentity; use Symfony\Ux\Toolkit\ComponentRepository\GithubRepository; use Symfony\Ux\Toolkit\ComponentRepository\OfficialRepository; use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; @@ -21,19 +22,40 @@ */ class RepositoryFactoryTest extends TestCase { - public function testItShouldFactoryRepositoryAccordingToItsName(): void - { - $factory = new RepositoryFactory(); + /** + * @dataProvider providesNames + */ + public function testItShouldFactoryRepositoryAccordingToItsName( + string $vendor, + string $name, + ?string $expectedInstance, + bool $shouldThrowException = false, + ): void { + $factory = new RepositoryFactory( + $this->createMock(OfficialRepository::class), + $this->createMock(GithubRepository::class) + ); + + if ($shouldThrowException) { + $this->expectException(\InvalidArgumentException::class); + } - $this->assertInstanceOf(OfficialRepository::class, $factory->factory('button')); - $this->assertInstanceOf(GithubRepository::class, $factory->factory('github.com/symfony/bar:button')); - $this->assertInstanceOf(GithubRepository::class, $factory->factory('github.com/foo/bar:button@dev')); - $this->assertInstanceOf(GithubRepository::class, $factory->factory('https://github.com/foo/bar:button@dev')); + $result = $factory->factory(new ComponentIdentity($vendor, 'mypackage', $name)); - $official = $factory->factory('button'); - $github = $factory->factory('github.com/symfony/bar:button@version'); + if ($shouldThrowException) { + return; + } - $this->assertEquals('Button', $official->getName()); - $this->assertEquals('Button', $github->getName()); + $this->assertInstanceOf($expectedInstance, $result); + } + + public static function providesNames(): array + { + return [ + ['symfony', 'button', OfficialRepository::class, false], + ['github.com/Foo', 'bar', GithubRepository::class, false], + ['gitlab.com/Foo', 'bar', null, true], + ['invalid name', 'bar', null, true], + ]; } } \ No newline at end of file From 263942cbce1c80e618720ab60671920730d41e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 07:00:07 +0100 Subject: [PATCH 08/51] tests for official repository --- src/Toolkit/.phpunit.cache/test-results | 2 +- .../OfficialRepositoryTest.php | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php diff --git a/src/Toolkit/.phpunit.cache/test-results b/src/Toolkit/.phpunit.cache/test-results index 7b52bca59f..961ffa9787 100644 --- a/src/Toolkit/.phpunit.cache/test-results +++ b/src/Toolkit/.phpunit.cache/test-results @@ -1 +1 @@ -{"version":1,"defects":{"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenComponentDoesNotExist":7,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testShouldAbleToCreateTheBadgeComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testByDefaultCannotEraseComponentByMistake":8,"Symfony\\Ux\\Toolkit\\Tests\\Component\\BadgeTest::testDefaultRenderingIsPossible":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#2":7,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponentWithVersion":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyOfficialComponent":7},"times":{"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testShouldAbleToCreateTheBadgeComponent":0.015,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testByDefaultCannotEraseComponentByMistake":0.003,"Symfony\\Ux\\Toolkit\\Tests\\Component\\BadgeTest::testDefaultRenderingIsPossible":0.036,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName":0.016,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenRepositoryIsNotSupported":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenComponentDoesNotExist":0.01,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#0":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#1":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#2":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#3":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifiyOfficialComponent":0.002,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyOfficialComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifiyGithubComponentEventWithoutScheme":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponentWithVersion":0}} \ No newline at end of file +{"version":1,"defects":{"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenComponentDoesNotExist":7,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testShouldAbleToCreateTheBadgeComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testByDefaultCannotEraseComponentByMistake":8,"Symfony\\Ux\\Toolkit\\Tests\\Component\\BadgeTest::testDefaultRenderingIsPossible":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#2":7,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponentWithVersion":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyOfficialComponent":7,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryGetContentOfExistentComponent":7,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryGetContentOfNonExistentComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryFailWhenComponentDoesNotExist":7},"times":{"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testShouldAbleToCreateTheBadgeComponent":0.016,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testByDefaultCannotEraseComponentByMistake":0.002,"Symfony\\Ux\\Toolkit\\Tests\\Component\\BadgeTest::testDefaultRenderingIsPossible":0.036,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName":0.016,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenRepositoryIsNotSupported":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenComponentDoesNotExist":0.01,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#0":0.001,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#1":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#2":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#3":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifiyOfficialComponent":0.002,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyOfficialComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifiyGithubComponentEventWithoutScheme":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponentWithVersion":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryGetContentOfExistentComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryGetContentOfNonExistentComponent":0.002,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryFailWhenComponentDoesNotExist":0.011}} \ No newline at end of file diff --git a/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php b/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php new file mode 100644 index 0000000000..e6a76fe403 --- /dev/null +++ b/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\Tests\ComponentRepository; + +use PHPUnit\Framework\TestCase; +use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\Ux\Toolkit\ComponentRepository\OfficialRepository; + +/** + * @author Jean-François Lépine + */ +class OfficialRepositoryTest extends TestCase +{ + public function testOfficialRepositoryGetContentOfExistentComponent(): void + { + $repository = new OfficialRepository(); + $component = new ComponentIdentity('symfony', 'ux-toolkit', 'Badge'); + + $content = $repository->getContent($component); + + $expectedContent = file_get_contents(__DIR__ . '/../../templates/components/ux/Badge.html.twig'); + $this->assertEquals($expectedContent, $content); + } + + public function testOfficialRepositoryFailWhenComponentDoesNotExist(): void + { + $repository = new OfficialRepository(); + $component = new ComponentIdentity('symfony', 'ux-toolkit', 'NonExistentComponent'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The component "NonExistentComponent" does not exist'); + $repository->getContent($component); + } +} From 8d691127439ab39ea467cfefcb94ce98965ad5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 07:16:43 +0100 Subject: [PATCH 09/51] Tests the remote github repository --- src/Toolkit/.phpunit.cache/test-results | 1 - src/Toolkit/composer.json | 3 + src/Toolkit/composer.lock | 182 +++++++++++++++++- .../ComponentRepository/GithubRepository.php | 15 +- src/Toolkit/src/UxToolkitBundle.php | 10 +- .../GithubRepositoryTest.php | 36 ++++ 6 files changed, 238 insertions(+), 9 deletions(-) delete mode 100644 src/Toolkit/.phpunit.cache/test-results create mode 100644 src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php diff --git a/src/Toolkit/.phpunit.cache/test-results b/src/Toolkit/.phpunit.cache/test-results deleted file mode 100644 index 961ffa9787..0000000000 --- a/src/Toolkit/.phpunit.cache/test-results +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":{"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenComponentDoesNotExist":7,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testShouldAbleToCreateTheBadgeComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testByDefaultCannotEraseComponentByMistake":8,"Symfony\\Ux\\Toolkit\\Tests\\Component\\BadgeTest::testDefaultRenderingIsPossible":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#2":7,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponentWithVersion":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyOfficialComponent":7,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryGetContentOfExistentComponent":7,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryGetContentOfNonExistentComponent":8,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryFailWhenComponentDoesNotExist":7},"times":{"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testShouldAbleToCreateTheBadgeComponent":0.016,"Symfony\\Ux\\Toolkit\\Tests\\Command\\UxToolkitInstallCommandTest::testByDefaultCannotEraseComponentByMistake":0.002,"Symfony\\Ux\\Toolkit\\Tests\\Component\\BadgeTest::testDefaultRenderingIsPossible":0.036,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName":0.016,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenRepositoryIsNotSupported":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFailWhenComponentDoesNotExist":0.01,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#0":0.001,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#1":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#2":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\RepositoryFactoryTest::testItShouldFactoryRepositoryAccordingToItsName#3":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifiyOfficialComponent":0.002,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyOfficialComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifiyGithubComponentEventWithoutScheme":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\ComponentIdentifierTest::testItShouldIdentifyGithubComponentWithVersion":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryGetContentOfExistentComponent":0,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryGetContentOfNonExistentComponent":0.002,"Symfony\\Ux\\Toolkit\\Tests\\ComponentRepository\\OfficialRepositoryTest::testOfficialRepositoryFailWhenComponentDoesNotExist":0.011}} \ No newline at end of file diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index 3a95d51bd0..073a3b4682 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -58,5 +58,8 @@ "name": "symfony/ux", "url": "https://github.com/symfony/ux" } + }, + "require-dev": { + "symfony/http-client": "6.4|^7.0" } } diff --git a/src/Toolkit/composer.lock b/src/Toolkit/composer.lock index ed6fd21a0a..d300d95053 100644 --- a/src/Toolkit/composer.lock +++ b/src/Toolkit/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c2568dddd4d88908cb27985a17efa397", + "content-hash": "5f6bb03f6153b3bd376aefec36bb7b5f", "packages": [ { "name": "gehrisandro/tailwind-merge-php", @@ -3516,15 +3516,189 @@ "time": "2025-01-29T07:06:14+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "symfony/http-client", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/7ce6078c79a4a7afff931c413d2959d3bffbfb8d", + "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-28T15:51:35+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T08:49:48+00:00" + } + ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.2" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/src/Toolkit/src/ComponentRepository/GithubRepository.php b/src/Toolkit/src/ComponentRepository/GithubRepository.php index 9fcde3e03b..7460677358 100644 --- a/src/Toolkit/src/ComponentRepository/GithubRepository.php +++ b/src/Toolkit/src/ComponentRepository/GithubRepository.php @@ -11,6 +11,9 @@ namespace Symfony\Ux\Toolkit\ComponentRepository; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Contracts\HttpClient\HttpClientInterface; + /** * @author Jean-François Lépine * @@ -18,6 +21,14 @@ */ class GithubRepository implements ComponentRepository { + public function __construct( + private readonly ?HttpClientInterface $httpClient = null, + ) { + if (!class_exists(HttpClient::class)) { + throw new \LogicException('You must install "symfony/http-client" to use ux-toolkit with remote component. Try running "composer require symfony/http-client".'); + } + } + public function getContent(ComponentIdentity $component): string { $url = \sprintf( @@ -28,6 +39,8 @@ public function getContent(ComponentIdentity $component): string $component->getName() ); - return file_get_contents($url); + $response = $this->httpClient->request('GET', $url); + + return $response->getContent(); } } diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index 233d0e10c2..4874ef093c 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -25,7 +25,6 @@ */ class UxToolkitBundle extends AbstractBundle { - public function build(ContainerBuilder $container): void { parent::build($container); @@ -35,6 +34,7 @@ public function build(ContainerBuilder $container): void $container->autowire(RepositoryFactory::class); $container->autowire(ComponentIdentifier::class); + // Prepare command $container->autowire(UxToolkitInstallCommand::class); $container ->registerForAutoconfiguration(UxToolkitInstallCommand::class) @@ -45,7 +45,11 @@ public function build(ContainerBuilder $container): void ->setPublic(true) ->addTag('console.command') ; - } + // Inject http client (if exists) to github repository + if($container->has('http_client')) { + $container->getDefinition(GithubRepository::class) + ->setArgument('$httpClient', $container->get('http_client')); + } + } } - diff --git a/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php b/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php new file mode 100644 index 0000000000..f808ae48c7 --- /dev/null +++ b/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Ux\Toolkit\Tests\ComponentRepository; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\Ux\Toolkit\ComponentRepository\GithubRepository; + +/** + * @author Jean-François Lépine + */ +class GithubRepositoryTest extends TestCase +{ + public function testGithubRepositoryUseClientAndTryToDownloadRemoteFile(): void + { + $client = new MockHttpClient(); + $client->setResponseFactory(fn() => new MockResponse('My badge content from github')); + $repository = new GithubRepository($client); + + $component = new ComponentIdentity('Halleck45', 'ux-toolkit', 'Badge', '1.0.0'); + $content = $repository->getContent($component); + + $this->assertEquals('My badge content from github', $content); + } +} From 4864f6031253bf6957bb42d5c9b00ef4dd323f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 07:55:43 +0100 Subject: [PATCH 10/51] Use finder insteadof f* function --- .../ComponentRepository/OfficialRepository.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Toolkit/src/ComponentRepository/OfficialRepository.php b/src/Toolkit/src/ComponentRepository/OfficialRepository.php index 3fb95a3391..8623a34805 100644 --- a/src/Toolkit/src/ComponentRepository/OfficialRepository.php +++ b/src/Toolkit/src/ComponentRepository/OfficialRepository.php @@ -11,6 +11,8 @@ namespace Symfony\Ux\Toolkit\ComponentRepository; +use Symfony\Component\Finder\Finder; + /** * @author Jean-François Lépine * @@ -20,17 +22,16 @@ class OfficialRepository implements ComponentRepository { public function getContent(ComponentIdentity $component): string { - $source = \sprintf(__DIR__.'/../../templates/components/ux/%s.html.twig', $component->getName()); - if (!file_exists($source) || !is_readable($source)) { + $finder = new Finder(); + $finder->files()->in(__DIR__.'/../../templates/components/ux/')->name($component->getName().'.html.twig'); + if (!$finder->hasResults()) { throw new \InvalidArgumentException(\sprintf('The component "%s" does not exist', $component->getName())); } - set_error_handler(function ($errno, $errstr, $errfile, $errline) { - throw new \InvalidArgumentException(\sprintf('Error reading file "%s" at line %d: %s', $errfile, $errline, $errstr)); - }); - $content = file_get_contents($source); - restore_error_handler(); + foreach ($finder as $file) { + return $file->getContents(); + } - return $content; + throw new \InvalidArgumentException(\sprintf('The component "%s" does not exist', $component->getName())); } } From edeb70e191b2e20bdc742995b4ee4bea228aa1c7 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 31 Jan 2025 12:14:49 +0100 Subject: [PATCH 11/51] Re-organize templates, fix Badge props/cva --- .../default/examples/ButtonOutline.html.twig | 3 -- .../templates/components/ux/Badge.html.twig | 38 ------------------- .../ux => default/components}/Alert.twig | 0 .../default/components/Badge.html.twig | 34 +++++++++++++++++ .../components}/Button.html.twig | 0 .../ux => default/components}/Card.twig | 0 .../ux => default/components}/Navbar.twig | 0 .../ux => default/components}/Table.twig | 0 .../ux => default/components}/Table/Row.twig | 0 .../default/examples/Badge.html.twig | 1 + .../default/examples/BadgeOutline.html.twig | 3 ++ .../default/examples/Button.html.twig | 1 + 12 files changed, 39 insertions(+), 41 deletions(-) delete mode 100644 src/Toolkit/registry/default/examples/ButtonOutline.html.twig delete mode 100644 src/Toolkit/templates/components/ux/Badge.html.twig rename src/Toolkit/templates/{components/ux => default/components}/Alert.twig (100%) create mode 100644 src/Toolkit/templates/default/components/Badge.html.twig rename src/Toolkit/templates/{components/ux => default/components}/Button.html.twig (100%) rename src/Toolkit/templates/{components/ux => default/components}/Card.twig (100%) rename src/Toolkit/templates/{components/ux => default/components}/Navbar.twig (100%) rename src/Toolkit/templates/{components/ux => default/components}/Table.twig (100%) rename src/Toolkit/templates/{components/ux => default/components}/Table/Row.twig (100%) create mode 100644 src/Toolkit/templates/default/examples/Badge.html.twig create mode 100644 src/Toolkit/templates/default/examples/BadgeOutline.html.twig create mode 100644 src/Toolkit/templates/default/examples/Button.html.twig diff --git a/src/Toolkit/registry/default/examples/ButtonOutline.html.twig b/src/Toolkit/registry/default/examples/ButtonOutline.html.twig deleted file mode 100644 index d1456ffe6f..0000000000 --- a/src/Toolkit/registry/default/examples/ButtonOutline.html.twig +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/Toolkit/templates/components/ux/Badge.html.twig b/src/Toolkit/templates/components/ux/Badge.html.twig deleted file mode 100644 index f15cd2bb26..0000000000 --- a/src/Toolkit/templates/components/ux/Badge.html.twig +++ /dev/null @@ -1,38 +0,0 @@ -{% props - default=false, - secondary=false, - destructive=false, - outline=false, -%} - - -{% set config = html_cva( - base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', - variants: { - variant: { - default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", - secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - outline: "text-foreground" - }, - defaultVariant: { - variant: 'default', - } - } -) %} - -{% set variant = "default" %} -{% if secondary %} - {% set variant = "secondary" %} -{% elseif destructive %} - {% set variant = "destructive" %} -{% elseif outline %} - {% set variant = "outline" %} -{% endif %} - -
- {% block content %}{% endblock %} -
diff --git a/src/Toolkit/templates/components/ux/Alert.twig b/src/Toolkit/templates/default/components/Alert.twig similarity index 100% rename from src/Toolkit/templates/components/ux/Alert.twig rename to src/Toolkit/templates/default/components/Alert.twig diff --git a/src/Toolkit/templates/default/components/Badge.html.twig b/src/Toolkit/templates/default/components/Badge.html.twig new file mode 100644 index 0000000000..3f26079d0c --- /dev/null +++ b/src/Toolkit/templates/default/components/Badge.html.twig @@ -0,0 +1,34 @@ +{%- props variant = 'default', outline = false -%} +{%- set style = html_cva( + base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', + variants: { + variant: { + default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + } + outline: { + true: "text-foreground bg-white", + } + }, + compoundVariants: [{ + variant: ['default'], + outline: ['true'], + class: 'border-primary', + }, { + variant: ['secondary'], + outline: ['true'], + class: 'border-secondary', + }, { + variant: ['destructive'], + outline: ['true'], + class: 'border-destructive', + },] +) -%} + +
+ {% block content %}{% endblock %} +
diff --git a/src/Toolkit/templates/components/ux/Button.html.twig b/src/Toolkit/templates/default/components/Button.html.twig similarity index 100% rename from src/Toolkit/templates/components/ux/Button.html.twig rename to src/Toolkit/templates/default/components/Button.html.twig diff --git a/src/Toolkit/templates/components/ux/Card.twig b/src/Toolkit/templates/default/components/Card.twig similarity index 100% rename from src/Toolkit/templates/components/ux/Card.twig rename to src/Toolkit/templates/default/components/Card.twig diff --git a/src/Toolkit/templates/components/ux/Navbar.twig b/src/Toolkit/templates/default/components/Navbar.twig similarity index 100% rename from src/Toolkit/templates/components/ux/Navbar.twig rename to src/Toolkit/templates/default/components/Navbar.twig diff --git a/src/Toolkit/templates/components/ux/Table.twig b/src/Toolkit/templates/default/components/Table.twig similarity index 100% rename from src/Toolkit/templates/components/ux/Table.twig rename to src/Toolkit/templates/default/components/Table.twig diff --git a/src/Toolkit/templates/components/ux/Table/Row.twig b/src/Toolkit/templates/default/components/Table/Row.twig similarity index 100% rename from src/Toolkit/templates/components/ux/Table/Row.twig rename to src/Toolkit/templates/default/components/Table/Row.twig diff --git a/src/Toolkit/templates/default/examples/Badge.html.twig b/src/Toolkit/templates/default/examples/Badge.html.twig new file mode 100644 index 0000000000..94afe17dba --- /dev/null +++ b/src/Toolkit/templates/default/examples/Badge.html.twig @@ -0,0 +1 @@ +Badge diff --git a/src/Toolkit/templates/default/examples/BadgeOutline.html.twig b/src/Toolkit/templates/default/examples/BadgeOutline.html.twig new file mode 100644 index 0000000000..3f92f232c2 --- /dev/null +++ b/src/Toolkit/templates/default/examples/BadgeOutline.html.twig @@ -0,0 +1,3 @@ +Badge +Badge +Badge diff --git a/src/Toolkit/templates/default/examples/Button.html.twig b/src/Toolkit/templates/default/examples/Button.html.twig new file mode 100644 index 0000000000..2b923e0021 --- /dev/null +++ b/src/Toolkit/templates/default/examples/Button.html.twig @@ -0,0 +1 @@ +Click me From dd67fb89825a300b6a1476043cd5d6ef7f3d8f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 08:12:08 +0100 Subject: [PATCH 12/51] Fixed typos --- src/Toolkit/composer.json | 17 +- src/Toolkit/composer.lock | 350 +++++++++--------- src/Toolkit/phpunit.xml.dist | 4 +- .../src/Command/UxToolkitInstallCommand.php | 23 +- .../ComponentIdentifier.php | 2 +- .../ComponentRepository/ComponentIdentity.php | 2 +- .../ComponentRepository.php | 2 +- .../ComponentRepository/GithubRepository.php | 2 +- .../OfficialRepository.php | 2 +- .../ComponentRepository/RepositoryFactory.php | 2 +- src/Toolkit/src/UxToolkitBundle.php | 12 +- .../Command/UxToolkitInstallCommandTest.php | 12 +- src/Toolkit/tests/Component/BadgeTest.php | 2 +- .../ComponentIdentifierTest.php | 6 +- .../GithubRepositoryTest.php | 6 +- .../OfficialRepositoryTest.php | 6 +- .../RepositoryFactoryTest.php | 10 +- src/Toolkit/tests/Fixtures/Kernel.php | 2 +- 18 files changed, 230 insertions(+), 232 deletions(-) diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index 073a3b4682..1c40ac0991 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/ux-toolkit", "type": "symfony-bundle", - "description": "Twig toolkit for Symfony", + "description": "Twig Toolkit for Symfony", "keywords": [ "symfony-ux", "twig", @@ -28,26 +28,26 @@ } ], "require": { - "php": ">=8.2", - "tales-from-a-dev/twig-tailwind-extra": "^0.3.0", - "twig/extra-bundle": "^2.12|^3.0", + "php": ">=8.3", + "twig/extra-bundle": "^3.19|^4.0", "twig/html-extra": "^3.19", "twig/twig": "^2.12|^3.0", "symfony/console": "^7.2", "symfony/framework-bundle": "^6.4|^7.0", "symfony/phpunit-bridge": "^6.4|^7.0", "symfony/twig-bundle": "^6.4|^7.0", - "symfony/ux-twig-component": "^2.22" + "symfony/ux-twig-component": "^2.22", + "symfony/filesystem": "^7.2" }, "autoload": { "psr-4": { - "Symfony\\Ux\\Toolkit\\": "src" + "Symfony\\UX\\Toolkit\\": "src" }, "exclude-from-classmap": [] }, "autoload-dev": { "psr-4": { - "Symfony\\Ux\\Toolkit\\Tests\\": "tests/" + "Symfony\\UX\\Toolkit\\Tests\\": "tests/" } }, "conflict": { @@ -60,6 +60,7 @@ } }, "require-dev": { - "symfony/http-client": "6.4|^7.0" + "symfony/http-client": "6.4|^7.0", + "tales-from-a-dev/twig-tailwind-extra": "^0.3.0" } } diff --git a/src/Toolkit/composer.lock b/src/Toolkit/composer.lock index d300d95053..714f306a9c 100644 --- a/src/Toolkit/composer.lock +++ b/src/Toolkit/composer.lock @@ -4,75 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f6bb03f6153b3bd376aefec36bb7b5f", + "content-hash": "21d10f6bc2641f1203a236c9b517314c", "packages": [ - { - "name": "gehrisandro/tailwind-merge-php", - "version": "v1.1.2", - "source": { - "type": "git", - "url": "https://github.com/gehrisandro/tailwind-merge-php.git", - "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/gehrisandro/tailwind-merge-php/zipball/dc11b9d4a625dd5be885900e5ef14c3efa260277", - "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277", - "shasum": "" - }, - "require": { - "php": "^8.1.0", - "psr/simple-cache": "^3.0" - }, - "require-dev": { - "laravel/pint": "^1.13.8", - "nunomaduro/collision": "^7.10", - "pestphp/pest": "^v2.24.0", - "pestphp/pest-plugin-arch": "^2.6", - "pestphp/pest-plugin-mock": "^2.0.0", - "pestphp/pest-plugin-type-coverage": "^2.8", - "phpstan/phpstan": "^1.10.55", - "rector/rector": "^1.0.5", - "symfony/var-dumper": "^6.4.2" - }, - "type": "library", - "autoload": { - "files": [ - "src/TailwindMerge.php" - ], - "psr-4": { - "TailwindMerge\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Sandro Gehri", - "email": "sandrogehri@gmail.com" - } - ], - "description": "TailwindMerge for PHP merges multiple Tailwind CSS classes by automatically resolving conflicts between them", - "keywords": [ - "classes", - "merge", - "php", - "tailwindcss" - ], - "support": { - "issues": "https://github.com/gehrisandro/tailwind-merge-php/issues", - "source": "https://github.com/gehrisandro/tailwind-merge-php/tree/v1.1.2" - }, - "funding": [ - { - "url": "https://github.com/gehrisandro", - "type": "github" - } - ], - "time": "2024-05-21T17:32:42+00:00" - }, { "name": "psr/cache", "version": "3.0.0", @@ -275,57 +208,6 @@ }, "time": "2024-09-11T13:17:53+00:00" }, - { - "name": "psr/simple-cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interfaces for simple caching", - "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" - ], - "support": { - "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" - }, - "time": "2021-10-29T13:26:27+00:00" - }, { "name": "symfony/cache", "version": "v7.2.3", @@ -3238,61 +3120,6 @@ ], "time": "2024-10-18T07:58:17+00:00" }, - { - "name": "tales-from-a-dev/twig-tailwind-extra", - "version": "v0.3.0", - "source": { - "type": "git", - "url": "https://github.com/tales-from-a-dev/twig-tailwind-extra.git", - "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tales-from-a-dev/twig-tailwind-extra/zipball/a3cb86414dd5810740cf91966bc1cf10047ce8ef", - "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef", - "shasum": "" - }, - "require": { - "gehrisandro/tailwind-merge-php": "^1.0", - "php": ">=8.2", - "symfony/cache": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "twig/twig": "^3.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.38", - "symfony/phpunit-bridge": "^6.4 || ^7.0" - }, - "type": "twig", - "autoload": { - "psr-4": { - "TalesFromADev\\Twig\\Extra\\Tailwind\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Romain Monteil", - "email": "monteil.romain@gmail.com" - } - ], - "description": "A Twig extension for Tailwind", - "homepage": "https://github.com/tales-from-a-dev/twig-tailwind-extra", - "keywords": [ - "extension", - "symfony", - "tailwind", - "twig" - ], - "support": { - "issues": "https://github.com/tales-from-a-dev/twig-tailwind-extra/issues", - "source": "https://github.com/tales-from-a-dev/twig-tailwind-extra/tree/v0.3.0" - }, - "time": "2024-08-07T23:27:08+00:00" - }, { "name": "twig/extra-bundle", "version": "v3.19.0", @@ -3517,6 +3344,124 @@ } ], "packages-dev": [ + { + "name": "gehrisandro/tailwind-merge-php", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/gehrisandro/tailwind-merge-php.git", + "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gehrisandro/tailwind-merge-php/zipball/dc11b9d4a625dd5be885900e5ef14c3efa260277", + "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277", + "shasum": "" + }, + "require": { + "php": "^8.1.0", + "psr/simple-cache": "^3.0" + }, + "require-dev": { + "laravel/pint": "^1.13.8", + "nunomaduro/collision": "^7.10", + "pestphp/pest": "^v2.24.0", + "pestphp/pest-plugin-arch": "^2.6", + "pestphp/pest-plugin-mock": "^2.0.0", + "pestphp/pest-plugin-type-coverage": "^2.8", + "phpstan/phpstan": "^1.10.55", + "rector/rector": "^1.0.5", + "symfony/var-dumper": "^6.4.2" + }, + "type": "library", + "autoload": { + "files": [ + "src/TailwindMerge.php" + ], + "psr-4": { + "TailwindMerge\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "TailwindMerge for PHP merges multiple Tailwind CSS classes by automatically resolving conflicts between them", + "keywords": [ + "classes", + "merge", + "php", + "tailwindcss" + ], + "support": { + "issues": "https://github.com/gehrisandro/tailwind-merge-php/issues", + "source": "https://github.com/gehrisandro/tailwind-merge-php/tree/v1.1.2" + }, + "funding": [ + { + "url": "https://github.com/gehrisandro", + "type": "github" + } + ], + "time": "2024-05-21T17:32:42+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, { "name": "symfony/http-client", "version": "v7.2.3", @@ -3689,6 +3634,61 @@ } ], "time": "2024-12-07T08:49:48+00:00" + }, + { + "name": "tales-from-a-dev/twig-tailwind-extra", + "version": "v0.3.0", + "source": { + "type": "git", + "url": "https://github.com/tales-from-a-dev/twig-tailwind-extra.git", + "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tales-from-a-dev/twig-tailwind-extra/zipball/a3cb86414dd5810740cf91966bc1cf10047ce8ef", + "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef", + "shasum": "" + }, + "require": { + "gehrisandro/tailwind-merge-php": "^1.0", + "php": ">=8.2", + "symfony/cache": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", + "twig/twig": "^3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.38", + "symfony/phpunit-bridge": "^6.4 || ^7.0" + }, + "type": "twig", + "autoload": { + "psr-4": { + "TalesFromADev\\Twig\\Extra\\Tailwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Monteil", + "email": "monteil.romain@gmail.com" + } + ], + "description": "A Twig extension for Tailwind", + "homepage": "https://github.com/tales-from-a-dev/twig-tailwind-extra", + "keywords": [ + "extension", + "symfony", + "tailwind", + "twig" + ], + "support": { + "issues": "https://github.com/tales-from-a-dev/twig-tailwind-extra/issues", + "source": "https://github.com/tales-from-a-dev/twig-tailwind-extra/tree/v0.3.0" + }, + "time": "2024-08-07T23:27:08+00:00" } ], "aliases": [], @@ -3697,7 +3697,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.2" + "php": ">=8.3" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/src/Toolkit/phpunit.xml.dist b/src/Toolkit/phpunit.xml.dist index f2f679f845..ba14d89f76 100644 --- a/src/Toolkit/phpunit.xml.dist +++ b/src/Toolkit/phpunit.xml.dist @@ -10,10 +10,10 @@ - + - + tests diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index 05ceac952f..dbad617610 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\Command; +namespace Symfony\UX\Toolkit\Command; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -17,8 +17,9 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentifier; -use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; /** * @author Jean-François Lépine @@ -88,12 +89,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::FAILURE; } - if (!file_exists($directory)) { - mkdir($directory, 0777, true); + $filesystem = new Filesystem(); + + if (!$filesystem->exists($directory)) { + $filesystem->mkdir($directory, 0777); } $filename = $directory.'/'.$component->getName().'.html.twig'; - if (file_exists($filename)) { + if ($filesystem->exists($filename)) { $io->error(\sprintf('The component "%s" already exists', $component->getName())); if (!$input->isInteractive()) { return Command::FAILURE; @@ -105,13 +108,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - if (!is_writable(\dirname($filename))) { - $io->error(\sprintf('The directory "%s" is not writable', \dirname($filename))); - - return Command::FAILURE; - } - - file_put_contents($filename, $repository->getContent($component)); + $filesystem->dumpFile($filename, $repository->getContent($component)); if ($io->isVerbose()) { $io->text(\sprintf('The component "%s" has been installed in "%s"', $component->getName(), $filename)); diff --git a/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php b/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php index 0163fbb1b2..16ae34b5cc 100644 --- a/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php +++ b/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\ComponentRepository; +namespace Symfony\UX\Toolkit\ComponentRepository; /** * @author Jean-François Lépine diff --git a/src/Toolkit/src/ComponentRepository/ComponentIdentity.php b/src/Toolkit/src/ComponentRepository/ComponentIdentity.php index 2944d4aa4e..62a0b76f18 100644 --- a/src/Toolkit/src/ComponentRepository/ComponentIdentity.php +++ b/src/Toolkit/src/ComponentRepository/ComponentIdentity.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\ComponentRepository; +namespace Symfony\UX\Toolkit\ComponentRepository; /** * @author Jean-François Lépine diff --git a/src/Toolkit/src/ComponentRepository/ComponentRepository.php b/src/Toolkit/src/ComponentRepository/ComponentRepository.php index b05f5f3568..14918a5c1e 100644 --- a/src/Toolkit/src/ComponentRepository/ComponentRepository.php +++ b/src/Toolkit/src/ComponentRepository/ComponentRepository.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\ComponentRepository; +namespace Symfony\UX\Toolkit\ComponentRepository; /** * @author Jean-François Lépine diff --git a/src/Toolkit/src/ComponentRepository/GithubRepository.php b/src/Toolkit/src/ComponentRepository/GithubRepository.php index 7460677358..d16c4b5f5e 100644 --- a/src/Toolkit/src/ComponentRepository/GithubRepository.php +++ b/src/Toolkit/src/ComponentRepository/GithubRepository.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\ComponentRepository; +namespace Symfony\UX\Toolkit\ComponentRepository; use Symfony\Component\HttpClient\HttpClient; use Symfony\Contracts\HttpClient\HttpClientInterface; diff --git a/src/Toolkit/src/ComponentRepository/OfficialRepository.php b/src/Toolkit/src/ComponentRepository/OfficialRepository.php index 8623a34805..678103a30f 100644 --- a/src/Toolkit/src/ComponentRepository/OfficialRepository.php +++ b/src/Toolkit/src/ComponentRepository/OfficialRepository.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\ComponentRepository; +namespace Symfony\UX\Toolkit\ComponentRepository; use Symfony\Component\Finder\Finder; diff --git a/src/Toolkit/src/ComponentRepository/RepositoryFactory.php b/src/Toolkit/src/ComponentRepository/RepositoryFactory.php index e408efcf55..4764a6726f 100644 --- a/src/Toolkit/src/ComponentRepository/RepositoryFactory.php +++ b/src/Toolkit/src/ComponentRepository/RepositoryFactory.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\ComponentRepository; +namespace Symfony\UX\Toolkit\ComponentRepository; /** * @author Jean-François Lépine diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index 4874ef093c..9adcd5a2f7 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -9,15 +9,15 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit; +namespace Symfony\UX\Toolkit; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; -use Symfony\Ux\Toolkit\Command\UxToolkitInstallCommand; -use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentifier; -use Symfony\Ux\Toolkit\ComponentRepository\GithubRepository; -use Symfony\Ux\Toolkit\ComponentRepository\OfficialRepository; -use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; +use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; +use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; +use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; /** * @author Jean-François Lépine diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index fcc83c832f..5251676e02 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -9,17 +9,17 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\Tests\Command; +namespace Symfony\UX\Toolkit\Tests\Command; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Ux\Toolkit\Command\UxToolkitInstallCommand; -use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentifier; -use Symfony\Ux\Toolkit\ComponentRepository\GithubRepository; -use Symfony\Ux\Toolkit\ComponentRepository\OfficialRepository; -use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; +use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; +use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; +use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; /** * @author Jean-François Lépine diff --git a/src/Toolkit/tests/Component/BadgeTest.php b/src/Toolkit/tests/Component/BadgeTest.php index 6ac36f771c..ad94ba4abe 100644 --- a/src/Toolkit/tests/Component/BadgeTest.php +++ b/src/Toolkit/tests/Component/BadgeTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\Tests\Component; +namespace Symfony\UX\Toolkit\Tests\Component; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Twig\Environment; diff --git a/src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php b/src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php index f4106e6cf8..e8a39435bd 100644 --- a/src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php +++ b/src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php @@ -9,11 +9,11 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\Tests\ComponentRepository; +namespace Symfony\UX\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; -use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentifier; -use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; /** * @author Jean-François Lépine diff --git a/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php b/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php index f808ae48c7..da5fcb87de 100644 --- a/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php +++ b/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php @@ -9,13 +9,13 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\Tests\ComponentRepository; +namespace Symfony\UX\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; -use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentity; -use Symfony\Ux\Toolkit\ComponentRepository\GithubRepository; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; /** * @author Jean-François Lépine diff --git a/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php b/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php index e6a76fe403..9556b63214 100644 --- a/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php +++ b/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php @@ -9,11 +9,11 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\Tests\ComponentRepository; +namespace Symfony\UX\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; -use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentity; -use Symfony\Ux\Toolkit\ComponentRepository\OfficialRepository; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; /** * @author Jean-François Lépine diff --git a/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php b/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php index 5a4d57aecb..596db5d41b 100644 --- a/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php +++ b/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php @@ -9,13 +9,13 @@ * file that was distributed with this source code. */ -namespace Symfony\Ux\Toolkit\Tests\ComponentRepository; +namespace Symfony\UX\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; -use Symfony\Ux\Toolkit\ComponentRepository\ComponentIdentity; -use Symfony\Ux\Toolkit\ComponentRepository\GithubRepository; -use Symfony\Ux\Toolkit\ComponentRepository\OfficialRepository; -use Symfony\Ux\Toolkit\ComponentRepository\RepositoryFactory; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; +use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; /** * @author Jean-François Lépine diff --git a/src/Toolkit/tests/Fixtures/Kernel.php b/src/Toolkit/tests/Fixtures/Kernel.php index 90df835108..577e55a224 100644 --- a/src/Toolkit/tests/Fixtures/Kernel.php +++ b/src/Toolkit/tests/Fixtures/Kernel.php @@ -16,7 +16,7 @@ use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpKernel\Kernel as BaseKernel; -use Symfony\Ux\Toolkit\UxToolkitBundle; +use Symfony\UX\Toolkit\UxToolkitBundle; use Symfony\UX\TwigComponent\TwigComponentBundle; /** From 24c3ef3bd4bda2aa48969146ee687f3c3c7d96d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 12:30:26 +0100 Subject: [PATCH 13/51] use filesystem --- .../src/Command/UxToolkitInstallCommand.php | 18 +++++++++--------- src/Toolkit/tests/Fixtures/Kernel.php | 7 +++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index dbad617610..c0a9861a63 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -55,6 +55,8 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); + $filesystem = new Filesystem(); + $name = $input->getArgument('component'); try { @@ -83,21 +85,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::FAILURE; } - if (!file_exists(\dirname($directory)) || !is_writable(\dirname($directory))) { - $io->error(\sprintf('The directory "%s" does not exist or is not writable', \dirname($directory))); + if (!$filesystem->exists(\dirname($directory))) { + $io->error(\sprintf('The directory "%s" does not exist.', \dirname($directory))); return Command::FAILURE; } - $filesystem = new Filesystem(); - if (!$filesystem->exists($directory)) { - $filesystem->mkdir($directory, 0777); + $filesystem->mkdir($directory); } - $filename = $directory.'/'.$component->getName().'.html.twig'; + $filename = $directory.\DIRECTORY_SEPARATOR.$component->getName().'.html.twig'; if ($filesystem->exists($filename)) { - $io->error(\sprintf('The component "%s" already exists', $component->getName())); + $io->error(\sprintf('The component "%s" already exists.', $component->getName())); if (!$input->isInteractive()) { return Command::FAILURE; } @@ -111,10 +111,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $filesystem->dumpFile($filename, $repository->getContent($component)); if ($io->isVerbose()) { - $io->text(\sprintf('The component "%s" has been installed in "%s"', $component->getName(), $filename)); + $io->text(\sprintf('The component "%s" has been installed in "%s".', $component->getName(), $filename)); } - $io->success(\sprintf('The component "%s" has been installed', $component->getName())); + $io->success(\sprintf('The component "%s" has been installed.', $component->getName())); return Command::SUCCESS; } diff --git a/src/Toolkit/tests/Fixtures/Kernel.php b/src/Toolkit/tests/Fixtures/Kernel.php index 577e55a224..a9ac839f0a 100644 --- a/src/Toolkit/tests/Fixtures/Kernel.php +++ b/src/Toolkit/tests/Fixtures/Kernel.php @@ -45,15 +45,14 @@ protected function configureContainer(ContainerConfigurator $containerConfigurat $containerConfigurator->extension('framework', $config); $containerConfigurator->extension('twig', [ - 'default_path' => __DIR__ . '/../../templates', + 'default_path' => __DIR__.'/../../templates', ]); $config = [ 'anonymous_template_directory' => 'components/', + 'defaults' => [], ]; $containerConfigurator->extension('twig_component', $config); } - - -} \ No newline at end of file +} From 6b33a587ce5b5486729c852d2f383c9cdcfb7997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 12:30:51 +0100 Subject: [PATCH 14/51] way to have screenshots comparaisons --- src/Toolkit/.gitignore | 4 +- .../ui/components/badge-destructive.html | 11 ++ src/Toolkit/tests/ui/generate-html | 43 +++++++ src/Toolkit/tests/ui/reference/.keepme | 0 src/Toolkit/tests/ui/suite | 110 ++++++++++++++++++ 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 src/Toolkit/tests/ui/components/badge-destructive.html create mode 100755 src/Toolkit/tests/ui/generate-html create mode 100644 src/Toolkit/tests/ui/reference/.keepme create mode 100755 src/Toolkit/tests/ui/suite diff --git a/src/Toolkit/.gitignore b/src/Toolkit/.gitignore index f264a1213b..c859396d8c 100644 --- a/src/Toolkit/.gitignore +++ b/src/Toolkit/.gitignore @@ -1,4 +1,6 @@ vendor composer.lock .phpunit.result.cache -var \ No newline at end of file +var +tests/ui/output +tests/ui/screens \ No newline at end of file diff --git a/src/Toolkit/tests/ui/components/badge-destructive.html b/src/Toolkit/tests/ui/components/badge-destructive.html new file mode 100644 index 0000000000..ba7bb4edae --- /dev/null +++ b/src/Toolkit/tests/ui/components/badge-destructive.html @@ -0,0 +1,11 @@ + + + + + + + + +my badge + + \ No newline at end of file diff --git a/src/Toolkit/tests/ui/generate-html b/src/Toolkit/tests/ui/generate-html new file mode 100755 index 0000000000..3f3eedee17 --- /dev/null +++ b/src/Toolkit/tests/ui/generate-html @@ -0,0 +1,43 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * @author Jean-François Lépine + */ + +use Twig\Extra\Html\HtmlExtension; + +require __DIR__ . '/../../vendor/autoload.php'; + +// mount he microkernel +$kernel = new Symfony\UX\Toolkit\Tests\Fixtures\Kernel('test', true); +$kernel->boot(); +$container = $kernel->getContainer()->get('test.service_container'); +$twig = $container->get('twig'); +$twig->addExtension(new HtmlExtension()); + +// list files in the components dir +$files = glob(__DIR__ . '/components/*.html'); + +// for each, we generate a corresponding file into the output dir +foreach ($files as $file) { + $name = pathinfo($file, PATHINFO_FILENAME); + echo " - " . $name . "\n"; + $html = file_get_contents($file); + + $template = $twig->createTemplate($html); + $output = $template->render([]); + + file_put_contents(__DIR__ . '/output/' . $name . '.html', $output); +} + +echo "Done\n"; \ No newline at end of file diff --git a/src/Toolkit/tests/ui/reference/.keepme b/src/Toolkit/tests/ui/reference/.keepme new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Toolkit/tests/ui/suite b/src/Toolkit/tests/ui/suite new file mode 100755 index 0000000000..7c5f5165b7 --- /dev/null +++ b/src/Toolkit/tests/ui/suite @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -e + +# +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view the LICENSE +# file that was distributed with this source code. +# +# +# @author Jean-François Lépine +# + +# +# This script is used to compare screenshots of the components. It generates HTML from twig files, then generates screenshots +# and compares them with the reference images. +# +# Usage: +# +# ./suite <--capture> +# + +PERCENTAGE_THRESHOLD=10 +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Ensure PHP is installed +if ! [ -x "$(command -v php)" ]; then + echo 'Error: PHP is not installed.' >&2 + exit 1 +fi + +# Ensure pageres is installed +if ! [ -x "$(command -v pageres)" ]; then + echo 'Error: pageres is not installed. Please run `npm install -g pageres-cli`.' >&2 + exit 1 +fi + +# check if we should generate reference images +if [ "$1" == "--capture" ]; then + generateReference=true +else + generateReference=false +fi + +referenceDir=$DIR/reference +if [ ! -d "$referenceDir" ]; then + mkdir -p $referenceDir +fi + +# Ensure reference is not empty +if [ ! "$(ls -A $referenceDir)" ]; then + if [ "$generateReference" = false ]; then + echo "Reference directory is empty. Please run again with --capture flag" + exit 1 + fi +fi + +if [ "$generateReference" = true ]; then + rm -Rf $DIR/reference/* +fi + +rm -Rf $DIR/output/* +rm -Rf $DIR/screens/* +mkdir -p $DIR/output +mkdir -p $DIR/screens + +# Generating HTML files from twig components +echo "Generating HTML files..." +./generate-html + +# Generating corresponding images +echo "Generating screenshots..." +files=( $DIR/output/*.html ) +for file in "${files[@]}" +do + name=$(basename $file) + destination=screens/${name%.*} + pageres $file --filename=$destination +done + +if [ "$generateReference" = true ]; then + echo "Reference images generated." + cp $DIR/screens/* $DIR/reference + exit 0 +fi + +# Comparing images +echo "Comparing images..." +for file in $DIR/screens/*.png +do + name=$(basename $file) + reference=$referenceDir/$name + + differenceInPercent=$(convert -metric AE $reference $file -trim -compare -format "%[distortion]" info:) + differenceInPercent=${differenceInPercent%.*} + + echo "- $name: $differenceInPercent%" + + if [ $differenceInPercent -gt $PERCENTAGE_THRESHOLD ]; then + echo "Difference between $reference and $file is $differenceInPercent%" + exit 1 + fi +done + +echo +cnt=$(ls -1 $DIR/screens/*.png | wc -l) +echo "All images ($cnt) are within the threshold of $PERCENTAGE_THRESHOLD% difference." +echo \ No newline at end of file From 3e4a7eef983d21c583028f3d3aaaab01f6d3d87c Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 31 Jan 2025 14:00:10 +0100 Subject: [PATCH 15/51] First iteration of Registry --- src/Toolkit/bin/build-registry.php | 27 +- src/Toolkit/composer.json | 3 + src/Toolkit/composer.lock | 3704 ----------------- .../registry/default/components/Alert.json | 9 + .../registry/default/components/Badge.json | 7 + .../registry/default/components/Button.json | 7 + .../registry/default/components/Card.json | 7 + .../registry/default/components/Navbar.json | 7 + .../registry/default/components/Table.json | 9 + .../default/components/Table/Row.json | 7 + .../registry/default/examples/Badge.json | 7 + .../default/examples/BadgeOutline.json | 7 + .../registry/default/examples/Button.json | 7 + src/Toolkit/src/Registry/Registry.php | 69 + src/Toolkit/src/Registry/RegistryItem.php | 50 + src/Toolkit/src/Registry/RegistryItemType.php | 10 + .../{Alert.twig => Alert.html.twig} | 1 + .../components/{Card.twig => Card.html.twig} | 0 .../{Navbar.twig => Navbar.html.twig} | 0 .../{Table.twig => Table.html.twig} | 0 .../Table/{Row.twig => Row.html.twig} | 2 +- 21 files changed, 234 insertions(+), 3706 deletions(-) delete mode 100644 src/Toolkit/composer.lock create mode 100644 src/Toolkit/registry/default/components/Alert.json create mode 100644 src/Toolkit/registry/default/components/Badge.json create mode 100644 src/Toolkit/registry/default/components/Button.json create mode 100644 src/Toolkit/registry/default/components/Card.json create mode 100644 src/Toolkit/registry/default/components/Navbar.json create mode 100644 src/Toolkit/registry/default/components/Table.json create mode 100644 src/Toolkit/registry/default/components/Table/Row.json create mode 100644 src/Toolkit/registry/default/examples/Badge.json create mode 100644 src/Toolkit/registry/default/examples/BadgeOutline.json create mode 100644 src/Toolkit/registry/default/examples/Button.json create mode 100644 src/Toolkit/src/Registry/Registry.php create mode 100644 src/Toolkit/src/Registry/RegistryItem.php create mode 100644 src/Toolkit/src/Registry/RegistryItemType.php rename src/Toolkit/templates/default/components/{Alert.twig => Alert.html.twig} (77%) rename src/Toolkit/templates/default/components/{Card.twig => Card.html.twig} (100%) rename src/Toolkit/templates/default/components/{Navbar.twig => Navbar.html.twig} (100%) rename src/Toolkit/templates/default/components/{Table.twig => Table.html.twig} (100%) rename src/Toolkit/templates/default/components/Table/{Row.twig => Row.html.twig} (72%) diff --git a/src/Toolkit/bin/build-registry.php b/src/Toolkit/bin/build-registry.php index 0e8614bd9b..741204327f 100755 --- a/src/Toolkit/bin/build-registry.php +++ b/src/Toolkit/bin/build-registry.php @@ -1,4 +1,29 @@ #!/usr/bin/env php files() + ->in($templateDir) + ->name('*.html.twig') + ->sortByName(); + +foreach ($finderTemplates as $file) { + $registry->add(RegistryItem::fromFile($file)); +} + +$registry->save($registryDir, $filesystem); diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index 1c40ac0991..080b94f803 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -39,6 +39,9 @@ "symfony/ux-twig-component": "^2.22", "symfony/filesystem": "^7.2" }, + "require-dev": { + "symfony/finder": "6.4|^7.0" + }, "autoload": { "psr-4": { "Symfony\\UX\\Toolkit\\": "src" diff --git a/src/Toolkit/composer.lock b/src/Toolkit/composer.lock deleted file mode 100644 index 714f306a9c..0000000000 --- a/src/Toolkit/composer.lock +++ /dev/null @@ -1,3704 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "21d10f6bc2641f1203a236c9b517314c", - "packages": [ - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/log", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" - }, - "time": "2024-09-11T13:17:53+00:00" - }, - { - "name": "symfony/cache", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "8d773a575e446de220dca03d600b2d8e1c1c10ec" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/8d773a575e446de220dca03d600b2d8e1c1c10ec", - "reference": "8d773a575e446de220dca03d600b2d8e1c1c10ec", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/cache": "^2.0|^3.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^2.5|^3", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.4|^7.0" - }, - "conflict": { - "doctrine/dbal": "<3.6", - "symfony/dependency-injection": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/var-dumper": "<6.4" - }, - "provide": { - "psr/cache-implementation": "2.0|3.0", - "psr/simple-cache-implementation": "1.0|2.0|3.0", - "symfony/cache-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/dbal": "^3.6|^4", - "predis/predis": "^1.1|^2.0", - "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/clock": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/filesystem": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "classmap": [ - "Traits/ValueWrapper.php" - ], - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-27T11:08:17+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/cache": "^3.0" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, - { - "name": "symfony/config", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "7716594aaae91d9141be080240172a92ecca4d44" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/7716594aaae91d9141be080240172a92ecca4d44", - "reference": "7716594aaae91d9141be080240172a92ecca4d44", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^7.1", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/finder": "<6.4", - "symfony/service-contracts": "<2.5" - }, - "require-dev": { - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/config/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-22T12:07:01+00:00" - }, - { - "name": "symfony/console", - "version": "v7.2.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-11T03:49:26+00:00" - }, - { - "name": "symfony/dependency-injection", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "1d321c4bc3fe926fd4c38999a4c9af4f5d61ddfc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/1d321c4bc3fe926fd4c38999a4c9af4f5d61ddfc", - "reference": "1d321c4bc3fe926fd4c38999a4c9af4f5d61ddfc", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^3.5", - "symfony/var-exporter": "^6.4|^7.0" - }, - "conflict": { - "ext-psr": "<1.1|>=2", - "symfony/config": "<6.4", - "symfony/finder": "<6.4", - "symfony/yaml": "<6.4" - }, - "provide": { - "psr/container-implementation": "1.1|2.0", - "symfony/service-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "symfony/config": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows you to standardize and centralize the way objects are constructed in your application", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-17T10:56:55+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.5-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, - { - "name": "symfony/error-handler", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/error-handler.git", - "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/959a74d044a6db21f4caa6d695648dcb5584cb49", - "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^6.4|^7.0" - }, - "conflict": { - "symfony/deprecation-contracts": "<2.5", - "symfony/http-kernel": "<6.4" - }, - "require-dev": { - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" - }, - "bin": [ - "Resources/bin/patch-type-declarations" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\ErrorHandler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to manage errors and ease debugging PHP code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-07T09:39:55+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/service-contracts": "<2.5" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-25T15:15:23+00:00" - }, - { - "name": "symfony/finder", - "version": "v7.2.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "symfony/filesystem": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-30T19:00:17+00:00" - }, - { - "name": "symfony/framework-bundle", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/framework-bundle.git", - "reference": "d37a43dd0b2079605fcab3056dac71934f06dc0f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/d37a43dd0b2079605fcab3056dac71934f06dc0f", - "reference": "d37a43dd0b2079605fcab3056dac71934f06dc0f", - "shasum": "" - }, - "require": { - "composer-runtime-api": ">=2.1", - "ext-xml": "*", - "php": ">=8.2", - "symfony/cache": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^7.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/filesystem": "^7.1", - "symfony/finder": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^7.2", - "symfony/polyfill-mbstring": "~1.0", - "symfony/routing": "^6.4|^7.0" - }, - "conflict": { - "doctrine/persistence": "<1.3", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/asset": "<6.4", - "symfony/asset-mapper": "<6.4", - "symfony/clock": "<6.4", - "symfony/console": "<6.4", - "symfony/dom-crawler": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/form": "<6.4", - "symfony/http-client": "<6.4", - "symfony/lock": "<6.4", - "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4", - "symfony/mime": "<6.4", - "symfony/property-access": "<6.4", - "symfony/property-info": "<6.4", - "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", - "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", - "symfony/security-core": "<6.4", - "symfony/security-csrf": "<7.2", - "symfony/serializer": "<7.1", - "symfony/stopwatch": "<6.4", - "symfony/translation": "<6.4", - "symfony/twig-bridge": "<6.4", - "symfony/twig-bundle": "<6.4", - "symfony/validator": "<6.4", - "symfony/web-profiler-bundle": "<6.4", - "symfony/webhook": "<7.2", - "symfony/workflow": "<6.4" - }, - "require-dev": { - "doctrine/persistence": "^1.3|^2|^3", - "dragonmantank/cron-expression": "^3.1", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "seld/jsonlint": "^1.10", - "symfony/asset": "^6.4|^7.0", - "symfony/asset-mapper": "^6.4|^7.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/dotenv": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/html-sanitizer": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/mailer": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/notifier": "^6.4|^7.0", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/scheduler": "^6.4.4|^7.0.4", - "symfony/security-bundle": "^6.4|^7.0", - "symfony/semaphore": "^6.4|^7.0", - "symfony/serializer": "^7.1", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/string": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/twig-bundle": "^6.4|^7.0", - "symfony/type-info": "^7.1", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/webhook": "^7.2", - "symfony/workflow": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0", - "twig/twig": "^3.12" - }, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "Symfony\\Bundle\\FrameworkBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-29T07:13:55+00:00" - }, - { - "name": "symfony/http-foundation", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee1b504b8926198be89d05e5b6fc4c3810c090f0", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php83": "^1.27" - }, - "conflict": { - "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4.12|>=7.0,<7.1.5" - }, - "require-dev": { - "doctrine/dbal": "^3.6|^4", - "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4.12|^7.1.5", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Defines an object-oriented layer for the HTTP specification", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-17T10:56:55+00:00" - }, - { - "name": "symfony/http-kernel", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", - "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/browser-kit": "<6.4", - "symfony/cache": "<6.4", - "symfony/config": "<6.4", - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<6.4", - "symfony/form": "<6.4", - "symfony/http-client": "<6.4", - "symfony/http-client-contracts": "<2.5", - "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4", - "symfony/translation": "<6.4", - "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<6.4", - "symfony/validator": "<6.4", - "symfony/var-dumper": "<6.4", - "twig/twig": "<3.12" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^7.1", - "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^7.1", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0", - "symfony/var-exporter": "^6.4|^7.0", - "twig/twig": "^3.12" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpKernel\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a structured process for converting a Request into a Response", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-29T07:40:13+00:00" - }, - { - "name": "symfony/mime", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2fc3b4bd67e4747e45195bc4c98bea4628476204", - "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<6.4", - "symfony/serializer": "<6.4.3|>7.0,<7.0.3" - }, - "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1|^4", - "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Mime\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows manipulating MIME messages", - "homepage": "https://symfony.com", - "keywords": [ - "mime", - "mime-type" - ], - "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-27T11:08:17+00:00" - }, - { - "name": "symfony/phpunit-bridge", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "2bbde92ab25a0e2c88160857af7be9db5da0d145" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/2bbde92ab25a0e2c88160857af7be9db5da0d145", - "reference": "2bbde92ab25a0e2c88160857af7be9db5da0d145", - "shasum": "" - }, - "require": { - "php": ">=7.2.5" - }, - "conflict": { - "phpunit/phpunit": "<7.5|9.1.2" - }, - "require-dev": { - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/error-handler": "^5.4|^6.4|^7.0", - "symfony/polyfill-php81": "^1.27" - }, - "bin": [ - "bin/simple-phpunit" - ], - "type": "symfony-bridge", - "extra": { - "thanks": { - "url": "https://github.com/sebastianbergmann/phpunit", - "name": "phpunit/phpunit" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Bridge\\PhpUnit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/", - "/bin/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides utilities for PHPUnit, especially user deprecation notices management", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-13T16:15:23+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "symfony/polyfill-intl-normalizer": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-php81", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-php83", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/property-access", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/property-access.git", - "reference": "b28732e315d81fbec787f838034de7d6c9b2b902" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/b28732e315d81fbec787f838034de7d6c9b2b902", - "reference": "b28732e315d81fbec787f838034de7d6c9b2b902", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/property-info": "^6.4|^7.0" - }, - "require-dev": { - "symfony/cache": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\PropertyAccess\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides functions to read and write from/to an object or array using a simple string notation", - "homepage": "https://symfony.com", - "keywords": [ - "access", - "array", - "extraction", - "index", - "injection", - "object", - "property", - "property-path", - "reflection" - ], - "support": { - "source": "https://github.com/symfony/property-access/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-17T10:56:55+00:00" - }, - { - "name": "symfony/property-info", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/property-info.git", - "reference": "dedb118fd588a92f226b390250b384d25f4192fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/dedb118fd588a92f226b390250b384d25f4192fe", - "reference": "dedb118fd588a92f226b390250b384d25f4192fe", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/string": "^6.4|^7.0", - "symfony/type-info": "~7.1.9|^7.2.2" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "<5.2", - "phpdocumentor/type-resolver": "<1.5.1", - "symfony/cache": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/serializer": "<6.4" - }, - "require-dev": { - "phpdocumentor/reflection-docblock": "^5.2", - "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\PropertyInfo\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kévin Dunglas", - "email": "dunglas@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Extracts information about PHP class' properties using metadata of popular sources", - "homepage": "https://symfony.com", - "keywords": [ - "doctrine", - "phpdoc", - "property", - "symfony", - "type", - "validator" - ], - "support": { - "source": "https://github.com/symfony/property-info/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-27T11:08:17+00:00" - }, - { - "name": "symfony/routing", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/ee9a67edc6baa33e5fae662f94f91fd262930996", - "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/config": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/yaml": "<6.4" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Routing\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Maps an HTTP request to a set of configuration variables", - "homepage": "https://symfony.com", - "keywords": [ - "router", - "routing", - "uri", - "url" - ], - "support": { - "source": "https://github.com/symfony/routing/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-17T10:56:55+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, - { - "name": "symfony/string", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.5" - }, - "require-dev": { - "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-13T13:31:26+00:00" - }, - { - "name": "symfony/translation-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Translation\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to translation", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, - { - "name": "symfony/twig-bridge", - "version": "v7.2.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/twig-bridge.git", - "reference": "29e4c66de9618e67dc1f5f13bc667aca2a228f1e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/29e4c66de9618e67dc1f5f13bc667aca2a228f1e", - "reference": "29e4c66de9618e67dc1f5f13bc667aca2a228f1e", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.12" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<6.4", - "symfony/form": "<6.4", - "symfony/http-foundation": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/mime": "<6.4", - "symfony/serializer": "<6.4", - "symfony/translation": "<6.4", - "symfony/workflow": "<6.4" - }, - "require-dev": { - "egulias/email-validator": "^2.1.10|^3|^4", - "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^6.4|^7.0", - "symfony/asset-mapper": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/emoji": "^7.1", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/html-sanitizer": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", - "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^6.4|^7.0", - "symfony/security-csrf": "^6.4|^7.0", - "symfony/security-http": "^6.4|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/workflow": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0", - "twig/cssinliner-extra": "^2.12|^3", - "twig/inky-extra": "^2.12|^3", - "twig/markdown-extra": "^2.12|^3" - }, - "type": "symfony-bridge", - "autoload": { - "psr-4": { - "Symfony\\Bridge\\Twig\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides integration for Twig with various Symfony components", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-19T14:25:03+00:00" - }, - { - "name": "symfony/twig-bundle", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/twig-bundle.git", - "reference": "cd2be4563afaef5285bb6e0a06c5445e644a5c01" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/cd2be4563afaef5285bb6e0a06c5445e644a5c01", - "reference": "cd2be4563afaef5285bb6e0a06c5445e644a5c01", - "shasum": "" - }, - "require": { - "composer-runtime-api": ">=2.1", - "php": ">=8.2", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/twig-bridge": "^6.4|^7.0", - "twig/twig": "^3.12" - }, - "conflict": { - "symfony/framework-bundle": "<6.4", - "symfony/translation": "<6.4" - }, - "require-dev": { - "symfony/asset": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/framework-bundle": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" - }, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "Symfony\\Bundle\\TwigBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a tight integration of Twig into the Symfony full-stack framework", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-23T08:11:15+00:00" - }, - { - "name": "symfony/type-info", - "version": "v7.2.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/type-info.git", - "reference": "3b5a17470fff0034f25fd4287cbdaa0010d2f749" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/3b5a17470fff0034f25fd4287cbdaa0010d2f749", - "reference": "3b5a17470fff0034f25fd4287cbdaa0010d2f749", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/container": "^1.1|^2.0" - }, - "require-dev": { - "phpstan/phpdoc-parser": "^1.0|^2.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\TypeInfo\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mathias Arlaud", - "email": "mathias.arlaud@gmail.com" - }, - { - "name": "Baptiste LEDUC", - "email": "baptiste.leduc@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Extracts PHP types information.", - "homepage": "https://symfony.com", - "keywords": [ - "PHPStan", - "phpdoc", - "symfony", - "type" - ], - "support": { - "source": "https://github.com/symfony/type-info/tree/v7.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-20T13:38:37+00:00" - }, - { - "name": "symfony/ux-twig-component", - "version": "v2.22.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/ux-twig-component.git", - "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/9b347f6ca2d9e18cee630787f0a6aa453982bf18", - "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/deprecation-contracts": "^2.2|^3.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "twig/twig": "^3.8" - }, - "conflict": { - "symfony/config": "<5.4.0" - }, - "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/css-selector": "^5.4|^6.0|^7.0", - "symfony/dom-crawler": "^5.4|^6.0|^7.0", - "symfony/framework-bundle": "^5.4|^6.0|^7.0", - "symfony/phpunit-bridge": "^6.0|^7.0", - "symfony/stimulus-bundle": "^2.9.1", - "symfony/twig-bundle": "^5.4|^6.0|^7.0", - "symfony/webpack-encore-bundle": "^1.15" - }, - "type": "symfony-bundle", - "extra": { - "thanks": { - "url": "https://github.com/symfony/ux", - "name": "symfony/ux" - } - }, - "autoload": { - "psr-4": { - "Symfony\\UX\\TwigComponent\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Twig components for Symfony", - "homepage": "https://symfony.com", - "keywords": [ - "components", - "symfony-ux", - "twig" - ], - "support": { - "source": "https://github.com/symfony/ux-twig-component/tree/v2.22.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-07T18:05:50+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a", - "reference": "82b478c69745d8878eb60f9a049a4d584996f73a", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/console": "<6.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/uid": "^6.4|^7.0", - "twig/twig": "^3.12" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-17T11:39:41+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d", - "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "lazy-loading", - "proxy", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-18T07:58:17+00:00" - }, - { - "name": "twig/extra-bundle", - "version": "v3.19.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "9746573ca4bc1cd03a767a183faadaf84e0c31fa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/9746573ca4bc1cd03a767a183faadaf84e0c31fa", - "reference": "9746573ca4bc1cd03a767a183faadaf84e0c31fa", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/framework-bundle": "^5.4|^6.4|^7.0", - "symfony/twig-bundle": "^5.4|^6.4|^7.0", - "twig/twig": "^3.2|^4.0" - }, - "require-dev": { - "league/commonmark": "^1.0|^2.0", - "symfony/phpunit-bridge": "^6.4|^7.0", - "twig/cache-extra": "^3.0", - "twig/cssinliner-extra": "^3.0", - "twig/html-extra": "^3.0", - "twig/inky-extra": "^3.0", - "twig/intl-extra": "^3.0", - "twig/markdown-extra": "^3.0", - "twig/string-extra": "^3.0" - }, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "Twig\\Extra\\TwigExtraBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - } - ], - "description": "A Symfony bundle for extra Twig extensions", - "homepage": "https://twig.symfony.com", - "keywords": [ - "bundle", - "extra", - "twig" - ], - "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.19.0" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2024-09-26T19:22:23+00:00" - }, - { - "name": "twig/html-extra", - "version": "v3.19.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/html-extra.git", - "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/c63b28e192c1b7c15bb60f81d2e48b140846239a", - "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/mime": "^5.4|^6.4|^7.0", - "twig/twig": "^3.13|^4.0" - }, - "require-dev": { - "symfony/phpunit-bridge": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Twig\\Extra\\Html\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - } - ], - "description": "A Twig extension for HTML", - "homepage": "https://twig.symfony.com", - "keywords": [ - "html", - "twig" - ], - "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.19.0" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2024-12-29T10:29:59+00:00" - }, - { - "name": "twig/twig", - "version": "v3.19.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/d4f8c2b86374f08efc859323dbcd95c590f7124e", - "reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php81": "^1.29" - }, - "require-dev": { - "phpstan/phpstan": "^2.0", - "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" - }, - "type": "library", - "autoload": { - "files": [ - "src/Resources/core.php", - "src/Resources/debug.php", - "src/Resources/escaper.php", - "src/Resources/string_loader.php" - ], - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Twig Team", - "role": "Contributors" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", - "keywords": [ - "templating" - ], - "support": { - "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.19.0" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2025-01-29T07:06:14+00:00" - } - ], - "packages-dev": [ - { - "name": "gehrisandro/tailwind-merge-php", - "version": "v1.1.2", - "source": { - "type": "git", - "url": "https://github.com/gehrisandro/tailwind-merge-php.git", - "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/gehrisandro/tailwind-merge-php/zipball/dc11b9d4a625dd5be885900e5ef14c3efa260277", - "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277", - "shasum": "" - }, - "require": { - "php": "^8.1.0", - "psr/simple-cache": "^3.0" - }, - "require-dev": { - "laravel/pint": "^1.13.8", - "nunomaduro/collision": "^7.10", - "pestphp/pest": "^v2.24.0", - "pestphp/pest-plugin-arch": "^2.6", - "pestphp/pest-plugin-mock": "^2.0.0", - "pestphp/pest-plugin-type-coverage": "^2.8", - "phpstan/phpstan": "^1.10.55", - "rector/rector": "^1.0.5", - "symfony/var-dumper": "^6.4.2" - }, - "type": "library", - "autoload": { - "files": [ - "src/TailwindMerge.php" - ], - "psr-4": { - "TailwindMerge\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Sandro Gehri", - "email": "sandrogehri@gmail.com" - } - ], - "description": "TailwindMerge for PHP merges multiple Tailwind CSS classes by automatically resolving conflicts between them", - "keywords": [ - "classes", - "merge", - "php", - "tailwindcss" - ], - "support": { - "issues": "https://github.com/gehrisandro/tailwind-merge-php/issues", - "source": "https://github.com/gehrisandro/tailwind-merge-php/tree/v1.1.2" - }, - "funding": [ - { - "url": "https://github.com/gehrisandro", - "type": "github" - } - ], - "time": "2024-05-21T17:32:42+00:00" - }, - { - "name": "psr/simple-cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interfaces for simple caching", - "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" - ], - "support": { - "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" - }, - "time": "2021-10-29T13:26:27+00:00" - }, - { - "name": "symfony/http-client", - "version": "v7.2.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/7ce6078c79a4a7afff931c413d2959d3bffbfb8d", - "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.4|^3.5.2", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "amphp/amp": "<2.5", - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.4" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "3.0" - }, - "require-dev": { - "amphp/http-client": "^4.2.1|^5.0", - "amphp/http-tunnel": "^1.0|^2.0", - "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4|^2.0", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", - "homepage": "https://symfony.com", - "keywords": [ - "http" - ], - "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-28T15:51:35+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v3.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-07T08:49:48+00:00" - }, - { - "name": "tales-from-a-dev/twig-tailwind-extra", - "version": "v0.3.0", - "source": { - "type": "git", - "url": "https://github.com/tales-from-a-dev/twig-tailwind-extra.git", - "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tales-from-a-dev/twig-tailwind-extra/zipball/a3cb86414dd5810740cf91966bc1cf10047ce8ef", - "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef", - "shasum": "" - }, - "require": { - "gehrisandro/tailwind-merge-php": "^1.0", - "php": ">=8.2", - "symfony/cache": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "twig/twig": "^3.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.38", - "symfony/phpunit-bridge": "^6.4 || ^7.0" - }, - "type": "twig", - "autoload": { - "psr-4": { - "TalesFromADev\\Twig\\Extra\\Tailwind\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Romain Monteil", - "email": "monteil.romain@gmail.com" - } - ], - "description": "A Twig extension for Tailwind", - "homepage": "https://github.com/tales-from-a-dev/twig-tailwind-extra", - "keywords": [ - "extension", - "symfony", - "tailwind", - "twig" - ], - "support": { - "issues": "https://github.com/tales-from-a-dev/twig-tailwind-extra/issues", - "source": "https://github.com/tales-from-a-dev/twig-tailwind-extra/tree/v0.3.0" - }, - "time": "2024-08-07T23:27:08+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.3" - }, - "platform-dev": [], - "plugin-api-version": "2.6.0" -} diff --git a/src/Toolkit/registry/default/components/Alert.json b/src/Toolkit/registry/default/components/Alert.json new file mode 100644 index 0000000000..b966032267 --- /dev/null +++ b/src/Toolkit/registry/default/components/Alert.json @@ -0,0 +1,9 @@ +{ + "name": "Alert", + "theme": "default", + "type": "component", + "code": "
\n Dependency test<\/twig:Button>\n {% block content %}Alert{% endblock %}\n<\/div>\n", + "dependencies": [ + "Button" + ] +} \ No newline at end of file diff --git a/src/Toolkit/registry/default/components/Badge.json b/src/Toolkit/registry/default/components/Badge.json new file mode 100644 index 0000000000..4660110c02 --- /dev/null +++ b/src/Toolkit/registry/default/components/Badge.json @@ -0,0 +1,7 @@ +{ + "name": "Badge", + "theme": "default", + "type": "component", + "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n variants: {\n variant: {\n default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary\/80\",\n secondary: \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary\/80\",\n destructive: \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive\/80\",\n }\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}{% endblock %}\n<\/div>\n", + "dependencies": [] +} \ No newline at end of file diff --git a/src/Toolkit/registry/default/components/Button.json b/src/Toolkit/registry/default/components/Button.json new file mode 100644 index 0000000000..7c64e1efee --- /dev/null +++ b/src/Toolkit/registry/default/components/Button.json @@ -0,0 +1,7 @@ +{ + "name": "Button", + "theme": "default", + "type": "component", + "code": "
\n {% block content %}{% endblock %}\n<\/table>\n", + "dependencies": [ + "Row" + ] +} \ No newline at end of file diff --git a/src/Toolkit/registry/default/components/Table/Row.json b/src/Toolkit/registry/default/components/Table/Row.json new file mode 100644 index 0000000000..d1f0f66d72 --- /dev/null +++ b/src/Toolkit/registry/default/components/Table/Row.json @@ -0,0 +1,7 @@ +{ + "name": "Row", + "theme": "default", + "type": "component", + "code": "\n {% block content %}Row{% endblock %}\n<\/tr>\n", + "dependencies": [] +} \ No newline at end of file diff --git a/src/Toolkit/registry/default/examples/Badge.json b/src/Toolkit/registry/default/examples/Badge.json new file mode 100644 index 0000000000..342708313b --- /dev/null +++ b/src/Toolkit/registry/default/examples/Badge.json @@ -0,0 +1,7 @@ +{ + "name": "Badge", + "theme": "default", + "type": "example", + "code": "Badge<\/twig:Badge>\n", + "dependencies": [] +} \ No newline at end of file diff --git a/src/Toolkit/registry/default/examples/BadgeOutline.json b/src/Toolkit/registry/default/examples/BadgeOutline.json new file mode 100644 index 0000000000..8b9ce08ae2 --- /dev/null +++ b/src/Toolkit/registry/default/examples/BadgeOutline.json @@ -0,0 +1,7 @@ +{ + "name": "BadgeOutline", + "theme": "default", + "type": "example", + "code": "Badge<\/twig:Badge>\nBadge<\/twig:Badge>\nBadge<\/twig:Badge>\n", + "dependencies": [] +} \ No newline at end of file diff --git a/src/Toolkit/registry/default/examples/Button.json b/src/Toolkit/registry/default/examples/Button.json new file mode 100644 index 0000000000..301f6cb7dd --- /dev/null +++ b/src/Toolkit/registry/default/examples/Button.json @@ -0,0 +1,7 @@ +{ + "name": "Button", + "theme": "default", + "type": "example", + "code": "Click me<\/twig:Button>\n", + "dependencies": [] +} \ No newline at end of file diff --git a/src/Toolkit/src/Registry/Registry.php b/src/Toolkit/src/Registry/Registry.php new file mode 100644 index 0000000000..e2c20823e7 --- /dev/null +++ b/src/Toolkit/src/Registry/Registry.php @@ -0,0 +1,69 @@ +items[] = $item; + } + + public function save(string $registryDir, Filesystem $filesystem): void + { + $filesystem->mkdir($registryDir); + + foreach ($this->items as $item) { + $itemPath = Path::join($registryDir, $item->theme, $item->type->value.'s', $item->parentName ?: '', $item->name.'.json'); + $filesystem->dumpFile($itemPath, json_encode([ + 'name' => $item->name, + 'theme' => $item->theme, + 'type' => $item->type->value, + 'code' => $item->code, + 'dependencies' => $this->getDependencies($item), + ], \JSON_PRETTY_PRINT)); + } + } + + private function getDependencies(RegistryItem $component): array + { + if ($component->type !== RegistryItemType::Component) { + return []; + } + + $dependencies = []; + + foreach ($this->items as $item) { + if ($item->theme !== $component->theme || $item->type !== $component->type) { + continue; + } + + if ($item->parentName === $component->name) { + $dependencies[] = $item->name; + } + + if (str_contains($component->code, 'name)) { + $dependencies[] = $item->name; + } + } + + return $dependencies; + } +} diff --git a/src/Toolkit/src/Registry/RegistryItem.php b/src/Toolkit/src/Registry/RegistryItem.php new file mode 100644 index 0000000000..6b07deff35 --- /dev/null +++ b/src/Toolkit/src/Registry/RegistryItem.php @@ -0,0 +1,50 @@ +default|new-york)/(?Pcomponent|example)s/(?P[A-Z][a-zA-Z]*)(?:/(?P[A-Z][a-zA-Z]*))?\.html\.twig$#'; + + public function __construct( + public string $name, + public RegistryItemType $type, + public string $theme, + public ?string $parentName, + public string $code, + ) { + + } + + public static function fromFile(SplFileInfo $file): self + { + if (!preg_match(self::REGEX_RELATIVE_FILE, $file->getRelativePathname(), $matches)) { + throw new \InvalidArgumentException(sprintf( + 'Unable to parse file path "%s", it must match the following pattern: "//(/)?.html.twig"', + $file->getRelativePathname(), + )); + } + + $theme = $matches['theme']; + $type = RegistryItemType::from($matches['type']); + $name = $matches['name'] ?? $matches['nameOrParentName']; + $parentName = isset($matches['name']) ? $matches['nameOrParentName'] : null; + + return new self( + $name, + $type, + $theme, + $parentName, + $file->getContents(), + ); + } +} diff --git a/src/Toolkit/src/Registry/RegistryItemType.php b/src/Toolkit/src/Registry/RegistryItemType.php new file mode 100644 index 0000000000..9a9692c49e --- /dev/null +++ b/src/Toolkit/src/Registry/RegistryItemType.php @@ -0,0 +1,10 @@ + + Dependency test {% block content %}Alert{% endblock %} diff --git a/src/Toolkit/templates/default/components/Card.twig b/src/Toolkit/templates/default/components/Card.html.twig similarity index 100% rename from src/Toolkit/templates/default/components/Card.twig rename to src/Toolkit/templates/default/components/Card.html.twig diff --git a/src/Toolkit/templates/default/components/Navbar.twig b/src/Toolkit/templates/default/components/Navbar.html.twig similarity index 100% rename from src/Toolkit/templates/default/components/Navbar.twig rename to src/Toolkit/templates/default/components/Navbar.html.twig diff --git a/src/Toolkit/templates/default/components/Table.twig b/src/Toolkit/templates/default/components/Table.html.twig similarity index 100% rename from src/Toolkit/templates/default/components/Table.twig rename to src/Toolkit/templates/default/components/Table.html.twig diff --git a/src/Toolkit/templates/default/components/Table/Row.twig b/src/Toolkit/templates/default/components/Table/Row.html.twig similarity index 72% rename from src/Toolkit/templates/default/components/Table/Row.twig rename to src/Toolkit/templates/default/components/Table/Row.html.twig index 452cfd3628..b509d57630 100644 --- a/src/Toolkit/templates/default/components/Table/Row.twig +++ b/src/Toolkit/templates/default/components/Table/Row.html.twig @@ -1,5 +1,5 @@ - {% block content %}Button{% endblock %} + {% block content %}Row{% endblock %} From 9eec43446c9bfd55e08efe8f119d2aaf3cd39b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 14:27:52 +0100 Subject: [PATCH 16/51] fixed tests with the new directory structure --- src/Toolkit/composer.json | 3 ++- .../ComponentRepository/ComponentIdentity.php | 6 +++--- .../ComponentRepository.php | 2 +- .../ComponentRepository/OfficialRepository.php | 2 +- src/Toolkit/src/Registry/Registry.php | 12 +++++++++++- src/Toolkit/src/Registry/RegistryItem.php | 18 ++++++++++++------ src/Toolkit/src/UxToolkitBundle.php | 2 +- .../default/components/Badge.html.twig | 4 ++-- .../Command/UxToolkitInstallCommandTest.php | 2 +- src/Toolkit/tests/Component/BadgeTest.php | 5 ++--- .../OfficialRepositoryTest.php | 2 +- src/Toolkit/tests/Fixtures/Kernel.php | 4 +++- 12 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index 080b94f803..1b7b47246f 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -40,7 +40,8 @@ "symfony/filesystem": "^7.2" }, "require-dev": { - "symfony/finder": "6.4|^7.0" + "symfony/finder": "6.4|^7.0", + "tales-from-a-dev/twig-tailwind-extra": "^0.3.0" }, "autoload": { "psr-4": { diff --git a/src/Toolkit/src/ComponentRepository/ComponentIdentity.php b/src/Toolkit/src/ComponentRepository/ComponentIdentity.php index 62a0b76f18..20a5f155c7 100644 --- a/src/Toolkit/src/ComponentRepository/ComponentIdentity.php +++ b/src/Toolkit/src/ComponentRepository/ComponentIdentity.php @@ -16,13 +16,13 @@ * * @internal */ -readonly final class ComponentIdentity +final readonly class ComponentIdentity { public function __construct( private string $vendor, private ?string $package = null, private ?string $name = null, - private ?string $version = 'main' + private ?string $version = 'main', ) { } @@ -45,4 +45,4 @@ public function getVersion(): ?string { return $this->version; } -} \ No newline at end of file +} diff --git a/src/Toolkit/src/ComponentRepository/ComponentRepository.php b/src/Toolkit/src/ComponentRepository/ComponentRepository.php index 14918a5c1e..186e6d0a55 100644 --- a/src/Toolkit/src/ComponentRepository/ComponentRepository.php +++ b/src/Toolkit/src/ComponentRepository/ComponentRepository.php @@ -19,4 +19,4 @@ interface ComponentRepository { public function getContent(ComponentIdentity $component): string; -} \ No newline at end of file +} diff --git a/src/Toolkit/src/ComponentRepository/OfficialRepository.php b/src/Toolkit/src/ComponentRepository/OfficialRepository.php index 678103a30f..564fecf5b2 100644 --- a/src/Toolkit/src/ComponentRepository/OfficialRepository.php +++ b/src/Toolkit/src/ComponentRepository/OfficialRepository.php @@ -23,7 +23,7 @@ class OfficialRepository implements ComponentRepository public function getContent(ComponentIdentity $component): string { $finder = new Finder(); - $finder->files()->in(__DIR__.'/../../templates/components/ux/')->name($component->getName().'.html.twig'); + $finder->files()->in(__DIR__.'/../../templates/default/components/')->name($component->getName().'.html.twig'); if (!$finder->hasResults()) { throw new \InvalidArgumentException(\sprintf('The component "%s" does not exist', $component->getName())); } diff --git a/src/Toolkit/src/Registry/Registry.php b/src/Toolkit/src/Registry/Registry.php index e2c20823e7..7c0fa02290 100644 --- a/src/Toolkit/src/Registry/Registry.php +++ b/src/Toolkit/src/Registry/Registry.php @@ -1,6 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\UX\Toolkit\Registry; use Symfony\Component\Filesystem\Filesystem; @@ -44,7 +54,7 @@ public function save(string $registryDir, Filesystem $filesystem): void private function getDependencies(RegistryItem $component): array { - if ($component->type !== RegistryItemType::Component) { + if (RegistryItemType::Component !== $component->type) { return []; } diff --git a/src/Toolkit/src/Registry/RegistryItem.php b/src/Toolkit/src/Registry/RegistryItem.php index 6b07deff35..5dcf29c5bf 100644 --- a/src/Toolkit/src/Registry/RegistryItem.php +++ b/src/Toolkit/src/Registry/RegistryItem.php @@ -1,6 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\UX\Toolkit\Registry; use Symfony\Component\Finder\SplFileInfo; @@ -11,7 +21,7 @@ final readonly class RegistryItem { /** - * https://regex101.com/r/8NcORd/1 + * https://regex101.com/r/8NcORd/1. */ private const REGEX_RELATIVE_FILE = '#^(?Pdefault|new-york)/(?Pcomponent|example)s/(?P[A-Z][a-zA-Z]*)(?:/(?P[A-Z][a-zA-Z]*))?\.html\.twig$#'; @@ -22,16 +32,12 @@ public function __construct( public ?string $parentName, public string $code, ) { - } public static function fromFile(SplFileInfo $file): self { if (!preg_match(self::REGEX_RELATIVE_FILE, $file->getRelativePathname(), $matches)) { - throw new \InvalidArgumentException(sprintf( - 'Unable to parse file path "%s", it must match the following pattern: "//(/)?.html.twig"', - $file->getRelativePathname(), - )); + throw new \InvalidArgumentException(\sprintf('Unable to parse file path "%s", it must match the following pattern: "//(/)?.html.twig"', $file->getRelativePathname())); } $theme = $matches['theme']; diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index 9adcd5a2f7..f358439107 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -47,7 +47,7 @@ public function build(ContainerBuilder $container): void ; // Inject http client (if exists) to github repository - if($container->has('http_client')) { + if ($container->has('http_client')) { $container->getDefinition(GithubRepository::class) ->setArgument('$httpClient', $container->get('http_client')); } diff --git a/src/Toolkit/templates/default/components/Badge.html.twig b/src/Toolkit/templates/default/components/Badge.html.twig index 3f26079d0c..eab142dd5d 100644 --- a/src/Toolkit/templates/default/components/Badge.html.twig +++ b/src/Toolkit/templates/default/components/Badge.html.twig @@ -6,7 +6,7 @@ default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - } + }, outline: { true: "text-foreground bg-white", } @@ -27,7 +27,7 @@ ) -%}
{% block content %}{% endblock %} diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index 5251676e02..d49799cd7d 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -56,7 +56,7 @@ public function testShouldAbleToCreateTheBadgeComponent(): void $this->assertFileExists($expectedFile); // The content of the file should be the same as the content of the Badge component - $expectedContent = file_get_contents(__DIR__.'/../../templates/components/ux/Badge.html.twig'); + $expectedContent = file_get_contents(__DIR__. '/../../templates/default/components/Badge.html.twig'); $actualContent = file_get_contents($expectedFile); $this->assertEquals($expectedContent, $actualContent); } diff --git a/src/Toolkit/tests/Component/BadgeTest.php b/src/Toolkit/tests/Component/BadgeTest.php index ad94ba4abe..ffb2938a82 100644 --- a/src/Toolkit/tests/Component/BadgeTest.php +++ b/src/Toolkit/tests/Component/BadgeTest.php @@ -26,17 +26,16 @@ public function testDefaultRenderingIsPossible(): void $html = <<Demo -my badge +my badge EOT; /** @var Environment $twig */ $twig = static::getContainer()->get('twig'); $twig->addExtension(new HtmlExtension()); - $template = $twig->createTemplate($html); $output = $template->render([]); $this->assertStringContainsString('class="inline-flex items-center ', $output); $this->assertStringContainsString('my badge', $output); } -} \ No newline at end of file +} diff --git a/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php b/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php index 9556b63214..441ec82969 100644 --- a/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php +++ b/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php @@ -27,7 +27,7 @@ public function testOfficialRepositoryGetContentOfExistentComponent(): void $content = $repository->getContent($component); - $expectedContent = file_get_contents(__DIR__ . '/../../templates/components/ux/Badge.html.twig'); + $expectedContent = file_get_contents(__DIR__ . '/../../templates/default/components/Badge.html.twig'); $this->assertEquals($expectedContent, $content); } diff --git a/src/Toolkit/tests/Fixtures/Kernel.php b/src/Toolkit/tests/Fixtures/Kernel.php index a9ac839f0a..ebfaa6a05c 100644 --- a/src/Toolkit/tests/Fixtures/Kernel.php +++ b/src/Toolkit/tests/Fixtures/Kernel.php @@ -18,6 +18,7 @@ use Symfony\Component\HttpKernel\Kernel as BaseKernel; use Symfony\UX\Toolkit\UxToolkitBundle; use Symfony\UX\TwigComponent\TwigComponentBundle; +use TalesFromADev\Twig\Extra\Tailwind\Bridge\Symfony\Bundle\TalesFromADevTwigExtraTailwindBundle; /** * @author Jean-François Lépine @@ -33,6 +34,7 @@ public function registerBundles(): iterable new TwigBundle(), new TwigComponentBundle(), new UxToolkitBundle(), + new TalesFromADevTwigExtraTailwindBundle(), ]; } @@ -45,7 +47,7 @@ protected function configureContainer(ContainerConfigurator $containerConfigurat $containerConfigurator->extension('framework', $config); $containerConfigurator->extension('twig', [ - 'default_path' => __DIR__.'/../../templates', + 'default_path' => __DIR__.'/../../templates/default', ]); $config = [ From b35b76efa25ad71ea75c26bf1029c9fdb25aba5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 15:29:33 +0100 Subject: [PATCH 17/51] Way to configure ux_toolkit --- .../src/DependencyInjection/Configuration.php | 38 +++++++++++++++++ .../DependencyInjection/ToolkitExtension.php | 42 +++++++++++++++++++ src/Toolkit/src/UxToolkitBundle.php | 10 ++++- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/Toolkit/src/DependencyInjection/Configuration.php create mode 100644 src/Toolkit/src/DependencyInjection/ToolkitExtension.php diff --git a/src/Toolkit/src/DependencyInjection/Configuration.php b/src/Toolkit/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000000..c808dd05ef --- /dev/null +++ b/src/Toolkit/src/DependencyInjection/Configuration.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\DependencyInjection; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +/** + * @author Jean-François Lépine + */ +class Configuration implements ConfigurationInterface +{ + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder('ux_toolkit'); + + $treeBuilder->getRootNode() + ->children() + ->stringNode('theme') + ->defaultValue('default') + ->end() + ->stringNode('prefix') + ->defaultValue('ux') + ->end() + ->end(); + + return $treeBuilder; + } +} diff --git a/src/Toolkit/src/DependencyInjection/ToolkitExtension.php b/src/Toolkit/src/DependencyInjection/ToolkitExtension.php new file mode 100644 index 0000000000..de91e12315 --- /dev/null +++ b/src/Toolkit/src/DependencyInjection/ToolkitExtension.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +/** + * @author Jean-François Lépine + */ +class ToolkitExtension extends Extension +{ + public function getAlias(): string + { + return 'ux_toolkit'; + } + + public function getConfiguration(array $config, ContainerBuilder $container): Configuration + { + return new Configuration(); + } + + public function load(array $configs, ContainerBuilder $container): void + { + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + // Expose the prefix and theme configured as parameter (for the moment). It will be injected to + // the service responsible for rendering the components. + $container->setParameter('ux_twig_toolkit.theme', $config['theme']); + $container->setParameter('ux_twig_toolkit.prefix', $config['prefix']); + } +} diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index f358439107..482b0aa09c 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -12,12 +12,14 @@ namespace Symfony\UX\Toolkit; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; +use Symfony\UX\Toolkit\DependencyInjection\ToolkitExtension; /** * @author Jean-François Lépine @@ -25,6 +27,11 @@ */ class UxToolkitBundle extends AbstractBundle { + public function getContainerExtension(): ?ExtensionInterface + { + return new ToolkitExtension(); + } + public function build(ContainerBuilder $container): void { parent::build($container); @@ -43,8 +50,7 @@ public function build(ContainerBuilder $container): void $container ->getDefinition(UxToolkitInstallCommand::class) ->setPublic(true) - ->addTag('console.command') - ; + ->addTag('console.command'); // Inject http client (if exists) to github repository if ($container->has('http_client')) { From daf701d0e1e4ce7665437cb67e1bb2bf8d1441c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 15:50:04 +0100 Subject: [PATCH 18/51] isolated compiler --- .../src/Command/UxToolkitInstallCommand.php | 39 +++++--------- .../Exception/TwigComponentAlreadyExist.php | 21 ++++++++ .../src/Compiler/TwigComponentCompiler.php | 51 +++++++++++++++++++ .../DependencyInjection/ToolkitExtension.php | 4 +- src/Toolkit/src/UxToolkitBundle.php | 8 +++ .../Command/UxToolkitInstallCommandTest.php | 7 ++- .../Compiler/TwigComponentCompilerTest.php | 51 +++++++++++++++++++ 7 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 src/Toolkit/src/Compiler/Exception/TwigComponentAlreadyExist.php create mode 100644 src/Toolkit/src/Compiler/TwigComponentCompiler.php create mode 100644 src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index c0a9861a63..a8bb104e22 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -18,6 +18,8 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; +use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist; +use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; @@ -35,6 +37,7 @@ class UxToolkitInstallCommand extends Command public function __construct( private readonly RepositoryFactory $repositoryFactory, private readonly ComponentIdentifier $componentIdentifier, + private readonly TwigComponentCompiler $compiler, ) { parent::__construct(); } @@ -77,38 +80,24 @@ protected function execute(InputInterface $input, OutputInterface $output): int ]); } - // Prepare destination - $directory = $input->getOption('destination'); - if (empty($directory)) { - $io->error('The destination directory is not valid'); - - return Command::FAILURE; - } - - if (!$filesystem->exists(\dirname($directory))) { - $io->error(\sprintf('The directory "%s" does not exist.', \dirname($directory))); - - return Command::FAILURE; - } - - if (!$filesystem->exists($directory)) { - $filesystem->mkdir($directory); - } - - $filename = $directory.\DIRECTORY_SEPARATOR.$component->getName().'.html.twig'; - if ($filesystem->exists($filename)) { - $io->error(\sprintf('The component "%s" already exists.', $component->getName())); + try { + $this->compiler->compile($component, $repository, $input->getOption('destination')); + } catch (TwigComponentAlreadyExist $e) { if (!$input->isInteractive()) { + $io->error(\sprintf('The component "%s" already exists.', $component->getName())); + return Command::FAILURE; } - // ask if we should overwrite the file - if (!$io->confirm('Do you want to overwrite the file?')) { + if (!$io->confirm( + \sprintf('The component "%s" already exists. Do you want to overwrite it?', $component->getName()) + )) { return Command::FAILURE; } - } - $filesystem->dumpFile($filename, $repository->getContent($component)); + // again + $this->compiler->compile($component, $repository, $input->getOption('destination')); + } if ($io->isVerbose()) { $io->text(\sprintf('The component "%s" has been installed in "%s".', $component->getName(), $filename)); diff --git a/src/Toolkit/src/Compiler/Exception/TwigComponentAlreadyExist.php b/src/Toolkit/src/Compiler/Exception/TwigComponentAlreadyExist.php new file mode 100644 index 0000000000..091e8a0115 --- /dev/null +++ b/src/Toolkit/src/Compiler/Exception/TwigComponentAlreadyExist.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Compiler\Exception; + +/** + * @author Jean-François Lépine + * + * @internal + */ +class TwigComponentAlreadyExist extends \LogicException +{ +} diff --git a/src/Toolkit/src/Compiler/TwigComponentCompiler.php b/src/Toolkit/src/Compiler/TwigComponentCompiler.php new file mode 100644 index 0000000000..a239d47156 --- /dev/null +++ b/src/Toolkit/src/Compiler/TwigComponentCompiler.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Compiler; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\UX\Toolkit\ComponentRepository\ComponentRepository; + +/** + * @author Jean-François Lépine + * + * @internal + */ +class TwigComponentCompiler +{ + public function __construct( + private readonly string $theme, + private readonly string $prefix, + ) { + } + + public function compile( + ComponentIdentity $component, + ComponentRepository $repository, + string $directory, + ): void { + $filesystem = new Filesystem(); + if (!$filesystem->exists($directory)) { + $filesystem->mkdir($directory); + } + + $filename = $directory.\DIRECTORY_SEPARATOR.$component->getName().'.html.twig'; + if ($filesystem->exists($filename)) { + throw new TwigComponentAlreadyExist(); + } + + $content = $repository->getContent($component); // @todo we should probably inject theme here + + $filesystem->dumpFile($filename, $content); + } +} diff --git a/src/Toolkit/src/DependencyInjection/ToolkitExtension.php b/src/Toolkit/src/DependencyInjection/ToolkitExtension.php index de91e12315..0b6dce6708 100644 --- a/src/Toolkit/src/DependencyInjection/ToolkitExtension.php +++ b/src/Toolkit/src/DependencyInjection/ToolkitExtension.php @@ -36,7 +36,7 @@ public function load(array $configs, ContainerBuilder $container): void // Expose the prefix and theme configured as parameter (for the moment). It will be injected to // the service responsible for rendering the components. - $container->setParameter('ux_twig_toolkit.theme', $config['theme']); - $container->setParameter('ux_twig_toolkit.prefix', $config['prefix']); + $container->setParameter('ux_toolkit.theme', $config['theme']); + $container->setParameter('ux_toolkit.prefix', $config['prefix']); } } diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index 482b0aa09c..8a7a119449 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; +use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; @@ -41,6 +42,13 @@ public function build(ContainerBuilder $container): void $container->autowire(RepositoryFactory::class); $container->autowire(ComponentIdentifier::class); + $container->autowire(TwigComponentCompiler::class); + $container->getDefinition(TwigComponentCompiler::class) + ->setArguments([ + '$theme' => '%ux_toolkit.theme%', + '$prefix' => '%ux_toolkit.prefix%', + ]); + // Prepare command $container->autowire(UxToolkitInstallCommand::class); $container diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index d49799cd7d..e133638451 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; +use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; @@ -33,7 +34,8 @@ public function testShouldAbleToCreateTheBadgeComponent(): void $this->createMock(GithubRepository::class) ); $identifier = new ComponentIdentifier(); - $command = new UxToolkitInstallCommand($repositoryFactory, $identifier); + $compiler = new TwigComponentCompiler('default', 'ux'); + $command = new UxToolkitInstallCommand($repositoryFactory, $identifier, $compiler); $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); mkdir($destination); @@ -68,7 +70,8 @@ public function testByDefaultCannotEraseComponentByMistake(): void $this->createMock(GithubRepository::class) ); $identifier = new ComponentIdentifier(); - $command = new UxToolkitInstallCommand($repositoryFactory, $identifier); + $compiler = new TwigComponentCompiler('default', 'ux'); + $command = new UxToolkitInstallCommand($repositoryFactory, $identifier, $compiler); $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); mkdir($destination); diff --git a/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php new file mode 100644 index 0000000000..d209362d7d --- /dev/null +++ b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests\Compiler; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist; +use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; +use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; + +/** + * @author Jean-François Lépine + */ +class TwigComponentCompilerTest extends KernelTestCase +{ + public function testItShouldCompileComponentToFile(): void + { + $compiler = new TwigComponentCompiler('default', 'ux'); + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('component_'); + $repository = new OfficialRepository(); + $component = new ComponentIdentity('symfony', '', 'Badge'); + $compiler->compile($component, $repository, $destination); + + $this->assertFileExists($destination); + $this->assertFileExists($destination.'/Badge.html.twig'); + + $content = file_get_contents($destination.'/Badge.html.twig'); + $this->assertStringContainsString('{% block content %}{% endblock %}', $content); + } + + public function testShouldThrowExceptionIfFileAlreadyExist(): void + { + $compiler = new TwigComponentCompiler('default', 'ux'); + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('component_'); + $repository = new OfficialRepository(); + $component = new ComponentIdentity('symfony', '', 'Badge'); + $compiler->compile($component, $repository, $destination); + + $this->expectException(TwigComponentAlreadyExist::class); + $compiler->compile($component, $repository, $destination); + } +} From 790ec26df954bb57721aeef829adf4b11e2a61a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 15:57:16 +0100 Subject: [PATCH 19/51] Take care of prefix in component generation --- src/Toolkit/src/Compiler/TwigComponentCompiler.php | 7 ++++++- src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php | 6 +++--- src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Toolkit/src/Compiler/TwigComponentCompiler.php b/src/Toolkit/src/Compiler/TwigComponentCompiler.php index a239d47156..7351fea5fe 100644 --- a/src/Toolkit/src/Compiler/TwigComponentCompiler.php +++ b/src/Toolkit/src/Compiler/TwigComponentCompiler.php @@ -39,7 +39,12 @@ public function compile( $filesystem->mkdir($directory); } - $filename = $directory.\DIRECTORY_SEPARATOR.$component->getName().'.html.twig'; + $filename = implode(\DIRECTORY_SEPARATOR, [ + $directory, + $this->prefix, + $component->getName().'.html.twig', + ]); + if ($filesystem->exists($filename)) { throw new TwigComponentAlreadyExist(); } diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index e133638451..1848107925 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -34,7 +34,7 @@ public function testShouldAbleToCreateTheBadgeComponent(): void $this->createMock(GithubRepository::class) ); $identifier = new ComponentIdentifier(); - $compiler = new TwigComponentCompiler('default', 'ux'); + $compiler = new TwigComponentCompiler('default', 'Acme'); $command = new UxToolkitInstallCommand($repositoryFactory, $identifier, $compiler); $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); @@ -54,11 +54,11 @@ public function testShouldAbleToCreateTheBadgeComponent(): void $this->assertEquals(Command::SUCCESS, $result); // A file should be created - $expectedFile = $destination.\DIRECTORY_SEPARATOR.'Badge.html.twig'; + $expectedFile = $destination.\DIRECTORY_SEPARATOR.'Acme'.\DIRECTORY_SEPARATOR.'Badge.html.twig'; $this->assertFileExists($expectedFile); // The content of the file should be the same as the content of the Badge component - $expectedContent = file_get_contents(__DIR__. '/../../templates/default/components/Badge.html.twig'); + $expectedContent = file_get_contents(__DIR__.'/../../templates/default/components/Badge.html.twig'); $actualContent = file_get_contents($expectedFile); $this->assertEquals($expectedContent, $actualContent); } diff --git a/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php index d209362d7d..5a082c2ddf 100644 --- a/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php +++ b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php @@ -24,16 +24,16 @@ class TwigComponentCompilerTest extends KernelTestCase { public function testItShouldCompileComponentToFile(): void { - $compiler = new TwigComponentCompiler('default', 'ux'); + $compiler = new TwigComponentCompiler('default', 'Acme'); $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('component_'); $repository = new OfficialRepository(); $component = new ComponentIdentity('symfony', '', 'Badge'); $compiler->compile($component, $repository, $destination); $this->assertFileExists($destination); - $this->assertFileExists($destination.'/Badge.html.twig'); + $this->assertFileExists($destination.'/Acme/Badge.html.twig'); - $content = file_get_contents($destination.'/Badge.html.twig'); + $content = file_get_contents($destination.'/Acme/Badge.html.twig'); $this->assertStringContainsString('{% block content %}{% endblock %}', $content); } From 672c5ed8e0339c1b05b6c9cb315ca26c2266dd9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 31 Jan 2025 16:26:51 +0100 Subject: [PATCH 20/51] fix linting of JSON file --- src/Toolkit/registry/default/components/Alert.json | 8 +++----- src/Toolkit/registry/default/components/Badge.json | 4 ++-- src/Toolkit/registry/default/components/Button.json | 4 ++-- src/Toolkit/registry/default/components/Card.json | 4 ++-- src/Toolkit/registry/default/components/Navbar.json | 4 ++-- src/Toolkit/registry/default/components/Table.json | 8 +++----- src/Toolkit/registry/default/components/Table/Row.json | 4 ++-- src/Toolkit/registry/default/examples/Badge.json | 4 ++-- src/Toolkit/registry/default/examples/BadgeOutline.json | 4 ++-- src/Toolkit/registry/default/examples/Button.json | 4 ++-- 10 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/Toolkit/registry/default/components/Alert.json b/src/Toolkit/registry/default/components/Alert.json index b966032267..f9eb3a018c 100644 --- a/src/Toolkit/registry/default/components/Alert.json +++ b/src/Toolkit/registry/default/components/Alert.json @@ -2,8 +2,6 @@ "name": "Alert", "theme": "default", "type": "component", - "code": "
\n Dependency test<\/twig:Button>\n {% block content %}Alert{% endblock %}\n<\/div>\n", - "dependencies": [ - "Button" - ] -} \ No newline at end of file + "code": "
\n Dependency test\n {% block content %}Alert{% endblock %}\n
\n", + "dependencies": ["Button"] +} diff --git a/src/Toolkit/registry/default/components/Badge.json b/src/Toolkit/registry/default/components/Badge.json index 4660110c02..450b75616a 100644 --- a/src/Toolkit/registry/default/components/Badge.json +++ b/src/Toolkit/registry/default/components/Badge.json @@ -2,6 +2,6 @@ "name": "Badge", "theme": "default", "type": "component", - "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n variants: {\n variant: {\n default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary\/80\",\n secondary: \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary\/80\",\n destructive: \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive\/80\",\n }\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}{% endblock %}\n<\/div>\n", + "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n variants: {\n variant: {\n default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary: \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive: \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n }\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}{% endblock %}\n
\n", "dependencies": [] -} \ No newline at end of file +} diff --git a/src/Toolkit/registry/default/components/Button.json b/src/Toolkit/registry/default/components/Button.json index 7c64e1efee..b04a66378d 100644 --- a/src/Toolkit/registry/default/components/Button.json +++ b/src/Toolkit/registry/default/components/Button.json @@ -2,6 +2,6 @@ "name": "Button", "theme": "default", "type": "component", - "code": "\n", "dependencies": [] -} \ No newline at end of file +} diff --git a/src/Toolkit/registry/default/components/Card.json b/src/Toolkit/registry/default/components/Card.json index 76b57fb9fb..0f0f6a6084 100644 --- a/src/Toolkit/registry/default/components/Card.json +++ b/src/Toolkit/registry/default/components/Card.json @@ -2,6 +2,6 @@ "name": "Card", "theme": "default", "type": "component", - "code": "
\n {% block content %}{% endblock %}\n<\/div>\n", + "code": "
\n {% block content %}{% endblock %}\n
\n", "dependencies": [] -} \ No newline at end of file +} diff --git a/src/Toolkit/registry/default/components/Navbar.json b/src/Toolkit/registry/default/components/Navbar.json index 19aa112c2c..f07fc1c9fc 100644 --- a/src/Toolkit/registry/default/components/Navbar.json +++ b/src/Toolkit/registry/default/components/Navbar.json @@ -2,6 +2,6 @@ "name": "Navbar", "theme": "default", "type": "component", - "code": "
\n {% block content %}{% endblock %}\n<\/table>\n", - "dependencies": [ - "Row" - ] -} \ No newline at end of file + "code": "{# ux:with{Row, Button} #}\n
\n {% block content %}{% endblock %}\n
\n", + "dependencies": ["Row"] +} diff --git a/src/Toolkit/registry/default/components/Table/Row.json b/src/Toolkit/registry/default/components/Table/Row.json index d1f0f66d72..f8a2d232c4 100644 --- a/src/Toolkit/registry/default/components/Table/Row.json +++ b/src/Toolkit/registry/default/components/Table/Row.json @@ -2,6 +2,6 @@ "name": "Row", "theme": "default", "type": "component", - "code": "\n {% block content %}Row{% endblock %}\n<\/tr>\n", + "code": "\n {% block content %}Row{% endblock %}\n\n", "dependencies": [] -} \ No newline at end of file +} diff --git a/src/Toolkit/registry/default/examples/Badge.json b/src/Toolkit/registry/default/examples/Badge.json index 342708313b..29e4189457 100644 --- a/src/Toolkit/registry/default/examples/Badge.json +++ b/src/Toolkit/registry/default/examples/Badge.json @@ -2,6 +2,6 @@ "name": "Badge", "theme": "default", "type": "example", - "code": "Badge<\/twig:Badge>\n", + "code": "Badge\n", "dependencies": [] -} \ No newline at end of file +} diff --git a/src/Toolkit/registry/default/examples/BadgeOutline.json b/src/Toolkit/registry/default/examples/BadgeOutline.json index 8b9ce08ae2..a2723af5e7 100644 --- a/src/Toolkit/registry/default/examples/BadgeOutline.json +++ b/src/Toolkit/registry/default/examples/BadgeOutline.json @@ -2,6 +2,6 @@ "name": "BadgeOutline", "theme": "default", "type": "example", - "code": "Badge<\/twig:Badge>\nBadge<\/twig:Badge>\nBadge<\/twig:Badge>\n", + "code": "Badge\nBadge\nBadge\n", "dependencies": [] -} \ No newline at end of file +} diff --git a/src/Toolkit/registry/default/examples/Button.json b/src/Toolkit/registry/default/examples/Button.json index 301f6cb7dd..531ebbb887 100644 --- a/src/Toolkit/registry/default/examples/Button.json +++ b/src/Toolkit/registry/default/examples/Button.json @@ -2,6 +2,6 @@ "name": "Button", "theme": "default", "type": "example", - "code": "Click me<\/twig:Button>\n", + "code": "Click me\n", "dependencies": [] -} \ No newline at end of file +} From ba179e1b077c8065e6d0d207e82388088855ea6f Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 31 Jan 2025 16:29:22 +0100 Subject: [PATCH 21/51] fix registry save output format --- src/Toolkit/registry/default/components/Alert.json | 4 +++- src/Toolkit/registry/default/components/Badge.json | 2 +- src/Toolkit/registry/default/components/Table.json | 4 +++- src/Toolkit/src/Registry/Registry.php | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Toolkit/registry/default/components/Alert.json b/src/Toolkit/registry/default/components/Alert.json index f9eb3a018c..bd499db051 100644 --- a/src/Toolkit/registry/default/components/Alert.json +++ b/src/Toolkit/registry/default/components/Alert.json @@ -3,5 +3,7 @@ "theme": "default", "type": "component", "code": "
\n Dependency test\n {% block content %}Alert{% endblock %}\n
\n", - "dependencies": ["Button"] + "dependencies": [ + "Button" + ] } diff --git a/src/Toolkit/registry/default/components/Badge.json b/src/Toolkit/registry/default/components/Badge.json index 450b75616a..1f25ab57bd 100644 --- a/src/Toolkit/registry/default/components/Badge.json +++ b/src/Toolkit/registry/default/components/Badge.json @@ -2,6 +2,6 @@ "name": "Badge", "theme": "default", "type": "component", - "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n variants: {\n variant: {\n default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary: \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive: \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n }\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n variants: {\n variant: {\n default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary: \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive: \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n },\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", "dependencies": [] } diff --git a/src/Toolkit/registry/default/components/Table.json b/src/Toolkit/registry/default/components/Table.json index 4cff916c3e..32b19bcc60 100644 --- a/src/Toolkit/registry/default/components/Table.json +++ b/src/Toolkit/registry/default/components/Table.json @@ -3,5 +3,7 @@ "theme": "default", "type": "component", "code": "{# ux:with{Row, Button} #}\n\n {% block content %}{% endblock %}\n
\n", - "dependencies": ["Row"] + "dependencies": [ + "Row" + ] } diff --git a/src/Toolkit/src/Registry/Registry.php b/src/Toolkit/src/Registry/Registry.php index 7c0fa02290..68c7e51f9b 100644 --- a/src/Toolkit/src/Registry/Registry.php +++ b/src/Toolkit/src/Registry/Registry.php @@ -48,7 +48,7 @@ public function save(string $registryDir, Filesystem $filesystem): void 'type' => $item->type->value, 'code' => $item->code, 'dependencies' => $this->getDependencies($item), - ], \JSON_PRETTY_PRINT)); + ], \JSON_PRETTY_PRINT|\JSON_UNESCAPED_SLASHES)."\n"); } } From f35483afc4a5fd2fc7a1354abb7c40e534dfb445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Sat, 1 Feb 2025 08:50:55 +0100 Subject: [PATCH 22/51] refactored project according our previous discussions --- src/Toolkit/registry/default/registry.json | 27 +++++++ .../src/Command/UxToolkitInstallCommand.php | 56 +++++++------ .../src/Compiler/TwigComponentCompiler.php | 14 ++-- .../ComponentIdentifier.php | 41 ---------- .../ComponentRepository.php | 4 +- .../src/ComponentRepository/CurrentTheme.php | 42 ++++++++++ .../ComponentRepository/GithubRepository.php | 66 +++++++++++++-- .../OfficialRepository.php | 13 +-- .../ComponentRepository/RepositoryFactory.php | 13 ++- .../RepositoryIdentifier.php | 48 +++++++++++ ...entIdentity.php => RepositoryIdentity.php} | 15 ++-- .../ComponentRepository/RepositorySources.php | 23 ++++++ .../src/DependencyInjection/Configuration.php | 2 +- src/Toolkit/src/Registry/Registry.php | 18 ++++- src/Toolkit/src/Registry/RegistryFactory.php | 67 ++++++++++++++++ src/Toolkit/src/Registry/RegistryItem.php | 40 +++++++--- src/Toolkit/src/UxToolkitBundle.php | 17 +++- .../Command/UxToolkitInstallCommandTest.php | 46 +++-------- .../Compiler/TwigComponentCompilerTest.php | 38 ++++++--- .../GithubRepositoryTest.php | 80 +++++++++++++++++-- .../OfficialRepositoryTest.php | 19 ++--- .../RepositoryFactoryTest.php | 21 +++-- ...rTest.php => RepositoryIdentifierTest.php} | 34 ++++---- 23 files changed, 530 insertions(+), 214 deletions(-) create mode 100644 src/Toolkit/registry/default/registry.json delete mode 100644 src/Toolkit/src/ComponentRepository/ComponentIdentifier.php create mode 100644 src/Toolkit/src/ComponentRepository/CurrentTheme.php create mode 100644 src/Toolkit/src/ComponentRepository/RepositoryIdentifier.php rename src/Toolkit/src/ComponentRepository/{ComponentIdentity.php => RepositoryIdentity.php} (68%) create mode 100644 src/Toolkit/src/ComponentRepository/RepositorySources.php create mode 100644 src/Toolkit/src/Registry/RegistryFactory.php rename src/Toolkit/tests/ComponentRepository/{ComponentIdentifierTest.php => RepositoryIdentifierTest.php} (61%) diff --git a/src/Toolkit/registry/default/registry.json b/src/Toolkit/registry/default/registry.json new file mode 100644 index 0000000000..45f0bdd92d --- /dev/null +++ b/src/Toolkit/registry/default/registry.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "name": "symfony", + "homepage": "https://symfony.com", + "items": [ + { + "name": "badge", + "manifest": "components/Badge.json", + "type": "registry:component", + "title": "Badge", + "description": "A simple badge component", + "registryDependencies": [], + "files": [], + "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n variants: {\n variant: {\n default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary: \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive: \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n },\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}{% endblock %}\n\n" + }, + { + "name": "button", + "manifest": "components/Button.json", + "type": "registry:component", + "title": "Button", + "description": "A Button component", + "registryDependencies": [], + "files": [], + "code": ",\n" + } + ] +} \ No newline at end of file diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index a8bb104e22..d3e7b011a2 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -17,11 +17,10 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Filesystem\Filesystem; use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist; use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; -use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; +use Symfony\UX\Toolkit\ComponentRepository\CurrentTheme; +use Symfony\UX\Toolkit\Registry\RegistryFactory; /** * @author Jean-François Lépine @@ -35,8 +34,8 @@ class UxToolkitInstallCommand extends Command { public function __construct( - private readonly RepositoryFactory $repositoryFactory, - private readonly ComponentIdentifier $componentIdentifier, + private readonly CurrentTheme $currentTheme, + private readonly RegistryFactory $registryFactory, private readonly TwigComponentCompiler $compiler, ) { parent::__construct(); @@ -58,52 +57,51 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $filesystem = new Filesystem(); - $name = $input->getArgument('component'); + // Download sources, or get them from vendors + $repository = $this->currentTheme->getIdentity(); - try { - $component = $this->componentIdentifier->identify($name); + $io->info( + \sprintf( + 'Downloading medata for %s/%s..', + $repository->getVendor(), + $repository->getPackage(), + ) + ); - // Get the correct source (remote or official) - $repository = $this->repositoryFactory->factory($component); - } catch (\Throwable $e) { - $io->error($e->getMessage()); + $name = $input->getArgument('component'); + $finder = $this->currentTheme->getRepository()->fetch($repository); + $registry = $this->registryFactory->create($finder); - return Command::FAILURE; - } + if (!$registry->has($name)) { + $io->error(\sprintf('The component "%s" does not exist.', $name)); - if ($io->isVerbose()) { - $io->text('Component information:'); - $io->table(['Vendor', 'Package', 'Version', 'Name'], [ - [$component->getVendor(), $component->getPackage(), $component->getVersion(), $component->getName()], - ]); + return Command::FAILURE; } + $component = $registry->get($name); + $destination = $input->getOption('destination'); try { - $this->compiler->compile($component, $repository, $input->getOption('destination')); + $io->info(\sprintf('Installing component "%s"...', $component->name)); + $this->compiler->compile($component, $destination); } catch (TwigComponentAlreadyExist $e) { if (!$input->isInteractive()) { - $io->error(\sprintf('The component "%s" already exists.', $component->getName())); + $io->error(\sprintf('The component "%s" already exists.', $component->name)); return Command::FAILURE; } if (!$io->confirm( - \sprintf('The component "%s" already exists. Do you want to overwrite it?', $component->getName()) + \sprintf('The component "%s" already exists. Do you want to overwrite it?', $component->name) )) { return Command::FAILURE; } // again - $this->compiler->compile($component, $repository, $input->getOption('destination')); - } - - if ($io->isVerbose()) { - $io->text(\sprintf('The component "%s" has been installed in "%s".', $component->getName(), $filename)); + $this->compiler->compile($component, $destination); } - $io->success(\sprintf('The component "%s" has been installed.', $component->getName())); + $io->success(\sprintf('The component "%s" has been installed.', $component->name)); return Command::SUCCESS; } diff --git a/src/Toolkit/src/Compiler/TwigComponentCompiler.php b/src/Toolkit/src/Compiler/TwigComponentCompiler.php index 7351fea5fe..732afc5958 100644 --- a/src/Toolkit/src/Compiler/TwigComponentCompiler.php +++ b/src/Toolkit/src/Compiler/TwigComponentCompiler.php @@ -13,8 +13,7 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; -use Symfony\UX\Toolkit\ComponentRepository\ComponentRepository; +use Symfony\UX\Toolkit\Registry\RegistryItem; /** * @author Jean-François Lépine @@ -24,16 +23,15 @@ class TwigComponentCompiler { public function __construct( - private readonly string $theme, private readonly string $prefix, ) { } public function compile( - ComponentIdentity $component, - ComponentRepository $repository, + RegistryItem $item, string $directory, ): void { + $filesystem = new Filesystem(); if (!$filesystem->exists($directory)) { $filesystem->mkdir($directory); @@ -42,15 +40,13 @@ public function compile( $filename = implode(\DIRECTORY_SEPARATOR, [ $directory, $this->prefix, - $component->getName().'.html.twig', + $item->name.'.html.twig', ]); if ($filesystem->exists($filename)) { throw new TwigComponentAlreadyExist(); } - $content = $repository->getContent($component); // @todo we should probably inject theme here - - $filesystem->dumpFile($filename, $content); + $filesystem->dumpFile($filename, $item->code); } } diff --git a/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php b/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php deleted file mode 100644 index 16ae34b5cc..0000000000 --- a/src/Toolkit/src/ComponentRepository/ComponentIdentifier.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\ComponentRepository; - -/** - * @author Jean-François Lépine - * - * @internal - */ -final class ComponentIdentifier -{ - public function identify(string $name): ComponentIdentity - { - if (preg_match('!^\w+$!', $name)) { - return new ComponentIdentity('symfony', 'ux-toolkit', ucfirst($name)); - } - - // github.com/vendor/package:component@version - // github.com/vendor/package:component (version is optional) - $name = preg_replace('!^(https://|http://)!', '', $name); - if (preg_match('!^github.com/(\w+)/(\w+):(\w+)(@.+)?$!', $name, $matches)) { - return new ComponentIdentity( - $matches[1], - $matches[2], - ucfirst($matches[3]), - trim($matches[4] ?? 'main', '@') - ); - } - - throw new \InvalidArgumentException('Source is not supported for this component'); - } -} diff --git a/src/Toolkit/src/ComponentRepository/ComponentRepository.php b/src/Toolkit/src/ComponentRepository/ComponentRepository.php index 186e6d0a55..78fa612a4a 100644 --- a/src/Toolkit/src/ComponentRepository/ComponentRepository.php +++ b/src/Toolkit/src/ComponentRepository/ComponentRepository.php @@ -11,6 +11,8 @@ namespace Symfony\UX\Toolkit\ComponentRepository; +use Symfony\Component\Finder\Finder; + /** * @author Jean-François Lépine * @@ -18,5 +20,5 @@ */ interface ComponentRepository { - public function getContent(ComponentIdentity $component): string; + public function fetch(RepositoryIdentity $component): Finder; } diff --git a/src/Toolkit/src/ComponentRepository/CurrentTheme.php b/src/Toolkit/src/ComponentRepository/CurrentTheme.php new file mode 100644 index 0000000000..ff176061c3 --- /dev/null +++ b/src/Toolkit/src/ComponentRepository/CurrentTheme.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\ComponentRepository; + +/** + * @author Jean-François Lépine + * + * @internal + */ +final class CurrentTheme +{ + private ComponentRepository $repository; + private RepositoryIdentity $identity; + + public function __construct( + string $theme, + RepositoryFactory $repositoryFactory, + RepositoryIdentifier $repositoryIdentifier, + ) { + $this->identity = $repositoryIdentifier->identify($theme); + $this->repository = $repositoryFactory->factory($this->identity); + } + + public function getRepository(): ComponentRepository + { + return $this->repository; + } + + public function getIdentity(): RepositoryIdentity + { + return $this->identity; + } +} diff --git a/src/Toolkit/src/ComponentRepository/GithubRepository.php b/src/Toolkit/src/ComponentRepository/GithubRepository.php index d16c4b5f5e..83c0773e9c 100644 --- a/src/Toolkit/src/ComponentRepository/GithubRepository.php +++ b/src/Toolkit/src/ComponentRepository/GithubRepository.php @@ -11,6 +11,8 @@ namespace Symfony\UX\Toolkit\ComponentRepository; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; use Symfony\Component\HttpClient\HttpClient; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -21,26 +23,78 @@ */ class GithubRepository implements ComponentRepository { + private readonly \ArrayObject $manifest; + public function __construct( + private readonly Filesystem $filesystem, private readonly ?HttpClientInterface $httpClient = null, ) { if (!class_exists(HttpClient::class)) { throw new \LogicException('You must install "symfony/http-client" to use ux-toolkit with remote component. Try running "composer require symfony/http-client".'); } + + if (!class_exists(\ZipArchive::class)) { + throw new \LogicException('You must have the Zip extension installed to use ux-toolkit with remote components.'); + } } - public function getContent(ComponentIdentity $component): string + public function fetch(RepositoryIdentity $component): Finder { - $url = \sprintf( - 'https://raw.githubusercontent.com/%s/%s/%s/templates/components/%s.html.twig', + // download a zip file of the github repository, place it in a temporary directory in cache + $zipUrl = \sprintf( + 'http://github.com/%s/%s/archive/%s.zip', $component->getVendor(), $component->getPackage(), $component->getVersion(), - $component->getName() ); - $response = $this->httpClient->request('GET', $url); + $destination = $this->getCacheDir(); + $zipFile = $destination.'/'.basename($zipUrl); + + $response = $this->httpClient->request('GET', $zipUrl, [ + 'sink' => $zipFile, + ]); + + // Ensure the request was successful + if (200 !== $response->getStatusCode()) { + throw new \RuntimeException(\sprintf('Failed to download the file from "%s".', $zipUrl)); + } + + // Ensure response contains valid github headers + $headers = $response->getHeaders(); + if (!isset($headers['content-type']) || !\in_array('application/zip', $headers['content-type'])) { + throw new \RuntimeException(\sprintf('The file from "%s" is not a valid zip file.', $zipUrl)); + } + + // Flush the response to the file + $this->filesystem->dumpFile($zipFile, $response->getContent()); + + // unzip the file + $zip = new \ZipArchive(); + $zip->open($zipFile); + $zip->extractTo($destination); + $zip->close(); + + $rootDir = $destination; + $finder = new Finder(); + $finder->files()->in($rootDir); + + return $finder; + } + + public function getCacheDir(): string + { + return $this->createTmpDir('cache'); + } + + private function createTmpDir(string $type): string + { + $dir = sys_get_temp_dir().'/ux_toolkit/'.uniqid($type.'_', true); + + if (!$this->filesystem->exists($dir)) { + $this->filesystem->mkdir($dir); + } - return $response->getContent(); + return $dir; } } diff --git a/src/Toolkit/src/ComponentRepository/OfficialRepository.php b/src/Toolkit/src/ComponentRepository/OfficialRepository.php index 564fecf5b2..d1fb63aa45 100644 --- a/src/Toolkit/src/ComponentRepository/OfficialRepository.php +++ b/src/Toolkit/src/ComponentRepository/OfficialRepository.php @@ -20,18 +20,11 @@ */ class OfficialRepository implements ComponentRepository { - public function getContent(ComponentIdentity $component): string + public function fetch(RepositoryIdentity $repository): Finder { $finder = new Finder(); - $finder->files()->in(__DIR__.'/../../templates/default/components/')->name($component->getName().'.html.twig'); - if (!$finder->hasResults()) { - throw new \InvalidArgumentException(\sprintf('The component "%s" does not exist', $component->getName())); - } + $finder->in(\sprintf(__DIR__.'/../../registry/%s', $repository->getPackage())); - foreach ($finder as $file) { - return $file->getContents(); - } - - throw new \InvalidArgumentException(\sprintf('The component "%s" does not exist', $component->getName())); + return $finder; } } diff --git a/src/Toolkit/src/ComponentRepository/RepositoryFactory.php b/src/Toolkit/src/ComponentRepository/RepositoryFactory.php index 4764a6726f..b196278c26 100644 --- a/src/Toolkit/src/ComponentRepository/RepositoryFactory.php +++ b/src/Toolkit/src/ComponentRepository/RepositoryFactory.php @@ -24,14 +24,13 @@ public function __construct( ) { } - public function factory(ComponentIdentity $component): ComponentRepository + public function factory(RepositoryIdentity $repository): ComponentRepository { - if ('symfony' === $component->getVendor()) { - return $this->officialRepository; - } - - if (str_starts_with($component->getVendor(), 'github.com/')) { - return $this->githubRepository; + switch ($repository->getType()) { + case RepositorySources::EMBEDDED: + return $this->officialRepository; + case RepositorySources::GITHUB: + return $this->githubRepository; } throw new \InvalidArgumentException('Source is not supported for this component'); diff --git a/src/Toolkit/src/ComponentRepository/RepositoryIdentifier.php b/src/Toolkit/src/ComponentRepository/RepositoryIdentifier.php new file mode 100644 index 0000000000..2480c84671 --- /dev/null +++ b/src/Toolkit/src/ComponentRepository/RepositoryIdentifier.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\ComponentRepository; + +/** + * @author Jean-François Lépine + * + * @internal + */ +final class RepositoryIdentifier +{ + public function identify(string $name): RepositoryIdentity + { + if (preg_match('!^\w+$!', $name)) { + // Official repository (with only the theme name) + return new RepositoryIdentity( + RepositorySources::EMBEDDED, + 'symfony', + 'default', + null + ); + } + + $name = preg_replace('!^(https://|http://)!', '', $name); + if (preg_match('!^github.com/(\w+)/(\w+)(@.+)?$!', $name, $matches)) { + // github.com/vendor/package@version + // github.com/vendor/package + // https://github.com/vendor/package + return new RepositoryIdentity( + RepositorySources::GITHUB, + $matches[1], + $matches[2], + trim($matches[3] ?? 'main', '@') + ); + } + + throw new \InvalidArgumentException('Source is not supported for this component'); + } +} diff --git a/src/Toolkit/src/ComponentRepository/ComponentIdentity.php b/src/Toolkit/src/ComponentRepository/RepositoryIdentity.php similarity index 68% rename from src/Toolkit/src/ComponentRepository/ComponentIdentity.php rename to src/Toolkit/src/ComponentRepository/RepositoryIdentity.php index 20a5f155c7..0bcff7d18f 100644 --- a/src/Toolkit/src/ComponentRepository/ComponentIdentity.php +++ b/src/Toolkit/src/ComponentRepository/RepositoryIdentity.php @@ -16,14 +16,17 @@ * * @internal */ -final readonly class ComponentIdentity +final readonly class RepositoryIdentity { public function __construct( + private int $type, private string $vendor, private ?string $package = null, - private ?string $name = null, private ?string $version = 'main', ) { + if (!\in_array($type, [RepositorySources::EMBEDDED, RepositorySources::GITHUB], true)) { + throw new \InvalidArgumentException('Only "official" and "github" types are supported for the moment.'); + } } public function getVendor(): string @@ -36,13 +39,13 @@ public function getPackage(): ?string return $this->package; } - public function getName(): ?string + public function getVersion(): ?string { - return $this->name; + return $this->version; } - public function getVersion(): ?string + public function getType(): int { - return $this->version; + return $this->type; } } diff --git a/src/Toolkit/src/ComponentRepository/RepositorySources.php b/src/Toolkit/src/ComponentRepository/RepositorySources.php new file mode 100644 index 0000000000..8dd075bc05 --- /dev/null +++ b/src/Toolkit/src/ComponentRepository/RepositorySources.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\ComponentRepository; + +/** + * @author Jean-François Lépine + * + * @internal + */ +enum RepositorySources: int +{ + public const int EMBEDDED = 1; + public const int GITHUB = 2; +} diff --git a/src/Toolkit/src/DependencyInjection/Configuration.php b/src/Toolkit/src/DependencyInjection/Configuration.php index c808dd05ef..613aec99a2 100644 --- a/src/Toolkit/src/DependencyInjection/Configuration.php +++ b/src/Toolkit/src/DependencyInjection/Configuration.php @@ -29,7 +29,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue('default') ->end() ->stringNode('prefix') - ->defaultValue('ux') + ->defaultValue(null) ->end() ->end(); diff --git a/src/Toolkit/src/Registry/Registry.php b/src/Toolkit/src/Registry/Registry.php index 68c7e51f9b..0342ff7b10 100644 --- a/src/Toolkit/src/Registry/Registry.php +++ b/src/Toolkit/src/Registry/Registry.php @@ -36,6 +36,22 @@ public function add(RegistryItem $item): void $this->items[] = $item; } + public function has(string $name): bool + { + return null !== $this->get($name); + } + + public function get(string $name): ?RegistryItem + { + foreach ($this->items as $item) { + if ($item->name === $name) { + return $item; + } + } + + return null; + } + public function save(string $registryDir, Filesystem $filesystem): void { $filesystem->mkdir($registryDir); @@ -48,7 +64,7 @@ public function save(string $registryDir, Filesystem $filesystem): void 'type' => $item->type->value, 'code' => $item->code, 'dependencies' => $this->getDependencies($item), - ], \JSON_PRETTY_PRINT|\JSON_UNESCAPED_SLASHES)."\n"); + ], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n"); } } diff --git a/src/Toolkit/src/Registry/RegistryFactory.php b/src/Toolkit/src/Registry/RegistryFactory.php new file mode 100644 index 0000000000..e880fcb0ae --- /dev/null +++ b/src/Toolkit/src/Registry/RegistryFactory.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Registry; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; + +/** + * @author Jean-François Lépine + * + * @internal + */ +final class RegistryFactory +{ + public function __construct( + private readonly Filesystem $filesystem, + ) { + } + + public function create(Finder $finder): Registry + { + $finderManifest = clone $finder; + $files = $finderManifest->files()->name('registry.json')->getIterator(); + $files->rewind(); + $manifestFile = $files->current(); + if (!$manifestFile) { + throw new \RuntimeException('The manifest file is missing.'); + } + + $registry = Registry::empty(); + $manifest = json_decode($manifestFile->getContents(), true); + + foreach ($manifest['items'] ?? [] as $item) { + $filename = $item['manifest']; + $localFinder = clone $finder; + $files = iterator_to_array($localFinder->path($item['manifest'])); + + if (1 !== \count($files)) { + throw new \RuntimeException(\sprintf('The file "%s" declared in the manifest is missing.', $filename)); + } + $file = reset($files); + + if (isset($item['hash'])) { + $hash = hash_file('sha256', $file->getRealPath()); + if ($hash !== $item['hash']) { + throw new \RuntimeException(\sprintf('The file "%s" declared in the manifest has an invalid hash.', $filename)); + } + } + + $item = RegistryItem::fromFile($file); + $registry->add($item); + } + + return $registry; + } +} diff --git a/src/Toolkit/src/Registry/RegistryItem.php b/src/Toolkit/src/Registry/RegistryItem.php index 5dcf29c5bf..84f8ea5aa7 100644 --- a/src/Toolkit/src/Registry/RegistryItem.php +++ b/src/Toolkit/src/Registry/RegistryItem.php @@ -36,21 +36,39 @@ public function __construct( public static function fromFile(SplFileInfo $file): self { - if (!preg_match(self::REGEX_RELATIVE_FILE, $file->getRelativePathname(), $matches)) { - throw new \InvalidArgumentException(\sprintf('Unable to parse file path "%s", it must match the following pattern: "//(/)?.html.twig"', $file->getRelativePathname())); + $json = json_decode($file->getContents(), true); + if (null === $json) { + throw new \RuntimeException(\sprintf('The file "%s" is not a valid JSON file.', $file->getRelativePathname())); } - $theme = $matches['theme']; - $type = RegistryItemType::from($matches['type']); - $name = $matches['name'] ?? $matches['nameOrParentName']; - $parentName = isset($matches['name']) ? $matches['nameOrParentName'] : null; + if (!isset($json['name'], $json['type'], $json['theme'], $json['code'])) { + throw new \RuntimeException(\sprintf('The file "%s" must contain the following keys: "name", "type", "theme" and "code".', $file->getRelativePathname())); + } return new self( - $name, - $type, - $theme, - $parentName, - $file->getContents(), + $json['name'], + RegistryItemType::from($json['type']), + $json['theme'], + $json['parentName'] ?? null, + $json['code'], ); + + // @todo: commented for the moment. Not sure to understand why we need regex here. To avoid to have json extension? + // if (!preg_match(self::REGEX_RELATIVE_FILE, $file->getRelativePathname(), $matches)) { + // throw new \InvalidArgumentException(\sprintf('Unable to parse file path "%s", it must match the following pattern: "//(/)?.html.twig"', $file->getRelativePathname())); + // } + // + // $theme = $matches['theme']; + // $type = RegistryItemType::from($matches['type']); + // $name = $matches['name'] ?? $matches['nameOrParentName']; + // $parentName = isset($matches['name']) ? $matches['nameOrParentName'] : null; + // + // return new self( + // $name, + // $type, + // $theme, + // $parentName, + // $file->getContents(), + // ); } } diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index 8a7a119449..78d6bf436a 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -16,11 +16,13 @@ use Symfony\Component\HttpKernel\Bundle\AbstractBundle; use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; +use Symfony\UX\Toolkit\ComponentRepository\CurrentTheme; use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentifier; use Symfony\UX\Toolkit\DependencyInjection\ToolkitExtension; +use Symfony\UX\Toolkit\Registry\RegistryFactory; /** * @author Jean-François Lépine @@ -40,12 +42,12 @@ public function build(ContainerBuilder $container): void $container->autowire(OfficialRepository::class); $container->autowire(GithubRepository::class); $container->autowire(RepositoryFactory::class); - $container->autowire(ComponentIdentifier::class); + $container->autowire(RepositoryIdentifier::class); + $container->autowire(RegistryFactory::class); $container->autowire(TwigComponentCompiler::class); $container->getDefinition(TwigComponentCompiler::class) ->setArguments([ - '$theme' => '%ux_toolkit.theme%', '$prefix' => '%ux_toolkit.prefix%', ]); @@ -65,5 +67,14 @@ public function build(ContainerBuilder $container): void $container->getDefinition(GithubRepository::class) ->setArgument('$httpClient', $container->get('http_client')); } + + // current theme + $container->autowire(CurrentTheme::class); + $container->getDefinition(CurrentTheme::class) + ->setArguments([ + '$theme' => '%ux_toolkit.theme%', + ]); + + } } diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index 1848107925..ae0fafd030 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -15,15 +15,20 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Filesystem\Filesystem; use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; +use Symfony\UX\Toolkit\ComponentRepository\CurrentTheme; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentifier; use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; +use Symfony\UX\Toolkit\Registry\RegistryFactory; /** * @author Jean-François Lépine + * + * @group wip */ class UxToolkitInstallCommandTest extends TestCase { @@ -33,15 +38,16 @@ public function testShouldAbleToCreateTheBadgeComponent(): void new OfficialRepository(), $this->createMock(GithubRepository::class) ); - $identifier = new ComponentIdentifier(); - $compiler = new TwigComponentCompiler('default', 'Acme'); - $command = new UxToolkitInstallCommand($repositoryFactory, $identifier, $compiler); + $currentTheme = new CurrentTheme('default', $repositoryFactory, new RepositoryIdentifier()); + $compiler = new TwigComponentCompiler('Acme'); + $registryFactory = new RegistryFactory(new Filesystem()); + $command = new UxToolkitInstallCommand($currentTheme, $registryFactory, $compiler); $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); mkdir($destination); $input = new ArrayInput([ - 'component' => 'badge', + 'component' => 'Badge', '--destination' => $destination, ]); @@ -49,7 +55,6 @@ public function testShouldAbleToCreateTheBadgeComponent(): void $output = new BufferedOutput(); $result = $command->run($input, $output); - // Command should be successful $this->assertEquals(Command::SUCCESS, $result); @@ -62,33 +67,4 @@ public function testShouldAbleToCreateTheBadgeComponent(): void $actualContent = file_get_contents($expectedFile); $this->assertEquals($expectedContent, $actualContent); } - - public function testByDefaultCannotEraseComponentByMistake(): void - { - $repositoryFactory = new RepositoryFactory( - new OfficialRepository(), - $this->createMock(GithubRepository::class) - ); - $identifier = new ComponentIdentifier(); - $compiler = new TwigComponentCompiler('default', 'ux'); - $command = new UxToolkitInstallCommand($repositoryFactory, $identifier, $compiler); - - $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); - mkdir($destination); - - $input = new ArrayInput([ - 'component' => 'badge', - '--destination' => $destination, - ]); - - $input->setInteractive(false); - $output = new BufferedOutput(); - - $result = $command->run($input, $output); - $this->assertEquals(Command::SUCCESS, $result); - - // run again => should fail (non interactive mode) - $result = $command->run($input, $output); - $this->assertEquals(Command::FAILURE, $result); - } } diff --git a/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php index 5a082c2ddf..4fc750805a 100644 --- a/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php +++ b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php @@ -14,8 +14,10 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist; use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentity; +use Symfony\UX\Toolkit\Registry\RegistryItem; +use Symfony\UX\Toolkit\Registry\RegistryItemType; /** * @author Jean-François Lépine @@ -24,28 +26,42 @@ class TwigComponentCompilerTest extends KernelTestCase { public function testItShouldCompileComponentToFile(): void { - $compiler = new TwigComponentCompiler('default', 'Acme'); + $compiler = new TwigComponentCompiler('Acme'); $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('component_'); - $repository = new OfficialRepository(); - $component = new ComponentIdentity('symfony', '', 'Badge'); - $compiler->compile($component, $repository, $destination); + + $item = new RegistryItem( + 'Badge', + RegistryItemType::Component, + 'default', + null, + '' + ); + + $compiler->compile($item, $destination); $this->assertFileExists($destination); $this->assertFileExists($destination.'/Acme/Badge.html.twig'); $content = file_get_contents($destination.'/Acme/Badge.html.twig'); - $this->assertStringContainsString('{% block content %}{% endblock %}', $content); + $this->assertStringContainsString('', $content); } public function testShouldThrowExceptionIfFileAlreadyExist(): void { - $compiler = new TwigComponentCompiler('default', 'ux'); + $compiler = new TwigComponentCompiler('Acme'); $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('component_'); - $repository = new OfficialRepository(); - $component = new ComponentIdentity('symfony', '', 'Badge'); - $compiler->compile($component, $repository, $destination); + + $item = new RegistryItem( + 'Badge', + RegistryItemType::Component, + 'default', + null, + '' + ); + + $compiler->compile($item, $destination); $this->expectException(TwigComponentAlreadyExist::class); - $compiler->compile($component, $repository, $destination); + $compiler->compile($item, $destination); } } diff --git a/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php b/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php index da5fcb87de..f412a39612 100644 --- a/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php +++ b/src/Toolkit/tests/ComponentRepository/GithubRepositoryTest.php @@ -12,10 +12,12 @@ namespace Symfony\UX\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentity; +use Symfony\UX\Toolkit\ComponentRepository\RepositorySources; /** * @author Jean-François Lépine @@ -24,13 +26,79 @@ class GithubRepositoryTest extends TestCase { public function testGithubRepositoryUseClientAndTryToDownloadRemoteFile(): void { + // Create a zip file with a pseudo manifest.json file + $manifest = '{"name:""Haleck45/ux-toolkit","version":"1.0.0"}'; + $workdir = sys_get_temp_dir().'/ux-toolkit'; + $filesystem = new Filesystem(); + $filesystem->mkdir($workdir); + $filesystem->dumpFile($workdir.'/manifest.json', $manifest); + $filesystem->dumpFile($workdir.'/README.md', 'My readme content'); + + $zip = new \ZipArchive(); + $zip->open($workdir.'/ux-toolkit-1.0.0.zip', \ZipArchive::CREATE); + $zip->addFile($workdir.'/manifest.json', 'manifest.json'); + $zip->addFile($workdir.'/README.md', 'README.md'); + $zip->close(); + + // Create a mock http client that will return the zip file $client = new MockHttpClient(); - $client->setResponseFactory(fn() => new MockResponse('My badge content from github')); - $repository = new GithubRepository($client); + $client->setResponseFactory(fn () => new MockResponse( + file_get_contents($workdir.'/ux-toolkit-1.0.0.zip'), + [ + 'http_code' => 200, + 'response_headers' => [ + 'content-type' => 'application/zip', + ], + ] + )); + + $filesystem = new Filesystem(); + $repository = new GithubRepository($filesystem, $client); + + $component = new RepositoryIdentity(RepositorySources::GITHUB, 'Halleck45', 'ux-toolkit', '1.0.0'); + $finder = $repository->fetch($component); + + // the manifest file should be extracted + $manifestFile = $finder->files()->path('manifest.json')->count(); + $this->assertSame(1, $manifestFile); + } + + public function testGithubRepositorybUTwITHiNVALIDhEADERS(): void + { + // Create a zip file with a pseudo manifest.json file + $manifest = '{"name:""Haleck45/ux-toolkit","version":"1.0.0"}'; + $workdir = sys_get_temp_dir().'/ux-toolkit'; + $filesystem = new Filesystem(); + $filesystem->mkdir($workdir); + $filesystem->dumpFile($workdir.'/manifest.json', $manifest); + $filesystem->dumpFile($workdir.'/README.md', 'My readme content'); + + $zip = new \ZipArchive(); + $zip->open($workdir.'/ux-toolkit-1.0.0.zip', \ZipArchive::CREATE); + $zip->addFile($workdir.'/manifest.json', 'manifest.json'); + $zip->addFile($workdir.'/README.md', 'README.md'); + $zip->close(); + + // Create a mock http client that will return the zip file + $client = new MockHttpClient(); + $client->setResponseFactory(fn () => new MockResponse( + file_get_contents($workdir.'/ux-toolkit-1.0.0.zip'), + [ + 'http_code' => 200, + 'response_headers' => [ + 'content-type' => 'application/json', + ], + ] + )); + + $filesystem = new Filesystem(); + $repository = new GithubRepository($filesystem, $client); + + $component = new RepositoryIdentity(RepositorySources::GITHUB, 'Halleck45', 'ux-toolkit', '1.0.0'); - $component = new ComponentIdentity('Halleck45', 'ux-toolkit', 'Badge', '1.0.0'); - $content = $repository->getContent($component); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The file from "http://github.com/Halleck45/ux-toolkit/archive/1.0.0.zip" is not a valid zip file.'); - $this->assertEquals('My badge content from github', $content); + $repository->fetch($component); } } diff --git a/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php b/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php index 441ec82969..7eefbf8bb7 100644 --- a/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php +++ b/src/Toolkit/tests/ComponentRepository/OfficialRepositoryTest.php @@ -12,8 +12,10 @@ namespace Symfony\UX\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\Component\Finder\Exception\DirectoryNotFoundException; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentity; use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; +use Symfony\UX\Toolkit\ComponentRepository\RepositorySources; /** * @author Jean-François Lépine @@ -23,21 +25,20 @@ class OfficialRepositoryTest extends TestCase public function testOfficialRepositoryGetContentOfExistentComponent(): void { $repository = new OfficialRepository(); - $component = new ComponentIdentity('symfony', 'ux-toolkit', 'Badge'); + $identity = new RepositoryIdentity(RepositorySources::EMBEDDED, 'symfony', 'default'); - $content = $repository->getContent($component); + $finder = $repository->fetch($identity); - $expectedContent = file_get_contents(__DIR__ . '/../../templates/default/components/Badge.html.twig'); - $this->assertEquals($expectedContent, $content); + $exists = $finder->files()->path('registry.json')->count(); + $this->assertEquals(1, $exists); } public function testOfficialRepositoryFailWhenComponentDoesNotExist(): void { $repository = new OfficialRepository(); - $component = new ComponentIdentity('symfony', 'ux-toolkit', 'NonExistentComponent'); + $identity = new RepositoryIdentity(RepositorySources::EMBEDDED, 'symfony', 'unexistent'); - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The component "NonExistentComponent" does not exist'); - $repository->getContent($component); + $this->expectException(DirectoryNotFoundException::class); + $finder = $repository->fetch($identity); } } diff --git a/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php b/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php index 596db5d41b..fae987a988 100644 --- a/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php +++ b/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php @@ -12,10 +12,11 @@ namespace Symfony\UX\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentity; +use Symfony\UX\Toolkit\ComponentRepository\RepositorySources; /** * @author Jean-François Lépine @@ -23,11 +24,10 @@ class RepositoryFactoryTest extends TestCase { /** - * @dataProvider providesNames + * @dataProvider providesSources */ public function testItShouldFactoryRepositoryAccordingToItsName( - string $vendor, - string $name, + int $type, ?string $expectedInstance, bool $shouldThrowException = false, ): void { @@ -40,7 +40,7 @@ public function testItShouldFactoryRepositoryAccordingToItsName( $this->expectException(\InvalidArgumentException::class); } - $result = $factory->factory(new ComponentIdentity($vendor, 'mypackage', $name)); + $result = $factory->factory(new RepositoryIdentity($type, 'myvendor', 'mypackage')); if ($shouldThrowException) { return; @@ -49,13 +49,12 @@ public function testItShouldFactoryRepositoryAccordingToItsName( $this->assertInstanceOf($expectedInstance, $result); } - public static function providesNames(): array + public static function providesSources(): array { return [ - ['symfony', 'button', OfficialRepository::class, false], - ['github.com/Foo', 'bar', GithubRepository::class, false], - ['gitlab.com/Foo', 'bar', null, true], - ['invalid name', 'bar', null, true], + [RepositorySources::EMBEDDED, OfficialRepository::class, false], + [RepositorySources::GITHUB, GithubRepository::class, false], + [99, null, true], ]; } -} \ No newline at end of file +} diff --git a/src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php b/src/Toolkit/tests/ComponentRepository/RepositoryIdentifierTest.php similarity index 61% rename from src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php rename to src/Toolkit/tests/ComponentRepository/RepositoryIdentifierTest.php index e8a39435bd..f40a04ba8d 100644 --- a/src/Toolkit/tests/ComponentRepository/ComponentIdentifierTest.php +++ b/src/Toolkit/tests/ComponentRepository/RepositoryIdentifierTest.php @@ -12,54 +12,54 @@ namespace Symfony\UX\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier; -use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentifier; +use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentity; +use Symfony\UX\Toolkit\ComponentRepository\RepositorySources; /** * @author Jean-François Lépine */ -class ComponentIdentifierTest extends TestCase +class RepositoryIdentifierTest extends TestCase { public function testItShouldIdentifyOfficialComponent(): void { - $identifier = new ComponentIdentifier(); - $identity = $identifier->identify('button'); + $identifier = new RepositoryIdentifier(); + $identity = $identifier->identify('default'); - $this->assertInstanceOf(ComponentIdentity::class, $identity); + $this->assertInstanceOf(RepositoryIdentity::class, $identity); + $this->assertEquals(RepositorySources::EMBEDDED, $identity->getType()); $this->assertEquals('symfony', $identity->getVendor()); - $this->assertEquals('Button', $identity->getName()); } public function testItShouldIdentifyGithubComponent(): void { - $identifier = new ComponentIdentifier(); - $identity = $identifier->identify('https://github.com/Halleck45/uikit:button'); + $identifier = new RepositoryIdentifier(); + $identity = $identifier->identify('https://github.com/Halleck45/uikit'); + $this->assertEquals(RepositorySources::GITHUB, $identity->getType()); $this->assertEquals('Halleck45', $identity->getVendor()); $this->assertEquals('uikit', $identity->getPackage()); - $this->assertEquals('Button', $identity->getName()); - $this->assertEquals('main', $identity->getVersion()); } public function testItShouldIdentifiyGithubComponentEventWithoutScheme(): void { - $identifier = new ComponentIdentifier(); - $identity = $identifier->identify('github.com/Halleck45/uikit:button'); + $identifier = new RepositoryIdentifier(); + $identity = $identifier->identify('github.com/Halleck45/uikit'); + $this->assertEquals(RepositorySources::GITHUB, $identity->getType()); $this->assertEquals('Halleck45', $identity->getVendor()); $this->assertEquals('uikit', $identity->getPackage()); - $this->assertEquals('Button', $identity->getName()); $this->assertEquals('main', $identity->getVersion()); } public function testItShouldIdentifyGithubComponentWithVersion(): void { - $identifier = new ComponentIdentifier(); - $identity = $identifier->identify('github.com/Halleck45/uikit:button@v1.0.0'); + $identifier = new RepositoryIdentifier(); + $identity = $identifier->identify('github.com/Halleck45/uikit@v1.0.0'); + $this->assertEquals(RepositorySources::GITHUB, $identity->getType()); $this->assertEquals('Halleck45', $identity->getVendor()); $this->assertEquals('uikit', $identity->getPackage()); - $this->assertEquals('Button', $identity->getName()); $this->assertEquals('v1.0.0', $identity->getVersion()); } } From 6a6cfa4cea9847b2deeb712fd5dc1f91052aba93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Sun, 2 Feb 2025 06:17:12 +0100 Subject: [PATCH 23/51] minor typos --- src/Toolkit/registry/default/registry.json | 6 ++---- src/Toolkit/src/ComponentRepository/ComponentRepository.php | 2 +- src/Toolkit/src/ComponentRepository/GithubRepository.php | 2 -- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Toolkit/registry/default/registry.json b/src/Toolkit/registry/default/registry.json index 45f0bdd92d..03e496a124 100644 --- a/src/Toolkit/registry/default/registry.json +++ b/src/Toolkit/registry/default/registry.json @@ -10,8 +10,7 @@ "title": "Badge", "description": "A simple badge component", "registryDependencies": [], - "files": [], - "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n variants: {\n variant: {\n default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary: \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive: \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n },\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}{% endblock %}\n\n" + "files": [] }, { "name": "button", @@ -20,8 +19,7 @@ "title": "Button", "description": "A Button component", "registryDependencies": [], - "files": [], - "code": ",\n" + "files": [] } ] } \ No newline at end of file diff --git a/src/Toolkit/src/ComponentRepository/ComponentRepository.php b/src/Toolkit/src/ComponentRepository/ComponentRepository.php index 78fa612a4a..e3d361a761 100644 --- a/src/Toolkit/src/ComponentRepository/ComponentRepository.php +++ b/src/Toolkit/src/ComponentRepository/ComponentRepository.php @@ -20,5 +20,5 @@ */ interface ComponentRepository { - public function fetch(RepositoryIdentity $component): Finder; + public function fetch(RepositoryIdentity $repository): Finder; } diff --git a/src/Toolkit/src/ComponentRepository/GithubRepository.php b/src/Toolkit/src/ComponentRepository/GithubRepository.php index 83c0773e9c..3af708724e 100644 --- a/src/Toolkit/src/ComponentRepository/GithubRepository.php +++ b/src/Toolkit/src/ComponentRepository/GithubRepository.php @@ -23,8 +23,6 @@ */ class GithubRepository implements ComponentRepository { - private readonly \ArrayObject $manifest; - public function __construct( private readonly Filesystem $filesystem, private readonly ?HttpClientInterface $httpClient = null, From 46bb2ad7c546bea9cd6c3fcd344bb13d9b9d72e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Sun, 2 Feb 2025 06:25:26 +0100 Subject: [PATCH 24/51] fixed linter --- src/Toolkit/doc/index.rst | 2 +- .../registry/default/components/Alert.json | 4 +- .../registry/default/components/Table.json | 4 +- src/Toolkit/registry/default/registry.json | 48 +++++++++---------- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/Toolkit/doc/index.rst b/src/Toolkit/doc/index.rst index 7bc10576c1..2fa0659828 100644 --- a/src/Toolkit/doc/index.rst +++ b/src/Toolkit/doc/index.rst @@ -25,4 +25,4 @@ the Symfony framework: https://symfony.com/doc/current/contributing/code/bc.html .. _`the Symfony UX initiative`: https://ux.symfony.com/ -.. _`Twig Component`: https://symfony.com/bundles/ux-twig-component/current/index.html +#.. _`Twig Component`: https://symfony.com/bundles/ux-twig-component/current/index.html diff --git a/src/Toolkit/registry/default/components/Alert.json b/src/Toolkit/registry/default/components/Alert.json index bd499db051..f9eb3a018c 100644 --- a/src/Toolkit/registry/default/components/Alert.json +++ b/src/Toolkit/registry/default/components/Alert.json @@ -3,7 +3,5 @@ "theme": "default", "type": "component", "code": "
\n Dependency test\n {% block content %}Alert{% endblock %}\n
\n", - "dependencies": [ - "Button" - ] + "dependencies": ["Button"] } diff --git a/src/Toolkit/registry/default/components/Table.json b/src/Toolkit/registry/default/components/Table.json index 32b19bcc60..4cff916c3e 100644 --- a/src/Toolkit/registry/default/components/Table.json +++ b/src/Toolkit/registry/default/components/Table.json @@ -3,7 +3,5 @@ "theme": "default", "type": "component", "code": "{# ux:with{Row, Button} #}\n\n {% block content %}{% endblock %}\n
\n", - "dependencies": [ - "Row" - ] + "dependencies": ["Row"] } diff --git a/src/Toolkit/registry/default/registry.json b/src/Toolkit/registry/default/registry.json index 03e496a124..290c225248 100644 --- a/src/Toolkit/registry/default/registry.json +++ b/src/Toolkit/registry/default/registry.json @@ -1,25 +1,25 @@ { - "$schema": "https://ui.shadcn.com/schema/registry.json", - "name": "symfony", - "homepage": "https://symfony.com", - "items": [ - { - "name": "badge", - "manifest": "components/Badge.json", - "type": "registry:component", - "title": "Badge", - "description": "A simple badge component", - "registryDependencies": [], - "files": [] - }, - { - "name": "button", - "manifest": "components/Button.json", - "type": "registry:component", - "title": "Button", - "description": "A Button component", - "registryDependencies": [], - "files": [] - } - ] -} \ No newline at end of file + "$schema": "https://ui.shadcn.com/schema/registry.json", + "name": "symfony", + "homepage": "https://symfony.com", + "items": [ + { + "name": "badge", + "manifest": "components/Badge.json", + "type": "registry:component", + "title": "Badge", + "description": "A simple badge component", + "registryDependencies": [], + "files": [] + }, + { + "name": "button", + "manifest": "components/Button.json", + "type": "registry:component", + "title": "Button", + "description": "A Button component", + "registryDependencies": [], + "files": [] + } + ] +} From a32ce217aef4a404faf105a5821af32e50f8d030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Sun, 2 Feb 2025 07:10:55 +0100 Subject: [PATCH 25/51] way to detecte circular deependencies between components --- .../src/Registry/DependenciesResolver.php | 65 +++++++++ src/Toolkit/src/Registry/Registry.php | 10 +- .../Registry/DependenciesResolverTest.php | 129 ++++++++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/Toolkit/src/Registry/DependenciesResolver.php create mode 100644 src/Toolkit/tests/Registry/DependenciesResolverTest.php diff --git a/src/Toolkit/src/Registry/DependenciesResolver.php b/src/Toolkit/src/Registry/DependenciesResolver.php new file mode 100644 index 0000000000..4e7b639881 --- /dev/null +++ b/src/Toolkit/src/Registry/DependenciesResolver.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Registry; + +/** + * @author Jean-François Lépine + * + * @internal + */ +class DependenciesResolver +{ + public function resolve(Registry $registry): array + { + $resolved = []; + $unresolved = []; + foreach (array_keys($registry->all()) as $itemName) { + [$resolved, $unresolved] = $this->resolveDependency($registry, $itemName, $resolved, $unresolved); + } + + $sorted = []; + foreach ($resolved as $itemName) { + $sorted[] = $registry->get($itemName); + } + + return $sorted; + } + + private function resolveDependency(Registry $registry, string $itemName, array $resolved, array $unresolved) + { + $unresolved[] = $itemName; + $dependencies = []; + if (null !== $registry->get($itemName)->parentName) { + $dependencies[] = $registry->get($itemName)->parentName; + } + + foreach ($dependencies as $dep) { + if (!\in_array($dep, $resolved)) { + if (!\in_array($dep, $unresolved)) { + $unresolved[] = $dep; + [$resolved, $unresolved] = $this->resolveDependency($registry, $dep, $resolved, $unresolved); + } else { + throw new \RuntimeException("Circular dependency detected: $itemName -> $dep."); + } + } + } + if (!\in_array($itemName, $resolved)) { + $resolved[] = $itemName; + } + + while (($index = array_search($itemName, $unresolved)) !== false) { + unset($unresolved[$index]); + } + + return [$resolved, $unresolved]; + } +} diff --git a/src/Toolkit/src/Registry/Registry.php b/src/Toolkit/src/Registry/Registry.php index 0342ff7b10..8e3a7f1e1c 100644 --- a/src/Toolkit/src/Registry/Registry.php +++ b/src/Toolkit/src/Registry/Registry.php @@ -33,7 +33,15 @@ public static function empty(): self public function add(RegistryItem $item): void { - $this->items[] = $item; + $this->items[$item->name] = $item; + } + + /** + * @return RegistryItem[] + */ + public function all(): array + { + return $this->items; } public function has(string $name): bool diff --git a/src/Toolkit/tests/Registry/DependenciesResolverTest.php b/src/Toolkit/tests/Registry/DependenciesResolverTest.php new file mode 100644 index 0000000000..8bee4ad31b --- /dev/null +++ b/src/Toolkit/tests/Registry/DependenciesResolverTest.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests\Registry; + +use PHPUnit\Framework\TestCase; +use Symfony\UX\Toolkit\Registry\DependenciesResolver; +use Symfony\UX\Toolkit\Registry\Registry; +use Symfony\UX\Toolkit\Registry\RegistryItem; +use Symfony\UX\Toolkit\Registry\RegistryItemType; + +/** + * @author Jean-François Lépine + * + * @group wip + */ +class DependenciesResolverTest extends TestCase +{ + public function testItShouldResolveDependenciesOrder(): void + { + $registry = new Registry(); + + $registry->add( + new RegistryItem( + 'cell', + RegistryItemType::Component, + 'default', + 'row', + '' + ) + ); + + $registry->add( + new RegistryItem( + 'table', + RegistryItemType::Component, + 'default', + null, + '' + ) + ); + + $registry->add( + new RegistryItem( + 'row', + RegistryItemType::Component, + 'default', + 'table', + '' + ) + ); + + $registry->add( + new RegistryItem( + 'button', + RegistryItemType::Component, + 'default', + null, + '' + ) + ); + + $registry->add( + new RegistryItem( + 'icon', + RegistryItemType::Component, + 'default', + 'button', + '' + ) + ); + + $resolver = new DependenciesResolver(); + $resolved = $resolver->resolve($registry); + $this->assertEquals('table', $resolved[0]->name); + $this->assertEquals('row', $resolved[1]->name); + $this->assertEquals('cell', $resolved[2]->name); + $this->assertEquals('button', $resolved[3]->name); + $this->assertEquals('icon', $resolved[4]->name); + } + + public function testCircularDependency(): void + { + $registry = new Registry(); + + $registry->add( + new RegistryItem( + 'cell', + RegistryItemType::Component, + 'default', + 'row', + '' + ) + ); + + $registry->add( + new RegistryItem( + 'table', + RegistryItemType::Component, + 'default', + 'cell', + '' + ) + ); + + $registry->add( + new RegistryItem( + 'row', + RegistryItemType::Component, + 'default', + 'table', + '' + ) + ); + + $resolver = new DependenciesResolver(); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Circular dependency detected: table -> cell'); + $resolver->resolve($registry); + } +} From 6b3b1267b570fda472117f79ee3f6aaa64a164a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Sun, 2 Feb 2025 07:23:15 +0100 Subject: [PATCH 26/51] use Zenstruck for command tests --- src/Toolkit/composer.json | 8 ++--- .../src/Command/UxToolkitInstallCommand.php | 2 +- .../src/Compiler/TwigComponentCompiler.php | 2 +- .../Command/UxToolkitInstallCommandTest.php | 35 ++++++------------- 4 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index 1b7b47246f..d14ac8444c 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -41,7 +41,9 @@ }, "require-dev": { "symfony/finder": "6.4|^7.0", - "tales-from-a-dev/twig-tailwind-extra": "^0.3.0" + "tales-from-a-dev/twig-tailwind-extra": "^0.3.0", + "zenstruck/console-test": "^1.7", + "symfony/http-client": "6.4|^7.0" }, "autoload": { "psr-4": { @@ -62,9 +64,5 @@ "name": "symfony/ux", "url": "https://github.com/symfony/ux" } - }, - "require-dev": { - "symfony/http-client": "6.4|^7.0", - "tales-from-a-dev/twig-tailwind-extra": "^0.3.0" } } diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index d3e7b011a2..6beeeab001 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -69,7 +69,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ) ); - $name = $input->getArgument('component'); + $name = ucfirst($input->getArgument('component')); $finder = $this->currentTheme->getRepository()->fetch($repository); $registry = $this->registryFactory->create($finder); diff --git a/src/Toolkit/src/Compiler/TwigComponentCompiler.php b/src/Toolkit/src/Compiler/TwigComponentCompiler.php index 732afc5958..74f5f3ad56 100644 --- a/src/Toolkit/src/Compiler/TwigComponentCompiler.php +++ b/src/Toolkit/src/Compiler/TwigComponentCompiler.php @@ -23,7 +23,7 @@ class TwigComponentCompiler { public function __construct( - private readonly string $prefix, + private readonly ?string $prefix, ) { } diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index ae0fafd030..57a0e59e8b 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -12,6 +12,7 @@ namespace Symfony\UX\Toolkit\Tests\Command; use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; @@ -24,42 +25,28 @@ use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; use Symfony\UX\Toolkit\Registry\RegistryFactory; +use Zenstruck\Console\Test\InteractsWithConsole; /** * @author Jean-François Lépine - * - * @group wip */ -class UxToolkitInstallCommandTest extends TestCase +class UxToolkitInstallCommandTest extends KernelTestCase { + use InteractsWithConsole; + public function testShouldAbleToCreateTheBadgeComponent(): void { - $repositoryFactory = new RepositoryFactory( - new OfficialRepository(), - $this->createMock(GithubRepository::class) - ); - $currentTheme = new CurrentTheme('default', $repositoryFactory, new RepositoryIdentifier()); - $compiler = new TwigComponentCompiler('Acme'); - $registryFactory = new RegistryFactory(new Filesystem()); - $command = new UxToolkitInstallCommand($currentTheme, $registryFactory, $compiler); - $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); mkdir($destination); - $input = new ArrayInput([ - 'component' => 'Badge', - '--destination' => $destination, - ]); - - $input->setInteractive(false); - $output = new BufferedOutput(); - - $result = $command->run($input, $output); - // Command should be successful - $this->assertEquals(Command::SUCCESS, $result); + $this->bootKernel(); + $this->consoleCommand('ux:toolkit:install badge --destination='.$destination) + ->execute() + ->assertSuccessful() + ->assertOutputContains('component "Badge" has been installed'); // A file should be created - $expectedFile = $destination.\DIRECTORY_SEPARATOR.'Acme'.\DIRECTORY_SEPARATOR.'Badge.html.twig'; + $expectedFile = $destination.\DIRECTORY_SEPARATOR.'Badge.html.twig'; $this->assertFileExists($expectedFile); // The content of the file should be the same as the content of the Badge component From 2c833803b56266527b5b1509845e87aff4677c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Sun, 2 Feb 2025 07:51:02 +0100 Subject: [PATCH 27/51] Way to get the list of available components in current theme --- .../src/Command/DebugUxToolkitCommand.php | 68 +++++++++++++++++++ .../src/Command/UxToolkitInstallCommand.php | 1 - .../ComponentRepository/GithubRepository.php | 14 ++-- src/Toolkit/src/UxToolkitBundle.php | 13 +++- .../Command/UxToolkitDebugCommandTest.php | 40 +++++++++++ .../Command/UxToolkitInstallCommandTest.php | 12 ---- 6 files changed, 128 insertions(+), 20 deletions(-) create mode 100644 src/Toolkit/src/Command/DebugUxToolkitCommand.php create mode 100644 src/Toolkit/tests/Command/UxToolkitDebugCommandTest.php diff --git a/src/Toolkit/src/Command/DebugUxToolkitCommand.php b/src/Toolkit/src/Command/DebugUxToolkitCommand.php new file mode 100644 index 0000000000..0cc16d599a --- /dev/null +++ b/src/Toolkit/src/Command/DebugUxToolkitCommand.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\UX\Toolkit\ComponentRepository\CurrentTheme; +use Symfony\UX\Toolkit\Registry\RegistryFactory; + +/** + * @author Jean-François Lépine + * + * @internal + */ +#[AsCommand( + name: 'debug:ux:toolkit', + description: 'This command list all components available in the current theme.' +)] +class DebugUxToolkitCommand extends Command +{ + public function __construct( + private readonly CurrentTheme $currentTheme, + private readonly RegistryFactory $registryFactory, + ) { + parent::__construct(); + } + + protected function configure(): void + { + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $repository = $this->currentTheme->getIdentity(); + $finder = $this->currentTheme->getRepository()->fetch($repository); + $registry = $this->registryFactory->create($finder); + + $io->title('Current theme:'); + $io->note('Update your config/packages/ux_toolkit.yaml to change the current theme.'); + $io->table(['Vendor', 'Package'], [[$repository->getVendor(), $repository->getPackage()]]); + + $io->title('Available components:'); + $table = []; + foreach ($registry->all() as $component) { + $table[] = [$component->name]; + } + + $io->table(['Component'], $table); + + $io->note('Run "symfony console ux:toolkit:install " to install a component.'); + + return Command::SUCCESS; + } +} diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index 6beeeab001..d2685b7c7b 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -58,7 +58,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - // Download sources, or get them from vendors $repository = $this->currentTheme->getIdentity(); $io->info( diff --git a/src/Toolkit/src/ComponentRepository/GithubRepository.php b/src/Toolkit/src/ComponentRepository/GithubRepository.php index 3af708724e..d99460be43 100644 --- a/src/Toolkit/src/ComponentRepository/GithubRepository.php +++ b/src/Toolkit/src/ComponentRepository/GithubRepository.php @@ -47,8 +47,14 @@ public function fetch(RepositoryIdentity $component): Finder ); $destination = $this->getCacheDir(); - $zipFile = $destination.'/'.basename($zipUrl); + $finder = new Finder(); + $finder->files()->in($destination); + + if ($this->filesystem->exists($destination.'/registry.json')) { + return $finder; + } + $zipFile = $destination.'/'.basename($zipUrl); $response = $this->httpClient->request('GET', $zipUrl, [ 'sink' => $zipFile, ]); @@ -58,7 +64,7 @@ public function fetch(RepositoryIdentity $component): Finder throw new \RuntimeException(\sprintf('Failed to download the file from "%s".', $zipUrl)); } - // Ensure response contains valid github headers + // Ensure response contains valid headers $headers = $response->getHeaders(); if (!isset($headers['content-type']) || !\in_array('application/zip', $headers['content-type'])) { throw new \RuntimeException(\sprintf('The file from "%s" is not a valid zip file.', $zipUrl)); @@ -73,10 +79,6 @@ public function fetch(RepositoryIdentity $component): Finder $zip->extractTo($destination); $zip->close(); - $rootDir = $destination; - $finder = new Finder(); - $finder->files()->in($rootDir); - return $finder; } diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index 78d6bf436a..7ba24735c6 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; +use Symfony\UX\Toolkit\Command\DebugUxToolkitCommand; use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; use Symfony\UX\Toolkit\ComponentRepository\CurrentTheme; @@ -51,17 +52,27 @@ public function build(ContainerBuilder $container): void '$prefix' => '%ux_toolkit.prefix%', ]); - // Prepare command + // Prepare commands $container->autowire(UxToolkitInstallCommand::class); $container ->registerForAutoconfiguration(UxToolkitInstallCommand::class) ->addTag('console.command'); + $container->autowire(DebugUxToolkitCommand::class); + $container + ->registerForAutoconfiguration(DebugUxToolkitCommand::class) + ->addTag('console.command'); + $container ->getDefinition(UxToolkitInstallCommand::class) ->setPublic(true) ->addTag('console.command'); + $container + ->getDefinition(DebugUxToolkitCommand::class) + ->setPublic(true) + ->addTag('console.command'); + // Inject http client (if exists) to github repository if ($container->has('http_client')) { $container->getDefinition(GithubRepository::class) diff --git a/src/Toolkit/tests/Command/UxToolkitDebugCommandTest.php b/src/Toolkit/tests/Command/UxToolkitDebugCommandTest.php new file mode 100644 index 0000000000..ed8164ffa8 --- /dev/null +++ b/src/Toolkit/tests/Command/UxToolkitDebugCommandTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Console\Test\InteractsWithConsole; + +/** + * @author Jean-François Lépine + */ +class UxToolkitDebugCommandTest extends KernelTestCase +{ + use InteractsWithConsole; + + public function testShouldBeAbleToLiseComponents(): void + { + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); + mkdir($destination); + + $this->bootKernel(); + $this->consoleCommand('debug:ux:toolkit') + ->execute() + ->assertSuccessful() + ->assertOutputContains('Current theme:') + ->assertOutputContains('Available components:') + ->assertOutputContains('Badge') + ->assertOutputContains('Button') + ; + } +} diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index 57a0e59e8b..3b924eb745 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -13,18 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\Filesystem\Filesystem; -use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; -use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; -use Symfony\UX\Toolkit\ComponentRepository\CurrentTheme; -use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentifier; -use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; -use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; -use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; -use Symfony\UX\Toolkit\Registry\RegistryFactory; use Zenstruck\Console\Test\InteractsWithConsole; /** From c3f82c0f3e67020b9535960803e358b7ad54c39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Sun, 2 Feb 2025 07:55:29 +0100 Subject: [PATCH 28/51] compatibility with phpunit 9.5 --- src/Toolkit/phpunit.xml.dist | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Toolkit/phpunit.xml.dist b/src/Toolkit/phpunit.xml.dist index ba14d89f76..85a6397b43 100644 --- a/src/Toolkit/phpunit.xml.dist +++ b/src/Toolkit/phpunit.xml.dist @@ -1,6 +1,6 @@ - + @@ -17,9 +17,4 @@ tests
- - - ./src - - From d404ff5a1941fcf05a8193a14f3b3bb865e7e6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Sun, 2 Feb 2025 08:10:18 +0100 Subject: [PATCH 29/51] Commands install component and its dependencies --- .../src/Command/UxToolkitInstallCommand.php | 4 +- .../src/Compiler/TwigComponentCompiler.php | 17 +++++- .../src/Registry/DependenciesResolver.php | 6 +- src/Toolkit/src/Registry/RegistryItem.php | 10 ++++ src/Toolkit/src/UxToolkitBundle.php | 2 + .../Compiler/TwigComponentCompilerTest.php | 60 ++++++++++++++++--- 6 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index d2685b7c7b..cb19fcd308 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -82,7 +82,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $destination = $input->getOption('destination'); try { $io->info(\sprintf('Installing component "%s"...', $component->name)); - $this->compiler->compile($component, $destination); + $this->compiler->compile($registry, $component, $destination); } catch (TwigComponentAlreadyExist $e) { if (!$input->isInteractive()) { $io->error(\sprintf('The component "%s" already exists.', $component->name)); @@ -97,7 +97,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } // again - $this->compiler->compile($component, $destination); + $this->compiler->compile($registry, $component, $destination); } $io->success(\sprintf('The component "%s" has been installed.', $component->name)); diff --git a/src/Toolkit/src/Compiler/TwigComponentCompiler.php b/src/Toolkit/src/Compiler/TwigComponentCompiler.php index 74f5f3ad56..cf7cd11609 100644 --- a/src/Toolkit/src/Compiler/TwigComponentCompiler.php +++ b/src/Toolkit/src/Compiler/TwigComponentCompiler.php @@ -13,6 +13,8 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist; +use Symfony\UX\Toolkit\Registry\DependenciesResolver; +use Symfony\UX\Toolkit\Registry\Registry; use Symfony\UX\Toolkit\Registry\RegistryItem; /** @@ -20,23 +22,36 @@ * * @internal */ -class TwigComponentCompiler +final class TwigComponentCompiler { public function __construct( private readonly ?string $prefix, + private readonly DependenciesResolver $dependenciesResolver, ) { } public function compile( + Registry $registry, RegistryItem $item, string $directory, ): void { + // resolve dependencies (avoid circular dependencies) + $this->dependenciesResolver->resolve($registry); $filesystem = new Filesystem(); if (!$filesystem->exists($directory)) { $filesystem->mkdir($directory); } + $componentsToInstall = array_merge([$item->name], $item->getDependencies()); + + foreach ($componentsToInstall as $componentName) { + $this->installComponent($registry->get($componentName), $directory, $filesystem); + } + } + + private function installComponent(RegistryItem $item, string $directory, Filesystem $filesystem) + { $filename = implode(\DIRECTORY_SEPARATOR, [ $directory, $this->prefix, diff --git a/src/Toolkit/src/Registry/DependenciesResolver.php b/src/Toolkit/src/Registry/DependenciesResolver.php index 4e7b639881..dcde4771cf 100644 --- a/src/Toolkit/src/Registry/DependenciesResolver.php +++ b/src/Toolkit/src/Registry/DependenciesResolver.php @@ -37,12 +37,8 @@ public function resolve(Registry $registry): array private function resolveDependency(Registry $registry, string $itemName, array $resolved, array $unresolved) { $unresolved[] = $itemName; - $dependencies = []; - if (null !== $registry->get($itemName)->parentName) { - $dependencies[] = $registry->get($itemName)->parentName; - } - foreach ($dependencies as $dep) { + foreach ($registry->get($itemName)->getDependencies() as $dep) { if (!\in_array($dep, $resolved)) { if (!\in_array($dep, $unresolved)) { $unresolved[] = $dep; diff --git a/src/Toolkit/src/Registry/RegistryItem.php b/src/Toolkit/src/Registry/RegistryItem.php index 84f8ea5aa7..25fe8c5fef 100644 --- a/src/Toolkit/src/Registry/RegistryItem.php +++ b/src/Toolkit/src/Registry/RegistryItem.php @@ -71,4 +71,14 @@ public static function fromFile(SplFileInfo $file): self // $file->getContents(), // ); } + + public function getDependencies(): array + { + $dependencies = []; + if (null !== $this->parentName) { + $dependencies[] = $this->parentName; + } + + return $dependencies; + } } diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index 7ba24735c6..0a3978cb20 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -23,6 +23,7 @@ use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentifier; use Symfony\UX\Toolkit\DependencyInjection\ToolkitExtension; +use Symfony\UX\Toolkit\Registry\DependenciesResolver; use Symfony\UX\Toolkit\Registry\RegistryFactory; /** @@ -45,6 +46,7 @@ public function build(ContainerBuilder $container): void $container->autowire(RepositoryFactory::class); $container->autowire(RepositoryIdentifier::class); $container->autowire(RegistryFactory::class); + $container->autowire(DependenciesResolver::class); $container->autowire(TwigComponentCompiler::class); $container->getDefinition(TwigComponentCompiler::class) diff --git a/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php index 4fc750805a..0b649070fc 100644 --- a/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php +++ b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php @@ -14,8 +14,8 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist; use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; -use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; -use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentity; +use Symfony\UX\Toolkit\Registry\DependenciesResolver; +use Symfony\UX\Toolkit\Registry\Registry; use Symfony\UX\Toolkit\Registry\RegistryItem; use Symfony\UX\Toolkit\Registry\RegistryItemType; @@ -26,9 +26,10 @@ class TwigComponentCompilerTest extends KernelTestCase { public function testItShouldCompileComponentToFile(): void { - $compiler = new TwigComponentCompiler('Acme'); + $compiler = new TwigComponentCompiler('Acme', new DependenciesResolver()); $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('component_'); + $registry = new Registry(); $item = new RegistryItem( 'Badge', RegistryItemType::Component, @@ -36,8 +37,9 @@ public function testItShouldCompileComponentToFile(): void null, '' ); + $registry->add($item); - $compiler->compile($item, $destination); + $compiler->compile($registry, $item, $destination); $this->assertFileExists($destination); $this->assertFileExists($destination.'/Acme/Badge.html.twig'); @@ -48,9 +50,10 @@ public function testItShouldCompileComponentToFile(): void public function testShouldThrowExceptionIfFileAlreadyExist(): void { - $compiler = new TwigComponentCompiler('Acme'); + $compiler = new TwigComponentCompiler('Acme', new DependenciesResolver()); $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('component_'); + $registry = new Registry(); $item = new RegistryItem( 'Badge', RegistryItemType::Component, @@ -58,10 +61,53 @@ public function testShouldThrowExceptionIfFileAlreadyExist(): void null, '' ); + $registry->add($item); - $compiler->compile($item, $destination); + $compiler->compile($registry, $item, $destination); $this->expectException(TwigComponentAlreadyExist::class); - $compiler->compile($item, $destination); + $compiler->compile($registry, $item, $destination); + } + + public function testDependenciesAreAlsoCompiled(): void + { + $compiler = new TwigComponentCompiler('Acme', new DependenciesResolver()); + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('component_'); + + $registry = new Registry(); + $registry->add( + new RegistryItem( + 'Badge', + RegistryItemType::Component, + 'default', + null, + '' + ) + ); + $registry->add( + new RegistryItem( + 'Table', + RegistryItemType::Component, + 'default', + null, + 'foo
' + ) + ); + $registry->add( + new RegistryItem( + 'Row', + RegistryItemType::Component, + 'default', + 'Table', + 'foo' + ) + ); + + $compiler->compile($registry, $registry->get('Row'), $destination); + + $this->assertFileExists($destination); + $this->assertFileExists($destination.'/Acme/Table.html.twig'); + $this->assertFileExists($destination.'/Acme/Row.html.twig'); + $this->assertFileDoesNotExist($destination.'/Acme/Badge.html.twig'); } } From 68f86cee15ab4e69caf28f9d3d9561449ab5fd55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Sun, 2 Feb 2025 08:21:04 +0100 Subject: [PATCH 30/51] trying to fix depreciations --- src/Toolkit/composer.json | 3 ++- src/Toolkit/phpunit.xml.dist | 5 +++++ .../tests/ComponentRepository/RepositoryFactoryTest.php | 9 ++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index d14ac8444c..26c9d6a106 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -43,7 +43,8 @@ "symfony/finder": "6.4|^7.0", "tales-from-a-dev/twig-tailwind-extra": "^0.3.0", "zenstruck/console-test": "^1.7", - "symfony/http-client": "6.4|^7.0" + "symfony/http-client": "6.4|^7.0", + "symfony/stopwatch": "^7.2" }, "autoload": { "psr-4": { diff --git a/src/Toolkit/phpunit.xml.dist b/src/Toolkit/phpunit.xml.dist index 85a6397b43..49611d1d02 100644 --- a/src/Toolkit/phpunit.xml.dist +++ b/src/Toolkit/phpunit.xml.dist @@ -11,10 +11,15 @@ + + tests + + + diff --git a/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php b/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php index fae987a988..d10c9b9c34 100644 --- a/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php +++ b/src/Toolkit/tests/ComponentRepository/RepositoryFactoryTest.php @@ -12,6 +12,7 @@ namespace Symfony\UX\Toolkit\Tests\ComponentRepository; use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; use Symfony\UX\Toolkit\ComponentRepository\OfficialRepository; use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory; @@ -21,7 +22,7 @@ /** * @author Jean-François Lépine */ -class RepositoryFactoryTest extends TestCase +class RepositoryFactoryTest extends KernelTestCase { /** * @dataProvider providesSources @@ -31,10 +32,8 @@ public function testItShouldFactoryRepositoryAccordingToItsName( ?string $expectedInstance, bool $shouldThrowException = false, ): void { - $factory = new RepositoryFactory( - $this->createMock(OfficialRepository::class), - $this->createMock(GithubRepository::class) - ); + $this->bootKernel(); + $factory = static::getContainer()->get(RepositoryFactory::class); if ($shouldThrowException) { $this->expectException(\InvalidArgumentException::class); From 8ccb3fc9f4c5301c86a19b0ec4e9f632a5e52046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Mon, 3 Feb 2025 07:34:27 +0100 Subject: [PATCH 31/51] Command for generate registry --- src/Toolkit/bin/build-registry.php | 38 ++---- .../registry/default/components/Alert.json | 5 +- .../registry/default/components/Card.json | 3 +- .../registry/default/components/Navbar.json | 3 +- .../registry/default/components/Table.json | 3 +- .../default/components/Table/Row.json | 3 +- .../registry/default/examples/Badge.json | 3 +- .../default/examples/BadgeOutline.json | 3 +- .../registry/default/examples/Button.json | 3 +- src/Toolkit/registry/default/registry.json | 69 +++++++--- .../src/Command/BuildRegistryCommand.php | 123 ++++++++++++++++++ src/Toolkit/src/Compiler/RegistryCompiler.php | 93 +++++++++++++ src/Toolkit/src/Registry/Registry.php | 83 +++++++----- src/Toolkit/src/Registry/RegistryFactory.php | 4 +- src/Toolkit/src/Registry/RegistryItem.php | 25 +++- src/Toolkit/src/UxToolkitBundle.php | 43 +++--- .../Command/BuildRegistryCommandTest.php | 74 +++++++++++ ...Test.php => DebugUxToolkitCommandTest.php} | 4 +- 18 files changed, 476 insertions(+), 106 deletions(-) create mode 100644 src/Toolkit/src/Command/BuildRegistryCommand.php create mode 100644 src/Toolkit/src/Compiler/RegistryCompiler.php create mode 100644 src/Toolkit/tests/Command/BuildRegistryCommandTest.php rename src/Toolkit/tests/Command/{UxToolkitDebugCommandTest.php => DebugUxToolkitCommandTest.php} (89%) diff --git a/src/Toolkit/bin/build-registry.php b/src/Toolkit/bin/build-registry.php index 741204327f..112066c1ce 100755 --- a/src/Toolkit/bin/build-registry.php +++ b/src/Toolkit/bin/build-registry.php @@ -1,29 +1,19 @@ #!/usr/bin/env php + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ -use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\Finder\Finder; -use Symfony\UX\Toolkit\Registry\Registry; -use Symfony\UX\Toolkit\Registry\RegistryItem; +require_once __DIR__.'/../vendor/autoload.php'; -// --- Configuration -$templateDir = __DIR__ . '/../templates'; -$registryDir = __DIR__ . '/../registry'; -// --- End of Configuration - -$registry = Registry::empty(); -$filesystem = new Filesystem(); - -$finderTemplates = Finder::create() - ->files() - ->in($templateDir) - ->name('*.html.twig') - ->sortByName(); - -foreach ($finderTemplates as $file) { - $registry->add(RegistryItem::fromFile($file)); -} - -$registry->save($registryDir, $filesystem); +$app = new Symfony\Component\Console\Application('Symfony UX Toolkit Builder', '0.1'); +$compiler = new Symfony\UX\Toolkit\Compiler\RegistryCompiler(new Symfony\Component\Filesystem\Filesystem()); +$app->add(new Symfony\UX\Toolkit\Command\BuildRegistryCommand($compiler)); +$app->setDefaultCommand('ux:toolkit:build-registry', true); +$app->run(); diff --git a/src/Toolkit/registry/default/components/Alert.json b/src/Toolkit/registry/default/components/Alert.json index f9eb3a018c..02317aadfd 100644 --- a/src/Toolkit/registry/default/components/Alert.json +++ b/src/Toolkit/registry/default/components/Alert.json @@ -1,7 +1,8 @@ { "name": "Alert", - "theme": "default", + "theme": "", "type": "component", "code": "
\n Dependency test\n {% block content %}Alert{% endblock %}\n
\n", - "dependencies": ["Button"] + "fingerprint": "9d173046a7c64cfc696f1a89aaac82a6", + "dependencies": [] } diff --git a/src/Toolkit/registry/default/components/Card.json b/src/Toolkit/registry/default/components/Card.json index 0f0f6a6084..22e7f250b1 100644 --- a/src/Toolkit/registry/default/components/Card.json +++ b/src/Toolkit/registry/default/components/Card.json @@ -1,7 +1,8 @@ { "name": "Card", - "theme": "default", + "theme": "", "type": "component", "code": "
\n {% block content %}{% endblock %}\n
\n", + "fingerprint": "b5033826eca1935e4055bf8e4d192a81", "dependencies": [] } diff --git a/src/Toolkit/registry/default/components/Navbar.json b/src/Toolkit/registry/default/components/Navbar.json index f07fc1c9fc..3f81158486 100644 --- a/src/Toolkit/registry/default/components/Navbar.json +++ b/src/Toolkit/registry/default/components/Navbar.json @@ -1,7 +1,8 @@ { "name": "Navbar", - "theme": "default", + "theme": "", "type": "component", "code": "\n", + "fingerprint": "213add68cdffa8aa3a270d7629bb489b", "dependencies": [] } diff --git a/src/Toolkit/registry/default/components/Table.json b/src/Toolkit/registry/default/components/Table.json index 4cff916c3e..34a66c89eb 100644 --- a/src/Toolkit/registry/default/components/Table.json +++ b/src/Toolkit/registry/default/components/Table.json @@ -1,7 +1,8 @@ { "name": "Table", - "theme": "default", + "theme": "", "type": "component", "code": "{# ux:with{Row, Button} #}\n\n {% block content %}{% endblock %}\n
\n", + "fingerprint": "60ec2663e1ba9c92b59d57935e565b2e", "dependencies": ["Row"] } diff --git a/src/Toolkit/registry/default/components/Table/Row.json b/src/Toolkit/registry/default/components/Table/Row.json index f8a2d232c4..5fd3c578be 100644 --- a/src/Toolkit/registry/default/components/Table/Row.json +++ b/src/Toolkit/registry/default/components/Table/Row.json @@ -1,7 +1,8 @@ { "name": "Row", - "theme": "default", + "theme": "", "type": "component", "code": "\n {% block content %}Row{% endblock %}\n\n", + "fingerprint": "0fded51fb960081206bbb62d932951a0", "dependencies": [] } diff --git a/src/Toolkit/registry/default/examples/Badge.json b/src/Toolkit/registry/default/examples/Badge.json index 29e4189457..c6e96dadce 100644 --- a/src/Toolkit/registry/default/examples/Badge.json +++ b/src/Toolkit/registry/default/examples/Badge.json @@ -1,7 +1,8 @@ { "name": "Badge", - "theme": "default", + "theme": "", "type": "example", "code": "Badge\n", + "fingerprint": "6eeb538fce2bda2152dd7af729d01253", "dependencies": [] } diff --git a/src/Toolkit/registry/default/examples/BadgeOutline.json b/src/Toolkit/registry/default/examples/BadgeOutline.json index a2723af5e7..df0c818c70 100644 --- a/src/Toolkit/registry/default/examples/BadgeOutline.json +++ b/src/Toolkit/registry/default/examples/BadgeOutline.json @@ -1,7 +1,8 @@ { "name": "BadgeOutline", - "theme": "default", + "theme": "", "type": "example", "code": "Badge\nBadge\nBadge\n", + "fingerprint": "ed027eb6cb73152ed3e7291fc39f6c07", "dependencies": [] } diff --git a/src/Toolkit/registry/default/examples/Button.json b/src/Toolkit/registry/default/examples/Button.json index 531ebbb887..20b4bb09e3 100644 --- a/src/Toolkit/registry/default/examples/Button.json +++ b/src/Toolkit/registry/default/examples/Button.json @@ -1,7 +1,8 @@ { "name": "Button", - "theme": "default", + "theme": "", "type": "example", "code": "Click me\n", + "fingerprint": "6449c62a6ead499bd1402d6b8506e45d", "dependencies": [] } diff --git a/src/Toolkit/registry/default/registry.json b/src/Toolkit/registry/default/registry.json index 290c225248..324c296132 100644 --- a/src/Toolkit/registry/default/registry.json +++ b/src/Toolkit/registry/default/registry.json @@ -1,25 +1,62 @@ { "$schema": "https://ui.shadcn.com/schema/registry.json", - "name": "symfony", - "homepage": "https://symfony.com", + "homepage": "https://...", "items": [ { - "name": "badge", - "manifest": "components/Badge.json", - "type": "registry:component", - "title": "Badge", - "description": "A simple badge component", - "registryDependencies": [], - "files": [] + "name": "Alert", + "theme": "", + "type": "component", + "fingerprint": "9d173046a7c64cfc696f1a89aaac82a6", + "dependencies": [] }, { - "name": "button", - "manifest": "components/Button.json", - "type": "registry:component", - "title": "Button", - "description": "A Button component", - "registryDependencies": [], - "files": [] + "name": "Badge", + "theme": "", + "type": "example", + "fingerprint": "6eeb538fce2bda2152dd7af729d01253", + "dependencies": [] + }, + { + "name": "Button", + "theme": "", + "type": "example", + "fingerprint": "6449c62a6ead499bd1402d6b8506e45d", + "dependencies": [] + }, + { + "name": "Card", + "theme": "", + "type": "component", + "fingerprint": "b5033826eca1935e4055bf8e4d192a81", + "dependencies": [] + }, + { + "name": "Navbar", + "theme": "", + "type": "component", + "fingerprint": "213add68cdffa8aa3a270d7629bb489b", + "dependencies": [] + }, + { + "name": "Table", + "theme": "", + "type": "component", + "fingerprint": "60ec2663e1ba9c92b59d57935e565b2e", + "dependencies": ["Row"] + }, + { + "name": "Row", + "theme": "", + "type": "component", + "fingerprint": "0fded51fb960081206bbb62d932951a0", + "dependencies": [] + }, + { + "name": "BadgeOutline", + "theme": "", + "type": "example", + "fingerprint": "ed027eb6cb73152ed3e7291fc39f6c07", + "dependencies": [] } ] } diff --git a/src/Toolkit/src/Command/BuildRegistryCommand.php b/src/Toolkit/src/Command/BuildRegistryCommand.php new file mode 100644 index 0000000000..9fb72aab62 --- /dev/null +++ b/src/Toolkit/src/Command/BuildRegistryCommand.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Finder\Finder; +use Symfony\UX\Toolkit\Compiler\RegistryCompiler; +use Symfony\UX\Toolkit\Registry\Registry; +use Symfony\UX\Toolkit\Registry\RegistryItem; + +/** + * @author Jean-François Lépine + * @author Hugo Alliaume + * + * @internal + */ +#[AsCommand( + name: 'ux:toolkit:build-registry', + description: 'This command allows to distribute your components, and build the registry.' +)] +class BuildRegistryCommand extends Command +{ + public function __construct( + private readonly RegistryCompiler $compiler, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption( + 'template-directory', + 't', + InputOption::VALUE_REQUIRED, + 'The directory where the templates are stored.', + 'templates' + ) + ->addOption( + 'destination', + 'r', + InputOption::VALUE_REQUIRED, + 'The directory where the registry will be stored.', + 'registry' + ) + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the component.', '') + ->addArgument('homepage', InputArgument::OPTIONAL, 'The homepage of the component.', 'https://...') + ->addOption( + 'authors', + '', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'The authors of the component.', + [] + ) + ->addOption( + 'licenses', + '', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'The licenses of the component.', + [] + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $io->title('Building the registry...'); + + $registry = Registry::empty(); + $registry->setName($input->getArgument('name')); + $registry->setHomepage($input->getArgument('homepage')); + + foreach ($input->getOption('authors') as $author) { + // author is a string like "name " + $email = null; + if (preg_match('/^(.+) <(.+)>$/', $author, $matches)) { + $author = ['name' => $matches[1], 'email' => $matches[2]]; + } + $registry->addAuthor($author, $email); + } + + foreach ($input->getOption('licenses') as $license) { + $registry->addLicense($license); + } + + $finderTemplates = Finder::create() + ->files() + ->in($input->getOption('template-directory')) + ->name('*.html.twig') + ->sortByName(); + + $table = []; + foreach ($finderTemplates as $file) { + $table[] = [$file->getRelativePathname()]; + $registry->add(RegistryItem::fromTwigFile($file)); + } + + $registryDir = $input->getOption('destination'); + $this->compiler->compile($registry, $registryDir); + + $io->table(['Templates'], $table); + + $io->success('The registry has been successfully built.'); + + return Command::SUCCESS; + } +} diff --git a/src/Toolkit/src/Compiler/RegistryCompiler.php b/src/Toolkit/src/Compiler/RegistryCompiler.php new file mode 100644 index 0000000000..367679021c --- /dev/null +++ b/src/Toolkit/src/Compiler/RegistryCompiler.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Compiler; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Filesystem\Path; +use Symfony\UX\Toolkit\Registry\Registry; +use Symfony\UX\Toolkit\Registry\RegistryItem; +use Symfony\UX\Toolkit\Registry\RegistryItemType; + +/** + * @author Jean-François Lépine + * @author Hugo Alliaume + * + * @internal + */ +final class RegistryCompiler +{ + public function __construct( + private readonly Filesystem $filesystem, + ) { + } + + public function compile(Registry $registry, string $registryDir): void + { + $this->filesystem->mkdir($registryDir); + + $registryJson = [ + '$schema' => 'https://ui.shadcn.com/schema/registry.json', + 'name' => $registry->getName(), + 'licenses' => $registry->getLicenses(), + 'authors' => $registry->getAuthors(), + 'homepage' => $registry->getHomepage(), + 'items' => [], + ]; + $registryJson = array_filter($registryJson); + + foreach ($registry->all() as $item) { + $itemPath = Path::join($registryDir, $item->theme, $item->type->value.'s', $item->parentName ?: '', $item->name.'.json'); + + $itemJson = [ + 'name' => $item->name, + 'theme' => $item->theme, + 'type' => $item->type->value, + 'code' => $item->code, + 'fingerprint' => md5($item->code), + 'dependencies' => $this->getDependencies($registry, $item), + ]; + + $this->filesystem->dumpFile($itemPath, json_encode($itemJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n"); + + unset($itemJson['code']); + $registryJson['items'][] = $itemJson; + } + + $registryPath = Path::join($registryDir, 'registry.json'); + $this->filesystem->dumpFile($registryPath, json_encode($registryJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n"); + } + + private function getDependencies(Registry $registry, RegistryItem $component): array + { + if (RegistryItemType::Component !== $component->type) { + return []; + } + + $dependencies = []; + + foreach ($registry->all() as $item) { + if ($item->theme !== $component->theme || $item->type !== $component->type) { + continue; + } + + if ($item->parentName === $component->name) { + $dependencies[] = $item->name; + } + + if (str_contains($component->code, 'name)) { + $dependencies[] = $item->name; + } + } + + return $dependencies; + } +} diff --git a/src/Toolkit/src/Registry/Registry.php b/src/Toolkit/src/Registry/Registry.php index 8e3a7f1e1c..125390804d 100644 --- a/src/Toolkit/src/Registry/Registry.php +++ b/src/Toolkit/src/Registry/Registry.php @@ -13,9 +13,6 @@ namespace Symfony\UX\Toolkit\Registry; -use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\Filesystem\Path; - /** * @internal */ @@ -26,6 +23,26 @@ final class Registry */ private array $items = []; + /** + * @var string[] + */ + private array $licenses = []; + + /** + * @var array array{name: string, email: string|null}[] + */ + private array $authors = []; + + /** + * @var string|null homepage + */ + private ?string $homepage; + + /** + * @var string|null name + */ + private ?string $name; + public static function empty(): self { return new self(); @@ -60,44 +77,46 @@ public function get(string $name): ?RegistryItem return null; } - public function save(string $registryDir, Filesystem $filesystem): void + public function addLicense(string $license): void { - $filesystem->mkdir($registryDir); + $this->licenses[] = $license; + } - foreach ($this->items as $item) { - $itemPath = Path::join($registryDir, $item->theme, $item->type->value.'s', $item->parentName ?: '', $item->name.'.json'); - $filesystem->dumpFile($itemPath, json_encode([ - 'name' => $item->name, - 'theme' => $item->theme, - 'type' => $item->type->value, - 'code' => $item->code, - 'dependencies' => $this->getDependencies($item), - ], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n"); - } + public function addAuthor(string $name, ?string $email): void + { + $this->authors[] = [ + 'name' => $name, + 'email' => $email, + ]; } - private function getDependencies(RegistryItem $component): array + public function setName(string $name): void { - if (RegistryItemType::Component !== $component->type) { - return []; - } + $this->name = $name; + } - $dependencies = []; + public function setHomepage(string $homepage): void + { + $this->homepage = $homepage; + } - foreach ($this->items as $item) { - if ($item->theme !== $component->theme || $item->type !== $component->type) { - continue; - } + public function getName(): string + { + return $this->name; + } - if ($item->parentName === $component->name) { - $dependencies[] = $item->name; - } + public function getLicenses(): array + { + return $this->licenses; + } - if (str_contains($component->code, 'name)) { - $dependencies[] = $item->name; - } - } + public function getAuthors(): array + { + return $this->authors; + } - return $dependencies; + public function getHomepage(): ?string + { + return $this->homepage; } } diff --git a/src/Toolkit/src/Registry/RegistryFactory.php b/src/Toolkit/src/Registry/RegistryFactory.php index e880fcb0ae..f33df52965 100644 --- a/src/Toolkit/src/Registry/RegistryFactory.php +++ b/src/Toolkit/src/Registry/RegistryFactory.php @@ -52,13 +52,13 @@ public function create(Finder $finder): Registry $file = reset($files); if (isset($item['hash'])) { - $hash = hash_file('sha256', $file->getRealPath()); + $hash = hash_file('md5', $file->getRealPath()); if ($hash !== $item['hash']) { throw new \RuntimeException(\sprintf('The file "%s" declared in the manifest has an invalid hash.', $filename)); } } - $item = RegistryItem::fromFile($file); + $item = RegistryItem::fromJsonFile($file); $registry->add($item); } diff --git a/src/Toolkit/src/Registry/RegistryItem.php b/src/Toolkit/src/Registry/RegistryItem.php index 25fe8c5fef..734b030670 100644 --- a/src/Toolkit/src/Registry/RegistryItem.php +++ b/src/Toolkit/src/Registry/RegistryItem.php @@ -34,7 +34,7 @@ public function __construct( ) { } - public static function fromFile(SplFileInfo $file): self + public static function fromJsonFile(SplFileInfo $file): self { $json = json_decode($file->getContents(), true); if (null === $json) { @@ -72,6 +72,29 @@ public static function fromFile(SplFileInfo $file): self // ); } + public static function fromTwigFile(SplFileInfo $file): self + { + if (!preg_match(self::REGEX_RELATIVE_FILE, $file->getRelativePathname(), $matches)) { + throw new \InvalidArgumentException(\sprintf('Unable to parse file path "%s", it must match the following pattern: "//(/)?.html.twig"', $file->getRelativePathname())); + } + + $name = $matches['name'] ?? $matches['nameOrParentName']; + $parentName = $matches['nameOrParentName'] ?? null; + if ($name === $parentName) { + $parentName = null; + } + $type = RegistryItemType::from($matches['type']); + $theme = ''; + + return new self( + $name, + $type, + $theme, + $parentName, + $file->getContents(), + ); + } + public function getDependencies(): array { $dependencies = []; diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index 0a3978cb20..eae7236568 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -14,8 +14,10 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; +use Symfony\UX\Toolkit\Command\BuildRegistryCommand; use Symfony\UX\Toolkit\Command\DebugUxToolkitCommand; use Symfony\UX\Toolkit\Command\UxToolkitInstallCommand; +use Symfony\UX\Toolkit\Compiler\RegistryCompiler; use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; use Symfony\UX\Toolkit\ComponentRepository\CurrentTheme; use Symfony\UX\Toolkit\ComponentRepository\GithubRepository; @@ -47,6 +49,7 @@ public function build(ContainerBuilder $container): void $container->autowire(RepositoryIdentifier::class); $container->autowire(RegistryFactory::class); $container->autowire(DependenciesResolver::class); + $container->autowire(RegistryCompiler::class); $container->autowire(TwigComponentCompiler::class); $container->getDefinition(TwigComponentCompiler::class) @@ -55,25 +58,9 @@ public function build(ContainerBuilder $container): void ]); // Prepare commands - $container->autowire(UxToolkitInstallCommand::class); - $container - ->registerForAutoconfiguration(UxToolkitInstallCommand::class) - ->addTag('console.command'); - - $container->autowire(DebugUxToolkitCommand::class); - $container - ->registerForAutoconfiguration(DebugUxToolkitCommand::class) - ->addTag('console.command'); - - $container - ->getDefinition(UxToolkitInstallCommand::class) - ->setPublic(true) - ->addTag('console.command'); - - $container - ->getDefinition(DebugUxToolkitCommand::class) - ->setPublic(true) - ->addTag('console.command'); + $this->addConsoleCommand($container, BuildRegistryCommand::class); + $this->addConsoleCommand($container, UxToolkitInstallCommand::class); + $this->addConsoleCommand($container, DebugUxToolkitCommand::class); // Inject http client (if exists) to github repository if ($container->has('http_client')) { @@ -87,7 +74,23 @@ public function build(ContainerBuilder $container): void ->setArguments([ '$theme' => '%ux_toolkit.theme%', ]); + } - + /** + * @param ContainerBuilder $container + * @param string $classname + * @return void + */ + public function addConsoleCommand(ContainerBuilder $container, string $classname): void + { + $container->autowire($classname); + $container + ->registerForAutoconfiguration($classname) + ->addTag('console.command'); + $container + ->getDefinition($classname) + ->setPublic(true) + ->addTag('console.command'); } + } diff --git a/src/Toolkit/tests/Command/BuildRegistryCommandTest.php b/src/Toolkit/tests/Command/BuildRegistryCommandTest.php new file mode 100644 index 0000000000..a273d1eb7f --- /dev/null +++ b/src/Toolkit/tests/Command/BuildRegistryCommandTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Console\Test\InteractsWithConsole; + +/** + * @author Jean-François Lépine + */ +class BuildRegistryCommandTest extends KernelTestCase +{ + use InteractsWithConsole; + + public function testShouldBeAbleToBuildRegistry(): void + { + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); + mkdir($destination); + + $this->bootKernel(); + $this->consoleCommand('ux:toolkit:build-registry --destination='.$destination) + ->execute() + ->assertSuccessful() + ->assertOutputContains('default/components/Alert.html.twig') + ->assertOutputContains('default/components/Table.html.twig') + ->assertOutputContains('default/components/Table/Row.html.twig') + ; + + $this->assertFileExists($destination.\DIRECTORY_SEPARATOR.'registry.json'); + $this->assertFileExists($destination.\DIRECTORY_SEPARATOR.'components/Alert.json'); + $this->assertFileExists($destination.\DIRECTORY_SEPARATOR.'components/Table.json'); + $this->assertFileExists($destination.\DIRECTORY_SEPARATOR.'components/Table/Row.json'); + + $row = json_decode(file_get_contents($destination.\DIRECTORY_SEPARATOR.'components/Table/Row.json'), true); + $this->assertSame('Row', $row['name']); + $this->assertNotEmpty($row['code']); + $this->assertSame(md5($row['code']), $row['fingerprint']); + } + + public function testShouldBeAbleToBuildRegistryWithMetadata(): void + { + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); + mkdir($destination); + + $this->bootKernel(); + $this->consoleCommand("ux:toolkit:build-registry --licenses=MIT --licenses=CECIL-B --destination=$destination SymfonyUX 'https://www.symfony.com'") + ->execute() + ->assertSuccessful() + ->assertOutputContains('default/components/Alert.html.twig') + ->assertOutputContains('default/components/Table.html.twig') + ->assertOutputContains('default/components/Table/Row.html.twig') + ; + + $this->assertFileExists($destination.\DIRECTORY_SEPARATOR.'registry.json'); + $this->assertFileExists($destination.\DIRECTORY_SEPARATOR.'components/Alert.json'); + $this->assertFileExists($destination.\DIRECTORY_SEPARATOR.'components/Table.json'); + $this->assertFileExists($destination.\DIRECTORY_SEPARATOR.'components/Table/Row.json'); + + $json = json_decode(file_get_contents($destination.\DIRECTORY_SEPARATOR.'registry.json'), true); + $this->assertSame(['MIT', 'CECIL-B'], $json['licenses']); + $this->assertSame('https://www.symfony.com', $json['homepage']); + $this->assertSame('SymfonyUX', $json['name']); + } +} diff --git a/src/Toolkit/tests/Command/UxToolkitDebugCommandTest.php b/src/Toolkit/tests/Command/DebugUxToolkitCommandTest.php similarity index 89% rename from src/Toolkit/tests/Command/UxToolkitDebugCommandTest.php rename to src/Toolkit/tests/Command/DebugUxToolkitCommandTest.php index ed8164ffa8..b47e2bad81 100644 --- a/src/Toolkit/tests/Command/UxToolkitDebugCommandTest.php +++ b/src/Toolkit/tests/Command/DebugUxToolkitCommandTest.php @@ -18,11 +18,11 @@ /** * @author Jean-François Lépine */ -class UxToolkitDebugCommandTest extends KernelTestCase +class DebugUxToolkitCommandTest extends KernelTestCase { use InteractsWithConsole; - public function testShouldBeAbleToLiseComponents(): void + public function testShouldBeAbleToListComponents(): void { $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); mkdir($destination); From d42b322d440a71b18c62ebe04f1a20779fea3ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Mon, 3 Feb 2025 08:44:18 +0100 Subject: [PATCH 32/51] fails installation when fingerprint is incorrect --- .../registry/default/components/Alert.json | 5 ++- .../registry/default/components/Badge.json | 4 +- .../registry/default/components/Button.json | 4 +- .../registry/default/components/Card.json | 1 + .../registry/default/components/Navbar.json | 1 + .../registry/default/components/Table.json | 5 ++- .../default/components/Table/Row.json | 1 + .../registry/default/examples/Badge.json | 1 + .../default/examples/BadgeOutline.json | 1 + .../registry/default/examples/Button.json | 1 + src/Toolkit/registry/default/registry.json | 42 +++++++++++++------ src/Toolkit/src/Compiler/RegistryCompiler.php | 2 + .../src/Compiler/TwigComponentCompiler.php | 7 +++- .../src/Registry/DependenciesResolver.php | 12 +++++- src/Toolkit/src/Registry/Registry.php | 9 +++- src/Toolkit/src/Registry/RegistryFactory.php | 15 ++++--- src/Toolkit/src/Registry/RegistryItem.php | 4 +- .../Command/UxToolkitInstallCommandTest.php | 1 + 18 files changed, 89 insertions(+), 27 deletions(-) diff --git a/src/Toolkit/registry/default/components/Alert.json b/src/Toolkit/registry/default/components/Alert.json index 02317aadfd..6662719382 100644 --- a/src/Toolkit/registry/default/components/Alert.json +++ b/src/Toolkit/registry/default/components/Alert.json @@ -1,8 +1,11 @@ { "name": "Alert", + "manifest": "components/Alert.json", "theme": "", "type": "component", "code": "
\n Dependency test\n {% block content %}Alert{% endblock %}\n
\n", "fingerprint": "9d173046a7c64cfc696f1a89aaac82a6", - "dependencies": [] + "dependencies": [ + "Button" + ] } diff --git a/src/Toolkit/registry/default/components/Badge.json b/src/Toolkit/registry/default/components/Badge.json index 1f25ab57bd..549650199f 100644 --- a/src/Toolkit/registry/default/components/Badge.json +++ b/src/Toolkit/registry/default/components/Badge.json @@ -1,7 +1,9 @@ { "name": "Badge", - "theme": "default", + "manifest": "components/Badge.json", + "theme": "", "type": "component", "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n variants: {\n variant: {\n default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary: \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive: \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n },\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "60d4bcc6f49889e80835dae477d51a11", "dependencies": [] } diff --git a/src/Toolkit/registry/default/components/Button.json b/src/Toolkit/registry/default/components/Button.json index b04a66378d..a41a8ceb89 100644 --- a/src/Toolkit/registry/default/components/Button.json +++ b/src/Toolkit/registry/default/components/Button.json @@ -1,7 +1,9 @@ { "name": "Button", - "theme": "default", + "manifest": "components/Button.json", + "theme": "", "type": "component", "code": "\n", + "fingerprint": "33a53115165083b116334ea7c448cc3f", "dependencies": [] } diff --git a/src/Toolkit/registry/default/components/Card.json b/src/Toolkit/registry/default/components/Card.json index 22e7f250b1..cfd7d3ac60 100644 --- a/src/Toolkit/registry/default/components/Card.json +++ b/src/Toolkit/registry/default/components/Card.json @@ -1,5 +1,6 @@ { "name": "Card", + "manifest": "components/Card.json", "theme": "", "type": "component", "code": "
\n {% block content %}{% endblock %}\n
\n", diff --git a/src/Toolkit/registry/default/components/Navbar.json b/src/Toolkit/registry/default/components/Navbar.json index 3f81158486..f8c3cf9862 100644 --- a/src/Toolkit/registry/default/components/Navbar.json +++ b/src/Toolkit/registry/default/components/Navbar.json @@ -1,5 +1,6 @@ { "name": "Navbar", + "manifest": "components/Navbar.json", "theme": "", "type": "component", "code": "\n", diff --git a/src/Toolkit/registry/default/components/Table.json b/src/Toolkit/registry/default/components/Table.json index 34a66c89eb..d4c66c748d 100644 --- a/src/Toolkit/registry/default/components/Table.json +++ b/src/Toolkit/registry/default/components/Table.json @@ -1,8 +1,11 @@ { "name": "Table", + "manifest": "components/Table.json", "theme": "", "type": "component", "code": "{# ux:with{Row, Button} #}\n\n {% block content %}{% endblock %}\n
\n", "fingerprint": "60ec2663e1ba9c92b59d57935e565b2e", - "dependencies": ["Row"] + "dependencies": [ + "Row" + ] } diff --git a/src/Toolkit/registry/default/components/Table/Row.json b/src/Toolkit/registry/default/components/Table/Row.json index 5fd3c578be..153345d59e 100644 --- a/src/Toolkit/registry/default/components/Table/Row.json +++ b/src/Toolkit/registry/default/components/Table/Row.json @@ -1,5 +1,6 @@ { "name": "Row", + "manifest": "components/Table/Row.json", "theme": "", "type": "component", "code": "\n {% block content %}Row{% endblock %}\n\n", diff --git a/src/Toolkit/registry/default/examples/Badge.json b/src/Toolkit/registry/default/examples/Badge.json index c6e96dadce..7ce20f8d24 100644 --- a/src/Toolkit/registry/default/examples/Badge.json +++ b/src/Toolkit/registry/default/examples/Badge.json @@ -1,5 +1,6 @@ { "name": "Badge", + "manifest": "examples/Badge.json", "theme": "", "type": "example", "code": "Badge\n", diff --git a/src/Toolkit/registry/default/examples/BadgeOutline.json b/src/Toolkit/registry/default/examples/BadgeOutline.json index df0c818c70..d621295389 100644 --- a/src/Toolkit/registry/default/examples/BadgeOutline.json +++ b/src/Toolkit/registry/default/examples/BadgeOutline.json @@ -1,5 +1,6 @@ { "name": "BadgeOutline", + "manifest": "examples/BadgeOutline.json", "theme": "", "type": "example", "code": "Badge\nBadge\nBadge\n", diff --git a/src/Toolkit/registry/default/examples/Button.json b/src/Toolkit/registry/default/examples/Button.json index 20b4bb09e3..e9b8e00da6 100644 --- a/src/Toolkit/registry/default/examples/Button.json +++ b/src/Toolkit/registry/default/examples/Button.json @@ -1,5 +1,6 @@ { "name": "Button", + "manifest": "examples/Button.json", "theme": "", "type": "example", "code": "Click me\n", diff --git a/src/Toolkit/registry/default/registry.json b/src/Toolkit/registry/default/registry.json index 324c296132..cdd9f8a045 100644 --- a/src/Toolkit/registry/default/registry.json +++ b/src/Toolkit/registry/default/registry.json @@ -4,58 +4,76 @@ "items": [ { "name": "Alert", + "manifest": "components/Alert.json", "theme": "", "type": "component", - "fingerprint": "9d173046a7c64cfc696f1a89aaac82a6", - "dependencies": [] + "dependencies": [ + "Button" + ] }, { "name": "Badge", + "manifest": "components/Badge.json", "theme": "", - "type": "example", - "fingerprint": "6eeb538fce2bda2152dd7af729d01253", + "type": "component", "dependencies": [] }, { "name": "Button", + "manifest": "components/Button.json", "theme": "", - "type": "example", - "fingerprint": "6449c62a6ead499bd1402d6b8506e45d", + "type": "component", "dependencies": [] }, { "name": "Card", + "manifest": "components/Card.json", "theme": "", "type": "component", - "fingerprint": "b5033826eca1935e4055bf8e4d192a81", "dependencies": [] }, { "name": "Navbar", + "manifest": "components/Navbar.json", "theme": "", "type": "component", - "fingerprint": "213add68cdffa8aa3a270d7629bb489b", "dependencies": [] }, { "name": "Table", + "manifest": "components/Table.json", "theme": "", "type": "component", - "fingerprint": "60ec2663e1ba9c92b59d57935e565b2e", - "dependencies": ["Row"] + "dependencies": [ + "Row" + ] }, { "name": "Row", + "manifest": "components/Table/Row.json", "theme": "", "type": "component", - "fingerprint": "0fded51fb960081206bbb62d932951a0", + "dependencies": [] + }, + { + "name": "Badge", + "manifest": "examples/Badge.json", + "theme": "", + "type": "example", "dependencies": [] }, { "name": "BadgeOutline", + "manifest": "examples/BadgeOutline.json", + "theme": "", + "type": "example", + "dependencies": [] + }, + { + "name": "Button", + "manifest": "examples/Button.json", "theme": "", "type": "example", - "fingerprint": "ed027eb6cb73152ed3e7291fc39f6c07", "dependencies": [] } ] diff --git a/src/Toolkit/src/Compiler/RegistryCompiler.php b/src/Toolkit/src/Compiler/RegistryCompiler.php index 367679021c..46d74e64aa 100644 --- a/src/Toolkit/src/Compiler/RegistryCompiler.php +++ b/src/Toolkit/src/Compiler/RegistryCompiler.php @@ -49,6 +49,7 @@ public function compile(Registry $registry, string $registryDir): void $itemJson = [ 'name' => $item->name, + 'manifest' => Path::makeRelative($itemPath, $registryDir), 'theme' => $item->theme, 'type' => $item->type->value, 'code' => $item->code, @@ -59,6 +60,7 @@ public function compile(Registry $registry, string $registryDir): void $this->filesystem->dumpFile($itemPath, json_encode($itemJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n"); unset($itemJson['code']); + unset($itemJson['fingerprint']); $registryJson['items'][] = $itemJson; } diff --git a/src/Toolkit/src/Compiler/TwigComponentCompiler.php b/src/Toolkit/src/Compiler/TwigComponentCompiler.php index cf7cd11609..d4ed68383d 100644 --- a/src/Toolkit/src/Compiler/TwigComponentCompiler.php +++ b/src/Toolkit/src/Compiler/TwigComponentCompiler.php @@ -16,6 +16,7 @@ use Symfony\UX\Toolkit\Registry\DependenciesResolver; use Symfony\UX\Toolkit\Registry\Registry; use Symfony\UX\Toolkit\Registry\RegistryItem; +use Symfony\UX\Toolkit\Registry\RegistryItemType; /** * @author Jean-François Lépine @@ -42,9 +43,7 @@ public function compile( if (!$filesystem->exists($directory)) { $filesystem->mkdir($directory); } - $componentsToInstall = array_merge([$item->name], $item->getDependencies()); - foreach ($componentsToInstall as $componentName) { $this->installComponent($registry->get($componentName), $directory, $filesystem); } @@ -52,6 +51,10 @@ public function compile( private function installComponent(RegistryItem $item, string $directory, Filesystem $filesystem) { + if (RegistryItemType::Component !== $item->type) { + return; + } + $filename = implode(\DIRECTORY_SEPARATOR, [ $directory, $this->prefix, diff --git a/src/Toolkit/src/Registry/DependenciesResolver.php b/src/Toolkit/src/Registry/DependenciesResolver.php index dcde4771cf..0f9ec8306f 100644 --- a/src/Toolkit/src/Registry/DependenciesResolver.php +++ b/src/Toolkit/src/Registry/DependenciesResolver.php @@ -22,7 +22,17 @@ public function resolve(Registry $registry): array { $resolved = []; $unresolved = []; - foreach (array_keys($registry->all()) as $itemName) { + + $concernedComponents = []; + foreach($registry->all() as $item) { + if ($item->type !== RegistryItemType::Component) { + continue; + } + + $concernedComponents[] = $item->name; + } + + foreach ($concernedComponents as $itemName) { [$resolved, $unresolved] = $this->resolveDependency($registry, $itemName, $resolved, $unresolved); } diff --git a/src/Toolkit/src/Registry/Registry.php b/src/Toolkit/src/Registry/Registry.php index 125390804d..9e973c43be 100644 --- a/src/Toolkit/src/Registry/Registry.php +++ b/src/Toolkit/src/Registry/Registry.php @@ -50,7 +50,7 @@ public static function empty(): self public function add(RegistryItem $item): void { - $this->items[$item->name] = $item; + $this->items[] = $item; } /** @@ -66,9 +66,14 @@ public function has(string $name): bool return null !== $this->get($name); } - public function get(string $name): ?RegistryItem + public function get(string $name, RegistryItemType $type = RegistryItemType::Component): ?RegistryItem { foreach ($this->items as $item) { + + if ($item->type !== $type) { + continue; + } + if ($item->name === $name) { return $item; } diff --git a/src/Toolkit/src/Registry/RegistryFactory.php b/src/Toolkit/src/Registry/RegistryFactory.php index f33df52965..014f33b5e4 100644 --- a/src/Toolkit/src/Registry/RegistryFactory.php +++ b/src/Toolkit/src/Registry/RegistryFactory.php @@ -51,15 +51,20 @@ public function create(Finder $finder): Registry } $file = reset($files); - if (isset($item['hash'])) { - $hash = hash_file('md5', $file->getRealPath()); - if ($hash !== $item['hash']) { + if (!isset($item['fingerprint']) && isset($item['code'])) { + throw new \RuntimeException(\sprintf('The file "%s" declared in the manifest must have a fingerprint.', $filename)); + } + + $itemObject = RegistryItem::fromJsonFile($file); + + if (isset($item['fingerprint'])) { + $hash = md5($itemObject->code); + if ($hash !== $item['fingerprint']) { throw new \RuntimeException(\sprintf('The file "%s" declared in the manifest has an invalid hash.', $filename)); } } - $item = RegistryItem::fromJsonFile($file); - $registry->add($item); + $registry->add($itemObject); } return $registry; diff --git a/src/Toolkit/src/Registry/RegistryItem.php b/src/Toolkit/src/Registry/RegistryItem.php index 734b030670..6a8a311228 100644 --- a/src/Toolkit/src/Registry/RegistryItem.php +++ b/src/Toolkit/src/Registry/RegistryItem.php @@ -83,7 +83,9 @@ public static function fromTwigFile(SplFileInfo $file): self if ($name === $parentName) { $parentName = null; } - $type = RegistryItemType::from($matches['type']); + // @todo: we should improve the way to detect examples + $isExample = preg_match('#examples#', $file->getRelativePathname()); + $type = $isExample ? RegistryItemType::Example : RegistryItemType::Component; $theme = ''; return new self( diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index 3b924eb745..6ac206c073 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -17,6 +17,7 @@ /** * @author Jean-François Lépine + * @group wip */ class UxToolkitInstallCommandTest extends KernelTestCase { From aacf196976b19537b5b4421d00a2ef36aaebeef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Mon, 3 Feb 2025 09:05:50 +0100 Subject: [PATCH 33/51] improved tests --- src/Toolkit/phpunit.xml.dist | 5 ++ .../src/Registry/DependenciesResolver.php | 4 +- src/Toolkit/src/Registry/RegistryItem.php | 2 +- .../Command/UxToolkitInstallCommandTest.php | 30 +++++++++++- .../ToolkitExtensionTest.php | 46 +++++++++++++++++++ src/Toolkit/tests/UxToolkitBundleTest.php | 32 +++++++++++++ 6 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 src/Toolkit/tests/DependencyInjection/ToolkitExtensionTest.php create mode 100644 src/Toolkit/tests/UxToolkitBundleTest.php diff --git a/src/Toolkit/phpunit.xml.dist b/src/Toolkit/phpunit.xml.dist index 49611d1d02..15f668e5cc 100644 --- a/src/Toolkit/phpunit.xml.dist +++ b/src/Toolkit/phpunit.xml.dist @@ -22,4 +22,9 @@ + + + src + + diff --git a/src/Toolkit/src/Registry/DependenciesResolver.php b/src/Toolkit/src/Registry/DependenciesResolver.php index 0f9ec8306f..32aae31391 100644 --- a/src/Toolkit/src/Registry/DependenciesResolver.php +++ b/src/Toolkit/src/Registry/DependenciesResolver.php @@ -24,8 +24,8 @@ public function resolve(Registry $registry): array $unresolved = []; $concernedComponents = []; - foreach($registry->all() as $item) { - if ($item->type !== RegistryItemType::Component) { + foreach ($registry->all() as $item) { + if (RegistryItemType::Component !== $item->type) { continue; } diff --git a/src/Toolkit/src/Registry/RegistryItem.php b/src/Toolkit/src/Registry/RegistryItem.php index 6a8a311228..a9a89dc94c 100644 --- a/src/Toolkit/src/Registry/RegistryItem.php +++ b/src/Toolkit/src/Registry/RegistryItem.php @@ -83,7 +83,7 @@ public static function fromTwigFile(SplFileInfo $file): self if ($name === $parentName) { $parentName = null; } - // @todo: we should improve the way to detect examples + // @todo: we should improve the way we detect examples $isExample = preg_match('#examples#', $file->getRelativePathname()); $type = $isExample ? RegistryItemType::Example : RegistryItemType::Component; $theme = ''; diff --git a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php index 6ac206c073..7df39b735a 100644 --- a/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php +++ b/src/Toolkit/tests/Command/UxToolkitInstallCommandTest.php @@ -11,12 +11,12 @@ namespace Symfony\UX\Toolkit\Tests\Command; -use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Zenstruck\Console\Test\InteractsWithConsole; /** * @author Jean-François Lépine + * * @group wip */ class UxToolkitInstallCommandTest extends KernelTestCase @@ -43,4 +43,32 @@ public function testShouldAbleToCreateTheBadgeComponent(): void $actualContent = file_get_contents($expectedFile); $this->assertEquals($expectedContent, $actualContent); } + + public function testShouldFailWhenComponentDoesNotExist(): void + { + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); + mkdir($destination); + + $this->bootKernel(); + $this->consoleCommand('ux:toolkit:install unknown --destination='.$destination) + ->execute() + ->assertFaulty() + ->assertOutputContains('The component "Unknown" does not exist.'); + } + + public function testShouldFailWhenComponentFileAlreadyExistsInNonInteractiveMode(): void + { + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); + mkdir($destination); + + $this->bootKernel(); + $this->consoleCommand('ux:toolkit:install badge --destination='.$destination) + ->execute() + ->assertSuccessful(); + + $this->consoleCommand('ux:toolkit:install badge --destination='.$destination) + ->execute() + ->assertFaulty() + ->assertOutputContains('The component "Badge" already exists.'); + } } diff --git a/src/Toolkit/tests/DependencyInjection/ToolkitExtensionTest.php b/src/Toolkit/tests/DependencyInjection/ToolkitExtensionTest.php new file mode 100644 index 0000000000..8ea58bc788 --- /dev/null +++ b/src/Toolkit/tests/DependencyInjection/ToolkitExtensionTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\UX\Toolkit\DependencyInjection\ToolkitExtension; + +/** + * @author Jean-François Lépine + */ +class ToolkitExtensionTest extends TestCase +{ + public function testGetAlias(): void + { + $extension = new ToolkitExtension(); + $this->assertEquals('ux_toolkit', $extension->getAlias()); + } + + public function testLoadInjectUsefulParameters(): void + { + $configs = [ + 'prefix' => 'Acme', + 'theme' => 'default', + ]; + + $container = new ContainerBuilder(); + $extension = new ToolkitExtension(); + $extension->load([$configs], $container); + + $this->assertTrue($container->hasParameter('ux_toolkit.prefix')); + $this->assertEquals('Acme', $container->getParameter('ux_toolkit.prefix')); + + $this->assertTrue($container->hasParameter('ux_toolkit.theme')); + $this->assertEquals('default', $container->getParameter('ux_toolkit.theme')); + } +} diff --git a/src/Toolkit/tests/UxToolkitBundleTest.php b/src/Toolkit/tests/UxToolkitBundleTest.php new file mode 100644 index 0000000000..95d769f46f --- /dev/null +++ b/src/Toolkit/tests/UxToolkitBundleTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\UX\Toolkit\DependencyInjection\ToolkitExtension; +use Symfony\UX\Toolkit\UxToolkitBundle; + +/** + * @author Jean-François Lépine + */ +class UxToolkitBundleTest extends KernelTestCase +{ + public function testBundleBuildsSuccessfully(): void + { + self::bootKernel(); + $container = self::$kernel->getContainer(); + + $this->assertInstanceOf(UxToolkitBundle::class, $container->get('kernel')->getBundles()['UxToolkitBundle']); + } +} From 7021e1884b943cb2d6e6ae32d33f41fe16de4283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Tue, 4 Feb 2025 07:02:16 +0100 Subject: [PATCH 34/51] minor fixes in test suite --- src/Toolkit/composer.json | 4 ++-- src/Toolkit/phpunit.xml.dist | 12 +++++++----- .../src/Compiler/TwigComponentCompiler.php | 4 ++-- .../tests/Compiler/TwigComponentCompilerTest.php | 4 ++-- src/Toolkit/tests/bootstrap.php | 16 ++++++++++++++++ 5 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 src/Toolkit/tests/bootstrap.php diff --git a/src/Toolkit/composer.json b/src/Toolkit/composer.json index 26c9d6a106..4fc5c0f5b9 100644 --- a/src/Toolkit/composer.json +++ b/src/Toolkit/composer.json @@ -34,7 +34,6 @@ "twig/twig": "^2.12|^3.0", "symfony/console": "^7.2", "symfony/framework-bundle": "^6.4|^7.0", - "symfony/phpunit-bridge": "^6.4|^7.0", "symfony/twig-bundle": "^6.4|^7.0", "symfony/ux-twig-component": "^2.22", "symfony/filesystem": "^7.2" @@ -44,7 +43,8 @@ "tales-from-a-dev/twig-tailwind-extra": "^0.3.0", "zenstruck/console-test": "^1.7", "symfony/http-client": "6.4|^7.0", - "symfony/stopwatch": "^7.2" + "symfony/stopwatch": "^7.2", + "symfony/phpunit-bridge": "^6.4|^7.0" }, "autoload": { "psr-4": { diff --git a/src/Toolkit/phpunit.xml.dist b/src/Toolkit/phpunit.xml.dist index 15f668e5cc..1aace93bab 100644 --- a/src/Toolkit/phpunit.xml.dist +++ b/src/Toolkit/phpunit.xml.dist @@ -1,6 +1,11 @@ - + @@ -8,11 +13,8 @@ - - + - - diff --git a/src/Toolkit/src/Compiler/TwigComponentCompiler.php b/src/Toolkit/src/Compiler/TwigComponentCompiler.php index d4ed68383d..cdfa1ce70f 100644 --- a/src/Toolkit/src/Compiler/TwigComponentCompiler.php +++ b/src/Toolkit/src/Compiler/TwigComponentCompiler.php @@ -49,7 +49,7 @@ public function compile( } } - private function installComponent(RegistryItem $item, string $directory, Filesystem $filesystem) + private function installComponent(RegistryItem $item, string $directory, Filesystem $filesystem): void { if (RegistryItemType::Component !== $item->type) { return; @@ -62,7 +62,7 @@ private function installComponent(RegistryItem $item, string $directory, Filesys ]); if ($filesystem->exists($filename)) { - throw new TwigComponentAlreadyExist(); + throw new TwigComponentAlreadyExist("The component '{$item->name}' already exists.", 0, null); } $filesystem->dumpFile($filename, $item->code); diff --git a/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php index 0b649070fc..767076f421 100644 --- a/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php +++ b/src/Toolkit/tests/Compiler/TwigComponentCompilerTest.php @@ -11,7 +11,7 @@ namespace Symfony\UX\Toolkit\Tests\Compiler; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use PHPUnit\Framework\TestCase; use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist; use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler; use Symfony\UX\Toolkit\Registry\DependenciesResolver; @@ -22,7 +22,7 @@ /** * @author Jean-François Lépine */ -class TwigComponentCompilerTest extends KernelTestCase +class TwigComponentCompilerTest extends TestCase { public function testItShouldCompileComponentToFile(): void { diff --git a/src/Toolkit/tests/bootstrap.php b/src/Toolkit/tests/bootstrap.php new file mode 100644 index 0000000000..3a6f67b1c3 --- /dev/null +++ b/src/Toolkit/tests/bootstrap.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Filesystem\Filesystem; + +require __DIR__.'/../vendor/autoload.php'; + +(new Filesystem())->remove(__DIR__.'/../var'); From 50577853d99d1a3eb453c416631f384fbd203600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Tue, 4 Feb 2025 07:54:02 +0100 Subject: [PATCH 35/51] fixed relative path, and tested in a real project --- src/Toolkit/Makefile | 3 ++ .../registry/default/components/Button.json | 4 +-- .../src/Command/UxToolkitInstallCommand.php | 4 +-- .../src/Compiler/TwigComponentCompiler.php | 7 ++-- .../default/components/Button.html.twig | 34 +++++++++++++++++-- 5 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 src/Toolkit/Makefile diff --git a/src/Toolkit/Makefile b/src/Toolkit/Makefile new file mode 100644 index 0000000000..95b2f5fc3b --- /dev/null +++ b/src/Toolkit/Makefile @@ -0,0 +1,3 @@ +.PHONY: build +build: + ./bin/build-registry.php --destination=registry/default \ No newline at end of file diff --git a/src/Toolkit/registry/default/components/Button.json b/src/Toolkit/registry/default/components/Button.json index a41a8ceb89..ef59fc977f 100644 --- a/src/Toolkit/registry/default/components/Button.json +++ b/src/Toolkit/registry/default/components/Button.json @@ -3,7 +3,7 @@ "manifest": "components/Button.json", "theme": "", "type": "component", - "code": "\n", - "fingerprint": "33a53115165083b116334ea7c448cc3f", + "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: '',\n variants: {\n variant: {\n default: \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-9 px-4 py-2\",\n secondary: \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 h-9 px-4 py-2\",\n destructive: \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 h-9 px-4 py-2\",\n ghost: \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2\",\n },\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}Button{% endblock %}\n\n", + "fingerprint": "6ae51f1eef38ae0fd3aa3b966c98d7f8", "dependencies": [] } diff --git a/src/Toolkit/src/Command/UxToolkitInstallCommand.php b/src/Toolkit/src/Command/UxToolkitInstallCommand.php index cb19fcd308..d8b2eca254 100644 --- a/src/Toolkit/src/Command/UxToolkitInstallCommand.php +++ b/src/Toolkit/src/Command/UxToolkitInstallCommand.php @@ -50,7 +50,7 @@ protected function configure(): void 'd', InputArgument::OPTIONAL, 'The destination directory', - 'templates/components/ux' + 'templates/components' ); } @@ -97,7 +97,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } // again - $this->compiler->compile($registry, $component, $destination); + $this->compiler->compile($registry, $component, $destination, true); } $io->success(\sprintf('The component "%s" has been installed.', $component->name)); diff --git a/src/Toolkit/src/Compiler/TwigComponentCompiler.php b/src/Toolkit/src/Compiler/TwigComponentCompiler.php index cdfa1ce70f..c0e0f5f91f 100644 --- a/src/Toolkit/src/Compiler/TwigComponentCompiler.php +++ b/src/Toolkit/src/Compiler/TwigComponentCompiler.php @@ -35,6 +35,7 @@ public function compile( Registry $registry, RegistryItem $item, string $directory, + bool $overwrite = false, ): void { // resolve dependencies (avoid circular dependencies) $this->dependenciesResolver->resolve($registry); @@ -45,11 +46,11 @@ public function compile( } $componentsToInstall = array_merge([$item->name], $item->getDependencies()); foreach ($componentsToInstall as $componentName) { - $this->installComponent($registry->get($componentName), $directory, $filesystem); + $this->installComponent($registry->get($componentName), $directory, $filesystem, $overwrite); } } - private function installComponent(RegistryItem $item, string $directory, Filesystem $filesystem): void + private function installComponent(RegistryItem $item, string $directory, Filesystem $filesystem, bool $overwrite): void { if (RegistryItemType::Component !== $item->type) { return; @@ -61,7 +62,7 @@ private function installComponent(RegistryItem $item, string $directory, Filesys $item->name.'.html.twig', ]); - if ($filesystem->exists($filename)) { + if ($filesystem->exists($filename) && !$overwrite) { throw new TwigComponentAlreadyExist("The component '{$item->name}' already exists.", 0, null); } diff --git a/src/Toolkit/templates/default/components/Button.html.twig b/src/Toolkit/templates/default/components/Button.html.twig index 7c75b3e94e..22dfbb83ad 100644 --- a/src/Toolkit/templates/default/components/Button.html.twig +++ b/src/Toolkit/templates/default/components/Button.html.twig @@ -1,5 +1,35 @@ - From 812e062bfb0d9e17b8b776d8cd123971fe6a9b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Tue, 4 Feb 2025 08:01:28 +0100 Subject: [PATCH 36/51] used shadcdn theme for button --- .../registry/default/components/Button.json | 4 ++-- .../default/components/Button.html.twig | 23 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Toolkit/registry/default/components/Button.json b/src/Toolkit/registry/default/components/Button.json index ef59fc977f..ba2d02871e 100644 --- a/src/Toolkit/registry/default/components/Button.json +++ b/src/Toolkit/registry/default/components/Button.json @@ -3,7 +3,7 @@ "manifest": "components/Button.json", "theme": "", "type": "component", - "code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: '',\n variants: {\n variant: {\n default: \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-9 px-4 py-2\",\n secondary: \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 h-9 px-4 py-2\",\n destructive: \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 h-9 px-4 py-2\",\n ghost: \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2\",\n },\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}Button{% endblock %}\n\n", - "fingerprint": "6ae51f1eef38ae0fd3aa3b966c98d7f8", + "code": "{%- props variant = 'default', outline = false, size = 'default' -%}\n{%- set style = html_cva(\n base: '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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive: \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n outline: {\n true: \"text-foreground bg-white\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 rounded-md px-3\",\n lg: \"h-11 rounded-md px-8\",\n icon: \"h-10 w-10\",\n },\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}Button{% endblock %}\n\n", + "fingerprint": "e8af4da83dc7b708414e4c5f2985ffa8", "dependencies": [] } diff --git a/src/Toolkit/templates/default/components/Button.html.twig b/src/Toolkit/templates/default/components/Button.html.twig index 22dfbb83ad..ccbb6e9fc9 100644 --- a/src/Toolkit/templates/default/components/Button.html.twig +++ b/src/Toolkit/templates/default/components/Button.html.twig @@ -1,16 +1,23 @@ -{%- props variant = 'default', outline = false -%} +{%- props variant = 'default', outline = false, size = 'default' -%} {%- set style = html_cva( - base: '', + base: '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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', variants: { variant: { - default: "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-9 px-4 py-2", - secondary: "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 h-9 px-4 py-2", - destructive: "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 h-9 px-4 py-2", - ghost: "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2", + default: "bg-primary text-primary-foreground hover:bg-primary/90", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", }, outline: { true: "text-foreground bg-white", - } + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, }, compoundVariants: [{ variant: ['default'], @@ -28,7 +35,7 @@ ) -%} \n", - "fingerprint": "e8af4da83dc7b708414e4c5f2985ffa8", + "code": "{%- props variant = 'default', outline = false, size = 'default' -%}\n{%- set style = html_cva(\n base: '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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive: \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n outline: {\n true: \"text-foreground bg-white\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 rounded-md px-3\",\n lg: \"h-11 rounded-md px-8\",\n icon: \"h-10 w-10\",\n },\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "e3d7e12f2c42fd1f3b47c435cee5b595", "dependencies": [] } diff --git a/src/Toolkit/registry/default/components/Grid.json b/src/Toolkit/registry/default/components/Grid.json index 2a9b23b4bc..789a293a5c 100644 --- a/src/Toolkit/registry/default/components/Grid.json +++ b/src/Toolkit/registry/default/components/Grid.json @@ -3,8 +3,8 @@ "manifest": "components/Grid.json", "theme": "", "type": "component", - "code": "{%- props\n xs = 0,\n sm = 0,\n md = 0,\n lg = 0,\n xl = 0,\n xsOffset = 0,\n smOffset = 0,\n mdOffset = 0,\n lgOffset = 0,\n xlOffset = 0,\n align = '',\n justify = '',\n direction = '',\n-%}\n\n{% set xsSize = false %}{% if xs > 0 %}{% set xsSize = \"true\" %}{% endif %}\n{% set smSize = false %}{% if sm > 0 %}{% set smSize = \"true\" %}{% endif %}\n{% set mdSize = false %}{% if md > 0 %}{% set mdSize = \"true\" %}{% endif %}\n{% set lgSize = false %}{% if lg > 0 %}{% set lgSize = \"true\" %}{% endif %}\n{% set xlSize = false %}{% if xl > 0 %}{% set xlSize = \"true\" %}{% endif %}\n\n{% set xsOffsetSize = false %}{% if xsOffset > 0 %}{% set xsOffsetSize = \"true\" %}{% endif %}\n{% set smOffsetSize = false %}{% if smOffset > 0 %}{% set smOffsetSize = \"true\" %}{% endif %}\n{% set mdOffsetSize = false %}{% if mdOffset > 0 %}{% set mdOffsetSize = \"true\" %}{% endif %}\n{% set lgOffsetSize = false %}{% if lgOffset > 0 %}{% set lgOffsetSize = \"true\" %}{% endif %}\n{% set xlOffsetSize = false %}{% if xlOffset > 0 %}{% set xlOffsetSize = \"true\" %}{% endif %}\n\n{% set alignClass = '' %}\n{% if align == 'start' %}{% set alignClass = 'items-start' %}\n{% elseif align == 'center' %}{% set alignClass = 'items-center' %}\n{% elseif align == 'end' %}{% set alignClass = 'items-end' %}\n{% endif %}\n\n{% set justifyClass = '' %}\n{% if justify == 'start' %}{% set justifyClass = 'justify-start' %}\n{% elseif justify == 'center' %}{% set justifyClass = 'justify-center' %}\n{% elseif justify == 'end' %}{% set justifyClass = 'justify-end' %}\n{% elseif justify == 'between' %}{% set justifyClass = 'justify-between' %}\n{% elseif justify == 'around' %}{% set justifyClass = 'justify-around' %}\n{% elseif justify == 'evenly' %}{% set justifyClass = 'justify-evenly' %}\n{% endif %}\n\n{% set directionClass = '' %}\n{% if direction == 'row' %}{% set directionClass = 'flex-row' %}\n{% elseif direction == 'column' %}{% set directionClass = 'flex-col' %}\n{% endif %}\n\n{%- set style = html_cva(\n base: 'grid',\n variants: {\n xsSize: {\n \"true\": 'grid-cols-' ~ xs,\n },\n smSize: {\n \"true\": 'sm:grid-cols-' ~ sm,\n },\n mdSize: {\n \"true\": 'md:grid-cols-' ~ md,\n },\n lgSize: {\n \"true\": 'lg:grid-cols-' ~ lg,\n },\n xlSize: {\n \"true\": 'xl:grid-cols-' ~ xl,\n },\n xsOffsetSize: {\n \"true\": 'col-start-' ~ (xsOffset + 1),\n },\n smOffsetSize: {\n \"true\": 'sm:col-start-' ~ (smOffset + 1),\n },\n mdOffsetSize: {\n \"true\": 'md:col-start-' ~ (mdOffset + 1),\n },\n lgOffsetSize: {\n \"true\": 'lg:col-start-' ~ (lgOffset + 1),\n },\n xlOffsetSize: {\n \"true\": 'xl:col-start-' ~ (xlOffset + 1),\n },\n },\n) -%}\n\n {% block content %}{% endblock %}\n", - "fingerprint": "478dbf00b7075704f05c8a629e4f9c54", + "code": "{%- props\n xs = 0,\n sm = 0,\n md = 0,\n lg = 0,\n xl = 0,\n align = '',\n justify = '',\n direction = '',\n gap = 0,\n-%}\n\n{% set xsSize = false %}{% if xs > 0 %}{% set xsSize = \"true\" %}{% endif %}\n{% set smSize = false %}{% if sm > 0 %}{% set smSize = \"true\" %}{% endif %}\n{% set mdSize = false %}{% if md > 0 %}{% set mdSize = \"true\" %}{% endif %}\n{% set lgSize = false %}{% if lg > 0 %}{% set lgSize = \"true\" %}{% endif %}\n{% set xlSize = false %}{% if xl > 0 %}{% set xlSize = \"true\" %}{% endif %}\n\n{% set alignClass = '' %}\n{% if align == 'start' %}{% set alignClass = 'items-start' %}\n{% elseif align == 'center' %}{% set alignClass = 'items-center' %}\n{% elseif align == 'end' %}{% set alignClass = 'items-end' %}\n{% endif %}\n\n{% set justifyClass = '' %}\n{% if justify == 'start' %}{% set justifyClass = 'justify-start' %}\n{% elseif justify == 'center' %}{% set justifyClass = 'justify-center' %}\n{% elseif justify == 'end' %}{% set justifyClass = 'justify-end' %}\n{% elseif justify == 'between' %}{% set justifyClass = 'justify-between' %}\n{% elseif justify == 'around' %}{% set justifyClass = 'justify-around' %}\n{% elseif justify == 'evenly' %}{% set justifyClass = 'justify-evenly' %}\n{% endif %}\n\n{% set directionClass = '' %}\n{% if direction == 'row' %}{% set directionClass = 'flex-row' %}\n{% elseif direction == 'column' %}{% set directionClass = 'flex-col' %}\n{% endif %}\n\n{% set gapClass = '' %}\n{% if gap > 0 %}\n {% set gapClass = 'gap-' ~ gap %}\n{% endif %}\n{%- set style = html_cva(\n base: 'grid',\n variants: {\n xsSize: {\n \"true\": 'grid-cols-' ~ xs,\n },\n smSize: {\n \"true\": 'sm:grid-cols-' ~ sm,\n },\n mdSize: {\n \"true\": 'md:grid-cols-' ~ md,\n },\n lgSize: {\n \"true\": 'lg:grid-cols-' ~ lg,\n },\n xlSize: {\n \"true\": 'xl:grid-cols-' ~ xl,\n },\n },\n) -%}\n\n {% block content %}{% endblock %}\n", + "fingerprint": "ddc1e8a66b02354dceb8301d389f1abd", "dependencies": [ "Col", "Row" diff --git a/src/Toolkit/registry/default/examples/AspectRatio.json b/src/Toolkit/registry/default/examples/AspectRatio.json new file mode 100644 index 0000000000..62d8490492 --- /dev/null +++ b/src/Toolkit/registry/default/examples/AspectRatio.json @@ -0,0 +1,9 @@ +{ + "name": "AspectRatio", + "manifest": "examples/AspectRatio.json", + "theme": "", + "type": "example", + "code": "\n \"Landscape\n", + "fingerprint": "eb827ab28e0df827a46f068d1c7927c0", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/registry.json b/src/Toolkit/registry/default/registry.json index b04ded8866..ba191d217c 100644 --- a/src/Toolkit/registry/default/registry.json +++ b/src/Toolkit/registry/default/registry.json @@ -11,6 +11,13 @@ "Button" ] }, + { + "name": "AspectRatio", + "manifest": "components/AspectRatio.json", + "theme": "", + "type": "component", + "dependencies": [] + }, { "name": "Badge", "manifest": "components/Badge.json", @@ -168,6 +175,13 @@ "type": "component", "dependencies": [] }, + { + "name": "AspectRatio", + "manifest": "examples/AspectRatio.json", + "theme": "", + "type": "example", + "dependencies": [] + }, { "name": "Badge", "manifest": "examples/Badge.json", diff --git a/src/Toolkit/templates/default/components/AspectRatio.html.twig b/src/Toolkit/templates/default/components/AspectRatio.html.twig new file mode 100644 index 0000000000..0d81a141d8 --- /dev/null +++ b/src/Toolkit/templates/default/components/AspectRatio.html.twig @@ -0,0 +1,9 @@ +{%- props ratio = (4/3) -%} +
+
+ {% block content %}{% endblock %} +
+
diff --git a/src/Toolkit/templates/default/components/Button.html.twig b/src/Toolkit/templates/default/components/Button.html.twig index ccbb6e9fc9..9927ba4c2d 100644 --- a/src/Toolkit/templates/default/components/Button.html.twig +++ b/src/Toolkit/templates/default/components/Button.html.twig @@ -38,5 +38,5 @@ class="{{ style.apply({variant, outline, size}, attributes.render('class'))|tailwind_merge }}" {{ attributes.defaults({}).without('class') }} > - {% block content %}Button{% endblock %} + {% block content %}{% endblock %} diff --git a/src/Toolkit/templates/default/examples/AspectRatio.html.twig b/src/Toolkit/templates/default/examples/AspectRatio.html.twig new file mode 100644 index 0000000000..0b73d532e9 --- /dev/null +++ b/src/Toolkit/templates/default/examples/AspectRatio.html.twig @@ -0,0 +1,6 @@ + + Landscape photograph by Tobias Tullius + \ No newline at end of file From cd96990317050d2e42ec835eac320afb3f4fe5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Thu, 6 Feb 2025 07:56:59 +0100 Subject: [PATCH 47/51] New component: Avatar --- .../registry/default/components/Avatar.json | 12 +++++++ .../components/Avatar/AvatarFallback.json | 9 ++++++ .../components/Avatar/AvatarImage.json | 9 ++++++ .../registry/default/examples/Avatar.json | 9 ++++++ src/Toolkit/registry/default/registry.json | 31 +++++++++++++++++++ .../default/components/Avatar.html.twig | 13 ++++++++ .../Avatar/AvatarFallback.html.twig | 13 ++++++++ .../components/Avatar/AvatarImage.html.twig | 11 +++++++ .../default/examples/Avatar.html.twig | 4 +++ 9 files changed, 111 insertions(+) create mode 100644 src/Toolkit/registry/default/components/Avatar.json create mode 100644 src/Toolkit/registry/default/components/Avatar/AvatarFallback.json create mode 100644 src/Toolkit/registry/default/components/Avatar/AvatarImage.json create mode 100644 src/Toolkit/registry/default/examples/Avatar.json create mode 100644 src/Toolkit/templates/default/components/Avatar.html.twig create mode 100644 src/Toolkit/templates/default/components/Avatar/AvatarFallback.html.twig create mode 100644 src/Toolkit/templates/default/components/Avatar/AvatarImage.html.twig create mode 100644 src/Toolkit/templates/default/examples/Avatar.html.twig diff --git a/src/Toolkit/registry/default/components/Avatar.json b/src/Toolkit/registry/default/components/Avatar.json new file mode 100644 index 0000000000..ce8d4285be --- /dev/null +++ b/src/Toolkit/registry/default/components/Avatar.json @@ -0,0 +1,12 @@ +{ + "name": "Avatar", + "manifest": "components/Avatar.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "c9bce3df7db54997fefe722308dcd098", + "dependencies": [ + "AvatarFallback", + "AvatarImage" + ] +} diff --git a/src/Toolkit/registry/default/components/Avatar/AvatarFallback.json b/src/Toolkit/registry/default/components/Avatar/AvatarFallback.json new file mode 100644 index 0000000000..762bf53584 --- /dev/null +++ b/src/Toolkit/registry/default/components/Avatar/AvatarFallback.json @@ -0,0 +1,9 @@ +{ + "name": "AvatarFallback", + "manifest": "components/Avatar/AvatarFallback.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: 'flex h-full w-full items-center justify-center rounded-full bg-muted',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "0170d9f5db25ece504dde4646320a3e1", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/components/Avatar/AvatarImage.json b/src/Toolkit/registry/default/components/Avatar/AvatarImage.json new file mode 100644 index 0000000000..d0022e0385 --- /dev/null +++ b/src/Toolkit/registry/default/components/Avatar/AvatarImage.json @@ -0,0 +1,9 @@ +{ + "name": "AvatarImage", + "manifest": "components/Avatar/AvatarImage.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: 'aspect-square h-full w-full',\n variants: {},\n compoundVariants: []\n) -%}\n\n", + "fingerprint": "0b34d0e7f0f6eeffea6dabaa5b84d908", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/examples/Avatar.json b/src/Toolkit/registry/default/examples/Avatar.json new file mode 100644 index 0000000000..b8f7013591 --- /dev/null +++ b/src/Toolkit/registry/default/examples/Avatar.json @@ -0,0 +1,9 @@ +{ + "name": "Avatar", + "manifest": "examples/Avatar.json", + "theme": "", + "type": "example", + "code": "\n \n JF\n", + "fingerprint": "b9576a44da59fd116793fdcfd0cf9502", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/registry.json b/src/Toolkit/registry/default/registry.json index ba191d217c..01f6d9d74f 100644 --- a/src/Toolkit/registry/default/registry.json +++ b/src/Toolkit/registry/default/registry.json @@ -18,6 +18,30 @@ "type": "component", "dependencies": [] }, + { + "name": "Avatar", + "manifest": "components/Avatar.json", + "theme": "", + "type": "component", + "dependencies": [ + "AvatarFallback", + "AvatarImage" + ] + }, + { + "name": "AvatarFallback", + "manifest": "components/Avatar/AvatarFallback.json", + "theme": "", + "type": "component", + "dependencies": [] + }, + { + "name": "AvatarImage", + "manifest": "components/Avatar/AvatarImage.json", + "theme": "", + "type": "component", + "dependencies": [] + }, { "name": "Badge", "manifest": "components/Badge.json", @@ -182,6 +206,13 @@ "type": "example", "dependencies": [] }, + { + "name": "Avatar", + "manifest": "examples/Avatar.json", + "theme": "", + "type": "example", + "dependencies": [] + }, { "name": "Badge", "manifest": "examples/Badge.json", diff --git a/src/Toolkit/templates/default/components/Avatar.html.twig b/src/Toolkit/templates/default/components/Avatar.html.twig new file mode 100644 index 0000000000..f7d1272ad5 --- /dev/null +++ b/src/Toolkit/templates/default/components/Avatar.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/src/Toolkit/templates/default/components/Avatar/AvatarFallback.html.twig b/src/Toolkit/templates/default/components/Avatar/AvatarFallback.html.twig new file mode 100644 index 0000000000..2b02b8ef91 --- /dev/null +++ b/src/Toolkit/templates/default/components/Avatar/AvatarFallback.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'flex h-full w-full items-center justify-center rounded-full bg-muted', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/src/Toolkit/templates/default/components/Avatar/AvatarImage.html.twig b/src/Toolkit/templates/default/components/Avatar/AvatarImage.html.twig new file mode 100644 index 0000000000..7d1f9fc99f --- /dev/null +++ b/src/Toolkit/templates/default/components/Avatar/AvatarImage.html.twig @@ -0,0 +1,11 @@ +{%- props -%} +{%- set style = html_cva( + base: 'aspect-square h-full w-full', + variants: {}, + compoundVariants: [] +) -%} + + \ No newline at end of file diff --git a/src/Toolkit/templates/default/examples/Avatar.html.twig b/src/Toolkit/templates/default/examples/Avatar.html.twig new file mode 100644 index 0000000000..86174a0db5 --- /dev/null +++ b/src/Toolkit/templates/default/examples/Avatar.html.twig @@ -0,0 +1,4 @@ + + + JF + \ No newline at end of file From 53ed9ed2e3470442df309410d9024a26e7d8b589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 7 Feb 2025 06:40:15 +0100 Subject: [PATCH 48/51] Add component: Breadcrumb --- .../registry/default/components/Blank.json | 9 +++ .../default/components/Breadcrumb.json | 16 +++++ .../Breadcrumb/BreadcrumbEllipsis.json | 9 +++ .../components/Breadcrumb/BreadcrumbItem.json | 9 +++ .../components/Breadcrumb/BreadcrumbLink.json | 9 +++ .../components/Breadcrumb/BreadcrumbList.json | 9 +++ .../components/Breadcrumb/BreadcrumbPage.json | 9 +++ .../Breadcrumb/BreadcrumbSeparator.json | 9 +++ .../registry/default/examples/Breadcrumb.json | 9 +++ src/Toolkit/registry/default/registry.json | 70 +++++++++++++++++++ .../default/components/Blank.html.twig | 13 ++++ .../default/components/Breadcrumb.html.twig | 13 ++++ .../Breadcrumb/BreadcrumbEllipsis.html.twig | 20 ++++++ .../Breadcrumb/BreadcrumbItem.html.twig | 13 ++++ .../Breadcrumb/BreadcrumbLink.html.twig | 13 ++++ .../Breadcrumb/BreadcrumbList.html.twig | 13 ++++ .../Breadcrumb/BreadcrumbPage.html.twig | 18 +++++ .../Breadcrumb/BreadcrumbSeparator.html.twig | 20 ++++++ .../default/examples/Breadcrumb.html.twig | 19 +++++ 19 files changed, 300 insertions(+) create mode 100644 src/Toolkit/registry/default/components/Blank.json create mode 100644 src/Toolkit/registry/default/components/Breadcrumb.json create mode 100644 src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbEllipsis.json create mode 100644 src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbItem.json create mode 100644 src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbLink.json create mode 100644 src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbList.json create mode 100644 src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbPage.json create mode 100644 src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbSeparator.json create mode 100644 src/Toolkit/registry/default/examples/Breadcrumb.json create mode 100644 src/Toolkit/templates/default/components/Blank.html.twig create mode 100644 src/Toolkit/templates/default/components/Breadcrumb.html.twig create mode 100644 src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbEllipsis.html.twig create mode 100644 src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbItem.html.twig create mode 100644 src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbLink.html.twig create mode 100644 src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbList.html.twig create mode 100644 src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbPage.html.twig create mode 100644 src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbSeparator.html.twig create mode 100644 src/Toolkit/templates/default/examples/Breadcrumb.html.twig diff --git a/src/Toolkit/registry/default/components/Blank.json b/src/Toolkit/registry/default/components/Blank.json new file mode 100644 index 0000000000..8b273f5b5b --- /dev/null +++ b/src/Toolkit/registry/default/components/Blank.json @@ -0,0 +1,9 @@ +{ + "name": "Blank", + "manifest": "components/Blank.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: '',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "9beffd90dfda5a86065ea9382f2b48d8", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/components/Breadcrumb.json b/src/Toolkit/registry/default/components/Breadcrumb.json new file mode 100644 index 0000000000..f1535312a0 --- /dev/null +++ b/src/Toolkit/registry/default/components/Breadcrumb.json @@ -0,0 +1,16 @@ +{ + "name": "Breadcrumb", + "manifest": "components/Breadcrumb.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: '',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "9beffd90dfda5a86065ea9382f2b48d8", + "dependencies": [ + "BreadcrumbEllipsis", + "BreadcrumbItem", + "BreadcrumbLink", + "BreadcrumbList", + "BreadcrumbPage", + "BreadcrumbSeparator" + ] +} diff --git a/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbEllipsis.json b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbEllipsis.json new file mode 100644 index 0000000000..0914a5dcb4 --- /dev/null +++ b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbEllipsis.json @@ -0,0 +1,9 @@ +{ + "name": "BreadcrumbEllipsis", + "manifest": "components/Breadcrumb/BreadcrumbEllipsis.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: 'flex h-9 w-9 items-center justify-center',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% set _block = block('content') %}\n {% if content is defined and content is not empty %}\n {% block content %}{% endblock %}\n {% else %}\n ...\n {% endif %}\n\n", + "fingerprint": "c1725f1ef14bef095bdc4db7ab29d75f", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbItem.json b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbItem.json new file mode 100644 index 0000000000..662849f858 --- /dev/null +++ b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbItem.json @@ -0,0 +1,9 @@ +{ + "name": "BreadcrumbItem", + "manifest": "components/Breadcrumb/BreadcrumbItem.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center gap-1.5',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "566c0fb53c2203aa5e33b7fed04381e6", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbLink.json b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbLink.json new file mode 100644 index 0000000000..600c919f6c --- /dev/null +++ b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbLink.json @@ -0,0 +1,9 @@ +{ + "name": "BreadcrumbLink", + "manifest": "components/Breadcrumb/BreadcrumbLink.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: 'transition-colors hover:text-foreground',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "fd5fd349e1b64d6e74b9c9de9951f398", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbList.json b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbList.json new file mode 100644 index 0000000000..7036d7d6a9 --- /dev/null +++ b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbList.json @@ -0,0 +1,9 @@ +{ + "name": "BreadcrumbList", + "manifest": "components/Breadcrumb/BreadcrumbList.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: 'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "ed441b8c8f02142eb07081ef4708669c", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbPage.json b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbPage.json new file mode 100644 index 0000000000..b6fcab2047 --- /dev/null +++ b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbPage.json @@ -0,0 +1,9 @@ +{ + "name": "BreadcrumbPage", + "manifest": "components/Breadcrumb/BreadcrumbPage.json", + "theme": "", + "type": "component", + "code": "{%- props \n disabled = false\n-%}\n{%- set style = html_cva(\n base: 'font-normal text-foreground',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% block content %}{% endblock %}\n\n", + "fingerprint": "28c28a5f7fcb66306c9fb7aa8563870a", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbSeparator.json b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbSeparator.json new file mode 100644 index 0000000000..0771708cf2 --- /dev/null +++ b/src/Toolkit/registry/default/components/Breadcrumb/BreadcrumbSeparator.json @@ -0,0 +1,9 @@ +{ + "name": "BreadcrumbSeparator", + "manifest": "components/Breadcrumb/BreadcrumbSeparator.json", + "theme": "", + "type": "component", + "code": "{%- props -%}\n{%- set style = html_cva(\n base: '[&>svg]:w-3.5 [&>svg]:h-3.5',\n variants: {},\n compoundVariants: []\n) -%}\n\n\n {% set _block = block('content') %}\n {% if content is defined and content is not empty %}\n {% block content %}{% endblock %}\n {% else %}\n \n {% endif %}\n\n", + "fingerprint": "968a5bd3d8061b5611acfa662499d109", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/examples/Breadcrumb.json b/src/Toolkit/registry/default/examples/Breadcrumb.json new file mode 100644 index 0000000000..e05b1d4b04 --- /dev/null +++ b/src/Toolkit/registry/default/examples/Breadcrumb.json @@ -0,0 +1,9 @@ +{ + "name": "Breadcrumb", + "manifest": "examples/Breadcrumb.json", + "theme": "", + "type": "example", + "code": "\n \n \n Home\n \n \n \n Docs\n \n \n \n Components\n \n \n \n Breadcrumb\n \n \n", + "fingerprint": "189cc730241ee5755e0766511a619062", + "dependencies": [] +} diff --git a/src/Toolkit/registry/default/registry.json b/src/Toolkit/registry/default/registry.json index 01f6d9d74f..8d21fae510 100644 --- a/src/Toolkit/registry/default/registry.json +++ b/src/Toolkit/registry/default/registry.json @@ -49,6 +49,69 @@ "type": "component", "dependencies": [] }, + { + "name": "Blank", + "manifest": "components/Blank.json", + "theme": "", + "type": "component", + "dependencies": [] + }, + { + "name": "Breadcrumb", + "manifest": "components/Breadcrumb.json", + "theme": "", + "type": "component", + "dependencies": [ + "BreadcrumbEllipsis", + "BreadcrumbItem", + "BreadcrumbLink", + "BreadcrumbList", + "BreadcrumbPage", + "BreadcrumbSeparator" + ] + }, + { + "name": "BreadcrumbEllipsis", + "manifest": "components/Breadcrumb/BreadcrumbEllipsis.json", + "theme": "", + "type": "component", + "dependencies": [] + }, + { + "name": "BreadcrumbItem", + "manifest": "components/Breadcrumb/BreadcrumbItem.json", + "theme": "", + "type": "component", + "dependencies": [] + }, + { + "name": "BreadcrumbLink", + "manifest": "components/Breadcrumb/BreadcrumbLink.json", + "theme": "", + "type": "component", + "dependencies": [] + }, + { + "name": "BreadcrumbList", + "manifest": "components/Breadcrumb/BreadcrumbList.json", + "theme": "", + "type": "component", + "dependencies": [] + }, + { + "name": "BreadcrumbPage", + "manifest": "components/Breadcrumb/BreadcrumbPage.json", + "theme": "", + "type": "component", + "dependencies": [] + }, + { + "name": "BreadcrumbSeparator", + "manifest": "components/Breadcrumb/BreadcrumbSeparator.json", + "theme": "", + "type": "component", + "dependencies": [] + }, { "name": "Button", "manifest": "components/Button.json", @@ -227,6 +290,13 @@ "type": "example", "dependencies": [] }, + { + "name": "Breadcrumb", + "manifest": "examples/Breadcrumb.json", + "theme": "", + "type": "example", + "dependencies": [] + }, { "name": "Button", "manifest": "examples/Button.json", diff --git a/src/Toolkit/templates/default/components/Blank.html.twig b/src/Toolkit/templates/default/components/Blank.html.twig new file mode 100644 index 0000000000..c43950b7f6 --- /dev/null +++ b/src/Toolkit/templates/default/components/Blank.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: '', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/src/Toolkit/templates/default/components/Breadcrumb.html.twig b/src/Toolkit/templates/default/components/Breadcrumb.html.twig new file mode 100644 index 0000000000..c43950b7f6 --- /dev/null +++ b/src/Toolkit/templates/default/components/Breadcrumb.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: '', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbEllipsis.html.twig b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbEllipsis.html.twig new file mode 100644 index 0000000000..caf8063029 --- /dev/null +++ b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbEllipsis.html.twig @@ -0,0 +1,20 @@ +{%- props -%} +{%- set style = html_cva( + base: 'flex h-9 w-9 items-center justify-center', + variants: {}, + compoundVariants: [] +) -%} + + diff --git a/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbItem.html.twig b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbItem.html.twig new file mode 100644 index 0000000000..33d43018ae --- /dev/null +++ b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbItem.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'inline-flex items-center gap-1.5', + variants: {}, + compoundVariants: [] +) -%} + +
  • + {% block content %}{% endblock %} +
  • diff --git a/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbLink.html.twig b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbLink.html.twig new file mode 100644 index 0000000000..1a61473114 --- /dev/null +++ b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbLink.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'transition-colors hover:text-foreground', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbList.html.twig b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbList.html.twig new file mode 100644 index 0000000000..9d3e0809ee --- /dev/null +++ b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbList.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5', + variants: {}, + compoundVariants: [] +) -%} + +
      + {% block content %}{% endblock %} +
    diff --git a/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbPage.html.twig b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbPage.html.twig new file mode 100644 index 0000000000..760fdfdb5e --- /dev/null +++ b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbPage.html.twig @@ -0,0 +1,18 @@ +{%- props + disabled = false +-%} +{%- set style = html_cva( + base: 'font-normal text-foreground', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbSeparator.html.twig b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbSeparator.html.twig new file mode 100644 index 0000000000..989947184d --- /dev/null +++ b/src/Toolkit/templates/default/components/Breadcrumb/BreadcrumbSeparator.html.twig @@ -0,0 +1,20 @@ +{%- props -%} +{%- set style = html_cva( + base: '[&>svg]:w-3.5 [&>svg]:h-3.5', + variants: {}, + compoundVariants: [] +) -%} + + diff --git a/src/Toolkit/templates/default/examples/Breadcrumb.html.twig b/src/Toolkit/templates/default/examples/Breadcrumb.html.twig new file mode 100644 index 0000000000..09b15a355d --- /dev/null +++ b/src/Toolkit/templates/default/examples/Breadcrumb.html.twig @@ -0,0 +1,19 @@ + + + + Home + + + + Docs + + + + Components + + + + Breadcrumb + + + \ No newline at end of file From 8f4c54e773fbbe55b663d4f9f38c02212aa5214b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 7 Feb 2025 07:48:41 +0100 Subject: [PATCH 49/51] First step for documentation --- src/Toolkit/src/UxToolkitBundle.php | 5 + ux.symfony.com/composer.json | 4 +- ux.symfony.com/composer.lock | 576 +++++++++++++----- ux.symfony.com/config/bundles.php | 2 + .../cookbook/component_architecture.md | 6 +- .../UxPackage/ToolkitController.php | 33 + .../src/Service/UxPackageRepository.php | 10 + ux.symfony.com/symfony.lock | 9 + .../components/AspectRatio.html.twig | 16 + .../templates/components/Avatar.html.twig | 13 + .../components/AvatarFallback.html.twig | 13 + .../components/AvatarImage.html.twig | 11 + .../templates/components/Breadcrumb.html.twig | 13 + .../components/BreadcrumbEllipsis.html.twig | 20 + .../components/BreadcrumbItem.html.twig | 13 + .../components/BreadcrumbLink.html.twig | 13 + .../components/BreadcrumbList.html.twig | 13 + .../components/BreadcrumbPage.html.twig | 18 + .../components/BreadcrumbSeparator.html.twig | 20 + .../templates/components/Button.html.twig | 42 ++ .../templates/components/Card.html.twig | 46 +- .../components/CardContent.html.twig | 13 + .../components/CardDescription.html.twig | 13 + .../templates/components/CardFooter.html.twig | 13 + .../templates/components/CardHeader.html.twig | 13 + .../templates/components/CardTitle.html.twig | 13 + .../components/CookbookCard.html.twig | 33 + .../templates/cookbook/index.html.twig | 2 +- .../templates/ux_packages/toolkit.html.twig | 135 ++++ 29 files changed, 931 insertions(+), 200 deletions(-) create mode 100644 ux.symfony.com/src/Controller/UxPackage/ToolkitController.php create mode 100644 ux.symfony.com/templates/components/AspectRatio.html.twig create mode 100644 ux.symfony.com/templates/components/Avatar.html.twig create mode 100644 ux.symfony.com/templates/components/AvatarFallback.html.twig create mode 100644 ux.symfony.com/templates/components/AvatarImage.html.twig create mode 100644 ux.symfony.com/templates/components/Breadcrumb.html.twig create mode 100644 ux.symfony.com/templates/components/BreadcrumbEllipsis.html.twig create mode 100644 ux.symfony.com/templates/components/BreadcrumbItem.html.twig create mode 100644 ux.symfony.com/templates/components/BreadcrumbLink.html.twig create mode 100644 ux.symfony.com/templates/components/BreadcrumbList.html.twig create mode 100644 ux.symfony.com/templates/components/BreadcrumbPage.html.twig create mode 100644 ux.symfony.com/templates/components/BreadcrumbSeparator.html.twig create mode 100644 ux.symfony.com/templates/components/Button.html.twig create mode 100644 ux.symfony.com/templates/components/CardContent.html.twig create mode 100644 ux.symfony.com/templates/components/CardDescription.html.twig create mode 100644 ux.symfony.com/templates/components/CardFooter.html.twig create mode 100644 ux.symfony.com/templates/components/CardHeader.html.twig create mode 100644 ux.symfony.com/templates/components/CardTitle.html.twig create mode 100644 ux.symfony.com/templates/components/CookbookCard.html.twig create mode 100644 ux.symfony.com/templates/ux_packages/toolkit.html.twig diff --git a/src/Toolkit/src/UxToolkitBundle.php b/src/Toolkit/src/UxToolkitBundle.php index eae7236568..98aea74d11 100644 --- a/src/Toolkit/src/UxToolkitBundle.php +++ b/src/Toolkit/src/UxToolkitBundle.php @@ -26,6 +26,7 @@ use Symfony\UX\Toolkit\ComponentRepository\RepositoryIdentifier; use Symfony\UX\Toolkit\DependencyInjection\ToolkitExtension; use Symfony\UX\Toolkit\Registry\DependenciesResolver; +use Symfony\UX\Toolkit\Registry\Registry; use Symfony\UX\Toolkit\Registry\RegistryFactory; /** @@ -50,6 +51,7 @@ public function build(ContainerBuilder $container): void $container->autowire(RegistryFactory::class); $container->autowire(DependenciesResolver::class); $container->autowire(RegistryCompiler::class); + $container->autowire(Registry::class); $container->autowire(TwigComponentCompiler::class); $container->getDefinition(TwigComponentCompiler::class) @@ -74,6 +76,9 @@ public function build(ContainerBuilder $container): void ->setArguments([ '$theme' => '%ux_toolkit.theme%', ]); + + // Make registry public (useful for exposing documentation) + $container->getDefinition(Registry::class)->setPublic(true); } /** diff --git a/ux.symfony.com/composer.json b/ux.symfony.com/composer.json index effedd6792..978ff2b421 100644 --- a/ux.symfony.com/composer.json +++ b/ux.symfony.com/composer.json @@ -26,7 +26,7 @@ "symfony/mercure-bundle": "^0.3.9", "symfony/monolog-bundle": "^3.10", "symfony/notifier": "7.2.*", - "symfony/runtime": "7.2.*", + "symfony/runtime": "^7.2", "symfony/serializer": "7.2.*", "symfony/stimulus-bundle": "2.x-dev", "symfony/translation": "7.2.*", @@ -46,6 +46,7 @@ "symfony/ux-svelte": "2.x-dev", "symfony/ux-swup": "2.x-dev", "symfony/ux-toggle-password": "2.x-dev", + "symfony/ux-toolkit": "2.x-dev", "symfony/ux-translator": "2.x-dev", "symfony/ux-turbo": "2.x-dev", "symfony/ux-twig-component": "2.x-dev", @@ -55,6 +56,7 @@ "symfony/yaml": "7.2.*", "symfonycasts/dynamic-forms": "^0.1.2", "symfonycasts/sass-bundle": "0.8.*", + "tales-from-a-dev/twig-tailwind-extra": "^0.3.0", "tempest/highlight": "^2.11.2", "twbs/bootstrap": "^5.3.3", "twig/extra-bundle": "^3.17", diff --git a/ux.symfony.com/composer.lock b/ux.symfony.com/composer.lock index ee947d5bfb..219a71431b 100644 --- a/ux.symfony.com/composer.lock +++ b/ux.symfony.com/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7a653290bf7adb58131f9455ff7b243e", + "content-hash": "b8787db30eccf7944de0d8e856d7c877", "packages": [ { "name": "composer/semver", @@ -1489,6 +1489,73 @@ }, "time": "2024-10-21T18:21:57+00:00" }, + { + "name": "gehrisandro/tailwind-merge-php", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/gehrisandro/tailwind-merge-php.git", + "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gehrisandro/tailwind-merge-php/zipball/dc11b9d4a625dd5be885900e5ef14c3efa260277", + "reference": "dc11b9d4a625dd5be885900e5ef14c3efa260277", + "shasum": "" + }, + "require": { + "php": "^8.1.0", + "psr/simple-cache": "^3.0" + }, + "require-dev": { + "laravel/pint": "^1.13.8", + "nunomaduro/collision": "^7.10", + "pestphp/pest": "^v2.24.0", + "pestphp/pest-plugin-arch": "^2.6", + "pestphp/pest-plugin-mock": "^2.0.0", + "pestphp/pest-plugin-type-coverage": "^2.8", + "phpstan/phpstan": "^1.10.55", + "rector/rector": "^1.0.5", + "symfony/var-dumper": "^6.4.2" + }, + "type": "library", + "autoload": { + "files": [ + "src/TailwindMerge.php" + ], + "psr-4": { + "TailwindMerge\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "TailwindMerge for PHP merges multiple Tailwind CSS classes by automatically resolving conflicts between them", + "keywords": [ + "classes", + "merge", + "php", + "tailwindcss" + ], + "support": { + "issues": "https://github.com/gehrisandro/tailwind-merge-php/issues", + "source": "https://github.com/gehrisandro/tailwind-merge-php/tree/v1.1.2" + }, + "funding": [ + { + "url": "https://github.com/gehrisandro", + "type": "github" + } + ], + "time": "2024-05-21T17:32:42+00:00" + }, { "name": "guzzlehttp/psr7", "version": "2.7.0", @@ -2664,6 +2731,57 @@ }, "time": "2024-09-11T13:17:53+00:00" }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, { "name": "ralouphie/getallheaders", "version": "3.0.3", @@ -2858,16 +2976,16 @@ }, { "name": "symfony/cache", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "2c926bc348184b4b235f2200fcbe8fcf3c8c5b8a" + "reference": "8d773a575e446de220dca03d600b2d8e1c1c10ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/2c926bc348184b4b235f2200fcbe8fcf3c8c5b8a", - "reference": "2c926bc348184b4b235f2200fcbe8fcf3c8c5b8a", + "url": "https://api.github.com/repos/symfony/cache/zipball/8d773a575e446de220dca03d600b2d8e1c1c10ec", + "reference": "8d773a575e446de220dca03d600b2d8e1c1c10ec", "shasum": "" }, "require": { @@ -2936,7 +3054,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.2.0" + "source": "https://github.com/symfony/cache/tree/v7.2.3" }, "funding": [ { @@ -2952,7 +3070,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T15:21:05+00:00" + "time": "2025-01-27T11:08:17+00:00" }, { "name": "symfony/cache-contracts", @@ -2974,12 +3092,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3032,16 +3150,16 @@ }, { "name": "symfony/config", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "bcd3c4adf0144dee5011bb35454728c38adec055" + "reference": "7716594aaae91d9141be080240172a92ecca4d44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/bcd3c4adf0144dee5011bb35454728c38adec055", - "reference": "bcd3c4adf0144dee5011bb35454728c38adec055", + "url": "https://api.github.com/repos/symfony/config/zipball/7716594aaae91d9141be080240172a92ecca4d44", + "reference": "7716594aaae91d9141be080240172a92ecca4d44", "shasum": "" }, "require": { @@ -3087,7 +3205,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.2.0" + "source": "https://github.com/symfony/config/tree/v7.2.3" }, "funding": [ { @@ -3103,20 +3221,20 @@ "type": "tidelift" } ], - "time": "2024-11-04T11:36:24+00:00" + "time": "2025-01-22T12:07:01+00:00" }, { "name": "symfony/console", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -3180,7 +3298,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.0" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -3196,20 +3314,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "a475747af1a1c98272a5471abc35f3da81197c5d" + "reference": "1d321c4bc3fe926fd4c38999a4c9af4f5d61ddfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a475747af1a1c98272a5471abc35f3da81197c5d", - "reference": "a475747af1a1c98272a5471abc35f3da81197c5d", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/1d321c4bc3fe926fd4c38999a4c9af4f5d61ddfc", + "reference": "1d321c4bc3fe926fd4c38999a4c9af4f5d61ddfc", "shasum": "" }, "require": { @@ -3260,7 +3378,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.2.0" + "source": "https://github.com/symfony/dependency-injection/tree/v7.2.3" }, "funding": [ { @@ -3276,7 +3394,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T15:45:00+00:00" + "time": "2025-01-17T10:56:55+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3297,12 +3415,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3530,16 +3648,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "672b3dd1ef8b87119b446d67c58c106c43f965fe" + "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/672b3dd1ef8b87119b446d67c58c106c43f965fe", - "reference": "672b3dd1ef8b87119b446d67c58c106c43f965fe", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/959a74d044a6db21f4caa6d695648dcb5584cb49", + "reference": "959a74d044a6db21f4caa6d695648dcb5584cb49", "shasum": "" }, "require": { @@ -3585,7 +3703,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.0" + "source": "https://github.com/symfony/error-handler/tree/v7.2.3" }, "funding": [ { @@ -3601,7 +3719,7 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:35:02+00:00" + "time": "2025-01-07T09:39:55+00:00" }, { "name": "symfony/event-dispatcher", @@ -3703,12 +3821,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3891,16 +4009,16 @@ }, { "name": "symfony/finder", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", "shasum": "" }, "require": { @@ -3935,7 +4053,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.0" + "source": "https://github.com/symfony/finder/tree/v7.2.2" }, "funding": [ { @@ -3951,7 +4069,7 @@ "type": "tidelift" } ], - "time": "2024-10-23T06:56:12+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/flex", @@ -4120,16 +4238,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "a8d0da4110fe643ab3cde7c938a03e222fe787c6" + "reference": "d37a43dd0b2079605fcab3056dac71934f06dc0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a8d0da4110fe643ab3cde7c938a03e222fe787c6", - "reference": "a8d0da4110fe643ab3cde7c938a03e222fe787c6", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/d37a43dd0b2079605fcab3056dac71934f06dc0f", + "reference": "d37a43dd0b2079605fcab3056dac71934f06dc0f", "shasum": "" }, "require": { @@ -4250,7 +4368,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.2.0" + "source": "https://github.com/symfony/framework-bundle/tree/v7.2.3" }, "funding": [ { @@ -4266,7 +4384,7 @@ "type": "tidelift" } ], - "time": "2024-11-20T16:27:35+00:00" + "time": "2025-01-29T07:13:55+00:00" }, { "name": "symfony/http-client", @@ -4365,16 +4483,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v3.5.1", + "version": "v3.5.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9" + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c2f3ad828596624ca39ea40f83617ef51ca8bbf9", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", "shasum": "" }, "require": { @@ -4423,7 +4541,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" }, "funding": [ { @@ -4439,20 +4557,20 @@ "type": "tidelift" } ], - "time": "2024-11-25T12:02:18+00:00" + "time": "2024-12-07T08:49:48+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744" + "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e88a66c3997859532bc2ddd6dd8f35aba2711744", - "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee1b504b8926198be89d05e5b6fc4c3810c090f0", + "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0", "shasum": "" }, "require": { @@ -4501,7 +4619,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.3" }, "funding": [ { @@ -4517,20 +4635,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T18:58:46+00:00" + "time": "2025-01-17T10:56:55+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d" + "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d", - "reference": "6b4722a25e0aed1ccb4914b9bcbd493cc4676b4d", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", + "reference": "caae9807f8e25a9b43ce8cc6fafab6cf91f0cc9b", "shasum": "" }, "require": { @@ -4615,7 +4733,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.3" }, "funding": [ { @@ -4631,7 +4749,7 @@ "type": "tidelift" } ], - "time": "2024-11-29T08:42:40+00:00" + "time": "2025-01-29T07:40:13+00:00" }, { "name": "symfony/intl", @@ -4956,16 +5074,16 @@ }, { "name": "symfony/mime", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "cc84a4b81f62158c3846ac7ff10f696aae2b524d" + "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/cc84a4b81f62158c3846ac7ff10f696aae2b524d", - "reference": "cc84a4b81f62158c3846ac7ff10f696aae2b524d", + "url": "https://api.github.com/repos/symfony/mime/zipball/2fc3b4bd67e4747e45195bc4c98bea4628476204", + "reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204", "shasum": "" }, "require": { @@ -5020,7 +5138,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.0" + "source": "https://github.com/symfony/mime/tree/v7.2.3" }, "funding": [ { @@ -5036,7 +5154,7 @@ "type": "tidelift" } ], - "time": "2024-11-23T09:19:39+00:00" + "time": "2025-01-27T11:08:17+00:00" }, { "name": "symfony/monolog-bridge", @@ -5890,16 +6008,16 @@ }, { "name": "symfony/property-access", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "3ae42efba01e45aaedecf5c93c8d6a3ab3a82276" + "reference": "b28732e315d81fbec787f838034de7d6c9b2b902" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/3ae42efba01e45aaedecf5c93c8d6a3ab3a82276", - "reference": "3ae42efba01e45aaedecf5c93c8d6a3ab3a82276", + "url": "https://api.github.com/repos/symfony/property-access/zipball/b28732e315d81fbec787f838034de7d6c9b2b902", + "reference": "b28732e315d81fbec787f838034de7d6c9b2b902", "shasum": "" }, "require": { @@ -5946,7 +6064,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.2.0" + "source": "https://github.com/symfony/property-access/tree/v7.2.3" }, "funding": [ { @@ -5962,31 +6080,33 @@ "type": "tidelift" } ], - "time": "2024-09-26T12:28:35+00:00" + "time": "2025-01-17T10:56:55+00:00" }, { "name": "symfony/property-info", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "b00580d9d7c9654e1df95df85105d0da67418b3f" + "reference": "dedb118fd588a92f226b390250b384d25f4192fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/b00580d9d7c9654e1df95df85105d0da67418b3f", - "reference": "b00580d9d7c9654e1df95df85105d0da67418b3f", + "url": "https://api.github.com/repos/symfony/property-info/zipball/dedb118fd588a92f226b390250b384d25f4192fe", + "reference": "dedb118fd588a92f226b390250b384d25f4192fe", "shasum": "" }, "require": { "php": ">=8.2", "symfony/string": "^6.4|^7.0", - "symfony/type-info": "^7.1" + "symfony/type-info": "~7.1.9|^7.2.2" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/dependency-injection": "<6.4" + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" }, "require-dev": { "phpdocumentor/reflection-docblock": "^5.2", @@ -6029,7 +6149,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.2.0" + "source": "https://github.com/symfony/property-info/tree/v7.2.3" }, "funding": [ { @@ -6045,20 +6165,20 @@ "type": "tidelift" } ], - "time": "2024-11-27T09:50:52+00:00" + "time": "2025-01-27T11:08:17+00:00" }, { "name": "symfony/routing", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e" + "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e10a2450fa957af6c448b9b93c9010a4e4c0725e", - "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e", + "url": "https://api.github.com/repos/symfony/routing/zipball/ee9a67edc6baa33e5fae662f94f91fd262930996", + "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996", "shasum": "" }, "require": { @@ -6110,7 +6230,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.2.0" + "source": "https://github.com/symfony/routing/tree/v7.2.3" }, "funding": [ { @@ -6126,20 +6246,20 @@ "type": "tidelift" } ], - "time": "2024-11-25T11:08:51+00:00" + "time": "2025-01-17T10:56:55+00:00" }, { "name": "symfony/runtime", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "2c350568f3eaccb25fbbbf962bd67cde273121a7" + "reference": "8e8d09bd69b7f6c0260dd3d58f37bd4fbdeab5ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/2c350568f3eaccb25fbbbf962bd67cde273121a7", - "reference": "2c350568f3eaccb25fbbbf962bd67cde273121a7", + "url": "https://api.github.com/repos/symfony/runtime/zipball/8e8d09bd69b7f6c0260dd3d58f37bd4fbdeab5ad", + "reference": "8e8d09bd69b7f6c0260dd3d58f37bd4fbdeab5ad", "shasum": "" }, "require": { @@ -6189,7 +6309,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v7.2.0" + "source": "https://github.com/symfony/runtime/tree/v7.2.3" }, "funding": [ { @@ -6205,20 +6325,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T11:43:25+00:00" + "time": "2024-12-29T21:39:47+00:00" }, { "name": "symfony/serializer", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0" + "reference": "320f30beb419ce4f96363ada5e225c41f1ef08ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0", - "reference": "3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0", + "url": "https://api.github.com/repos/symfony/serializer/zipball/320f30beb419ce4f96363ada5e225c41f1ef08ab", + "reference": "320f30beb419ce4f96363ada5e225c41f1ef08ab", "shasum": "" }, "require": { @@ -6287,7 +6407,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.2.0" + "source": "https://github.com/symfony/serializer/tree/v7.2.3" }, "funding": [ { @@ -6303,7 +6423,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T15:21:05+00:00" + "time": "2025-01-29T07:13:55+00:00" }, { "name": "symfony/service-contracts", @@ -6329,12 +6449,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -6721,12 +6841,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -6782,16 +6902,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "9958f5a5b6640734fe4b24c18897191f77a02c61" + "reference": "29e4c66de9618e67dc1f5f13bc667aca2a228f1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9958f5a5b6640734fe4b24c18897191f77a02c61", - "reference": "9958f5a5b6640734fe4b24c18897191f77a02c61", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/29e4c66de9618e67dc1f5f13bc667aca2a228f1e", + "reference": "29e4c66de9618e67dc1f5f13bc667aca2a228f1e", "shasum": "" }, "require": { @@ -6872,7 +6992,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.2.0" + "source": "https://github.com/symfony/twig-bridge/tree/v7.2.2" }, "funding": [ { @@ -6888,7 +7008,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T14:26:33+00:00" + "time": "2024-12-19T14:25:03+00:00" }, { "name": "symfony/twig-bundle", @@ -6976,29 +7096,24 @@ }, { "name": "symfony/type-info", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "e0bfd95bceb3886c59487828537691aecb7d9c6b" + "reference": "3b5a17470fff0034f25fd4287cbdaa0010d2f749" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/e0bfd95bceb3886c59487828537691aecb7d9c6b", - "reference": "e0bfd95bceb3886c59487828537691aecb7d9c6b", + "url": "https://api.github.com/repos/symfony/type-info/zipball/3b5a17470fff0034f25fd4287cbdaa0010d2f749", + "reference": "3b5a17470fff0034f25fd4287cbdaa0010d2f749", "shasum": "" }, "require": { "php": ">=8.2", "psr/container": "^1.1|^2.0" }, - "conflict": { - "phpstan/phpdoc-parser": "<1.0", - "symfony/dependency-injection": "<6.4" - }, "require-dev": { - "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/dependency-injection": "^6.4|^7.0" + "phpstan/phpdoc-parser": "^1.0|^2.0" }, "type": "library", "autoload": { @@ -7036,7 +7151,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.2.0" + "source": "https://github.com/symfony/type-info/tree/v7.2.2" }, "funding": [ { @@ -7052,7 +7167,7 @@ "type": "tidelift" } ], - "time": "2024-11-18T09:51:31+00:00" + "time": "2024-12-20T13:38:37+00:00" }, { "name": "symfony/uid", @@ -8274,6 +8389,87 @@ ], "time": "2024-12-05T16:05:57+00:00" }, + { + "name": "symfony/ux-toolkit", + "version": "2.x-dev", + "dist": { + "type": "path", + "url": "../src/Toolkit", + "reference": "f0345541d15b0781efce35d5610f3d22622d0a76" + }, + "require": { + "php": ">=8.3", + "symfony/console": "^7.2", + "symfony/filesystem": "^7.2", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/ux-twig-component": "^2.22", + "twig/extra-bundle": "^3.19|^4.0", + "twig/html-extra": "^3.19", + "twig/twig": "^2.12|^3.0" + }, + "conflict": { + "symfony/ux-twig-component": "<2.21" + }, + "require-dev": { + "symfony/finder": "6.4|^7.0", + "symfony/http-client": "6.4|^7.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symfony/stopwatch": "^7.2", + "tales-from-a-dev/twig-tailwind-extra": "^0.3.0", + "zenstruck/console-test": "^1.7" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "name": "symfony/ux", + "url": "https://github.com/symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\Toolkit\\": "src" + }, + "exclude-from-classmap": [] + }, + "autoload-dev": { + "psr-4": { + "Symfony\\UX\\Toolkit\\Tests\\": "tests/" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Hugo Alliaume", + "email": "hugo@alliau.me" + }, + { + "name": "Jean-François Lépine", + "email": "lepinejeanfrancois@gmail.com" + }, + { + "name": "Simon André", + "email": "smn.andre@gmail.com" + } + ], + "description": "Twig Toolkit for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "components", + "symfony-ux", + "twig" + ], + "transport-options": { + "symlink": false, + "relative": true + } + }, { "name": "symfony/ux-translator", "version": "2.x-dev", @@ -8456,12 +8652,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/ux-twig-component.git", - "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18" + "reference": "f29033b95e93aea2d498dc40eac185ed14b07800" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/9b347f6ca2d9e18cee630787f0a6aa453982bf18", - "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/f29033b95e93aea2d498dc40eac185ed14b07800", + "reference": "f29033b95e93aea2d498dc40eac185ed14b07800", "shasum": "" }, "require": { @@ -8516,7 +8712,7 @@ "twig" ], "support": { - "source": "https://github.com/symfony/ux-twig-component/tree/v2.22.1" + "source": "https://github.com/symfony/ux-twig-component/tree/2.x" }, "funding": [ { @@ -8532,7 +8728,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T18:05:50+00:00" + "time": "2025-01-25T02:19:26+00:00" }, { "name": "symfony/ux-typed", @@ -8782,16 +8978,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.2.0", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c" + "reference": "82b478c69745d8878eb60f9a049a4d584996f73a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6a22929407dec8765d6e2b6ff85b800b245879c", - "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a", + "reference": "82b478c69745d8878eb60f9a049a4d584996f73a", "shasum": "" }, "require": { @@ -8845,7 +9041,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.2.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.3" }, "funding": [ { @@ -8861,7 +9057,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T15:48:14+00:00" + "time": "2025-01-17T11:39:41+00:00" }, { "name": "symfony/var-exporter", @@ -9203,6 +9399,61 @@ }, "time": "2024-10-22T16:58:17+00:00" }, + { + "name": "tales-from-a-dev/twig-tailwind-extra", + "version": "v0.3.0", + "source": { + "type": "git", + "url": "https://github.com/tales-from-a-dev/twig-tailwind-extra.git", + "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tales-from-a-dev/twig-tailwind-extra/zipball/a3cb86414dd5810740cf91966bc1cf10047ce8ef", + "reference": "a3cb86414dd5810740cf91966bc1cf10047ce8ef", + "shasum": "" + }, + "require": { + "gehrisandro/tailwind-merge-php": "^1.0", + "php": ">=8.2", + "symfony/cache": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", + "twig/twig": "^3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.38", + "symfony/phpunit-bridge": "^6.4 || ^7.0" + }, + "type": "twig", + "autoload": { + "psr-4": { + "TalesFromADev\\Twig\\Extra\\Tailwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Monteil", + "email": "monteil.romain@gmail.com" + } + ], + "description": "A Twig extension for Tailwind", + "homepage": "https://github.com/tales-from-a-dev/twig-tailwind-extra", + "keywords": [ + "extension", + "symfony", + "tailwind", + "twig" + ], + "support": { + "issues": "https://github.com/tales-from-a-dev/twig-tailwind-extra/issues", + "source": "https://github.com/tales-from-a-dev/twig-tailwind-extra/tree/v0.3.0" + }, + "time": "2024-08-07T23:27:08+00:00" + }, { "name": "tempest/highlight", "version": "2.11.2", @@ -9313,7 +9564,7 @@ }, { "name": "twig/extra-bundle", - "version": "v3.17.0", + "version": "v3.19.0", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", @@ -9371,7 +9622,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.17.0" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.19.0" }, "funding": [ { @@ -9387,16 +9638,16 @@ }, { "name": "twig/html-extra", - "version": "v3.17.0", + "version": "v3.19.0", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "2086023d3ffc4bae2b1115f715d17f97fd013665" + "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/2086023d3ffc4bae2b1115f715d17f97fd013665", - "reference": "2086023d3ffc4bae2b1115f715d17f97fd013665", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/c63b28e192c1b7c15bb60f81d2e48b140846239a", + "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a", "shasum": "" }, "require": { @@ -9439,7 +9690,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.17.0" + "source": "https://github.com/twigphp/html-extra/tree/v3.19.0" }, "funding": [ { @@ -9451,7 +9702,7 @@ "type": "tidelift" } ], - "time": "2024-09-30T06:41:48+00:00" + "time": "2024-12-29T10:29:59+00:00" }, { "name": "twig/intl-extra", @@ -9658,16 +9909,16 @@ }, { "name": "twig/twig", - "version": "v3.17.0", + "version": "v3.19.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "d3a64b742a5e74c57e3964d766e1032982145872" + "reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/d3a64b742a5e74c57e3964d766e1032982145872", - "reference": "d3a64b742a5e74c57e3964d766e1032982145872", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/d4f8c2b86374f08efc859323dbcd95c590f7124e", + "reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e", "shasum": "" }, "require": { @@ -9722,7 +9973,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.17.0" + "source": "https://github.com/twigphp/Twig/tree/v3.19.0" }, "funding": [ { @@ -9734,7 +9985,7 @@ "type": "tidelift" } ], - "time": "2024-12-10T15:19:11+00:00" + "time": "2025-01-29T07:06:14+00:00" } ], "packages-dev": [ @@ -12582,6 +12833,7 @@ "symfony/ux-svelte": 20, "symfony/ux-swup": 20, "symfony/ux-toggle-password": 20, + "symfony/ux-toolkit": 20, "symfony/ux-translator": 20, "symfony/ux-turbo": 20, "symfony/ux-twig-component": 20, diff --git a/ux.symfony.com/config/bundles.php b/ux.symfony.com/config/bundles.php index c9bb8d7be9..b277f4adc3 100644 --- a/ux.symfony.com/config/bundles.php +++ b/ux.symfony.com/config/bundles.php @@ -32,4 +32,6 @@ Symfonycasts\SassBundle\SymfonycastsSassBundle::class => ['all' => true], Symfony\UX\Icons\UXIconsBundle::class => ['all' => true], Symfony\UX\Map\UXMapBundle::class => ['all' => true], + Symfony\UX\Toolkit\UxToolkitBundle::class => ['all' => true], + TalesFromADev\Twig\Extra\Tailwind\Bridge\Symfony\Bundle\TalesFromADevTwigExtraTailwindBundle::class => ['all' => true], ]; diff --git a/ux.symfony.com/cookbook/component_architecture.md b/ux.symfony.com/cookbook/component_architecture.md index d178c87d4a..dd03081f09 100644 --- a/ux.symfony.com/cookbook/component_architecture.md +++ b/ux.symfony.com/cookbook/component_architecture.md @@ -42,15 +42,15 @@ So here you can see we have an `Alert` component that itself uses an Icon compon Or you can compose with the following syntax: ```twig - + - + ``` -So here we have a `Card` component, and we provide the content of this component with two other components. +So here we have a `Alert` component, and we provide the content of this component with two other components. ### Independence diff --git a/ux.symfony.com/src/Controller/UxPackage/ToolkitController.php b/ux.symfony.com/src/Controller/UxPackage/ToolkitController.php new file mode 100644 index 0000000000..b6daf796e1 --- /dev/null +++ b/ux.symfony.com/src/Controller/UxPackage/ToolkitController.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Controller\UxPackage; + +use App\Service\LiveDemoRepository; +use App\Service\UxPackageRepository; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\UX\Toolkit\Registry\Registry; + +class ToolkitController extends AbstractController +{ + #[Route('/toolkit', name: 'app_toolkit')] + public function __invoke(UxPackageRepository $packageRepository, Registry $registry): Response + { + $package = $packageRepository->find('toolkit'); + return $this->render('ux_packages/toolkit.html.twig', [ + 'components' => $registry->all(), + 'package' => $package, + ]); + } + +} diff --git a/ux.symfony.com/src/Service/UxPackageRepository.php b/ux.symfony.com/src/Service/UxPackageRepository.php index 0810e0e8de..56d13b24c7 100644 --- a/ux.symfony.com/src/Service/UxPackageRepository.php +++ b/ux.symfony.com/src/Service/UxPackageRepository.php @@ -231,6 +231,16 @@ public function findAll(?string $query = null): array 'Switch the visibility of a password field', ), + new UxPackage( + 'toolkit', + 'Toolkit', + 'app_toolkit', + '#BE0404', + 'linear-gradient(142deg,rgb(60, 230, 236) -15%,rgb(77, 97, 214) 95%)', + 'Build your Design System.', + 'Collection of components and templates that you can use to build your pages.', + ), + (new UxPackage( 'typed', 'Typed', diff --git a/ux.symfony.com/symfony.lock b/ux.symfony.com/symfony.lock index 98914f5767..54aa7cd1d5 100644 --- a/ux.symfony.com/symfony.lock +++ b/ux.symfony.com/symfony.lock @@ -678,6 +678,15 @@ "symfonycasts/sass-bundle": { "version": "v0.1.0" }, + "tales-from-a-dev/twig-tailwind-extra": { + "version": "0.3", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "0.2", + "ref": "7243ab070ed66198eb82c026684e9b9773e7b64a" + } + }, "theseer/tokenizer": { "version": "1.2.1" }, diff --git a/ux.symfony.com/templates/components/AspectRatio.html.twig b/ux.symfony.com/templates/components/AspectRatio.html.twig new file mode 100644 index 0000000000..0f656abf85 --- /dev/null +++ b/ux.symfony.com/templates/components/AspectRatio.html.twig @@ -0,0 +1,16 @@ +{%- props ratio = (4/3) -%} +{%- set style = html_cva( + base: 'rounded-xl border bg-card text-card-foreground shadow', + variants: {}, + compoundVariants: [] +) -%} + + +
    +
    + {% block content %}Button{% endblock %} +
    +
    diff --git a/ux.symfony.com/templates/components/Avatar.html.twig b/ux.symfony.com/templates/components/Avatar.html.twig new file mode 100644 index 0000000000..f7d1272ad5 --- /dev/null +++ b/ux.symfony.com/templates/components/Avatar.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/ux.symfony.com/templates/components/AvatarFallback.html.twig b/ux.symfony.com/templates/components/AvatarFallback.html.twig new file mode 100644 index 0000000000..2b02b8ef91 --- /dev/null +++ b/ux.symfony.com/templates/components/AvatarFallback.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'flex h-full w-full items-center justify-center rounded-full bg-muted', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/ux.symfony.com/templates/components/AvatarImage.html.twig b/ux.symfony.com/templates/components/AvatarImage.html.twig new file mode 100644 index 0000000000..7d1f9fc99f --- /dev/null +++ b/ux.symfony.com/templates/components/AvatarImage.html.twig @@ -0,0 +1,11 @@ +{%- props -%} +{%- set style = html_cva( + base: 'aspect-square h-full w-full', + variants: {}, + compoundVariants: [] +) -%} + + \ No newline at end of file diff --git a/ux.symfony.com/templates/components/Breadcrumb.html.twig b/ux.symfony.com/templates/components/Breadcrumb.html.twig new file mode 100644 index 0000000000..c43950b7f6 --- /dev/null +++ b/ux.symfony.com/templates/components/Breadcrumb.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: '', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/ux.symfony.com/templates/components/BreadcrumbEllipsis.html.twig b/ux.symfony.com/templates/components/BreadcrumbEllipsis.html.twig new file mode 100644 index 0000000000..caf8063029 --- /dev/null +++ b/ux.symfony.com/templates/components/BreadcrumbEllipsis.html.twig @@ -0,0 +1,20 @@ +{%- props -%} +{%- set style = html_cva( + base: 'flex h-9 w-9 items-center justify-center', + variants: {}, + compoundVariants: [] +) -%} + + diff --git a/ux.symfony.com/templates/components/BreadcrumbItem.html.twig b/ux.symfony.com/templates/components/BreadcrumbItem.html.twig new file mode 100644 index 0000000000..33d43018ae --- /dev/null +++ b/ux.symfony.com/templates/components/BreadcrumbItem.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'inline-flex items-center gap-1.5', + variants: {}, + compoundVariants: [] +) -%} + +
  • + {% block content %}{% endblock %} +
  • diff --git a/ux.symfony.com/templates/components/BreadcrumbLink.html.twig b/ux.symfony.com/templates/components/BreadcrumbLink.html.twig new file mode 100644 index 0000000000..1a61473114 --- /dev/null +++ b/ux.symfony.com/templates/components/BreadcrumbLink.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'transition-colors hover:text-foreground', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/ux.symfony.com/templates/components/BreadcrumbList.html.twig b/ux.symfony.com/templates/components/BreadcrumbList.html.twig new file mode 100644 index 0000000000..9d3e0809ee --- /dev/null +++ b/ux.symfony.com/templates/components/BreadcrumbList.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5', + variants: {}, + compoundVariants: [] +) -%} + +
      + {% block content %}{% endblock %} +
    diff --git a/ux.symfony.com/templates/components/BreadcrumbPage.html.twig b/ux.symfony.com/templates/components/BreadcrumbPage.html.twig new file mode 100644 index 0000000000..760fdfdb5e --- /dev/null +++ b/ux.symfony.com/templates/components/BreadcrumbPage.html.twig @@ -0,0 +1,18 @@ +{%- props + disabled = false +-%} +{%- set style = html_cva( + base: 'font-normal text-foreground', + variants: {}, + compoundVariants: [] +) -%} + + + {% block content %}{% endblock %} + diff --git a/ux.symfony.com/templates/components/BreadcrumbSeparator.html.twig b/ux.symfony.com/templates/components/BreadcrumbSeparator.html.twig new file mode 100644 index 0000000000..989947184d --- /dev/null +++ b/ux.symfony.com/templates/components/BreadcrumbSeparator.html.twig @@ -0,0 +1,20 @@ +{%- props -%} +{%- set style = html_cva( + base: '[&>svg]:w-3.5 [&>svg]:h-3.5', + variants: {}, + compoundVariants: [] +) -%} + + diff --git a/ux.symfony.com/templates/components/Button.html.twig b/ux.symfony.com/templates/components/Button.html.twig new file mode 100644 index 0000000000..ccbb6e9fc9 --- /dev/null +++ b/ux.symfony.com/templates/components/Button.html.twig @@ -0,0 +1,42 @@ +{%- props variant = 'default', outline = false, size = 'default' -%} +{%- set style = html_cva( + base: '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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + outline: { + true: "text-foreground bg-white", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + compoundVariants: [{ + variant: ['default'], + outline: ['true'], + class: 'border-primary', + }, { + variant: ['secondary'], + outline: ['true'], + class: 'border-secondary', + }, { + variant: ['destructive'], + outline: ['true'], + class: 'border-destructive', + },] +) -%} + + diff --git a/ux.symfony.com/templates/components/Card.html.twig b/ux.symfony.com/templates/components/Card.html.twig index 72474c38bd..d59ae42996 100644 --- a/ux.symfony.com/templates/components/Card.html.twig +++ b/ux.symfony.com/templates/components/Card.html.twig @@ -1,33 +1,13 @@ -{% props name, image, url, description, tags, lazyload = true %} - -
    - -
    - {{ name }} -
    - -
    -

    - - {{- name -}} - -

    -

    - {{- description -}} -

    -

    - {% for tag in tags %} - {{ tag }} - {% endfor %} -

    -
    - -
    +{%- props -%} +{%- set style = html_cva( + base: 'rounded-xl border bg-card text-card-foreground shadow', + variants: {}, + compoundVariants: [] +) -%} + +
    + {% block content %}{% endblock %} +
    diff --git a/ux.symfony.com/templates/components/CardContent.html.twig b/ux.symfony.com/templates/components/CardContent.html.twig new file mode 100644 index 0000000000..c54f24197d --- /dev/null +++ b/ux.symfony.com/templates/components/CardContent.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'p-6 pt-0', + variants: {}, + compoundVariants: [] +) -%} + +
    + {% block content %}{% endblock %} +
    diff --git a/ux.symfony.com/templates/components/CardDescription.html.twig b/ux.symfony.com/templates/components/CardDescription.html.twig new file mode 100644 index 0000000000..cdd70d9422 --- /dev/null +++ b/ux.symfony.com/templates/components/CardDescription.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'text-sm text-muted-foreground', + variants: {}, + compoundVariants: [] +) -%} + +
    + {% block content %}{% endblock %} +
    diff --git a/ux.symfony.com/templates/components/CardFooter.html.twig b/ux.symfony.com/templates/components/CardFooter.html.twig new file mode 100644 index 0000000000..1325d35311 --- /dev/null +++ b/ux.symfony.com/templates/components/CardFooter.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'flex items-center p-6 pt-0', + variants: {}, + compoundVariants: [] +) -%} + +
    + {% block content %}{% endblock %} +
    diff --git a/ux.symfony.com/templates/components/CardHeader.html.twig b/ux.symfony.com/templates/components/CardHeader.html.twig new file mode 100644 index 0000000000..fb96700289 --- /dev/null +++ b/ux.symfony.com/templates/components/CardHeader.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'flex flex-col space-y-1.5 p-6', + variants: {}, + compoundVariants: [] +) -%} + +
    + {% block content %}{% endblock %} +
    diff --git a/ux.symfony.com/templates/components/CardTitle.html.twig b/ux.symfony.com/templates/components/CardTitle.html.twig new file mode 100644 index 0000000000..c6decf07ea --- /dev/null +++ b/ux.symfony.com/templates/components/CardTitle.html.twig @@ -0,0 +1,13 @@ +{%- props -%} +{%- set style = html_cva( + base: 'font-semibold leading-none tracking-tight', + variants: {}, + compoundVariants: [] +) -%} + +
    + {% block content %}{% endblock %} +
    diff --git a/ux.symfony.com/templates/components/CookbookCard.html.twig b/ux.symfony.com/templates/components/CookbookCard.html.twig new file mode 100644 index 0000000000..72474c38bd --- /dev/null +++ b/ux.symfony.com/templates/components/CookbookCard.html.twig @@ -0,0 +1,33 @@ +{% props name, image, url, description, tags, lazyload = true %} + +
    + +
    + {{ name }} +
    + +
    +

    + + {{- name -}} + +

    +

    + {{- description -}} +

    +

    + {% for tag in tags %} + {{ tag }} + {% endfor %} +

    +
    + +
    diff --git a/ux.symfony.com/templates/cookbook/index.html.twig b/ux.symfony.com/templates/cookbook/index.html.twig index a15a44139c..e5f745b194 100644 --- a/ux.symfony.com/templates/cookbook/index.html.twig +++ b/ux.symfony.com/templates/cookbook/index.html.twig @@ -23,7 +23,7 @@
    {% for cookbook in cookbooks %} - +
    + +
    +
    + {% block card_example %} + + + Symfony is cool + Symfony is a set of reusable PHP components... + + + ... and a PHP framework for web projects + + + Visit symfony + + + {% endblock %} +
    +
    + +
    +
    + +
    +
    + {% block breadcrumb_example %} + + + + Home + + + + + + + + Docs + + + + Components + + + + Breadcrumb + + + + {% endblock %} +
    + +
    +
    + +
    +
    + {% block avatar_example %} + + + CN + + + + CN + + {% endblock %} +
    +
    +{% endblock %} + +{% block javascripts %} +{{ parent() }} + +{% endblock %} + +{% block stylesheets %} +{# some tailwind classes are overrided by current theme #} + +{% endblock %} \ No newline at end of file From 5dd8607c92dc71191d685d1bd7743d67bdb83ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 7 Feb 2025 14:05:29 +0100 Subject: [PATCH 50/51] adds details about ux_toolkit configuration in documentation --- .../config/packages/ux_toolkit.yaml | 4 + .../ux_packages/_package_install.html.twig | 2 +- .../templates/ux_packages/toolkit.html.twig | 90 +++++++++++++++++-- 3 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 ux.symfony.com/config/packages/ux_toolkit.yaml diff --git a/ux.symfony.com/config/packages/ux_toolkit.yaml b/ux.symfony.com/config/packages/ux_toolkit.yaml new file mode 100644 index 0000000000..163295af88 --- /dev/null +++ b/ux.symfony.com/config/packages/ux_toolkit.yaml @@ -0,0 +1,4 @@ +when@dev: + ux_toolkit: + theme: default + prefix: null \ No newline at end of file diff --git a/ux.symfony.com/templates/ux_packages/_package_install.html.twig b/ux.symfony.com/templates/ux_packages/_package_install.html.twig index 0e8f36d6a6..4d3115f58c 100644 --- a/ux.symfony.com/templates/ux_packages/_package_install.html.twig +++ b/ux.symfony.com/templates/ux_packages/_package_install.html.twig @@ -4,7 +4,7 @@
    {% component Terminal with {bottomPadding: 20} %} {% block content %} - composer require {{ package.composerName }} + composer require {% if installOnlyInDevMode|default(false) == true%} --dev {% endif %}{{ package.composerName }} {% endblock %} {% endcomponent %}
    diff --git a/ux.symfony.com/templates/ux_packages/toolkit.html.twig b/ux.symfony.com/templates/ux_packages/toolkit.html.twig index 452552b949..521c955054 100644 --- a/ux.symfony.com/templates/ux_packages/toolkit.html.twig +++ b/ux.symfony.com/templates/ux_packages/toolkit.html.twig @@ -69,15 +69,15 @@ Home - + - + Docs - + Components @@ -102,18 +102,96 @@
    {% block avatar_example %} - - CN + + SF - CN + SF {% endblock %}
    {% endblock %} + +{% block package_install %} + {% set installOnlyInDevMode = true %} + {{ parent() }} +
    +

    + Then install your components with the following command: +

    + + {% component Terminal with {bottomPadding: 20} %} + {% block content %} + php bin/console ux:toolkit:install badge + php bin/console ux:toolkit:install button + php bin/console ux:toolkit:install card + php bin/console ux:toolkit:install breadcrumb + php bin/console ux:toolkit:install avatar + php bin/console ux:toolkit:install ... + {% endblock %} + {% endcomponent %} + +

    + Official components require tailwindcss to be installed in your project. Visit the Tailwindcss documentation for more information. + Components are based on shadcdn/ui design system. +

    + +

    + If you use component library using html_cva() or tailwind_merge() functions, remember to install them with: +

    + + {% component Terminal with {bottomPadding: 20} %} + {% block content %} + composer require --dev tales-from-a-dev/twig-tailwind-extra twig/extra-bundle + {% endblock %} + {% endcomponent %} + + +
    +
    +

    Philosophy

    +
    +
    +

    + Toolkit provides ready-to-use components, but freely customizable. +

    +

    + Official components are copied into your project in the templates/components folder, like classic TwigComponent. + Feel free to modify the code to suit your needs. +

    +
    +
    +

    + It is quite possible to install other component libraries, or even redistribute your own components + (for example, if you want to provide your internal Design System). +

    +

    + The choice of the component library is made in the configuration of your project: +

    + + + +

    + The theme key allows you to choose the component library to use, and can be any GitHub repository. +

    +
    +
    +
    +{% endblock %} + + {% block javascripts %} {{ parent() }} From 34d1aaada1d601eb7a582b3761711e5157fe3ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20L=C3=A9pine?= Date: Fri, 7 Feb 2025 14:22:53 +0100 Subject: [PATCH 51/51] documentation; note about how to find templates --- .../templates/ux_packages/toolkit.html.twig | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ux.symfony.com/templates/ux_packages/toolkit.html.twig b/ux.symfony.com/templates/ux_packages/toolkit.html.twig index 521c955054..683ed38560 100644 --- a/ux.symfony.com/templates/ux_packages/toolkit.html.twig +++ b/ux.symfony.com/templates/ux_packages/toolkit.html.twig @@ -135,8 +135,8 @@ {% endcomponent %}

    - Official components require tailwindcss to be installed in your project. Visit the Tailwindcss documentation for more information. - Components are based on shadcdn/ui design system. + Official components require tailwindcss to be installed in your project. Visit the Tailwind CSS documentation for more information. + Components are based on shadcdn/ui design system.

    @@ -187,6 +187,15 @@ The theme key allows you to choose the component library to use, and can be any GitHub repository.

    + +
    +

    + If you want to distribute your own theme, please + classify your repository with the ux-toolkit topic to be referenced easily. +

    +

    + Your repository will be automatically detected by the GitHub search engine. +

    {% endblock %}