diff --git a/.dockerignore b/.dockerignore index 16939af..2660982 100755 --- a/.dockerignore +++ b/.dockerignore @@ -41,3 +41,9 @@ vendor/ CLAUDE.md grumphp.yml Makefile + +**/node_modules/ +landing/dist/ +config/jwt/ +.env +.env.* diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8c0a711..a5b716a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,15 +48,15 @@ jobs: - name: Check HTTP reachability run: | - for i in {1.10}; do - if curl -s -f http://localhost:8000/api/v1 > /dev/null 2>&1; then + for i in {1..10}; do + if curl -s -f http://localhost:8000/health > /dev/null 2>&1; then echo "App is ready" break fi echo "Waiting for app... ($i/10)" sleep 2 done - curl -v --fail-with-body http://localhost:8000/api/v1 || true + curl -v --fail-with-body http://localhost:8000/health - name: Create test database run: docker compose exec -T app php bin/console -e test doctrine:database:create --if-not-exists @@ -68,7 +68,7 @@ jobs: run: docker compose exec -T app vendor/bin/phpunit - name: Run PHPUnit with coverage - run: docker compose exec -T -e XDEBUG_MODE=coverage app vendor/bin/phpunit --testsuite=Unit --coverage-clover var/coverage/clover.xml --coverage-text + run: docker compose exec -T -e XDEBUG_MODE=coverage app vendor/bin/phpunit --coverage-clover var/coverage/clover.xml --coverage-text - name: Check coverage threshold (80%) run: | @@ -89,9 +89,8 @@ jobs: run: docker compose exec -T app vendor/bin/behat --no-interaction --colors - name: Audit PHP dependencies - # Exit 1 = vulnerabilities (blocking), exit 2 = abandoned packages (non-blocking) - run: | - docker compose exec -T app composer audit || { code=$?; [ "$code" -eq 2 ] || exit "$code"; } + # Vulnerabilities and abandoned packages block CI. + run: docker compose exec -T app composer audit - name: Run CS Fixer (dry-run) run: docker compose exec -T app vendor/bin/php-cs-fixer fix --dry-run --diff @@ -106,7 +105,7 @@ jobs: run: docker compose exec -T app php bin/console lint:yaml config - name: Doctrine Schema Validator - run: docker compose exec -T app php bin/console -e test doctrine:schema:validate --skip-sync || true + run: docker compose exec -T app php bin/console -e test doctrine:schema:validate --skip-sync secret-scan: name: Secret Scan @@ -135,6 +134,12 @@ jobs: - name: Build prod image run: docker build -f docker/frankenphp/Dockerfile -t signalist:ci . + - name: Validate patched runtime + run: | + docker run --rm --entrypoint frankenphp signalist:ci build-info | grep -F 'golang.org/x/net' | grep -F 'v0.60.0' + docker run --rm --entrypoint php signalist:ci -v + docker run --rm --entrypoint frankenphp -e APP_DOMAIN=http://localhost signalist:ci validate --config /etc/caddy/Caddyfile + - name: Scan image for vulnerabilities uses: aquasecurity/trivy-action@v0.35.0 with: @@ -199,6 +204,32 @@ jobs: - name: Audit JS dependencies run: npm audit --audit-level=high + bluesky: + name: Bluesky adapter + runs-on: ubuntu-latest + defaults: + run: + working-directory: integrations/bluesky + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: '24' + cache: 'npm' + cache-dependency-path: integrations/bluesky/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run offline SDK tests + run: npm test + + - name: Audit dependencies + run: npm audit --audit-level=high + frontend: name: Frontend runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 3cdcf50..1198f6c 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,8 @@ /landing/.astro/ ###< Landing ### +/integrations/bluesky/node_modules/ + ###> friendsofphp/php-cs-fixer ### /.php-cs-fixer.php /.php-cs-fixer.cache diff --git a/AGENTS.md b/AGENTS.md index f001fc4..cc662f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,17 +94,35 @@ Execution principles: - feature work: add the right test level instead of only broad smoke coverage - refactor: verify behavior before and after with the relevant checks -Ask first: - -- adding composer or npm packages -- changing PostgreSQL schema or pgvector dimensions -- changing AI provider configuration or prompt contracts -- changing the project MCP protocol implementation -- changing `phpstan.neon` -- changing shared instruction files +## Autonomous Execution + +An explicit request to implement a feature, fix, or roadmap authorizes the necessary local implementation and verification. Continue through the requested scope without asking for permission for each routine step. State material assumptions, use existing conventions, and report progress and blockers. + +Within the requested scope, work autonomously on: + +- adding necessary composer or npm packages after checking compatibility +- changing PostgreSQL schema and pgvector dimensions through reviewed, tested migration files; apply migrations only to local development or test databases +- implementing AI provider configuration and versioned prompt contracts without exposing secrets or sending raw personal data +- implementing application MCP endpoints with authentication, authorization, and scoped output; this does not authorize external agent runtime MCP access +- starting local development services and running relevant quality gates +- making reversible local code, configuration, test, and documentation changes required to complete the task + +Use configurable defaults when external accounts or product decisions are unavailable. Complete and test independent work while reporting the remaining setup. Ask for clarification only when a missing decision materially affects correctness, security, cost, or irreversible behavior. + +Keep fixing PHPStan findings in code, types, or PHPDoc. Do not weaken analysis or quality gates to obtain passing checks. Changes to `phpstan.neon` still require explicit approval. + +Ask first for: + +- destructive operations that discard existing user work or data +- production database changes, public deployment, or publishing +- live external messages, email delivery, social posts, charges, or paid API usage unless already explicitly authorized +- enabling external agent runtime MCP access +- shared instruction changes that were not explicitly requested or approved in the current conversation - running `git commit` - running `git push` +Explicit authorization persists for the approved scope in the current conversation; do not ask again for the same action. These autonomous execution rules take precedence over stricter routine-step approval guidance in repo-local workflows. Preserve security and privacy requirements. + Never: - commit directly to `main`, `master`, or `develop` @@ -180,7 +198,8 @@ Policy: - duplicated or conflicting guidance - only reusable, stable guidance should be added - temporary context, one-off fixes, and local anecdotes should not be added to instruction files -- changes to instruction files must be proposed first and applied only after explicit confirmation in the current conversation +- agent-initiated instruction improvements must be proposed first and applied only after explicit confirmation in the current conversation +- an explicit user request to change instruction files authorizes that requested edit; explain the intended change and apply it without a second confirmation ## Quality Gates diff --git a/Makefile b/Makefile index 897f05e..2a0bda6 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ .PHONY: help up down build rebuild shell logs lint analyse tests-unit tests-integration tests db-migrate db-reset cache-clear composer-install composer-update front-install front-dev front-build front-preview front-lint front-test front-test-watch front-hooks grumphp +RECTOR_FLAGS ?= + # Default target .DEFAULT_GOAL := help @@ -75,7 +77,7 @@ analyse: ## Run PHPStan static analysis docker compose exec app vendor/bin/phpstan analyse rector: ## Run Rector to refactor code - docker compose exec app vendor/bin/rector process + docker compose exec app vendor/bin/rector process $(RECTOR_FLAGS) rector-dry: ## Run Rector in dry-run mode docker compose exec app vendor/bin/rector process --dry-run @@ -96,8 +98,8 @@ tests-integration: ## Run integration tests tests: ## Run all tests docker compose exec app vendor/bin/phpunit -tests-coverage: ## Run unit tests with coverage report (HTML + text) - docker compose exec -e XDEBUG_MODE=coverage app vendor/bin/phpunit --testsuite=Unit --coverage-html var/coverage --coverage-text +tests-coverage: ## Run backend behavior tests with coverage report (HTML + text) + docker compose exec -e XDEBUG_MODE=coverage app vendor/bin/phpunit --coverage-html var/coverage --coverage-text tests-api: ## Run Behat API tests docker compose exec app vendor/bin/behat --colors diff --git a/README.md b/README.md index cfb4004..f61fedb 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,19 @@ publishing insights across newsletters and social media. **Monitor → Curate → Synthesize → Publish** - **Aggregate** RSS feeds into categorized, searchable inboxes -- **Summarize** articles automatically with LLMs (OpenAI, Anthropic, Mistral) -- **Bookmark & tag** content with AI-assisted auto-tagging +- **Enrich** articles through a provider-independent AI port (live provider setup deferred) +- **Bookmark & tag** content, with owner-scoped tag management and optional AI tagging - **Generate newsletters** from curated articles, configurable by reading time -- **Publish** to social media (X, LinkedIn, Threads, Bluesky, WhatsApp) -- **Search** with full-text and semantic (vector) search +- **Share** links to X, LinkedIn, Threads, Bluesky and WhatsApp; approve API publishing to configured LinkedIn, X and Bluesky accounts +- **Search** with full text; semantic/hybrid modes become available with a configured embedding provider +- **Collaborate** in workspaces with roles, annotations and independent read status +- **Control privacy** with consent, export, restriction and password-confirmed deletion +- **Manage plans** through configured Stripe checkout, portal and signed webhooks --- +Implementation and release configuration: [deployment guide](docs/DEPLOYMENT.md). External provider integrations are disabled until credentials and account approvals are supplied. Production deployment and live provider validation remain outstanding. + ## Tech Stack | Layer | Technology | @@ -28,8 +33,9 @@ publishing insights across newsletters and social media. | **Backend** | PHP 8.5 · Symfony 7.4 · API Platform 4.x · FrankenPHP | | **Frontend** | React 19 · TypeScript · Vite 7 · MUI v7 · react-i18next | | **Database** | PostgreSQL 16 · pgvector | +| **Landing** | Astro 7 · Tailwind v4 · Node 22.19+ | | **Queue** | Symfony Messenger · Redis | -| **AI** | Symfony AI (OpenAI / Anthropic / Mistral) | +| **AI** | Provider-independent adapters; live provider setup deferred | | **Auth** | LexikJWT | | **Testing** | PHPUnit · Behat · Vitest · React Testing Library | diff --git a/composer.json b/composer.json index d0f2e90..a3390a1 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "ext-ctype": "*", "ext-iconv": "*", "ext-pdo_pgsql": "*", + "ext-sodium": "*", "api-platform/doctrine-orm": "^4.2", "api-platform/symfony": "^4.2", "doctrine/dbal": "^3.10", @@ -17,20 +18,27 @@ "doctrine/doctrine-migrations-bundle": "^3.7", "doctrine/orm": "^3.5", "laminas/laminas-feed": "^2.26", - "laminas/laminas-http": "^2.23", "lexik/jwt-authentication-bundle": "^3.2", + "mcp/sdk": "^0.8.1", "nelmio/cors-bundle": "^2.6", + "nyholm/psr7": "^1.8", "runtime/frankenphp-symfony": "^0.2", + "stripe/stripe-php": "^22.0", "symfony/console": "7.4.*", "symfony/dotenv": "7.4.*", "symfony/flex": "^2.10", "symfony/framework-bundle": "7.4.*", + "symfony/http-client": "7.4.*", "symfony/mailer": "7.4.*", "symfony/messenger": "7.4.*", + "symfony/process": "7.4.*", "symfony/property-access": "7.4.*", "symfony/property-info": "7.4.*", + "symfony/psr-http-message-bridge": "^7.4", + "symfony/rate-limiter": "7.4.*", "symfony/redis-messenger": "7.4.*", "symfony/runtime": "7.4.*", + "symfony/scheduler": "7.4.*", "symfony/security-bundle": "7.4.*", "symfony/serializer": "7.4.*", "symfony/twig-bundle": "7.4.*", @@ -40,10 +48,11 @@ }, "require-dev": { "behat/behat": "^3.29", - "behatch/contexts": "^1.3", + "behat/mink": "^1.13", + "behat/mink-browserkit-driver": "^2.3", "doctrine/doctrine-fixtures-bundle": "^4.3", "enlightn/security-checker": "^2.0", - "friends-of-behat/mink-browserkit-driver": "^1.6", + "friends-of-behat/mink-extension": "^2.7", "friends-of-behat/symfony-extension": "^2.6", "friendsofphp/php-cs-fixer": "^3.91", "phpro/grumphp": ">=2.18", diff --git a/composer.lock b/composer.lock index a551503..9c0a740 100644 --- a/composer.lock +++ b/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": "8e9a15b173fcca114c7de29edab8e6d1", + "content-hash": "778f4471f5393d728f448fa1b3bbc34b", "packages": [ { "name": "api-platform/doctrine-common", @@ -1156,55 +1156,6 @@ }, "time": "2026-01-26T15:45:40+00:00" }, - { - "name": "brick/varexporter", - "version": "0.6.0", - "source": { - "type": "git", - "url": "https://github.com/brick/varexporter.git", - "reference": "af98bfc2b702a312abbcaff37656dbe419cec5bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/brick/varexporter/zipball/af98bfc2b702a312abbcaff37656dbe419cec5bc", - "reference": "af98bfc2b702a312abbcaff37656dbe419cec5bc", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^5.0", - "php": "^8.1" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "6.8.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Brick\\VarExporter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A powerful alternative to var_export(), which can export closures and objects without __set_state()", - "keywords": [ - "var_export" - ], - "support": { - "issues": "https://github.com/brick/varexporter/issues", - "source": "https://github.com/brick/varexporter/tree/0.6.0" - }, - "funding": [ - { - "url": "https://github.com/BenMorel", - "type": "github" - } - ], - "time": "2025-02-20T17:42:39+00:00" - }, { "name": "doctrine/collections", "version": "2.6.0", @@ -1498,29 +1449,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.5", + "version": "1.1.6", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "conflict": { - "phpunit/phpunit": "<=7.5 || >=13" + "phpunit/phpunit": "<=7.5 || >=14" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12 || ^13", - "phpstan/phpstan": "1.4.10 || 2.1.11", + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -1540,9 +1491,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" }, - "time": "2025-04-07T20:06:18+00:00" + "time": "2026-02-07T07:09:04+00:00" }, { "name": "doctrine/doctrine-bundle", @@ -2002,27 +1953,25 @@ }, { "name": "doctrine/lexer", - "version": "3.0.1", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + "reference": "71d305eaa6480d3d3460c7d053d1da7749fa9cca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/71d305eaa6480d3d3460c7d053d1da7749fa9cca", + "reference": "71d305eaa6480d3d3460c7d053d1da7749fa9cca", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.21" + "doctrine/coding-standard": "^14", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10.5.58 || ^12.5.4" }, "type": "library", "autoload": { @@ -2059,7 +2008,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.0.1" + "source": "https://github.com/doctrine/lexer/tree/3.0.3" }, "funding": [ { @@ -2075,7 +2024,7 @@ "type": "tidelift" } ], - "time": "2024-02-05T11:56:58+00:00" + "time": "2026-10-01T06:59:29+00:00" }, { "name": "doctrine/migrations", @@ -2626,61 +2575,55 @@ "time": "2025-12-19T11:28:10+00:00" }, { - "name": "laminas/laminas-http", - "version": "2.23.0", + "name": "laminas/laminas-stdlib", + "version": "3.21.0", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-http.git", - "reference": "9462fc84330d25b23383823831380abb33907fdd" + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-http/zipball/9462fc84330d25b23383823831380abb33907fdd", - "reference": "9462fc84330d25b23383823831380abb33907fdd", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/b1c81514cfe158aadf724c42b34d3d0a8164c096", + "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096", "shasum": "" }, "require": { - "laminas/laminas-loader": "^2.10", - "laminas/laminas-stdlib": "^3.6", - "laminas/laminas-uri": "^2.14", - "laminas/laminas-validator": "^2.15 || ^3.0", "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "conflict": { - "zendframework/zend-http": "*" + "zendframework/zend-stdlib": "*" }, "require-dev": { - "ext-curl": "*", - "laminas/laminas-coding-standard": "^3.0.1", - "phpunit/phpunit": "^10.5.38" - }, - "suggest": { - "paragonie/certainty": "For automated management of cacert.pem" + "laminas/laminas-coding-standard": "^3.1.0", + "phpbench/phpbench": "^1.4.1", + "phpunit/phpunit": "^11.5.42", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13.1" }, "type": "library", "autoload": { "psr-4": { - "Laminas\\Http\\": "src/" + "Laminas\\Stdlib\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "description": "SPL extensions, array utilities, error handlers, and more", "homepage": "https://laminas.dev", "keywords": [ - "http", - "http client", - "laminas" + "laminas", + "stdlib" ], "support": { "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-http/", + "docs": "https://docs.laminas.dev/laminas-stdlib/", "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-http/issues", - "rss": "https://github.com/laminas/laminas-http/releases.atom", - "source": "https://github.com/laminas/laminas-http" + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" }, "funding": [ { @@ -2688,523 +2631,664 @@ "type": "community_bridge" } ], - "time": "2025-12-05T11:02:08+00:00" + "time": "2025-10-11T18:13:12+00:00" }, { - "name": "laminas/laminas-loader", - "version": "2.12.0", + "name": "lcobucci/jwt", + "version": "5.6.0", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-loader.git", - "reference": "ec8cee33fb254ee4d9c8e8908c870e5c797e1272" + "url": "https://github.com/lcobucci/jwt.git", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/ec8cee33fb254ee4d9c8e8908c870e5c797e1272", - "reference": "ec8cee33fb254ee4d9c8e8908c870e5c797e1272", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", "shasum": "" }, "require": { - "php": "^8.0.0" - }, - "conflict": { - "zendframework/zend-loader": "*" + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.4.0", - "phpunit/phpunit": "~9.5.25" + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^11.1" + }, + "suggest": { + "lcobucci/clock": ">= 3.2" }, "type": "library", "autoload": { "psr-4": { - "Laminas\\Loader\\": "src/" + "Lcobucci\\JWT\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "Autoloading and plugin loading strategies", - "homepage": "https://laminas.dev", + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", "keywords": [ - "laminas", - "loader" + "JWS", + "jwt" ], "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-loader/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-loader/issues", - "rss": "https://github.com/laminas/laminas-loader/releases.atom", - "source": "https://github.com/laminas/laminas-loader" + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/5.6.0" }, "funding": [ { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" } ], - "abandoned": true, - "time": "2025-12-30T11:30:39+00:00" + "time": "2025-10-17T11:30:53+00:00" }, { - "name": "laminas/laminas-servicemanager", - "version": "4.5.0", + "name": "lexik/jwt-authentication-bundle", + "version": "v3.2.0", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-servicemanager.git", - "reference": "a6996829c8ce55025cca1b57b1e8a8b165e3926c" + "url": "https://github.com/lexik/LexikJWTAuthenticationBundle.git", + "reference": "60df75dc70ee6f597929cb2f0812adda591dfa4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/a6996829c8ce55025cca1b57b1e8a8b165e3926c", - "reference": "a6996829c8ce55025cca1b57b1e8a8b165e3926c", + "url": "https://api.github.com/repos/lexik/LexikJWTAuthenticationBundle/zipball/60df75dc70ee6f597929cb2f0812adda591dfa4b", + "reference": "60df75dc70ee6f597929cb2f0812adda591dfa4b", "shasum": "" }, "require": { - "brick/varexporter": "^0.3.8 || ^0.4.0 || ^0.5.0 || ^0.6.0", - "laminas/laminas-stdlib": "^3.19", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "psr/container": "^1.1 || ^2.0" - }, - "conflict": { - "laminas/laminas-code": "<4.10.0", - "zendframework/zend-code": "<3.3.1" - }, - "provide": { - "psr/container-implementation": "^1.0 || ^2.0" + "ext-openssl": "*", + "lcobucci/jwt": "^5.0", + "php": ">=8.2", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.4|^3.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/security-bundle": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^1.0|^2.0|^3.0" }, "require-dev": { - "composer/package-versions-deprecated": "^1.11.99.5", - "friendsofphp/proxy-manager-lts": "^1.0.18", - "laminas/laminas-cli": "^1.11", - "laminas/laminas-coding-standard": "~3.1.0", - "laminas/laminas-container-config-test": "^1.1", - "mikey179/vfsstream": "^1.6.12", - "phpbench/phpbench": "^1.4.1", - "phpunit/phpunit": "^10.5.58", - "psalm/plugin-phpunit": "^0.19.5", - "symfony/console": "^6.4.17 || ^7.3.4", - "vimeo/psalm": "^6.13.1" + "api-platform/core": "^3.0|^4.0", + "rector/rector": "^1.2", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "suggest": { - "friendsofphp/proxy-manager-lts": "To handle lazy initialization of services", - "laminas/laminas-cli": "To consume CLI commands provided by this component" - }, - "type": "library", - "extra": { - "laminas": { - "module": "Laminas\\ServiceManager", - "config-provider": "Laminas\\ServiceManager\\ConfigProvider" - } + "gesdinet/jwt-refresh-token-bundle": "Implements a refresh token system over Json Web Tokens in Symfony", + "spomky-labs/lexik-jose-bridge": "Provides a JWT Token encoder with encryption support" }, + "type": "symfony-bundle", "autoload": { "psr-4": { - "Laminas\\ServiceManager\\": "src/" - } + "Lexik\\Bundle\\JWTAuthenticationBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Factory-Driven Dependency Injection Container", - "homepage": "https://laminas.dev", + "authors": [ + { + "name": "Jeremy Barthe", + "email": "j.barthe@lexik.fr", + "homepage": "https://github.com/jeremyb" + }, + { + "name": "Nicolas Cabot", + "email": "n.cabot@lexik.fr", + "homepage": "https://github.com/slashfan" + }, + { + "name": "Cedric Girard", + "email": "c.girard@lexik.fr", + "homepage": "https://github.com/cedric-g" + }, + { + "name": "Dev Lexik", + "email": "dev@lexik.fr", + "homepage": "https://github.com/lexik" + }, + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com", + "homepage": "https://github.com/chalasr" + }, + { + "name": "Lexik Community", + "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle/graphs/contributors" + } + ], + "description": "This bundle provides JWT authentication for your Symfony REST API", + "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle", "keywords": [ - "PSR-11", - "dependency-injection", - "di", - "dic", - "laminas", - "service-manager", - "servicemanager" + "Authentication", + "JWS", + "api", + "bundle", + "jwt", + "rest", + "symfony" ], "support": { - "chat": "https://laminas.dev/chat", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-servicemanager/issues", - "source": "https://github.com/laminas/laminas-servicemanager/tree/4.5.0" + "issues": "https://github.com/lexik/LexikJWTAuthenticationBundle/issues", + "source": "https://github.com/lexik/LexikJWTAuthenticationBundle/tree/v3.2.0" }, "funding": [ { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" + "url": "https://github.com/chalasr", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/lexik/jwt-authentication-bundle", + "type": "tidelift" } ], - "time": "2025-10-14T09:41:04+00:00" + "time": "2025-12-20T17:47:00+00:00" }, { - "name": "laminas/laminas-stdlib", - "version": "3.21.0", + "name": "mcp/sdk", + "version": "v0.8.1", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-stdlib.git", - "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096" + "url": "https://github.com/modelcontextprotocol/php-sdk.git", + "reference": "c5dbfb64e5a2a30872ec3927b4cf13a2eb05e10e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/b1c81514cfe158aadf724c42b34d3d0a8164c096", - "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096", + "url": "https://api.github.com/repos/modelcontextprotocol/php-sdk/zipball/c5dbfb64e5a2a30872ec3927b4cf13a2eb05e10e", + "reference": "c5dbfb64e5a2a30872ec3927b4cf13a2eb05e10e", "shasum": "" }, "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" - }, - "conflict": { - "zendframework/zend-stdlib": "*" + "ext-fileinfo": "*", + "opis/json-schema": "^2.4", + "php": "^8.1", + "php-http/discovery": "^1.20", + "phpdocumentor/reflection-docblock": "^5.6 || ^6.0", + "psr/clock": "^1.0", + "psr/container": "^1.0 || ^2.0", + "psr/event-dispatcher": "^1.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/uid": "^5.4 || ^6.4 || ^7.3 || ^8.0" }, "require-dev": { - "laminas/laminas-coding-standard": "^3.1.0", - "phpbench/phpbench": "^1.4.1", - "phpunit/phpunit": "^11.5.42", - "psalm/plugin-phpunit": "^0.19.5", - "vimeo/psalm": "^6.13.1" + "composer/semver": "^3.0", + "ext-openssl": "*", + "firebase/php-jwt": "^6.10 || ^7.0", + "laminas/laminas-httphandlerrunner": "^2.12", + "nyholm/psr7": "^1.8", + "nyholm/psr7-server": "^1.1", + "phar-io/composer-distributor": "^1.0.2", + "php-cs-fixer/shim": "^3.91", + "phpdocumentor/shim": "^3", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^10.5", + "psr/simple-cache": "^2.0 || ^3.0", + "symfony/cache": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/console": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/finder": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/http-client": "^5.4 || ^6.4 || ^7.3 || ^8.0", + "symfony/process": "^5.4 || ^6.4 || ^7.3 || ^8.0" }, - "type": "library", + "suggest": { + "symfony/finder": "Required for file-based discovery." + }, + "type": "library", "autoload": { "psr-4": { - "Laminas\\Stdlib\\": "src/" + "Mcp\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "description": "SPL extensions, array utilities, error handlers, and more", - "homepage": "https://laminas.dev", - "keywords": [ - "laminas", - "stdlib" + "Apache-2.0" ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-stdlib/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-stdlib/issues", - "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", - "source": "https://github.com/laminas/laminas-stdlib" - }, - "funding": [ + "authors": [ { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Kyrian Obikwelu", + "email": "koshnawaza@gmail.com" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" } ], - "time": "2025-10-11T18:13:12+00:00" + "description": "Model Context Protocol SDK for Client and Server applications in PHP", + "support": { + "issues": "https://github.com/modelcontextprotocol/php-sdk/issues", + "source": "https://github.com/modelcontextprotocol/php-sdk/tree/v0.8.1" + }, + "time": "2026-08-29T23:10:56+00:00" }, { - "name": "laminas/laminas-translator", - "version": "1.3.0", + "name": "nelmio/cors-bundle", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-translator.git", - "reference": "1d6bf7d857c3fc310a30f5ea028731b9bf8be9db" + "url": "https://github.com/nelmio/NelmioCorsBundle.git", + "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-translator/zipball/1d6bf7d857c3fc310a30f5ea028731b9bf8be9db", - "reference": "1d6bf7d857c3fc310a30f5ea028731b9bf8be9db", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", + "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", "shasum": "" }, "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "laminas/laminas-coding-standard": "~3.1.0", - "phpunit/phpunit": "^11.5.46", - "vimeo/psalm": "^6.14.3" + "phpstan/phpstan": "^1.11.5", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4", + "phpstan/phpstan-symfony": "^1.4.4", + "phpunit/phpunit": "^8" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } }, - "type": "library", "autoload": { "psr-4": { - "Laminas\\Translator\\": "src/" - } + "Nelmio\\CorsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Interfaces for the Translator component of laminas-i18n", - "homepage": "https://laminas.dev", + "authors": [ + { + "name": "Nelmio", + "homepage": "http://nelm.io" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", "keywords": [ - "i18n", - "laminas" + "api", + "cors", + "crossdomain" ], "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-i18n/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-translator/issues", - "rss": "https://github.com/laminas/laminas-translator/releases.atom", - "source": "https://github.com/laminas/laminas-translator" + "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.1" }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2025-12-30T14:26:45+00:00" + "time": "2026-01-12T15:59:08+00:00" }, { - "name": "laminas/laminas-uri", - "version": "2.14.0", + "name": "nyholm/psr7", + "version": "1.8.2", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-uri.git", - "reference": "e804288f4540988903dc0ede386ce5eec87198df" + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/e804288f4540988903dc0ede386ce5eec87198df", - "reference": "e804288f4540988903dc0ede386ce5eec87198df", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", "shasum": "" }, "require": { - "laminas/laminas-escaper": "^2.9", - "laminas/laminas-validator": "^2.39 || ^3.0", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" }, - "conflict": { - "zendframework/zend-uri": "*" + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.4.0", - "phpunit/phpunit": "^11.0" + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, "autoload": { "psr-4": { - "Laminas\\Uri\\": "src/" + "Nyholm\\Psr7\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "A component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", - "homepage": "https://laminas.dev", + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", "keywords": [ - "laminas", - "uri" + "psr-17", + "psr-7" ], "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-uri/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-uri/issues", - "rss": "https://github.com/laminas/laminas-uri/releases.atom", - "source": "https://github.com/laminas/laminas-uri" + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" }, "funding": [ { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" } ], - "time": "2025-12-05T10:02:11+00:00" + "time": "2024-09-09T07:06:30+00:00" }, { - "name": "laminas/laminas-validator", - "version": "3.15.0", + "name": "opis/json-schema", + "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-validator.git", - "reference": "9e4ce57074ae4b9b6c03610fd30560c4aefa1a9c" + "url": "https://github.com/opis/json-schema.git", + "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/9e4ce57074ae4b9b6c03610fd30560c4aefa1a9c", - "reference": "9e4ce57074ae4b9b6c03610fd30560c4aefa1a9c", + "url": "https://api.github.com/repos/opis/json-schema/zipball/8458763e0dd0b6baa310e04f1829fc73da4e8c8a", + "reference": "8458763e0dd0b6baa310e04f1829fc73da4e8c8a", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-fileinfo": "*", - "ext-filter": "*", - "ext-intl": "*", - "laminas/laminas-servicemanager": "^4.1.0", - "laminas/laminas-stdlib": "^3.19", - "laminas/laminas-translator": "^1.0", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "psr/container": "^1.1 || ^2.0", - "psr/http-client": "^1.0.3", - "psr/http-factory": "^1.1.0", - "psr/http-message": "^1.0.1 || ^2.0.0" + "ext-json": "*", + "opis/string": "^2.1", + "opis/uri": "^1.0", + "php": "^7.4 || ^8.0" }, "require-dev": { - "laminas/laminas-coding-standard": "^3.1.0", - "laminas/laminas-diactoros": "^3.8.0", - "phpunit/phpunit": "^10.5.60", - "psalm/plugin-phpunit": "^0.19.5", - "vimeo/psalm": "^6.14.2" - }, - "suggest": { - "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", - "laminas/laminas-i18n-resources": "Translations of validator messages" + "ext-bcmath": "*", + "ext-intl": "*", + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { - "laminas": { - "component": "Laminas\\Validator", - "config-provider": "Laminas\\Validator\\ConfigProvider" + "branch-alias": { + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "Laminas\\Validator\\": "src/" + "Opis\\JsonSchema\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], - "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", - "homepage": "https://laminas.dev", + "authors": [ + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + }, + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + } + ], + "description": "Json Schema Validator for PHP", + "homepage": "https://opis.io/json-schema", "keywords": [ - "laminas", + "json", + "json-schema", + "schema", + "validation", "validator" ], "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-validator/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-validator/issues", - "rss": "https://github.com/laminas/laminas-validator/releases.atom", - "source": "https://github.com/laminas/laminas-validator" + "issues": "https://github.com/opis/json-schema/issues", + "source": "https://github.com/opis/json-schema/tree/2.6.0" }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2026-02-16T08:59:56+00:00" + "time": "2025-10-17T12:46:48+00:00" }, { - "name": "lcobucci/jwt", - "version": "5.6.0", + "name": "opis/string", + "version": "2.1.0", "source": { "type": "git", - "url": "https://github.com/lcobucci/jwt.git", - "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" + "url": "https://github.com/opis/string.git", + "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", - "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", + "url": "https://api.github.com/repos/opis/string/zipball/3e4d2aaff518ac518530b89bb26ed40f4503635e", + "reference": "3e4d2aaff518ac518530b89bb26ed40f4503635e", "shasum": "" }, "require": { - "ext-openssl": "*", - "ext-sodium": "*", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "psr/clock": "^1.0" + "ext-iconv": "*", + "ext-json": "*", + "php": "^7.4 || ^8.0" }, "require-dev": { - "infection/infection": "^0.29", - "lcobucci/clock": "^3.2", - "lcobucci/coding-standard": "^11.0", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.10.7", - "phpstan/phpstan-deprecation-rules": "^1.1.3", - "phpstan/phpstan-phpunit": "^1.3.10", - "phpstan/phpstan-strict-rules": "^1.5.0", - "phpunit/phpunit": "^11.1" - }, - "suggest": { - "lcobucci/clock": ">= 3.2" + "phpunit/phpunit": "^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, "autoload": { "psr-4": { - "Lcobucci\\JWT\\": "src" + "Opis\\String\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Luís Cobucci", - "email": "lcobucci@gmail.com", - "role": "Developer" + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" } ], - "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "description": "Multibyte strings as objects", + "homepage": "https://opis.io/string", "keywords": [ - "JWS", - "jwt" + "multi-byte", + "opis", + "string", + "string manipulation", + "utf-8" ], "support": { - "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.6.0" + "issues": "https://github.com/opis/string/issues", + "source": "https://github.com/opis/string/tree/2.1.0" }, - "funding": [ + "time": "2025-10-17T12:38:41+00:00" + }, + { + "name": "opis/uri", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opis/uri.git", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/uri/zipball/0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "reference": "0f3ca49ab1a5e4a6681c286e0b2cc081b93a7d5a", + "shasum": "" + }, + "require": { + "opis/string": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ { - "url": "https://github.com/lcobucci", - "type": "github" + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" }, { - "url": "https://www.patreon.com/lcobucci", - "type": "patreon" + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" } ], - "time": "2025-10-17T11:30:53+00:00" + "description": "Build, parse and validate URIs and URI-templates", + "homepage": "https://opis.io", + "keywords": [ + "URI Template", + "parse url", + "punycode", + "uri", + "uri components", + "url", + "validate uri" + ], + "support": { + "issues": "https://github.com/opis/uri/issues", + "source": "https://github.com/opis/uri/tree/1.1.0" + }, + "time": "2021-05-22T15:57:08+00:00" }, { - "name": "lexik/jwt-authentication-bundle", - "version": "v3.2.0", + "name": "php-http/discovery", + "version": "1.20.0", "source": { "type": "git", - "url": "https://github.com/lexik/LexikJWTAuthenticationBundle.git", - "reference": "60df75dc70ee6f597929cb2f0812adda591dfa4b" + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lexik/LexikJWTAuthenticationBundle/zipball/60df75dc70ee6f597929cb2f0812adda591dfa4b", - "reference": "60df75dc70ee6f597929cb2f0812adda591dfa4b", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", "shasum": "" }, "require": { - "ext-openssl": "*", - "lcobucci/jwt": "^5.0", - "php": ">=8.2", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/deprecation-contracts": "^2.4|^3.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/security-bundle": "^6.4|^7.0|^8.0", - "symfony/translation-contracts": "^1.0|^2.0|^3.0" + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" }, "require-dev": { - "api-platform/core": "^3.0|^4.0", - "rector/rector": "^1.2", - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dom-crawler": "^6.4|^7.0|^8.0", - "symfony/filesystem": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/phpunit-bridge": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" }, - "suggest": { - "gesdinet/jwt-refresh-token-bundle": "Implements a refresh token system over Json Web Tokens in Symfony", - "spomky-labs/lexik-jose-bridge": "Provides a JWT Token encoder with encryption support" + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true }, - "type": "symfony-bundle", "autoload": { "psr-4": { - "Lexik\\Bundle\\JWTAuthenticationBundle\\": "" + "Http\\Discovery\\": "src/" }, "exclude-from-classmap": [ - "/Tests/" + "src/Composer/Plugin.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3213,100 +3297,128 @@ ], "authors": [ { - "name": "Jeremy Barthe", - "email": "j.barthe@lexik.fr", - "homepage": "https://github.com/jeremyb" - }, - { - "name": "Nicolas Cabot", - "email": "n.cabot@lexik.fr", - "homepage": "https://github.com/slashfan" - }, - { - "name": "Cedric Girard", - "email": "c.girard@lexik.fr", - "homepage": "https://github.com/cedric-g" - }, - { - "name": "Dev Lexik", - "email": "dev@lexik.fr", - "homepage": "https://github.com/lexik" - }, - { - "name": "Robin Chalas", - "email": "robin.chalas@gmail.com", - "homepage": "https://github.com/chalasr" - }, + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "ee15a5a2022c8383906b225055e9496a6a5e6e3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/ee15a5a2022c8383906b225055e9496a6a5e6e3b", + "reference": "ee15a5a2022c8383906b225055e9496a6a5e6e3b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "Lexik Community", - "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle/graphs/contributors" + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], - "description": "This bundle provides JWT authentication for your Symfony REST API", - "homepage": "https://github.com/lexik/LexikJWTAuthenticationBundle", + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "https://www.phpdoc.org", "keywords": [ - "Authentication", - "JWS", - "api", - "bundle", - "jwt", - "rest", - "symfony" + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" ], "support": { - "issues": "https://github.com/lexik/LexikJWTAuthenticationBundle/issues", - "source": "https://github.com/lexik/LexikJWTAuthenticationBundle/tree/v3.2.0" + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.2.1" }, - "funding": [ - { - "url": "https://github.com/chalasr", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/lexik/jwt-authentication-bundle", - "type": "tidelift" - } - ], - "time": "2025-12-20T17:47:00+00:00" + "time": "2025-11-10T21:21:55+00:00" }, { - "name": "nelmio/cors-bundle", - "version": "2.6.1", + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.7", "source": { "type": "git", - "url": "https://github.com/nelmio/NelmioCorsBundle.git", - "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c" + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", - "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903", "shasum": "" }, "require": { - "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { - "phpstan/phpstan": "^1.11.5", - "phpstan/phpstan-deprecation-rules": "^1.2.0", - "phpstan/phpstan-phpunit": "^1.4", - "phpstan/phpstan-symfony": "^1.4.4", - "phpunit/phpunit": "^8" + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" }, - "type": "symfony-bundle", + "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "Nelmio\\CorsBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "phpDocumentor\\Reflection\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3314,83 +3426,125 @@ ], "authors": [ { - "name": "Nelmio", - "homepage": "http://nelm.io" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" }, { - "name": "Symfony Community", - "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], - "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", - "keywords": [ - "api", - "cors", - "crossdomain" - ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { - "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", - "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.1" + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.7" }, - "time": "2026-01-12T15:59:08+00:00" + "time": "2026-03-18T20:47:46+00:00" }, { - "name": "nikic/php-parser", - "version": "v5.7.0", + "name": "phpdocumentor/type-resolver", + "version": "1.12.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, - "bin": [ - "bin/php-parse" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" } ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + }, + "time": "2025-11-21T15:09:14+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.6", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "1427af4647235b4a73b13940b1f1258d3584f36e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1427af4647235b4a73b13940b1f1258d3584f36e", + "reference": "1427af4647235b4a73b13940b1f1258d3584f36e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.6" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-09-27T20:07:45+00:00" }, { "name": "psr/cache", @@ -3752,6 +3906,119 @@ }, "time": "2023-04-04T09:54:51+00:00" }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, { "name": "psr/link", "version": "2.0.1", @@ -3908,7 +4175,69 @@ "type": "github" } ], - "time": "2023-12-12T12:06:11+00:00" + "time": "2023-12-12T12:06:11+00:00" + }, + { + "name": "stripe/stripe-php", + "version": "v22.0.0", + "source": { + "type": "git", + "url": "https://github.com/stripe/stripe-php.git", + "reference": "c4c1f658483e717519935222e47af224ef4a508b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/c4c1f658483e717519935222e47af224ef4a508b", + "reference": "c4c1f658483e717519935222e47af224ef4a508b", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=7.4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.94.0", + "phpstan/phpstan": "^1.2", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "files": [ + "lib/version_check.php" + ], + "psr-4": { + "Stripe\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stripe and contributors", + "homepage": "https://github.com/stripe/stripe-php/contributors" + } + ], + "description": "Stripe PHP Library", + "homepage": "https://stripe.com/", + "keywords": [ + "api", + "payment processing", + "stripe" + ], + "support": { + "issues": "https://github.com/stripe/stripe-php/issues", + "source": "https://github.com/stripe/stripe-php/tree/v22.0.0" + }, + "time": "2026-10-01T00:43:03+00:00" }, { "name": "symfony/asset", @@ -3985,16 +4314,16 @@ }, { "name": "symfony/cache", - "version": "v7.4.5", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "8dde98d5a4123b53877aca493f9be57b333f14bd" + "reference": "e7e604eab58639503495a46f5c194cc69a9c80a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/8dde98d5a4123b53877aca493f9be57b333f14bd", - "reference": "8dde98d5a4123b53877aca493f9be57b333f14bd", + "url": "https://api.github.com/repos/symfony/cache/zipball/e7e604eab58639503495a46f5c194cc69a9c80a8", + "reference": "e7e604eab58639503495a46f5c194cc69a9c80a8", "shasum": "" }, "require": { @@ -4008,7 +4337,6 @@ }, "conflict": { "doctrine/dbal": "<3.6", - "ext-redis": "<6.1", "ext-relay": "<0.12.1", "symfony/dependency-injection": "<6.4", "symfony/http-kernel": "<6.4", @@ -4020,7 +4348,7 @@ "symfony/cache-implementation": "1.1|2.0|3.0" }, "require-dev": { - "cache/integration-tests": "dev-master", + "cache/integration-tests": "^1.0.3", "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", @@ -4065,7 +4393,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.5" + "source": "https://github.com/symfony/cache/tree/v7.4.20" }, "funding": [ { @@ -4085,20 +4413,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-09-29T12:35:16+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", - "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2", "shasum": "" }, "require": { @@ -4112,7 +4440,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -4145,7 +4473,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.1" }, "funding": [ { @@ -4156,12 +4484,16 @@ "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": "2025-03-13T15:25:07+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/clock", @@ -4243,16 +4575,16 @@ }, { "name": "symfony/config", - "version": "v7.4.4", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb" + "reference": "696e12da8eea497a1a3808d714b43ff67a7df43e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4275b53b8ab0cf37f48bf273dc2285c8178efdfb", - "reference": "4275b53b8ab0cf37f48bf273dc2285c8178efdfb", + "url": "https://api.github.com/repos/symfony/config/zipball/696e12da8eea497a1a3808d714b43ff67a7df43e", + "reference": "696e12da8eea497a1a3808d714b43ff67a7df43e", "shasum": "" }, "require": { @@ -4298,7 +4630,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.4.4" + "source": "https://github.com/symfony/config/tree/v7.4.17" }, "funding": [ { @@ -4318,7 +4650,7 @@ "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2026-08-20T09:55:18+00:00" }, { "name": "symfony/console", @@ -4420,16 +4752,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v7.4.5", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2" + "reference": "a7faee783a87b55f27460fc571f8c639bf9d93a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/76a02cddca45a5254479ad68f9fa274ead0a7ef2", - "reference": "76a02cddca45a5254479ad68f9fa274ead0a7ef2", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a7faee783a87b55f27460fc571f8c639bf9d93a2", + "reference": "a7faee783a87b55f27460fc571f8c639bf9d93a2", "shasum": "" }, "require": { @@ -4480,7 +4812,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.4.5" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.20" }, "funding": [ { @@ -4500,20 +4832,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-09-29T12:35:16+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -4526,7 +4858,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -4551,7 +4883,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -4562,12 +4894,16 @@ "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": "2024-09-25T14:21:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/doctrine-bridge", @@ -4762,16 +5098,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.4.4", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" + "reference": "82239fe00f7cdc9f17c4166fff4ebdc22bd26ec0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", - "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/82239fe00f7cdc9f17c4166fff4ebdc22bd26ec0", + "reference": "82239fe00f7cdc9f17c4166fff4ebdc22bd26ec0", "shasum": "" }, "require": { @@ -4820,7 +5156,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.4.4" + "source": "https://github.com/symfony/error-handler/tree/v7.4.20" }, "funding": [ { @@ -4840,20 +5176,20 @@ "type": "tidelift" } ], - "time": "2026-01-20T16:42:42+00:00" + "time": "2026-09-22T18:56:40+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.4", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746" + "reference": "d269974ee93c61d03620ffee358355bfdb471d66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d269974ee93c61d03620ffee358355bfdb471d66", + "reference": "d269974ee93c61d03620ffee358355bfdb471d66", "shasum": "" }, "require": { @@ -4905,7 +5241,7 @@ "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.4.4" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.17" }, "funding": [ { @@ -4925,20 +5261,20 @@ "type": "tidelift" } ], - "time": "2026-01-05T11:45:34+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { @@ -4952,7 +5288,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -4985,7 +5321,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -4996,25 +5332,29 @@ "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": "2024-09-25T14:21:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/filesystem", - "version": "v7.4.0", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + "reference": "90d412aa5277c6819db39e7605aa46b1019e3232" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/90d412aa5277c6819db39e7605aa46b1019e3232", + "reference": "90d412aa5277c6819db39e7605aa46b1019e3232", "shasum": "" }, "require": { @@ -5051,7 +5391,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + "source": "https://github.com/symfony/filesystem/tree/v7.4.18" }, "funding": [ { @@ -5071,20 +5411,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-08-23T10:03:40+00:00" }, { "name": "symfony/finder", - "version": "v7.4.5", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "url": "https://api.github.com/repos/symfony/finder/zipball/2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", + "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", "shasum": "" }, "require": { @@ -5119,7 +5459,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.5" + "source": "https://github.com/symfony/finder/tree/v7.4.20" }, "funding": [ { @@ -5139,7 +5479,7 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-09-26T06:31:10+00:00" }, { "name": "symfony/flex", @@ -5216,16 +5556,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v7.4.5", + "version": "v7.4.14", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "dcf89ca6712d9e1b5d3f14dea0e1c2685a05d1cd" + "reference": "4d11fd50d0a3d2c43b400154ad7ec35b84ea3e5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/dcf89ca6712d9e1b5d3f14dea0e1c2685a05d1cd", - "reference": "dcf89ca6712d9e1b5d3f14dea0e1c2685a05d1cd", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/4d11fd50d0a3d2c43b400154ad7ec35b84ea3e5b", + "reference": "4d11fd50d0a3d2c43b400154ad7ec35b84ea3e5b", "shasum": "" }, "require": { @@ -5248,7 +5588,7 @@ }, "conflict": { "doctrine/persistence": "<1.3", - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/asset": "<6.4", "symfony/asset-mapper": "<6.4", @@ -5261,7 +5601,7 @@ "symfony/lock": "<6.4", "symfony/mailer": "<6.4", "symfony/messenger": "<7.4", - "symfony/mime": "<6.4", + "symfony/mime": "<6.4.37|>=7.0,<7.4.9|>=8.0,<8.0.9", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", @@ -5275,13 +5615,13 @@ "symfony/twig-bundle": "<6.4", "symfony/validator": "<6.4", "symfony/web-profiler-bundle": "<6.4", - "symfony/webhook": "<7.2", + "symfony/webhook": "<7.4", "symfony/workflow": "<7.4" }, "require-dev": { "doctrine/persistence": "^1.3|^2|^3", "dragonmantank/cron-expression": "^3.1", - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "seld/jsonlint": "^1.10", "symfony/asset": "^6.4|^7.0|^8.0", "symfony/asset-mapper": "^6.4|^7.0|^8.0", @@ -5299,7 +5639,7 @@ "symfony/lock": "^6.4|^7.0|^8.0", "symfony/mailer": "^6.4|^7.0|^8.0", "symfony/messenger": "^7.4|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4.37|^7.4.9|^8.0.9", "symfony/notifier": "^6.4|^7.0|^8.0", "symfony/object-mapper": "^7.3|^8.0", "symfony/polyfill-intl-icu": "~1.0", @@ -5319,7 +5659,7 @@ "symfony/uid": "^6.4|^7.0|^8.0", "symfony/validator": "^7.4|^8.0", "symfony/web-link": "^6.4|^7.0|^8.0", - "symfony/webhook": "^7.2|^8.0", + "symfony/webhook": "^7.4|^8.0", "symfony/workflow": "^7.4|^8.0", "symfony/yaml": "^7.3|^8.0", "twig/twig": "^3.12" @@ -5350,7 +5690,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.4.5" + "source": "https://github.com/symfony/framework-bundle/tree/v7.4.14" }, "funding": [ { @@ -5370,20 +5710,203 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2026-06-27T08:31:38+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.4.20", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "750a420b8dd0af3d6e1c6c1412802abf2cdd2b6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/750a420b8dd0af3d6e1c6c1412802abf2cdd2b6c", + "reference": "750a420b8dd0af3d6e1c6c1412802abf2cdd2b6c", + "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/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "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", + "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/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.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.4.20" + }, + "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-09-24T11:02:13+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.7.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/35be0019e2c2c9fba80f9dc033290a5240f7b44f", + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f", + "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\\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.3" + }, + "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-08-04T08:41:16+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.5", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" + "reference": "9d0761c0da6bd5b801b401666834901a9a1ffc33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9d0761c0da6bd5b801b401666834901a9a1ffc33", + "reference": "9d0761c0da6bd5b801b401666834901a9a1ffc33", "shasum": "" }, "require": { @@ -5432,7 +5955,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.4.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.20" }, "funding": [ { @@ -5452,20 +5975,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-09-24T07:12:44+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.5", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" + "reference": "392784b5d7b959b8d872542a2530c50c540a788f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/392784b5d7b959b8d872542a2530c50c540a788f", + "reference": "392784b5d7b959b8d872542a2530c50c540a788f", "shasum": "" }, "require": { @@ -5507,7 +6030,7 @@ "symfony/config": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", "symfony/dom-crawler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "symfony/finder": "^6.4|^7.0|^8.0", @@ -5523,7 +6046,7 @@ "symfony/validator": "^6.4|^7.0|^8.0", "symfony/var-dumper": "^6.4|^7.0|^8.0", "symfony/var-exporter": "^6.4|^7.0|^8.0", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "type": "library", "autoload": { @@ -5551,7 +6074,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.4.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.20" }, "funding": [ { @@ -5571,20 +6094,20 @@ "type": "tidelift" } ], - "time": "2026-01-28T10:33:42+00:00" + "time": "2026-09-29T14:05:16+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.4", + "version": "v7.4.19", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6" + "reference": "6f6f2441bef07a42d2617b9b3aadf04a321aa514" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6", + "url": "https://api.github.com/repos/symfony/mailer/zipball/6f6f2441bef07a42d2617b9b3aadf04a321aa514", + "reference": "6f6f2441bef07a42d2617b9b3aadf04a321aa514", "shasum": "" }, "require": { @@ -5635,7 +6158,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.4" + "source": "https://github.com/symfony/mailer/tree/v7.4.19" }, "funding": [ { @@ -5655,7 +6178,7 @@ "type": "tidelift" } ], - "time": "2026-01-08T08:25:11+00:00" + "time": "2026-09-15T06:01:15+00:00" }, { "name": "symfony/messenger", @@ -5753,16 +6276,16 @@ }, { "name": "symfony/mime", - "version": "v7.4.5", + "version": "v7.4.19", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" + "reference": "f8ac7d0a800bcabbcd1f7de56594cf05e6cd5693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", + "url": "https://api.github.com/repos/symfony/mime/zipball/f8ac7d0a800bcabbcd1f7de56594cf05e6cd5693", + "reference": "f8ac7d0a800bcabbcd1f7de56594cf05e6cd5693", "shasum": "" }, "require": { @@ -5773,25 +6296,95 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "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": "^5.2", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4.3|^7.0.3|^8.0" + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.44|^7.4.17|^8.1.5" + }, + "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.4.19" + }, + "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-09-04T10:45:44+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "b38026df55197f9e39a44f3215788edf83187b80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80", + "reference": "b38026df55197f9e39a44f3215788edf83187b80", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Mime\\": "" + "Symfony\\Component\\OptionsResolver\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -5811,14 +6404,15 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Allows manipulating MIME messages", + "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", "keywords": [ - "mime", - "mime-type" + "config", + "configuration", + "options" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.5" + "source": "https://github.com/symfony/options-resolver/tree/v7.4.0" }, "funding": [ { @@ -5838,20 +6432,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2025-11-12T15:39:26+00:00" }, { "name": "symfony/password-hasher", - "version": "v7.4.4", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "ab8e0ef42483f31c417c82ecfcf7be7b91d784fe" + "reference": "18a7d92126c95962f7efbcc9e421ba710a366847" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/ab8e0ef42483f31c417c82ecfcf7be7b91d784fe", - "reference": "ab8e0ef42483f31c417c82ecfcf7be7b91d784fe", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/18a7d92126c95962f7efbcc9e421ba710a366847", + "reference": "18a7d92126c95962f7efbcc9e421ba710a366847", "shasum": "" }, "require": { @@ -5894,7 +6488,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v7.4.4" + "source": "https://github.com/symfony/password-hasher/tree/v7.4.8" }, "funding": [ { @@ -5914,20 +6508,20 @@ "type": "tidelift" } ], - "time": "2026-01-01T22:13:48+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", "shasum": "" }, "require": { @@ -5976,7 +6570,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" }, "funding": [ { @@ -5996,20 +6590,20 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-09-25T15:46:47+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.33.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + "reference": "533d68cf99c8f8ee2e8893c59ed59192e92cfd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/533d68cf99c8f8ee2e8893c59ed59192e92cfd74", + "reference": "533d68cf99c8f8ee2e8893c59ed59192e92cfd74", "shasum": "" }, "require": { @@ -6063,7 +6657,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.43.0" }, "funding": [ { @@ -6083,20 +6677,20 @@ "type": "tidelift" } ], - "time": "2024-09-10T14:38:51+00:00" + "time": "2026-09-23T14:40:04+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", "shasum": "" }, "require": { @@ -6148,7 +6742,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" }, "funding": [ { @@ -6168,20 +6762,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-09-23T22:02:09+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", "shasum": "" }, "require": { @@ -6233,7 +6827,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" }, "funding": [ { @@ -6253,20 +6847,20 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-09-24T15:19:04+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.33.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/80ccff923a8d61f73ebe9da0aa94e25232f004ce", + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce", "shasum": "" }, "require": { @@ -6313,7 +6907,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.43.0" }, "funding": [ { @@ -6333,7 +6927,7 @@ "type": "tidelift" } ], - "time": "2025-07-08T02:45:35+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { "name": "symfony/polyfill-php84", @@ -6384,16 +6978,244 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.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": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/86a42cbcc07adc331481a5ab310a4024bfc227f6", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6", + "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\\Php85\\": "" + }, + "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.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.43.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-09-25T15:46:47+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.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": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v7.4.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "ed0ae095b86994d370d5791612e55984f15aa30e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/ed0ae095b86994d370d5791612e55984f15aa30e", + "reference": "ed0ae095b86994d370d5791612e55984f15aa30e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "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", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + "source": "https://github.com/symfony/process/tree/v7.4.19" }, "funding": [ { @@ -6413,41 +7235,37 @@ "type": "tidelift" } ], - "time": "2025-06-24T13:30:11+00:00" + "time": "2026-09-02T12:38:34+00:00" }, { - "name": "symfony/polyfill-php85", - "version": "v1.33.0", + "name": "symfony/property-access", + "version": "v7.4.16", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + "url": "https://github.com/symfony/property-access.git", + "reference": "c3dce76a6697c6428f7d80d5cd1cf0aa5d9679f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "url": "https://api.github.com/repos/symfony/property-access/zipball/c3dce76a6697c6428f7d80d5cd1cf0aa5d9679f3", + "reference": "c3dce76a6697c6428f7d80d5cd1cf0aa5d9679f3", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.2", + "symfony/property-info": "^6.4.32|~7.3.10|^7.4.4|^8.0.4" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } + "require-dev": { + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" }, + "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php85\\": "" + "Symfony\\Component\\PropertyAccess\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6456,24 +7274,29 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "description": "Provides functions to read and write from/to an object or array using a simple string notation", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + "source": "https://github.com/symfony/property-access/tree/v7.4.16" }, "funding": [ { @@ -6493,45 +7316,50 @@ "type": "tidelift" } ], - "time": "2025-06-23T16:12:55+00:00" + "time": "2026-07-30T12:37:26+00:00" }, { - "name": "symfony/polyfill-uuid", - "version": "v1.33.0", + "name": "symfony/property-info", + "version": "v7.4.20", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + "url": "https://github.com/symfony/property-info.git", + "reference": "d24c4c810b1285c5d8d77c6fd7b25e714479bd48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "url": "https://api.github.com/repos/symfony/property-info/zipball/d24c4c810b1285c5d8d77c6fd7b25e714479bd48", + "reference": "d24c4c810b1285c5d8d77c6fd7b25e714479bd48", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/type-info": "^7.4.7|^8.0.7" }, - "provide": { - "ext-uuid": "*" + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" }, - "suggest": { - "ext-uuid": "For best performance" + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Uuid\\": "" - } + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6539,24 +7367,26 @@ ], "authors": [ { - "name": "Grégoire Pineau", - "email": "lyrixx@lyrixx.info" + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for uuid functions", + "description": "Extracts information about PHP class' properties using metadata of popular sources", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "uuid" + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" + "source": "https://github.com/symfony/property-info/tree/v7.4.20" }, "funding": [ { @@ -6576,34 +7406,46 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-09-28T09:47:41+00:00" }, { - "name": "symfony/property-access", - "version": "v7.4.4", + "name": "symfony/psr-http-message-bridge", + "version": "v7.4.8", "source": { "type": "git", - "url": "https://github.com/symfony/property-access.git", - "reference": "fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1" + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "76f1a57719a4a04c0ea18678a6c9305b5dcb9da8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1", - "reference": "fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/76f1a57719a4a04c0ea18678a6c9305b5dcb9da8", + "reference": "76f1a57719a4a04c0ea18678a6c9305b5dcb9da8", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/property-info": "^6.4.32|~7.3.10|^7.4.4|^8.0.4" + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.4" }, "require-dev": { - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", + "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0" }, - "type": "library", + "type": "symfony-bridge", "autoload": { "psr-4": { - "Symfony\\Component\\PropertyAccess\\": "" + "Symfony\\Bridge\\PsrHttpMessage\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6623,21 +7465,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "description": "PSR HTTP message bridge", "homepage": "https://symfony.com", "keywords": [ - "access", - "array", - "extraction", - "index", - "injection", - "object", - "property", - "property-path", - "reflection" + "http", + "http-message", + "psr-17", + "psr-7" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.4.4" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.8" }, "funding": [ { @@ -6657,46 +7494,34 @@ "type": "tidelift" } ], - "time": "2026-01-05T08:47:25+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { - "name": "symfony/property-info", - "version": "v7.4.5", + "name": "symfony/rate-limiter", + "version": "v7.4.18", "source": { "type": "git", - "url": "https://github.com/symfony/property-info.git", - "reference": "1c9d326bd69602561e2ea467a16c09b5972eee21" + "url": "https://github.com/symfony/rate-limiter.git", + "reference": "6703d040ab83401b27f3c7b29ff4c8c8106fedba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/1c9d326bd69602561e2ea467a16c09b5972eee21", - "reference": "1c9d326bd69602561e2ea467a16c09b5972eee21", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/6703d040ab83401b27f3c7b29ff4c8c8106fedba", + "reference": "6703d040ab83401b27f3c7b29ff4c8c8106fedba", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0|^8.0", - "symfony/type-info": "~7.3.10|^7.4.4|^8.0.4" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "<5.2|>=6", - "phpdocumentor/type-resolver": "<1.5.1", - "symfony/cache": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/serializer": "<6.4" + "symfony/options-resolver": "^7.3|^8.0" }, "require-dev": { - "phpdocumentor/reflection-docblock": "^5.2", - "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0" + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/lock": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\PropertyInfo\\": "" + "Symfony\\Component\\RateLimiter\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6708,26 +7533,22 @@ ], "authors": [ { - "name": "Kévin Dunglas", - "email": "dunglas@gmail.com" + "name": "Wouter de Jong", + "email": "wouter@wouterj.nl" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Extracts information about PHP class' properties using metadata of popular sources", + "description": "Provides a Token Bucket implementation to rate limit input and output in your application", "homepage": "https://symfony.com", "keywords": [ - "doctrine", - "phpdoc", - "property", - "symfony", - "type", - "validator" + "limiter", + "rate-limiter" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.4.5" + "source": "https://github.com/symfony/rate-limiter/tree/v7.4.18" }, "funding": [ { @@ -6747,7 +7568,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-08-07T15:34:54+00:00" }, { "name": "symfony/redis-messenger", @@ -6827,16 +7648,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.4", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147" + "reference": "2bacb72be5eadeaf9874d5accb75bfb33d21c74b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147", + "url": "https://api.github.com/repos/symfony/routing/zipball/2bacb72be5eadeaf9874d5accb75bfb33d21c74b", + "reference": "2bacb72be5eadeaf9874d5accb75bfb33d21c74b", "shasum": "" }, "require": { @@ -6844,22 +7665,110 @@ "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "symfony/config": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/yaml": "<6.4" + "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|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.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.4.20" + }, + "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-09-28T19:09:50+00:00" + }, + { + "name": "symfony/runtime", + "version": "v7.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "15497f743dd714f3167cb6a56509b9d42e6417b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/15497f743dd714f3167cb6a56509b9d42e6417b3", + "reference": "15497f743dd714f3167cb6a56509b9d42e6417b3", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.2" + }, + "conflict": { + "symfony/dotenv": "<6.4", + "symfony/http-foundation": "<6.4" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0|^8.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/dotenv": "^6.4|^7.0|^8.0", "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/http-kernel": "^6.4|^7.0|^8.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" }, - "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Routing\\": "" + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" }, "exclude-from-classmap": [ "/Tests/" @@ -6871,24 +7780,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": "Maps an HTTP request to a set of configuration variables", + "description": "Enables decoupling PHP applications from global state", "homepage": "https://symfony.com", "keywords": [ - "router", - "routing", - "uri", - "url" + "runtime" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.4" + "source": "https://github.com/symfony/runtime/tree/v7.4.14" }, "funding": [ { @@ -6908,44 +7814,40 @@ "type": "tidelift" } ], - "time": "2026-01-12T12:19:02+00:00" + "time": "2026-06-05T06:22:21+00:00" }, { - "name": "symfony/runtime", - "version": "v7.4.1", + "name": "symfony/scheduler", + "version": "v7.4.20", "source": { "type": "git", - "url": "https://github.com/symfony/runtime.git", - "reference": "876f902a6cb6b26c003de244188c06b2ba1c172f" + "url": "https://github.com/symfony/scheduler.git", + "reference": "eda92b395d3cc8e175f73fa27eb24d878a044e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/876f902a6cb6b26c003de244188c06b2ba1c172f", - "reference": "876f902a6cb6b26c003de244188c06b2ba1c172f", + "url": "https://api.github.com/repos/symfony/scheduler/zipball/eda92b395d3cc8e175f73fa27eb24d878a044e25", + "reference": "eda92b395d3cc8e175f73fa27eb24d878a044e25", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0|^2.0", - "php": ">=8.2" - }, - "conflict": { - "symfony/dotenv": "<6.4" + "php": ">=8.2", + "symfony/clock": "^6.4|^7.0|^8.0" }, "require-dev": { - "composer/composer": "^2.6", + "dragonmantank/cron-expression": "^3.1", + "symfony/cache": "^6.4.36|^7.4.8|^8.0.8", "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dotenv": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.1|^8.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Runtime\\": "", - "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + "Symfony\\Component\\Scheduler\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6957,21 +7859,27 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Sergey Rabochiy", + "email": "upyx.00@gmail.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Enables decoupling PHP applications from global state", + "description": "Provides scheduling through Symfony Messenger", "homepage": "https://symfony.com", "keywords": [ - "runtime" + "cron", + "schedule", + "scheduler" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v7.4.1" + "source": "https://github.com/symfony/scheduler/tree/v7.4.20" }, "funding": [ { @@ -6991,7 +7899,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T14:04:53+00:00" + "time": "2026-09-26T06:31:10+00:00" }, { "name": "symfony/security-bundle", @@ -7107,16 +8015,16 @@ }, { "name": "symfony/security-core", - "version": "v7.4.4", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "958a70725a8d669bec6721f4cd318d209712e944" + "reference": "4e5c01656aa79e6d463fde45117041ebccbaf829" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/958a70725a8d669bec6721f4cd318d209712e944", - "reference": "958a70725a8d669bec6721f4cd318d209712e944", + "url": "https://api.github.com/repos/symfony/security-core/zipball/4e5c01656aa79e6d463fde45117041ebccbaf829", + "reference": "4e5c01656aa79e6d463fde45117041ebccbaf829", "shasum": "" }, "require": { @@ -7144,6 +8052,7 @@ "symfony/expression-language": "^6.4|^7.0|^8.0", "symfony/http-foundation": "^6.4|^7.0|^8.0", "symfony/ldap": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", "symfony/string": "^6.4|^7.0|^8.0", "symfony/translation": "^6.4.3|^7.0.3|^8.0", "symfony/validator": "^6.4|^7.0|^8.0" @@ -7174,7 +8083,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.4.4" + "source": "https://github.com/symfony/security-core/tree/v7.4.20" }, "funding": [ { @@ -7194,7 +8103,7 @@ "type": "tidelift" } ], - "time": "2026-01-14T09:36:49+00:00" + "time": "2026-09-28T16:00:27+00:00" }, { "name": "symfony/security-csrf", @@ -7272,16 +8181,16 @@ }, { "name": "symfony/security-http", - "version": "v7.4.4", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "9d41a473637bf5d074c5f5a73177fd9d769407fd" + "reference": "5fa566b8bfbb398cded6988f059c3d97506ed692" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/9d41a473637bf5d074c5f5a73177fd9d769407fd", - "reference": "9d41a473637bf5d074c5f5a73177fd9d769407fd", + "url": "https://api.github.com/repos/symfony/security-http/zipball/5fa566b8bfbb398cded6988f059c3d97506ed692", + "reference": "5fa566b8bfbb398cded6988f059c3d97506ed692", "shasum": "" }, "require": { @@ -7340,7 +8249,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v7.4.4" + "source": "https://github.com/symfony/security-http/tree/v7.4.20" }, "funding": [ { @@ -7360,7 +8269,7 @@ "type": "tidelift" } ], - "time": "2026-01-14T10:11:16+00:00" + "time": "2026-09-28T18:33:58+00:00" }, { "name": "symfony/serializer", @@ -7467,16 +8376,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", "shasum": "" }, "require": { @@ -7494,7 +8403,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7530,7 +8439,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" }, "funding": [ { @@ -7550,7 +8459,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-07-27T15:39:01+00:00" }, { "name": "symfony/stopwatch", @@ -7620,16 +8529,16 @@ }, { "name": "symfony/string", - "version": "v7.4.4", + "version": "v7.4.19", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" + "reference": "02731ce2bd26514bec9efc0baef2cd0b0cf67f46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", - "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "url": "https://api.github.com/repos/symfony/string/zipball/02731ce2bd26514bec9efc0baef2cd0b0cf67f46", + "reference": "02731ce2bd26514bec9efc0baef2cd0b0cf67f46", "shasum": "" }, "require": { @@ -7687,7 +8596,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.4" + "source": "https://github.com/symfony/string/tree/v7.4.19" }, "funding": [ { @@ -7707,20 +8616,20 @@ "type": "tidelift" } ], - "time": "2026-01-12T10:54:30+00:00" + "time": "2026-09-11T14:46:58+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.1", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { @@ -7733,7 +8642,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7769,7 +8678,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { @@ -7789,36 +8698,36 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/twig-bridge", - "version": "v7.4.5", + "version": "v7.4.19", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "f2dd26b604e856476ef7e0efa4568bc07eb7ddc8" + "reference": "10d369dd3845bb650a9450e643d6d9027e8a86af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/f2dd26b604e856476ef7e0efa4568bc07eb7ddc8", - "reference": "f2dd26b604e856476ef7e0efa4568bc07eb7ddc8", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/10d369dd3845bb650a9450e643d6d9027e8a86af", + "reference": "10d369dd3845bb650a9450e643d6d9027e8a86af", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.21" + "twig/twig": "^3.21|^4.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/console": "<6.4", "symfony/form": "<6.4.32|>7,<7.3.10|>7.4,<7.4.4|>8.0,<8.0.4", "symfony/http-foundation": "<6.4", "symfony/http-kernel": "<6.4", - "symfony/mime": "<6.4", + "symfony/mime": "<6.4.37|>7,<7.4.9|>8.0,<8.0.9", "symfony/serializer": "<6.4", "symfony/translation": "<6.4", "symfony/workflow": "<6.4" @@ -7826,7 +8735,7 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "symfony/asset": "^6.4|^7.0|^8.0", "symfony/asset-mapper": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", @@ -7839,7 +8748,7 @@ "symfony/http-foundation": "^7.3|^8.0", "symfony/http-kernel": "^6.4|^7.0|^8.0", "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4.37|^7.4.9|^8.0.9", "symfony/polyfill-intl-icu": "~1.0", "symfony/property-info": "^6.4|^7.0|^8.0", "symfony/routing": "^6.4|^7.0|^8.0", @@ -7847,7 +8756,7 @@ "symfony/security-core": "^6.4|^7.0|^8.0", "symfony/security-csrf": "^6.4|^7.0|^8.0", "symfony/security-http": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4.3|^7.0.3|^8.0", + "symfony/serializer": "^6.4.44|^7.4.17|^8.1.5", "symfony/stopwatch": "^6.4|^7.0|^8.0", "symfony/translation": "^6.4|^7.0|^8.0", "symfony/validator": "^6.4|^7.0|^8.0", @@ -7884,7 +8793,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.4.5" + "source": "https://github.com/symfony/twig-bridge/tree/v7.4.19" }, "funding": [ { @@ -7904,20 +8813,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2026-09-14T12:19:06+00:00" }, { "name": "symfony/twig-bundle", - "version": "v7.4.4", + "version": "v7.4.19", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "e8829e02ff96a391ed0703bac9e7ff0537480b6b" + "reference": "231c0b37dada053458d47281261a3af0270a634d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/e8829e02ff96a391ed0703bac9e7ff0537480b6b", - "reference": "e8829e02ff96a391ed0703bac9e7ff0537480b6b", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/231c0b37dada053458d47281261a3af0270a634d", + "reference": "231c0b37dada053458d47281261a3af0270a634d", "shasum": "" }, "require": { @@ -7929,7 +8838,7 @@ "symfony/http-foundation": "^6.4|^7.0|^8.0", "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", "symfony/twig-bridge": "^7.3|^8.0", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "conflict": { "symfony/framework-bundle": "<6.4", @@ -7974,7 +8883,7 @@ "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.4.4" + "source": "https://github.com/symfony/twig-bundle/tree/v7.4.19" }, "funding": [ { @@ -7994,20 +8903,20 @@ "type": "tidelift" } ], - "time": "2026-01-06T12:34:24+00:00" + "time": "2026-09-11T14:46:58+00:00" }, { "name": "symfony/type-info", - "version": "v7.4.4", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "f83c725e72b39b2704b9d6fc85070ad6ac7a5889" + "reference": "bbaaf3366400f5c85f66800b955c77b46e8abad2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/f83c725e72b39b2704b9d6fc85070ad6ac7a5889", - "reference": "f83c725e72b39b2704b9d6fc85070ad6ac7a5889", + "url": "https://api.github.com/repos/symfony/type-info/zipball/bbaaf3366400f5c85f66800b955c77b46e8abad2", + "reference": "bbaaf3366400f5c85f66800b955c77b46e8abad2", "shasum": "" }, "require": { @@ -8057,7 +8966,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.4.4" + "source": "https://github.com/symfony/type-info/tree/v7.4.20" }, "funding": [ { @@ -8077,7 +8986,7 @@ "type": "tidelift" } ], - "time": "2026-01-09T12:14:21+00:00" + "time": "2026-09-28T12:19:37+00:00" }, { "name": "symfony/uid", @@ -8263,16 +9172,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.4", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" + "reference": "e088da50b813f32473a76871616cbb8fa54653a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e088da50b813f32473a76871616cbb8fa54653a8", + "reference": "e088da50b813f32473a76871616cbb8fa54653a8", "shasum": "" }, "require": { @@ -8288,7 +9197,7 @@ "symfony/http-kernel": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/uid": "^6.4|^7.0|^8.0", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "bin": [ "Resources/bin/var-dump-server" @@ -8326,7 +9235,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.18" }, "funding": [ { @@ -8346,20 +9255,20 @@ "type": "tidelift" } ], - "time": "2026-01-01T22:13:48+00:00" + "time": "2026-08-30T20:10:52+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.4.0", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "03a60f169c79a28513a78c967316fbc8bf17816f" + "reference": "d6a87acbe48cbc707b9aba7f1a6c2215aab02603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/03a60f169c79a28513a78c967316fbc8bf17816f", - "reference": "03a60f169c79a28513a78c967316fbc8bf17816f", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d6a87acbe48cbc707b9aba7f1a6c2215aab02603", + "reference": "d6a87acbe48cbc707b9aba7f1a6c2215aab02603", "shasum": "" }, "require": { @@ -8407,7 +9316,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.4.0" + "source": "https://github.com/symfony/var-exporter/tree/v7.4.18" }, "funding": [ { @@ -8427,7 +9336,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:15:23+00:00" + "time": "2026-08-23T10:03:40+00:00" }, { "name": "symfony/web-link", @@ -8518,16 +9427,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.1", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" + "reference": "0222bf4dfaa1e3ceb8fff579635d655f2950d5a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0222bf4dfaa1e3ceb8fff579635d655f2950d5a7", + "reference": "0222bf4dfaa1e3ceb8fff579635d655f2950d5a7", "shasum": "" }, "require": { @@ -8570,7 +9479,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.1" + "source": "https://github.com/symfony/yaml/tree/v7.4.20" }, "funding": [ { @@ -8590,20 +9499,20 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:11:45+00:00" + "time": "2026-09-26T06:31:10+00:00" }, { "name": "twig/twig", - "version": "v3.23.0", + "version": "v3.30.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9" + "reference": "8c737079b726af72ff8ef3c595be9f6a810ea1ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", - "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/8c737079b726af72ff8ef3c595be9f6a810ea1ef", + "reference": "8c737079b726af72ff8ef3c595be9f6a810ea1ef", "shasum": "" }, "require": { @@ -8613,7 +9522,8 @@ "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "phpstan/phpstan": "^2.0", + "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" }, @@ -8657,7 +9567,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.23.0" + "source": "https://github.com/twigphp/Twig/tree/v3.30.0" }, "funding": [ { @@ -8669,7 +9579,73 @@ "type": "tidelift" } ], - "time": "2026-01-23T21:00:41+00:00" + "time": "2026-09-25T13:20:01+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "8a163706bbcac613733dba03f51e13b5eb1bb819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/8a163706bbcac613733dba03f51e13b5eb1bb819", + "reference": "8a163706bbcac613733dba03f51e13b5eb1bb819", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, + "branch-alias": { + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.5.0" + }, + "time": "2026-10-05T23:14:19+00:00" }, { "name": "willdurand/negotiation", @@ -9792,52 +10768,72 @@ "time": "2025-11-22T12:18:15+00:00" }, { - "name": "behatch/contexts", - "version": "1.3", + "name": "behat/mink-browserkit-driver", + "version": "v2.3.0", "source": { "type": "git", - "url": "https://github.com/Behatch/contexts.git", - "reference": "5eecf620927201ff2e6bdafcec2bc4241ece65e6" + "url": "https://github.com/minkphp/MinkBrowserKitDriver.git", + "reference": "d361516cba6e684bdc4518b9c044edc77f249e36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behatch/contexts/zipball/5eecf620927201ff2e6bdafcec2bc4241ece65e6", - "reference": "5eecf620927201ff2e6bdafcec2bc4241ece65e6", + "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/d361516cba6e684bdc4518b9c044edc77f249e36", + "reference": "d361516cba6e684bdc4518b9c044edc77f249e36", "shasum": "" }, "require": { - "behat/behat": "*", - "behat/mink": "*", - "behat/mink-extension": "*", - "justinrainbow/json-schema": "*" + "behat/mink": "^1.11.0@dev", + "ext-dom": "*", + "php": ">=7.2", + "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/dom-crawler": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "behat/mink-goutte-driver": "*", - "behat/mink-selenium2-driver": "*" + "mink/driver-testsuite": "dev-master", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/error-handler": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/http-client": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/mime": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } }, - "type": "library", "autoload": { - "psr-0": { - "Sanpi\\Behatch": "src/" + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "beerware" + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } ], - "description": "Behatch contexts", + "description": "Symfony2 BrowserKit driver for Mink framework", + "homepage": "https://mink.behat.org/", "keywords": [ - "BDD", - "Behat", - "Context", - "Symfony2" + "Mink", + "Symfony2", + "browser", + "testing" ], "support": { - "issues": "https://github.com/Behatch/contexts/issues", - "source": "https://github.com/Behatch/contexts/tree/1.3" + "issues": "https://github.com/minkphp/MinkBrowserKitDriver/issues", + "source": "https://github.com/minkphp/MinkBrowserKitDriver/tree/v2.3.0" }, - "abandoned": true, - "time": "2013-12-17T10:12:28+00:00" + "time": "2025-11-22T12:42:18+00:00" }, { "name": "clue/ndjson-react", @@ -10512,69 +11508,6 @@ ], "time": "2025-08-14T07:29:31+00:00" }, - { - "name": "friends-of-behat/mink-browserkit-driver", - "version": "v1.6.2", - "source": { - "type": "git", - "url": "https://github.com/FriendsOfBehat/MinkBrowserKitDriver.git", - "reference": "4f7d58037f8aa5f3aa17308cb6341b029859ea65" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FriendsOfBehat/MinkBrowserKitDriver/zipball/4f7d58037f8aa5f3aa17308cb6341b029859ea65", - "reference": "4f7d58037f8aa5f3aa17308cb6341b029859ea65", - "shasum": "" - }, - "require": { - "behat/mink": "^1.7", - "php": "^7.4|^8.0", - "symfony/browser-kit": "^4.4|^5.0|^6.0|^7.0", - "symfony/dom-crawler": "^4.4|^5.0|^6.0|^7.0" - }, - "replace": { - "behat/mink-browserkit-driver": "self.version" - }, - "require-dev": { - "friends-of-behat/mink-driver-testsuite": "dev-master", - "symfony/http-kernel": "^4.4|^5.0|^6.0|^7.0" - }, - "type": "mink-driver", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Behat\\Mink\\Driver\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } - ], - "description": "Symfony2 BrowserKit driver for Mink framework", - "homepage": "http://mink.behat.org/", - "keywords": [ - "Mink", - "Symfony2", - "browser", - "testing" - ], - "support": { - "source": "https://github.com/FriendsOfBehat/MinkBrowserKitDriver/tree/v1.6.2" - }, - "abandoned": "behat/mink-browserkit-driver", - "time": "2024-02-06T13:25:07+00:00" - }, { "name": "friends-of-behat/mink-extension", "version": "v2.7.5", @@ -10886,25 +11819,26 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.0", + "version": "7.15.5", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee80339fd9177ba44c49cdb653ff02a4d1106b9a", + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", + "guzzlehttp/promises": "^2.5.3", + "guzzlehttp/psr7": "^2.13.1", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-client-implementation": "1.0" @@ -10912,9 +11846,10 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -10992,7 +11927,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + "source": "https://github.com/guzzle/guzzle/tree/7.15.5" }, "funding": [ { @@ -11008,28 +11943,29 @@ "type": "tidelift" } ], - "time": "2025-08-23T22:36:01+00:00" + "time": "2026-08-24T09:21:06+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.3.0", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "481557b130ef3790cf82b713667b43030dc9c957" + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", - "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "url": "https://api.github.com/repos/guzzle/promises/zipball/cde49999552d185d64715fe9c1f77a2aadd2f9f1", + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "type": "library", "extra": { @@ -11075,7 +12011,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.3.0" + "source": "https://github.com/guzzle/promises/tree/2.5.3" }, "funding": [ { @@ -11091,27 +12027,29 @@ "type": "tidelift" } ], - "time": "2025-08-22T14:34:08+00:00" + "time": "2026-08-24T09:11:28+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.8.0", + "version": "2.13.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "21dc724a0583619cd1652f673303492272778051" + "reference": "95e7828100de18b4e269fb1703be530082d5166d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", - "reference": "21dc724a0583619cd1652f673303492272778051", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/95e7828100de18b4e269fb1703be530082d5166d", + "reference": "95e7828100de18b4e269fb1703be530082d5166d", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-factory-implementation": "1.0", @@ -11119,8 +12057,9 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -11184,105 +12123,30 @@ "message", "psr-7", "request", - "response", - "stream", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.8.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", - "type": "tidelift" - } - ], - "time": "2025-08-23T21:21:41+00:00" - }, - { - "name": "justinrainbow/json-schema", - "version": "6.6.4", - "source": { - "type": "git", - "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7", - "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7", - "shasum": "" - }, - "require": { - "ext-json": "*", - "marc-mabe/php-enum": "^4.4", - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "3.3.0", - "json-schema/json-schema-test-suite": "^23.2", - "marc-mabe/php-enum-phpstan": "^2.0", - "phpspec/prophecy": "^1.19", - "phpstan/phpstan": "^1.12", - "phpunit/phpunit": "^8.5" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" + "response", + "stream", + "uri", + "url" ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.13.1" + }, + "funding": [ { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" + "url": "https://github.com/GrahamCampbell", + "type": "github" }, { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" + "url": "https://github.com/Nyholm", + "type": "github" }, { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" } ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/jsonrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "support": { - "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4" - }, - "time": "2025-12-19T15:01:32+00:00" + "time": "2026-08-24T09:13:11+00:00" }, { "name": "kelunik/certificate", @@ -11585,99 +12449,26 @@ ], "time": "2026-01-15T06:54:53+00:00" }, - { - "name": "marc-mabe/php-enum", - "version": "v4.7.2", - "source": { - "type": "git", - "url": "https://github.com/marc-mabe/php-enum.git", - "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", - "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", - "shasum": "" - }, - "require": { - "ext-reflection": "*", - "php": "^7.1 | ^8.0" - }, - "require-dev": { - "phpbench/phpbench": "^0.16.10 || ^1.0.4", - "phpstan/phpstan": "^1.3.1", - "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", - "vimeo/psalm": "^4.17.0 | ^5.26.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-3.x": "3.2-dev", - "dev-master": "4.7-dev" - } - }, - "autoload": { - "psr-4": { - "MabeEnum\\": "src/" - }, - "classmap": [ - "stubs/Stringable.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Marc Bennewitz", - "email": "dev@mabe.berlin", - "homepage": "https://mabe.berlin/", - "role": "Lead" - } - ], - "description": "Simple and fast implementation of enumerations with native PHP", - "homepage": "https://github.com/marc-mabe/php-enum", - "keywords": [ - "enum", - "enum-map", - "enum-set", - "enumeration", - "enumerator", - "enummap", - "enumset", - "map", - "set", - "type", - "type-hint", - "typehint" - ], - "support": { - "issues": "https://github.com/marc-mabe/php-enum/issues", - "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" - }, - "time": "2025-09-14T11:18:39+00:00" - }, { "name": "masterminds/html5", - "version": "2.10.0", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "fcf91eb64359852f00d921887b219479b4f21251" + "reference": "a1e7a2f88ee13635d86fc61cfbdf2306a76ddfc7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", - "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/a1e7a2f88ee13635d86fc61cfbdf2306a76ddfc7", + "reference": "a1e7a2f88ee13635d86fc61cfbdf2306a76ddfc7", "shasum": "" }, "require": { "ext-dom": "*", - "php": ">=5.3.0" + "php": ">=7.4" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10" }, "type": "library", "extra": { @@ -11721,9 +12512,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + "source": "https://github.com/Masterminds/html5-php/tree/2.11.0" }, - "time": "2025-07-25T09:04:22+00:00" + "time": "2026-08-18T06:18:41+00:00" }, { "name": "monolog/monolog", @@ -11888,6 +12679,64 @@ ], "time": "2025-08-01T08:46:24+00:00" }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, { "name": "ondram/ci-detector", "version": "4.2.0", @@ -14793,16 +15642,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v7.4.4", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965" + "reference": "6b037d62595666cf65de867073c9a1f00184be0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/71fd6a82fc357c8b5de22f78b228acfc43dee965", - "reference": "71fd6a82fc357c8b5de22f78b228acfc43dee965", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/6b037d62595666cf65de867073c9a1f00184be0c", + "reference": "6b037d62595666cf65de867073c9a1f00184be0c", "shasum": "" }, "require": { @@ -14841,7 +15690,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.4" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.17" }, "funding": [ { @@ -14861,7 +15710,7 @@ "type": "tidelift" } ], - "time": "2026-01-05T08:47:25+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/expression-language", @@ -14931,77 +15780,6 @@ ], "time": "2026-01-05T08:47:25+00:00" }, - { - "name": "symfony/options-resolver", - "version": "v7.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "b38026df55197f9e39a44f3215788edf83187b80" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80", - "reference": "b38026df55197f9e39a44f3215788edf83187b80", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "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 an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.4.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": "2025-11-12T15:39:26+00:00" - }, { "name": "symfony/phpunit-bridge", "version": "v7.4.3", @@ -15087,71 +15865,6 @@ ], "time": "2025-12-09T15:33:45+00:00" }, - { - "name": "symfony/process", - "version": "v7.4.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "608476f4604102976d687c483ac63a79ba18cc97" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", - "reference": "608476f4604102976d687c483ac63a79ba18cc97", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "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/v7.4.5" - }, - "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-26T15:07:59+00:00" - }, { "name": "symfony/translation", "version": "v7.4.4", @@ -15254,16 +15967,16 @@ }, { "name": "symfony/web-profiler-bundle", - "version": "v7.4.4", + "version": "v7.4.20", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "be165e29e6109efb89bfaefe56e3deccf72a8643" + "reference": "916c2a4edf51270a5173a57d8d3936c02a4543a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/be165e29e6109efb89bfaefe56e3deccf72a8643", - "reference": "be165e29e6109efb89bfaefe56e3deccf72a8643", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/916c2a4edf51270a5173a57d8d3936c02a4543a1", + "reference": "916c2a4edf51270a5173a57d8d3936c02a4543a1", "shasum": "" }, "require": { @@ -15275,7 +15988,7 @@ "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", "symfony/routing": "^6.4|^7.0|^8.0", "symfony/twig-bundle": "^6.4|^7.0|^8.0", - "twig/twig": "^3.15" + "twig/twig": "^3.15|^4.0" }, "conflict": { "symfony/form": "<6.4", @@ -15320,7 +16033,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.4" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.20" }, "funding": [ { @@ -15340,7 +16053,7 @@ "type": "tidelift" } ], - "time": "2026-01-07T11:56:45+00:00" + "time": "2026-09-28T19:09:50+00:00" }, { "name": "theseer/tokenizer", @@ -15402,7 +16115,8 @@ "php": ">=8.5.0", "ext-ctype": "*", "ext-iconv": "*", - "ext-pdo_pgsql": "*" + "ext-pdo_pgsql": "*", + "ext-sodium": "*" }, "platform-dev": {}, "plugin-api-version": "2.9.0" diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index e9e3166..acc1629 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -4,11 +4,15 @@ doctrine: profiling_collect_backtrace: '%kernel.debug%' use_savepoints: true types: + vector: App\Infrastructure\Persistence\Type\VectorType uuid: Symfony\Bridge\Doctrine\Types\UuidType + mapping_types: + vector: vector orm: auto_generate_proxy_classes: true enable_lazy_ghost_objects: true + enable_native_lazy_objects: true report_fields_where_declared: true validate_xml_mapping: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware diff --git a/config/packages/http_discovery.yaml b/config/packages/http_discovery.yaml new file mode 100644 index 0000000..2a789e7 --- /dev/null +++ b/config/packages/http_discovery.yaml @@ -0,0 +1,10 @@ +services: + Psr\Http\Message\RequestFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\ResponseFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\ServerRequestFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\StreamFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\UploadedFileFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\UriFactoryInterface: '@http_discovery.psr17_factory' + + http_discovery.psr17_factory: + class: Http\Discovery\Psr17Factory diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index 1ca3fd1..f0b1039 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -3,6 +3,15 @@ framework: failure_transport: failed transports: + oauth_priority: + dsn: '%env(MESSENGER_TRANSPORT_DSN)%' + options: + stream: 'signalist_oauth' + group: 'signalist_oauth_consumers' + consumer: 'signalist_oauth_worker' + retry_strategy: + max_retries: 2 + delay: 1000 async: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' options: @@ -24,6 +33,22 @@ framework: consumer: 'signalist_failed_worker' routing: + 'App\Domain\Spotlight\Message\AnswerArticleQuestionMessage': async + 'App\Domain\Privacy\Message\ApplyRetentionMessage': async + 'App\Domain\Social\Message\CrosspostMessage': async + 'App\Domain\Social\Message\GenerateSocialRecapMessage': async + 'App\Domain\Connection\Message\RefreshAccountMessage': oauth_priority + 'App\Domain\Sync\Message\SyncRaindropMessage': async + 'App\Domain\Connection\Message\ExchangeOAuthCodeMessage': oauth_priority + 'App\Domain\Connection\Message\PrepareBlueskyAuthorizationMessage': oauth_priority + 'App\Domain\Billing\Message\SyncBillingSeatsMessage': async + 'App\Domain\Billing\Message\CreateBillingLinkMessage': async + 'App\Domain\Billing\Message\ReconcileBillingEventMessage': async + 'App\Domain\Organization\Message\SendInvitationMessage': async + 'App\Domain\Newsletter\Message\GenerateNewsletterMessage': async + 'App\Domain\Search\Message\RunArticleSearchMessage': async + 'App\Domain\Waitlist\Message\SendWaitlistConfirmationMessage': async + 'App\Domain\AI\Message\EnrichArticleMessage': async 'App\Domain\Feed\Message\CrawlFeedMessage': async 'App\Domain\Auth\Message\SendVerificationEmailMessage': async @@ -31,5 +56,6 @@ when@test: framework: messenger: transports: + oauth_priority: 'in-memory://' async: 'in-memory://' failed: 'in-memory://' diff --git a/config/packages/rate_limiter.yaml b/config/packages/rate_limiter.yaml new file mode 100644 index 0000000..ff95ab0 --- /dev/null +++ b/config/packages/rate_limiter.yaml @@ -0,0 +1,6 @@ +framework: + rate_limiter: + waitlist: + policy: sliding_window + limit: 10 + interval: '1 hour' diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 3ae3c39..5b5d6f4 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -9,6 +9,18 @@ security: property: email firewalls: + oauth_callback: + pattern: ^/api/v1/connections/(raindrop|linkedin|x|bluesky)/callback$ + stateless: true + security: false + billing_webhook: + pattern: ^/api/v1/billing/webhook$ + stateless: true + security: false + waitlist: + pattern: ^/api/v1/waitlist(?:/|$) + stateless: true + security: false dev: pattern: ^/(_profiler|_wdt|assets|build)/ security: false @@ -33,12 +45,17 @@ security: stateless: true security: false api: - pattern: ^/api/ + pattern: ^/(api/|mcp(?:/|$)) stateless: true provider: app_user_provider + user_checker: App\Infrastructure\Auth\ActiveUserChecker jwt: ~ access_control: + - { path: ^/api/v1/connections/(raindrop|linkedin|x|bluesky)/callback$, roles: PUBLIC_ACCESS } + - { path: ^/api/v1/billing/webhook$, roles: PUBLIC_ACCESS } + - { path: ^/mcp(?:/|$), roles: IS_AUTHENTICATED_FULLY } + - { path: ^/api/v1/waitlist(?:/|$), roles: PUBLIC_ACCESS } - { path: ^/api/v1/auth/login$, roles: PUBLIC_ACCESS } - { path: ^/api/v1/auth/register$, roles: PUBLIC_ACCESS } - { path: ^/api/v1/auth/verify-email$, roles: PUBLIC_ACCESS } diff --git a/config/reference.php b/config/reference.php index 83d8acf..f7f6a1e 100644 --- a/config/reference.php +++ b/config/reference.php @@ -31,7 +31,7 @@ * @psalm-type ImportsConfig = list * @psalm-type ParametersConfig = array|Param|null>|Param|null> * @psalm-type ArgumentsType = list|array @@ -121,14 +121,14 @@ * } * @psalm-type ServicesConfig = array{ * _defaults?: DefaultsType, - * _instanceof?: InstanceofType, + * _instanceof?: array, * ... * } * @psalm-type ExtensionType = array * @psalm-type FrameworkConfig = array{ * secret?: scalar|Param|null, * http_method_override?: bool|Param, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false - * allowed_http_method_override?: list|null, + * allowed_http_method_override?: null|list, * trust_x_sendfile_type_header?: scalar|Param|null, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%" * ide?: scalar|Param|null, // Default: "%env(default::SYMFONY_IDE)%" * test?: bool|Param, @@ -136,9 +136,9 @@ * set_locale_from_accept_language?: bool|Param, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false * set_content_language_from_locale?: bool|Param, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false * enabled_locales?: list, - * trusted_hosts?: list, + * trusted_hosts?: Param|string|list, * trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"] - * trusted_headers?: list, + * trusted_headers?: Param|string|list, * error_controller?: scalar|Param|null, // Default: "error_controller" * handle_all_throwables?: bool|Param, // HttpKernel will handle all kinds of \Throwable. // Default: true * csrf_protection?: bool|array{ @@ -202,35 +202,35 @@ * property?: scalar|Param|null, * service?: scalar|Param|null, * }, - * supports?: list, + * supports?: Param|string|list, * definition_validators?: list, * support_strategy?: scalar|Param|null, - * initial_marking?: list, - * events_to_dispatch?: list|null, - * places?: list, + * initial_marking?: \BackedEnum|Param|string|list, + * events_to_dispatch?: null|list, + * places?: Param|string|list, * }>, - * transitions: list, - * to?: list, * weight?: int|Param, // Default: 1 - * metadata?: list, + * metadata?: array, * }>, - * metadata?: list, + * metadata?: array, * }>, * }, * router?: bool|array{ // Router configuration * enabled?: bool|Param, // Default: false - * resource: scalar|Param|null, + * resource?: scalar|Param|null, * type?: scalar|Param|null, * cache_dir?: scalar|Param|null, // Deprecated: Setting the "framework.router.cache_dir.cache_dir" configuration option is deprecated. It will be removed in version 8.0. // Default: "%kernel.build_dir%" * default_uri?: scalar|Param|null, // The default URI used to generate URLs in a non-HTTP context. // Default: null @@ -261,7 +261,7 @@ * }, * request?: bool|array{ // Request configuration * enabled?: bool|Param, // Default: false - * formats?: array>, + * formats?: array>, * }, * assets?: bool|array{ // Assets configuration * enabled?: bool|Param, // Default: true @@ -271,7 +271,7 @@ * version_format?: scalar|Param|null, // Default: "%%s?%%s" * json_manifest_path?: scalar|Param|null, // Default: null * base_path?: scalar|Param|null, // Default: "" - * base_urls?: list, + * base_urls?: Param|string|list, * packages?: array, + * base_urls?: Param|string|list, * }>, * }, * asset_mapper?: bool|array{ // Asset Mapper configuration * enabled?: bool|Param, // Default: false - * paths?: array, + * paths?: Param|string|array, * excluded_patterns?: list, * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true * server?: bool|Param, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true @@ -303,7 +303,7 @@ * }, * translator?: bool|array{ // Translator configuration * enabled?: bool|Param, // Default: false - * fallbacks?: list, + * fallbacks?: Param|string|list, * logging?: bool|Param, // Default: false * formatter?: scalar|Param|null, // Default: "translator.formatter.default" * cache_dir?: scalar|Param|null, // Default: "%kernel.cache_dir%/translations" @@ -322,7 +322,7 @@ * domains?: list, * locales?: list, * }>, - * globals?: array, @@ -333,7 +333,7 @@ * enabled?: bool|Param, // Default: true * cache?: scalar|Param|null, // Deprecated: Setting the "framework.validation.cache.cache" configuration option is deprecated. It will be removed in version 8.0. * enable_attributes?: bool|Param, // Default: true - * static_method?: list, + * static_method?: Param|string|list, * translation_domain?: scalar|Param|null, // Default: "validators" * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|"loose"|Param, // Default: "html5" * mapping?: array{ @@ -360,10 +360,10 @@ * mapping?: array{ * paths?: list, * }, - * default_context?: list, + * default_context?: array, * named_serializers?: array, + * default_context?: array, * include_built_in_normalizers?: bool|Param, // Whether to include the built-in normalizers // Default: true * include_built_in_encoders?: bool|Param, // Whether to include the built-in encoders // Default: true * }>, @@ -396,7 +396,7 @@ * default_doctrine_dbal_provider?: scalar|Param|null, // Default: "database_connection" * default_pdo_provider?: scalar|Param|null, // Default: null * pools?: array, + * adapters?: Param|string|list, * tags?: scalar|Param|null, // Default: null * public?: bool|Param, // Default: false * default_lifetime?: scalar|Param|null, // Default lifetime of the pool. @@ -417,17 +417,17 @@ * web_link?: bool|array{ // Web links configuration * enabled?: bool|Param, // Default: true * }, - * lock?: bool|string|array{ // Lock configuration + * lock?: Param|bool|string|array{ // Lock configuration * enabled?: bool|Param, // Default: false - * resources?: array>, + * resources?: Param|string|array>, * }, - * semaphore?: bool|string|array{ // Semaphore configuration + * semaphore?: Param|bool|string|array{ // Semaphore configuration * enabled?: bool|Param, // Default: false - * resources?: array, + * resources?: Param|string|array, * }, * messenger?: bool|array{ // Messenger configuration * enabled?: bool|Param, // Default: true - * routing?: array, * }>, * serializer?: array{ @@ -437,12 +437,12 @@ * context?: array, * }, * }, - * transports?: array, + * options?: array, * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null - * retry_strategy?: string|array{ + * retry_strategy?: Param|string|array{ * service?: scalar|Param|null, // Service id to override the retry strategy entirely. // Default: null * max_retries?: int|Param, // Default: 3 * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 @@ -453,26 +453,26 @@ * rate_limiter?: scalar|Param|null, // Rate limiter name to use when processing messages. // Default: null * }>, * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null - * stop_worker_on_signals?: list, + * stop_worker_on_signals?: Param|int|string|list, * default_bus?: scalar|Param|null, // Default: null * buses?: array, * }>, * }>, * }, * scheduler?: bool|array{ // Scheduler configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * }, * 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, @@ -510,9 +510,9 @@ * retry_failed?: bool|array{ * enabled?: bool|Param, // Default: false * retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null - * http_codes?: array, + * methods?: Param|string|list, * }>, * max_retries?: int|Param, // Default: 3 * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 @@ -522,7 +522,7 @@ * }, * }, * mock_response_factory?: scalar|Param|null, // The id of the service that should generate mock responses. It should be either an invokable or an iterable. - * scoped_clients?: array, + * methods?: Param|string|list, * }>, * max_retries?: int|Param, // Default: 3 * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 @@ -582,10 +582,10 @@ * transports?: array, * envelope?: array{ // Mailer Envelope configuration * sender?: scalar|Param|null, - * recipients?: list, - * allowed_recipients?: list, + * recipients?: Param|string|list, + * allowed_recipients?: Param|string|list, * }, - * headers?: array, * dkim_signer?: bool|array{ // DKIM signer configuration @@ -622,20 +622,20 @@ * chatter_transports?: array, * texter_transports?: array, * notification_on_failed_messages?: bool|Param, // Default: false - * channel_policy?: array>, + * channel_policy?: array>, * admin_recipients?: list, * }, * rate_limiter?: bool|array{ // Rate limiter configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * limiters?: array, + * policy?: "fixed_window"|"token_bucket"|"sliding_window"|"compound"|"no_limit"|Param, // The algorithm to be used by this limiter. + * limiters?: Param|string|list, * limit?: int|Param, // The maximum allowed hits in a fixed interval or burst. * interval?: scalar|Param|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". @@ -658,20 +658,20 @@ * allow_safe_elements?: bool|Param, // Allows "safe" elements and attributes. // Default: false * allow_static_elements?: bool|Param, // Allows all static elements and attributes from the W3C Sanitizer API standard. // Default: false * allow_elements?: array, - * block_elements?: list, - * drop_elements?: list, + * block_elements?: Param|string|list, + * drop_elements?: Param|string|list, * allow_attributes?: array, * drop_attributes?: array, * force_attributes?: array>, * force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false - * allowed_link_schemes?: list, - * allowed_link_hosts?: list|null, + * allowed_link_schemes?: Param|string|list, + * allowed_link_hosts?: Param|null|string|list, * allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false - * allowed_media_schemes?: list, - * allowed_media_hosts?: list|null, + * allowed_media_schemes?: Param|string|list, + * allowed_media_hosts?: Param|null|string|list, * allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false - * with_attribute_sanitizers?: list, - * without_attribute_sanitizers?: list, + * with_attribute_sanitizers?: Param|string|list, + * without_attribute_sanitizers?: Param|string|list, * max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0 * }>, * }, @@ -679,7 +679,7 @@ * enabled?: bool|Param, // Default: false * message_bus?: scalar|Param|null, // The message bus to use. // Default: "messenger.default_bus" * routing?: array, * }, @@ -826,7 +826,7 @@ * }, * termsOfService?: scalar|Param|null, // A URL to the Terms of Service for the API. MUST be in the format of a URL. // Default: null * tags?: list, * license?: array{ @@ -942,14 +942,15 @@ * name?: mixed, * allow_create?: mixed, * item_uri_template?: mixed, - * ... + * ... * }, + * ... * } * @psalm-type DoctrineConfig = array{ * dbal?: array{ * default_connection?: scalar|Param|null, - * types?: array, * driver_schemes?: array, @@ -1035,6 +1036,7 @@ * use_savepoints?: bool|Param, // Use savepoints for nested transactions * instancename?: scalar|Param|null, // Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection. It is generally used to connect to an Oracle RAC server to select the name of a particular instance. * connectstring?: scalar|Param|null, // Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.When using this option, you will still need to provide the user and password parameters, but the other parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods from Doctrine\DBAL\Connection will no longer function as expected. + * ... * }>, * replicas?: array * }>, + * ... * }>, + * ... * }, * orm?: array{ * default_entity_manager?: scalar|Param|null, @@ -1083,17 +1088,17 @@ * evict_cache?: bool|Param, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false * }, * entity_managers?: array, * }>, * }>, + * ... * }, * connection?: scalar|Param|null, * class_metadata_factory_name?: scalar|Param|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" @@ -1122,7 +1128,7 @@ * report_fields_where_declared?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.16 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/10455. // Default: true * validate_xml_mapping?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14. See https://github.com/doctrine/orm/pull/6728. // Default: false * second_level_cache?: array{ - * region_cache_driver?: string|array{ + * region_cache_driver?: Param|string|array{ * type?: scalar|Param|null, // Default: null * id?: scalar|Param|null, * pool?: scalar|Param|null, @@ -1133,7 +1139,7 @@ * enabled?: bool|Param, // Default: true * factory?: scalar|Param|null, * regions?: array, * }, * hydrators?: array, - * mappings?: array, * datetime_functions?: array, * }, - * filters?: array, + * ... * }>, * identity_generation_preferences?: array, * }>, * resolve_target_entities?: array, + * ... * }, * } * @psalm-type DoctrineMigrationsConfig = array{ @@ -1232,6 +1240,7 @@ * id?: scalar|Param|null, * type?: scalar|Param|null, * value?: mixed, + * ... * }>, * autoescape_service?: scalar|Param|null, // Default: null * autoescape_service_method?: scalar|Param|null, // Default: null @@ -1243,7 +1252,7 @@ * 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?: list, + * file_name_pattern?: Param|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" @@ -1280,9 +1289,9 @@ * allow_if_all_abstain?: bool|Param, // Default: false * allow_if_equal_granted_denied?: bool|Param, // Default: true * }, - * password_hashers?: array, + * migrate_from?: Param|string|list, * hash_algorithm?: scalar|Param|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" * key_length?: scalar|Param|null, // Default: 40 * ignore_case?: bool|Param, // Default: false @@ -1296,26 +1305,26 @@ * providers?: array, + * providers?: Param|string|list, * }, * entity?: array{ - * class: scalar|Param|null, // The full entity class name of your user class. + * class?: scalar|Param|null, // The full entity class name of your user class. * property?: scalar|Param|null, // Default: null * manager_name?: scalar|Param|null, // Default: null * }, * memory?: array{ * users?: array, + * roles?: Param|string|list, * }>, * }, * ldap?: array{ - * service: scalar|Param|null, - * base_dn: scalar|Param|null, + * service?: scalar|Param|null, + * base_dn?: scalar|Param|null, * search_dn?: scalar|Param|null, // Default: null * search_password?: scalar|Param|null, // Default: null * extra_fields?: list, - * default_roles?: list, + * default_roles?: Param|string|list, * role_fetcher?: scalar|Param|null, // Default: null * uid_key?: scalar|Param|null, // Default: "sAMAccountName" * filter?: scalar|Param|null, // Default: "({uid_key}={user_identifier})" @@ -1325,10 +1334,10 @@ * class?: scalar|Param|null, // Default: "Lexik\\Bundle\\JWTAuthenticationBundle\\Security\\User\\JWTUser" * }, * }>, - * firewalls: array, + * methods?: Param|string|list, * security?: bool|Param, // Default: true * user_checker?: scalar|Param|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" * request_matcher?: scalar|Param|null, @@ -1347,8 +1356,8 @@ * path?: scalar|Param|null, // Default: "/logout" * target?: scalar|Param|null, // Default: "/" * invalidate_session?: bool|Param, // Default: true - * clear_site_data?: list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>, - * delete_cookies?: array, + * delete_cookies?: Param|string|array, + * signature_properties?: list, * lifetime?: int|Param, // The lifetime of the login link in seconds. // Default: 600 * max_uses?: int|Param, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null * used_link_cache?: scalar|Param|null, // Cache service id used to expired links of max_uses is set. @@ -1490,14 +1499,14 @@ * success_handler?: scalar|Param|null, * failure_handler?: scalar|Param|null, * realm?: scalar|Param|null, // Default: null - * token_extractors?: list, - * token_handler: string|array{ + * token_extractors?: Param|string|list, + * token_handler?: Param|string|array{ * id?: scalar|Param|null, - * oidc_user_info?: string|array{ - * base_uri: scalar|Param|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). + * oidc_user_info?: Param|string|array{ + * base_uri?: scalar|Param|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). * discovery?: array{ // Enable the OIDC discovery. * cache?: array{ - * id: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. + * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. * }, * }, * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" @@ -1505,27 +1514,27 @@ * }, * oidc?: array{ * discovery?: array{ // Enable the OIDC discovery. - * base_uri: list, + * base_uri?: Param|string|list, * cache?: array{ - * id: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. + * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. * }, * }, * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" - * audience: scalar|Param|null, // Audience set in the token, for validation purpose. - * issuers: list, + * audience?: scalar|Param|null, // Audience set in the token, for validation purpose. + * issuers?: list, * algorithm?: array, - * algorithms: list, + * algorithms?: list, * key?: scalar|Param|null, // Deprecated: The "key" option is deprecated and will be removed in 8.0. Use the "keyset" option instead. // JSON-encoded JWK used to sign the token (must contain a "kty" key). * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). * encryption?: bool|array{ * enabled?: bool|Param, // Default: false * enforce?: bool|Param, // When enabled, the token shall be encrypted. // Default: false - * algorithms: list, - * keyset: scalar|Param|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). + * algorithms?: list, + * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). * }, * }, * cas?: array{ - * validation_url: scalar|Param|null, // CAS server validation URL + * validation_url?: scalar|Param|null, // CAS server validation URL * prefix?: scalar|Param|null, // CAS prefix // Default: "cas" * http_client?: scalar|Param|null, // HTTP Client service // Default: null * }, @@ -1548,10 +1557,10 @@ * remember_me?: array{ * secret?: scalar|Param|null, // Default: "%kernel.secret%" * service?: scalar|Param|null, - * user_providers?: list, + * user_providers?: Param|string|list, * catch_exceptions?: bool|Param, // Default: true * signature_properties?: list, - * token_provider?: string|array{ + * token_provider?: Param|string|array{ * service?: scalar|Param|null, // The service ID of a custom remember-me token provider. * doctrine?: bool|array{ * enabled?: bool|Param, // Default: false @@ -1576,14 +1585,14 @@ * path?: scalar|Param|null, // Use the urldecoded format. // Default: null * host?: scalar|Param|null, // Default: null * port?: int|Param, // Default: null - * ips?: list, + * ips?: Param|string|list, * attributes?: array, * route?: scalar|Param|null, // Default: null - * methods?: list, + * methods?: Param|string|list, * allow_if?: scalar|Param|null, // Default: null - * roles?: list, + * roles?: Param|string|list, * }>, - * role_hierarchy?: array>, + * role_hierarchy?: array>, * } * @psalm-type LexikJwtAuthenticationConfig = array{ * public_key?: scalar|Param|null, // The key used to sign tokens (useless for HMAC). If not set, the key will be automatically computed from the secret key. // Default: null @@ -1637,14 +1646,14 @@ * access_token_issuance?: bool|array{ * enabled?: bool|Param, // Default: false * signature?: array{ - * algorithm: scalar|Param|null, // The algorithm use to sign the access tokens. - * key: scalar|Param|null, // The signature key. It shall be JWK encoded. + * algorithm?: scalar|Param|null, // The algorithm use to sign the access tokens. + * key?: scalar|Param|null, // The signature key. It shall be JWK encoded. * }, * encryption?: bool|array{ * enabled?: bool|Param, // Default: false - * key_encryption_algorithm: scalar|Param|null, // The key encryption algorithm is used to encrypt the token. - * content_encryption_algorithm: scalar|Param|null, // The key encryption algorithm is used to encrypt the token. - * key: scalar|Param|null, // The encryption key. It shall be JWK encoded. + * key_encryption_algorithm?: scalar|Param|null, // The key encryption algorithm is used to encrypt the token. + * content_encryption_algorithm?: scalar|Param|null, // The key encryption algorithm is used to encrypt the token. + * key?: scalar|Param|null, // The encryption key. It shall be JWK encoded. * }, * }, * access_token_verification?: bool|array{ @@ -1654,7 +1663,7 @@ * claim_checkers?: list, * mandatory_claims?: list, * allowed_algorithms?: list, - * keyset: scalar|Param|null, // The signature keyset. It shall be JWKSet encoded. + * keyset?: scalar|Param|null, // The signature keyset. It shall be JWKSet encoded. * }, * encryption?: bool|array{ * enabled?: bool|Param, // Default: false @@ -1662,7 +1671,7 @@ * header_checkers?: list, * allowed_key_encryption_algorithms?: list, * allowed_content_encryption_algorithms?: list, - * keyset: scalar|Param|null, // The encryption keyset. It shall be JWKSet encoded. + * keyset?: scalar|Param|null, // The encryption keyset. It shall be JWKSet encoded. * }, * }, * blocklist_token?: bool|array{ @@ -1740,7 +1749,10 @@ final class App */ public static function config(array $config): array { - return AppReference::config($config); + /** @var ConfigType $config */ + $config = AppReference::config($config); + + return $config; } } diff --git a/config/services.yaml b/config/services.yaml index df2bae4..a6a491f 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -7,6 +7,13 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: + mail_sender: 'noreply@signalist.app' + retention_disabled: false + linkedin_version: '202605' + node_binary: '/usr/bin/node' + backend_url: 'http://localhost:8000' + billing_empty: '' + plan_limits: !php/const App\Domain\Billing\Service\PlanCatalog::DEFAULTS services: # default configuration for services in *this* file @@ -14,17 +21,109 @@ services: autowire: true # Automatically injects dependencies in your services. autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. bind: + string $mailSender: '%env(default:mail_sender:SIGNALIST_MAIL_SENDER)%' string $environment: '%kernel.environment%' # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name App\: resource: '../src/' + exclude: '../src/DataFixtures/' # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + App\Domain\Social\Port\RecapIntelligenceInterface: + alias: App\Infrastructure\AI\UnconfiguredRecapIntelligence + + App\Domain\Social\Port\CrosspostAdapterInterface: + alias: App\Infrastructure\External\Social\PlatformCrosspostAdapter + App\Infrastructure\External\Social\PlatformCrosspostAdapter: + arguments: + $linkedinVersion: '%env(default:linkedin_version:LINKEDIN_API_VERSION)%' + + App\Domain\Privacy\MessageHandler\ApplyRetentionMessageHandler: + arguments: + $enabled: '%env(bool:default:retention_disabled:SIGNALIST_RETENTION_ENABLED)%' + + App\Domain\Spotlight\Port\ArticleQuestionInterface: + alias: App\Infrastructure\AI\UnconfiguredArticleQuestions + # Repository bindings + App\Domain\Sync\Port\RaindropClientInterface: + alias: App\Infrastructure\External\Raindrop\RaindropClient + App\Domain\Connection\Port\BlueskyGatewayInterface: + alias: App\Infrastructure\Connection\BlueskyBridge + App\Infrastructure\Connection\BlueskyBridge: + arguments: + $projectDir: '%kernel.project_dir%' + $nodeBinary: '%env(default:node_binary:SIGNALIST_NODE_BINARY)%' + $databaseUrl: '%env(resolve:DATABASE_URL)%' + $encodedKey: '%env(default:billing_empty:CREDENTIAL_ENCRYPTION_KEY)%' + $privateKey: '%env(default:billing_empty:BLUESKY_CLIENT_PRIVATE_KEY)%' + $backendUrl: '%env(default:backend_url:SIGNALIST_PUBLIC_API_URL)%' + $frontendUrl: '%env(FRONTEND_URL)%' + App\Infrastructure\Security\TokenCipher: + arguments: + $encodedKey: '%env(default:billing_empty:CREDENTIAL_ENCRYPTION_KEY)%' + App\Domain\Connection\Port\OAuthGatewayInterface: + alias: App\Infrastructure\Connection\ProviderOAuthGateway + App\Infrastructure\Connection\ProviderOAuthGateway: + arguments: + $backendUrl: '%env(default:backend_url:SIGNALIST_PUBLIC_API_URL)%' + $credentials: + raindrop: + clientId: '%env(default:billing_empty:RAINDROP_CLIENT_ID)%' + clientSecret: '%env(default:billing_empty:RAINDROP_CLIENT_SECRET)%' + linkedin: + clientId: '%env(default:billing_empty:LINKEDIN_CLIENT_ID)%' + clientSecret: '%env(default:billing_empty:LINKEDIN_CLIENT_SECRET)%' + x: + clientId: '%env(default:billing_empty:X_CLIENT_ID)%' + clientSecret: '%env(default:billing_empty:X_CLIENT_SECRET)%' + App\UI\Controller\OAuthCallbackController: + arguments: + $frontendUrl: '%env(FRONTEND_URL)%' + App\Domain\Billing\Service\PlanCatalog: + arguments: + $limits: '%plan_limits%' + App\Domain\Billing\Port\BillingGatewayInterface: + alias: App\Infrastructure\Billing\StripeBillingGateway + App\Infrastructure\Billing\StripeBillingGateway: + arguments: + $secretKey: '%env(default:billing_empty:STRIPE_SECRET_KEY)%' + $webhookSecret: '%env(default:billing_empty:STRIPE_WEBHOOK_SECRET)%' + $teamPriceId: '%env(default:billing_empty:STRIPE_TEAM_PRICE_ID)%' + $enterprisePriceId: '%env(default:billing_empty:STRIPE_ENTERPRISE_PRICE_ID)%' + $frontendUrl: '%env(FRONTEND_URL)%' + App\Infrastructure\Billing\BillingWebhookReceiver: + arguments: + $webhookSecret: '%env(default:billing_empty:STRIPE_WEBHOOK_SECRET)%' + App\Infrastructure\Organization\InvitationToken: + arguments: + $appSecret: '%env(APP_SECRET)%' + $frontendUrl: '%env(FRONTEND_URL)%' + App\Infrastructure\MCP\McpServerFactory: + arguments: + $cacheDir: '%kernel.cache_dir%' + App\UI\Controller\McpController: + arguments: + $frontendUrl: '%env(FRONTEND_URL)%' + $backendUrl: '%env(default:backend_url:SIGNALIST_PUBLIC_API_URL)%' + App\Domain\Search\Port\SearchRepositoryInterface: + alias: App\Infrastructure\Persistence\Search\PgvectorSearchRepository + App\UI\Controller\WaitlistController: + arguments: + $waitlistLimiter: '@limiter.waitlist' + App\Infrastructure\Waitlist\WaitlistToken: + arguments: + $appSecret: '%env(APP_SECRET)%' + App\Domain\Waitlist\MessageHandler\SendWaitlistConfirmationMessageHandler: + arguments: + $frontendUrl: '%env(FRONTEND_URL)%' + App\Domain\AI\Port\ArticleIntelligenceInterface: + alias: App\Infrastructure\AI\UnconfiguredArticleIntelligence + App\Domain\Category\Port\CategoryRepositoryInterface: alias: App\Infrastructure\Persistence\Category\DoctrineCategoryRepository diff --git a/config/services_dev.yaml b/config/services_dev.yaml new file mode 100644 index 0000000..147ce7d --- /dev/null +++ b/config/services_dev.yaml @@ -0,0 +1,6 @@ +services: + _defaults: + autowire: true + autoconfigure: true + App\DataFixtures\: + resource: '../src/DataFixtures/' diff --git a/config/services_test.yaml b/config/services_test.yaml index 94ebf08..b9ed285 100644 --- a/config/services_test.yaml +++ b/config/services_test.yaml @@ -1,4 +1,24 @@ services: + App\Infrastructure\Billing\StripeBillingGateway: + arguments: + $secretKey: '' + $teamPriceId: '' + $webhookSecret: '' + $enterprisePriceId: '' + $frontendUrl: 'http://localhost:5173' + App\Infrastructure\Connection\ProviderOAuthGateway: + arguments: + $credentials: [] + $backendUrl: 'http://localhost:18000' + App\Infrastructure\Connection\BlueskyBridge: + arguments: + $privateKey: '' + $encodedKey: '' + $projectDir: '%kernel.project_dir%' + $nodeBinary: '/usr/bin/node' + $databaseUrl: '%env(resolve:DATABASE_URL)%' + $backendUrl: 'http://localhost:18000' + $frontendUrl: 'http://localhost:5173' _defaults: autowire: true autoconfigure: true diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..7c527e8 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,77 @@ +# Standalone production topology. Configure through a protected operator env file. +x-app: &app + image: signalist:${SIGNALIST_IMAGE_TAG:-local} + build: + context: . + dockerfile: docker/frankenphp/Dockerfile + restart: unless-stopped + env_file: ${SIGNALIST_PRODUCTION_ENV_FILE:?Set a protected production environment file} + environment: + APP_ENV: prod + APP_DEBUG: "0" + JWT_SECRET_KEY: /run/keys/private.pem + JWT_PUBLIC_KEY: /run/keys/public.pem + volumes: + - ${SIGNALIST_JWT_DIR:?Set the directory containing JWT keys}:/run/keys:ro + - app_cache:/app/var + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: [signalist] +services: + app: + <<: *app + ports: ["80:80", "443:443", "443:443/udp"] + volumes: + - ${SIGNALIST_JWT_DIR:?Set the directory containing JWT keys}:/run/keys:ro + - app_cache:/app/var + - caddy_data:/data + - caddy_config:/config + healthcheck: + test: ["CMD", "php", "-r", "exit(is_dir('/app/frontend/dist') && is_file('/app/vendor/autoload.php') ? 0 : 1);"] + interval: 30s + timeout: 5s + retries: 3 + messenger: + <<: *app + command: php bin/console messenger:consume oauth_priority async --time-limit=3600 --memory-limit=256M + scheduler: + <<: *app + command: php bin/console messenger:consume scheduler_signalist --time-limit=3600 --memory-limit=256M + postgres: + image: pgvector/pgvector:pg16 + restart: unless-stopped + environment: + POSTGRES_USER: signalist + POSTGRES_DB: signalist + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set the database password} + volumes: + - postgres_data:/var/lib/postgresql/data + - ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U signalist -d signalist"] + interval: 10s + timeout: 5s + retries: 5 + networks: [signalist] + redis: + image: redis:7-alpine + restart: unless-stopped + command: redis-server --appendonly yes + volumes: ["redis_data:/data"] + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: [signalist] +networks: + signalist: +volumes: + postgres_data: + redis_data: + app_cache: + caddy_data: + caddy_config: diff --git a/docker-compose.yml b/docker-compose.yml index 0541237..f81d607 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,9 +6,9 @@ services: container_name: signalist-app restart: unless-stopped ports: - - "8000:80" - - "4443:443" - - "4443:443/udp" + - "${SIGNALIST_HTTP_PORT:-8000}:80" + - "${SIGNALIST_HTTPS_PORT:-4443}:443" + - "${SIGNALIST_HTTPS_PORT:-4443}:443/udp" volumes: - .:/app:cached - ./docker/frankenphp/php.ini:/usr/local/etc/php/conf.d/app.ini:ro @@ -25,7 +25,8 @@ services: - REDIS_URL=redis://redis:6379 - MESSENGER_TRANSPORT_DSN=redis://redis:6379 - MAILER_DSN=smtp://mailpit:1025 - - FRONTEND_URL=http://localhost:5173 + - FRONTEND_URL=${FRONTEND_URL:-http://localhost:5173} + - SIGNALIST_PUBLIC_API_URL=${SIGNALIST_PUBLIC_API_URL:-http://localhost:${SIGNALIST_HTTP_PORT:-8000}} - FRANKENPHP_CONFIG=worker ./public/index.php - SERVER_NAME=:80 depends_on: @@ -56,7 +57,8 @@ services: - REDIS_URL=redis://redis:6379 - MESSENGER_TRANSPORT_DSN=redis://redis:6379 - MAILER_DSN=smtp://mailpit:1025 - - FRONTEND_URL=http://localhost:5173 + - FRONTEND_URL=${FRONTEND_URL:-http://localhost:5173} + - SIGNALIST_PUBLIC_API_URL=${SIGNALIST_PUBLIC_API_URL:-http://localhost:${SIGNALIST_HTTP_PORT:-8000}} depends_on: postgres: condition: service_healthy @@ -64,10 +66,16 @@ services: condition: service_healthy mailpit: condition: service_started - command: sh -c 'while true; do php bin/console messenger:consume async --time-limit=30 --memory-limit=256M -vv; done' + command: sh -c 'while true; do php bin/console messenger:consume oauth_priority async --time-limit=30 --memory-limit=256M; done' networks: - signalist + scheduler: + extends: + service: messenger + container_name: signalist-scheduler + command: php bin/console messenger:consume scheduler_signalist --time-limit=3600 --memory-limit=256M + postgres: image: pgvector/pgvector:pg16 container_name: signalist-postgres diff --git a/docker/frankenphp/Caddyfile.prod b/docker/frankenphp/Caddyfile.prod new file mode 100644 index 0000000..b635bab --- /dev/null +++ b/docker/frankenphp/Caddyfile.prod @@ -0,0 +1,34 @@ +{ + frankenphp + order php_server before file_server +} + +{$APP_DOMAIN} { + encode zstd br gzip + header { + X-Content-Type-Options "nosniff" + Referrer-Policy "strict-origin-when-cross-origin" + Strict-Transport-Security "max-age=31536000" + Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self'; connect-src 'self'; frame-src 'self'; object-src 'none'; frame-ancestors 'none';" + -Server + } + handle /health { + respond "OK" 200 + } + @backend path /api/* /mcp/* /oauth/* + handle @backend { + root * /app/public + php_server + } + handle /assets/* { + root * /app/frontend/dist + header Cache-Control "public, max-age=31536000, immutable" + file_server + } + handle { + root * /app/frontend/dist + header Cache-Control "no-cache" + try_files {path} /index.html + file_server + } +} diff --git a/docker/frankenphp/Dockerfile b/docker/frankenphp/Dockerfile index 48ecc9f..d22bf71 100644 --- a/docker/frankenphp/Dockerfile +++ b/docker/frankenphp/Dockerfile @@ -1,4 +1,22 @@ -FROM dunglas/frankenphp:1-php8.5-alpine +FROM dunglas/frankenphp:1.13.0-builder-php8.5-alpine AS patched-frankenphp +WORKDIR /go/src/app/caddy/frankenphp +# Keep upstream modules and PHP linkage; patch the embedded HTTP/2 dependency. +RUN go get golang.org/x/net@v0.60.0 \ + && GOBIN=/usr/local/bin ../../go.sh install \ + -ldflags "-w -s -extldflags '-Wl,-z,stack-size=0x80000' -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP 1.13.0 PHP $PHP_VERSION Caddy' -X 'github.com/caddyserver/caddy/v2.CustomBinaryName=frankenphp' -X 'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy'" -buildvcs=true \ + && frankenphp version \ + && frankenphp build-info + +FROM node:24-alpine AS frontend +WORKDIR /build/frontend +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci --ignore-scripts +COPY frontend/ ./ +RUN npm run build + +FROM dunglas/frankenphp:1.13.0-php8.5-alpine +COPY --from=patched-frankenphp /usr/local/bin/frankenphp /usr/local/bin/frankenphp +RUN setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp # Install mlocati/docker-php-extension-installer for easier extension management ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ @@ -10,6 +28,8 @@ RUN apk add --no-cache \ curl \ zip \ unzip \ + nodejs \ + npm \ && install-php-extensions \ pdo_pgsql \ intl \ @@ -29,13 +49,16 @@ COPY . . # Copy PHP configuration COPY docker/frankenphp/php.ini /usr/local/etc/php/conf.d/app.ini -COPY docker/frankenphp/Caddyfile /etc/caddy/Caddyfile +COPY docker/frankenphp/Caddyfile.prod /etc/caddy/Caddyfile +COPY --from=frontend /build/frontend/dist /app/frontend/dist ENV APP_ENV=prod # Install dependencies without running post-install scripts (cache:clear requires # env vars and services that are not available at image build time) RUN composer install --no-dev --optimize-autoloader --classmap-authoritative --no-interaction --no-scripts \ + && cd /app/integrations/bluesky && npm ci --omit=dev --ignore-scripts \ + && touch /app/.env \ && mkdir -p /app/var \ && chown -R www-data:www-data /app/var diff --git a/docker/frankenphp/Dockerfile.dev b/docker/frankenphp/Dockerfile.dev index 40284e1..a830501 100644 --- a/docker/frankenphp/Dockerfile.dev +++ b/docker/frankenphp/Dockerfile.dev @@ -1,4 +1,15 @@ -FROM dunglas/frankenphp:1-php8.5-alpine +FROM dunglas/frankenphp:1.13.0-builder-php8.5-alpine AS patched-frankenphp +WORKDIR /go/src/app/caddy/frankenphp +# Keep upstream modules and PHP linkage; patch the embedded HTTP/2 dependency. +RUN go get golang.org/x/net@v0.60.0 \ + && GOBIN=/usr/local/bin ../../go.sh install \ + -ldflags "-w -s -extldflags '-Wl,-z,stack-size=0x80000' -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP 1.13.0 PHP $PHP_VERSION Caddy' -X 'github.com/caddyserver/caddy/v2.CustomBinaryName=frankenphp' -X 'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy'" -buildvcs=true \ + && frankenphp version \ + && frankenphp build-info + +FROM dunglas/frankenphp:1.13.0-php8.5-alpine +COPY --from=patched-frankenphp /usr/local/bin/frankenphp /usr/local/bin/frankenphp +RUN setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp # Install mlocati/docker-php-extension-installer for easier extension management ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ @@ -23,6 +34,9 @@ RUN apk add --no-cache \ # Install Composer COPY --from=composer:2 /usr/bin/composer /usr/bin/composer +# Official Bluesky OAuth SDK runs only through the bounded background adapter. +RUN apk add --no-cache nodejs npm + # Set working directory WORKDIR /app diff --git a/docs/BROWSER-QA.md b/docs/BROWSER-QA.md new file mode 100644 index 0000000..46154ce --- /dev/null +++ b/docs/BROWSER-QA.md @@ -0,0 +1,66 @@ +# Browser QA — 2026-10-13 + +Testing uses the Codex in-app browser against the local React app, built landing pages and Mailpit. Accounts and content are synthetic (`example.test` addresses). Existing accounts and content were not edited. External AI, payment and social credentials remain unconfigured; no real email, payment or publication was sent. + +## Results observed in the browser + +| Area | Observed result | +| --- | --- | +| Authentication | Invalid fixture credentials rejected. Synthetic registration validates password mismatch, queues verification email, supports resend, denies login before verification, accepts the signed verification link and permits subsequent login. Logout clears access. | +| Onboarding/categories | Empty-account onboarding shown. First-category dialog creates a category and slug. Spotlight also creates a category and navigates to it; its add-feed command queues a successful public RSS crawl. | +| RSS | Official Symfony feed added through Spotlight; background crawl updates the fetched timestamp and imports 10 articles visible in the inbox. A 404 source produces a safe Error state. | +| Reading | Inbox lists seeded articles; marking read persists into article detail and after reload. Article content renders without the injected script text. Source links and share menu available. | +| Bookmarks/tags | Bookmark appears in saved list. Article tag assignment, catalog creation, filtering and rename exercised; filtered result points to the matching article. | +| Search/Spotlight | Full-text search returns the correct seeded PostgreSQL article. Command overlay works; category command succeeds. AI question/enrichment requests show configuration/consent failure feedback. | +| Newsletters | Manual newsletter saved; asynchronous draft reaches Draft ready. HTML preview groups the saved article by category and retains its source URL. Send without consent is denied. After granting consent, delivery reaches Sent and the message appears in local Mailpit. A custom 72-hour interval persists and shows the next delivery date; the QA newsletter was restored to Manual afterward. | +| Workspaces | Workspace, shared category and shared feed created. Shared categories stay outside the personal sidebar. Shared article detail renders. Shared bookmark, annotation and activity entries persist. | +| Membership/access | Free plan blocks a second member. Temporary local Team entitlement allows a synthetic reader invitation, delivered to Mailpit and accepted by the matching verified account. Reader cannot manage feeds; personal inbox remains empty. Reader read state does not mark the administrator's article read. Last-administrator demotion is rejected; another member can become curator. | +| Privacy | Profile display name saved. Newsletter consent persists. Restriction disables optional consent controls; resuming restores them. JSON export downloaded and checked for seeded articles/bookmark and absence of profile credentials. Account deletion requires a password and explicit final action; that final action was not executed. | +| Social | Provider-independent recap reaches Draft, includes source link, supports editing, copying to clipboard and saving. Publication disabled without a connected account. | +| Connections | Raindrop, LinkedIn, X and Bluesky show configuration pending and disabled connection controls. Successful live OAuth/sync cannot be tested without provider setup. | +| Billing | Personal Free and workspace Team scopes show separate quotas and seat counts. Unconfigured checkout and portal controls disabled. No payment attempted. | +| Waitlist/landing | Browser submission succeeds through a local same-origin API proxy; confirmation mail appears in Mailpit; signed confirmation succeeds. French landing navigation and privacy page render correctly. Withdrawal's final erasure action was not executed. | +| Translation | English/French application controls and workspace annotation views render translated labels. | + +## Fixes made during QA + +- Workspace quota failures now explain the plan limit rather than suggesting invalid permissions. Reproduced again in the browser on a new Free workspace; regression tests cover plan-limit and unexpected errors. +- Login displays a generic message for HTTP 500/503 instead of exposing server exception details. Regression tests cover both statuses. +- Check-email translations render their intended emphasis rather than displaying literal `` markup. A regression test checks the rendered email element. +- Landing waitlist email field now has a translated accessible label. Rebuilt markup was inspected in the browser. + +Frontend verification after the follow-up fixes: 40 tests passed; production build passed. The build retains a non-blocking bundle-size warning. ESLint, TypeScript and `git diff --check` also passed. The six-page landing build passed after the accessible-label change. The targeted MCP integration test passed with nine assertions. + +## Limits and environment interruptions + +- The first public RSS test URL returned HTTP 404 and produced the expected safe Error state. The feed linked from Symfony’s official blog then crawled successfully and its 10 articles were verified in the browser. +- Container project mounts became unreadable (`Operation not permitted`). Recreating only Signalist containers did not restore them. QA continued with installed host PHP, a Redis extension built only in temporary storage, and loopback URLs; global PHP configuration was not changed. The user restarted Podman, restoring mount access. Normal Signalist containers were restarted and the temporary host PHP server/worker stopped; final RSS checks ran against the normal container runtime. +- In-app browser download observation stalled once; after recovery the export downloaded successfully. A later delivery click timed out, but its result was checked before retrying: it had already reached Sent, preventing duplicate delivery. +- Direct cross-origin landing requests failed in the in-app browser. Local API requests returned 202 with the expected CORS headers. The temporary landing proxy establishes that the form, backend and confirmation flow work together, but does not validate a public deployment's cross-origin transport. +- The first viewport attempt inspected the wrong tab and remained at 1280px. The follow-up applied the override to the original QA tab and verified a real 390px viewport. The French screenshot from the first pass remains a desktop view. +- Direct MCP navigation was blocked by the browser client. The local MCP integration test independently verified authentication and owner boundaries (one test, nine assertions). +- Live AI inference, real OAuth, Raindrop synchronization, Stripe checkout/webhooks and external social publication remain credential-dependent checks. The scheduled-newsletter follow-up below exercises the existing scheduler/worker end to end. MCP protocol behavior is covered by its local integration test; browser inspection alone does not prove it. +- Irreversible account/content deletion and waitlist erasure were not executed in this browser pass. Existing automated API tests cover deletion behavior. + +Screenshots are retained locally in `var/browser-qa/`: shared workspace/annotation, French desktop view, corrected quota feedback, and the successfully crawled RSS inbox. Synthetic QA data remains available for review. + +## Follow-up on the current Podman environment + +The continuation kept the running Podman containers and configuration in place: no starts, stops, restarts, rebuilds, migrations, dependency installation or runtime replacements. App, Messenger and scheduler container identities/startup times stayed unchanged. + +- Created a separate `QA Scheduler Probe` newsletter through the UI and verified the worker's delivery target was local Mailpit. Only that synthetic record's due timestamp was moved into the past. The existing scheduler picked it up, the worker generated and delivered it, the browser showed Sent, and the email appeared in current Mailpit. The record was restored to Manual and its next-delivery indicator disappeared. +- Tested the original browser tab at 390px. Mobile navigation opens and routes correctly. Inbox, search, settings, social drafts, connections, billing, tags, feeds and workspace controls fit the viewport. Loaded inbox content also measures 390px with no document overflow. Desktop sizing was restored afterward. +- Fixed an unlabeled mobile navigation button using translated text and `aria-expanded`; added a navigation activation regression test. +- Reproduced workspace horizontal overflow (629px content in a 390px viewport). Constrained the main flex area with `minWidth: 0` and an explicit mobile width; the same workspace now measures 390px and keeps its controls visible. +- Final checks: 40 frontend tests, ESLint, TypeScript and production build passed; `git diff --check` passed. The non-blocking bundle-size warning remains. + +Additional local evidence: `mobile-newsletter.jpg`, `mobile-workspace.jpg`, `mobile-inbox.jpg`, `mobile-results.json` and `scheduled-mail.jpg` in `var/browser-qa/`. + + +## Bundle and dependency follow-up + +On 2026-10-13, production pages and the authenticated layout were split into on-demand chunks. The largest emitted JS chunk decreased from 877.09 KB to 495.59 KB (gzip 274.42 KB to 163.68 KB). The build no longer reports its bundle-size warning. Earlier entries above describe the historical build before this follow-up. + +A production preview on port 4173 used the current Podman backend on port 18000. Synthetic QA sign-in, inbox, settings and newsletters loaded successfully; the navigation stayed available during page loading. Screenshot: `var/browser-qa/bundle-preview.jpg` (local ignored evidence). + +All three abandoned Composer dependencies were removed or replaced. Normal Composer audit and strict manifest validation passed. Symfony HttpClient fetched a live public RSS feed with 10 articles. Six new RSS tests and two route tests bring totals to 193 backend tests / 769 assertions, 85 API scenarios / 641 steps and 42 frontend tests. PHP formatting, PHPStan, Rector, frontend ESLint, TypeScript and production build passed. The existing app, Messenger and scheduler container IDs and start times remained unchanged; no containers were restarted or reconfigured. diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..95080ff --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,71 @@ +# Deployment and integration configuration + +The application and workers run locally. Public deployment, production migrations, real email delivery, payment operations and live social posting have not been executed. OpenAI setup was explicitly deferred; all external AI adapters currently report configuration pending. + +## Local development + +Start Docker services with `SIGNALIST_HTTP_PORT=18000 SIGNALIST_HTTPS_PORT=18443 rtk docker compose up -d`. Run `rtk make db-migrate`, then `SIGNALIST_HTTP_PORT=18000 rtk make front-dev`. The standard setup uses port 8000. Passing `SIGNALIST_HTTP_PORT=18000` to both Compose and Vite keeps their development URLs aligned; `SIGNALIST_DEV_BACKEND_URL` can override the proxy target. Mailpit is at `http://localhost:8025`. The scheduler dispatches newsletter, billing and connection jobs; the worker consumes `oauth_priority` and `async`. + +Run `rtk make quality`, `rtk make tests`, then `rtk make tests-api`. Backend test suites share a disposable database and must run sequentially. If Rector workers time out, use `rtk make quality RECTOR_FLAGS=--debug` to run the same Rector checks serially. Frontend checks: `rtk make front-lint`, `rtk make front-test`, `rtk npm run typecheck` from `frontend/`, and `rtk make front-build`. Landing: `rtk make landing-build` with Node 22.19+ (Node 24 recommended). Landing uses patched Astro 7; see its [upgrade guide](https://docs.astro.build/en/guides/upgrade-to/v7/). Offline Bluesky: `rtk docker compose exec app node --test integrations/bluesky/*.test.mjs` (Node 22.19+; container uses Node 24). + +## Production topology + +`docker/frankenphp/Dockerfile` builds the React SPA and installs production PHP and Bluesky dependencies. `docker-compose.prod.yml` is a standalone topology with HTTPS, internal PostgreSQL/Redis, workers, scheduler and persistent volumes. It does not expose PostgreSQL, Redis or development email tools. No migration runs automatically on startup. + +Provide a protected environment file outside the repository and set `SIGNALIST_PRODUCTION_ENV_FILE` to its absolute path. Set `SIGNALIST_JWT_DIR` to a protected directory containing `private.pem` and `public.pem`. Pass database initialization variables through a protected Compose `--env-file` or process environment. Never place credentials in tracked files, build arguments, frontend variables or logs. + +Required application settings: + +| Setting | Purpose | +| --- | --- | +| `APP_DOMAIN` | Public application hostname for Caddy HTTPS | +| `APP_SECRET` | Random application signing secret | +| `DATABASE_URL` | PostgreSQL URL; host `postgres` in this topology | +| `POSTGRES_PASSWORD` | Database initialization secret for Compose | +| `MESSENGER_TRANSPORT_DSN`, `REDIS_URL` | Internal Redis transport and cache endpoints | +| `MAILER_DSN`, `SIGNALIST_MAIL_SENDER` | Authorized production transport and verified sender address | +| `FRONTEND_URL` | Public SPA URL, same origin as API for the supported OAuth cookie flow | +| `SIGNALIST_PUBLIC_API_URL` | Public HTTPS backend origin and OAuth metadata base | +| `JWT_PASSPHRASE` | Passphrase matching mounted signing key | +| `CORS_ALLOW_ORIGIN` | Anchored allowlist regex for actual trusted origins | + +Set `APP_ENV=prod`, `APP_DEBUG=0`. JWT key paths are set by Compose to `/run/keys/private.pem` and `/run/keys/public.pem`. Mount keys read-only. Keep TLS verification enabled; the Bluesky bridge explicitly enforces it. Configure verified sender identity before real newsletter or invitation delivery. + +Before an approved deployment: build and scan the image, back up PostgreSQL, prove restore, review migration SQL and retained vector indexes, plan ANALYZE/IVFFlat rebuild after substantial initial data loading, configure DNS/TLS, run migrations once, then start app/workers/scheduler. Validate registration/verification, member boundaries, failed-job handling, provider callbacks, webhook signatures and expiry behavior in staging. Keep previous image and database backup available; do not downgrade migrations that would erase shared content. Redis uses append-only persistence; job records also provide recovery outboxes. + +Monitor worker exits, queue/failure depth, scheduler heartbeat, DB capacity, backups, OAuth expiry and uncertain publication/delivery states. `/health` is a liveness endpoint, not proof of database, queue or email readiness. Review errors without exposing provider payloads or tokens. Do not blindly retry uncertain social posts, email sends or Raindrop creates: inspect remote outcome first. + +## External account setup + +`CREDENTIAL_ENCRYPTION_KEY` must be a base64-encoded 32-byte random key. Losing it requires reconnecting accounts; rotating it requires a controlled ciphertext migration. PHP tokens use sodium secretbox; Bluesky sessions use AES-GCM with owner, store kind and record key authenticated as context. No keys have been provisioned by this implementation. + +| Provider | Configuration | Callback | +| --- | --- | --- | +| Raindrop.io | `RAINDROP_CLIENT_ID`, `RAINDROP_CLIENT_SECRET` | `/api/v1/connections/raindrop/callback` | +| LinkedIn | `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET`, optional `LINKEDIN_API_VERSION` (default `202605`) | `/api/v1/connections/linkedin/callback` | +| X | `X_CLIENT_ID`, `X_CLIENT_SECRET` | `/api/v1/connections/x/callback` | +| Bluesky | `BLUESKY_CLIENT_PRIVATE_KEY` (ES256/P-256 PKCS8 PEM or compatible JWK), public HTTPS backend | `/api/v1/connections/bluesky/callback` | + +Register exact redirect URIs and appropriate approved scopes/products with each provider. Non-Bluesky callbacks must echo the exact authorization state; missing state is rejected even when a browser cookie exists. Raindrop’s [published OAuth response contract](https://developer.raindrop.io/v1/authentication/token) does not document state echo, so confirm that support in staging before enabling its OAuth connection. A cookie-only fallback is deliberately unsupported. LinkedIn and X posting adapters follow their official [Posts API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api?view=li-lms-2026-05) and [Create Posts API](https://docs.x.com/x-api/posts/create-post). Provider access, fees and live behavior remain unverified. + +Bluesky uses the official AT Protocol SDK for confidential OAuth, PAR, PKCE, DPoP, session refresh and `putRecord`. Public metadata is at `/oauth/bluesky/client-metadata.json`, public keys at `/oauth/bluesky/jwks.json`. Discovery restricts HTTPS, public addresses, DNS-pinned connections, redirects, deadlines and response sizes. Publication uses an SDK-generated stable record key. No app-password fallback is implemented. Changing the signing key requires updating public metadata and provider sessions safely. + +Raindrop sync imports and exports bookmarks in checkpointed batches. Local changes win simultaneous conflicts. Remote-only changes update imported article titles/URLs, without rewriting RSS source metadata. Local deletions stay local; remote bookmarks are never deleted by sync. Notes are excluded unless explicitly selected. A lost remote-create response leaves an uncertain status to prevent blind duplication. Disconnect erases local authorization; users should also revoke application access in provider settings. + +## Billing and quotas + +Set `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `STRIPE_TEAM_PRICE_ID`, and optionally `STRIPE_ENTERPRISE_PRICE_ID`. Price IDs must represent recurring per-seat prices. The final checkout price is authoritative; landing paid prices are unannounced. Configure the billing portal and point signed subscription webhooks at `/api/v1/billing/webhook`. Subscription reconciliation reads authoritative current provider state; expired entitlements fall back to Free. + +Defaults: Free 5 RSS feeds / 1 member / 100 daily imported articles / no AI calls; Team 30 feeds / 5 members / unlimited article intake / 1,000 monthly AI calls; Enterprise unlimited feeds/members/article intake / 10,000 monthly AI calls. Override `plan_limits` through deployment configuration after product review. AI quotas count provider operations, not tokens or currency. Seat changes enqueue Stripe quantity updates with prorations. Active subscriptions must be cancelled before deleting their billed account/workspace. + +## AI, privacy and retention + +Implement configured adapters for `ArticleIntelligenceInterface`, `RecapIntelligenceInterface` and `ArticleQuestionInterface` before enabling AI. Enrichment stores separate AI summary, tags and `vector(1536)` chunks; semantic/hybrid search requires embeddings of exactly that dimension. Provider inputs exclude private bookmark notes and minimize common identifiers and known names. Arbitrary-name recognition and complete anonymization are not guaranteed. AI output remains source content requiring human review. + +`app:privacy:retention` defaults to dry-run. `--apply` deletes eligible records; review counts, backups and policy first. The daily job runs only when `SIGNALIST_RETENTION_ENABLED=1`. Personal and shared bookmarks preserve articles. Backups, logs, failure queues and third-party data require separate operator policies. Public privacy/terms pages are deployment drafts and must be completed with real operator identity, contacts, legal bases, recipients and agreements before collecting production data. + +## Landing and MCP + +For local port 18000, build with `PUBLIC_API_BASE_URL=http://localhost:18000/api/v1`. Build landing with `PUBLIC_SITE_URL` set to the actual public origin and `PUBLIC_API_BASE_URL` set to its API `/api/v1` base. Set optional `PUBLIC_CONTACT_EMAIL`. Deploy `landing/dist` to an approved static host. Analytics are off by default; optional `PUBLIC_PLAUSIBLE_SCRIPT_URL` accepts the site's current `https://.../js/pa-....js` URL from [Plausible installation settings](https://plausible.io/docs/plausible-script). It instruments public landing pages only and receives no application content from this code. + +MCP is authenticated at `/mcp/`, scoped to the same user, read-only and restricted to configured hosts/origins. It provides `search_articles`, `get_article`, `get_feed_summary`, and `list_categories`. This application endpoint does not enable agent-runtime MCP access; shared agent settings still block external MCP servers. diff --git a/docs/IMPLEMENTATION-PLAN.md b/docs/IMPLEMENTATION-PLAN.md new file mode 100644 index 0000000..a351d9e --- /dev/null +++ b/docs/IMPLEMENTATION-PLAN.md @@ -0,0 +1,71 @@ +# Remaining roadmap implementation plan + +This working plan covers `docs/ROADMAP.md`. The user authorized autonomous local implementation and requested provider-independent work while deferring OpenAI setup. See `DEPLOYMENT.md` for implemented configuration and external prerequisites. This is not a production launch report. + +## Inspected starting point + +- RSS, categories, articles, bookmarks, authentication, and email verification exist. +- The landing page exists in English and French, but its waitlist has no backend. +- AI, newsletters, organizations, subscriptions, third-party sync, and MCP implementations are absent. +- Article embeddings currently use JSON, not a PostgreSQL vector column. +- User deletion timestamps exist; the complete privacy lifecycle does not. +- Existing dependencies are installed locally. No Compose services were running during inspection. + +## Delivery order and acceptance + +| Stage | Implementation | Acceptance evidence | +| --- | --- | --- | +| 1. Baseline | Start existing development services; verify current backend, frontend, and landing build | Existing quality and test gates pass, or failures documented before new work | +| 2. Privacy and launch foundation | Consent lifecycle, export, profile updates, processing restrictions, deletion, retention, access audit, encrypted external credentials; persisted waitlist with consent and withdrawal | Owner isolation, export contents, deletion cascades, restricted processing, retention, and waitlist validation tested | +| 3. AI intelligence | Symfony AI provider adapters, versioned prompts, HTML cleaning, text chunking, anonymization, async summaries/embeddings/tags, combined semantic and full-text search | Unit tests with deterministic provider doubles; database search integration tests; API and frontend interaction tests | +| 4. Content automation | Newsletter CRUD, reading-time budget, category selection, linked summaries, HTML preview, schedules, async delivery, social recap previews | Selection and reading-time tests; scheduled-job idempotency; authorization and rendering tests; mail delivery checked locally | +| 5. Teams and subscriptions | Organizations, invitations, roles, shared curation, annotations, activity, per-member read state, plan quotas, Stripe checkout/portal and verified webhooks | Cross-organization denial tests, role matrix tests, quota enforcement, webhook replay and signature tests | +| 6. Integrations and ecosystem | Raindrop OAuth and bidirectional sync, conflict policy, social share links, LinkedIn/X/Bluesky OAuth and async publishing, Cmd+K, authenticated read-only MCP tools | OAuth state and token handling tests, sync conflict tests, duplicate publishing prevention, scoped MCP responses, frontend command tests | +| 7. Release verification | Deployment configuration and runbook, privacy-friendly analytics configuration, honest landing copy, roadmap status updates | Full project checks, frontend and landing production builds, documented live integration prerequisites | + +Each stage includes backend implementation, API contracts, translatable frontend flows, relevant tests, and documentation. Roadmap tasks become Done only after their applicable checks pass. External integrations retain an explicit live-verification status. + +## Implementation scope authorized by the user + +### Dependencies + +Add only packages needed for roadmap features: Symfony AI bundle and provider bridges, Symfony Scheduler, an HTML readability parser, Stripe's PHP SDK, and an MCP server implementation compatible with the existing Symfony application. Exact package names, versions, and compatibility must be verified against official documentation before installation. Reuse existing HTTP, mail, React Query, router, and UI dependencies where possible. + +### Database migrations + +- Replace embedding JSON storage with pgvector `vector(1536)` and the planned IVFFlat index. Validate existing values before conversion; do not silently discard incompatible values. +- Add tags and article associations; waitlist entries; consent records and access audit metadata. +- Add newsletters, schedules, delivery state, social recaps, and synchronization state. +- Add organizations, memberships, invitations, annotations, activity, and per-member read state. +- Add subscriptions, usage counters, and encrypted external-account credentials. +- Preserve existing personal ownership and introduce separate organization-scoped workspace routes; do not migrate personal data into shared workspaces. +- Generate and test migrations against local development/test databases before any production execution. + +### AI configuration and prompts + +- Support the three providers specified by the roadmap: OpenAI, Anthropic, Mistral. +- Keep provider/model selection configurable; require 1536-dimensional output for the chosen embedding model. +- Version prompts for summaries, tags, newsletter synthesis, social recap formats, and command intent extraction. +- Anonymize external inputs; do not log raw article payloads or personal prompts. +- Use deterministic test doubles until credentials and live API use are authorized. +- Preserve source attribution and treat ingested article text as untrusted input. + +### Project MCP implementation + +Implement authenticated `/mcp/` access to `search_articles`, `get_feed_summary`, `list_categories`, and `get_article`, with the same user/workspace permission boundaries as application APIs. This does not enable agent runtime MCP access or change shared agent settings. + +## Product decisions and external prerequisites + +- Pricing is inconsistent: landing advertises Team at EUR 29/month for up to five users, while the roadmap requests per-seat billing. Keep prices and quotas configurable; obtain the commercial decision before enabling paid checkout. +- Enterprise SSO appears in landing copy but is not a roadmap deliverable. Remove unimplemented availability claims or mark them as planned. +- Deployable code can be completed locally; production domain, hosting, provider credentials, OAuth app approvals, Stripe products/prices, email delivery settings, and analytics site identifiers require account-specific setup. +- Privacy policy and provider agreements need the actual operator, purposes, providers, and retention decisions. Technical privacy controls alone do not establish legal compliance. +- Live email, social publishing, charges, and public deployment require explicit authorization. Tests use local delivery or provider doubles. + +## Verification and completion reporting + +Run targeted tests per feature, then the repository's broad gates: `make quality`, `make tests`, `make tests-api`, `make front-lint`, `make front-test`, frontend `npm run typecheck`, `make front-build`, and `make landing-build`. + +Report implemented behavior, passed/failed checks, migration validation, remaining account setup, and any unverified external behavior separately. Do not mark simulated integration tests as successful live integration tests. + +No commits, pushes, production migrations, public deployment or paid API usage have been performed. AGENTS.md was changed at the user’s explicit request; other shared instruction assets remain unchanged. diff --git a/docs/PR-NOTES.md b/docs/PR-NOTES.md new file mode 100644 index 0000000..eeceb8c --- /dev/null +++ b/docs/PR-NOTES.md @@ -0,0 +1,58 @@ +# Roadmap implementation review + +## What and why + +Signalist previously provided RSS reading, categories, bookmarks and authentication. This change implements the remaining provider-independent product flows: privacy controls, article intelligence pipelines, full-text and vector search, sourced Q&A, tags, newsletters, shared workspaces, billing, account connections, bookmark synchronization, social drafts and publishing, Spotlight, and authenticated read-only MCP tools. The English/French landing page now has a confirmed, withdrawable waitlist and describes actual availability. + +The user authorized autonomous local implementation and requested that OpenAI setup be deferred. `AGENTS.md` records that execution policy while retaining approval requirements for commits, pushes, production changes and live external actions. + +## How + +- Keep application APIs scoped to the authenticated owner or authorized workspace member. Preserve personal ownership instead of migrating personal content into shared workspaces. +- Use domain handlers, provider ports and Messenger jobs for slow work. Persist scheduling, billing, synchronization and publication state; stop uncertain outbound actions instead of blindly duplicating them. +- Add consent, processing restriction, portable export, account erasure, retention and minimized audit records. Encrypt external credentials and OAuth SDK sessions; bind callbacks to authorization state. +- Store embeddings as PostgreSQL `vector(1536)` with cosine and full-text indexes. Validate provider output and minimize article text before external processing. Unconfigured AI adapters report their unavailable state; deterministic content flows remain usable. +- Add translated React pages and dedicated query/mutation hooks. Provide deployment configuration, configuration guidance and privacy documentation drafts. +- Update vulnerable dependencies, including the landing page's upgrade to Astro 7. Node 24 is used in CI and production builds. Add offline Bluesky SDK tests to CI and keep health, mapping and coverage checks enforced. + +## Verification + +Local checks completed on 2026-10-13: + +| Check | Result | +| --- | --- | +| Backend quality | `make quality RECTOR_FLAGS=--debug` passed: formatting, PHPStan and serial Rector | +| Backend behavior | `make tests`: 193 tests, 769 assertions passed | +| Domain statement coverage | 82.65% (1,515 of 1,833 statements); the 80% CI threshold is unchanged | +| API contracts | `make tests-api`: 85 scenarios, 641 steps passed | +| Frontend | 42 tests passed after route splitting; ESLint, TypeScript and production build passed | +| Bluesky SDK bridge | Five offline tests passed: metadata, record IDs, fixed errors, ciphertext binding and discovery restrictions | +| Landing | Six English/French pages built; internal links checked | +| Dependencies | PHP audit found no vulnerabilities or abandoned packages; all three JavaScript dependency trees audited with no vulnerabilities | +| Doctrine | Mapping validation passed; database synchronization was deliberately skipped because custom PostgreSQL indexes are managed by migrations | +| Migrations | Fresh disposable database migrated through all 18 versions. Rollback/reapply passed through version `20261012001300`; the final Q&A migration then applied successfully | +| Deployment configuration | Production Compose and Caddy configuration validated; CI YAML parsed with duplicate-key rejection | +| Patch hygiene | `git diff --check` passed | + +The frontend now loads pages and the authenticated layout on demand. The largest JavaScript chunk fell from 877.09 KB (274.42 KB gzip) to 495.59 KB (163.68 KB gzip); the bundle-size warning is gone. Navigation remains mounted while route content loads. These figures compare the largest emitted chunk, not the aggregate initial download. + +Removed unused `behatch/contexts`, replaced `friends-of-behat/mink-browserkit-driver` with maintained `behat/mink-browserkit-driver` 2.3, and replaced the Laminas HTTP transport with existing Symfony HttpClient. This removes transitive `laminas/laminas-loader` while retaining Laminas Feed parsing. Behat prerequisites are now explicit dependencies. Composer removed 11 packages and installed one without upgrading unrelated packages. Normal `composer audit` now blocks abandoned packages in CI. + +RSS fetching has a 20-second total duration, three-redirect limit, 4 MiB response limit and private-network protection. Six regression tests cover RSS, Atom, failed HTTP responses, oversized responses, malformed XML and private-network rejection. A live public feed returned 10 articles. API database resets explicitly require a disposable `_test` database. + +Production preview browser checks passed for login, inbox, settings and newsletters against the existing Podman backend. App, Messenger and scheduler container IDs and start times remained unchanged. See [follow-up browser evidence](BROWSER-QA.md#bundle-and-dependency-follow-up). + +## Risks and follow-up + +- Live AI adapters, Symfony AI provider setup, credentials and provider-specific summary/tagging prompts remain deferred. Semantic search and generated answers require configured providers. +- Input minimization removes common identifiers and known names; arbitrary-name NER is incomplete. It does not guarantee anonymity. Review this boundary before enabling an external AI provider. +- Raindrop OAuth rejects callbacks without matching response state. Its published contract does not document state echo; verify support in staging before enabling that connection. +- Stripe, LinkedIn, X, Bluesky and Raindrop adapters are covered locally with test doubles or offline SDK tests. Real accounts, approved scopes, public callbacks, webhook setup, commercial prices and actual provider behavior need staging validation. +- Newsletter delivery and external publishing need configured services and explicit authorization for live actions. Uncertain outcomes require operator inspection before retrying. +- Production image construction remains unverified: Docker Hub downloads failed with network/socket errors. Build and scan the complete image before deployment. +- Operator identity, legal text, supplier agreements, hosting/domain configuration, backups and restore rehearsal remain launch work. Technical privacy controls are not a legal compliance determination. +- Production deployment, production migrations, paid API use, real charges and social publication were not performed. No commit or push was made. + +See [roadmap](ROADMAP.md), [deployment guide](DEPLOYMENT.md) and [privacy guidance](PRIVACY.md) for detailed status and configuration. + +Browser QA subsequently exercised the available local UI flows and fixed quota feedback, login server-detail rendering, check-email markup and the waitlist email label. See [browser QA report](BROWSER-QA.md) for evidence and limits. diff --git a/docs/PRIVACY.md b/docs/PRIVACY.md new file mode 100644 index 0000000..e5cccfc --- /dev/null +++ b/docs/PRIVACY.md @@ -0,0 +1,33 @@ +# Privacy operations and supplier agreement draft + +This is implementation documentation and an operator completion template. It does not establish that a deployment complies with applicable law or that any supplier agreement has been executed. + +## Implemented controls + +Authenticated Settings and `/api/v1/user/*` endpoints support display-name correction, JSON export, purpose-specific consent, consent withdrawal, processing restriction and password-confirmed account deletion. Exports include owned reading data, tags, newsletters, search/answer history, social drafts/publications, consent history and access metadata; they exclude passwords, signing material, authorization codes, OAuth/session tokens and other users' private records. + +Deleting an account erases personal feeds/articles/bookmarks, private annotations, searches, drafts, external credentials, SDK sessions and consent/audit records. A restricted anonymized tombstone prevents stale JWT access until physical deletion. Shared content with remaining members stays in its workspace; attribution is anonymized and administration must be transferred when needed. Active subscriptions must be cancelled first. Seat updates are queued after membership changes. Remote public posts, payment records and third-party copies require separate operator/provider handling. + +AI, RSS, newsletters, synchronization and publishing recheck active ownership, processing restriction or applicable consent at their processing boundaries. External AI ports receive minimized source text and known names/common identifiers are removed. This does not detect every person name or every sensitive datum. Private bookmark notes never enter AI prompts. Raindrop notes leave the application only when the user selects note sync. Social publishing requires approval of the actual text and a currently matching connected account identity. + +PHP authorization values use sodium secretbox. Bluesky SDK stores use AES-GCM with authenticated owner/kind/record context. Encryption keys are operator-provisioned; no key is generated or stored in this repository. HTTPS verification remains enabled. Logs and access audit records use metadata rather than raw provider tokens or AI inputs. + +Optional retention: 90-day unbookmarked articles (both personal and shared bookmarks protect them), expired OAuth attempts/sessions, seven-day unconfirmed waitlist entries, 30-day access events and deleted-account tombstones. It defaults to disabled/dry-run; the operator must authorize and enable it. Failure queues, infrastructure logs, backups and remote services need additional retention and deletion procedures. + +## Operator and supplier register to complete before public launch + +| Field | Required deployment decision | +| --- | --- | +| Controller/operator | Legal identity, address, privacy contact, representative/DPO where applicable | +| Purposes/legal bases | Authentication, reading, collaboration, optional processing, marketing and billing | +| Hosting/backup | Provider, locations, access controls, backup lifetime and restore/deletion policy | +| Email supplier | Provider, sender identity, delivery logs, retention and deletion process | +| AI supplier | Deferred; record exact provider/model, input categories, purposes, training use, retention, locations and agreement before enabling | +| Stripe | Contract/account configuration, payment roles, statutory retention and request handling | +| Raindrop/LinkedIn/X/Bluesky | User-authorized functions, scopes, retained data, revocation and remote deletion process | +| Analytics | Off by default; record configured Plausible deployment and public-page data collection if enabled | +| Security incidents | Responsible contacts, detection, containment, notification and evidence retention procedure | + +For each processor agreement, document subject matter/duration, nature and purpose, data categories, data subjects, documented instructions, confidentiality, security, subprocessors, assistance with rights/incidents, return/deletion and audit arrangements. Assess any international transfer mechanism for the actual deployment. These entries require actual contracts and operator decisions. The official [GDPR text](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32016R0679) and [EU controller/processor standard clauses](https://commission.europa.eu/publications/standard-contractual-clauses-controllers-and-processors-eueea_en) provide the primary reference framework. + +Complete the public English/French privacy and terms drafts with these deployment details before accepting production registrations or waitlist requests. Exercise deletion/export/restriction and provider revocation in staging using non-sensitive test data; verify backup handling separately. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index ef2d17a..895c4e3 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,10 +1,15 @@ # Signalist Roadmap > This document tracks every feature, sub-task, and deliverable across all project phases. -> **Status indicators:** `Done` | `In Progress` | `Not Started` +> **Status indicators:** `Done` | `In Progress` | `Not Started` | `Deferred` > > Updated by the agent after each feature passes quality checks (`make quality`) and tests. + +> Local implementation covers privacy, pgvector storage, provider-independent AI pipelines, search/Q&A, tags, newsletters, teams, billing, OAuth/sync, social drafts/publishing, Spotlight and read-only MCP. Live AI/provider setup, production domain/deployment and operator agreements remain external prerequisites. See [deployment guide](DEPLOYMENT.md). +> Task names below describe capabilities: handlers/messages are consolidated where useful, new async endpoints use thin controllers, and Doctrine supplies persistence without redundant repository wrappers. An implementation marked Done does not claim successful live provider validation. +> Verification on 2026-10-13: 193 backend tests, 85 API scenarios, 42 frontend tests and five offline Bluesky SDK tests passed; domain statement coverage is 82.65%. Frontend/landing builds and local quality checks passed. See [review notes](PR-NOTES.md) for exact checks, migration evidence and remaining launch risks. [Browser QA](BROWSER-QA.md) records the local end-to-end pass and its limits. + --- ## Phase 1 — MVP: Core RSS Engine & Dashboard UI @@ -17,7 +22,7 @@ | `Category` entity (name, slug, description, color, position) | Done | | `Article` entity (guid, title, url, summary, content, author, imageUrl, publishedAt, isRead) | Done | | `Bookmark` entity (notes, article relation, createdAt) | Done | -| `ArticleEmbedding` entity (embedding JSON, chunkIndex, chunkText) | Done | +| `ArticleEmbedding` entity (vector(1536), chunkIndex, chunkText) | Done | | Initial migration (`Version20260131135830`) | Done | | Unique constraints (feed.url, category.slug, bookmark.article_id) | Done | | Indexes on `published_at`, `is_read` | Done | @@ -267,20 +272,20 @@ | Task | Status | |------|--------| -| Value proposition copywriting | Not Started | -| Wireframes / mockup (Figma or equivalent) | Not Started | -| Pricing tiers definition (free, team, enterprise) | Not Started | +| Value proposition copywriting | Done | +| Wireframes / mockup (Figma or equivalent) | Done | +| Pricing tiers definition (free, team, enterprise) | Done (quotas; commercial prices pending) | ### 2.2 Implementation | Task | Status | |------|--------| -| Landing page setup (`landing/` — Astro 5 + Tailwind v4) | Done | +| Landing page setup (`landing/` — Astro 7 + Tailwind v4) | Done | | Hero section (headline, subheadline, CTA, inbox preview) | Done | | Features section (Monitor / Curate / Synthesize / Publish) | Done | | Pricing section (Free / Team / Enterprise) | Done | -| Social proof / testimonials section | Done | -| Waitlist / early access form (UI only — service TBD) | Done | +| Product principles section (fabricated testimonials removed) | Done | +| Waitlist / early access form with confirmed, withdrawable backend | Done | | SEO meta tags + Open Graph | Done | | Responsive design (mobile + desktop) | Done | @@ -288,9 +293,9 @@ | Task | Status | |------|--------| -| Domain configuration | Not Started | -| Deploy to production (Vercel / Netlify / other) | Not Started | -| Analytics (privacy-friendly, e.g. Plausible) | Not Started | +| Domain configuration | Not Started (external setup) | +| Deploy to production (Vercel / Netlify / other) | Not Started (external setup) | +| Analytics (privacy-friendly, e.g. Plausible) | Done | --- @@ -300,72 +305,72 @@ | Task | Status | |------|--------| -| Install and configure `symfony/ai` | Not Started | -| Configure LLM providers (OpenAI, Anthropic, Mistral) | Not Started | -| Environment variables for API keys | Not Started | -| AI client adapter (`Infrastructure/AI/`) | Not Started | +| Install and configure `symfony/ai` | Deferred (provider setup) | +| Configure LLM providers (OpenAI, Anthropic, Mistral) | Deferred (provider setup) | +| Environment variables for API keys | Deferred (provider setup) | +| AI client adapter (`Infrastructure/AI/`) | Done (provider ports; live adapter deferred) | ### 3.2 Article Embeddings (pgvector) | Task | Status | |------|--------| -| Enable pgvector `vector(1536)` column type (replace JSON) | Not Started | -| IVFFlat index on embeddings | Not Started | -| HTML cleaning pipeline (Readability) | Not Started | -| Text chunking strategy | Not Started | -| `GenerateEmbeddingMessage` (async) | Not Started | -| `GenerateEmbeddingMessageHandler` | Not Started | -| Embedding generation adapter (OpenAI / Mistral) | Not Started | -| Unit tests for embedding pipeline | Not Started | +| Enable pgvector `vector(1536)` column type (replace JSON) | Done | +| IVFFlat index on embeddings | Done | +| HTML cleaning pipeline (Readability) | Done | +| Text chunking strategy | Done | +| `GenerateEmbeddingMessage` (async) | Done | +| `GenerateEmbeddingMessageHandler` | Done | +| Embedding generation adapter (OpenAI / Mistral) | Deferred (provider setup) | +| Unit tests for embedding pipeline | Done | ### 3.3 Semantic Search | Task | Status | |------|--------| -| Search domain (`src/Domain/Search/`) | Not Started | -| `SemanticSearchQuery` + `SemanticSearchHandler` | Not Started | -| `SearchRepositoryInterface` port | Not Started | -| `PgvectorSearchRepository` adapter | Not Started | -| Cosine similarity search endpoint | Not Started | -| Combined full-text + semantic search | Not Started | -| Search API Platform resource | Not Started | -| Frontend search UI with results | Not Started | -| Unit + integration tests | Not Started | +| Search domain (`src/Domain/Search/`) | Done | +| `SemanticSearchQuery` + `SemanticSearchHandler` | Done | +| `SearchRepositoryInterface` port | Done | +| `PgvectorSearchRepository` adapter | Done | +| Cosine similarity search endpoint | Done | +| Combined full-text + semantic search | Done | +| Search API Platform resource | Done | +| Frontend search UI with results | Done | +| Unit + integration tests | Done | ### 3.4 LLM Summarization | Task | Status | |------|--------| -| `SummarizeArticleMessage` (async) | Not Started | -| `SummarizeArticleMessageHandler` | Not Started | -| Summarization prompt templates | Not Started | -| Source URL retention in summaries | Not Started | -| Summary storage (Article entity field) | Not Started | -| Frontend summary display | Not Started | -| Unit tests for summarization | Not Started | +| `SummarizeArticleMessage` (async) | Done | +| `SummarizeArticleMessageHandler` | Done | +| Summarization prompt templates | Deferred (provider setup) | +| Source URL retention in summaries | Done | +| Summary storage (Article entity field) | Done | +| Frontend summary display | Done | +| Unit tests for summarization | Done | ### 3.5 Auto-Tagging | Task | Status | |------|--------| -| Tag entity + migration | Not Started | -| `AutoTagArticleMessage` (async) | Not Started | -| `AutoTagArticleMessageHandler` | Not Started | -| LLM-based tag extraction | Not Started | -| Tag CRUD domain (Command/Query/Handler) | Not Started | -| Tag API endpoint | Not Started | -| Frontend tag display + filtering | Not Started | -| Unit tests | Not Started | +| Tag entity + migration | Done | +| `AutoTagArticleMessage` (async) | Done | +| `AutoTagArticleMessageHandler` | Done | +| LLM-based tag extraction | Deferred (provider setup) | +| Tag CRUD domain (Command/Query/Handler) | Done | +| Tag API endpoint | Done | +| Frontend tag display + filtering | Done | +| Unit tests | Done | ### 3.6 Data Anonymization (GDPR) | Task | Status | |------|--------| -| `ArticleAnonymizer` service | Not Started | -| PII detection (email, phone, names, IPs) | Not Started | -| Anonymization before LLM calls | Not Started | -| Logging of data sent to external AI services | Not Started | -| Unit tests for anonymizer | Not Started | +| `ArticleAnonymizer` service | Done | +| PII detection (email, phone, names, IPs) | In Progress (common identifiers and known names; arbitrary-name NER pending) | +| Anonymization before LLM calls | Done | +| Logging of data sent to external AI services | Done | +| Unit tests for anonymizer | Done | --- @@ -375,55 +380,55 @@ | Task | Status | |------|--------| -| Newsletter entity + migration | Not Started | -| `CreateNewsletterCommand` + `CreateNewsletterHandler` | Not Started | -| `UpdateNewsletterCommand` + `UpdateNewsletterHandler` | Not Started | -| `DeleteNewsletterCommand` + `DeleteNewsletterHandler` | Not Started | -| `GetNewsletterQuery` + `GetNewsletterHandler` | Not Started | -| `ListNewslettersQuery` + `ListNewslettersHandler` | Not Started | -| Newsletter input/output DTOs | Not Started | -| `NewsletterRepositoryInterface` port | Not Started | -| `DoctrineNewsletterRepository` adapter | Not Started | -| Newsletter API Platform resource | Not Started | -| Unit tests for all handlers | Not Started | +| Newsletter entity + migration | Done | +| `CreateNewsletterCommand` + `CreateNewsletterHandler` | Done | +| `UpdateNewsletterCommand` + `UpdateNewsletterHandler` | Done | +| `DeleteNewsletterCommand` + `DeleteNewsletterHandler` | Done | +| `GetNewsletterQuery` + `GetNewsletterHandler` | Done | +| `ListNewslettersQuery` + `ListNewslettersHandler` | Done | +| Newsletter input/output DTOs | Done | +| `NewsletterRepositoryInterface` port | Done | +| `DoctrineNewsletterRepository` adapter | Done | +| Newsletter API Platform resource | Done | +| Unit tests for all handlers | Done | ### 4.2 Newsletter Content Generation | Task | Status | |------|--------| -| `NewsletterContentBuilder` service | Not Started | -| Reading time calculation (200 wpm) | Not Started | -| Category-grouped article selection | Not Started | -| LLM-generated concise summaries | Not Started | -| Clickable titles with source URLs | Not Started | -| HTML email template | Not Started | -| Unit tests | Not Started | +| `NewsletterContentBuilder` service | Done | +| Reading time calculation (200 wpm) | Done | +| Category-grouped article selection | Done | +| LLM-generated concise summaries | Deferred (uses stored AI summaries when available) | +| Clickable titles with source URLs | Done | +| HTML email template | Done | +| Unit tests | Done | ### 4.3 Newsletter Scheduling | Task | Status | |------|--------| -| Symfony Scheduler integration | Not Started | -| Daily / weekly / custom schedule options | Not Started | -| `GenerateNewsletterMessage` (async) | Not Started | -| `GenerateNewsletterMessageHandler` | Not Started | +| Symfony Scheduler integration | Done | +| Daily / weekly / custom schedule options | Done | +| `GenerateNewsletterMessage` (async) | Done | +| `GenerateNewsletterMessageHandler` | Done | | Symfony Mailer configuration | Done | -| Email delivery adapter | Not Started | -| Schedule management UI (frontend) | Not Started | -| Unit + integration tests | Not Started | +| Email delivery adapter | Done | +| Schedule management UI (frontend) | Done | +| Unit + integration tests | Done | ### 4.4 Raindrop.io Sync | Task | Status | |------|--------| -| OAuth2 flow for Raindrop.io | Not Started | -| `RaindropSyncMessage` (async) | Not Started | -| `RaindropSyncMessageHandler` | Not Started | -| Raindrop API client (`Infrastructure/External/Raindrop/`) | Not Started | -| Bidirectional bookmark sync | Not Started | -| Conflict resolution strategy | Not Started | -| Sync status UI (frontend) | Not Started | -| Unit tests | Not Started | +| OAuth2 flow for Raindrop.io | In Progress (secure state binding implemented; provider state echo unverified) | +| `RaindropSyncMessage` (async) | Done | +| `RaindropSyncMessageHandler` | Done | +| Raindrop API client (`Infrastructure/External/Raindrop/`) | Done | +| Bidirectional bookmark sync | Done | +| Conflict resolution strategy | Done | +| Sync status UI (frontend) | Done | +| Unit tests | Done | ### 4.5 Social Recap (AI-Generated Weekly Post) @@ -432,20 +437,20 @@ | Task | Status | |------|--------| -| `SocialRecap` entity + migration (platform, period, content, status) | Not Started | -| `CreateSocialRecapCommand` + `CreateSocialRecapHandler` | Not Started | -| `DeleteSocialRecapCommand` + `DeleteSocialRecapHandler` | Not Started | -| `GetSocialRecapQuery` + `GetSocialRecapHandler` | Not Started | -| `ListSocialRecapsQuery` + `ListSocialRecapsHandler` | Not Started | -| `SocialRecapRepositoryInterface` port | Not Started | -| `DoctrineSocialRecapRepository` adapter | Not Started | -| `SocialRecapContentBuilder` service (reuses `NewsletterContentBuilder` aggregation) | Not Started | -| Platform output formatters: LinkedIn, X (thread), Instagram caption | Not Started | -| LLM prompt templates per platform (tone, length, hashtag style) | Not Started | -| `GenerateSocialRecapMessage` (async) + handler | Not Started | -| Social Recap API Platform resource | Not Started | -| Frontend: recap generator UI (select period, platform, preview, copy) | Not Started | -| Unit tests for content builder + formatters | Not Started | +| `SocialRecap` entity + migration (platform, period, content, status) | Done | +| `CreateSocialRecapCommand` + `CreateSocialRecapHandler` | Done | +| `DeleteSocialRecapCommand` + `DeleteSocialRecapHandler` | Done | +| `GetSocialRecapQuery` + `GetSocialRecapHandler` | Done | +| `ListSocialRecapsQuery` + `ListSocialRecapsHandler` | Done | +| `SocialRecapRepositoryInterface` port | Done | +| `DoctrineSocialRecapRepository` adapter | Done | +| `SocialRecapContentBuilder` service (reuses `NewsletterContentBuilder` aggregation) | Done | +| Platform output formatters: LinkedIn, X (thread), Instagram caption | Done | +| LLM prompt templates per platform (tone, length, hashtag style) | Done | +| `GenerateSocialRecapMessage` (async) + handler | Done | +| Social Recap API Platform resource | Done | +| Frontend: recap generator UI (select period, platform, preview, copy) | Done | +| Unit tests for content builder + formatters | Done | --- @@ -455,37 +460,37 @@ | Task | Status | |------|--------| -| `Organization` entity + migration | Not Started | -| `OrganizationMember` entity (userId, orgId, role) + migration | Not Started | -| Roles: `admin`, `curator`, `reader` | Not Started | -| `InviteMemberCommand` + `InviteMemberHandler` | Not Started | -| `RemoveMemberCommand` + `RemoveMemberHandler` | Not Started | -| `UpdateMemberRoleCommand` + `UpdateMemberRoleHandler` | Not Started | -| Organization API endpoints | Not Started | -| Role-based access control (Symfony voters) | Not Started | -| Unit tests | Not Started | +| `Organization` entity + migration | Done | +| `OrganizationMember` entity (userId, orgId, role) + migration | Done | +| Roles: `admin`, `curator`, `reader` | Done | +| `InviteMemberCommand` + `InviteMemberHandler` | Done | +| `RemoveMemberCommand` + `RemoveMemberHandler` | Done | +| `UpdateMemberRoleCommand` + `UpdateMemberRoleHandler` | Done | +| Organization API endpoints | Done | +| Role-based access control (Symfony voters) | Done | +| Unit tests | Done | ### 5.2 Shared Workspace | Task | Status | |------|--------| -| Feeds and categories scoped to organization | Not Started | -| Shared bookmarks with curator attribution | Not Started | -| Collaborative annotations on bookmarks | Not Started | -| Activity feed (who curated what, when) | Not Started | -| Per-member read status (independent of shared articles) | Not Started | -| Frontend team workspace UI | Not Started | -| Unit + integration tests | Not Started | +| Feeds and categories scoped to organization | Done | +| Shared bookmarks with curator attribution | Done | +| Collaborative annotations on bookmarks | Done | +| Activity feed (who curated what, when) | Done | +| Per-member read status (independent of shared articles) | Done | +| Frontend team workspace UI | Done | +| Unit + integration tests | Done | ### 5.3 Billing & Plans | Task | Status | |------|--------| -| Plan model (free, team, enterprise) | Not Started | -| Per-seat pricing logic | Not Started | -| Usage limits per plan (feeds, members, AI calls) | Not Started | -| Stripe integration (subscriptions) | Not Started | -| Plan management UI | Not Started | +| Plan model (free, team, enterprise) | Done | +| Per-seat pricing logic | Done | +| Usage limits per plan (feeds, members, AI calls) | Done | +| Stripe integration (subscriptions) | Done | +| Plan management UI | Done | --- @@ -495,28 +500,28 @@ | Task | Status | |------|--------| -| Spotlight domain (`src/Domain/Spotlight/`) | Not Started | -| `Cmd+K` activation (frontend) | Not Started | -| Command parser (NLP intent mapping) | Not Started | -| CRUD intent handlers (add feed, create category, etc.) | Not Started | -| AI query handler (ask questions about articles) | Not Started | -| Spotlight UI component (overlay, suggestions, results) | Not Started | -| Frontend + backend integration | Not Started | -| Unit tests for command parser | Not Started | +| Spotlight domain (`src/Domain/Spotlight/`) | Done | +| `Cmd+K` activation (frontend) | Done | +| Command parser (NLP intent mapping) | Done (bounded English/French grammar; AI intent extraction deferred) | +| CRUD intent handlers (add feed, create category, etc.) | Done | +| AI query handler (ask questions about articles) | Done | +| Spotlight UI component (overlay, suggestions, results) | Done | +| Frontend + backend integration | Done | +| Unit tests for command parser | Done | ### 6.2 MCP Server (Model Context Protocol) | Task | Status | |------|--------| -| MCP infrastructure (`src/Infrastructure/MCP/`) | Not Started | -| `#[AsTool]` tool definitions | Not Started | -| `search_articles` tool | Not Started | -| `get_feed_summary` tool | Not Started | -| `list_categories` tool | Not Started | -| `get_article` tool | Not Started | -| MCP routing (`/mcp/` prefix) | Not Started | -| Authentication for MCP endpoints | Not Started | -| Integration tests | Not Started | +| MCP infrastructure (`src/Infrastructure/MCP/`) | Done | +| SDK tool definitions (explicit registration) | Done | +| `search_articles` tool | Done | +| `get_feed_summary` tool | Done | +| `list_categories` tool | Done | +| `get_article` tool | Done | +| MCP routing (`/mcp/` prefix) | Done | +| Authentication for MCP endpoints | Done | +| Integration tests | Done | ### 6.3 Social Sharing @@ -524,14 +529,14 @@ | Task | Status | |------|--------| -| Share intent model | Not Started | -| WhatsApp share adapter | Not Started | -| X (Twitter) share adapter | Not Started | -| LinkedIn share adapter | Not Started | -| Threads share adapter | Not Started | -| Bluesky share adapter | Not Started | -| Share button UI (frontend) | Not Started | -| Unit tests | Not Started | +| Share intent model | Done | +| WhatsApp share adapter | Done | +| X (Twitter) share adapter | Done | +| LinkedIn share adapter | Done | +| Threads share adapter | Done | +| Bluesky share adapter | Done | +| Share button UI (frontend) | Done | +| Unit tests | Done | #### 6.3.2 Crossposting via Platform APIs (requires OAuth2 per platform) @@ -540,19 +545,19 @@ | Task | Status | |------|--------| -| `SocialAccount` entity + migration (platform, accessToken, refreshToken, expiresAt) | Not Started | -| `SocialAccountRepositoryInterface` port | Not Started | -| `DoctrineSocialAccountRepository` adapter | Not Started | -| OAuth2 flow: LinkedIn (`Infrastructure/External/LinkedIn/`) | Not Started | -| OAuth2 flow: X / Twitter (`Infrastructure/External/X/`) | Not Started | -| OAuth2 flow: Bluesky (`Infrastructure/External/Bluesky/`) | Not Started | -| `CrosspostMessage` (async) + `CrosspostMessageHandler` | Not Started | -| `CrosspostAdapterInterface` port (per-platform implementations) | Not Started | -| Token refresh strategy (background job) | Not Started | -| Crosspost status tracking (pending, published, failed) | Not Started | -| Frontend: connect social accounts UI (OAuth2 redirect flow) | Not Started | -| Frontend: crosspost action on articles + recap posts | Not Started | -| Unit tests for adapters | Not Started | +| `SocialAccount` entity + migration (platform, accessToken, refreshToken, expiresAt) | Done | +| `SocialAccountRepositoryInterface` port | Done | +| `DoctrineSocialAccountRepository` adapter | Done | +| OAuth2 flow: LinkedIn (`Infrastructure/External/LinkedIn/`) | Done | +| OAuth2 flow: X / Twitter (`Infrastructure/External/X/`) | Done | +| OAuth2 flow: Bluesky (`Infrastructure/External/Bluesky/`) | Done | +| `CrosspostMessage` (async) + `CrosspostMessageHandler` | Done | +| `CrosspostAdapterInterface` port (per-platform implementations) | Done | +| Token refresh strategy (background job) | Done | +| Crosspost status tracking (pending, published, failed) | Done | +| Frontend: connect social accounts UI (OAuth2 redirect flow) | Done | +| Frontend: crosspost action on articles + recap posts | Done | +| Unit tests for adapters | Done | --- @@ -560,22 +565,22 @@ | Task | Status | |------|--------| -| `UserConsent` entity + migration | Not Started | -| Consent management endpoints (grant, withdraw) | Not Started | -| `GET /api/v1/user/data-export` (Access right) | Not Started | -| `DELETE /api/v1/user/account` (Erasure right) | Not Started | -| `GET /api/v1/user/data-export?format=portable` (Portability) | Not Started | -| `PUT /api/v1/user/profile` (Rectification) | Not Started | -| `POST /api/v1/user/restrict-processing` (Restriction) | Not Started | -| `POST /api/v1/user/opt-out` (Objection) | Not Started | -| Soft-delete (`deletedAt`) on personal data entities | Not Started | -| Cascade deletion for related personal data | Not Started | -| Data retention scheduler (90-day articles, 30-day sessions) | Not Started | -| Encrypted sensitive columns (`EncryptedStringType`) | Not Started | -| Data access audit logging (`DataAccessAuditor`) | Not Started | -| Privacy policy documentation | Not Started | -| DPA documentation for AI providers | Not Started | -| GDPR-specific problem types (consent-required, etc.) | Not Started | +| `UserConsent` entity + migration | Done | +| Consent management endpoints (grant, withdraw) | Done | +| `GET /api/v1/user/data-export` (Access right) | Done | +| `DELETE /api/v1/user/account` (Erasure right) | Done | +| `GET /api/v1/user/data-export?format=portable` (Portability) | Done | +| `PUT /api/v1/user/profile` (Rectification) | Done | +| `POST /api/v1/user/restrict-processing` (Restriction) | Done | +| `POST /api/v1/user/opt-out` (Objection) | Done | +| User tombstone (`deletedAt`) with immediate owned personal-data erasure | Done | +| Cascade deletion for related personal data | Done | +| Data retention scheduler (90-day articles, 30-day sessions) | Done | +| Encrypted credential columns (sodium and SDK ciphertext) | Done | +| Data access audit logging (`DataAccessAuditor`) | Done | +| Privacy policy documentation | In Progress (deployment drafts; operator completion required) | +| DPA documentation for AI providers | In Progress (deployment drafts; operator completion required) | +| GDPR-specific problem types (consent-required, etc.) | Done | --- diff --git a/features/api/billing.feature b/features/api/billing.feature new file mode 100644 index 0000000..43f5cfd --- /dev/null +++ b/features/api/billing.feature @@ -0,0 +1,23 @@ +Feature: Billing remains private and fails clearly until configured + Background: + Given I set the content type to JSON + And there are default users + And I am authenticated as "admin@signalist.app" + + Scenario: Read default free plan limits + When I send a "GET" request to "/api/v1/billing" + Then the response status code should be 200 + And the response should be JSON + And the JSON response "plan" should equal "free" + And the JSON response "configured" should equal "false" + + Scenario: Checkout requires complete provider configuration + When I send a "POST" request to "/api/v1/billing/links" with body: + """ + {"kind":"checkout"} + """ + Then the response status code should be 503 + + Scenario: Arbitrary billing link identifiers do not expose URLs + When I send a "GET" request to "/api/v1/billing/links/00000000-0000-0000-0000-000000000000" + Then the response status code should be 404 diff --git a/features/api/connections.feature b/features/api/connections.feature new file mode 100644 index 0000000..db2b2b8 --- /dev/null +++ b/features/api/connections.feature @@ -0,0 +1,25 @@ +Feature: Connected accounts remain unavailable until configured + Background: + Given I set the content type to JSON + And there are default users + And I am authenticated as "admin@signalist.app" + + Scenario: List connection readiness without exposing credentials + When I send a "GET" request to "/api/v1/connections" + Then the response status code should be 200 + And the response should be JSON + + Scenario: Authorization requires complete provider configuration + When I send a "POST" request to "/api/v1/connections/x/authorize" with body: + """ + {} + """ + Then the response status code should be 503 + + Scenario: Disconnect cannot target an arbitrary account + When I send a "DELETE" request to "/api/v1/connections/00000000-0000-0000-0000-000000000000" + Then the response status code should be 404 + + Scenario: A forged OAuth callback is rejected + When I send a "GET" request to "/api/v1/connections/x/callback?code=forged&state=forged" + Then the response status code should be 400 diff --git a/features/api/organizations.feature b/features/api/organizations.feature new file mode 100644 index 0000000..7dfff58 --- /dev/null +++ b/features/api/organizations.feature @@ -0,0 +1,35 @@ +Feature: Shared workspace permissions + Background: + Given I set the content type to JSON + And there are default users + And I am authenticated as "admin@signalist.app" + + Scenario: Create workspace and category + When I send a "POST" request to "/api/v1/organizations" with body: + """ + {"name":"Editorial"} + """ + Then the response status code should be 201 + And the response should be JSON + And I store the response "id" as "workspaceId" + When I send a "POST" request to "/api/v1/organizations/stored:workspaceId/categories" with body: + """ + {"name":"Tech", "slug":"tech"} + """ + Then the response status code should be 201 + When I send a "GET" request to "/api/v1/categories" + Then the response status code should be 200 + And the response should be JSON + And the JSON collection should be empty + + Scenario: Nonmember cannot read workspace + When I send a "POST" request to "/api/v1/organizations" with body: + """ + {"name":"Editorial"} + """ + Then the response status code should be 201 + And the response should be JSON + And I store the response "id" as "workspaceId" + Given I am authenticated as "user@signalist.app" + When I send a "GET" request to "/api/v1/organizations/stored:workspaceId/articles" + Then the response status code should be 404 diff --git a/features/api/privacy.feature b/features/api/privacy.feature new file mode 100644 index 0000000..06507d6 --- /dev/null +++ b/features/api/privacy.feature @@ -0,0 +1,43 @@ +Feature: Account privacy controls + Background: + Given I set the content type to JSON + And there are default users + And I am authenticated as "admin@signalist.app" + + Scenario: Read own profile + When I send a "GET" request to "/api/v1/user/profile" + Then the response status code should be 200 + And the response should be JSON + And the JSON response "email" should equal "admin@signalist.app" + + Scenario: Reject unknown consent purpose + When I send a "POST" request to "/api/v1/user/consent" with body: + """ + {"purpose":"anything", "granted":true} + """ + Then the response status code should be 422 + + Scenario: Restrict processing while retaining access to privacy controls + When I send a "POST" request to "/api/v1/user/restrict-processing" with body: + """ + {"restricted":true} + """ + Then the response status code should be 204 + When I send a "GET" request to "/api/v1/user/profile" + Then the response status code should be 200 + And the response should be JSON + And the JSON response "processingRestricted" should equal "true" + + Scenario: Require password confirmation before deleting account + When I send a "DELETE" request to "/api/v1/user/account" with body: + """ + {"password":"wrong"} + """ + Then the response status code should be 403 + + Scenario: Export own data + When I send a "GET" request to "/api/v1/user/data-export?format=portable" + Then the response status code should be 200 + And the response should be JSON + And the JSON response should contain "profile" + And the JSON response should contain "bookmarks" diff --git a/features/api/social.feature b/features/api/social.feature new file mode 100644 index 0000000..be7e8b0 --- /dev/null +++ b/features/api/social.feature @@ -0,0 +1,31 @@ +Feature: Private social drafts and explicitly approved publishing + Background: + Given I set the content type to JSON + And there are default users + And I am authenticated as "admin@signalist.app" + + Scenario: A curated recap can be queued without an AI provider + When I send a "POST" request to "/api/v1/social-recaps" with body: + """ + {"platform":"linkedin","start":"2026-01-01","end":"2026-01-07","useAi":false} + """ + Then the response status code should be 202 + And the JSON response "status" should equal "pending" + + Scenario: Publishing requires explicit confirmation + When I send a "POST" request to "/api/v1/crossposts" with body: + """ + {"accountId":"00000000-0000-0000-0000-000000000000","text":"Review first","confirmed":false} + """ + Then the response status code should be 422 + + Scenario: Arbitrary recap identifiers expose no drafts + When I send a "GET" request to "/api/v1/social-recaps/00000000-0000-0000-0000-000000000000" + Then the response status code should be 404 + + Scenario: Unsupported platforms are rejected + When I send a "POST" request to "/api/v1/social-recaps" with body: + """ + {"platform":"unknown","start":"2026-01-01","end":"2026-01-07"} + """ + Then the response status code should be 422 diff --git a/features/api/spotlight.feature b/features/api/spotlight.feature new file mode 100644 index 0000000..d00a9dd --- /dev/null +++ b/features/api/spotlight.feature @@ -0,0 +1,30 @@ +Feature: Spotlight commands + Background: + Given I set the content type to JSON + And there are default users + And I am authenticated as "admin@signalist.app" + + Scenario: Create category using command + When I send a "POST" request to "/api/v1/spotlight" with body: + """ + {"command":"create category Technology"} + """ + Then the response status code should be 200 + And the response should be JSON + And the JSON response should contain "path" + + Scenario: Unsafe feed command cannot mutate data + When I send a "POST" request to "/api/v1/spotlight" with body: + """ + {"command":"add feed http://127.0.0.1/private in Tech"} + """ + Then the response status code should be 400 + + Scenario: Unknown command becomes a search + When I send a "POST" request to "/api/v1/spotlight" with body: + """ + {"command":"quantum computing"} + """ + Then the response status code should be 200 + And the response should be JSON + And the JSON response "path" should equal "/search?q=quantum%20computing" diff --git a/features/api/tags.feature b/features/api/tags.feature new file mode 100644 index 0000000..e23f204 --- /dev/null +++ b/features/api/tags.feature @@ -0,0 +1,24 @@ +Feature: Owner-scoped tag catalog + Background: + Given I set the content type to JSON + And there are default users + And I am authenticated as "admin@signalist.app" + + Scenario: Create a tag + When I send a "POST" request to "/api/v1/tags" with body: + """ + {"name":"Technology"} + """ + Then the response status code should be 201 + And the JSON response "name" should equal "Technology" + + Scenario: Empty tag names fail validation + When I send a "POST" request to "/api/v1/tags" with body: + """ + {"name":""} + """ + Then the response status code should be 422 + + Scenario: Foreign tag identifiers cannot expose articles + When I send a "GET" request to "/api/v1/tags/00000000-0000-0000-0000-000000000000/articles" + Then the response status code should be 404 diff --git a/features/api/waitlist.feature b/features/api/waitlist.feature new file mode 100644 index 0000000..ee03e5e --- /dev/null +++ b/features/api/waitlist.feature @@ -0,0 +1,33 @@ +Feature: Double opt-in waitlist + Background: + Given I set the content type to JSON + + Scenario: Accept valid request without revealing enrollment status + When I send a "POST" request to "/api/v1/waitlist" with body: + """ + {"email":"waitlist@example.com", "consent":true} + """ + Then the response status code should be 202 + And the response should be JSON + And the JSON response should contain "message" + + Scenario: Consent is mandatory + When I send a "POST" request to "/api/v1/waitlist" with body: + """ + {"email":"waitlist@example.com", "consent":false} + """ + Then the response status code should be 422 + + Scenario: Email must be valid + When I send a "POST" request to "/api/v1/waitlist" with body: + """ + {"email":"invalid", "consent":true} + """ + Then the response status code should be 422 + + Scenario: Forged withdrawal link is rejected + When I send a "POST" request to "/api/v1/waitlist/00000000-0000-0000-0000-000000000000/withdraw?signature=forged" with body: + """ + {} + """ + Then the response status code should be 400 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a196d64..73bbd82 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -114,12 +114,12 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -128,9 +128,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -138,21 +138,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -169,13 +169,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", - "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -185,14 +185,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -202,37 +202,37 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -252,27 +252,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -280,26 +280,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -350,31 +350,31 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -382,13 +382,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1296,29 +1296,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -2455,9 +2469,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { @@ -2557,31 +2571,31 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", - "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.0.0", + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", - "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.18", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2590,7 +2604,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -2602,26 +2616,26 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", - "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", - "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.18", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -2629,13 +2643,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", - "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.18", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2644,9 +2659,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", - "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -2654,14 +2669,15 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", - "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.18", - "tinyrainbow": "^3.0.3" + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2794,16 +2810,42 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -2827,13 +2869,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.11.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz", + "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/bidi-js": { @@ -2847,9 +2892,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -2857,23 +2902,10 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", "dev": true, "funding": [ { @@ -2891,11 +2923,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -2927,9 +2959,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001815", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz", + "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==", "dev": true, "funding": [ { @@ -2948,9 +2980,9 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -3278,9 +3310,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", - "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz", + "integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -3301,9 +3333,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.283", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", - "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", + "version": "1.5.453", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.453.tgz", + "integrity": "sha512-9r/nz9akllr3SZsHUhfK10ktUH6j0Dbo3cKdh91khw0FvR6HhXGnATmOq5X8ena2aq4GXLQ1koh3nvKaQJaNmQ==", "dev": true, "license": "ISC" }, @@ -3368,16 +3400,16 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3728,19 +3760,6 @@ "node": ">=16.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -3806,16 +3825,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -3981,9 +4000,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4227,16 +4246,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -4258,10 +4267,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4393,19 +4412,18 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", - "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", "dev": true, "license": "MIT", "dependencies": { - "commander": "^14.0.2", + "commander": "^14.0.3", "listr2": "^9.0.5", - "micromatch": "^4.0.8", - "nano-spawn": "^2.0.0", - "pidtree": "^0.6.0", + "picomatch": "^4.0.3", "string-argv": "^0.3.2", - "yaml": "^2.8.1" + "tinyexec": "^1.0.4", + "yaml": "^2.8.2" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -4537,33 +4555,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -4627,23 +4618,10 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/nano-spawn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", - "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" - } - }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -4667,11 +4645,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.58", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz", + "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/object-assign": { "version": "4.1.1", @@ -4863,23 +4844,10 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", "dev": true, "funding": [ { @@ -4897,9 +4865,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -5054,9 +5022,9 @@ } }, "node_modules/react-router": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", - "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz", + "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -5076,12 +5044,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", - "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz", + "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==", "license": "MIT", "dependencies": { - "react-router": "7.13.0" + "react-router": "7.18.4" }, "engines": { "node": ">=20.0.0" @@ -5347,9 +5315,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -5364,9 +5332,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", "dev": true, "license": "MIT" }, @@ -5498,9 +5466,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { @@ -5525,9 +5493,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "dev": true, "license": "MIT", "engines": { @@ -5554,19 +5522,6 @@ "dev": true, "license": "MIT" }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/tough-cookie": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", @@ -5665,9 +5620,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "dev": true, "funding": [ { @@ -5715,13 +5670,13 @@ } }, "node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz", + "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.27.0", + "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -5790,31 +5745,31 @@ } }, "node_modules/vitest": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", - "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.0.18", - "@vitest/mocker": "4.0.18", - "@vitest/pretty-format": "4.0.18", - "@vitest/runner": "4.0.18", - "@vitest/snapshot": "4.0.18", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", - "std-env": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -5830,12 +5785,15 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.18", - "@vitest/browser-preview": "4.0.18", - "@vitest/browser-webdriverio": "4.0.18", - "@vitest/ui": "4.0.18", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -5856,6 +5814,12 @@ "@vitest/browser-webdriverio": { "optional": true }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, "@vitest/ui": { "optional": true }, @@ -5864,6 +5828,9 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, @@ -6016,9 +5983,9 @@ } }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", "dev": true, "license": "MIT", "engines": { diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx new file mode 100644 index 0000000..17a6566 --- /dev/null +++ b/frontend/src/App.test.tsx @@ -0,0 +1,33 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { Link, MemoryRouter } from 'react-router-dom'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import App from './App'; + +const state = vi.hoisted(() => ({ authenticated: true })); +vi.mock('./hooks/useAuth', () => ({ useAuth: () => ({ isAuthenticated: state.authenticated }) })); +vi.mock('./components/Layout/Sidebar', () => ({ default: () => })); +vi.mock('./components/Layout/CommandPalette', () => ({ default: () => null })); +vi.mock('./pages/SettingsPage', () => ({ default: () =>

