Files
pl_mail/composer.json
T
Paul LütznerandClaude Opus 5 972a8a9d3f Give plMail a landing page, built from the repository it describes
A wiki is where somebody already inside GitHub looks. It has no way to be
the thing a link to plMail shows a person who has never seen it, so there
is now a site as well: a landing page and the whole handbook, at
karatektus.github.io/pl_mail, deployed by .github/workflows/pages.yml on
every push to main.

It holds no content. The landing page IS README.md and the handbook IS
docs/, rendered into a shell by bin/build-site.php — nothing on the site
can say something the repository does not, because there is nowhere for
it to be written down. Even the hero is lifted rather than written: its
lead is README's own opening paragraph, because a hero composed here
would be the one piece of prose no file backs and the first thing to go
stale. The wiki mirror stays; two outputs of one source cannot disagree
with each other.

The tree is preserved rather than flattened, unlike the wiki mirror,
which had to flatten because a wiki has one namespace. So a cross-link
between two pages needs only its extension changed and an image reference
needs nothing, and the rewriting that remains is the small honest kind: a
path that escapes docs/ becomes a link into GitHub, because the site
carries the handbook and not the source.

**The theme picker is generated from the app's own enum.** Theme::swatch()
already carries surface, ink and accent per theme for the appearance
picker, and the rest of a palette is derived from those at build time —
so the site offers exactly the seven plMail offers, and adding an eighth
to the app adds it here. Transcribing them would have agreed until
somebody added one, and then the site would quietly offer six of seven.

The language picker lists a locale as available when docs/<locale>/
exists and as "not translated yet" when it does not, which makes
translating the handbook the act of creating that directory and nothing
else. Today that means English, with German and Pirate shown and
disabled — a switcher that silently omitted them would say the site has
no opinion about German, where this says where the gap is.

Search is the whole handbook as 140KB of JSON, fetched on first focus and
matched in the browser. A hosted index would be faster to write and would
stop the site being a directory of files that works from a checkout, from
a fork's Pages, and behind whatever proxy somebody puts it behind.

league/commonmark is a require-dev dependency: the site is built in CI and
never by the application, so a Markdown parser in the runtime image would
be a package nothing runs. Bumped guzzlehttp/guzzle to 7.15.2 while
composer was open — 7.15.1 carries two advisories, one of which is a
noncanonical host bypassing host-based checks, which is the shape of the
SSRF guard.

Also corrects this file's claim that the non-Docker Playwright variants
start `symfony serve`. They have not since that server was replaced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 21:54:19 +02:00

152 lines
5.1 KiB
JSON

{
"type": "project",
"license": "AGPL-3.0-or-later",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.4",
"ext-ctype": "*",
"ext-iconv": "*",
"ext-pcntl": "*",
"doctrine/doctrine-bundle": "^3.3.1",
"doctrine/doctrine-migrations-bundle": "^4.0",
"doctrine/orm": "^3.6",
"dragonmantank/cron-expression": "^3.6",
"endroid/qr-code": "^6.1",
"knplabs/knp-paginator-bundle": "*",
"league/oauth2-client": "^2.9",
"league/oauth2-google": "^5.0",
"lexik/jwt-authentication-bundle": "^3.2",
"minishlink/web-push": "^11.0",
"phpdocumentor/reflection-docblock": "^6.0",
"phpstan/phpdoc-parser": "^2.3.3",
"sabre/vobject": "^4.6",
"scheb/2fa-backup-code": "^8.6",
"scheb/2fa-bundle": "^8.6",
"scheb/2fa-totp": "^8.6",
"scheb/2fa-trusted-device": "^8.6",
"symfony/asset": "8.1.*",
"symfony/asset-mapper": "8.1.*",
"symfony/console": "8.1.*",
"symfony/doctrine-messenger": "8.1.*",
"symfony/dotenv": "8.1.*",
"symfony/expression-language": "8.1.*",
"symfony/flex": "^2",
"symfony/form": "8.1.*",
"symfony/framework-bundle": "8.1.*",
"symfony/html-sanitizer": "8.1.*",
"symfony/http-client": "8.1.*",
"symfony/intl": "8.1.*",
"symfony/mailer": "8.1.*",
"symfony/mercure-bundle": "^0.4.2",
"symfony/mime": "8.1.*",
"symfony/monolog-bundle": "^3.0|^4.0",
"symfony/notifier": "8.1.*",
"symfony/process": "8.1.*",
"symfony/property-access": "8.1.*",
"symfony/property-info": "8.1.*",
"symfony/rate-limiter": "8.1.*",
"symfony/runtime": "8.1.*",
"symfony/scheduler": "8.1.*",
"symfony/security-bundle": "8.1.*",
"symfony/serializer": "8.1.*",
"symfony/stimulus-bundle": "^3.3",
"symfony/string": "8.1.*",
"symfony/translation": "8.1.*",
"symfony/twig-bundle": "8.1.*",
"symfony/uid": "8.1.*",
"symfony/ux-autocomplete": "^3.3",
"symfony/ux-turbo": "^3.3",
"symfony/validator": "8.1.*",
"symfony/web-link": "8.1.*",
"symfony/yaml": "8.1.*",
"symfonycasts/tailwind-bundle": "^0.14.0",
"thenetworg/oauth2-azure": "*",
"tijsverkoyen/css-to-inline-styles": "^2.4",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.28",
"webklex/php-imap": "^6.2"
},
"config": {
"allow-plugins": {
"php-http/discovery": true,
"symfony/flex": true,
"symfony/runtime": true
},
"bump-after-update": true,
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
},
"files": [
"config/bootstrap_generated_secrets.php"
]
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*",
"symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*",
"symfony/polyfill-php82": "*",
"symfony/polyfill-php83": "*",
"symfony/polyfill-php84": "*"
},
"scripts": {
"site": "php bin/build-site.php",
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd",
"importmap:install": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
],
"test": [
"vendor/bin/phpunit"
],
"stan": [
"vendor/bin/phpstan analyse --configuration=phpstan.dist.neon"
]
},
"scripts-descriptions": {
"test": "Run the PHPUnit suite (unit tests). Browser end-to-end tests live in tests/e2e and run via `npm run test:e2e`.",
"site": "Build the landing page and handbook into build/site — the same command GitHub Pages runs. Open build/site/index.html or serve it with `php -S 127.0.0.1:8080 -t build/site`.",
"stan": "Run PHPStan at level 5 against src/ and tests/, ignoring what phpstan-baseline.neon already records."
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "8.1.*"
}
},
"require-dev": {
"league/commonmark": "^2.9",
"phpstan/phpstan": "^2.2",
"phpstan/phpstan-doctrine": "^2.0",
"phpstan/phpstan-symfony": "^2.0",
"phpunit/phpunit": "^13.2.5",
"symfony/browser-kit": "8.1.*",
"symfony/css-selector": "8.1.*",
"symfony/debug-bundle": "8.1.*",
"symfony/maker-bundle": "^1.0",
"symfony/stopwatch": "8.1.*",
"symfony/web-profiler-bundle": "8.1.*"
}
}