diff --git a/.gitignore b/.gitignore index 2648e4a..da675e7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,11 @@ /.twig-cs-fixer.cache ###< vincentlanglet/twig-cs-fixer ### +###> symfony/asset-mapper ### +/public/assets/ +/assets/vendor/ +###< symfony/asset-mapper ### + ###> phpunit/phpunit ### /phpunit.xml /.phpunit.cache/ diff --git a/.markdownlintignore b/.markdownlintignore index d143ace..1de60dc 100644 --- a/.markdownlintignore +++ b/.markdownlintignore @@ -10,3 +10,5 @@ web/*.md web/core/ web/libraries/ web/*/contrib/ +# Symfony (cache + scratch dir, gitignored) +var/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 39bf787..c1783d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Dev dependencies for coding standards and composer normalization: `ergebnis/composer-normalize`, `friendsofphp/php-cs-fixer`, `vincentlanglet/twig-cs-fixer`. - Project README with local development instructions. +- Frontend tooling: Tailwind CSS (via `symfonycasts/tailwind-bundle`), + Symfony AssetMapper, and Stimulus (via `symfony/stimulus-bundle`). + Decision recorded in [ADR 002](docs/adr/002-frontend-tooling.md). +- Base Twig layout (`templates/base.html.twig`) and frontend asset + entrypoints (`assets/app.js`, `assets/styles/app.css`). - `LICENSE` file at repo root containing the full Mozilla Public License 2.0 text. - ADR `docs/adr/002-project-license-mpl-2.md` recording the MPL-2.0 license decision and its rationale. diff --git a/README.md b/README.md index ef63147..92e7c36 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,39 @@ task test task test-coverage ``` +## Frontend assets + +The project uses [Tailwind CSS](https://tailwindcss.com/) on top of +Symfony's [AssetMapper](https://symfony.com/doc/current/frontend/asset_mapper.html), +with [Stimulus](https://stimulus.hotwired.dev/) for behaviour. There is +no Node toolchain — the Tailwind binary is managed by +[`symfonycasts/tailwind-bundle`](https://github.com/SymfonyCasts/tailwind-bundle). +See [ADR 002](docs/adr/002-frontend-tooling.md) for the rationale. + +```sh +# One-time: download the Tailwind binary (also runs lazily on first build) +itkdev-docker-compose php bin/console tailwind:build + +# Build the compiled stylesheet +itkdev-docker-compose php bin/console tailwind:build + +# Watch source files and rebuild on change (development) +itkdev-docker-compose php bin/console tailwind:build --watch + +# Compile and version the full importmap + assets (production) +itkdev-docker-compose php bin/console asset-map:compile + +# Inspect what AssetMapper sees +itkdev-docker-compose php bin/console debug:asset-map +``` + +Source files live under [`assets/`](assets): + +- `assets/app.js` — JavaScript entrypoint, boots Stimulus. +- `assets/styles/app.css` — Tailwind entrypoint (`@import "tailwindcss";`). +- `assets/controllers/` — Stimulus controllers, auto-registered by + filename (`hello_controller.js` → `data-controller="hello"`). + For one-off commands without a dedicated task, fall back to the underlying tools, e.g. `docker compose --profile dev run --rm prettier ` or `itkdev-docker-compose `. diff --git a/Taskfile.yml b/Taskfile.yml index 6cadb0d..700ad33 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -112,6 +112,8 @@ tasks: - task: coding-standards-php-check - task: coding-standards-twig-check - task: coding-standards-yaml-check + - task: coding-standards-js-check + - task: coding-standards-css-check - task: coding-standards-markdown-check - task: coding-standards-composer-check silent: true @@ -122,6 +124,8 @@ tasks: - task: coding-standards-php-apply - task: coding-standards-twig-apply - task: coding-standards-yaml-apply + - task: coding-standards-js-apply + - task: coding-standards-css-apply - task: coding-standards-markdown-apply - task: coding-standards-composer-apply silent: true @@ -162,6 +166,30 @@ tasks: - task compose -- --profile dev run --rm prettier '**/*.{yml,yaml}' --write silent: true + coding-standards-js-check: + desc: 'Check JavaScript formatting (Prettier). Mirrors the JavaScript CI workflow.' + cmds: + - task compose -- --profile dev run --rm prettier 'assets/**/*.js' --check + silent: true + + coding-standards-js-apply: + desc: 'Apply JavaScript formatting (Prettier).' + cmds: + - task compose -- --profile dev run --rm prettier 'assets/**/*.js' --write + silent: true + + coding-standards-css-check: + desc: 'Check CSS/SCSS formatting (Prettier). Mirrors the Styles CI workflow.' + cmds: + - task compose -- --profile dev run --rm prettier 'assets/**/*.{css,scss}' --check + silent: true + + coding-standards-css-apply: + desc: 'Apply CSS/SCSS formatting (Prettier).' + cmds: + - task compose -- --profile dev run --rm prettier 'assets/**/*.{css,scss}' --write + silent: true + coding-standards-markdown-check: desc: 'Check Markdown (markdownlint).' cmds: diff --git a/assets/app.css b/assets/app.css deleted file mode 100644 index 426e8b2..0000000 --- a/assets/app.css +++ /dev/null @@ -1 +0,0 @@ -/* Placeholder stylesheet. Replace when a frontend stack is adopted. */ diff --git a/assets/app.js b/assets/app.js index ea7e37e..fe3d606 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1 +1,2 @@ -// Placeholder JavaScript entry. Replace when a frontend stack is adopted. +import "./stimulus_bootstrap.js"; +import "./stimulus_bootstrap.js"; diff --git a/assets/controllers.json b/assets/controllers.json new file mode 100644 index 0000000..a1c6e90 --- /dev/null +++ b/assets/controllers.json @@ -0,0 +1,4 @@ +{ + "controllers": [], + "entrypoints": [] +} diff --git a/assets/controllers/csrf_protection_controller.js b/assets/controllers/csrf_protection_controller.js new file mode 100644 index 0000000..3c806b9 --- /dev/null +++ b/assets/controllers/csrf_protection_controller.js @@ -0,0 +1,139 @@ +/* + * Stateless CSRF protection — client side. + * + * Installed by the `symfony/stimulus-bundle` Flex recipe (see + * https://github.com/symfony/recipes). Pairs with Symfony's + * `SameOriginCsrfTokenManager` server side: on form submit, this script + * writes a random token into a `__Host-` cookie and the form's hidden + * `_csrf_token` field, then the server checks that the two match + * (double-submit cookie pattern). Also forwards the token as a header + * for Turbo-driven submissions. + * + * Edits to this file are preserved across `composer install`; Flex only + * re-applies the recipe on `composer recipes:update` / `--force`, and + * will prompt before overwriting local changes. + */ + +const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/; +const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/; + +// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager +// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event +// and thus this event-listener will not be executed. +document.addEventListener( + "submit", + function (event) { + generateCsrfToken(event.target); + }, + true, +); + +// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie +// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked +document.addEventListener("turbo:submit-start", function (event) { + const h = generateCsrfHeaders(event.detail.formSubmission.formElement); + Object.keys(h).map(function (k) { + event.detail.formSubmission.fetchRequest.headers[k] = h[k]; + }); +}); + +// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted +document.addEventListener("turbo:submit-end", function (event) { + removeCsrfToken(event.detail.formSubmission.formElement); +}); + +export function generateCsrfToken(formElement) { + const csrfField = formElement.querySelector( + 'input[data-controller="csrf-protection"], input[name="_csrf_token"]', + ); + + if (!csrfField) { + return; + } + + let csrfCookie = csrfField.getAttribute( + "data-csrf-protection-cookie-value", + ); + let csrfToken = csrfField.value; + + if (!csrfCookie && nameCheck.test(csrfToken)) { + csrfField.setAttribute( + "data-csrf-protection-cookie-value", + (csrfCookie = csrfToken), + ); + csrfField.defaultValue = csrfToken = btoa( + String.fromCharCode.apply( + null, + (window.crypto || window.msCrypto).getRandomValues( + new Uint8Array(18), + ), + ), + ); + } + csrfField.dispatchEvent(new Event("change", { bubbles: true })); + + if (csrfCookie && tokenCheck.test(csrfToken)) { + const cookie = + csrfCookie + + "_" + + csrfToken + + "=" + + csrfCookie + + "; path=/; samesite=strict"; + document.cookie = + window.location.protocol === "https:" + ? "__Host-" + cookie + "; secure" + : cookie; + } +} + +export function generateCsrfHeaders(formElement) { + const headers = {}; + const csrfField = formElement.querySelector( + 'input[data-controller="csrf-protection"], input[name="_csrf_token"]', + ); + + if (!csrfField) { + return headers; + } + + const csrfCookie = csrfField.getAttribute( + "data-csrf-protection-cookie-value", + ); + + if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { + headers[csrfCookie] = csrfField.value; + } + + return headers; +} + +export function removeCsrfToken(formElement) { + const csrfField = formElement.querySelector( + 'input[data-controller="csrf-protection"], input[name="_csrf_token"]', + ); + + if (!csrfField) { + return; + } + + const csrfCookie = csrfField.getAttribute( + "data-csrf-protection-cookie-value", + ); + + if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { + const cookie = + csrfCookie + + "_" + + csrfField.value + + "=0; path=/; samesite=strict; max-age=0"; + + document.cookie = + window.location.protocol === "https:" + ? "__Host-" + cookie + "; secure" + : cookie; + } +} + +/* stimulusFetch: 'lazy' */ +export default "csrf-protection-controller"; diff --git a/assets/controllers/hello_controller.js b/assets/controllers/hello_controller.js new file mode 100644 index 0000000..6fc936c --- /dev/null +++ b/assets/controllers/hello_controller.js @@ -0,0 +1,17 @@ +import { Controller } from "@hotwired/stimulus"; + +/* + * This is an example Stimulus controller! + * + * Any element with a data-controller="hello" attribute will cause + * this controller to be executed. The name "hello" comes from the filename: + * hello_controller.js -> "hello" + * + * Delete this file or adapt it for your use! + */ +export default class extends Controller { + connect() { + this.element.textContent = + "Hello Stimulus! Edit me in assets/controllers/hello_controller.js"; + } +} diff --git a/assets/stimulus_bootstrap.js b/assets/stimulus_bootstrap.js new file mode 100644 index 0000000..8548354 --- /dev/null +++ b/assets/stimulus_bootstrap.js @@ -0,0 +1,8 @@ +import { startStimulusApp } from "@symfony/stimulus-bundle"; + +const app = startStimulusApp(); +import { startStimulusApp } from "@symfony/stimulus-bundle"; + +const app = startStimulusApp(); +// register any custom, 3rd party controllers here +// app.register('some_controller_name', SomeImportedController); diff --git a/assets/styles/app.css b/assets/styles/app.css new file mode 100644 index 0000000..f1d8c73 --- /dev/null +++ b/assets/styles/app.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/composer.json b/composer.json index d9449a8..4a69f18 100644 --- a/composer.json +++ b/composer.json @@ -7,16 +7,23 @@ "php": ">=8.4", "ext-ctype": "*", "ext-iconv": "*", + "symfony/asset": "^8.1", + "symfony/asset-mapper": "^8.1", "symfony/console": "~8.1.0", "symfony/dotenv": "~8.1.0", "symfony/flex": "^2", "symfony/framework-bundle": "~8.1.0", "symfony/runtime": "~8.1.0", - "symfony/yaml": "~8.1.0" + "symfony/stimulus-bundle": "^3.1", + "symfony/twig-bundle": "~8.1.0", + "symfony/yaml": "~8.1.0", + "symfonycasts/tailwind-bundle": "^0.13.0", + "twig/extra-bundle": "^2.12 || ^3.0", + "twig/twig": "^2.12 || ^3.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.52", - "friendsofphp/php-cs-fixer": "^3.95", + "friendsofphp/php-cs-fixer": "^3.95.5", "phpunit/phpunit": "^11.5", "rregeer/phpunit-coverage-check": "^0.3.1", "symfony/browser-kit": "~8.1.0", @@ -76,7 +83,8 @@ ], "auto-scripts": { "cache:clear": "symfony-cmd", - "assets:install %PUBLIC_DIR%": "symfony-cmd" + "assets:install %PUBLIC_DIR%": "symfony-cmd", + "importmap:install": "symfony-cmd" } } } diff --git a/composer.lock b/composer.lock index 151bb67..84fcd77 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,85 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2cee5bdbab8cb853de62d0437753bd74", + "content-hash": "21f6c085529afc7c14f91927af22ea88", "packages": [ + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -208,6 +285,157 @@ }, "time": "2024-09-11T13:17:53+00:00" }, + { + "name": "symfony/asset", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "4bd4d143b7e53f40d45877df52eb2b18282bdac4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/4bd4d143b7e53f40d45877df52eb2b18282bdac4", + "reference": "4bd4d143b7e53f40d45877df52eb2b18282bdac4", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "require-dev": { + "symfony/http-client": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Asset\\": "" + }, + "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": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/asset-mapper", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset-mapper.git", + "reference": "74b1b7b7019c728cb1f8672b502260e683b6374e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/74b1b7b7019c728cb1f8672b502260e683b6374e", + "reference": "74b1b7b7019c728cb1f8672b502260e683b6374e", + "shasum": "" + }, + "require": { + "composer/semver": "^3.0", + "php": ">=8.4.1", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0" + }, + "require-dev": { + "symfony/asset": "^7.4|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/event-dispatcher-contracts": "^3.0", + "symfony/finder": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\AssetMapper\\": "" + }, + "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 directories of assets & makes them available in a public directory with versioned filenames.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset-mapper/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, { "name": "symfony/cache", "version": "v8.1.0", @@ -1394,42 +1622,55 @@ "time": "2026-05-29T05:06:50+00:00" }, { - "name": "symfony/http-foundation", + "name": "symfony/http-client", "version": "v8.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "af11474600f06718086c2cda4fa6fa8d0a672e7e" + "url": "https://github.com/symfony/http-client.git", + "reference": "68a48e4c31f63fcd1bdff997a85a09e55efe8cdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/af11474600f06718086c2cda4fa6fa8d0a672e7e", - "reference": "af11474600f06718086c2cda4fa6fa8d0a672e7e", + "url": "https://api.github.com/repos/symfony/http-client/zipball/68a48e4c31f63fcd1bdff997a85a09e55efe8cdb", + "reference": "68a48e4c31f63fcd1bdff997a85a09e55efe8cdb", "shasum": "" }, "require": { "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "^1.1" + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/http-client-contracts": "^3.7", + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "doctrine/dbal": "<4.3" + "amphp/amp": "<3", + "php-http/discovery": "<1.15" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" }, "require-dev": { - "doctrine/dbal": "^4.3", - "predis/predis": "^1.1|^2.0", + "amphp/http-client": "^5.3.2", + "amphp/http-tunnel": "^2.0", + "guzzlehttp/guzzle": "^7.10", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", "symfony/cache": "^7.4|^8.0", - "symfony/clock": "^7.4|^8.0", "symfony/dependency-injection": "^7.4|^8.0", - "symfony/expression-language": "^7.4|^8.0", "symfony/http-kernel": "^7.4|^8.0", - "symfony/mime": "^7.4|^8.0", - "symfony/rate-limiter": "^7.4|^8.0" + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" + "Symfony\\Component\\HttpClient\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1441,18 +1682,21 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Defines an object-oriented layer for the HTTP specification", + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", + "keywords": [ + "http" + ], "support": { - "source": "https://github.com/symfony/http-foundation/tree/v8.1.0" + "source": "https://github.com/symfony/http-client/tree/v8.1.0" }, "funding": [ { @@ -1475,30 +1719,193 @@ "time": "2026-05-29T05:06:50+00:00" }, { - "name": "symfony/http-kernel", - "version": "v8.1.0", + "name": "symfony/http-client-contracts", + "version": "v3.7.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "cefeb37c82eed3e0c42fa25ba64cd3a908d90f39" + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cefeb37c82eed3e0c42fa25ba64cd3a908d90f39", - "reference": "cefeb37c82eed3e0c42fa25ba64cd3a908d90f39", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", "shasum": "" }, "require": { - "php": ">=8.4.1", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^7.4|^8.0", - "symfony/event-dispatcher": "^7.4|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/polyfill-ctype": "^1.8" + "php": ">=8.1" }, - "conflict": { - "symfony/dependency-injection": "<8.1", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-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.7.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T13:17:50+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "af11474600f06718086c2cda4fa6fa8d0a672e7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/af11474600f06718086c2cda4fa6fa8d0a672e7e", + "reference": "af11474600f06718086c2cda4fa6fa8d0a672e7e", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<4.3" + }, + "require-dev": { + "doctrine/dbal": "^4.3", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "cefeb37c82eed3e0c42fa25ba64cd3a908d90f39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cefeb37c82eed3e0c42fa25ba64cd3a908d90f39", + "reference": "cefeb37c82eed3e0c42fa25ba64cd3a908d90f39", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", "symfony/flex": "<2.10", "symfony/http-client-contracts": "<2.5", "symfony/translation-contracts": "<2.5", @@ -1585,16 +1992,16 @@ }, { "name": "symfony/polyfill-deepclone", - "version": "v1.37.0", + "version": "v1.39.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-deepclone.git", - "reference": "2ca9e9e75ead5174f2b44613a646bdc9338b8eb4" + "reference": "1b034bc050d84cc9c187de373f744912e1e35f1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-deepclone/zipball/2ca9e9e75ead5174f2b44613a646bdc9338b8eb4", - "reference": "2ca9e9e75ead5174f2b44613a646bdc9338b8eb4", + "url": "https://api.github.com/repos/symfony/polyfill-deepclone/zipball/1b034bc050d84cc9c187de373f744912e1e35f1f", + "reference": "1b034bc050d84cc9c187de373f744912e1e35f1f", "shasum": "" }, "require": { @@ -1648,7 +2055,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-deepclone/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-deepclone/tree/v1.39.0" }, "funding": [ { @@ -1668,7 +2075,7 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:03:27+00:00" + "time": "2026-06-10T20:07:50+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -2002,6 +2409,71 @@ ], "time": "2026-05-26T02:25:22+00:00" }, + { + "name": "symfony/process", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, { "name": "symfony/routing", "version": "v8.1.0", @@ -2254,35 +2726,111 @@ "time": "2026-03-28T09:44:51+00:00" }, { - "name": "symfony/string", - "version": "v8.1.0", + "name": "symfony/stimulus-bundle", + "version": "v3.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" + "url": "https://github.com/symfony/stimulus-bundle.git", + "reference": "9dc58d7cbb77356642c63434033e64d00b6c6946" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", - "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/9dc58d7cbb77356642c63434033e64d00b6c6946", + "reference": "9dc58d7cbb77356642c63434033e64d00b6c6946", "shasum": "" }, "require": { - "php": ">=8.4.1", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-intl-grapheme": "^1.33", - "symfony/polyfill-intl-normalizer": "^1.0", - "symfony/polyfill-mbstring": "^1.0" + "php": ">=8.4", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/deprecation-contracts": "^2.0|^3.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "twig/twig": "^2.15.3|^3.8" }, "conflict": { - "symfony/translation-contracts": "<2.5" + "symfony/asset-mapper": "<6.4" }, "require-dev": { - "symfony/emoji": "^7.4|^8.0", - "symfony/http-client": "^7.4|^8.0", - "symfony/intl": "^7.4|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^7.4|^8.0" + "phpunit/phpunit": "^11.1|^12.0", + "symfony/asset-mapper": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/twig-bundle": "^7.4|^8.0", + "zenstruck/browser": "^1.9" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\UX\\StimulusBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Integration with your Symfony app & Stimulus!", + "keywords": [ + "symfony-ux" + ], + "support": { + "source": "https://github.com/symfony/stimulus-bundle/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-22T05:04:55+00:00" + }, + { + "name": "symfony/string", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -2343,6 +2891,280 @@ ], "time": "2026-05-29T05:06:50+00:00" }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-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.7.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-05T13:30:16+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "25bb8c01edaab85e13142f6010df09b990388343" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/25bb8c01edaab85e13142f6010df09b990388343", + "reference": "25bb8c01edaab85e13142f6010df09b990388343", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.25" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/form": "<7.4.4|>8.0,<8.0.4", + "symfony/mime": "<7.4.9|>8.0,<8.0.9" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/asset": "^7.4|^8.0", + "symfony/asset-mapper": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/form": "^7.4.4|^8.0.4", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/mime": "^7.4.9|^8.0.9", + "symfony/polyfill-intl-icu": "^1.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/security-http": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/workflow": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0", + "twig/cssinliner-extra": "^3", + "twig/inky-extra": "^3", + "twig/markdown-extra": "^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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "b7f4a471a07b8b52174d153e4db12f46954192ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/b7f4a471a07b8b52174d153e4db12f46954192ed", + "reference": "b7f4a471a07b8b52174d153e4db12f46954192ed", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.4.1", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0" + }, + "require-dev": { + "symfony/asset": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, { "name": "symfony/var-dumper", "version": "v8.1.0", @@ -2556,38 +3378,248 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfonycasts/tailwind-bundle", + "version": "v0.13.0", + "source": { + "type": "git", + "url": "https://github.com/SymfonyCasts/tailwind-bundle.git", + "reference": "0cedad5861c365d61bb9fdd52124b4bdf57c477c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SymfonyCasts/tailwind-bundle/zipball/0cedad5861c365d61bb9fdd52124b4bdf57c477c", + "reference": "0cedad5861c365d61bb9fdd52124b4bdf57c477c", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/asset-mapper": "^6.3|^7.0|^8.0", + "symfony/cache": "^6.3|^7.0|^8.0", + "symfony/console": "^5.4|^6.3|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.2|^3.0", + "symfony/http-client": "^5.4|^6.3|^7.0|^8.0", + "symfony/process": "^5.4|^6.3|^7.0|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6", + "symfony/filesystem": "^6.3|^7.0|^8.0", + "symfony/framework-bundle": "^6.3|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.3.9|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfonycasts\\TailwindBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ryan Weaver", + "homepage": "https://symfonycasts.com" + } + ], + "description": "Delightful Tailwind Support for Symfony + AssetMapper", + "keywords": [ + "asset-mapper", + "tailwind" + ], + "support": { + "issues": "https://github.com/SymfonyCasts/tailwind-bundle/issues", + "source": "https://github.com/SymfonyCasts/tailwind-bundle/tree/v0.13.0" + }, + "time": "2026-05-29T11:22:13+00:00" + }, + { + "name": "twig/extra-bundle", + "version": "v3.24.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", + "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.0|^8.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0|^8.0", + "twig/twig": "^3.2|^4.0" + }, + "require-dev": { + "league/commonmark": "^2.7", + "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.24.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2026-02-07T08:07:38+00:00" + }, + { + "name": "twig/twig", + "version": "v3.27.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "ae2071bffb38f04847fc0864d730c94b9cb8ab74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/ae2071bffb38f04847fc0864d730c94b9cb8ab74", + "reference": "ae2071bffb38f04847fc0864d730c94b9cb8ab74", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "php-cs-fixer/shim": "^3.0@stable", + "phpstan/phpstan": "^2.0@stable", + "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": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" } ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], "support": { - "source": "https://github.com/symfony/yaml/tree/v8.1.0" + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.27.1" }, "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, { "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/twig/twig", "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-05-30T17:09:26+00:00" } ], "packages-dev": [ @@ -2734,83 +3766,6 @@ ], "time": "2024-11-12T16:29:46+00:00" }, - { - "name": "composer/semver", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2025-08-20T19:15:30+00:00" - }, { "name": "composer/xdebug-handler", "version": "3.0.5", @@ -3522,16 +4477,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.95.4", + "version": "v3.95.5", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "3f8f68856837a77e1f1d870354eca3c8747f2f72" + "reference": "7f86d8763063f5d2e2e2d0e1e45bb2f15895361d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/3f8f68856837a77e1f1d870354eca3c8747f2f72", - "reference": "3f8f68856837a77e1f1d870354eca3c8747f2f72", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7f86d8763063f5d2e2e2d0e1e45bb2f15895361d", + "reference": "7f86d8763063f5d2e2e2d0e1e45bb2f15895361d", "shasum": "" }, "require": { @@ -3549,7 +4504,7 @@ "react/event-loop": "^1.5", "react/socket": "^1.16", "react/stream": "^1.4", - "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0 || ^9.0", "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", @@ -3615,7 +4570,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.4" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.5" }, "funding": [ { @@ -3623,20 +4578,20 @@ "type": "github" } ], - "time": "2026-06-03T18:02:44+00:00" + "time": "2026-06-09T14:55:16+00:00" }, { "name": "justinrainbow/json-schema", - "version": "6.8.2", + "version": "6.9.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76" + "reference": "bd1bda2ebfc8bff418565941771ea8f03c557886" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2c89ebb95ca9cedc9347f780333f7b25792dcb76", - "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/bd1bda2ebfc8bff418565941771ea8f03c557886", + "reference": "bd1bda2ebfc8bff418565941771ea8f03c557886", "shasum": "" }, "require": { @@ -3646,7 +4601,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "3.3.0", - "json-schema/json-schema-test-suite": "dev-main", + "json-schema/json-schema-test-suite": "^23.2", "marc-mabe/php-enum-phpstan": "^2.0", "phpspec/prophecy": "^1.19", "phpstan/phpstan": "^1.12", @@ -3696,9 +4651,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.9.0" }, - "time": "2026-05-05T05:39:01+00:00" + "time": "2026-06-05T14:05:24+00:00" }, { "name": "localheinz/diff", @@ -6498,71 +7453,6 @@ ], "time": "2026-05-29T05:06:50+00:00" }, - { - "name": "symfony/process", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", - "shasum": "" - }, - "require": { - "php": ">=8.4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "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": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, { "name": "symfony/stopwatch", "version": "v8.1.0", @@ -6679,86 +7569,6 @@ ], "time": "2025-11-17T20:03:58+00:00" }, - { - "name": "twig/twig", - "version": "v3.27.1", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "ae2071bffb38f04847fc0864d730c94b9cb8ab74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/ae2071bffb38f04847fc0864d730c94b9cb8ab74", - "reference": "ae2071bffb38f04847fc0864d730c94b9cb8ab74", - "shasum": "" - }, - "require": { - "php": ">=8.1.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" - }, - "require-dev": { - "php-cs-fixer/shim": "^3.0@stable", - "phpstan/phpstan": "^2.0@stable", - "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.27.1" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2026-05-30T17:09:26+00:00" - }, { "name": "vincentlanglet/twig-cs-fixer", "version": "3.14.0", diff --git a/config/bundles.php b/config/bundles.php index 49d3fb6..1a6d22b 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -2,4 +2,8 @@ return [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], + Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], + Symfonycasts\TailwindBundle\SymfonycastsTailwindBundle::class => ['all' => true], ]; diff --git a/config/packages/asset_mapper.yaml b/config/packages/asset_mapper.yaml new file mode 100644 index 0000000..f7653e9 --- /dev/null +++ b/config/packages/asset_mapper.yaml @@ -0,0 +1,11 @@ +framework: + asset_mapper: + # The paths to make available to the asset mapper. + paths: + - assets/ + missing_import_mode: strict + +when@prod: + framework: + asset_mapper: + missing_import_mode: warn diff --git a/config/packages/symfonycasts_tailwind.yaml b/config/packages/symfonycasts_tailwind.yaml new file mode 100644 index 0000000..3921e12 --- /dev/null +++ b/config/packages/symfonycasts_tailwind.yaml @@ -0,0 +1,6 @@ +symfonycasts_tailwind: + # Specify the EXACT version of Tailwind CSS you want to use + binary_version: 'v4.1.11' + + # Alternatively, you can specify the path to the binary that you manage yourself + #binary: 'node_modules/.bin/tailwindcss' diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml new file mode 100644 index 0000000..3f795d9 --- /dev/null +++ b/config/packages/twig.yaml @@ -0,0 +1,6 @@ +twig: + file_name_pattern: '*.twig' + +when@test: + twig: + strict_variables: true diff --git a/config/reference.php b/config/reference.php index 3f07cb0..14b81cc 100644 --- a/config/reference.php +++ b/config/reference.php @@ -267,7 +267,7 @@ * formats?: array>, * }, * assets?: bool|array{ // Assets configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false * version_strategy?: scalar|Param|null, // Default: null * version?: scalar|Param|null, // Default: null @@ -286,7 +286,7 @@ * }>, * }, * asset_mapper?: bool|array{ // Asset Mapper configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * paths?: string|array, * excluded_patterns?: list, * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true @@ -471,7 +471,7 @@ * }, * disallow_search_engine_index?: bool|Param, // Enabled by default when debug is enabled. // Default: true * http_client?: bool|array{ // HTTP Client configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * max_host_connections?: int|Param, // The maximum number of connections to a single host. * default_options?: array{ * headers?: array, @@ -701,28 +701,137 @@ * }, * }, * } + * @psalm-type TwigConfig = array{ + * form_themes?: list, + * globals?: array, + * autoescape_service?: scalar|Param|null, // Default: null + * autoescape_service_method?: scalar|Param|null, // Default: null + * cache?: scalar|Param|null, // Default: true + * charset?: scalar|Param|null, // Default: "%kernel.charset%" + * debug?: bool|Param, // Default: "%kernel.debug%" + * strict_variables?: bool|Param, // Default: "%kernel.debug%" + * auto_reload?: scalar|Param|null, + * optimizations?: int|Param, + * default_path?: scalar|Param|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates" + * file_name_pattern?: string|list, + * paths?: array, + * date?: array{ // The default format options used by the date filter. + * format?: scalar|Param|null, // Default: "F j, Y H:i" + * interval_format?: scalar|Param|null, // Default: "%d days" + * timezone?: scalar|Param|null, // The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used. // Default: null + * }, + * number_format?: array{ // The default format options for the number_format filter. + * decimals?: int|Param, // Default: 0 + * decimal_point?: scalar|Param|null, // Default: "." + * thousands_separator?: scalar|Param|null, // Default: "," + * }, + * mailer?: array{ + * html_to_text_converter?: scalar|Param|null, // A service implementing the "Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface". // Default: null + * }, + * } + * @psalm-type TwigExtraConfig = array{ + * cache?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * html?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * markdown?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * intl?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * cssinliner?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * inky?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * string?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * commonmark?: array{ + * renderer?: array{ // Array of options for rendering HTML. + * block_separator?: scalar|Param|null, + * inner_separator?: scalar|Param|null, + * soft_break?: scalar|Param|null, + * }, + * html_input?: "strip"|"allow"|"escape"|Param, // How to handle HTML input. + * allow_unsafe_links?: bool|Param, // Remove risky link and image URLs by setting this to false. // Default: true + * max_nesting_level?: int|Param, // The maximum nesting level for blocks. // Default: 9223372036854775807 + * max_delimiters_per_line?: int|Param, // The maximum number of strong/emphasis delimiters per line. // Default: 9223372036854775807 + * slug_normalizer?: array{ // Array of options for configuring how URL-safe slugs are created. + * instance?: mixed, + * max_length?: int|Param, // Default: 255 + * unique?: mixed, + * }, + * commonmark?: array{ // Array of options for configuring the CommonMark core extension. + * enable_em?: bool|Param, // Default: true + * enable_strong?: bool|Param, // Default: true + * use_asterisk?: bool|Param, // Default: true + * use_underscore?: bool|Param, // Default: true + * unordered_list_markers?: list, + * }, + * ... + * }, + * } + * @psalm-type StimulusConfig = array{ + * controller_paths?: list, + * controllers_json?: scalar|Param|null, // Default: "%kernel.project_dir%/assets/controllers.json" + * } + * @psalm-type SymfonycastsTailwindConfig = array{ + * input_css?: list, + * config_file?: scalar|Param|null, // Path to the tailwind.config.js file // Default: "%kernel.project_dir%/tailwind.config.js" + * binary?: scalar|Param|null, // The tailwind binary to use instead of downloading a new one // Default: null + * binary_version?: scalar|Param|null, // Tailwind CLI version to download - null means the latest version // Default: null + * binary_platform?: "auto"|"linux-arm64"|"linux-arm64-musl"|"linux-x64"|"linux-x64-musl"|"macos-arm64"|"macos-x64"|"windows-x64"|Param, // Tailwind CLI platform to download - "auto" will try to detect the platform automatically // Default: "auto" + * postcss_config_file?: scalar|Param|null, // Path to PostCSS config file which is passed to the Tailwind CLI // Default: null + * strict_mode?: bool|Param|null, // When enabled, an exception will be thrown if there are no built assets (default: false in `test` env, true otherwise) // Default: null + * process_timeout?: int|Param, // Timeout in seconds for the Tailwind build process - use "0" to disable // Default: 60 + * } * @psalm-type ConfigType = array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, * services?: ServicesConfig, * framework?: FrameworkConfig, + * twig?: TwigConfig, + * twig_extra?: TwigExtraConfig, + * stimulus?: StimulusConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, * "when@dev"?: array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, * services?: ServicesConfig, * framework?: FrameworkConfig, + * twig?: TwigConfig, + * twig_extra?: TwigExtraConfig, + * stimulus?: StimulusConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, * }, * "when@prod"?: array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, * services?: ServicesConfig, * framework?: FrameworkConfig, + * twig?: TwigConfig, + * twig_extra?: TwigExtraConfig, + * stimulus?: StimulusConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, * }, * "when@test"?: array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, * services?: ServicesConfig, * framework?: FrameworkConfig, + * twig?: TwigConfig, + * twig_extra?: TwigExtraConfig, + * stimulus?: StimulusConfig, + * symfonycasts_tailwind?: SymfonycastsTailwindConfig, * }, * ... + */ +return [ + 'app' => ['path' => './assets/app.js', 'entrypoint' => true], + '@hotwired/stimulus' => ['version' => '3.2.2'], + '@symfony/stimulus-bundle' => ['path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js'], +]; diff --git a/symfony.lock b/symfony.lock index 6348573..562a46b 100644 --- a/symfony.lock +++ b/symfony.lock @@ -26,6 +26,21 @@ "bin/phpunit" ] }, + "symfony/asset-mapper": { + "version": "8.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "c01c47af2ec66a74ec046eccb919cfe27d3464ec" + }, + "files": [ + "assets/app.js", + "assets/styles/app.css", + "config/packages/asset_mapper.yaml", + "importmap.php" + ] + }, "symfony/console": { "version": "8.1", "recipe": { @@ -93,7 +108,56 @@ "config/routes.yaml" ] }, + "symfony/stimulus-bundle": { + "version": "3.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.24", + "ref": "d21494ed2ddbde38942e8278299a23ce5cf4a9a1" + }, + "files": [ + "assets/controllers.json", + "assets/controllers/csrf_protection_controller.js", + "assets/controllers/hello_controller.js", + "assets/stimulus_bootstrap.js" + ] + }, + "symfony/twig-bundle": { + "version": "8.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "f250159ebe99153d0c640a3e7742876fc7453f2c" + }, + "files": [ + "config/packages/twig.yaml", + "templates/base.html.twig" + ] + }, + "symfonycasts/tailwind-bundle": { + "version": "0.13", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "0.8", + "ref": "d0bd0276f74de90adfaa4c6cd74cc0caacd77e0a" + }, + "files": [ + "config/packages/symfonycasts_tailwind.yaml" + ] + }, + "twig/extra-bundle": { + "version": "v3.24.0" + }, "vincentlanglet/twig-cs-fixer": { - "version": "3.14.0" + "version": "3.14", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "3.0", + "ref": "d42582ae1bce86fd43491d6264c738b0867f8ffe" + } } } diff --git a/templates/base.html.twig b/templates/base.html.twig new file mode 100644 index 0000000..c223fd4 --- /dev/null +++ b/templates/base.html.twig @@ -0,0 +1,19 @@ + + + + + + {% block title %}ai-lib{% endblock %} + + {% block stylesheets %} + + {% endblock %} + + {% block javascripts %} + {% block importmap %}{{ importmap('app') }}{% endblock %} + {% endblock %} + + + {% block body %}{% endblock %} + +