Account settings route

})); +vi.mock('./pages/NewslettersPage', () => ({ default: () =>

Newsletter route

})); +vi.mock('./pages/LoginPage', () => ({ default: () =>

Sign in route

})); + +describe('On-demand routes', () => { + beforeEach(() => { state.authenticated = true; }); + + it('loads protected pages while retaining application navigation', async () => { + render(); + expect(await screen.findByRole('heading', { name: 'Account settings route' })).toBeInTheDocument(); + const navigation = screen.getByRole('navigation', { name: 'Application navigation' }); + fireEvent.click(screen.getByRole('link', { name: 'Open newsletters' })); + expect(await screen.findByRole('heading', { name: 'Newsletter route' })).toBeInTheDocument(); + expect(screen.getByRole('navigation', { name: 'Application navigation' })).toBe(navigation); + }); + + it('redirects unauthenticated deep links before showing protected content', async () => { + state.authenticated = false; + render(); + expect(await screen.findByRole('heading', { name: 'Sign in route' })).toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: 'Account settings route' })).not.toBeInTheDocument(); + expect(screen.queryByRole('navigation')).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d6553fb..a653bb0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,33 +1,62 @@ +import { lazy, Suspense } from 'react'; import { Routes, Route } from 'react-router-dom'; -import AppLayout from './components/Layout/AppLayout'; import ProtectedRoute from './components/Auth/ProtectedRoute'; -import Feed from './pages/Feed'; -import CategoryPage from './pages/CategoryPage'; -import BookmarksPage from './pages/BookmarksPage'; -import ArticlePage from './pages/ArticlePage'; -import FeedManagementPage from './pages/FeedManagementPage'; -import LoginPage from './pages/LoginPage'; -import RegisterPage from './pages/RegisterPage'; -import CheckEmailPage from './pages/CheckEmailPage'; -import VerifyEmailPage from './pages/VerifyEmailPage'; +import LoadingSpinner from './components/Common/LoadingSpinner'; + +const AppLayout = lazy(() => import('./components/Layout/AppLayout')); +const Feed = lazy(() => import('./pages/Feed')); +const CategoryPage = lazy(() => import('./pages/CategoryPage')); +const BookmarksPage = lazy(() => import('./pages/BookmarksPage')); +const ArticlePage = lazy(() => import('./pages/ArticlePage')); +const FeedManagementPage = lazy(() => import('./pages/FeedManagementPage')); +const LoginPage = lazy(() => import('./pages/LoginPage')); +const RegisterPage = lazy(() => import('./pages/RegisterPage')); +const CheckEmailPage = lazy(() => import('./pages/CheckEmailPage')); +const VerifyEmailPage = lazy(() => import('./pages/VerifyEmailPage')); +const SettingsPage = lazy(() => import('./pages/SettingsPage')); +const WaitlistPage = lazy(() => import('./pages/WaitlistPage')); +const NewslettersPage = lazy(() => import('./pages/NewslettersPage')); +const SearchPage = lazy(() => import('./pages/SearchPage')); +const WorkspacesPage = lazy(() => import('./pages/WorkspacesPage')); +const WorkspacePage = lazy(() => import('./pages/WorkspacePage')); +const WorkspaceArticlePage = lazy(() => import('./pages/WorkspaceArticlePage')); +const InvitationPage = lazy(() => import('./pages/InvitationPage')); +const BillingPage = lazy(() => import('./pages/BillingPage')); +const ConnectionsPage = lazy(() => import('./pages/ConnectionsPage')); +const SocialPage = lazy(() => import('./pages/SocialPage')); +const TagsPage = lazy(() => import('./pages/TagsPage')); function App() { return ( - - } /> - } /> - } /> - } /> - }> - }> - } /> - } /> - } /> - } /> - } /> + }> + + } /> + } /> + } /> + } /> + } /> + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + - - + + ); } diff --git a/frontend/src/api/newsletters.ts b/frontend/src/api/newsletters.ts new file mode 100644 index 0000000..a4e395d --- /dev/null +++ b/frontend/src/api/newsletters.ts @@ -0,0 +1,12 @@ +import apiClient from './client'; +export interface Newsletter { + id: string; title: string; readingMinutes: number; intervalHours: number | null; + nextRunAt: string | null; html: string | null; generatedAt: string | null; lastRunStatus?: string | null; +} +export interface NewsletterInput { title: string; readingMinutes: number; intervalHours: number | null; } +export const newsletterApi = { + list: async (): Promise => (await apiClient.get('/newsletters')).data, + save: async (input: NewsletterInput, id?: string): Promise => (await (id ? apiClient.put(`/newsletters/${id}`, input) : apiClient.post('/newsletters', input))).data, + delete: async (id: string): Promise => { await apiClient.delete(`/newsletters/${id}`); }, + generate: async (id: string, deliver: boolean): Promise<{ runId: string; status: string }> => (await apiClient.post(`/newsletters/${id}/${deliver ? 'send' : 'generate'}`)).data, +}; diff --git a/frontend/src/api/privacy.ts b/frontend/src/api/privacy.ts new file mode 100644 index 0000000..0ec3728 --- /dev/null +++ b/frontend/src/api/privacy.ts @@ -0,0 +1,19 @@ +import apiClient from './client'; + +export type ConsentPurpose = 'external_ai' | 'newsletter' | 'marketing'; +export interface PrivacyProfile { + id: string; + email: string; + displayName: string; + processingRestricted: boolean; + consent: Record; +} + +export const privacyApi = { + profile: async (): Promise => (await apiClient.get('/user/profile')).data, + update: async (displayName: string): Promise => { await apiClient.put('/user/profile', { displayName }); }, + consent: async (purpose: ConsentPurpose, granted: boolean): Promise => { await apiClient.post('/user/consent', { purpose, granted }); }, + restrict: async (restricted: boolean): Promise => { await apiClient.post('/user/restrict-processing', { restricted }); }, + export: async (): Promise => (await apiClient.get('/user/data-export')).data, + delete: async (password: string): Promise => { await apiClient.delete('/user/account', { data: { password } }); }, +}; diff --git a/frontend/src/api/workspaces.ts b/frontend/src/api/workspaces.ts new file mode 100644 index 0000000..89818a6 --- /dev/null +++ b/frontend/src/api/workspaces.ts @@ -0,0 +1,33 @@ +import apiClient from './client'; +import type { Article, Category, Feed, AddFeedInput, CreateCategoryInput, UpdateFeedInput } from '../types'; +export type MemberRole = 'admin' | 'curator' | 'reader'; +export interface Workspace { id: string; name: string; role: MemberRole; } +export interface WorkspaceMember { id: string; name: string; email?: string | null; role: MemberRole; } +export interface SharedBookmark { id: string; article: Article; curator: string | null; annotations: { id: string; body: string; author: string | null; createdAt: string }[]; } +const base = (id: string) => `/organizations/${id}`; +export const workspaceApi = { + list: async (): Promise => (await apiClient.get('/organizations')).data, + create: async (name: string): Promise => (await apiClient.post('/organizations', { name })).data, + get: async (id: string): Promise => (await apiClient.get(base(id))).data, + members: async (id: string): Promise => (await apiClient.get(`${base(id)}/members`)).data, + invite: async (id: string, email: string, role: MemberRole): Promise => { await apiClient.post(`${base(id)}/invitations`, { email, role }); }, + role: async (id: string, memberId: string, role: MemberRole): Promise => { await apiClient.patch(`${base(id)}/members/${memberId}`, { role }); }, + remove: async (id: string, memberId: string): Promise => { await apiClient.delete(`${base(id)}/members/${memberId}`); }, + accept: async (id: string, signature: string): Promise => (await apiClient.post(`/organizations/invitations/${encodeURIComponent(id)}/accept`, null, { params: { signature } })).data, + articles: async (id: string, page: number): Promise => (await apiClient.get(`${base(id)}/articles`, { params: { page } })).data, + article: async (id: string, articleId: string): Promise
=> (await apiClient.get(`${base(id)}/articles/${articleId}`)).data, + read: async (id: string, articleId: string, isRead: boolean): Promise => { await apiClient.patch(`${base(id)}/articles/${articleId}/read`, { isRead }); }, + categories: async (id: string): Promise => (await apiClient.get(`${base(id)}/categories`)).data, + createCategory: async (id: string, input: CreateCategoryInput): Promise => { await apiClient.post(`${base(id)}/categories`, input); }, + updateCategory: async (id: string, categoryId: string, input: CreateCategoryInput): Promise => { await apiClient.put(`${base(id)}/categories/${categoryId}`, input); }, + deleteCategory: async (id: string, categoryId: string): Promise => { await apiClient.delete(`${base(id)}/categories/${categoryId}`); }, + feeds: async (id: string): Promise => (await apiClient.get(`${base(id)}/feeds`)).data, + createFeed: async (id: string, input: AddFeedInput): Promise => { await apiClient.post(`${base(id)}/feeds`, input); }, + updateFeed: async (id: string, feedId: string, input: UpdateFeedInput): Promise => { await apiClient.put(`${base(id)}/feeds/${feedId}`, input); }, + deleteFeed: async (id: string, feedId: string): Promise => { await apiClient.delete(`${base(id)}/feeds/${feedId}`); }, + bookmarks: async (id: string): Promise => (await apiClient.get(`${base(id)}/bookmarks`)).data, + bookmark: async (id: string, articleId: string): Promise => { await apiClient.post(`${base(id)}/bookmarks`, { articleId }); }, + annotate: async (id: string, bookmarkId: string, body: string): Promise => { await apiClient.post(`${base(id)}/bookmarks/${bookmarkId}/annotations`, { body }); }, + removeBookmark: async (id: string, bookmarkId: string): Promise => { await apiClient.delete(`${base(id)}/bookmarks/${bookmarkId}`); }, + activity: async (id: string): Promise<{ action: string; actor: string | null; createdAt: string }[]> => (await apiClient.get(`${base(id)}/activity`)).data, +}; diff --git a/frontend/src/components/Article/ArticleTagsEditor.tsx b/frontend/src/components/Article/ArticleTagsEditor.tsx new file mode 100644 index 0000000..6cc8f19 --- /dev/null +++ b/frontend/src/components/Article/ArticleTagsEditor.tsx @@ -0,0 +1,17 @@ +import { useState } from 'react'; +import { Alert, Autocomplete, Button, Stack, TextField } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import apiClient from '../../api/client'; +import { useTags } from '../../hooks/useTags'; +export default function ArticleTagsEditor({ id, names }: { id: string; names: string[] }) { + const { t } = useTranslation(); const { tags, assign } = useTags(); + const [selected, setSelected] = useState(names); const cache = useQueryClient(); + const enrich = useMutation({ mutationFn: () => apiClient.post(`/articles/${id}/enrich`), onSuccess: () => cache.invalidateQueries({ queryKey: ['article', id] }) }); + return + tag.name) ?? []} value={selected} onChange={(_, value) => setSelected(value)} renderInput={(params) => } /> + + {enrich.isSuccess && {t('tags.queued')}} + {(assign.isError || enrich.isError) && {t('tags.error')}} + ; +} diff --git a/frontend/src/components/Article/ShareMenu.tsx b/frontend/src/components/Article/ShareMenu.tsx new file mode 100644 index 0000000..7cd1d02 --- /dev/null +++ b/frontend/src/components/Article/ShareMenu.tsx @@ -0,0 +1,19 @@ +import { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { Button, Menu, MenuItem } from '@mui/material'; +import ShareIcon from '@mui/icons-material/Share'; +import { useTranslation } from 'react-i18next'; +import { shareLinks } from '../../lib/shareLinks'; +export default function ShareMenu({ title, url }: { title: string; url: string }) { + const { t } = useTranslation(); + const [anchor, setAnchor] = useState(null); + const links = shareLinks(title, url); + if (links.length === 0) return null; + return <> + + setAnchor(null)}> + {links.map((link) => setAnchor(null)}>{link.platform})} + setAnchor(null)}>{t('social.editor')} + + ; +} diff --git a/frontend/src/components/Auth/ProtectedRoute.tsx b/frontend/src/components/Auth/ProtectedRoute.tsx index 6e8193e..00e4802 100644 --- a/frontend/src/components/Auth/ProtectedRoute.tsx +++ b/frontend/src/components/Auth/ProtectedRoute.tsx @@ -1,11 +1,12 @@ -import { Navigate, Outlet } from 'react-router-dom'; +import { Navigate, Outlet, useLocation } from 'react-router-dom'; import { useAuth } from '../../hooks/useAuth'; export default function ProtectedRoute() { const { isAuthenticated } = useAuth(); + const location = useLocation(); if (!isAuthenticated) { - return ; + return ; } return ; diff --git a/frontend/src/components/Layout/AppLayout.test.tsx b/frontend/src/components/Layout/AppLayout.test.tsx new file mode 100644 index 0000000..75c575c --- /dev/null +++ b/frontend/src/components/Layout/AppLayout.test.tsx @@ -0,0 +1,19 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { describe, it, expect, vi } from 'vitest'; +import AppLayout from './AppLayout'; + +vi.mock('./Sidebar', () => ({ default: ({ mobileOpen }: { mobileOpen: boolean }) => mobileOpen ?